日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Netty是如何解決TCP粘包拆包的?

 我們都知道TCP是基于字節(jié)流的傳輸協(xié)議。

我們提供的服務有:網(wǎng)站設計、成都網(wǎng)站建設、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、昆都侖ssl等。為上1000+企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務,是有科學管理、有技術的昆都侖網(wǎng)站制作公司

那么數(shù)據(jù)在通信層傳播其實就像河水一樣并沒有明顯的分界線,而數(shù)據(jù)具體表示什么意思什么地方有句號什么地方有分號這個對于TCP底層來說并不清楚。應用層向TCP層發(fā)送用于網(wǎng)間傳輸?shù)?、?位字節(jié)表示的數(shù)據(jù)流,然后TCP把數(shù)據(jù)流分區(qū)成適當長度的報文段,之后TCP把結果包傳給IP層,由它來通過網(wǎng)絡將包傳送給接收端實體的TCP層。

所以對于這個數(shù)據(jù)拆分成大包小包的問題就是我們今天要講的粘包和拆包的問題。

1、TCP粘包拆包問題說明

粘包和拆包這兩個概念估計大家還不清楚,通過下面這張圖我們來分析一下:

假設客戶端分別發(fā)送兩個數(shù)據(jù)包D1,D2個服務端,但是發(fā)送過程中數(shù)據(jù)是何種形式進行傳播這個并不清楚,分別有下列4種情況:

  1.  服務端一次接受到了D1和D2兩個數(shù)據(jù)包,兩個包粘在一起,稱為粘包;
  2.  服務端分兩次讀取到數(shù)據(jù)包D1和D2,沒有發(fā)生粘包和拆包;
  3.  服務端分兩次讀到了數(shù)據(jù)包,第一次讀到了D1和D2的部分內容,第二次讀到了D2的剩下部分,這個稱為拆包;
  4.  服務器分三次讀到了數(shù)據(jù)部分,第一次讀到了D1包,第二次讀到了D2包的部分內容,第三次讀到了D2包的剩下內容。

2、TCP粘包產(chǎn)生原因

我們知道在TCP協(xié)議中,應用數(shù)據(jù)分割成TCP認為最適合發(fā)送的數(shù)據(jù)塊,這部分是通過“MSS”(最大數(shù)據(jù)包長度)選項來控制的,通常這種機制也被稱為一種協(xié)商機制,MSS規(guī)定了TCP傳往另一端的最大數(shù)據(jù)塊的長度。

這個值TCP協(xié)議在實現(xiàn)的時候往往用MTU值代替(需要減去IP數(shù)據(jù)包包頭的大小20Bytes和TCP數(shù)據(jù)段的包頭20Bytes)所以往往MSS為1460。通訊雙方會根據(jù)雙方提供的MSS值得最小值確定為這次連接的最大MSS值。

tcp為提高性能,發(fā)送端會將需要發(fā)送的數(shù)據(jù)發(fā)送到緩沖區(qū),等待緩沖區(qū)滿了之后,再將緩沖中的數(shù)據(jù)發(fā)送到接收方。同理,接收方也有緩沖區(qū)這樣的機制,來接收數(shù)據(jù)。

發(fā)生粘包拆包的原因主要有以下這些:

  1.  應用程序寫入數(shù)據(jù)的字節(jié)大小大于套接字發(fā)送緩沖區(qū)的大小將發(fā)生拆包;
  2.  進行MSS大小的TCP分段。MSS是TCP報文段中的數(shù)據(jù)字段的最大長度,當TCP報文長度-TCP頭部長度>mss的時候將發(fā)生拆包;
  3.  應用程序寫入數(shù)據(jù)小于套接字緩沖區(qū)大小,網(wǎng)卡將應用多次寫入的數(shù)據(jù)發(fā)送到網(wǎng)絡上,將發(fā)生粘包;
  4.  數(shù)據(jù)包大于MTU的時候將會進行切片。MTU即(Maxitum Transmission Unit) 最大傳輸單元,由于以太網(wǎng)傳輸電氣方面的限制,每個以太網(wǎng)幀都有最小的大小64bytes最大不能超過1518bytes,刨去以太網(wǎng)幀的幀頭14Bytes和幀尾CRC校驗部分4Bytes,那么剩下承載上層協(xié)議的地方也就是Data域最大就只能有1500Bytes這個值我們就把它稱之為MTU。這個就是網(wǎng)絡層協(xié)議非常關心的地方,因為網(wǎng)絡層協(xié)議比如IP協(xié)議會根據(jù)這個值來決定是否把上層傳下來的數(shù)據(jù)進行分片。

