TCP Echo Server(select)

到现在,我Unix,数据库的扫盲基本完成了,就差网络这一块了。从今天开始网络扫盲。上周五开始看HTTPD的实现,我们的httpd是AA(AA是我们SBE的元老之一)开发的,只要是AA开发的东西,学习一遍都很有收获。因为AA是一位非常优秀的架构师,而且自己写过很多代码。

上周五开林和徐辰已经跟我讲过一篇大概的流程了,可我直接看代码还是有点吃力。于是我下载了<Unix Network Programming– Volume1: The Sockets Networking API>, 周末翻一一些,看到下面这些图示,觉得很清楚,在这里抄袭一遍。

这里用的是select实现:
int select(int maxfdp1, fd_set * readset, fd_set * writeset, fd_set * exceptset, const struct timeval * timeout) ;
大概的意思就是select会检查ID从0到maxfdp1-1的句柄(我们可以把句柄理解成一个打开的I/O设备),我们事先在readset/writeset/exceptset中把自己感兴趣的I/O操作置位。select调用会逐一检查看看有没有任何我们感兴趣的I/O发生,如果在句柄ID=4对应的点有数据到达,就会把readset中的第4位 bit置1…如果所有感兴趣的句柄上都没有I/O发生,select会阻塞等待(timeout==NULL)。当select返回的时候,程序只要检查readset/writeset就可以知道是哪个句柄上发生了什么操作了。

这里注意一点readset/writeset/exceptset都是传入的指针,程序员手动将自己感兴趣的I/O bit置位。但是select会改动那些bit位。所以每次调用select之前,我们都要重新这些set赋值

image image

上面左图是server上还没有链接建立前的状态,server打开了一个socket句柄正在监听一个端口。中图表示程序维护了一个句柄数组client[FD_SETSIZE]用来记录所有成功建立链接的socket句柄,数组的长度也就限定了程序能够建立链接的上限,开始我们把所有的数组元素全部初始化成-1,表示这个位置可用,当连接建立以后可以在值为-1的位置填入真正的句柄ID。如果我们是在forground启动server的话,句柄0,1,2已经被预留成标准输入,标准输出和标准错误输出,所以listen句柄ID=3。rset=readset–将第3个bit置1。

maxfd = listenfd;  //listenfd是打开的监听句柄
maxi = -1;          //maxi表示在client数组中ID最大的链接句柄的位置,开始的时候没有链接,所以maxi=-1
for (i = 0; i < FD_SETSIZE; i++) client[i] = -1;
FD_ZERO(&readset); //初始化清零
FD_SET(listenfd, &readset); //如果listenfd=3,将readset中的第3个bit置1

 

image   image

上面左图是当第一个链接成功建立后的状态。注意新建立的connected句柄跟原来的listening句柄是不同的句柄!假设connected句柄ID=4,右图显示正确链接后client和readset的状态:

nready = select(maxfd + 1, &readset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &rset))
{
     connfd = accept(listenfd, (SA *) &cliaddr, &clilen); //拿到新的链接句柄connfd
     for (i = 0; i < FD_SETSIZE; i++) //在client数组中找到第一个空闲的位置,把connfd保存起来
    {
         if (client[i] < 0) //client[i]<0表示这个位置可用
        {
          client[i] = connfd;
          break;
        }
     }
     FD_SET(connfd, &readset); //手动置位,应为在第一次连接建立之前maxfd=3,select根本就不看readset中>3的比特位
     if (connfd > maxfd) maxfd = connfd;
     if (i > maxi)           maxi = i;
     …

}

image image

当第二个链接成功建立以后,server上的状态如上图。3个句柄,listening句柄,还有两个保存在client中的链接句柄。再次强调一下,server端的listen句柄上得到client端的connect请求后,我们每次调用accept就是打开一个新的connect句柄,意味着新建立的链接是全新的,和listen句柄一点关系也没有!

下面看看,如果client端断开连接(假设是connection1=4对应的链接: client[0]=4),client会发送FIN到server,使得client[0]=4对应的I/O可读,于是
sockfd=client[0];
if ( (n = read(sockfd, buf, MAXLINE)) == 0)  //如果read=0,很有可能是读到FIN了(其实这里是个攻击漏洞)
{
     close(sockfd);
     FD_CLR(sockfd, &readset); //将readset中的第4个bit清零
     client[0] = -1;                 //client中的0号元素又空出来了
}

 image

好了,抄完了。23:59分,睡觉去了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章