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;
}