深入剖析TCP协议(内容二):从OSI与TCP/IP网络模型到三次握手、四次挥手、状态管理、性能优化及Linux内核源码实现的全面技术指南

常见问题

TCP和UDP

TCP和UDP的区别?

  • 连接

    • TCP 是面向连接的传输层协议,传输数据前先要建立连接
    • UDP 是不需要连接,即刻传输数据
  • 服务对象

    • TCP 是一对一的两点服务,即一条连接只有两个端点
    • UDP 支持一对一、一对多、多对多的交互通信
  • 可靠性

    • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达
    • UDP 是尽最大努力交付,不保证可靠交付数据
  • 拥塞控制、流量控制

    • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性
    • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率
  • 首部开销

    • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的
    • UDP 首部只有 8 个字节,并且是固定不变的,开销较小
  • 传输方式

    • TCP 是流式传输,没有边界,但保证顺序和可靠
    • UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序
  • 分片不同

    • TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片
    • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU

ISN

① 为什么客户端和服务端的初始序列号 ISN 是不相同的?

如果一个已经失效的连接被重用了,但是该旧连接的历史报文还残留在网络中,如果序列号相同,那么就无法分辨出该报文是不是历史报文,如果历史报文被新的连接接收了,则会产生数据错乱。所以,每次建立连接前重新初始化一个序列号主要是为了通信双方能够根据序号将不属于本连接的报文段丢弃。另一方面是为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收。

② 初始序列号 ISN 是如何随机产生的?

起始 ISN 是基于时钟的,每 4 毫秒 + 1,转一圈要 4.55 个小时。RFC1948 中提出了一个较好的初始化序列号 ISN 随机生成算法。

ISN = M + F (localhost, localport, remotehost, remoteport)

  • M 是一个计时器,这个计时器每隔 4 毫秒加 1
  • F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择

UDP

在这里插入图片描述

总结

  • TCP 向上层提供面向连接的可靠服务 ,UDP 向上层提供无连接不可靠服务
  • UDP 没有 TCP 传输可靠,但是可以在实时性要求搞的地方有所作为
  • 对数据准确性要求高,速度可以相对较慢的,可以选用TCP

TCP数据可靠性

一句话:通过校验和序列号确认应答超时重传连接管理流量控制拥塞控制等机制来保证可靠性。

(1)校验和

在数据传输过程中,将发送的数据段都当做一个16位的整数,将这些整数加起来,并且前面的进位不能丢弃,补在最后,然后取反,得到校验和。

发送方:在发送数据之前计算校验和,并进行校验和的填充。接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方进行比较。

(2)序列号

TCP 传输时将每个字节的数据都进行了编号,这就是序列号。序列号的作用不仅仅是应答作用,有了序列号能够将接收到的数据根据序列号进行排序,并且去掉重复的数据。

(3)确认应答

TCP 传输过程中,每次接收方接收到数据后,都会对传输方进行确认应答,也就是发送 ACK 报文,这个 ACK 报文中带有对应的确认序列号,告诉发送方,接收了哪些数据,下一次数据从哪里传。

(4)超时重传

在进行 TCP 传输时,由于存在确认应答与序列号机制,也就是说发送方发送一部分数据后,都会等待接收方发送的 ACK 报文,并解析 ACK 报文,判断数据是否传输成功。如果发送方发送完数据后,迟迟都没有接收到接收方传来的 ACK 报文,那么就对刚刚发送的数据进行重发。

(5)连接管理

就是指三次握手、四次挥手的过程。

(6)流量控制

如果发送方的发送速度太快,会导致接收方的接收缓冲区填充满了,这时候继续传输数据,就会造成大量丢包,进而引起丢包重传等等一系列问题。TCP 支持根据接收端的处理能力来决定发送端的发送速度,这就是流量控制机制。

具体实现方式:接收端将自己的接收缓冲区大小放入 TCP 首部的『窗口大小』字段中,通过 ACK 通知发送端。

(7)拥塞控制

TCP 传输过程中一开始就发送大量数据,如果当时网络非常拥堵,可能会造成拥堵加剧。所以 TCP 引入了慢启动机制,在开始发送数据的时候,先发少量的数据探探路。

TCP协议如何提高传输效率

一句话:TCP 协议提高效率的方式有滑动窗口快重传延迟应答捎带应答等。

(1)滑动窗口