3、如何解決TCP粘包拆包

我們知道tcp是無界的數(shù)據(jù)流,且協(xié)議本身無法避免粘包,拆包的發(fā)生,那我們只能在應用層數(shù)據(jù)協(xié)議上,加以控制。通常在制定傳輸數(shù)據(jù)時,可以使用如下方法:

  1.  設置定長消息,服務端每次讀取既定長度的內容作為一條完整消息;
  2.  使用帶消息頭的協(xié)議、消息頭存儲消息開始標識及消息長度信息,服務端獲取消息頭的時候解析出消息長度,然后向后讀取該長度的內容;
  3.  設置消息邊界,服務端從網(wǎng)絡流中按消息邊界分離出消息內容。比如在消息末尾加上換行符用以區(qū)分消息結束。

當然應用層還有更多復雜的方式可以解決這個問題,這個就屬于網(wǎng)絡層的問題了,我們還是用java提供的方式來解決這個問題。Spring Boot 學習筆記分享給你,我們先看一個例子看看粘包是如何發(fā)生的。

服務端: 

 
 
 
 
  1. public class HelloWordServer {  
  2.     private int port;  
  3.     public HelloWordServer(int port) {  
  4.         this.port = port;  
  5.     }  
  6.     public void start(){  
  7.         EventLoopGroup bossGroup = new NioEventLoopGroup();  
  8.         EventLoopGroup workGroup = new NioEventLoopGroup();   
  9.         ServerBootstrap server = new ServerBootstrap().group(bossGroup,workGroup)  
  10.                                     .channel(NioServerSocketChannel.class) 
  11.                                     .childHandler(new ServerChannelInitializer());  
  12.         try {  
  13.             ChannelFuture future = server.bind(port).sync();  
  14.             future.channel().closeFuture().sync();  
  15.         } catch (InterruptedException e) {  
  16.             e.printStackTrace();  
  17.         }finally {  
  18.             bossGroup.shutdownGracefully();  
  19.             workGroup.shutdownGracefully();  
  20.         }  
  21.     }   
  22.     public static void main(String[] args) {  
  23.         HelloWordServer server = new HelloWordServer(7788);  
  24.         server.start();  
  25.     }  

服務端Initializer: 

 
 
 
 
  1. public class ServerChannelInitializer extends ChannelInitializer {  
  2.     @Override  
  3.     protected void initChannel(SocketChannel socketChannel) throws Exception {  
  4.         ChannelPipeline pipeline = socketChannel.pipeline();  
  5.         // 字符串解碼 和 編碼  
  6.         pipeline.addLast("decoder", new StringDecoder());  
  7.         pipeline.addLast("encoder", new StringEncoder());  
  8.         // 自己的邏輯Handler  
  9.         pipeline.addLast("handler", new HelloWordServerHandler());  
  10.     }  

服務端handler: 

 
 
 
 
  1. public class HelloWordServerHandler extends ChannelInboundHandlerAdapter {  
  2.     private int counter;  
  3.     @Override  
  4.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
  5.         String body = (String)msg;  
  6.         System.out.println("server receive order : " + body + ";the counter is: " + ++counter);  
  7.     }  
  8.     @Override  
  9.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
  10.         super.exceptionCaught(ctx, cause);  
  11.     }  

客戶端: 

 
 
 
 
  1. public class HelloWorldClient {  
  2.     private  int port;  
  3.     private  String address;  
  4.     public HelloWorldClient(int port,String address) {  
  5.         this.port = port;  
  6.         this.address = address;  
  7.     }  
  8.     public void start(){  
  9.         EventLoopGroup group = new NioEventLoopGroup();  
  10.         Bootstrap bootstrap = new Bootstrap();  
  11.         bootstrap.group(group)  
  12.                 .channel(NioSocketChannel.class)  
  13.                 .handler(new ClientChannelInitializer());  
  14.         try {  
  15.             ChannelFuture future = bootstrap.connect(address,port).sync();       
  16.              future.channel().closeFuture().sync();  
  17.         } catch (Exception e) {  
  18.             e.printStackTrace(); 
  19.          }finally {  
  20.             group.shutdownGracefully();  
  21.         }   
  22.     }  
  23.     public static void main(String[] args) {  
  24.         HelloWorldClient client = new HelloWorldClient(7788,"127.0.0.1");  
  25.         client.start();  
  26.     }  

客戶端Initializer: 

 
 
 
 
  1. public class ClientChannelInitializer extends  ChannelInitializer {  
  2.     protected void initChannel(SocketChannel socketChannel) throws Exception {  
  3.         ChannelPipeline pipeline = socketChannel.pipeline();  
  4.         pipeline.addLast("decoder", new StringDecoder());  
  5.         pipeline.addLast("encoder", new StringEncoder());  
  6.         // 客戶端的邏輯 
  7.         pipeline.addLast("handler", new HelloWorldClientHandler());  
  8.     }  

客戶端handler: 

 
 
 
 
  1. public class HelloWorldClientHandler extends ChannelInboundHandlerAdapter {  
  2.     private byte[] req;  
  3.     private int counter;  
  4.     public BaseClientHandler() {  
  5.         req = ("Unless required by applicable law or agreed to in writing, software\n" +  
  6.                 "  distributed under the License is distributed on an \"AS IS\" BASIS,\n" +  
  7.                 "  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +  
  8.                 "  See the License for the specific language governing permissions and\n" +  
  9.                 "  limitations under the License.This connector uses the BIO implementation that requires the JSSE\n" +  
  10.                 "  style configuration. When using the APR/native implementation, the\n" +  
  11.                 "  penSSL style configuration is required as described in the APR/native\n" +  
  12.                 "  documentation.An Engine represents the entry point (within Catalina) that processes\n" +  
  13.                 "  every request.  The Engine implementation for Tomcat stand alone\n" +  
  14.                 "  analyzes the HTTP headers included with the request, and passes them\n" +  
  15.                 "  on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software\n" +  
  16.                 "# distributed under the License is distributed on an \"AS IS\" BASIS,\n" +  
  17.                 "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +  
  18.                 "# See the License for the specific language governing permissions and\n" +  
  19.                 "# limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log\n" +  
  20.                 "# each component that extends LifecycleBase changing state:\n" +  
  21.                 "#org.apache.catalina.util.LifecycleBase.level = FINE"  
  22.                 ).getBytes(); 
  23.     }  
  24.     @Override  
  25.     public void channelActive(ChannelHandlerContext ctx) throws Exception {  
  26.         ByteBuf message;  
  27.         //將上面的所有字符串作為一個消息體發(fā)送出去  
  28.         message = Unpooled.buffer(req.length);  
  29.         message.writeBytes(req);  
  30.         ctx.writeAndFlush(message);  
  31.     }  
  32.     @Override  
  33.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
  34.         String buf = (String)msg;  
  35.         System.out.println("Now is : " + buf + " ; the counter is : "+ (++counter));  
  36.     }  
  37.     @Override  
  38.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
  39.         ctx.close();  
  40.     }  

運行客戶端和服務端我們能看到:

我們看到這個長長的字符串被截成了2段發(fā)送,這就是發(fā)生了拆包的現(xiàn)象。同樣粘包我們也很容易去模擬,我們把BaseClientHandler中的channelActive方法里面的: 

 
 
 
 
  1. message = Unpooled.buffer(req.length);  
  2. message.writeBytes(req);  
  3. ctx.writeAndFlush(message); 

這幾行代碼是把我們上面的一長串字符轉成的byte數(shù)組寫進流里發(fā)送出去,那么我們可以在這里把上面發(fā)送消息的這幾行循環(huán)幾遍這樣發(fā)送的內容增多了就有可能在拆包的時候把上一條消息的一部分分配到下一條消息里面了,修改如下: 

 
 
 
 
  1. for (int i = 0; i < 3; i++) {  
  2.     message = Unpooled.buffer(req.length);  
  3.     message.writeBytes(req);  
  4.     ctx.writeAndFlush(message);  

改完之后我們再運行一下,輸出太長不好截圖,我們在輸出結果中能看到循環(huán)3次之后的消息服務端收到的就不是之前的完整的一條了,而是被拆分了4次發(fā)送。

對于上面出現(xiàn)的粘包和拆包的問題,Netty已有考慮,并且有實施的方案:LineBasedFrameDecoder。另外,微信搜索Java技術棧,在后臺回復:面試,可以獲取我整理的 Java 系列面試題和答案。

我們重新改寫一下ServerChannelInitializer: 

 
 
 
 
  1. public class ServerChannelInitializer extends ChannelInitializer {  
  2.     @Override 
  3.     protected void initChannel(SocketChannel socketChannel) throws Exception {  
  4.         ChannelPipeline pipeline = socketChannel.pipeline();  
  5.         pipeline.addLast(new LineBasedFrameDecoder(2048));         
  6.         // 字符串解碼 和 編碼  
  7.         pipeline.addLast("decoder", new StringDecoder());  
  8.         pipeline.addLast("encoder", new StringEncoder());   
  9.         // 自己的邏輯Handler  
  10.         pipeline.addLast("handler", new BaseServerHandler());  
  11.     }  

新增:pipeline.addLast(new LineBasedFrameDecoder(2048))。同時,我們還得對上面發(fā)送的消息進行改造BaseClientHandler: 

 
 
 
 
  1. public class BaseClientHandler extends ChannelInboundHandlerAdapter {  
  2.     private byte[] req;  
  3.     private int counter;  
  4.     req = ("Unless required by applicable dfslaw or agreed to in writing, software" +  
  5.                 "  distributed under the License is distributed on an \"AS IS\" BASIS," +  
  6.                 "  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." +  
  7.                 "  See the License for the specific language governing permissions and" +  
  8.                 "  limitations under the License.This connector uses the BIO implementation that requires the JSSE" +  
  9.                 "  style configuration. When using the APR/native implementation, the" +  
  10.                 "  penSSL style configuration is required as described in the APR/native" +  
  11.                 "  documentation.An Engine represents the entry point (within Catalina) that processes" +  
  12.                 "  every request.  The Engine implementation for Tomcat stand alone" +  
  13.                 "  analyzes the HTTP headers included with the request, and passes them" +  
  14.                 "  on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software" +  
  15.                 "# distributed under the License is distributed on an \"AS IS\" BASIS," +  
  16.                 "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." +  
  17.                 "# See the License for the specific language governing permissions and" +  
  18.                 "# limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log" +  
  19.                 "# each component that extends LifecycleBase changing state:" +  
  20.                 "#org.apache.catalina.util.LifecycleBase.level = FINE\n"  
  21.                 ).getBytes();     
  22.     @Override  
  23.     public void channelActive(ChannelHandlerContext ctx) throws Exception {  
  24.         ByteBuf message;  
  25.         message = Unpooled.buffer(req.length);  
  26.         message.writeBytes(req);  
  27.         ctx.writeAndFlush(message);  
  28.     }  
  29.     @Override  
  30.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
  31.         String buf = (String)msg;  
  32.         System.out.println("Now is : " + buf + " ; the counter is : "+ (++counter));  
  33.     }  
  34.     @Override 
  35.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
  36.         ctx.close();  
  37.     }  

去掉所有的”\n”,只保留字符串末尾的這一個。原因稍后再說。channelActive方法中我們不必再用循環(huán)多次發(fā)送消息了,只發(fā)送一次就好(第一個例子中發(fā)送一次的時候是發(fā)生了拆包的),然后我們再次運行,大家會看到這么長一串字符只發(fā)送了一串就發(fā)送完畢。程序輸出我就不截圖了。下面來解釋一下LineBasedFrameDecoder。

LineBasedFrameDecoder的工作原理是它依次遍歷ByteBuf 中的可讀字節(jié),判斷看是否有”\n” 或者” \r\n”,如果有,就以此位置為結束位置,從可讀索引到結束位置區(qū)間的字節(jié)就組成了一行。它是以換行符為結束標志的解碼器。支持攜帶結束符或者不攜帶結束符兩種解碼方式,同時支持配置單行的最大長度。如果連續(xù)讀取到最大長度后仍然沒有發(fā)現(xiàn)換行符,就會拋出異常,同時忽略掉之前讀到的異常碼流。這個對于我們確定消息最大長度的應用場景還是很有幫助。

對于上面的判斷看是否有”\n” 或者” \r\n”以此作為結束的標志我們可能回想,要是沒有”\n” 或者” \r\n”那還有什么別的方式可以判斷消息是否結束呢。別擔心,Netty對于此已經(jīng)有考慮,還有別的解碼器可以幫助我們解決問題,另外,關注公眾號Java技術棧,在后臺回復:面試,可以獲取我整理的 Java 系列面試題和答案,非常齊全。


當前標題:Netty是如何解決TCP粘包拆包的?
本文鏈接:http://www.5511xx.com/article/djjhepo.html