日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Netty 框架学习(二):Netty粘包和拆包

發布時間:2025/1/21 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Netty 框架学习(二):Netty粘包和拆包 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 一、什么是粘包和拆包
    • 二、粘包和拆包示例代碼
      • 1、TimeServerHandler
      • 2、TimeClientHandler
    • 三、使用Netty解決粘包和拆包
      • 1、TimeServerHandler中的ChildChannelHandler
      • 2、TimeClientHandler
    • 四、LineBasedFrameDecoder和StringEncoder

一、什么是粘包和拆包

先從數據的發送和接收講起, Netty 發送和讀取數據的單位,使用 ByteBuf 來充當。每一次發送,就是向Channel 寫入一個 ByteBuf ;每一次讀取,就是從 Channel 讀到一個 ByteBuf 。

發送一次數據調用如下方法:

channel.writeAndFlush(buffer);

讀取一次數據調用如下方法:

public void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf byteBuf = (ByteBuf) msg;//.... }

我們理想是:發送端每發送一個buffer,接收端就能接收到一個一模一樣的buffer。

但是實際通訊情況可能不能很好的滿足,所以就會發生粘包和拆包。

  • 粘包指接收端讀取的時候,多個發送過來的 ByteBuf “粘”在了一起。換句話說,接收端讀取一次的 ByteBuf ,讀到了多個發送端的 ByteBuf ,是為粘包。
  • 就是接收端將一個發送端的ByteBuf “拆”開了,形成一個破碎的包,我們定義這種 ByteBuf 為半包。
    換句話說,接收端讀取一次的 ByteBuf ,讀到了發送端的一個 ByteBuf的一部分,是為半包。
  • 更加深理解這兩個詞,我們把Netty權威指南圖拿過來看下:

    • 服務端分兩次讀到了兩個獨立的數據包,分別是D1和D2,沒有粘包和拆包
    • 服務端一次接收到了兩個數據包,D1和D2粘在一起,發生了粘包
    • 服務端分兩次讀到了兩個數據包,一次讀到了完整的D1包和D2的部分包D2_1,第二次讀到了D2剩下的包D2_2
    • 服務端分兩次讀到了兩個數據包,一次讀到了D1的部分分D1_1,第二次讀到了D1的剩下的包D1_2和完整的D2包

    二、粘包和拆包示例代碼

    把上一篇文章的服務端和客戶端修改下

    1、TimeServerHandler

    public class TimeServerHandler extends ChannelInboundHandlerAdapter {private int counter;/*** 收到客戶端消息,自動觸發* @param ctx* @param msg* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {//將 msg 轉為 Netty 的 ByteBuf 對象,類似 JDK 中的 java.nio.ByteBuffer,不過 ButeBuf 功能更強,更靈活ByteBuf buf = (ByteBuf) msg;//readableBytes:獲取緩沖區可讀字節數,然后創建字節數組//從而避免了像 java.nio.ByteBuffer 時,只能盲目的創建特定大小的字節數組,比如 1024byte[] req = new byte[buf.readableBytes()];//readBytes:將緩沖區字節數組復制到新建的 byte 數組中//然后將字節數組轉為字符串buf.readBytes(req);String body = new String(req, "UTF-8").substring(0,req.length-System.getProperty("line.separator").length());System.out.println("The time server receive order : " + body + "; the counter is :" + (++counter));//回復消息//copiedBuffer:創建一個新的緩沖區,內容為里面的參數//通過 ChannelHandlerContext 的 write 方法將消息異步發送給客戶端*/String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());ctx.write(resp);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//flush:將消息發送隊列中的消息寫入到 SocketChannel 中發送給對方,為了頻繁的喚醒 Selector 進行消息發送//Netty 的 write 方法并不直接將消息寫如 SocketChannel 中,調用 write 只是把待發送的消息放到發送緩存數組中,再通過調用 flush//方法,將發送緩沖區的消息全部寫入到 SocketChannel 中ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {/**當發生異常時,關閉 ChannelHandlerContext,釋放和它相關聯的句柄等資源 */ctx.close();} }

    2、TimeClientHandler

    public class TimeClientHandler extends ChannelInboundHandlerAdapter {private int counter;private byte[] req;/*** Creates a client-side handler.*/public TimeClientHandler() {req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();}/*** 當客戶端和服務端 TCP 鏈路建立成功之后,Netty 的 NIO 線程會調用 channelActive 方法* @param ctx*/@Overridepublic void channelActive(ChannelHandlerContext ctx) {ByteBuf message = null;for (int i = 0;i<100;i++) {message = Unpooled.buffer(req.length);message.writeBytes(req);ctx.writeAndFlush(message);}}/*** 當服務端返回應答消息時,channelRead 方法被調用,從 Netty 的 ByteBuf 中讀取并打印應答消息* @param ctx* @param msg* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {ByteBuf buf = (ByteBuf) msg;byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req, "UTF-8");System.out.println("Now is : " + body + "this counter is:" + (++counter));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 釋放資源System.out.println("Unexpected exception from downstream : "+ cause.getMessage());ctx.close();} }

    運行程序之后服務端和客戶端打印結果為:


    說明以上程序發生了粘包。

    三、使用Netty解決粘包和拆包

    還使用上一節示例代碼

    1、TimeServerHandler中的ChildChannelHandler

    public class ChildChannelHandler extends ChannelInitializer<SocketChannel> {protected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));socketChannel.pipeline().addLast(new StringEncoder());socketChannel.pipeline().addLast(new TimeServerHandler());} }

    2、TimeClientHandler

    public class TimeClientHandler extends ChannelInboundHandlerAdapter {private int counter;private byte[] req;/*** Creates a client-side handler.*/public TimeClientHandler() {req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();}/*** 當客戶端和服務端 TCP 鏈路建立成功之后,Netty 的 NIO 線程會調用 channelActive 方法* @param ctx*/@Overridepublic void channelActive(ChannelHandlerContext ctx) {ByteBuf message = null;for (int i = 0;i<100;i++) {message = Unpooled.buffer(req.length);message.writeBytes(req);ctx.writeAndFlush(message);}}/*** 當服務端返回應答消息時,channelRead 方法被調用,從 Netty 的 ByteBuf 中讀取并打印應答消息* @param ctx* @param msg* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {ByteBuf body = (ByteBuf) msg;System.out.println("Now is : " + body + "this counter is:" + (++counter));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 釋放資源System.out.println("Unexpected exception from downstream : "+ cause.getMessage());ctx.close();} }

    四、LineBasedFrameDecoder和StringEncoder

    LineBasedFrameDecoder工作原理是依次遍歷ByteBuf中可讀字節,判斷是否有"\n","\r\n"。如果有,就以此位置為結束位置。它是以換行符為結束的解碼器。支持攜帶結束符或者不帶結束符兩種方式。同時支持配置行數最大長度。如果讀取到最大行數還沒有發現換行符,就會拋出異常,同時忽略掉以前的異常碼流。

    總結

    以上是生活随笔為你收集整理的Netty 框架学习(二):Netty粘包和拆包的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。