如果每一个发送的数据段,都要收到 ACK 应答之后再发送下一个数据段,这样的话我们效率很低,大部分时间都用在了等待 ACK 应答上了。

为了提高效率我们可以一次发送多条数据,这样就能使等待时间大大减少,从而提高性能。窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。

(2)快重传

快重传也叫高速重发控制

那么如果出现了丢包,需要进行重传。一般分为两种情况:

情况一:数据包已经抵达,ACK被丢了。这种情况下,部分ACK丢了并不影响,因为可以通过后续的ACK进行确认;

情况二:数据包直接丢了。发送端会连续收到多个相同的 ACK 确认,发送端立即将对应丢失的数据重传。

(3)延迟应答

如果接收数据的主机立刻返回ACK应答,这时候返回的窗口大小可能比较小。

  • 假设接收端缓冲区为1M,一次收到了512K的数据;如果立刻应答,返回的窗口就是512K;
  • 但实际上可能处理端处理速度很快,10ms之内就把512K的数据从缓存区消费掉了;
  • 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
  • 如果接收端稍微等一会在应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;

窗口越大,网络吞吐量就越大,传输效率就越高;我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。

(4)捎带应答

在延迟应答的基础上,很多情况下,客户端服务器在应用层也是一发一收的。这时候常常采用捎带应答的方式来提高效率,而ACK响应常常伴随着数据报文共同传输。如:三次握手。

TCP如何处理拥塞

网络拥塞现象是指到达通信网络中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象。拥塞控制是处理网络拥塞现象的一种机制。

拥塞控制的四个阶段:

  • 慢启动
  • 拥塞避免
  • 快速重传
  • 快速恢复

Socket

基于TCP协议的客户端和服务器工作:

在这里插入图片描述

  • 服务端和客户端初始化 socket,得到文件描述符
  • 服务端调用 bind,将绑定在 IP 地址和端口
  • 服务端调用 listen,进行监听
  • 服务端调用 accept,等待客户端连接
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求
  • 服务端 accept 返回用于传输的 socket 的文件描述符
  • 客户端调用 write 写入数据;服务端调用 read 读取数据
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭

listen 时候参数 backlog 的意义?

Linux内核中会维护两个队列:

  • 未完成连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态;
  • 已完成连接队列(Accpet 队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态;

请添加图片描述

SYN 队列 与 Accpet 队列

int listen (int socketfd, int backlog)
  • 参数一 socketfd 为 socketfd 文件描述符
  • 参数二 backlog,这参数在历史内环版本有一定的变化

在早期Linux内核backlog是SYN队列大小,也就是未完成的队列大小。在Linux内核2.2之后,backlog变成accept队列,也就是已完成连接建立的队列长度,所以现在通常认为backlog是accept队列。但是上限值是内核参数somaxconn的大小,也就说accpet队列长度=min(backlog, somaxconn)。

accept 发送在三次握手的哪一步?

我们先看看客户端连接服务端时,发送了什么?

在这里插入图片描述

  • 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 client_isn,客户端进入 SYNC_SENT 状态
  • 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端进入 SYNC_RCVD 状态
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1
  • 应答包到达服务器端后,服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态

从上面的描述过程,我们可以得知客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。

客户端调用 close 了,连接是断开的流程是什么?

我们看看客户端主动调用了 close,会发生什么?

在这里插入图片描述

  • 客户端调用 close,表明客户端没有数据需要发送了,则此时会向服务端发送FIN报文,进入FIN_WAIT_1状态
  • 服务端接收到了 FIN 报文,TCP协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为EOF表示在该连接上再无额外数据到达。此时服务端进入 CLOSE_WAIT 状态
  • 接着,当处理完数据后,自然就会读到 EOF,于是也调用 close 关闭它的套接字,这会使得会发出一个 FIN 包,之后处于 LAST_ACK 状态
  • 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态
  • 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态
  • 客户端进过 2MSL 时间之后,也进入 CLOSE 状态

TCP源码

tcp_v4_connect()

  • 描述: 建立与服务器连接,发送SYN段

  • 返回值: 0或错误码

  • 代码关键路径:

    int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
    {
        .....      
        /* 设置目的地址和目标端口 */
        inet->dport = usin->sin_port;
        inet->daddr = daddr;
        ....     
        /* 初始化MSS上限 */
        tp->rx_opt.mss_clamp = 536;
    
        /* Socket identity is still unknown (sport may be zero).
         * However we set state to SYN-SENT and not releasing socket
         * lock select source port, enter ourselves into the hash tables and
         * complete initialization after this.
         */
        tcp_set_state(sk, TCP_SYN_SENT);/* 设置状态 */
        err = tcp_v4_hash_connect(sk);/* 将传输控制添加到ehash散列表中,并动态分配端口 */
        if (err)
            goto failure;
        ....
        if (!tp->write_seq)/* 还未计算初始序号 */
            /* 根据双方地址、端口计算初始序号 */
            tp->write_seq = secure_tcp_sequence_number(inet->saddr,
                                   inet->daddr,
                                   inet->sport,
                                   usin->sin_port);
    
        /* 根据初始序号和当前时间,随机算一个初始id */
        inet->id = tp->write_seq ^ jiffies;
    
        /* 发送SYN段 */
        err = tcp_connect(sk);
        rt = NULL;
        if (err)
            goto failure;
    
        return 0;
    }
    

sys_accept()

  • 描述: 调用tcp_accept(), 并把它返回的newsk进行连接描述符分配后返回给用户空间。

  • 返回值: 连接描述符

  • 代码关键路径:

    asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen)
    {
        struct socket *sock, *newsock;
        .....     
        sock = sockfd_lookup(fd, &err);/* 获得侦听端口的socket */
        .....    
        if (!(newsock = sock_alloc()))/* 分配一个新的套接口,用来处理与客户端的连接 */ 
        .....     
        /* 调用传输层的accept,对TCP来说,是inet_accept */
        err = sock->ops->accept(sock, newsock, sock->file->f_flags);
        ....    
        if (upeer_sockaddr) {/* 调用者需要获取对方套接口地址和端口 */
            /* 调用传输层回调获得对方的地址和端口 */
            if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) {
            }
            /* 成功后复制到用户态 */
            err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen);
        }
        .....     
        if ((err = sock_map_fd(newsock)) < 0)/* 为新连接分配文件描述符 */
    
        return err;
    }
    

