linux 內核tcp數據發送的實現

在分析之前先來看下SO_RCVTIMEO和SO_SNDTIMEO套接口吧,前面分析代碼時沒太注意這兩個.這裏算是個補充. 

SO_RCVTIMEO和SO_SNDTIMEO套接口選項可以給套接口的讀和寫,來設置超時時間,在unix網絡編程中,說是他們只能用於讀和寫,而像accept和connect都不能用他們來設置.可是我在閱讀內核源碼的過程中看到,在linux中,accept和connect可以分別用SO_RCVTIMEO和SO_SNDTIMEO套接口來設置超時,這裏他們的超時時間也就是sock的sk_rcvtimeo和sk_sndtimeo域.accept和connect的相關代碼我前面都介紹過了,這裏再提一下.其中accept的相關部分在inet_csk_accept中,會調用sock_rcvtimeo來取得超時時間(如果是非阻塞則忽略超時間).而connect的相關代碼在inet_stream_connect中通過調用sock_sndtimeo來取得超時時間(如果非阻塞則忽略超時時間). 

--------------------------------------------------------------------------------- 
tcp發送數據最終都會調用到tcp_sendmsg,舉個例子吧,比如send系統調用. 

send系統調用會z直接調用sys_sendto,然後填充msghdr數據結構,並調用sock_sendmsg,而在他中,則最終會調用__sock_sendmsg.在這個函數裏面會初始化sock_iocb結構,然後調用tcp_sendmsg. 

在sys_sendto中還會做和前面幾個系統調用差不多的操作,就是通過fd得到socket,在sock_sendmsg中則會設置aio所需的操作. 

我們簡要的看下__sock_sendmsg的實現.可以看到在內核中數據都是用msghdr來表示的(也就是會將char *轉爲msghdr),而這個結構這裏就不介紹了,unix網絡編程裏面有詳細的介紹.而struct kiocb則是aio會用到的. 

Java代碼  收藏代碼
  1. static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,  
  2.                  struct msghdr *msg, size_t size)  
  3. {  
  4.     struct sock_iocb *si = kiocb_to_siocb(iocb);  
  5.     int err;  
  6.   
  7.     si->sock = sock;  
  8.     si->scm = NULL;  
  9.     si->msg = msg;  
  10.     si->size = size;  
  11.   
  12.     err = security_socket_sendmsg(sock, msg, size);  
  13.     if (err)  
  14.         return err;  
  15.   
  16. ///這裏就會調用tcp_sendmsg.  
  17.     return sock->ops->sendmsg(iocb, sock, msg, size);  
  18. }  


我們在前面知道tcp將數據傳遞給ip層的時候調用ip_queue_xmit,而在這個函數沒有做任何切片的工作,切片的工作都在tcp層完成了.而udp則是需要在ip層進行切片(通過ip_append_data). 而tcp的數據是字節流的,因此在 
tcp_sendmsg中主要做的工作就是講字節流分段(根據mss),然後傳遞給ip層. 可以看到它的任務和ip_append_data很類似,流程其實也差不多. 所以有興趣的可以看下我前面的blog 


而在tcp_sendmsg中也是要看網卡是否支持Scatter/Gather I/O,從而進行相關操作. 



下面我們來看它的實現,我們分段來看: 



Java代碼  收藏代碼
  1. ///首先取出句柄的flag,主要是看是非阻塞還是阻塞模式.  
  2. flags = msg->msg_flags;  
  3. ///這裏取得發送超時時間.  
  4.     timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);  
  5.   
  6. ///如果connect還沒有完成則等待連接完成(如是非阻塞則直接返回).  
  7.     if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))  
  8.         if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)  
  9.             goto out_err;  
  10.   
  11.     /* This should be in poll */  
  12.     clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);  
  13. ///取出當前的mss,在tcp_current_mss還會設置xmit_size_goal,這個值一般都是等於mss,除非有gso的情況下,有所不同.這裏我們就認爲他是和mms相等的.  
  14.     mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));  
  15.     size_goal = tp->xmit_size_goal;  



在取得了相關的值之後我們進入循環處理msg,我們知道msghdr有可能是包含很多buffer的,因此這裏我們分爲兩層循環,一層是遍歷msg的buffer,一層是對buffer進行處理(切包或者組包)併發送給ip層. 

