深入理解TCP协议及其源代码

TCP在linux下的实现过程:

首先服务器端调用socket()创建服务器端的套接字之后调用bind()绑定创建socket是所拿到的socket文件描述符,之后调用acppet()阻塞自己等待客户端的连接。

客户端同样调用socket()创建客户端的套接字,之后调用connect()去连接服务器【根据服务器端的套接字锁定服务器】,此时TCP报文段中SYN=1,seq为一随机数字x,且客户端的连接状态置为SYS_SEND。

服务器端的accept()的阻塞收到该报文段之后被打断,置连接状态为SYN_RECV,并发送TCP报文段,SYN=1,ACK=1,seq为随机数字y,ack=x+1。

客户端收到该报文段后置状态为ESTABLISED,ACK=1,seq=x+1,ack=y+1。

服务器端接收到后置自己状态为ESTABLISED。此时三次握手已经结束。

之后便可调用read与write实现客户端与服务器端的通信。

深入理解TCP协议及其源代码

 

 

 那么具体的通信过程是怎么样的?

使用ipv4时,所有与TCP文件都在都在 net/ipv4/ -directory 目录下。摘选其中重要的TCP文件如下:

深入理解TCP协议及其源代码


 

首先探究TCP/IP协议栈的初始化:

TCP/IP协议栈的初始化的函数入口是inet_init():

大致流程为:

首先地址族协议初始化语句for (i = 0; i < NPROTO; ++i) pops[i] = NULL;

接下来是proto_init()协议初始化;

协议初始化完成后再执行dev_init()设备的初始化。

static int __init inet_init(void)
{
    struct inet_protosw *q;
    struct list_head *r;
    int rc = -EINVAL;
 
    BUILD_BUG_ON(sizeof(struct inet_skb_parm) > FIELD_SIZEOF(struct sk_buff, cb));
 
    sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL);
    if (!sysctl_local_reserved_ports)
        goto out;
 
    //tcp协议注册(传输层)
    rc = proto_register(&tcp_prot, 1); //第二个参数为1,表示在高速缓存内部分配空间
    if (rc)
        goto out_free_reserved_ports;
 
    //udp协议注册(传输层)
    rc = proto_register(&udp_prot, 1);
    if (rc)
        goto out_unregister_tcp_proto;
 
    //raw原始协议注册(传输层)
    rc = proto_register(&raw_prot, 1);
    if (rc)
        goto out_unregister_udp_proto;
 
    //icmp协议注册(传输层)
    rc = proto_register(&ping_prot, 1);
    if (rc)
        goto out_unregister_raw_proto;
 
    /*
     *    Tell SOCKET that we are alive...
     */
 
    (void)sock_register(&inet_family_ops);
 
#ifdef CONFIG_SYSCTL
    ip_static_sysctl_init();
#endif
 
    tcp_prot.sysctl_mem = init_net.ipv4.sysctl_tcp_mem;
 
    /*
     *    Add all the base protocols.
     */
 
    if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
        pr_crit("%s: Cannot add ICMP protocol\n", __func__);
    if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
        pr_crit("%s: Cannot add UDP protocol\n", __func__);
    if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
        pr_crit("%s: Cannot add TCP protocol\n", __func__);
#ifdef CONFIG_IP_MULTICAST
    if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
        pr_crit("%s: Cannot add IGMP protocol\n", __func__);
#endif
 
    /* Register the socket-side information for inet_create. */
    for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
        INIT_LIST_HEAD(r);
 
    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
        inet_register_protosw(q);
 
    /*
     *    Set the ARP module up
     */
 
    arp_init();
 
    /*
     *    Set the IP module up
     */
 
    ip_init();
 
    tcp_v4_init();
 
    /* Setup TCP slab cache for open requests. */
    tcp_init();
 
    /* Setup UDP memory threshold */
    udp_init();
 
    /* Add UDP-Lite (RFC 3828) */
    udplite4_register();
 
    ping_init();
 
    /*
     *    Set the ICMP layer up
     */
 
    if (icmp_init() < 0)
        panic("Failed to create the ICMP control socket.\n");
 
    /*
     *    Initialise the multicast router
     */
#if defined(CONFIG_IP_MROUTE)
    if (ip_mr_init())
        pr_crit("%s: Cannot init ipv4 mroute\n", __func__);
#endif
    /*
     *    Initialise per-cpu ipv4 mibs
     */
 
    if (init_ipv4_mibs())
        pr_crit("%s: Cannot init ipv4 mibs\n", __func__);
 
    ipv4_proc_init();
 
    ipfrag_init();
 
    dev_add_pack(&ip_packet_type);
 
    rc = 0;
out:
    return rc;
out_unregister_raw_proto:
    proto_unregister(&raw_prot);
out_unregister_udp_proto:
    proto_unregister(&udp_prot);
out_unregister_tcp_proto:
    proto_unregister(&tcp_prot);