tcp_accept()

[注]: 在内核2.6.32以后对应函数为inet_csk_accept().

  • 描述: 通过在规定时间内,判断tcp_sock->accept_queue队列非空,代表有新的连接进入.

  • 返回值: (struct sock *)newsk;

  • 代码关键路径:

    struct sock *tcp_accept(struct sock *sk, int flags, int *err)
    {
        ....
        /* Find already established connection */
        if (!tp->accept_queue) {/* accept队列为空,说明还没有收到新连接 */
            long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);/* 如果套口是非阻塞的,或者在一定时间内没有新连接,则返回 */
    
            if (!timeo)/* 超时时间到,没有新连接,退出 */
                goto out;
    
            /* 运行到这里,说明有新连接到来,则等待新的传输控制块 */
            error = wait_for_connect(sk, timeo);
            if (error)
                goto out;
        }
    
        req = tp->accept_queue;
        if ((tp->accept_queue = req->dl_next) == NULL)
            tp->accept_queue_tail = NULL;
    
        newsk = req->sk;
        sk_acceptq_removed(sk);
        tcp_openreq_fastfree(req);
        ....
    
        return newsk;
    }
    

三次握手

客户端发送SYN段
  • 由tcp_v4_connect()->tcp_connect()->tcp_transmit_skb()发送,并置为TCP_SYN_SENT.

  • 代码关键路径:

    /* 构造并发送SYN段 */
    int tcp_connect(struct sock *sk)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct sk_buff *buff;
    
        tcp_connect_init(sk);/* 初始化传输控制块中与连接相关的成员 */
    
        /* 为SYN段分配报文并进行初始化 */
        buff = alloc_skb(MAX_TCP_HEADER + 15, sk->sk_allocation);
        if (unlikely(buff == NULL))
            return -ENOBUFS;
    
        /* Reserve space for headers. */
        skb_reserve(buff, MAX_TCP_HEADER);
    
        TCP_SKB_CB(buff)->flags = TCPCB_FLAG_SYN;
        TCP_ECN_send_syn(sk, tp, buff);
        TCP_SKB_CB(buff)->sacked = 0;
        skb_shinfo(buff)->tso_segs = 1;
        skb_shinfo(buff)->tso_size = 0;
        buff->csum = 0;
        TCP_SKB_CB(buff)->seq = tp->write_seq++;
        TCP_SKB_CB(buff)->end_seq = tp->write_seq;
        tp->snd_nxt = tp->write_seq;
        tp->pushed_seq = tp->write_seq;
        tcp_ca_init(tp);
    
        /* Send it off. */
        TCP_SKB_CB(buff)->when = tcp_time_stamp;
        tp->retrans_stamp = TCP_SKB_CB(buff)->when;
    
        /* 将报文添加到发送队列上 */
        __skb_queue_tail(&sk->sk_write_queue, buff);
        sk_charge_skb(sk, buff);
        tp->packets_out += tcp_skb_pcount(buff);
        /* 发送SYN段 */
        tcp_transmit_skb(sk, skb_clone(buff, GFP_KERNEL));
        TCP_INC_STATS(TCP_MIB_ACTIVEOPENS);
    
        /* Timer for repeating the SYN until an answer. */
        /* 启动重传定时器 */
        tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
        return 0;
    }
    
