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分,睡覺去了。

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