out_free_reserved_ports:
    kfree(sysctl_local_reserved_ports);
    goto out;
}
 
fs_initcall(inet_init);

 


 

其次,探究TCP报文段的数据段是怎么实现的。

在传输数据包时使用的数据结构:

文件位置:linux-5.0.1/include/linux/sk_buff.h

作用:Linux利用套接字缓冲区在协议层和网络设备之间传送数据。Sk_buff包含了一些指针和长度信息,从而可让协议层以标准的函数或方法对应用程序的数据进行处理。每个sk_buff均包含一个数据块、四个数据指针以及两个长度字段【见下注释】。

仅仅摘选与分析TCP传输有关的数据字段:

 1 struct sk_buff {
 2     /* These two members must be first. */
 3     struct sk_buff        *next;  //  因为sk_buff结构体是双链表,所以有前驱后继。这是个指向后面的sk_buff结构体指针
 4     struct sk_buff        *prev;  //  这是指向前一个sk_buff结构体指针
 6     struct sock            *sk;  // 指向拥有此缓冲的套接字sock结构体
 7     ktime_t            tstamp;  // 时间戳,表示这个skb的接收到的时间
 8     struct net_device    *dev;  // 表示一个网络设备,当skb为输出/输入时,dev表示要输出/输入到的设备
 9     unsigned long    _skb_dst;  // 主要用于路由子系统,保存路由有关的东西
10     char            cb[48];  // 保存每层的控制信息,每一层的私有信息
11     unsigned int        len,  // 表示数据区的长度(tail - data)与分片结构体数据区的长度之和。其实这个len中数据区长度是个有效长度,
12                                       // 因为不删除协议头,所以只计算有效协议头和包内容。如:当在L3时,不会计算L2的协议头长度。
13                 data_len;  // 只表示分片结构体数据区的长度,所以len = (tail - data) + data_len;
14     __u16            mac_len,  // mac报头的长度
15     __u8            pkt_type:3,  // 标记帧的类型
16     __be16            protocol:16;  // 这是包的协议类型,标识是IP包还是ARP包或者其他数据包21     __u16            tc_index;    /* traffic control index */
22 #ifdef CONFIG_NET_CLS_ACT
23     __u16            tc_verd;    /* traffic control verdict */
24 
25     sk_buff_data_t        transport_header;      // 指向四层帧头结构体指针
26     sk_buff_data_t        network_header;           // 指向三层IP头结构体指针
27     sk_buff_data_t        mac_header;           // 指向二层mac头的头
28     /* These elements must be at the end, see alloc_skb() for details.  */
29     sk_buff_data_t        tail;              // 指向数据区中实际数据结束的位置
30     sk_buff_data_t        end;              // 指向数据区中结束的位置(非实际数据区域结束位置)
31     unsigned char        *head,              // 指向数据区中开始的位置(非实际数据区域开始位置)
32                          *data;              // 指向数据区中实际数据开始的位置            
33     unsigned int        truesize;          // 表示总长度,包括sk_buff自身长度和数据区以及分片结构体的数据区长度
34 };   

sk_buff通过一个双链表实现,且在该DS中维护了mac层IP层以及传输层的指针,则其在各个层之间的传输过程的实现变很清楚了,当刚接受到该数据时在第二层此时data指针也就是指向实际数据开始的位置与mac header 指针相同,之后在向上层进行包装是通过修改data指针是的它指向network_header(IP层头指针),在往传输层进行传输的时候就修改data指向transport_header即可。这样做就可以避免数据的复制移动,处理更为高效。

深入理解TCP协议及其源代码

 

数据包在各层之间传递在linux中的实现


 

其次通过探究传输层与IP层如何交互以及三次握手的究竟的具体实现

TCP层的数据收发分析:

接收数据:在ipv4的情况下,TCP接受从网络层传输的来的数据是通过tcp_v4_rcv。该方法首先检查包是否是给本机的,然后去从hash表中【该表的键值为IP+端口号】找匹配的TCP端口号。然后如果并没有该socket,就把他的数据传送给tcp_v4_do_rcv,检查socket的状态,如果状态为TCP_ESTABLISHED,数据就被传送到tcp_rcv_established(),并且将数据copy到就收队列中。其他的状态则交给tcp_rcv_state_process【也就是我们所需要关注的三次握手的过程在三次握手的】处理。

当用户想从socket读数据时(tcp_recvmsg),所有的队列必须按顺序处理【因为TCP保证可靠传输】,首先是recevie queue,然后是prequeue队列中的数据。

int tcp_v4_rcv(struct sk_buff *skb)
{
  ...【仅仅摘取相关部分的代码】
/* 获取开始序号*/ TCP_SKB_CB(skb)->seq = ntohl(th->seq); /* 获取结束序号,syn与fin各占1 */ TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin + skb->len - th->doff * 4); /* 获取确认序号 */ TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq); /* 获取标记字节,tcp首部第14个字节 */ TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th); TCP_SKB_CB(skb)->tcp_tw_isn = 0; /* 获取ip头的服务字段 */ TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph); TCP_SKB_CB(skb)->sacked = 0;   
   【根据不同状态进行不同处理】