服务端发送SYN和ACK处理

服务端接收到SYN段后,发送SYN/ACK处理:

  • 由tcp_v4_do_rcv()->tcp_rcv_state_process()->tcp_v4_conn_request()->tcp_v4_send_synack().

  • tcp_v4_send_synack()

    • tcp_make_synack(sk, dst, req); ** 根据路由、传输控制块、连接请求块中的构建SYN+ACK段 **

    • ip_build_and_send_pkt(); * 生成IP数据报并发送出去 *

在这里插入图片描述

  • 代码关键路径:

    /* 向客户端发送SYN+ACK报文 */
    static int tcp_v4_send_synack(struct sock *sk, struct open_request *req,
                      struct dst_entry *dst)
    {
        int err = -1;
        struct sk_buff * skb;
    
        /* First, grab a route. */
        /* 查找到客户端的路由 */
        if (!dst && (dst = tcp_v4_route_req(sk, req)) == NULL)
            goto out;
    
        /* 根据路由、传输控制块、连接请求块中的构建SYN+ACK段 */
        skb = tcp_make_synack(sk, dst, req);
    
        if (skb) {/* 生成SYN+ACK段成功 */
            struct tcphdr *th = skb->h.th;
    
            /* 生成校验码 */
            th->check = tcp_v4_check(th, skb->len,
                         req->af.v4_req.loc_addr,
                         req->af.v4_req.rmt_addr,
                         csum_partial((char *)th, skb->len,
                                  skb->csum));
    
            /* 生成IP数据报并发送出去 */
            err = ip_build_and_send_pkt(skb, sk, req->af.v4_req.loc_addr,
                            req->af.v4_req.rmt_addr,
                            req->af.v4_req.opt);
            if (err == NET_XMIT_CN)
                err = 0;
        }
    
    out:
        dst_release(dst);
        return err;
    }
    
客户端回复确认ACK段
  • 由tcp_v4_do_rcv()->tcp_rcv_state_process().当前客户端处于TCP_SYN_SENT状态。

  • tcp_rcv_synsent_state_process(); * tcp_rcv_synsent_state_process处理SYN_SENT状态下接收到的TCP段 *

    • tcp_ack(); ** 处理接收到的ack报文 **

    • tcp_send_ack(); * 在主动连接时,向服务器端发送ACK完成连接,并更新窗口 *

      • alloc_skb(); ** 构造ack段 **
      • tcp_transmit_skb(); ** 将ack段发出 **
    • tcp_urg(sk, skb, th); ** 处理完第二次握手后,还需要处理带外数据 **

    • tcp_data_snd_check(sk); * 检测是否有数据需要发送 *

      • 检查sk->sk_send_head队列上是否有待发送的数据。
    • tcp_write_xmit(); ** 将TCP发送队列上的段发送出去 **

  • 代码关键路径:

/* 在SYN_SENT状态下处理接收到的段,但是不处理带外数据 */
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
                   struct tcphdr *th, unsigned len)
{
  struct tcp_sock *tp = tcp_sk(sk);
  int saved_clamp = tp->rx_opt.mss_clamp;

  /* 解析TCP选项并保存到传输控制块中 */
  tcp_parse_options(skb, &tp->rx_opt, 0);