首先來看當buf空間不夠時的情況,它這裏判斷buf空間是否足夠是通過 

Java代碼  收藏代碼
  1. !tcp_send_head(sk) ||  
  2.                 (copy = size_goal - skb->len) <= 0  


來判斷的,這裏稍微解釋下這個: 

這裏tcp_send_head返回值爲sk->sk_send_head,也就是指向當前的將要發送的buf的位置.如果爲空,則說明buf沒有空間,我們就需要alloc一個段來保存將要發送的msg. 

而skb->len指的是當前的skb的所包含的數據的大小(包含頭的大小).而這個值如果大於size_goal,則說明buf已滿,我們需要重新alloc一個端.如果小於size_goal,則說明buf還有空間來容納一些數據來組成一個等於mss的數據包再發送給ip層. 

Java代碼  收藏代碼
  1.     /* Ok commence sending. */  
  2.     iovlen = msg->msg_iovlen;  
  3.     iov = msg->msg_iov;  
  4. ///copy的大小  
  5.     copied = 0;  
  6.   
  7.     err = -EPIPE;  
  8. ///如果發送端已經完全關閉則返回,並設置errno.  
  9.     if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))  
  10.         goto do_error;  
  11.   
  12.   
  13. while (--iovlen >= 0) {  
  14. ///取得當前buf長度  
  15.         int seglen = iov->iov_len;  
  16. ///buf的基地址.  
  17.         unsigned char __user *from = iov->iov_base;  
  18.         iov++;  
  19.         while (seglen > 0) {  
  20.             int copy;  
  21. ///我們知道sock的發送隊列sk_write_queue是一個雙向鏈表,而用tcp_write_queue_tail則是取得鏈表的最後一個元素.(如果鏈表爲空則返回NULL).  
  22.   
  23.             skb = tcp_write_queue_tail(sk);  
  24.   
  25. ///上面介紹過了.主要是判斷buf是否有空閒空間.  
  26.             if (!tcp_send_head(sk) ||  
  27.                 (copy = size_goal - skb->len) <= 0) {  
  28.   
  29. new_segment:  
  30. ///開始alloc一個新的段.  
  31.                 if (!sk_stream_memory_free(sk))  
  32.                     goto wait_for_sndbuf;  
  33. ///alloc的大小一般都是等於mss的大小,這裏是通過select_size得到的.  
  34.                 skb = sk_stream_alloc_skb(sk, select_size(sk),  
  35.                         sk->sk_allocation);  
  36.                 if (!skb)  
  37.                     goto wait_for_memory;  
  38.                 /* 
  39.                  * Check whether we can use HW checksum. 
  40.                  */  
  41.                 if (sk->sk_route_caps & NETIF_F_ALL_CSUM)  
  42.                     skb->ip_summed = CHECKSUM_PARTIAL;  
  43. ///將這個skb加入到sk_write_queue隊列中,並更新sk_send_head域.  
  44.                 skb_entail(sk, skb);  
  45. ///將copy值更新.  
  46.                 copy = size_goal;  
  47.             }  



接下來如果走到這裏,則說明 要麼已經alloc一個新的buf,要麼當前的buf中還有空閒空間. 
這裏先來分析alloc一個新的buf的情況. 

這裏先看下skb中的幾個域的含義: 


 

head and end 指的是alloc了的buf的起始和終止位置,而data and tail 指的是數據段的起始和終止位置,因此經過每一層tail和data都會變化的,而初始值這兩個是相等的. 

我們來看skb_tailroom,它主要是用來判斷得到當前的skb的tailroom的大小.tailroom也就是當前buf的剩餘數據段的大小,這裏也就是用來判斷當前buf是否能夠再添加數據. 

Java代碼  收藏代碼
  1. static inline int skb_is_nonlinear(const struct sk_buff *skb)  
  2. {  
  3.     return skb->data_len;  
  4. }  
  5. static inline int skb_tailroom(const struct sk_buff *skb)  
  6. {  
  7. ///如果是新alloc的skb則會返回tailroom否則返回0  
  8.     return skb_is_nonlinear(skb) ? 0 : skb->end - skb->tail;  
  9. }  


接下來來看代碼: 


Java代碼  收藏代碼
  1. while (--iovlen >= 0) {  
  2. ...........................  
  3.         while (seglen > 0) {  
  4.   
  5. ///如果copy大於buf的大小,則縮小copy.  
  6.             if (copy > seglen)  
  7.                 copy = seglen;  
  8. ///這裏查看skb的空間.如果大於0,則說明是新建的skb.  
  9.             if (skb_tailroom(skb) > 0) {  
  10. ///如果需要複製的數據大於所剩的空間,則先複製當前skb所能容納的大小.  
  11.                 if (copy > skb_tailroom(skb))  
  12.                     copy = skb_tailroom(skb);  
  13. ///複製數據到sk_buff.大小爲copy.如果成功進入do_fault,(我們下面會分析)  
  14.                 if ((err = skb_add_data(skb, from, copy)) != 0)  
  15.                     goto do_fault;  
  16.             }   



如果走到這一步,當前的sk buff中有空閒空間 也分兩種情況,一種是 設備支持Scatter/Gather I/O(原理和udp的ip_append_data一樣,可以看我以前的blog). 


另外一種情況是設備不支持S/G IO,可是mss變大了.這種情況下我們需要返回new_segment,新建一個段,然後再處理. 
\我建議在看這段代碼前,可以看下我前面blog分析ip_append_data的那篇.因爲那裏對S/G IO的設備處理切片的分析比較詳細,而這裏和那邊處理基本類似.這裏我對frags的操作什麼的都是很簡單的描述,詳細的在ip_append_data那裏已經描述過. 


然後再來了解下PSH標記,這個標記主要是用來使接收方將sk->receive_queue上緩存的skb提交給用戶進程.詳細的介紹可以看tcp協議的相關部分(推功能).在這裏設置這個位會有兩種情況,第一種是我們寫了超過一半窗口大小的數據,此時我們需要標記最後一個段的PSH位.或者我們有一個完整的tcp段發送出去,此時我們也需要標記pSH位. 


Java代碼  收藏代碼
  1. while (--iovlen >= 0) {  
  2. ...........................  
  3.         while (seglen > 0) {  
  4. ...............................  
  5.   
  6. else {  
  7.   
  8.             int merge = 0;  
  9. ///取得nr_frags也就是保存物理頁的數組.  
  10.                 int i = skb_shinfo(skb)->nr_frags;  
  11. ///從socket取得當前的發送物理頁.  
  12.                 struct page *page = TCP_PAGE(sk);  
  13. ///取得當前頁的位移.  
  14.                 int off = TCP_OFF(sk);  
  15. ///這裏主要是判斷skb的發送頁是否已經存在於nr_frags中,如果存在並且也沒有滿,則我們只需要將數據合併到這個頁就可以了,而不需要在frag再添加一個頁.  
  16.                 if (skb_can_coalesce(skb, i, page, off) &&  
  17.                     off != PAGE_SIZE) {  
  18.                     merge = 1;  
  19.                 } else if (i == MAX_SKB_FRAGS ||  
  20.                        (!i &&  
  21.                        !(sk->sk_route_caps & NETIF_F_SG))) {  
  22. ///到這裏說明要麼設備不支持SG IO,要麼頁已經滿了.因爲我們知道nr_frags的大小是有限制的.此時調用tcp_mark_push來加一個PSH標記.  
  23.                     tcp_mark_push(tp, skb);  
  24.                     goto new_segment;  
  25.                 } else if (page) {  
  26.                     if (off == PAGE_SIZE) {  
  27. ///這裏說明當前的發送頁已滿.  
  28.                         put_page(page);  
  29.                         TCP_PAGE(sk) = page = NULL;  
  30.                         off = 0;  
  31.                     }  
  32.                 } else  
  33.                     off = 0;  
  34.   
  35.                 if (copy > PAGE_SIZE - off)  
  36.                     copy = PAGE_SIZE - off;  
  37. .................................  
  38. ///如果page爲NULL則需要新alloc一個物理頁.  
  39.             if (!page) {  
  40.                     /* Allocate new cache page. */  
  41.                     if (!(page = sk_stream_alloc_page(sk)))  
  42.                         goto wait_for_memory;  
  43.                 }  
  44. ///開始複製數據到這個物理頁.  
  45.                 err = skb_copy_to_page(sk, from, skb, page,  
  46.                                off, copy);  
  47.                 if (err) {  
  48. ///出錯的情況.  
  49.                     if (!TCP_PAGE(sk)) {  
  50.                         TCP_PAGE(sk) = page;  
  51.                         TCP_OFF(sk) = 0;  
  52.                     }  
  53.                     goto do_error;  
  54.                 }  
  55.   
  56. ///判斷是否爲新建的物理頁.  
  57.                 if (merge) {  
  58. ///如果只是在存在的物理頁添加數據,則只需要更新size  
  59.                     skb_shinfo(skb)->frags[i - 1].size +=  
  60.                                     copy;  
  61.                 } else {  
  62. ///負責添加此物理頁到skb的frags.  
  63.                     skb_fill_page_desc(skb, i, page, off, copy);  
  64.                     if (TCP_PAGE(sk)) {  
  65. ///設置物理頁的引用計數.  
  66.                         get_page(page);  
  67.                     } else if (off + copy < PAGE_SIZE) {  
  68.                         get_page(page);  
  69.                         TCP_PAGE(sk) = page;  
  70.                     }  
  71.                 }  
  72. ///設置位移.  
  73.                 TCP_OFF(sk) = off + copy;  
  74.             }  



數據複製完畢,接下來就該發送數據了. 

這裏我們要知道幾個tcp_push,tcp_one_push最終都會調用__tcp_push_pending_frames,而在它中間最終會調用tcp_write_xmit,而tcp_write_xmit則會調用tcp_transmit_skb,這個函數最終會調用ip_queue_xmit來講數據發送給ip層.這裏要注意,我們這裏的分析忽略掉了,tcp的一些管理以及信息交互的過程. 


接下來看數據傳輸之前先來分析下TCP_PUSH幾個函數的實現,tcp_push這幾個類似函數的最後一個參數都是一個控制nagle算法的參數,來看下這幾個函數的原型: 

Java代碼  收藏代碼
  1. static inline void tcp_push(struct sock *sk, int flags, int mss_now,  
  2.                 int nonagle)  
  3.   
  4. void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,  
  5.                    int nonagle)  
  6.   
  7. static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)  


我們還要知道tcp sock有一個nonagle域,這個域是會被tcp_cork套接口選項時被設置爲TCP_NAGLE_CORK .先來看tcp_push的實現: 


Java代碼  收藏代碼
  1. static inline void tcp_push(struct sock *sk, int flags, int mss_now,  
  2.                 int nonagle)  
  3. {  
  4.     struct tcp_sock *tp = tcp_sk(sk);  
  5.   
  6.     if (tcp_send_head(sk)) {  
  7.         struct sk_buff *skb = tcp_write_queue_tail(sk);  
  8. ///MSG_MORE這個參數我們在ip_append_data那裏已經介紹過了,就是告訴ip層,我這裏主要是一些小的數據包,然後ip層就會提前劃分一個mtu大小的buf,然後等待數據的到來.因此如果沒有設置這個或者forced_push返回真(我們寫了超過最大窗口一般的數據),就標記一個PSH.  
  9.         if (!(flags & MSG_MORE) || forced_push(tp))  
  10.             tcp_mark_push(tp, skb);  
  11.         tcp_mark_urg(tp, flags, skb);  
  12. ///這裏還是根據是否有設置MSG_MORE來判斷使用哪個flags.因此可以看到如果我們設置了tcp_cork套接字選項和設置msg的MSG_MORE比較類似.最終調用tcp_push都會傳遞給__tcp_push_pending_frames的參數爲TCP_NAGLE_CORK .  
  13.         __tcp_push_pending_frames(sk, mss_now,  
  14.                       (flags & MSG_MORE) ? TCP_NAGLE_CORK : nonagle);  
  15.     }  
  16. }  


在看tcp_write_xmit之前,我們先來看下tcp_nagle_test,這個函數主要用來檢測nagle算法.如果當前允許數據段立即被髮送,則返回1,否則爲0. 

Java代碼  收藏代碼
  1. ///這個函數就不介紹了,內核的註釋很詳細.  
  2.   
  3. /* Return 0, if packet can be sent now without violation Nagle's rules: 
  4.  * 1. It is full sized. 
  5.  * 2. Or it contains FIN. (already checked by caller) 
  6.  * 3. Or TCP_NODELAY was set. 
  7.  * 4. Or TCP_CORK is not set, and all sent packets are ACKed. 
  8.  *    With Minshall's modification: all sent small packets are ACKed. 
  9.  */  
  10. static inline int tcp_nagle_check(const struct tcp_sock *tp,  
  11.                   const struct sk_buff *skb,  
  12.                   unsigned mss_now, int nonagle)  
  13. {  
  14.     return (skb->len < mss_now &&  
  15.         ((nonagle & TCP_NAGLE_CORK) ||  
  16.          (!nonagle && tp->packets_out && tcp_minshall_check(tp))));  
  17. }  
  18. static inline int tcp_nagle_test(struct tcp_sock *tp, struct sk_buff *skb,  
  19.                  unsigned int cur_mss, int nonagle)  
  20. {  
  21. ///如果設置了TCP_NAGLE_PUSH則返回1,也就是數據可以立即發送  
  22.     if (nonagle & TCP_NAGLE_PUSH)  
  23.         return 1;  
  24.   
  25.     /* Don't use the nagle rule for urgent data (or for the final FIN). 
  26.      * Nagle can be ignored during F-RTO too (see RFC4138). 
  27.      */  
  28.     if (tcp_urg_mode(tp) || (tp->frto_counter == 2) ||  
  29.         (TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN))  
  30.         return 1;  
  31. ///再次檢測 nonagle域,相關的檢測,上面已經說明了.  
  32.     if (!tcp_nagle_check(tp, skb, cur_mss, nonagle))  
  33.         return 1;  
  34.   
  35.     return 0;  
  36. }  


然後看下tcp_write_xmit的實現, 

Java代碼  收藏代碼
  1. static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)  
  2. {  
  3.     struct tcp_sock *tp = tcp_sk(sk);  
  4.     struct sk_buff *skb;  
  5.     unsigned int tso_segs, sent_pkts;  
  6.     int cwnd_quota;  
  7.     int result;  
  8. ///檢測狀態.  
  9.     if (unlikely(sk->sk_state == TCP_CLOSE))  
  10.         return 0;  
  11.   
  12.     sent_pkts = 0;  
  13.   
  14. ///探測mtu.  
  15.     if ((result = tcp_mtu_probe(sk)) == 0) {  
  16.         return 0;  
  17.     } else if (result > 0) {  
  18.         sent_pkts = 1;  
  19.     }  
  20.   
  21. ///開始處理數據包.  
  22.     while ((skb = tcp_send_head(sk))) {  
  23.         unsigned int limit;  
  24.   
  25.         tso_segs = tcp_init_tso_segs(sk, skb, mss_now);  
  26.         BUG_ON(!tso_segs);  
  27. ///主要用來測試congestion window..  
  28.         cwnd_quota = tcp_cwnd_test(tp, skb);  
  29.         if (!cwnd_quota)  
  30.             break;  
  31.   
  32.         if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))  
  33.             break;  
  34.   
  35.         if (tso_segs == 1) {  
  36. ///主要看這裏,如果這個skb是寫隊列的最後一個buf,則傳輸TCP_NAGLE_PUSH給tcp_nagle_test,這個時侯直接返回1,於是接着往下面走,否則則說明數據包不要求理解發送,我們就跳出循環(這時數據段就不會被髮送).比如設置了TCP_CORK.  
  37.             if (unlikely(!tcp_nagle_test(tp, skb, mss_now,  
  38.                              (tcp_skb_is_last(sk, skb) ?  
  39.                               nonagle : TCP_NAGLE_PUSH))))  
  40.                 break;  
  41.         } else {  
  42.             if (tcp_tso_should_defer(sk, skb))  
  43.                 break;  
  44.         }  
  45.   
  46.         limit = mss_now;  
  47.         if (tso_segs > 1 && !tcp_urg_mode(tp))  
  48.             limit = tcp_mss_split_point(sk, skb, mss_now,  
  49.                             cwnd_quota);  
  50.   
  51.         if (skb->len > limit &&  
  52.             unlikely(tso_fragment(sk, skb, limit, mss_now)))  
  53.             break;  
  54.   
  55.         TCP_SKB_CB(skb)->when = tcp_time_stamp;  
  56. ///傳輸數據給3層.  
  57.         if (unlikely(tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC)))  
  58.             break;  
  59.   
  60.         /* Advance the send_head.  This one is sent out. 
  61.          * This call will increment packets_out. 
  62.          */  
  63.         tcp_event_new_data_sent(sk, skb);  
  64.   
  65.         tcp_minshall_update(tp, mss_now, skb);  
  66.         sent_pkts++;  
  67.     }  
  68.   
  69.     if (likely(sent_pkts)) {  
  70.         tcp_cwnd_validate(sk);  
  71.         return 0;  
  72.     }  
  73.     return !tp->packets_out && tcp_send_head(sk);  
  74. }  