process: /* TIME_WAIT转过去处理 */ if (sk->sk_state == TCP_TIME_WAIT) goto do_time_wait; /* TCP_NEW_SYN_RECV状态处理 */ if (sk->sk_state == TCP_NEW_SYN_RECV) { struct request_sock *req = inet_reqsk(sk); struct sock *nsk; /* 获取控制块 */ sk = req->rsk_listener; if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) { sk_drops_add(sk, skb); reqsk_put(req); goto discard_it; } /* 不是listen状态 */ if (unlikely(sk->sk_state != TCP_LISTEN)) { /* 从连接队列移除控制块 */ inet_csk_reqsk_queue_drop_and_put(sk, req); /* 根据skb参数重新查找控制块 */ goto lookup; } /* We own a reference on the listener, increase it again * as we might lose it too soon. */ sock_hold(sk); refcounted = true; /* 处理第三次握手ack,成功返回新控制块 */ nsk = tcp_check_req(sk, skb, req, false); /* 失败 */ if (!nsk) { reqsk_put(req); goto discard_and_relse; } /* 未新建控制块,进一步处理 */ if (nsk == sk) { reqsk_put(req); } /* 有新建控制块,进行初始化等 */ else if (tcp_child_process(sk, nsk, skb)) { /* 失败发送rst */ tcp_v4_send_reset(nsk, skb); goto discard_and_relse; } else { sock_put(sk); return 0; } } /* TIME_WAIT和TCP_NEW_SYN_RECV以外的状态 */ /* ttl错误 */ if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) { __NET_INC_STATS(net, LINUX_MIB_TCPMINTTLDROP); goto discard_and_relse; } if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) goto discard_and_relse; if (tcp_v4_inbound_md5_hash(sk, skb)) goto discard_and_relse; /* 初始化nf成员 */ nf_reset(skb); /* tcp过滤 */ if (tcp_filter(sk, skb)) goto discard_and_relse; /* 取tcp和ip头 */ th = (const struct tcphdr *)skb->data; iph = ip_hdr(skb); /* 清空设备 */ skb->dev = NULL; /* LISTEN状态处理 */ if (sk->sk_state == TCP_LISTEN) { ret = tcp_v4_do_rcv(sk, skb); goto put_and_return; } /* TIME_WAIT和TCP_NEW_SYN_RECV和LISTEN以外的状态 */ /* 记录cpu */ sk_incoming_cpu_update(sk); bh_lock_sock_nested(sk); /* 分段统计 */ tcp_segs_in(tcp_sk(sk), skb); ret = 0; /* 未被用户锁定 */ if (!sock_owned_by_user(sk)) { /* 未能加入到prequeue */ if (!tcp_prequeue(sk, skb)) /* 进入tcpv4处理 */ ret = tcp_v4_do_rcv(sk, skb); } /* 已经被用户锁定,加入到backlog */ else if (tcp_add_backlog(sk, skb)) { goto discard_and_relse; } bh_unlock_sock(sk); put_and_return: /* 减少引用计数 */ if (refcounted) sock_put(sk); return ret; no_tcp_socket: if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) goto discard_it; if (tcp_checksum_complete(skb)) { csum_error: __TCP_INC_STATS(net, TCP_MIB_CSUMERRORS); bad_packet: __TCP_INC_STATS(net, TCP_MIB_INERRS); } else { /* 发送rst */ tcp_v4_send_reset(NULL, skb); } discard_it: /* Discard frame. */ kfree_skb(skb); return 0; discard_and_relse: sk_drops_add(sk, skb); if (refcounted) sock_put(sk); goto discard_it; do_time_wait: if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { inet_twsk_put(inet_twsk(sk)); goto discard_it; } /* 校验和错误 */ if (tcp_checksum_complete(skb)) { inet_twsk_put(inet_twsk(sk)); goto csum_error; } /* TIME_WAIT入包处理 */ switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) { /* 收到syn */ case TCP_TW_SYN: { /* 查找监听控制块 */ struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev), &tcp_hashinfo, skb, __tcp_hdrlen(th), iph->saddr, th->source, iph->daddr, th->dest, inet_iif(skb)); /* 找到 */ if (sk2) { /* 删除tw控制块 */ inet_twsk_deschedule_put(inet_twsk(sk)); /* 记录监听控制块 */ sk = sk2; refcounted = false; /* 进行新请求的处理 */ goto process; } /* Fall through to ACK */ } /* 发送ack */ case TCP_TW_ACK: tcp_v4_timewait_ack(sk, skb); break; /* 发送rst */ case TCP_TW_RST: tcp_v4_send_reset(sk, skb); /* 删除tw控制块 */ inet_twsk_deschedule_put(inet_twsk(sk)); goto discard_it; /* 成功*/ case TCP_TW_SUCCESS:; } goto discard_it; }

