到現在,我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賦值
上面左圖是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
上面左圖是當第一個鏈接成功建立後的狀態。注意新建立的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;
…
}
當第二個鏈接成功建立以後,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號元素又空出來了
}
好了,抄完了。23:59分,睡覺去了。