以下基于linux內(nèi)核2.4.0源碼(轉(zhuǎn)自www.yuanma.org/)
以前一直使用的網(wǎng)絡(luò)通訊的函數(shù)都是工作在阻塞模式。在看connect實(shí)現(xiàn)源碼時(shí),突然想到tcp/ip的
三次握手在內(nèi)核如何實(shí)現(xiàn)的,尤其是在非阻塞模式下式,涉及到等待對(duì)端回送ack包,而本端又要立即返
回,想來(lái)這種實(shí)現(xiàn)肯定是遵循某種規(guī)則或是將所有的相關(guān)函數(shù)組合起來(lái)。
查看一些網(wǎng)絡(luò)通信書(shū)籍,可知果然如此。應(yīng)用編程如果設(shè)置為非阻塞模式,則連接時(shí),connect發(fā)送
SYN包后立即返回-EINPROGRESS,表示操作正在處理中;隨后應(yīng)用可以在connect返回后做一些其它的處理,
最后在select函數(shù)中來(lái)捕獲socket的連接、讀寫(xiě)、異常事件以觸發(fā)相關(guān)操作,下面我們看看內(nèi)核中的相關(guān)
實(shí)現(xiàn):
一、tcp/ip連接的三次握手過(guò)程:
client SYN包---> server
client <---ACK包 server
client ACK包---> server
二、客戶(hù)端支持
client發(fā)送2個(gè)包,一個(gè)SYN包,一個(gè)對(duì)服務(wù)器的響應(yīng)ACK包。
client函數(shù)調(diào)用鏈:connect-->sys_connect->inet_stream_connect->tcp_connect...
看inet_stream_connect中實(shí)現(xiàn)的部分代碼段:
...
switch (sock->state) {
...
/*此處調(diào)用tcp_connect函數(shù)發(fā)送SYN包*/
err = sk->prot->connect(sk, uaddr, addr_len);
if (err < 0) //出錯(cuò)則退出
goto out;
sock->state = SS_CONNECTING;
/* 此處僅設(shè)置socket的狀態(tài)為SS_CONNECTING表示連接狀態(tài)正在處理;
* 不同之處在于非阻塞情況下,返回值設(shè)置為-EINPROGRESS表示操作正在處理
* 而阻塞式情況則在獲得ACK包后將返回值置為-EALREADY.
*/
err = -EINPROGRESS;
break;
}
timeo = sock_sndtimeo(sk, flags&O_NONBLOCK); //注意,如果此時(shí)設(shè)置了非阻塞選項(xiàng),則timeo返回0
//如果socket對(duì)應(yīng)的sock狀態(tài)是SYN包已發(fā)送或收到SYN包并發(fā)送了ACK包,并等待對(duì)端發(fā)送第三此的ACK包
if ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV)) {
/* 錯(cuò)誤返回碼err前面已經(jīng)設(shè)置 */
if (!timeo || !inet_wait_for_connect(sk, timeo))
/*注意上面所判斷的2中情況,1、如果是非阻塞模式,則!timeo為1,則直接跳到out返回-EINPROGRESS結(jié)束connect函數(shù)
2、若為阻塞模式,則在inet_wait_for_connect函數(shù)中通過(guò)schedule_timeout函數(shù)放棄cpu控制權(quán)睡眠,等待服務(wù)器端
發(fā)送ACK響應(yīng)包后被喚醒繼續(xù)處理。如果沒(méi)有異常出現(xiàn),則置socket狀態(tài)為SS_CONNECTED,表示連接成功,正確返回
*/
goto out;
err = sock_intr_errno(timeo);
if (signal_pending(current)) /*處理未決信號(hào)*/
goto out;
}
...
sock->state = SS_CONNECTED;
err = 0;
out:
release_sock(sk);
return err;
...
上面的描述有一個(gè)問(wèn)題:對(duì)服務(wù)器的響應(yīng)ACK包是什么時(shí)候發(fā)送的?對(duì)于非阻塞模式,應(yīng)該是應(yīng)用處理過(guò)程中
的某個(gè)異步時(shí)間;對(duì)于阻塞模式,則是在inet_wait_for_connect函數(shù)中睡眠時(shí)處理。
即網(wǎng)卡在收到對(duì)方的ack包后,上傳給對(duì)應(yīng)的socket時(shí)發(fā)送服務(wù)器的響應(yīng)ACK包
函數(shù)調(diào)用鏈為:netif_rx-->net_rx_action-->...(IP層處理)-->tcp_v4_rcv-->tcp_v4_do_rcv-->
tcp_rcv_state_process-->tcp_rcv_synsent_state_process-->tcp_send_synack-->tcp_transmit_skb...
發(fā)送SYN包后,socket對(duì)應(yīng)的sock的狀態(tài)變成TCPF_SYN_SENT,網(wǎng)卡收到服務(wù)器的ack傳到tcp層時(shí),根據(jù)TCPF_SYN_SENT
狀態(tài),做相關(guān)判斷后再發(fā)送用于第三次握手的ack包。至此,將socket的狀態(tài)改為連接建立,即TCP_ESTABLISHED。
具體的代碼大家可以根據(jù)我提供的函數(shù)調(diào)用鏈查看。
注意,以TCPF_前綴開(kāi)頭的狀態(tài)都表示是中間狀態(tài),而已TCP_為前綴的狀態(tài)才是socket的一個(gè)相對(duì)穩(wěn)定的狀態(tài)。
這里有一個(gè)疑問(wèn),,根據(jù)接收處理源碼,先前的SYN包應(yīng)該發(fā)送給服務(wù)器的監(jiān)聽(tīng)socket,而第三次握手似乎應(yīng)該發(fā)送給
連接(未真正連接,因?yàn)槿挝帐诌€沒(méi)完成呢)的socket,這個(gè)問(wèn)題有待進(jìn)一步確認(rèn)
三、服務(wù)器端支持
服務(wù)器端此時(shí)必須是監(jiān)聽(tīng)狀態(tài),則其函數(shù)調(diào)用鏈為:
netif_rx-->net_rx_action-->...(IP層處理)-->tcp_v4_rcv-->tcp_v4_do_rcv-->
tcp_rcv_state_process-->tcp_v4_conn_request-->tcp_v4_send_synack...
在tcp_v4_conn_request,中部分代碼如下:
...
case TCP_LISTEN:
if(th->ack) /*監(jiān)聽(tīng)時(shí)收到的ack包都丟棄?*/
return 1;
if(th->syn) {/*如果是SYN包,則調(diào)用tcp_v4_conn_request*/
if(tp->af_specific->conn_request(sk, skb) < 0)
return 1;
...
四、后記
這里只是給了一個(gè)tcp/ip三次握手原理的實(shí)現(xiàn)框架簡(jiǎn)述,結(jié)合相關(guān)理論教材和這個(gè)流程,仔細(xì)閱讀
linux內(nèi)核對(duì)tcp/ip的實(shí)現(xiàn),對(duì)自己理解tcp/ip的精髓不無(wú)裨益,期待著與大家一起進(jìn)步。
聯(lián)系客服