来自网路层的数据的处理流程图【TCP层的内部函数】:

深入理解TCP协议及其源代码

 

向网络层发送数据

当应用程序想TCP的socket中写数据的时候首先被调用的方法就是tcp_sendmsg(),它对数据进行分段并且用上边介绍过得sk_buff封装数据,之后把buffer放至写队列中

  1 参数含义:msg:要发送的数据;
  2         size:本次要发送的数据量
  3 int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
  4         size_t size)
  5 {
  6     struct sock *sk = sock->sk;
  7     struct iovec *iov;
  8     struct tcp_sock *tp = tcp_sk(sk);
  9     struct sk_buff *skb;
 10     int iovlen, flags;
 11     int mss_now, size_goal;
 12     int err, copied;
 13     long timeo;
 14 
 15     lock_sock(sk);
 16     TCP_CHECK_TIMER(sk);
 17 
 18     //计算超时时间,如果设置了MSG_DONTWAIT标记,则超时时间为0
 19     flags = msg->msg_flags;
 20     timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
 21 
 22     //只有ESTABLISHED和CLOSE_WAIT两个状态可以发送数据,其它状态需要等待连接完成;
 23     //CLOSE_WAIT是收到对端FIN但是本端还没有发送FIN时所处状态,所以也可以发送数据
 24     if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
 25         if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
 26             goto out_err;
 27 
 28     /* This should be in poll */
 29     clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
 30 
 31     //每次发送都操作都会重新获取MSS值,保存到mss_now中
 32     mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));
 33     //获取一个skb可以容纳的数据量。如果不支持TSO,那么该值就是MSS,否则是MSS的整数倍
 34     size_goal = tp->xmit_size_goal;
 35 
 36     //应用要发送的数据被保存在msg中,以数组方式组织,msg_iovlen为数组大小,msg_iov为数组第一个元素
 37     iovlen = msg->msg_iovlen;
 38     iov = msg->msg_iov;
 39     //copied将记录本次能够写入TCP的字节数,如果成功,最终会返回给应用,初始化为0
 40     copied = 0;
 41 
 42     //检查之前TCP连接是否发生过异常
 43     err = -EPIPE;
 44     if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
 45         goto do_error;
 46 
 47     //外层循环用来遍历msg_iov数组
 48     while (--iovlen >= 0) {
 49         //msg_iov数组中每个元素包含的数据量都可以不同,每个元素自己有多少数据量记录在自己的iov_len字段中
 50         int seglen = iov->iov_len;
 51         //from指向要拷贝的数据起点
 52         unsigned char __user *from = iov->iov_base;
 53 
 54         //iov指向下一个数组元素
 55         iov++;
 56         //内层循环用于拷贝一个数组元素
 57         while (seglen > 0) {
 58             //copy保存本轮循环要拷贝的数据量,下面会根据不同的情况计算该值
 59             int copy;
 60             //获取发送队列中最后一个数据块,因为该数据块当前已保存数据可能还没有超过
 61             //size_goal,所以可以继续往该数据块中填充数据
 62             skb = tcp_write_queue_tail(sk);
 63 
 64             //cond1:tcp_send_head()返回NULL表示待发送的新数为空(可能有待确认数据)
 65             //cond2:copy <= 0说明发送队列最后一个skb数据量也达到了size_goal,不能
 66             //  继续填充数据了。当两次发送之间MSS发生变化会出现小于0的情况
 67             
 68             //这两种情况中的任意一种发生都只能选择分配新的skb
 69             if (!tcp_send_head(sk) ||
 70                 (copy = size_goal - skb->len) <= 0) {
 71 new_segment:
 72                 /* Allocate new segment. If the interface is SG,
 73                  * allocate skb fitting to single page.
 74                  */
 75                 //即将分配内存,首先检查内存使用是否会超限,如果会要先等待有内存可用
 76                 if (!sk_stream_memory_free(sk))
 77                     goto wait_for_sndbuf;
 78                 //分配skb,select_size()的返回值决定了skb的线性区域大小,见下文
 79                 skb = sk_stream_alloc_skb(sk, select_size(sk), sk->sk_allocation);
 80                 //分配失败,需要等待有剩余内存可用后才能继续发送
 81                 if (!skb)
 82                     goto wait_for_memory;
 83 
 84                 /*
 85                  * Check whether we can use HW checksum.
 86                  */
 87                 //根据硬件能力确定TCP是否需要执行校验工作
 88                 if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
 89                     skb->ip_summed = CHECKSUM_PARTIAL;
 90 
 91                 //将新分配的skb加入到TCB的发送队列中,并且更新相关内存记账信息
 92                 skb_entail(sk, skb);
 93                 //设置本轮要拷贝的数据量为size_goal,因为该skb是新分配的,所以
 94                 //一定可以容纳这么多,但是具体能不能拷贝这么多,还需要看有没有这么
 95                 //多的数据要发送,见下方
 96                 copy = size_goal;
 97             }
 98             //如果skb可以容纳的数据量超过了当前数组元素中已有数据量,那么本轮只拷贝数组元素中已有的数据量
 99             if (copy > seglen)
100                 copy = seglen;
101 
102             /* Where to copy to? */
103             if (skb_tailroom(skb) > 0) {
104                 //如果skb的线性部分还有空间,先填充这部分
105 
106                 //如果线性空间部分小于当前要拷贝的数据量,则调整本轮要拷贝的数据量
107                 /* We have some space in skb head. Superb! */
108                 if (copy > skb_tailroom(skb))
109                     copy = skb_tailroom(skb);
110                 //拷贝数据,如果出错则结束发送过程
111                 if ((err = skb_add_data(skb, from, copy)) != 0)
112                     goto do_fault;
113             } else {
114                 //merge用于指示是否可以将新拷贝的数据和当前skb的最后一个片段合并。如果
115                 //它们在页面内刚好是连续的,那么就可以合并为一个片段
116                 int merge = 0;
117                 //i为当前skb中已经存在的分片个数
118                 int i = skb_shinfo(skb)->nr_frags;
119                 //page指向上一次分配的页面,off指向该页面中的偏移量
120                 struct page *page = TCP_PAGE(sk);
121                 int off = TCP_OFF(sk);
122                 //该函数用于判断该skb最后一个片段是否就是当前页面的最后一部分,如果是,那么新拷贝的
123                 //数据和该片段就可以合并,所以设置merge为1,这样可以节省一个frag_list[]位置
124                 if (skb_can_coalesce(skb, i, page, off) && off != PAGE_SIZE) {
125                     /* We can extend the last page fragment. */
126                     merge = 1;
127                 } else if (i == MAX_SKB_FRAGS || (!i && !(sk->sk_route_caps & NETIF_F_SG))) {
128                     //如果skb中已经容纳的分片已经达到了限定值(条件1),或者网卡不支持SG IO
129                     //那么就不能往skb中添加分片,设置PUSH标志位,然后跳转到new_segment处,
130                     //然后重新分配一个skb,继续拷贝数据
131                     /* Need to add new fragment and cannot
132                      * do this because interface is non-SG,
133                      * or because all the page slots are
134                      * busy. */
135                     tcp_mark_push(tp, skb);
136                     goto new_segment;
137                 } else if (page) {
138                     //如果上一次分配的页面已经使用完了,设定sk_sndpage为NULL
139                     if (off == PAGE_SIZE) {
140                         put_page(page);
141                         TCP_PAGE(sk) = page = NULL;
142                         off = 0;
143                     }
144                 } else
145                     off = 0;
146                 //如果要拷贝的数据量超过了当前页面剩余空间,调整本轮要拷贝的数据量
147                 if (copy > PAGE_SIZE - off)
148                     copy = PAGE_SIZE - off;
149                 //检查拷贝copy字节数据后是否会导致发送内存超标,如果超标需要等待内存可用
150                 if (!sk_wmem_schedule(sk, copy))
151                     goto wait_for_memory;
152                 //如果没有可用页面,则分配一个新的,分配失败则会等待内存可用
153                 if (!page) {
154                     /* Allocate new cache page. */
155                     if (!(page = sk_stream_alloc_page(sk)))
156                         goto wait_for_memory;
157                 }
158                 //拷贝copy字节数据到页面中
159                 err = skb_copy_to_page(sk, from, skb, page, off, copy);
160                 //拷贝失败处理
161                 if (err) {
162                     //虽然本次拷贝失败了,但是如果页面是新分配的,也不会收回了,
163                     //而是将其继续指派给当前TCB,这样下次发送就可以直接使用了
164                     if (!TCP_PAGE(sk)) {
165                         TCP_PAGE(sk) = page;
166                         TCP_OFF(sk) = 0;
167                     }
168                     goto do_error;
169                 }
170 
171                 //更新skb中相关指针、计数信息
172                 if (merge) {
173                     //因为可以和最后一个分片合并,所以只需要更新该分片的大小即可
174                     skb_shinfo(skb)->frags[i - 1].size += copy;
175                 } else {
176                     //占用一个新的frag_list[]元素
177                     skb_fill_page_desc(skb, i, page, off, copy);
178                     if (TCP_PAGE(sk)) {
179                         //如果是旧页面,但是因为新分配了片段,所以累加对页面的引用计数
180                         //从这里可以看出,skb中的每个片段都会持有一个对页面的引用计数
181                         get_page(page);
182                     } else if (off + copy < PAGE_SIZE) {
183                         //页面是新分配的,并且本次拷贝没有将页面用完,所以持有页面的
184                         //引用计数,然后将页面指定给sk_sndmsg_page字段,下次可以继续使用
185                         get_page(page);
186                         TCP_PAGE(sk) = page;
187                     }
188                 }
189                 //设置sk_sndmsg_off的偏移量
190                 TCP_OFF(sk) = off + copy;
191             }//end of 'else'
192 
193             //如果本轮是第一次拷贝,清除PUSH标记
194             if (!copied)
195                 TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;
196             //write_seq记录的是发送队列中下一个要分配的序号,所以这里需要更新它
197             tp->write_seq += copy;
198             //更新该数据包的最后一个字节的序号
199             TCP_SKB_CB(skb)->end_seq += copy;
200             skb_shinfo(skb)->gso_segs = 0;
201 
202             //用户空间缓存区指针前移
203             from += copy;
204             //累加已经拷贝字节数
205             copied += copy;
206             //如果所有要发送的数据都拷贝完了,结束发送过程
207             if ((seglen -= copy) == 0 && iovlen == 0)
208                 goto out;
209             //如果该skb没有填满,继续下一轮拷贝
210             if (skb->len < size_goal || (flags & MSG_OOB))
211                 continue;
212             //如果需要设置PUSH标志位,那么设置PUSH,然后发送数据包,可将PUSH可以让TCP尽快的发送数据
213             if (forced_push(tp)) {
214                 tcp_mark_push(tp, skb);
215                 //尽可能的将发送队列中的skb发送出去,禁用nalge
216                 __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
217             } else if (skb == tcp_send_head(sk))
218                 //当前只有这一个skb,也发送出去。因为只有一个,所以肯定也不存在拥塞,可以发送
219                 tcp_push_one(sk, mss_now);
220 
221             //继续拷贝数据
222             continue;
223 
224 wait_for_sndbuf:
225             //设置套接字结构中发送缓存不足的标志
226             set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
227 wait_for_memory:
228             //如果已经有数据拷贝到了发送缓存中,那么调用tcp_push()立即发送,这样可能可以
229             //让发送缓存快速的有剩余空间可用
230             if (copied)
231                 tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
232             //等待有空余内存可以使用,如果timeo不为0,那么这一步会休眠
233             if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
234                 goto do_error;
235             //睡眠后MSS可能发生了变化,所以重新计算
236             mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));
237             size_goal = tp->xmit_size_goal;
238         }//end of 'while (seglen > 0)',内层循环
239     }//end of 'while (--iovlen >= 0)',外层循环

