下面的代码实现 echo 回射功能,下面具体分析启动与终止过程。
#include "unp.h" int main(int argc,char**argv) { int listenfd,connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr,servaddr; listenfd=Socket(AF_INET,SOCK_STREAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htosn(SERV_PORT); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); Bind(listenfd,(SA*)&servaddr,sizeof(servaddr)); Listen(listenfd,LISTENQ); for(;;) { clilen=sizeof(cliaddr); connfd=Accept(listenfd,(SA*)&cliaddr,&clilen); if((childpi=fork())==0) { Close(listenfd); str_echo(connfd); exit(0); } Close(connfd); } } void str_echo(int connfd) { ssize_t n; char buf[MAXLINE]; again: while((n=read(connfd,buf,MAXLINE))>0) Write(connfd,buf,n); if(n<0&&errno==EINTR) goto again; // 这里EINTR表示是系统调用中断,若是这种情况 // 则重新调用系统调用。 // else if(n<0) err_sys("str_echo:read error"); } ////////////////////////////////////////////// //client.c int main(int argc,char**argv) { int sockfd; struct sockaddr_in servaddr; if(argc!=2) err_quit("usage:tcpcli<IPaddress>"); sockfd=Socket(AF_IENT,SOCK_STREAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(SERV_PORT); Inet_pton(AF_INET,argv[1],&servaddr.sin_addr); Connect(sockfd,(SA*)&servaddr,sizeof(servaddr)); str_cli(stdin,sockfd); exit(0); } void str_cli(FILE*fp,int sockfd) { char sendline[MAXLINE],recvline[MAXLINE]; while(Fgets(sendline,MAXLIEN,fp)!=NULL) { Write(sockfd,sendline,strlen(sendline)); if(Read(sockfd,recvline,MAXLINE)==0) err_quit("str_cli:server terimnate prematily"); Fputs(recvline,stdout); } }
1,首先,服务器启动,它调用socket,bind ,listen,accept,并阻塞与accept调用,我们还没有启动客户。此时,查看套接字情况netstat
有一个套接字处于LISTEN的状态,
2,然后,启动客户,客户调用socket和connect,后者引发三次握手过程,当三路握手完成后,客户中的connect和accept均返回,于是链接建立。
a,客户调用str_cli函数,该函数阻塞与fgets调用。
b, 服务器accept返回后,调用fork,再由子进程调用str_echo。该函数阻塞与read调用,等待读入数据。
c, 另一方面,服务器父进程再次调用accept并阻塞,等待下一个客户连接。
自此,有三个进程都在眠,客户进程,服务器父进程,服务器子进程。
当三路握手完成后,我们首先列出客户的步骤,然后列出服务器的步骤,是有原因的:客户接受到三路握手的第二个分节(ack+syn)connect就返回了,而服务器要等到三路
握手的第三个分节才返回(ack)即在connect返回之后再过半个RTT才返回。
此时,再次查看套接字状态,可以看到,有两个ESTABLIED,一个LISTEN 态,(因为我们是在同一主机运行客户与服务器的)。
终止:
在客户端,我们键入终端字符EOF字符(ctrl+D)以终止客户,此时,如果立即执行netstat命令(立即,因为过段时间,套接字状态会有变化)。
可以看到,当前链接的客户端套接字进入了TIME_WAIT状态,服务器父进程仍在LISTEN状态。
可以总结出:
1,当我们键入EOF字符释,fgets返回一个空指针,于是str_cli函数返回。
2,当str_cli返回到客户的main函数时候,main函数通过exit终止。
3,进程终止处理的部分工作是关闭所有打开的描述符,当然包括套接字,因此客户打开的套接字由内核关闭。这导致客户tcp发送一个FIN分节给服务器,服务器则ACK响应,
这就是TCP连接终止序列的前半部分。(注意到,这时候的服务器子进程还是存在的),自此,服务器子进程套接字处于CLOSE_WAIT状态,客户套接字则处于FIN_WAIT_2
状态(接受了ACK分节后从FIN_WAIT_1变迁的).
4, 当服务器紫禁城TCP接受到FIN时候,服务器紫禁城阻塞于read调用,于是read返回0,这导致str_echo函数返回服务器子进程的main函数。
5,服务器子进程通过调用exit(0)来终止
5,服务器子进程打开的所有描述符随之关闭。由子进程来关闭已链接套接字会引发TCP链接终止的后两个分节:一个从服务器端发出的FIN后从客户端到服务器的ACK。
自此,连接完全终止,客户套接字进入TIME_WAIT状态(需要等待一段时间才关闭),服务器子进程套接字关闭了。
6,进程终止处理的另一个部分:在服务器子进程终止时,给父进程发送一个SIGCHLD信号。这一点在本例中发生了,但是我们没有捕获处理,既然附近成没有处理
那么紫禁城于是进入僵死状态。 这可以用ps命令看到。
玩