然後返回來,來看剛纔緊接着的實現: 

Java代碼  收藏代碼
  1. while (--iovlen >= 0) {  
  2. ...........................  
  3.         while (seglen > 0) {  
  4. ...............................  
  5.   
  6. ///如果第一次組完一個段,則設置PSH.  
  7.             if (!copied)  
  8.                 TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;  
  9. ///然後設置寫隊列長度.  
  10.             tp->write_seq += copy;  
  11.             TCP_SKB_CB(skb)->end_seq += copy;  
  12.             skb_shinfo(skb)->gso_segs = 0;  
  13. ///更新buf基地址以及複製的buf大小.  
  14.             from += copy;  
  15.             copied += copy;  
  16. ///buf已經複製完則退出循環.併發送這個段.  
  17.             if ((seglen -= copy) == 0 && iovlen == 0)  
  18.                 goto out;  
  19. ///如果skb的數據大小小於所需拷貝的數據大小或者存在帶外數據,我們繼續循環,而當存在帶外數據時,我們接近着的循環會退出循環,然後調用tcp_push將數據發出.  
  20.             if (skb->len < size_goal || (flags & MSG_OOB))  
  21.                 continue;  
  22. ///forced_push用來判斷我們是否已經寫了多於一半窗口大小的數據到對端.如果是,我們則要發送一個推數據(PSH).  
  23.             if (forced_push(tp)) {  
  24.                 tcp_mark_push(tp, skb);  
  25. ///調用__tcp_push_pending_frames將開啓NAGLE算法的緩存的段全部發送出去.  
  26.                 __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);  
  27.             } else if (skb == tcp_send_head(sk))  
  28. ///如果當前將要發送的buf剛好爲skb,則會傳發送當前的buf  
  29.                 tcp_push_one(sk, mss_now);  
  30.             continue;  
  31.   
  32. wait_for_sndbuf:  
  33.             set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);  
  34. wait_for_memory:  
  35.             if (copied)  
  36. ///內存不夠,則儘量將本地的NAGLE算法所緩存的數據發送出去.  
  37.                 tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);  
  38.   
  39.             if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)  
  40.                 goto do_error;  
  41. ///更新相關域.  
  42.             mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));  
  43.             size_goal = tp->xmit_size_goal;  
  44.         }  
  45.     }  