深入理解TCP协议及其源代码

发送数据至ip层的处理


 

三次握手的具体实现过程:

首先是客户端需要发送SYN=1,seq=x的数据段的实现,此函数从sys_connect()入手。

整个发送的调用过程为:tcp_v4_connect()->tcp_connect()->tcp_transmit_skb(),在发送结束后置状态为TCP_SYN_SENT。

截取发送过中的的代码:

 1:  /* 构造并发送SYN段 */
 2:  int tcp_connect(struct sock *sk)
 3:  {
 4:      struct tcp_sock *tp = tcp_sk(sk);
 5:      struct sk_buff *buff;
 6:  
 7:      tcp_connect_init(sk);/* 初始化传输控制块中与连接相关的成员 */
 8:  
 9:      /* 为SYN段分配报文并进行初始化 */
10:      buff = alloc_skb(MAX_TCP_HEADER + 15, sk->sk_allocation);
11:      if (unlikely(buff == NULL))
12:          return -ENOBUFS;
13:  
14:      /* Reserve space for headers. */
15:      skb_reserve(buff, MAX_TCP_HEADER);
16:  
17:      TCP_SKB_CB(buff)->flags = TCPCB_FLAG_SYN;
18:      TCP_ECN_send_syn(sk, tp, buff);
19:      TCP_SKB_CB(buff)->sacked = 0;
20:      skb_shinfo(buff)->tso_segs = 1;
21:      skb_shinfo(buff)->tso_size = 0;
22:      buff->csum = 0;
23:      TCP_SKB_CB(buff)->seq = tp->write_seq++;
24:      TCP_SKB_CB(buff)->end_seq = tp->write_seq;
25:      tp->snd_nxt = tp->write_seq;
26:      tp->pushed_seq = tp->write_seq;
27:      tcp_ca_init(tp);
28:  
29:      /* Send it off. */
30:      TCP_SKB_CB(buff)->when = tcp_time_stamp;
31:      tp->retrans_stamp = TCP_SKB_CB(buff)->when;
32:  
33:      /* 将报文添加到发送队列上 */
34:      __skb_queue_tail(&sk->sk_write_queue, buff);
35:      sk_charge_skb(sk, buff);
36:      tp->packets_out += tcp_skb_pcount(buff);
37:      /* 发送SYN段 */
38:      tcp_transmit_skb(sk, skb_clone(buff, GFP_KERNEL));
39:      TCP_INC_STATS(TCP_MIB_ACTIVEOPENS);
40:  
41:      /* Timer for repeating the SYN until an answer. */
42:      /* 启动重传定时器 */
43:      tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
44:      return 0;
45:  }

