1、按tcp/ip協議的描述,tcp三次握手過程,tcp的狀態遷移如下所示:
1)、客戶端通過connect系統調用向處於LISTEN狀態的服務端發送sync請求,客戶端進入SYNC_SEND狀態;
2)、服務端收到sync報文後,向客戶端發送sync+ack報文,服務端進入SYNC_RECV狀態;
3)、客戶端收到sync+ack後,進入ESTABLISHED狀態,並向服務端發送ack,服務端接收到ack後,進入ESTABLISHED狀態。
(sys_connect) sync
client(SYNC_SEND) ----------> server(LISTEN)
sync+ack
client(SYNC_SEND) <---------- server(SYNC_RECV)
ack
client(ESTABLISHED) ----------> server(ESTABLISHED)
2、但Linux的實現跟這裏的描述並非完全一致,在Linux裏,服務端收到sync報文並向客戶端發送sync+ack後,並沒有將自己置位SYNC_RECV狀態,而是申請了一個request_sock,並將request_sock掛到sock的半連接隊列裏,等收到客戶端的ack時,再從半連接隊列裏找到request_sock,並將其置位ESTABLISHED,然後掛到全連接隊列中。
3、相關實現:
client端:
1)、client端調用connect系統調用發送sync報文,最終調用tcp_v4_connect,在tcp_v4_connect裏通過tcp_set_state(sk, TCP_SYN_SENT)將sk狀態置位SYNC_SNET;
2)、client收到server的sync+ack後,將狀態置爲ESTABLISHED。
tcp_v4_rcv
tcp_v4_do_rcv
tcp_rcv_state_process
tcp_rcv_synsent_state_process
tcp_finish_connect
tcp_set_state(sk, TCP_ESTABLISHED)
server端:
tcp_v4_rcv
tcp_v4_do_rcv
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
//收到sync包時,狀態爲TCP_LISTEN,條件成立
if (sk->sk_state == TCP_LISTEN) {
//這裏是從半連接隊列的syn_table裏找request_sock,
//1、如果是sync報文,這裏返回爲空,跳轉到tcp_rcv_state_process
//在函數tcp_rcv_state_process裏會創建一個新的request_sock,然後掛到半連接隊列裏
//具體流程:tcp_rcv_state_process->tcp_v4_conn_request->tcp_conn_request->inet_csk_reqsk_queue_hash_add
//2、如果是client的ack報文,則這裏返回的nsk爲第1步創建的request_sock,並且在tcp_v4_hnd_req->tcp_check_req裏
//將sk->sk_state狀態置位SYNC_RECV,然後把request_sock從半連接隊列裏移除(tcp_check_req->inet_csk_reqsk_queue_removed)
//具體流程:tcp_v4_hnd_req->tcp_check_req->tcp_v4_syn_recv_sock->tcp_create_openreq_child->inet_csk_clone_lock
//(newsk->sk_state = TCP_SYN_RECV)
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
//ack時找到的request_sock與sk不是同一個,條件成立
if (nsk != sk) {
sock_rps_save_rxhash(nsk, skb);
//該函數最終調用tcp_rcv_state_process,在tcp_rcv_state_process裏判斷sk->sk_state狀態爲SYNC_RECV,
//並且收到的是ack報文,然後將狀態置位ESTABLISHED。
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
} else
sock_rps_save_rxhash(sk, skb);
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
return 0;
}