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的包來驗證三次握手的環節【由於不是很熟悉操作,沒有做】
至此就分析完畢了。