之后服务器在收到SYN数据段时,处理入口就是上边讲述过的tcp_v4_do_rcv()。

整个接收数据处理并发送的调用过程为:tcp_v4_do_rcv()->tcp_rcv_state_process()->tcp_v4_conn_request()->tcp_v4_send_synack().

其中tcp_v4_send_synack() 完成构建SYN+ACK段 *,生成IP数据报并发送出去 。【源代码如下】

 1:  /* 向客户端发送SYN+ACK报文 */
 2:  static int tcp_v4_send_synack(struct sock *sk, struct open_request *req,
 3:                    struct dst_entry *dst)
 4:  {
 5:      int err = -1;
 6:      struct sk_buff * skb;
 7:  
 8:      /* First, grab a route. */
 9:      /* 查找到客户端的路由 */
10:      if (!dst && (dst = tcp_v4_route_req(sk, req)) == NULL)
11:          goto out;
12:  
13:      /* 根据路由、传输控制块、连接请求块中的构建SYN+ACK段 */
14:      skb = tcp_make_synack(sk, dst, req);
15:  
16:      if (skb) {/* 生成SYN+ACK段成功 */
17:          struct tcphdr *th = skb->h.th;
18:  
19:          /* 生成校验码 */
20:          th->check = tcp_v4_check(th, skb->len,
21:                       req->af.v4_req.loc_addr,
22:                       req->af.v4_req.rmt_addr,
23:                       csum_partial((char *)th, skb->len,
24:                                skb->csum));
25:  
26:          /* 生成IP数据报并发送出去 */
27:          err = ip_build_and_send_pkt(skb, sk, req->af.v4_req.loc_addr,
28:                          req->af.v4_req.rmt_addr,
29:                          req->af.v4_req.opt);
30:          if (err == NET_XMIT_CN)
31:              err = 0;
32:      }
33:  
34:  out:
35:      dst_release(dst);
36:      return err;
37:  }
38:  

