在tcp_v4_do_rcv中,有下面一段代码,是关于TCP衔接树立时分的代码:
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
if (nsk != sk) {
if (tcp_child_process(sk, nsk, skb))
goto reset;
return 0;
}
}
tcp_v4_hnd_req的回来值,不同状况下不同。
NULL 呈现过错
nsk==sk 接受到SYN
nsk!=sk 接受到ACK
接受到ACK包时,tcp_v4_hnd_req函数会新建一个sock结构,并设置其初始状况为SYN_RECV,并回来新建的sock结构。
接着调用tcp_child_process函数,改动新建的sock的状况为ESTABLISHED。
(以下依据linux内核2.4.0)
SYN_RECV状况,望文生义,是收到SYN包后应该置的状况。关于SYN_RECV状况,受某些教科书的误导,我曾经一向理解为服务器收到SYN包后应该置此状况。也没细想到底是置那个socket的状况,最近在看三次握手协议在linux内核中的实现时,才细心考虑这个问题应该是置衔接套接字的状况而非监听套接字的状况。
一般,SYN包只用于TCP三次握手协议中。常见的tcp三次握手协议进程(当然还有一起衔接)
半衔接等其它一些状况)如下:
1、client SYN包—> server
2、client —SYN包/ACK包 server
3、client ACK包—> server
依据tcp状况图,对应下述4个状况的改变
a、client发送结束,状况变成SYN_SEND;
b、server收到SYN报并发送ack承认包和SYN包,状况变为SYN_RECV
c、client发送ack包结束,状况变成ESTABLISHED
d、server发送ack包结束,状况变成ESTABLISHED
在linux内核中,上述几个状况对应为TCP_SYN_SEND、TCP_SYN_RECV、TCP_ESTABLISHED.
RFC793中关于SYN_RECV状况的描绘如下:
SYN-RECEIVED – represents waiting for a confirming connection
request acknowledgment after having both received and sent a
connection request.
从上面能够看出,这个状况是在本端接收到对端衔接恳求,并发送衔接对端恳求后,等候对端应对时所置的状况。所以,本质上衔接的进程是两边恳求应对的来回, 应该称四次握手,仅仅常见的使用以c/s形式为主,而linux、包含绝大部分操作系统都把服务器端的应对和恳求封装在一个包里边。
但在linux内核中,却是在监听套接字收到了客户端的ACK包后,才创立衔接套接字并初始化为TCP_SYN_RECV状况,如下函数调用联系:
tcp_v4_rcv–>tcp_v4_do_rcv–>tcp_v4_hnd_req–>tcp_check_req–>
tcp_v4_syn_recv_sock–>tcp_create_openreq_child…
struct sock *tcp_create_openreq_child(struct sock *sk, struct open_request *req, struct sk_buff *skb)
{
struct sock *newsk = sk_alloc(PF_INET, GFP_ATOMIC, 0); /*创立衔接sock结构*/
if(newsk != NULL) {
struct tcp_opt *newtp;
…
memcpy(newsk, sk, sizeof(*newsk));
newsk->state = TCP_SYN_RECV; /*置初始状况为SYN_RECV*/
//以下为一些初始化newsk结构的操作
…
}
这儿好像都正常了,但还有一点,服务器收到ACK包后,状况应该改为衔接状况,而此刻衔接套接字的状况仍是TCP_SYN_RECV
原因在于现在对ack包还没处理完,^_^,如下:
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
…
if (sk->state == TCP_LISTEN) { //此处是监听套接字的状况
struct sock *nsk = tcp_v4_hnd_req(sk, skb); //获得了上面讲的衔接套接字
if (!nsk)
goto discard;
if (nsk != sk) { //明显监听与衔接套接字不等
if (tcp_child_process(sk, nsk, skb)) //此处调用tcp_rcv_state_process置套接字为衔接树立状况
goto reset;
return 0;
}
}
…
}
可见,在linux内核中,SYN_RECV状况的坚持时刻是十分时间短的(也很难创立条件让此状况坚持),这也是咱们实践使用中经过netstat根本看不到这个状况的原因。