TCP状态测试(CS模型改进3)

来源:互联网 发布:淘宝直通车效果怎么样 编辑:程序博客网 时间:2024/06/05 02:50

本文测试TCP的11种状态,更深入理解CS模型。

这里写图片描述

尽管我们TCP程序很小,然而对于我们弄清客户和服务器如何启动,如何终止,更为重要的是当发生某些错误(例如客户主机崩溃,客户进程崩溃,网络连接断开,等等)时将会发生什么,本例十分重要。只有弄清这些边界条件以及TCP/IP协议的相互作用,我们才能写出能够处理这些情况的健壮的客户和服务器程序。

(1) 启动服务器

这里写图片描述
服务器启动后,它调用socket,bind,listen和accept,并阻塞于accept调用。(我们还没有启动客户端)
我们检查服务器监听套接字的状态。
这里写图片描述
处于LISTEN状态

(2)启动客户端

这里写图片描述
客户调用socket和connect,后者引起TCP的三次握手过程。当三次握手完成后,客户connect和服务器accept均返回,连接建立。接下来发生的步骤:
(1)客户调用str_cli函数,该函数将阻塞fgets调用,因为我们还未曾键入过一行文本。
(2)当服务器中accept返回时,服务器调用fork,再由子进程调用str_echo。该函数调用readline,readline调用read,而read在等待客户送入一行文本期间阻塞。
(3)另一方面,服务器父进程再次调用accept并阻塞,等待下一个客户连接。
目前,我们有三个进程都在休眠:客户进程,服务器父进程,服务器子进程。
这里写图片描述
此时处于连接状态。

(3)正常终止

此时连接建立,不论我们在客户端的便准输入中输入什么,都会回射到它的标准输出中。
这里写图片描述
接着输入ctrl+d

这里写图片描述
客户端处于TIME_WAIT状态。服务器端LISTEN
我们可以总结出正常终止客户和服务器的步骤:
(1)当我们键入EOF字符(ctrl+d),fgets返回一个空指针,于是srt_cli函数返回。
(2)当str_cli函数返回到客户的main函数,main调用exit终止。
(3)进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字由内核关闭。这导致客户TCP发送一个FIN给服务器,服务器TCP则以ACK相应,这就是TCP连接终止序列的前半部分。至此,服务器套接字处于CLOSE_WAIT状态,客户套接字处于FIN_WAIT_2状态。
(4)当服务器TCP接收FIN时,服务器子进程阻塞于readline调用,返回0,导致str_echo函数返回服务器子进程的main函数。
(5)服务器子进程通过调用exit来终止。
(6)服务器子进程中打开的所有描述符随之关闭。由子进程来关闭已连接套接字会引发TCP连接终止序列的最后两个分节:一个服务器到客户的FIN和一个客户到服务器的ACK。之此,连接完全终止,客户套接字进入TIME_WAIT状态。
(7)进程终止处理的另一部分内容:在服务器子进程终止时,给父进程发送SIGCHLD信号。但是我们并没有捕获该信号,而该信号的默认行为是被忽略。既然父进程未加处理,子进程于是进入僵死状态。
这里写图片描述
在下一篇博客讲解信号处理。
本篇代码:

服务器端

#include<unistd.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<time.h>#include<stdio.h>#include<stdlib.h>#include<errno.h>#include<string.h>#define ERR_EXIT(m) \    do \    { \        perror(m); \        exit(EXIT_FAILURE); \    } while(0)ssize_t writen(int fd,void *buf,size_t count){        size_t nleft=count;//剩余发送字节数        ssize_t nwritten;//已经发送字节数        char *bufp=(char *)buf;        while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞        {                if((nwritten=write(fd,bufp,nleft))<0)                {                        if(errno==EINTR)//被中断                                continue;                        return -1;                }                else if(nwritten==0)//对等方关闭                        continue;//读到EOF,对方关闭                bufp+=nwritten;                nleft-=nwritten;        }        return count;}void str_echo(int sockfd){    char recvbuf[1024];        while(1){        memset(recvbuf,0,sizeof(recvbuf));//初始化recvbuf                int ret=read(sockfd,recvbuf,1024);//一行一行接收数据。        if(ret==-1)        {            ERR_EXIT("readine");        }        if(ret==0)        {            printf("client close\n");            break;        }           fputs(recvbuf,stdout);//读取一行数据,就将其输出到标准输出                writen(sockfd,recvbuf,strlen(recvbuf));//buf中数据被复制到了TCP发送缓冲区        }}int main(){    int listenfd,connfd;    /*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)        ERR_EXIT("socket");    //IPV4地址结构    struct sockaddr_in servaddr,cliaddr;    memset(&servaddr,0,sizeof(servaddr));    servaddr.sin_family=AF_INET;//地址家族    servaddr.sin_port=htons(5188);//端口,主机转网络    /*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");*/    inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)        ERR_EXIT("bind");    if(listen(listenfd,SOMAXCONN)<0)        ERR_EXIT("listen");    pid_t pid;    socklen_t clilen =sizeof(cliaddr);    while(1){        if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&clilen))<0)                    ERR_EXIT("accept");         pid=fork();        if(pid==-1)                ERR_EXIT("fork");            if(pid==0)            {                    close(listenfd);                    str_echo(connfd);                    exit(EXIT_SUCCESS);//将子进程退出,要不它会fork()            }            else                    close(connfd);          }    return 0;}

客户端

#include<unistd.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdio.h>#include<stdlib.h>#include<errno.h>#include<string.h>#define ERR_EXIT(m) \    do \    { \        perror(m); \        exit(EXIT_FAILURE); \    } while(0)ssize_t writen(int fd,void *buf,size_t count){        size_t nleft=count;//剩余发送字节数        ssize_t nwritten;//已经发送字节数        char *bufp=(char *)buf;        while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞        {                if((nwritten=write(fd,bufp,nleft))<0)                {                        if(errno==EINTR)//被中断                                continue;                        return -1;                }                else if(nwritten==0)//对等方关闭                        continue;//读到EOF,对方关闭                bufp+=nwritten;                nleft-=nwritten;        }        return count;}ssize_t readline(int sockfd,void *buf,size_t maxline){        ssize_t n,rc;        char c,*ptr;        ptr=buf;        for(n=1;n<maxline;n++){        again:                if((rc=read(sockfd,&c,1))==1){                        *ptr++=c;                        if(c=='\n')                                break;                }else if(rc==0){                        *ptr=0;                        return(n-1);                }else{                        if(errno==EINTR)                                goto again;                        return -1;                }        }        *ptr=0;        return n;}void str_cli(FILE *fp,int sockfd){    char sendline[1024]={0};    char recvline[1024]={0};    while(fgets(sendline,sizeof(sendline),fp)!=NULL){        writen(sockfd,&sendline,strlen(sendline));        if(readline(sockfd,recvline,sizeof(recvline))==0)            ERR_EXIT("readline");        fputs(recvline,stdout);        memset(sendline,0,sizeof(sendline));        memset(recvline,0,sizeof(recvline));    }}int main(){    int sockfd;    /*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */    if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)        ERR_EXIT("socket");    //IPV4地址结构    struct sockaddr_in servaddr;    memset(&servaddr,0,sizeof(servaddr));    servaddr.sin_family=AF_INET;//地址家族    servaddr.sin_port=htons(5188);//端口,主机转网络    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");    /*inet_aton("127.0.0.1",&servaddr.sin_addr);*/    if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)        ERR_EXIT("connect");    str_cli(stdin,sockfd);    exit(0);    return 0;}