客户端回复确认ACK段

处理的入口函数同样为上述的tcp_v4_do_rcv()

服务器端的接收并发送数据段的的调用过程:tcp_v4_do_rcv()->tcp_rcv_state_process().

在发送结束后,客户端处于TCP_SYN_SENT状态。

根据TCP_SYN_SENT状态,在tcp_v4_do_rcv()调用的处理为:tcp_rcv_synsent_state_process()

  /* 在SYN_SENT状态下处理接收到的段,但是不处理带外数据 */
    static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
                       struct tcphdr *th, unsigned len)
    {
     if (th->ack) {/* 处理ACK标志 */
         /* rfc3
          * "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;
         }
         /* rfc
          *   "fifth, if neither of the SYN or RST bits is set then
          *    drop the segment and return."
          *
          *    See note below!
          *                                        --ANK()
          */
         if (!th->syn)/* 在SYN_SENT状态下接收到的段必须存在SYN标志,否则说明接收到的段无效,丢弃该段 */
             goto discard_and_undo;
   discard:
            __kfree_skb(skb);
            return ;
        } else {/*tcp_send_ack();在主动连接时,向服务器端发送ACK完成连接,并更新窗口 
                  alloc_skb();构造ack段
                  tcp_transmit_skb(); * 将ack段发出 *



            tcp_send_ack(sk);
        }
        return -1;
    }
    /* 在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 */

服务端收到ACK段

处理入口仍然为上述tcp_v4_do_rcv(),

处理的调用过程为tcp_v4_do_rcv()->tcp_rcv_state_process().

处理结束后当前服务端处于TCP_SYN_RECV状态变为TCP_ESTABLISHED状态。

  1:  /* 除了ESTABLISHED和TIME_WAIT状态外,其他状态下的TCP段处理都由本函数实现 */ 
  2:  int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
  3:                struct tcphdr *th, unsigned len)
  4:  {
  5:      struct tcp_sock *tp = tcp_sk(sk);
  6:      int queued = 0;
  7:  
  8:      tp->rx_opt.saw_tstamp = 0;
  9:  
 10:      switch (sk->sk_state) {
 11:      .....
 12:      /* SYN_RECV状态的处理 */
 13:      if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&/* 解析TCP选项,如果首部中存在时间戳选项 */
 14:          tcp_paws_discard(tp, skb)) {/* PAWS检测失败,则丢弃报文 */
 15:          if (!th->rst) {/* 如果不是RST段 */
 16:              /* 发送DACK给对端,说明接收到的TCP段已经处理过 */
 17:              NET_INC_STATS_BH(LINUX_MIB_PAWSESTABREJECTED);
 18:              tcp_send_dupack(sk, skb);
 19:              goto discard;
 20:          }
 21:          /* Reset is accepted even if it did not pass PAWS. */
 22:      }
 23:  
 24:      /* step 1: check sequence number */
 25:      if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {/* TCP段序号无效 */
 26:          if (!th->rst)/* 如果TCP段无RST标志,则发送DACK给对方 */
 27:              tcp_send_dupack(sk, skb);
 28:          goto discard;
 29:      }
 30:  
 31:      /* step 2: check RST bit */
 32:      if(th->rst) {/* 如果有RST标志,则重置连接 */
 33:          tcp_reset(sk);
 34:          goto discard;
 35:      }
 36:  
 37:      /* 如果有必要,则更新时间戳 */
 38:      tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);
 39:  
 40:      /* step 3: check security and precedence [ignored] */
 41:  
 42:      /*  step 4:
 43:       *
 44:       *  Check for a SYN in window.
 45:       */
 46:      if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {/* 如果有SYN标志并且序号在接收窗口内 */
 47:          NET_INC_STATS_BH(LINUX_MIB_TCPABORTONSYN);
 48:          tcp_reset(sk);/* 复位连接 */
 49:          return 1;
 50:      }
 51:  
 52:      /* step 5: check the ACK field */
 53:      if (th->ack) {/* 如果有ACK标志 */
 54:          /* 检查ACK是否为正常的第三次握手 */
 55:          int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);
 56:  
 57:          switch(sk->sk_state) {
 58:          case TCP_SYN_RECV:
 59:              if (acceptable) {
 60:                  tp->copied_seq = tp->rcv_nxt;
 61:                  mb();
 62:                  /* 正常的第三次握手,设置连接状态为TCP_ESTABLISHED */
 63:                  tcp_set_state(sk, TCP_ESTABLISHED);
 64:                  sk->sk_state_change(sk);
 65:  
 66:                  /* Note, that this wakeup is only for marginal
 67:                   * crossed SYN case. Passively open sockets
 68:                   * are not waked up, because sk->sk_sleep ==
 69:                   * NULL and sk->sk_socket == NULL.
 70:                   */
 71:                  if (sk->sk_socket) {/* 状态已经正常,唤醒那些等待的线程 */
 72:                      sk_wake_async(sk,0,POLL_OUT);
 73:                  }
 74:  
 75:                  /* 初始化传输控制块,如果存在时间戳选项,同时平滑RTT为0,则需计算重传超时时间 */
 76:                  tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
 77:                  tp->snd_wnd = ntohs(th->window) <<
 78:                            tp->rx_opt.snd_wscale;
 79:                  tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq,
 80:                          TCP_SKB_CB(skb)->seq);
 81:  
 82:                  /* tcp_ack considers this ACK as duplicate
 83:                   * and does not calculate rtt.
 84:                   * Fix it at least with timestamps.
 85:                   */
 86:                  if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
 87:                      !tp->srtt)
 88:                      tcp_ack_saw_tstamp(tp, 0);
 89:  
 90:                  if (tp->rx_opt.tstamp_ok)
 91:                      tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
 92:  
 93:                  /* Make sure socket is routed, for
 94:                   * correct metrics.
 95:                   */
 96:                  /* 建立路由,初始化拥塞控制模块 */
 97:                  tp->af_specific->rebuild_header(sk);
 98:  
 99:                  tcp_init_metrics(sk);
100:  
101:                  /* Prevent spurious tcp_cwnd_restart() on
102:                   * first data packet.
103:                   */
104:                  tp->lsndtime = tcp_time_stamp;/* 更新最近一次发送数据包的时间 */
105:  
106:                  tcp_initialize_rcv_mss(sk);
107:                  tcp_init_buffer_space(sk);
108:                  tcp_fast_path_on(tp);/* 计算有关TCP首部预测的标志 */
109:              } else {
110:                  return 1;
111:              }
112:              break;
113:          .....
114:          }
115:      } else
116:          goto discard;
117:      .....
118:  
119:      /* step 6: check the URG bit */
120:      tcp_urg(sk, skb, th);/* 检测带外数据位 */
121:  
122:      /* tcp_data could move socket to TIME-WAIT */
123:      if (sk->sk_state != TCP_CLOSE) {/* 如果tcp_data需要发送数据和ACK则在这里处理 */
124:          tcp_data_snd_check(sk);
125:          tcp_ack_snd_check(sk);
126:      }
127:  
128:      if (!queued) { /* 如果段没有加入队列,或者前面的流程需要释放报文,则释放它 */
129:  discard:
130:          __kfree_skb(skb);
131:      }
132:      return 0;

至此三次握手就处理完毕了。

三次握手核心函数的断点如图所示:

 深入理解TCP协议及其源代码

可使用wireshark通过抓取localhost的包来验证三次握手的环节【由于不是很熟悉操作,没有做】

 

 

至此就分析完毕了。

上一篇:python 网络编程


下一篇:Impala/Presto/ES/kudu/Parquet TPC_DS基准测试