3.4 同时打开

两个应用程序同时彼此执行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 of
5530          * simultaneous connect with crossed SYNs.
5531          * Particularly, it can be connect to self.
5532          */
5533         tcp_set_state(sk, TCP_SYN_RECV);//状态切换为TCP_SYN_RECV
5534
5535         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         }
5543
5544         tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
5545         tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
5546
5547         /* RFC1323: The window in SYN & SYN/ACK segments is
5548          * 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;
5553
5554         TCP_ECN_rcv_syn(tp, th);
5555
5556         tcp_mtup_init(sk);
5557         tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
5558         tcp_initialize_rcv_mss(sk);
5559
5560         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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值