深入理解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實現客戶端與服務器端的通信。

 

 

 那么具體的通信過程是怎么樣的?

使用ipv4時,所有與TCP文件都在都在 net/ipv4/ -directory 目錄下。摘選其中重要的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即可。這樣做就可以避免數據的復制移動,處理更為高效。

 

數據包在各層之間傳遞在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的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)',外層循環

發送數據至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;

至此三次握手就處理完畢了。

三次握手核心函數的斷點如圖所示:

 

可使用wireshark通過抓取localhost的包來驗證三次握手的環節【由於不是很熟悉操作,沒有做】

 

 

至此就分析完畢了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM