本文接上篇《腦殘式網絡編程入門(一):跟着動畫來學TCP三次握手和四次揮手》,繼續腦殘式的網絡編程知識學習 ^_^。php
套接字socket是大多數程序員都很是熟悉的概念,它是計算機網絡編程的基礎,TCP/UDP收發消息都靠它。咱們熟悉的web服務器底層依賴它,咱們用到的MySQL關係數據庫、Redis內存數據庫底層依賴它。咱們用微信和別人聊天也依賴它,咱們玩網絡遊戲時依賴它,讀者們可以閱讀這篇文章也是由於有它在背後默默地支持着網絡通訊。html
本篇文章依然嘗試使用動畫圖片的方式,來對這個知識點進行「腦殘式」講解(哈哈),指望讀者們能夠更加簡單、直觀地理解Socket通訊的數據讀寫本質。git
友情提示:若是您的網速較慢,加載gif動畫可能較慢,請耐心等候哦。程序員
學習交流:github
- 即時通信開發交流3羣:185926912[推薦]web
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》算法
(本文同步發佈於:http://www.52im.net/thread-1732-1-1.html)數據庫
錢文品(老錢):畢業於華中科技大學計算機科學與技術專業,互聯網分佈式高併發技術十年老兵,目前任掌閱科技資深後端工程師。熟練使用 Java、Python、Golang 等多種計算機語言,開發過遊戲,製做過網站,寫過消息推送系統和MySQL 中間件,實現過開源的 ORM 框架、Web 框架、RPC 框架等。編程
做者的Github: https://github.com/pyloque後端
本文是系列文章中的第2篇,本系列大綱以下:
《腦殘式網絡編程入門(一):跟着動畫來學TCP三次握手和四次揮手》
《腦殘式網絡編程入門(二):咱們在讀寫Socket時,究竟在讀寫什麼?》(本文)
當客戶端和服務器使用TCP協議進行通訊時,客戶端封裝一個請求對象req,將請求對象req序列化成字節數組,而後經過套接字socket將字節數組發送到服務器,服務器經過套接字socket讀取到字節數組,再反序列化成請求對象req,進行處理,處理完畢後,生成一個響應對應res,將響應對象res序列化成字節數組,而後經過套接字將本身數組發送給客戶端,客戶端經過套接字socket讀取到本身數組,再反序列化成響應對象。
通訊框架每每能夠將序列化的過程隱藏起來,咱們所看到的現象就是上圖所示,請求對象req和響應對象res在客戶端和服務器之間跑來跑去。
也許你以爲這個過程仍是挺簡單的,很好理解,可是實際上背後發生的一系列事件超出了大家中大多數人的想象。通訊的真實過程要比上面的這張圖複雜太多。你也許會問,咱們須要瞭解的那麼深刻麼,直接拿來用不就能夠了麼?
在互聯網技術服務行業工做多年的經驗告訴我,若是你對底層機制不瞭解,你就會不明白爲何對套接字socket的讀寫會出現各類奇奇乖乖的問題,爲何有時會阻塞,有時又不阻塞,有時候還報錯,爲何會有粘包半包問題,NIO具體又是什麼,它是什麼特別新鮮的技術麼?對於這些問題的理解都須要你瞭解底層機制。
爲了方便你們對通訊底層的理解,我花了些時間作了下面這個動畫,它並不能徹底覆蓋底層細節的全貌,可是對於理解套接字的工做機制已經足夠了。請讀者仔細觀察這個動畫,後面的講解將圍繞着這個動畫展開。
咱們平時用到的套接字其實只是一個引用(一個對象ID),這個套接字對象其實是放在操做系統內核中。這個套接字對象內部有兩個重要的緩衝結構,一個是讀緩衝(read buffer),一個是寫緩衝(write buffer),它們都是有限大小的數組結構。
當咱們對客戶端的socket寫入字節數組時(序列化後的請求消息對象req),是將字節數組拷貝到內核區套接字對象的write buffer中,內核網絡模塊會有單獨的線程負責不停地將write buffer的數據拷貝到網卡硬件,網卡硬件再將數據送到網線,通過一些列路由器交換機,最終送達服務器的網卡硬件中。
一樣,服務器內核的網絡模塊也會有單獨的線程不停地將收到的數據拷貝到套接字的read buffer中等待用戶層來讀取。最終服務器的用戶進程經過socket引用的read方法將read buffer中的數據拷貝到用戶程序內存中進行反序列化成請求對象進行處理。而後服務器將處理後的響應對象走一個相反的流程發送給客戶端,這裏就再也不具體描述。
咱們注意到write buffer空間都是有限的,因此若是應用程序往套接字裏寫的太快,這個空間是會滿的。一旦滿了,寫操做就會阻塞,直到這個空間有足夠的位置騰出來。不過有了NIO(非阻塞IO),寫操做也能夠不阻塞,能寫多少是多少,經過返回值來肯定到底寫進去多少,那些沒有寫進去的內容用戶程序會緩存起來,後續會繼續重試寫入。
一樣咱們也注意到read buffer的內容可能會是空的。這樣套接字的讀操做(通常是讀一個定長的字節數組)也會阻塞,直到read buffer中有了足夠的內容(填充滿字節數組)纔會返回。有了NIO,就能夠有多少讀多少,無須阻塞了。讀不夠的,後續會繼續嘗試讀取。
那上面這張圖就展示了套接字的所有過程麼?顯然不是,數據的確認過程(ack)就徹底沒有展示。好比當寫緩衝的內容拷貝到網卡後,是不會當即從寫緩衝中將這些拷貝的內容移除的,而要等待對方的ack過來以後纔會移除。若是網絡情況很差,ack遲遲不過來,寫緩衝很快就會滿的。
細心的同窗可能注意到圖中的消息req被拷貝到網卡的時候變成了大寫的REQ,這是爲何呢?由於這兩個東西已經不是徹底同樣的了。內核的網絡模塊會將緩衝區的消息進行分塊傳輸,若是緩衝區的內容太大,是會被拆分紅多個獨立的小消息包的。而且還要在每一個消息包上附加上一些額外的頭信息,好比源網卡地址和目標網卡地址、消息的序號等信息,到了接收端須要對這些消息包進行從新排序組裝去頭後纔會扔進讀緩衝中。這些複雜的細節過程就很是難以在動畫上予以呈現了。
還有個問題那就是若是讀緩衝滿了怎麼辦,網卡收到了對方的消息要怎麼處理?通常的作法就是丟棄掉不給對方ack,對方若是發現ack遲遲沒有來,就會重發消息。那緩衝爲何會滿?是由於消息接收方處理的慢而發送方生產的消息太快了,這時候tcp協議就會有個動態窗口調整算法來限制發送方的發送速率,使得收發效率趨於匹配。若是是udp協議的話,消息一丟那就完全丟了。
網絡協議內部實現還有更多複雜的細節有待繼續挖掘,留着之後繼續分析吧。
若是您以爲本系列文章過於基礎,您可直接閱讀如下系列:
《網絡編程懶人入門(五):快速理解爲何說UDP有時比TCP更有優點》
《鮮爲人知的網絡編程》系列文章爲高階必讀,該系列目錄以下:
《鮮爲人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)》
《鮮爲人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》
關於移動端網絡特性及優化手段的總結性文章請見:
《現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障》
《通俗易懂-深刻理解TCP協議(下):RTT、滑動窗口、擁塞處理》
《理論聯繫實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》
《高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少》
《高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題》
《高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了》
《高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索》
《技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)》
《Java新一代網絡編程模型AIO原理及Linux系統AIO介紹》
《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示》
《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示》
《NIO框架入門(三):iOS與MINA二、Netty4的跨平臺UDP雙向通訊實戰》
《NIO框架入門(四):Android與MINA二、Netty4的跨平臺UDP雙向通訊實戰》
《P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介》
《P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解》
《P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解》
>> 更多同類文章 ……
(本文同步發佈於:http://www.52im.net/thread-1732-1-1.html)