摘要:接收方只需要等待,直到讀到確定數(shù)量的字節(jié),然后處理即可。而這個字節(jié)流的前個字節(jié)用于表示對象的長度,接下來的字節(jié)就是傳輸?shù)膶ο蟮淖止?jié)流,最后不夠最大長度的用任意字節(jié)進行填充即可。
什么是tcp半包粘包?
簡單來講就是接收到的tcp包并不一定是一個完整的包。
它可能是1個包的一部分,也可能是多個完整包加上1個包的一部分。
為什么?
因為tcp的定義是面向字節(jié)流的傳輸協(xié)議,所以操作系統(tǒng)實現(xiàn)這個協(xié)議的時候,只保證字節(jié)的正確傳輸,而至于字節(jié)的應用層語義(可能這個字節(jié)是個分隔符,也可能這個字節(jié)和周圍3個字節(jié)組成一個int,代表類的某個字段),操作系統(tǒng)是不管的。
比如下面這個例子(基于java):
public class Account { private int accountnum; private double balance; private int num; }
要傳輸這個Account對象,實際上就是傳輸它的三個字段accountnum,balance和num,它們的大小分別是4,8,4個字節(jié)。當傳輸時,實現(xiàn)tcp協(xié)議的系統(tǒng)只負責把這16個字節(jié)傳輸?shù)浇邮辗剑恢肋@些字節(jié)的含義。比如前4個字節(jié)是accountnum,然而這是應用層的語義,實現(xiàn)tcp協(xié)議的系統(tǒng)并不知道,也不需要知道,因為tcp規(guī)范里就沒規(guī)定需要知道上層的語義。
那么接收方如何接收呢?
對于這個例子實在是太簡單了,接收方只需要每次都接收16個字節(jié)就能保證每一次都能得到一個完整的Account對象,連分隔符都不需要。在已知確定傳輸對象長度(字節(jié)數(shù)目)的時候:
即使接收到的tcp包并不一定是一個完整的包。
接收方只需要等待,直到讀到確定數(shù)量的字節(jié),然后處理即可。
比如現(xiàn)在只傳輸了4個字節(jié),我們知道16個字節(jié)才能組成完整的Account對象,那么再讀12個字節(jié)后進行處理即可。
這個例子有什么意義?
根據(jù)這個例子受到啟發(fā),只要傳輸對象的長度是確定的,那么接收端很容易就能夠對傳輸對象進行解析(就是處理tcp粘包半包)。
然而對象的長度是確定的嗎?往往都不是,比如一個上面的對象現(xiàn)在加一個String類型的成員字段,這個String字段變成字節(jié)的時候長度就是未知的,但這并不影響我們把它變成定長的對象。
HOW?
設置最大傳輸長度,每次都接收最大傳輸長度的字節(jié)流。而這個字節(jié)流的前4個字節(jié)用于表示對象的長度,接下來的字節(jié)就是傳輸?shù)膶ο蟮淖止?jié)流,最后不夠最大長度的用任意字節(jié)進行填充即可。
比如:
public class Account { private int accountnum; private double balance; private int num; private String extra; }
對于增加了String類型字段extra的新Account類來說,它的一個對象長度是不確定的,現(xiàn)在要傳輸它該怎么辦?設置最大的長度為400字節(jié),前4個字節(jié)存儲實例對象的長度x,之后的x個字節(jié)為對象,最后沒用到的位置用0x0(也就是0)來填充。比如下圖所示:
需要注意的是,前四個字節(jié)只是字節(jié),并不是x,需要把這4個字節(jié)轉成int類型的變量,然后這個int變量對應的10進制數(shù)是x。
這個方式看起來具有很明顯的局限性?
長度是有限制的?比如一次只能傳輸最多400-4=396個字節(jié)的對象?
但是可以把超大的對象再次分開,每一次都只傳輸最大包(400)長度,然后再拼接即可解決。
比如現(xiàn)在設計這400個字節(jié)的存儲格式是這樣的:
前4個字節(jié)存儲這個對象總共被分成幾個最大傳輸?shù)陌?,接著?個字節(jié)存儲這是第幾個,然后是長度,然后是內容,最后是填充。
這樣看起來最大長度就解決了。
然而。。。基于java nio的傳輸適合傳輸大文件(巨長的字節(jié)流)嗎?
nio是什么原理?
是I/O多路復用,簡單來講就是我有一個叫做選擇器(Selector)的類不斷輪詢不同的連接(Socket)的i/o事件,發(fā)現(xiàn)了i/o事件就處理,處理時可以用Selector所在的線程,也可以另開啟一個線程。如果要用Selector的線程處理i/o事件,那么i/o的操作時間必須很短,否則可能會丟失消息,而如果開啟一個線程,i/o的時間也應該很短。why?因為如果i/o時間很長,并且線程很多,那么就退化成了bio的模型。。。那么就沒必要用nio了。。。
歸結起來就是nio就不適合多用戶傳輸大文件,否則必然退化為bio模型。
所以實際上不要考慮這種大文件的傳輸,如果要傳輸大文件還是用bio模型比較好,并且在bio的傳輸模式下java提供了對象的序列化和反序列化,這樣都不需要我們定義長度字段了。
具體的代碼
參考:https://github.com/ItCrazyer/...
說明一點的是,這個例子里傳輸?shù)膶ο笫遣欢ㄩL的字符串,不是一個定義的類(不是像上面的Account這種),并且使用了SelectionKey對象的attachment方法,來暫存數(shù)據(jù),暫存數(shù)據(jù)存儲在TempData這個類的對象里,為什么?
因為雖然我們知道確定的長度(比如是600),并且據(jù)此處理定長的數(shù)據(jù),但是一次傳來的數(shù)據(jù)很可能是好幾個定長的數(shù)據(jù)包,而且每一次我們都必須讀完,比如傳來了1300字節(jié)的數(shù)據(jù),就必須1300字節(jié)都讀完,不能這一次i/o事件我只讀600,然后下一次i/o事件在讀接下來的700個字節(jié),那是沒辦法做到的,因為i/o響應的是這一次可讀,并不響應你還有數(shù)據(jù),所以這次不讀完下次就沒有了。。。
不過經(jīng)過我測試。。。沒有讀完的數(shù)據(jù)再下一次仍然可以讀取到,并不會因為這次沒讀完下一次就沒了。。所以不加緩存處理也沒問題!?。?/p>
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/72139.html
摘要:如果什么事都沒得做,它也不會死循環(huán),它會將線程休眠起來,直到下一個事件來了再繼續(xù)干活,這樣的一個線程稱之為線程。而請求處理邏輯既可以使用單獨的線程池進行處理,也可以跟放在讀寫線程一塊處理。 Netty到底是什么 從HTTP說起 有了Netty,你可以實現(xiàn)自己的HTTP服務器,F(xiàn)TP服務器,UDP服務器,RPC服務器,WebSocket服務器,Redis的Proxy服務器,MySQL的P...
摘要:修改之前的服務端開發(fā)代碼修改為下面代碼綁定端口同步等待成功等待服務端監(jiān)聽端口關閉主要修改了和方法和原理分析的工作原理是它依次遍歷中的可讀字節(jié)判斷看是否有或如果有就以此位置為結束位置從可讀索引到結束位置區(qū)間的字節(jié)就組成了一行它是以換行符為結束 修改之前的 Netty 服務端開發(fā) 代碼, 修改為下面代碼 public class TimeServer { public void ...
摘要:概述在簡易框架需求與設計這篇文章中已經(jīng)給出了協(xié)議的具體細節(jié),協(xié)議類型為二進制協(xié)議,如下協(xié)議的解碼我們稱為,編碼我們成為,下文我們將直接使用和術語。直接貼代碼,參考前文提到的協(xié)議格式閱讀以下代碼協(xié)議編碼器 概述 在《簡易RPC框架:需求與設計》這篇文章中已經(jīng)給出了協(xié)議的具體細節(jié),協(xié)議類型為二進制協(xié)議,如下: ---------------------------------------...
摘要:的方法,的默認實現(xiàn)會判斷是否是類型注意自動拆箱,自動裝箱問題。適應自旋鎖鎖競爭是下的,會經(jīng)過用戶態(tài)到內核態(tài)的切換,是比較花時間的。在中引入了自適應的自旋鎖,說明自旋的時間不固定,要不要自旋變得越來越聰明。 前言 只有光頭才能變強 之前在刷博客的時候,發(fā)現(xiàn)一些寫得比較好的博客都會默默收藏起來。最近在查閱補漏,有的知識點比較重要的,但是在之前的博客中還沒有寫到,于是趁著閑整理一下。 文本的...
閱讀 1964·2021-11-19 09:40
閱讀 2148·2021-10-09 09:43
閱讀 3304·2021-09-06 15:00
閱讀 2821·2019-08-29 13:04
閱讀 2776·2019-08-26 11:53
閱讀 3539·2019-08-26 11:46
閱讀 2330·2019-08-26 11:38
閱讀 398·2019-08-26 11:27