最後來看下出錯或者成功tcp_sendmsg所做的: 

Java代碼  收藏代碼
  1. out:  
  2. ///這裏是成功返回所做的.  
  3.     if (copied)  
  4. ///這裏可以看到最終的flag是tp->nonagle,而這個就是看套接口選項是否有開nagle算法,如果沒開的話,立即把數據發出去,否則則會村訊nagle算法,將小數據緩存起來.  
  5.         tcp_push(sk, flags, mss_now, tp->nonagle);  
  6.     TCP_CHECK_TIMER(sk);  
  7.     release_sock(sk);  
  8.     return copied;  
  9.   
  10. do_fault:  
  11.     if (!skb->len) {  
  12. ///從write隊列unlink掉當前的buf.  
  13.         tcp_unlink_write_queue(skb, sk);  
  14. ///更新send)head  
  15.         tcp_check_send_head(sk, skb);  
  16. ///釋放skb.  
  17.         sk_wmem_free_skb(sk, skb);  
  18.     }  
  19.   
  20. do_error:  
  21.     if (copied)  
  22. ///如果copied不爲0,則說明發送成功一部分數據,因此此時返回out.  
  23.         goto out;  
  24. out_err:  
  25. ///否則進入錯誤處理.  
  26.     err = sk_stream_error(sk, flags, err);  
  27.     TCP_CHECK_TIMER(sk);  
  28.     release_sock(sk);  
  29.     return err;  
發佈了7 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章