  if (th->ack) {/* 处理ACK标志 */
      /* rfc793:
       * "If the state is SYN-SENT then
       *    first check the ACK bit
       *      If the ACK bit is set
       *    If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
       *        a reset (unless the RST bit is set, if so drop
       *        the segment and return)"
       *
       *  We do not send data with SYN, so that RFC-correct
       *  test reduces to:
       */
      if (TCP_SKB_CB(skb)->ack_seq != tp->snd_nxt)
          goto reset_and_undo;

      if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
          !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp,
               tcp_time_stamp)) {
          NET_INC_STATS_BH(LINUX_MIB_PAWSACTIVEREJECTED);
          goto reset_and_undo;
      }

      /* Now ACK is acceptable.
       *
       * "If the RST bit is set
       *    If the ACK was acceptable then signal the user "error:
       *    connection reset", drop the segment, enter CLOSED state,
       *    delete TCB, and return."
       */

      if (th->rst) {/* 收到ACK+RST段,需要tcp_reset设置错误码,并关闭套接口 */
          tcp_reset(sk);
          goto discard;
      }

      /* rfc793:
       *   "fifth, if neither of the SYN or RST bits is set then
       *    drop the segment and return."
       *
       *    See note below!
       *                                        --ANK(990513)
       */
      if (!th->syn)/* 在SYN_SENT状态下接收到的段必须存在SYN标志,否则说明接收到的段无效,丢弃该段 */
          goto discard_and_undo;

      /* rfc793:
       *   "If the SYN bit is on ...
       *    are acceptable then ...
       *    (our SYN has been ACKed), change the connection
       *    state to ESTABLISHED..."
       */

      /* 从首部标志中获取显示拥塞通知的特性 */
      TCP_ECN_rcv_synack(tp, th);
      if (tp->ecn_flags&TCP_ECN_OK)/* 如果支持ECN,则设置标志 */
          sk->sk_no_largesend = 1;

      /* 设置与窗口相关的成员变量 */
      tp->snd_wl1 = TCP_SKB_CB(skb)->seq;
      tcp_ack(sk, skb, FLAG_SLOWPATH);

      /* Ok.. it's good. Set up sequence numbers and
       * move to established.
       */
      tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
      tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;

      /* RFC1323: The window in SYN & SYN/ACK segments is
       * never scaled.
       */
      tp->snd_wnd = ntohs(th->window);
      tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq, TCP_SKB_CB(skb)->seq);

      if (!tp->rx_opt.wscale_ok) {
          tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0;
          tp->window_clamp = min(tp->window_clamp, 65535U);
      }

      if (tp->rx_opt.saw_tstamp) {/* 根据是否支持时间戳选项来设置传输控制块的相关字段 */
          tp->rx_opt.tstamp_ok       = 1;
          tp->tcp_header_len =
              sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
          tp->advmss      -= TCPOLEN_TSTAMP_ALIGNED;
          tcp_store_ts_recent(tp);
      } else {
          tp->tcp_header_len = sizeof(struct tcphdr);
      }

      /* 初始化PMTU、MSS等成员变量 */
      if (tp->rx_opt.sack_ok && sysctl_tcp_fack)
          tp->rx_opt.sack_ok |= 2;

      tcp_sync_mss(sk, tp->pmtu_cookie);
      tcp_initialize_rcv_mss(sk);

      /* Remember, tcp_poll() does not lock socket!
       * Change state from SYN-SENT only after copied_seq
       * is initialized. */
      tp->copied_seq = tp->rcv_nxt;
      mb();
      tcp_set_state(sk, TCP_ESTABLISHED);

      /* Make sure socket is routed, for correct metrics.  */
      tp->af_specific->rebuild_header(sk);

      tcp_init_metrics(sk);

      /* Prevent spurious tcp_cwnd_restart() on first data
       * packet.
       */
      tp->lsndtime = tcp_time_stamp;

      tcp_init_buffer_space(sk);

      /* 如果启用了连接保活,则启用连接保活定时器 */
      if (sock_flag(sk, SOCK_KEEPOPEN))
          tcp_reset_keepalive_timer(sk, keepalive_time_when(tp));

      if (!tp->rx_opt.snd_wscale)/* 首部预测 */
          __tcp_fast_path_on(tp, tp->snd_wnd);
      else
          tp->pred_flags = 0;

      if (!sock_flag(sk, SOCK_DEAD)) {/* 如果套口不处于SOCK_DEAD状态,则唤醒等待该套接口的进程 */
          sk->sk_state_change(sk);
          sk_wake_async(sk, 0, POLL_OUT);
      }

      /* 连接建立完成,根据情况进入延时确认模式 */
      if (sk->sk_write_pending || tp->defer_accept || tp->ack.pingpong) {
          /* Save one ACK. Data will be ready after
           * several ticks, if write_pending is set.
           *
           * It may be deleted, but with this feature tcpdumps
           * look so _wonderfully_ clever, that I was not able
           * to stand against the temptation 8)     --ANK
           */
          tcp_schedule_ack(tp);
          tp->ack.lrcvtime = tcp_time_stamp;
          tp->ack.ato  = TCP_ATO_MIN;
          tcp_incr_quickack(tp);
          tcp_enter_quickack_mode(tp);
          tcp_reset_xmit_timer(sk, TCP_TIME_DACK, TCP_DELACK_MAX);

discard:
          __kfree_skb(skb);
          return 0;
      } else {/* 不需要延时确认,立即发送ACK段 */
          tcp_send_ack(sk);
      }
      return -1;
  }

  /* No ACK in the segment */

  if (th->rst) {/* 收到RST段,则丢弃传输控制块 */
      /* rfc793:
       * "If the RST bit is set
       *
       *      Otherwise (no ACK) drop the segment and return."
       */

      goto discard_and_undo;
  }

  /* PAWS check. */
  /* PAWS检测失效,也丢弃传输控制块 */
  if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp && tcp_paws_check(&tp->rx_opt, 0))
      goto discard_and_undo;

  /* 在SYN_SENT状态下收到了SYN段并且没有ACK,说明是两端同时打开 */
  if (th->syn) {
      /* We see SYN without ACK. It is attempt of
       * simultaneous connect with crossed SYNs.
       * Particularly, it can be connect to self.
       */
      tcp_set_state(sk, TCP_SYN_RECV);/* 设置状态为TCP_SYN_RECV */

      if (tp->rx_opt.saw_tstamp) {/* 设置时间戳相关的字段 */
          tp->rx_opt.tstamp_ok = 1;
          tcp_store_ts_recent(tp);
          tp->tcp_header_len =
              sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
      } else {
          tp->tcp_header_len = sizeof(struct tcphdr);
      }

      /* 初始化窗口相关的成员变量 */
      tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
      tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;

      /* RFC1323: The window in SYN & SYN/ACK segments is
       * never scaled.
       */
      tp->snd_wnd    = ntohs(th->window);
      tp->snd_wl1    = TCP_SKB_CB(skb)->seq;
      tp->max_window = tp->snd_wnd;

      TCP_ECN_rcv_syn(tp, th);/* 从首部标志中获取显式拥塞通知的特性。 */
      if (tp->ecn_flags&TCP_ECN_OK)
          sk->sk_no_largesend = 1;

      /* 初始化MSS相关的成员变量 */
      tcp_sync_mss(sk, tp->pmtu_cookie);
      tcp_initialize_rcv_mss(sk);

      /* 向对端发送SYN+ACK段,并丢弃接收到的SYN段 */
      tcp_send_synack(sk);
#if 0
      /* Note, we could accept data and URG from this segment.
       * There are no obstacles to make this.
       *
       * However, if we ignore data in ACKless segments sometimes,
       * we have no reasons to accept it sometimes.
       * Also, seems the code doing it in step6 of tcp_rcv_state_process
       * is not flawless. So, discard packet for sanity.
       * Uncomment this return to process the data.
       */
      return -1;
#else
      goto discard;
#endif
  }
  /* "fifth, if neither of the SYN or RST bits is set then
   * drop the segment and return."
   */

