3.4 同时打开

来源:互联网 发布:aft3登陆器源码 编辑:程序博客网 时间:2024/05/29 13:26
两个应用程序同时彼此执行connect系统调用向对方发送SYN请求,一方所使用的源|目的IP和源|目的端口恰好是对方的目的|源IP和目的|源端口,这就是“同时打开”。尽管这种情况很少见,但TCP是支持的。

    这种场景下,应用进程先发送一个SYN,然后socket进入TCP_SYN_SENT状态,这时收到了对端的SYN,而非SYN|ACK。处理流程与3.3节中客户端处理SYN|ACK的相似:tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_state_process->tcp_rcv_synsent_state_process,这时由于包中没有ACK标记,处理流程会有不同:

5373 static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,5374                      const struct tcphdr *th, unsigned int len)5375 {5376     struct inet_connection_sock *icsk = inet_csk(sk);5377     struct tcp_sock *tp = tcp_sk(sk);5378     struct tcp_fastopen_cookie foc = { .len = -1 };5379     int saved_clamp = tp->rx_opt.mss_clamp;...5385     if (th->ack) {  //包中不带ACK标记,不会走此路径...5508         return -1;5509     }...5528     if (th->syn) {5529         /* We see SYN without ACK. It is attempt of5530          * simultaneous connect with crossed SYNs.5531          * Particularly, it can be connect to self.5532          */5533         tcp_set_state(sk, TCP_SYN_RECV);//状态切换为TCP_SYN_RECV55345535         if (tp->rx_opt.saw_tstamp) {5536             tp->rx_opt.tstamp_ok = 1;5537             tcp_store_ts_recent(tp);5538             tp->tcp_header_len =5539                 sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;5540         } else {5541             tp->tcp_header_len = sizeof(struct tcphdr);5542         }55435544         tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;5545         tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;55465547         /* RFC1323: The window in SYN & SYN/ACK segments is5548          * never scaled.5549          */5550         tp->snd_wnd    = ntohs(th->window);5551         tp->snd_wl1    = TCP_SKB_CB(skb)->seq;5552         tp->max_window = tp->snd_wnd;55535554         TCP_ECN_rcv_syn(tp, th);55555556         tcp_mtup_init(sk);5557         tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);5558         tcp_initialize_rcv_mss(sk);55595560         tcp_send_synack(sk);//发送SYN|ACK...
  5560:这个负责发送SYN|ACK的函数tcp_send_synack有些特殊:

2615 int tcp_send_synack(struct sock *sk)2616 {2617     struct sk_buff *skb;2618 2619     skb = tcp_write_queue_head(sk);2620     if (skb == NULL || !(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)) { //包一定携带SYN标记2621         pr_debug("%s: wrong queue state\n", __func__);2622         return -EFAULT;2623     }2624     if (!(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_ACK)) { //未携带ACK标记2625         if (skb_cloned(skb)) {2626             struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);2627             if (nskb == NULL)2628                 return -ENOMEM;2629             tcp_unlink_write_queue(skb, sk);2630             skb_header_release(nskb);2631             __tcp_add_write_queue_head(sk, nskb);2632             sk_wmem_free_skb(sk, skb);2633             sk->sk_wmem_queued += nskb->truesize;2634             sk_mem_charge(sk, nskb->truesize);2635             skb = nskb;2636         }2637     2638         TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_ACK; //加上ACK标记2639         TCP_ECN_send_synack(tcp_sk(sk), skb);2640     }2641     TCP_SKB_CB(skb)->when = tcp_time_stamp;2642     return tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC); //发送出去2643 }
  在同时打开的情况下,发送队列中已经有一个SYN包等待确认。tcp_send_synack的基本功能是:将发送队列中的SYN包加上ACK标记位再发送。这样TCP收到SYN后发送的SYN|ACK的序列号与最开始发送的SYN包一致,从而使得收到对端的SYN|ACK包时,如果仅仅把这个包当做ACK使用,则其确认号正好可以确认掉之前发送的SYN|ACK,这样连接就可以在收到对端的SYN|ACK后得以正常建立。

  2625-2635:如果队列中的SYN包是clone的副本,则这个skb与原本共享数据;为了不影响原本,必须copy一个新的skb——nskb(使用skb_copy产生的nskb与其源skb的数据完全一致,但承载数据的内存区完全独立的),加入ACK标记后再发送,而原来的skb必须释放掉。

  SYN|ACK接收时会被当做ACK处理,流程与C/S模式下的server一致,不同的是查找连接时会在established表中找到socket,无需再建立新的socket。

  在同时打开模式下,两端的TCP的数据交互流程都是:

1、发送SYN,TCP状态机跳转到TCP_SYN_SENT

2、收到SYN,TCP状态机跳转到TCP_SYN_RECV,发送SYN|ACK

3、收到ACK(被忽略SYN标记的SYN|ACK),TCP状态机跳转到TCP_ESTABLISHED,进入可以进行数据交互的状态。

  接下来的数据交互并不受连接建立方式的影响,即无论是用C/S模式下的三次握手还是同时打开的方法建立的连接,连接建立完毕后的行为都是完全一样的。

0 0
原创粉丝点击