侵删html
转载自 http://blog.csdn.net/amaowolf/article/details/8008575
转载自 http://www.cnblogs.com/jianqiang2010/archive/2010/08/20/1804598.html
转载自 吴秦http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html
转载自http://blog.csdn.net/lgp88/article/details/7176509
转载自http://www.cnblogs.com/ccsccs/articles/4224253.html
转载自http://blog.csdn.net/piaojun_pj/article/details/5991968/
转载自http://www.it.uom.gr/teaching/distrubutedSite/dsIdaLiu/labs/lab2_1/sockets.html设计模式
首要解决的问题是如何惟一标识一个进程,在本地能够经过进程PID来惟一标识一个进程,可是在网络中这是行不通的。其实TCP/IP协议族已经帮咱们解决了这个问题,网络层的ip地址能够惟一标识网络中的主机,而传输层的协议+端口能够惟一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就能够标识网络的进程了,网络中的进程通讯就能够利用这个标志与其它进程进行交互。服务器
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,均可以用“打开open –> 读写write/read –> 关闭close”模式来操做。个人理解就是Socket就是该模式的一个实现,socket便是一种特殊的文件,一些socket函数就是对其进行的操做(读/写IO、打开、关闭)。
说白了Socket是应用层与TCP/IP协议族通讯的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来讲,一组简单的接口就是所有,让Socket去组织数据,以符合指定的协议。 网络
int socket(int domain, int type, int protocol);
socket函数对应于普通文件的打开操做。普通文件的打开操做返回一个文件描述字,而socket()用于建立一个socket描述符(socket descriptor),它惟一标识一个socket。这个socket描述字跟文件描述字同样,后续的操做都有用到它,把它做为参数,经过它来进行一些读写操做。数据结构
正如能够给fopen的传入不一样参数值,以打开不一样的文件。建立socket的时候,也能够指定不一样的参数建立不一样的socket描述符,socket函数的三个参数分别为:
•domain:即协议域,又称为协议族(family)。经常使用的协议族有,AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通讯中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名做为地址。
•type:指定socket类型。经常使用的socket类型有,SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
•protocol:就是指定协议。经常使用的协议有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。并发
注意:并非上面的type和protocol能够随意组合的,如SOCK_STREAM不能够跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。dom
SOCK_STREAM式套接字的通讯双方均须要具备地址,其中服务器端的地址须要明确指定,ipv4的指定方法是使用 struct sockaddr_in类型的变量。异步
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。 servaddr.sin_port = htons(DEFAULT_PORT);//设置的端口
INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不肯定地址,或“全部地址”、“任意地址”。也就是表示本机的全部IP,由于有些机子不止一块网卡,多网卡的状况下,这个就表示全部网卡ip地址的意思。客户端connect时,不能使用INADDR_ANY选项。必须指明要链接哪一个服务器IP。socket
htons将主机的无符号短整形数转换成网络字节顺序
htonl将主机的无符号长整形数转换成网络字节顺序tcp
网络字节序与主机字节序:
主机字节序就是咱们日常说的大端和小端模式:不一样的CPU有不一样的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫作主机序。引用标准的Big-Endian和Little-Endian的定义以下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
网络字节序:4个字节的32 bit值如下面的次序传输:首先是0~7bit,其次8~15bit,而后16~23bit,最后是24~31bit。这种传输次序称做大端字节序。因为TCP/IP首部中全部的二进制整数在网络中传输时都要求以这种次序,所以它又称做网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。
在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序同样使用的是Big-Endian。谨记对主机字节序不要作任何假定,务必将其转化为网络字节序再赋给socket。
当咱们调用socket建立一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。若是想要给它赋值一个地址,就必须调用bind()函数,不然就当调用connect()、listen()时系统会自动随机分配一个端口。
bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数的三个参数分别为:
•sockfd:即socket描述字,它是经过socket()函数建立了,惟一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
•addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
struct sockaddr{ sa_family_t sin_family; //地址族(Address Family),也就是地址类型 char sa_data[14]; //IP地址和端口号 };
sockaddr 是一种通用的结构体,能够用来保存多种类型的IP地址和端口号。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,但没有相关函数将这个字符串转换成须要的形式,也就很难给 sockaddr 类型的变量赋值。正是因为通用结构体 sockaddr 使用不便,才针对不一样的地址类型定义了不一样的结构体。 如ipv6对应的是:
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
Unix域对应的是:
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
•addrlen:对应的是地址的长度。
一般服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就能够经过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为何一般服务器端在listen以前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
若是做为一个服务器,在调用socket()、bind()以后就会调用listen()来监听这个socket,若是客户端这时调用connect()发出链接请求,服务器端就会接收到这个请求。
int listen(int sockfd, int backlog); int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket能够排队的最大链接个数。socket()函数建立的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的链接请求。
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端经过调用connect函数来创建与TCP服务器的链接。
TCP服务器端依次调用socket()、bind()、listen()以后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()以后就向TCP服务器发送了一个链接请求。TCP服务器监听到这个请求以后,就会调用accept()函数取接收请求,这样链接就创建好了。以后就能够开始网络I/O操做了,即类同于普通文件的读写I/O操做。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为客户端协议地址的长度。若是accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,表明与返回客户的TCP链接。
注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已链接的socket描述字。一个服务器一般一般仅仅只建立一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每一个由服务器进程接受的客户链接建立了一个已链接socket描述字,当服务器完成了对某个客户的服务,相应的已链接socket描述字就被关闭。
至此服务器与客户已经创建好链接了。能够调用网络I/O进行读写操做了,即实现了网咯中不一样进程之间的通讯!网络I/O操做有下面几组:
•read()/write()
•recv()/send()
•readv()/writev()
•recvmsg()/sendmsg()
•recvfrom()/sendto()
它们的声明以下:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read函数是负责从fd中读取内容。当读成功时,read返回实际所读的字节数,若是返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。若是错误为EINTR说明读是由中断引发的,若是是ECONNREST表示网络链接出了问题。
write函数将buf中的nbytes字节内容写入文件描述符fd。成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当咱们向套接字文件描述符写时有两种可能。1)write的返回值大于0,表示写了部分或者是所有的数据。2)返回的值小于0,此时出现了错误。咱们要根据错误类型来处理。若是错误为EINTR表示在写的时候出现了中断错误。若是为EPIPE表示网络链接出现了问题(对方已经关闭了链接)。
recv函数和send函数提供了read和write函数同样的功能,不一样的是他们提供了四个参数。前面的三个参数和read、write函数是同样的。第四个参数能够是0或者是如下组合:
MSG_DONTROUTE:不查找表,是send函数使用的标志,这个标志告诉IP,目的主机在本地网络上,没有必要查找表,这个标志通常用在网络诊断和路由程序里面。
MSG_OOB:表示能够接收和发送带外数据。
MSG_PEEK:查看数据,并不从系统缓冲区移走数据。是recv函数使用的标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容。这样在下次读取的时候,依然是同样的内容,通常在有个进程读写数据的时候使用这个标志。
MSG_WAITALL:等待全部数据,是recv函数的使用标志,表示等到全部的信息到达时才返回,使用这个标志的时候,recv返回一直阻塞,直到指定的条件知足时,或者是发生了错误。
这里只描述同步Socket的send函数的执行流程。当调用该函数时,
(1)send先比较待发送数据的长度len和套接字s的发送缓冲的长度, 若是len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
(2)若是len小于或者等于s的发送缓冲区的长度,那么send先检查协议s的发送缓冲中的数据是否正在发送,若是是就等待协议把数据发送完,若是协议尚未开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len
(3)若是len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完
(4)若是len小于剩余 空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并非send把s的发送缓冲中的数据传到链接的另外一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。
若是send函数copy数据成功,就返回实际copy的字节数,若是send在copy数据时出现错误,那么send就返回SOCKET_ERROR;若是send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
注意:send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,可是此时这些数据并不必定立刻被传到链接的另外一端。若是协议在后续的传送过程当中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。(每个除send外的socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,若是在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)
注意:在Unix系统下,若是send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
经过测试发现,异步socket的send函数在网络刚刚断开时还能发送返回相应的字节数,同时使用select检测也是可写的,可是过几秒钟以后,再send就会出错了,返回-1。select也不能检测出可写了。
这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,
(1)recv先等待s的发送缓冲中的数据被协议传送完毕,若是协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,
(2)若是s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,若是s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,因此 在这种状况下要调用几回recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。若是recv在copy时出错,那么它返回SOCKET_ERROR;若是recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:在Unix系统下,若是recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,若是事件没有发生,进程或线程就被阻塞,函数不能当即返回)。但是使用Select就能够完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时没必要非要等待事件的发生,一旦执行确定返回,以返回值的不一样来反映函数的执行状况,若是事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,因此效率较高)方式工做的程序,它可以监视咱们须要监视的文件描述符的变化状况——读写或是异常。
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
struct fd_set能够理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,fd_set集合能够经过一些宏由人为来操做。
FD_ZERO(fd_set *set); //Clear all entries from the set. FD_SET(int fd, fd_set *set); //Add fd to the set. FD_CLR(int fd, fd_set *set); //Remove fd from the set. FD_ISSET(int fd, fd_set *set); //Return true if fd is in the set.
struct timeval表明时间值。
struct timeval { int tv_sec; //seconds int tv_usec; //microseconds,注意这里是微秒不是毫秒 };
int maxfdp是一个整数值,是指集合中全部文件描述符的范围,即全部文件描述符的最大值加1。
fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,咱们是要监视这些文件描述符的读变化的,即咱们关心是否能够从这些文件中读取数据了,若是这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,若是没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。能够传入NULL值,表示不关心任何文件的读变化。
fd_set * writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,咱们是要监视这些文件描述符的写变化的,即咱们关心是否能够向这些文件中写入数据了,若是这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,若是没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。能够传入NULL值,表示不关心任何文件的写变化。
fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval * timeout是select的超时时间,这个参数相当重要,它可使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,必定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,无论文件描述符是否有变化,都马上返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间以内有事件到来就返回了,不然在超时后无论怎样必定返回,返回值同上述。
返回值:返回状态发生变化的描述符总数。 负值:select错误 ;正值:某些文件可读写或出错 ;0:等待超时,没有可读写或错误的文件
理解select模型:
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit能够对应一个文件描述符fd。则1字节长的fd_set最大能够对应8个fd。
(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,NULL,NULL)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
基于上面的讨论,能够轻松得出select模型的特色:
(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。听说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。
(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array做为源数据和fd_set进行FD_ISSET判断。二是select返回后会把之前加入的但并没有事件发生的fd清空,则每次开始 select前都要从新从array取得fd逐一加入(FD_ZERO最早),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。
使用select和non-blocking实现server处理多client实例
int close(int sockfd);
close 一个套接字的默认行为是把套接字标记为已关闭,而后当即返回到调用进程,该套接字描述符不能再由调用进程使用,也就是说它不能再做为read或write的第一个参数,然而TCP将尝试发送已排队等待发送到对端,发送完毕后发生的是正常的TCP链接终止序列。
在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共享着的进程个数,当父进程或某一子进程close掉套接字时,描述符引用计数会相应的减一,当引用计数仍大于零时,这个close调用就不会引起TCP的四路握手断连过程。
int shutdown(int sockfd,int howto);
该函数的行为依赖于howto的值
SHUT_RD:值为0,关闭链接的读这一半。
SHUT_WR:值为1,关闭链接的写这一半。
SHUT_RDWR:值为2,链接的读和写都关闭。
终止网络链接的通用方法是调用close函数。但使用shutdown能更好的控制断连过程(使用第二个参数)。
close与shutdown的区别主要表如今:
close函数会关闭套接字ID,若是有其余的进程共享着这个套接字,那么它仍然是打开的,这个链接仍然能够用来读和写,而且有时候这是很是重要的 ,特别是对于多进程并发服务器来讲。
而shutdown会切断进程共享的套接字的全部链接,无论这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。
咱们知道tcp创建链接要进行“三次握手”,即交换三个分组。大体流程以下:
•客户端向服务器发送一个SYN J
•服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
•客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,可是这个三次握手发生在socket的哪几个函数中:
从图中能够看出,当客户端调用connect时,触发了链接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到链接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1以后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,链接创建。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
上面介绍了socket中TCP的三次握手创建过程,及其涉及的socket函数。如今咱们介绍socket中的四次握手释放链接的过程,请看下图:
图示过程以下:
•某个应用进程首先调用close主动关闭链接,这时TCP发送一个FIN M;
•另外一端接收到FIN M以后,执行被动关闭,对这个FIN进行确认。它的接收也做为文件结束符传递给应用进程,由于FIN的接收意味着应用进程在相应的链接上再也接收不到额外数据;
•一段时间以后,接收到文件结束符的应用进程调用close关闭它的socket。这致使它的TCP也发送一个FIN N;
•接收到这个FIN的源发送端TCP对它进行确认。
这样每一个方向上都有一个FIN和ACK。
CLIENT
#include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <netdb.h> #define SERV_TCP_PORT 8000 /* server's port */ int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in serv_addr; char *serv_host = "localhost"; struct hostent *host_ptr; int port; int buff_size = 0; /* command line: client [host [port]]*/ if(argc >= 2) serv_host = argv[1]; /* read the host if provided */ if(argc == 3) sscanf(argv[2], "%d", &port); /* read the port if provided */ else port = SERV_TCP_PORT; /* get the address of the host */ if((host_ptr = gethostbyname(serv_host)) == NULL) { perror("gethostbyname error"); exit(1); } if(host_ptr->h_addrtype != AF_INET) { perror("unknown address type"); exit(1); } bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = ((struct in_addr *)host_ptr->h_addr_list[0])->s_addr; serv_addr.sin_port = htons(port); /* open a TCP socket */ if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("can't open stream socket"); exit(1); } /* connect to the server */ if(connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { perror("can't connect to server"); exit(1); } /* write a message to the server */ write(sockfd, "hello world", sizeof("hello world")); close(sockfd); }
SERVER
#include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/tcp.h> #include <arpa/inet.h> #define SERV_TCP_PORT 8000 /* server's port number */ #define MAX_SIZE 80 int main(int argc, char *argv[]) { int sockfd, newsockfd, clilen; struct sockaddr_in cli_addr, serv_addr; int port; char string[MAX_SIZE]; int len; /* command line: server [port_number] */ if(argc == 2) sscanf(argv[1], "%d", &port); /* read the port number if provided */ else port = SERV_TCP_PORT; /* open a TCP socket (an Internet stream socket) */ if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("can't open stream socket"); exit(1); } /* bind the local address, so that the cliend can send to server */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(port); if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { perror("can't bind local address"); exit(1); } /* listen to the socket */ listen(sockfd, 5); for(;;) { /* wait for a connection from a client; this is an iterative server */ clilen = sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if(newsockfd < 0) { perror("can't bind local address"); } /* read a message from the client */ len = read(newsockfd, string, MAX_SIZE); /* make sure it's a proper string */ string[len] = 0; printf("%s\n", string); close(newsockfd); } }