当前位置:编程学习 > C/C++ >>

TIME_WAIT状态下对接收到的数据包如何处理

       1、保证TCP连接关闭的可靠性。如果最终发送的ACK丢失,被动关闭的一端会重传最终的FIN包,如果执行主动关闭的一端没有维护这个连接的状态信息,会发送RST包响应,导致连接不正常关闭。
      2、允许老的重复分组在网络中消逝。假设在一个连接关闭后,发起建立连接的一端(客户端)立即重用原来的端口、IP地址和服务端建立新的连接。老的连接上的分组可能在新的连接建立后到达服务端,TCP必须防止来自某个连接的老的重复分组在连接终止后再现,从而被误解为同一个连接的化身。要实现这种功能,TCP不能给处于TIME_WAIT状态的连接启动新的连接。TIME_WAIT的持续时间是2MSL,保证在建立新的连接之前老的重复分组在网络中消逝。这个规则有一个例外:如果到达的SYN的序列号大于前一个连接的结束序列号,源自Berkeley的实现将给当前处于TIME_WAIT状态的连接启动新的化身。
  最初在看《Unix网络编程》 的时候看到这个状态,但是在项目中发现对这个状态的理解有误,特别是第二个理由。原本认为在TIME_WAIT状态下肯定不会再使用相同的五元组(协议类型,源目的IP、源目的端口号)建立一个新的连接,看书还是不认真啊!为了加深理解,决定结合内核代码,好好来看下内核在TIME_WAIT状态下的处理。其实TIME_WAIT存在的第二个原因的解释更多的是从被动关闭一方的角度来说明的。如果是执行主动关闭的是客户端,客户端户进入TIME_WAIT状态,假设客户端重用端口号来和服务器建立连接,内核会不会允许客户端来建立连接?内核如何来处理这种情况?书本中不会对这些点讲的那么详细,要从内核源码中来找答案。
  我们先来看服务器段进入TIME_WAIT后内核的处理,即服务器主动关闭连接。TCP层的接收函数是tcp_v4_rcv(),和TIME_WAIT状态相关的主要代码如下所示:
[cpp]
int tcp_v4_rcv(struct sk_buff *skb) 

    ...... 
 
    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest); 
    if (!sk) 
        goto no_tcp_socket; 
process: 
    if (sk->sk_state == TCP_TIME_WAIT) 
        goto do_time_wait;     
        ...... 
 
discard_it: 
    /* Discard frame. */ 
    kfree_skb(skb); 
    return 0; 
    ...... 
do_time_wait: 
    ...... 
 
    switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) { 
    case TCP_TW_SYN: { 
        struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev), 
                            &tcp_hashinfo, 
                            iph->daddr, th->dest, 
                            inet_iif(skb)); 
        if (sk2) { 
            inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row); 
            inet_twsk_put(inet_twsk(sk)); 
            sk = sk2; 
            goto process; 
        } 
        /* Fall through to ACK */ 
    } 
    case TCP_TW_ACK: 
        tcp_v4_timewait_ack(sk, skb); 
        break; 
    case TCP_TW_RST: 
        goto no_tcp_socket; 
    case TCP_TW_SUCCESS:;  
    } 
    goto discard_it; 

int tcp_v4_rcv(struct sk_buff *skb)
{
    ......

    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
    if (!sk)
        goto no_tcp_socket;
process:
    if (sk->sk_state == TCP_TIME_WAIT)
        goto do_time_wait;   
        ......

discard_it:
    /* Discard frame. */
    kfree_skb(skb);
    return 0;
    ......
do_time_wait:
    ......

    switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
    case TCP_TW_SYN: {
        struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
                            &tcp_hashinfo,
                            iph->daddr, th->dest,
                            inet_iif(skb));
        if (sk2) {
            inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
            inet_twsk_put(inet_twsk(sk));
            sk = sk2;
            goto process;
        }
        /* Fall through to ACK */
    }
    case TCP_TW_ACK:
        tcp_v4_timewait_ack(sk, skb);
        break;
    case TCP_TW_RST:
        goto no_tcp_socket;
    case TCP_TW_SUCCESS:;
    }
    goto discard_it;
}  接收到SKb包后,会调用__inet_lookup_skb()查找对应的sock结构。如果套接字状态是TIME_WAIT状态,会跳转到do_time_wait标签处处理。从代码中可以看到,主要由tcp_timewait_state_process()函数来处理SKB包,处理后根据返回值来做相应的处理。
  在看tcp_timewait_state_process()函数中的处理之前,需要先看一看不同的返回值会对应什么样的处理。
  如果返回值是TCP_TW_SYN,则说明接收到的是一个“合法”的SYN包(也就是说这个SYN包可以接受),这时会首先查找内核中是否有对应的监听套接字,如果存在相应的监听套接字,则会释放TIME_WAIT状态的传输控制结构,跳转到process处开始处理,开始建立一个新的连接。如果没有找到监听套接字会执行到TCP_TW_ACK分支。
  如果返回值是TCP_TW_ACK,则会调用tcp_v4_timewait_ack()发送ACK,然后跳转到discard_it标签处,丢掉数据包。
  如果返回值是TCP_TW_RST,则会调用tcp_v4_send_reset()给对端发送RST包,然后丢掉数据包。
  如果返回值是TCP_TW_SUCCESS,则会直接丢掉数据包。
  接下来我们通过tcp_timewait_state_process()函数来看TIME_WAIT状态下的数据包处理。补充:软件开发 , C++ ,

CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,