discard_and_undo:
  tcp_clear_options(&tp->rx_opt);
  tp->rx_opt.mss_clamp = saved_clamp;
  goto discard;

reset_and_undo:
  tcp_clear_options(&tp->rx_opt);
  tp->rx_opt.mss_clamp = saved_clamp;
  return 1;
}
服务端收到ACK段
  • 由tcp_v4_do_rcv()->tcp_rcv_state_process().当前服务端处于TCP_SYN_RECV状态变为TCP_ESTABLISHED状态。

  • 代码关键路径:

    /* 除了ESTABLISHED和TIME_WAIT状态外,其他状态下的TCP段处理都由本函数实现 */ 
    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
                  struct tcphdr *th, unsigned len)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        int queued = 0;
    
        tp->rx_opt.saw_tstamp = 0;
    
        switch (sk->sk_state) {
        .....
        /* SYN_RECV状态的处理 */
        if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&/* 解析TCP选项,如果首部中存在时间戳选项 */
            tcp_paws_discard(tp, skb)) {/* PAWS检测失败,则丢弃报文 */
            if (!th->rst) {/* 如果不是RST段 */
                /* 发送DACK给对端,说明接收到的TCP段已经处理过 */
                NET_INC_STATS_BH(LINUX_MIB_PAWSESTABREJECTED);
                tcp_send_dupack(sk, skb);
                goto discard;
            }
            /* Reset is accepted even if it did not pass PAWS. */
        }
    
        /* step 1: check sequence number */
        if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {/* TCP段序号无效 */
            if (!th->rst)/* 如果TCP段无RST标志,则发送DACK给对方 */
                tcp_send_dupack(sk, skb);
            goto discard;
        }
    
        /* step 2: check RST bit */
        if(th->rst) {/* 如果有RST标志,则重置连接 */
            tcp_reset(sk);
            goto discard;
        }
    
        /* 如果有必要,则更新时间戳 */
        tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);
    
        /* step 3: check security and precedence [ignored] */
    
        /*  step 4:
         *
         *  Check for a SYN in window.
         */
        if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {/* 如果有SYN标志并且序号在接收窗口内 */
            NET_INC_STATS_BH(LINUX_MIB_TCPABORTONSYN);
            tcp_reset(sk);/* 复位连接 */
            return 1;
        }
    
        /* step 5: check the ACK field */
        if (th->ack) {/* 如果有ACK标志 */
            /* 检查ACK是否为正常的第三次握手 */
            int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);
    
            switch(sk->sk_state) {
            case TCP_SYN_RECV:
                if (acceptable) {
                    tp->copied_seq = tp->rcv_nxt;
                    mb();
                    /* 正常的第三次握手,设置连接状态为TCP_ESTABLISHED */
                    tcp_set_state(sk, TCP_ESTABLISHED);
                    sk->sk_state_change(sk);
    
                    /* Note, that this wakeup is only for marginal
                     * crossed SYN case. Passively open sockets
                     * are not waked up, because sk->sk_sleep ==
                     * NULL and sk->sk_socket == NULL.
                     */
                    if (sk->sk_socket) {/* 状态已经正常,唤醒那些等待的线程 */
                        sk_wake_async(sk,0,POLL_OUT);
                    }
    
                    /* 初始化传输控制块,如果存在时间戳选项,同时平滑RTT为0,则需计算重传超时时间 */
                    tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
                    tp->snd_wnd = ntohs(th->window) <<
                              tp->rx_opt.snd_wscale;
                    tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq,
                            TCP_SKB_CB(skb)->seq);
    
                    /* tcp_ack considers this ACK as duplicate
                     * and does not calculate rtt.
                     * Fix it at least with timestamps.
                     */
                    if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
                        !tp->srtt)
                        tcp_ack_saw_tstamp(tp, 0);
    
                    if (tp->rx_opt.tstamp_ok)
                        tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
    
                    /* Make sure socket is routed, for
                     * correct metrics.
                     */
                    /* 建立路由,初始化拥塞控制模块 */
                    tp->af_specific->rebuild_header(sk);
    
                    tcp_init_metrics(sk);
    
                    /* Prevent spurious tcp_cwnd_restart() on
                     * first data packet.
                     */
                    tp->lsndtime = tcp_time_stamp;/* 更新最近一次发送数据包的时间 */
    
                    tcp_initialize_rcv_mss(sk);
                    tcp_init_buffer_space(sk);
                    tcp_fast_path_on(tp);/* 计算有关TCP首部预测的标志 */
                } else {
                    return 1;
                }
                break;
            .....
            }
        } else
            goto discard;
        .....
    
        /* step 6: check the URG bit */
        tcp_urg(sk, skb, th);/* 检测带外数据位 */
    
        /* tcp_data could move socket to TIME-WAIT */
        if (sk->sk_state != TCP_CLOSE) {/* 如果tcp_data需要发送数据和ACK则在这里处理 */
            tcp_data_snd_check(sk);
            tcp_ack_snd_check(sk);
        }
    
        if (!queued) { /* 如果段没有加入队列,或者前面的流程需要释放报文,则释放它 */
    discard:
            __kfree_skb(skb);
        }
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三年呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值