中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

建設(shè)網(wǎng)站那些公司靠譜/百度網(wǎng)盟推廣

建設(shè)網(wǎng)站那些公司靠譜,百度網(wǎng)盟推廣,那間公司做網(wǎng)站好,深圳網(wǎng)站建設(shè) 網(wǎng)絡(luò)推廣文章目錄 三. Netty 進階1. 粘包與半包1.1 粘包現(xiàn)象服務(wù)端代碼客戶端代碼 1.2 半包現(xiàn)象服務(wù)端代碼客戶端代碼 1.3 現(xiàn)象分析粘包半包緣由滑動窗口MSS 限制Nagle 算法 1.4 解決方案方法1,短鏈接方法2,固定長度方法3,固定分隔符方法4&#xff0c…

文章目錄

  • 三. Netty 進階
    • 1. 粘包與半包
      • 1.1 ==粘包==現(xiàn)象
        • 服務(wù)端代碼
        • 客戶端代碼
      • 1.2 ==半包==現(xiàn)象
        • 服務(wù)端代碼
        • 客戶端代碼
      • 1.3 現(xiàn)象分析
        • 粘包
        • 半包
        • 緣由
          • 滑動窗口
          • MSS 限制
          • Nagle 算法
      • 1.4 解決方案
        • 方法1,短鏈接
        • 方法2,固定長度
        • 方法3,固定分隔符
        • 方法4,預(yù)設(shè)長度
    • 2. 協(xié)議設(shè)計與解析
      • 2.1 為什么需要協(xié)議?
      • 2.2 redis 協(xié)議舉例
      • 2.3 http 協(xié)議舉例
      • 2.4 自定義協(xié)議要素
        • 編解碼器
          • MessageCodec
          • TestMessageCodec
          • 輸出
        • 💡 什么時候可以加 @Sharable
    • 3. 聊天室案例
      • 3.1 聊天室業(yè)務(wù)介紹
      • 3.2 聊天室業(yè)務(wù)-登錄
      • 3.3 聊天室業(yè)務(wù)-單聊
      • 3.4 聊天室業(yè)務(wù)-群聊
      • 3.5 聊天室業(yè)務(wù)-退出
      • 3.6 聊天室業(yè)務(wù)-空閑檢測
        • 連接假死

三. Netty 進階

1. 粘包與半包

1.1 粘包現(xiàn)象

服務(wù)端代碼

@Slf4j
public class HelloWorldServer {void start() {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("connected {}", ctx.channel());super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.debug("disconnect {}", ctx.channel());super.channelInactive(ctx);}});}});ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();log.debug("stoped");}}public static void main(String[] args) {new HelloWorldServer().start();}
}

客戶端代碼

客戶端代碼希望發(fā)送 10 個消息,每個消息是 16 字節(jié)

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class HelloWorldClient {public static void main(String[] args) {NioEventLoopGroup worker = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("connected...");ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {// 會在連接 channel 建立成功之后,會觸發(fā) active事件@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("sending...");for (int i = 0; i < 10; i++) {ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(new byte[]{0, 1, 2, 3, 4,5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15});ctx.writeAndFlush(buffer);}}});}});ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("client error", e);} finally {worker.shutdownGracefully();}}
}

服務(wù)器端的某次輸出,可以看到一次就接收了 160 個字節(jié),而非分 10 次接收。而我們客戶端是發(fā)了10次,每次16字節(jié),這就是粘包現(xiàn)象。

// 客戶端輸出
/*
11:57:47 [DEBUG] [nioEventLoopGroup-2-1] c.z.a.HelloWorldClient - connetted...
11:57:47 [DEBUG] [nioEventLoopGroup-2-1] c.z.a.HelloWorldClient - sending...
*/// 服務(wù)器端輸出
/*
11:57:08 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0x6e5efdad] binding...
11:57:08 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0x6e5efdad, L:/0:0:0:0:0:0:0:0:8080] bound...
11:57:47 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xfaf43bd5, L:/127.0.0.1:8080 - R:/127.0.0.1:65248] REGISTERED
11:57:47 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xfaf43bd5, L:/127.0.0.1:8080 - R:/127.0.0.1:65248] ACTIVE
11:57:47 [DEBUG] [nioEventLoopGroup-3-1] c.z.a.HelloWorldServer - connected [id: 0xfaf43bd5, L:/127.0.0.1:8080 - R:/127.0.0.1:65248]
11:57:47 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xfaf43bd5, L:/127.0.0.1:8080 - R:/127.0.0.1:65248] READ: 160B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
11:57:47 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xfaf43bd5, L:/127.0.0.1:8080 - R:/127.0.0.1:65248] READ COMPLETE*/

1.2 半包現(xiàn)象

服務(wù)端代碼

只添加serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);這一行代碼

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class HelloWorldServer {void start() {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();// 為現(xiàn)象明顯,服務(wù)端修改一下接收緩沖區(qū),其它代碼不變// (調(diào)整系統(tǒng)的接收緩沖區(qū)-即滑動窗口)serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("connected {}", ctx.channel());super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.debug("disconnect {}", ctx.channel());super.channelInactive(ctx);}});}});ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();log.debug("stoped");}}public static void main(String[] args) {new HelloWorldServer().start();}
}

客戶端代碼

客戶端沒有任何代碼變動

@Slf4j
public class HelloWorldClient {public static void main(String[] args) {NioEventLoopGroup worker = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("connected...");ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {// 會在連接 channel 建立成功之后,會觸發(fā) active事件@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("sending...");for (int i = 0; i < 10; i++) {ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(new byte[]{0, 1, 2, 3, 4,5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15});ctx.writeAndFlush(buffer);}}});}});ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("client error", e);} finally {worker.shutdownGracefully();}}
}

輸出如下,可以看到客戶端發(fā)了10次,每次16字節(jié)的消息,但是服務(wù)端分幾次接受了36B、50B、50B、24B加起來共160字節(jié)。

(只要是使用TCP協(xié)議,那就會出現(xiàn)粘包和半包問題)

// 客戶端輸出
/*
12:10:42 [DEBUG] [nioEventLoopGroup-2-1] c.z.a.HelloWorldClient - connected...
12:10:42 [DEBUG] [nioEventLoopGroup-2-1] c.z.a.HelloWorldClient - sending...
*/// 服務(wù)端輸出
/*
12:10:32 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0xdafb9db3] binding...
12:10:32 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0xdafb9db3, L:/0:0:0:0:0:0:0:0:8080] bound...
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] REGISTERED
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] ACTIVE
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] c.z.a.HelloWorldServer - connected [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450]
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ: 36B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03                                     |....            |
+--------+-------------------------------------------------+----------------+
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ COMPLETE
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ: 50B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000020| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000030| 04 05                                           |..              |
+--------+-------------------------------------------------+----------------+
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ COMPLETE
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ: 50B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 04 05 |................|
|00000010| 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 04 05 |................|
|00000020| 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 04 05 |................|
|00000030| 06 07                                           |..              |
+--------+-------------------------------------------------+----------------+
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ COMPLETE
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ: 24B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 04 05 06 07 |................|
|00000010| 08 09 0a 0b 0c 0d 0e 0f                         |........        |
+--------+-------------------------------------------------+----------------+
12:10:42 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ COMPLETE
12:12:26 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] READ COMPLETE
12:12:26 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x07aed482, L:/127.0.0.1:8080 - R:/127.0.0.1:65450] EXCEPTION: java.io.IOException: 遠(yuǎn)程主機強迫關(guān)閉了一個現(xiàn)有的連接。*/

注意

serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影響的底層接收緩沖區(qū)(即滑動窗口)大小,僅決定了 netty 讀取的最小單位,netty 實際每次讀取的一般是它的整數(shù)倍

1.3 現(xiàn)象分析

粘包

  • 現(xiàn)象,發(fā)送 abc def,接收 abcdef

  • 原因

    • 應(yīng)用層(應(yīng)用層面):接收方 ByteBuf 設(shè)置太大(Netty 默認(rèn) 1024)
    • 滑動窗口(TCP層面):假設(shè)發(fā)送方 256 bytes 表示一個完整報文,但由于接收方處理不及時且窗口大小足夠大,這 256 bytes 字節(jié)就會緩沖在接收方的滑動窗口中,當(dāng)滑動窗口中緩沖了多個報文就會粘包
    • Nagle 算法(TCP層面):會造成粘包(因為報文頭也占據(jù)一定的大小,如果攜帶的實際數(shù)據(jù)較小,那顯然不劃算,因此會攢夠了一批量數(shù)據(jù),然后發(fā)送過去,因此也會造成粘包)

半包

  • 現(xiàn)象,發(fā)送 abcdef,接收 abc def
  • 原因
    • 應(yīng)用層(應(yīng)用層面):接收方 ByteBuf 小于實際發(fā)送數(shù)據(jù)量
    • 滑動窗口(TCP層面):假設(shè)接收方的窗口只剩了 128 bytes,發(fā)送方的報文大小是 256 bytes,這時放不下了,只能先發(fā)送前 128 bytes,等待 ack 后才能發(fā)送剩余部分,這就造成了半包
    • MSS 限制(鏈路層面):當(dāng)發(fā)送的數(shù)據(jù)超過 MSS 限制后,會將數(shù)據(jù)切分發(fā)送,就會造成半包

本質(zhì)是因為 TCP 是流式協(xié)議,消息無邊界

緣由

滑動窗口
  • TCP 以一個段(segment)為單位,每發(fā)送一個段就需要進行一次確認(rèn)應(yīng)答(ack)處理,但如果這么做,缺點是包的往返時間越長性能就越差(因為tcp協(xié)議是可靠的傳輸協(xié)議,因此需要保證1次請求1次應(yīng)答,但是如果等到上一次請求得到應(yīng)答,再發(fā)送下一次請求,那效率顯然就很低,因此下面引入了滑動窗口來解決這個問題)

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-leJ1X8om-1690552927974)(assets/0049.png)]

  • 為了解決此問題,引入了窗口概念,窗口大小即決定了無需等待應(yīng)答而可以繼續(xù)發(fā)送的數(shù)據(jù)最大值

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-c4Pwo8wB-1690552927976)(assets/0051.png)]

  • 窗口實際就起到一個緩沖區(qū)的作用,同時也能起到流量控制的作用

    • 圖中深色的部分即要發(fā)送的數(shù)據(jù),高亮的部分即窗口
  • 窗口內(nèi)的數(shù)據(jù)才允許被發(fā)送,當(dāng)應(yīng)答未到達(dá)前,窗口必須停止滑動

  • 如果 1001~2000 這個段的數(shù)據(jù) ack 回來了,窗口就可以向前滑動

  • 接收方也會維護一個窗口,只有落在窗口內(nèi)的數(shù)據(jù)才能允許接收

MSS 限制
  • 鏈路層對一次能夠發(fā)送的最大數(shù)據(jù)有限制,這個限制稱之為 MTU(maximum transmission unit),不同的鏈路設(shè)備的 MTU 值也有所不同,例如

  • 以太網(wǎng)的 MTU 是 1500

  • FDDI(光纖分布式數(shù)據(jù)接口)的 MTU 是 4352

  • 本地回環(huán)地址的 MTU 是 65535 - 本地測試不走網(wǎng)卡

  • MSS 是最大段長度(maximum segment size),它是 MTU 刨去 tcp 頭和 ip 頭后剩余能夠作為數(shù)據(jù)傳輸?shù)淖止?jié)數(shù)

  • ipv4 tcp 頭占用 20 bytes,ip 頭占用 20 bytes,因此以太網(wǎng) MSS 的值為 1500 - 40 = 1460

  • TCP 在傳遞大量數(shù)據(jù)時,會按照 MSS 大小將數(shù)據(jù)進行分割發(fā)送

  • MSS 的值在三次握手時通知對方自己 MSS 的值,然后在兩者之間選擇一個小值作為 MSS

Nagle 算法
  • 即使發(fā)送一個字節(jié),也需要加入 tcp 頭和 ip 頭,也就是總字節(jié)數(shù)會使用 41 bytes,非常不經(jīng)濟。因此為了提高網(wǎng)絡(luò)利用率,tcp 希望盡可能發(fā)送足夠大的數(shù)據(jù),這就是 Nagle 算法產(chǎn)生的緣由
  • 該算法是指發(fā)送端即使還有應(yīng)該發(fā)送的數(shù)據(jù),但如果這部分?jǐn)?shù)據(jù)很少的話,則進行延遲發(fā)送
    • 如果 SO_SNDBUF 的數(shù)據(jù)達(dá)到 MSS,則需要發(fā)送
    • 如果 SO_SNDBUF 中含有 FIN(表示需要連接關(guān)閉)這時將剩余數(shù)據(jù)發(fā)送,再關(guān)閉
    • 如果 TCP_NODELAY = true,則需要發(fā)送
    • 已發(fā)送的數(shù)據(jù)都收到 ack 時,則需要發(fā)送
    • 上述條件不滿足,但發(fā)生超時(一般為 200ms)則需要發(fā)送
    • 除上述情況,延遲發(fā)送

1.4 解決方案

  1. 短鏈接,發(fā)一個包建立一次連接,這樣連接建立到連接斷開之間就是消息的邊界,缺點效率太低
  2. 每一條消息采用固定長度,缺點浪費空間
  3. 每一條消息采用分隔符,例如 \n,缺點需要轉(zhuǎn)義
  4. 每一條消息分為 head 和 body,head 中包含 body 的長度

方法1,短鏈接

以解決粘包為例

public class HelloWorldClient {static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);public static void main(String[] args) {// 分 10 次發(fā)送for (int i = 0; i < 10; i++) {send();}}private static void send() {NioEventLoopGroup worker = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("conneted...");ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.debug("sending...");ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});ctx.writeAndFlush(buffer);// 發(fā)完即關(guān)ctx.channel().close();}});}});ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("client error", e);} finally {worker.shutdownGracefully();}}
}

輸出,略

半包用這種辦法還是解決不了,因為接收方的緩沖區(qū)大小是有限的(可再服務(wù)端通過如下方式調(diào)整netty的接收緩沖區(qū),查看還是會出現(xiàn)半包現(xiàn)象:serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));

方法2,固定長度

客戶端的操作就是一次發(fā)送一個100字節(jié)長度的消息, 其中每10個字節(jié)表示一條消息(不夠10個字節(jié)的補位,湊夠10個字節(jié))

服務(wù)端使用定長解碼器,每10個字節(jié),湊夠1條消息,才給到下一個handler

客戶端代碼

@Slf4j
public class HelloWorldClient {public static void main(String[] args) {send();}private static void send() {NioEventLoopGroup worker = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {// 會在連接 channel 建立成功之后,會觸發(fā) active事件@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {/*這里的操作就是發(fā)送一個100字節(jié)長度的消息, 其中每10個字節(jié)表示一條消息*/ByteBuf buf = ctx.alloc().buffer();char c = '0';Random r = new Random();for (int i = 0; i < 10; i++) {byte[] bytes = fill10Bytes(c, r.nextInt(10) + 1);c++;buf.writeBytes(bytes);}ctx.writeAndFlush(buf);}});}});ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("client error", e);} finally {worker.shutdownGracefully();}}// 填夠10個字節(jié)public static byte[] fill10Bytes(char c, int len) {byte[] bytes = new byte[10];Arrays.fill(bytes, (byte) '_');for (int i = 0; i < len; i++) {bytes[i] = (byte) c;}System.out.println(new String(bytes));return bytes;}
}

服務(wù)端代碼

@Slf4j
public class HelloWorldServer {void start() {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();// 為現(xiàn)象明顯,服務(wù)端修改一下接收緩沖區(qū),其它代碼不變// (調(diào)整系統(tǒng)的接收緩沖區(qū)(即滑動窗口))// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);// 調(diào)整netty的接收緩沖區(qū)// serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,//                             new AdaptiveRecvByteBufAllocator(16, 16, 16));serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 注意順序: 要先添加定長的解碼處理器, 再添加日志處理器ch.pipeline().addLast(new FixedLengthFrameDecoder(10));ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));}});ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();log.debug("stoped");}}public static void main(String[] args) {new HelloWorldServer().start();}
}

客戶端輸出,可以看到客戶端輸出,一次寫入了100個字節(jié)

Connected to the target VM, address: '127.0.0.1:50935', transport: 'socket'
13:51:29 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xdc9dffac] REGISTERED
13:51:29 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xdc9dffac] CONNECT: /127.0.0.1:8080
13:51:29 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xdc9dffac, L:/127.0.0.1:50973 - R:/127.0.0.1:8080] ACTIVE
000000____
111_______
2222222222
333333____
444_______
55________
6666666___
77777_____
8888______
99999_____
13:51:29 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xdc9dffac, L:/127.0.0.1:50973 - R:/127.0.0.1:8080] WRITE: 100B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 5f 5f 5f 5f 31 31 31 5f 5f 5f |000000____111___|
|00000010| 5f 5f 5f 5f 32 32 32 32 32 32 32 32 32 32 33 33 |____222222222233|
|00000020| 33 33 33 33 5f 5f 5f 5f 34 34 34 5f 5f 5f 5f 5f |3333____444_____|
|00000030| 5f 5f 35 35 5f 5f 5f 5f 5f 5f 5f 5f 36 36 36 36 |__55________6666|
|00000040| 36 36 36 5f 5f 5f 37 37 37 37 37 5f 5f 5f 5f 5f |666___77777_____|
|00000050| 38 38 38 38 5f 5f 5f 5f 5f 5f 39 39 39 39 39 5f |8888______99999_|
|00000060| 5f 5f 5f 5f                                     |____            |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xdc9dffac, L:/127.0.0.1:50973 - R:/127.0.0.1:8080] FLUSH

服務(wù)端輸出,重點看服務(wù)端輸出,服務(wù)端每10個字節(jié)接收作為一條消息,交給后面的handler處理,并沒有發(fā)生半包

13:51:21 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0xafdaf078] binding...
13:51:21 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0xafdaf078, L:/0:0:0:0:0:0:0:0:8080] bound...
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] REGISTERED
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] ACTIVE
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 5f 5f 5f 5f                   |000000____      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 31 31 5f 5f 5f 5f 5f 5f 5f                   |111_______      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 32 32 32 32 32 32 32 32 32 32                   |2222222222      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 33 33 33 33 33 5f 5f 5f 5f                   |333333____      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 34 34 34 5f 5f 5f 5f 5f 5f 5f                   |444_______      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 35 35 5f 5f 5f 5f 5f 5f 5f 5f                   |55________      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 36 36 36 36 36 36 36 5f 5f 5f                   |6666666___      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 37 37 37 37 37 5f 5f 5f 5f 5f                   |77777_____      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 38 38 38 38 5f 5f 5f 5f 5f 5f                   |8888______      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ: 10B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 39 39 39 39 39 5f 5f 5f 5f 5f                   |99999_____      |
+--------+-------------------------------------------------+----------------+
13:51:29 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x0d23d3d4, L:/127.0.0.1:8080 - R:/127.0.0.1:50973] READ COMPLETE

缺點是,數(shù)據(jù)包的大小不好把握

  • 長度定的太大,浪費
  • 長度定的太小,對某些數(shù)據(jù)包又顯得不夠

方法3,固定分隔符

服務(wù)端代碼

  • 服務(wù)端加入,默認(rèn)以 \n 或 \r\n 作為分隔符的處理器(如果超出最大長度仍未出現(xiàn)分隔符,則拋出異常)
@Slf4j
public class HelloWorldServer {void start() {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();// 為現(xiàn)象明顯,服務(wù)端修改一下接收緩沖區(qū),其它代碼不變// (調(diào)整系統(tǒng)的接收緩沖區(qū)(即滑動窗口))// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);// 調(diào)整netty的接收緩沖區(qū)// serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,//                             new AdaptiveRecvByteBufAllocator(16, 16, 16));serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 注意順序: 要先添加基于分隔符的解碼處理器, 再添加日志處理器ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));}});ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("{} binding...", channelFuture.channel());channelFuture.sync();log.debug("{} bound...", channelFuture.channel());channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();log.debug("stoped");}}public static void main(String[] args) {new HelloWorldServer().start();}
}

客戶端代碼

客戶端在每條消息之后,加入 \n 分隔符

@Slf4j
public class HelloWorldClient {public static void main(String[] args) {send();}private static void send() {NioEventLoopGroup worker = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {// 會在連接 channel 建立成功之后,會觸發(fā) active事件@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {/*在每條消息后面加上1個\n, 來表示消息的分隔符*/ByteBuf buf = ctx.alloc().buffer();char c = '0';Random r = new Random();for (int i = 0; i < 10; i++) {StringBuilder sb = makeString(c, r.nextInt(256) + 1);buf.writeBytes(sb.toString().getBytes());c++;}// 還是1次性寫入bufctx.writeAndFlush(buf);}});}});ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("client error", e);} finally {worker.shutdownGracefully();}}public static StringBuilder makeString(char c, int len) {StringBuilder sb = new StringBuilder(len + 2);for (int i = 0; i < len; i++) {sb.append(c);}sb.append("\n");return sb;}
}

客戶端輸出

14:27:28 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x45f4846a] REGISTERED
14:27:28 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x45f4846a] CONNECT: /127.0.0.1:8080
14:27:28 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x45f4846a, L:/127.0.0.1:54341 - R:/127.0.0.1:8080] ACTIVE
14:27:28 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x45f4846a, L:/127.0.0.1:54341 - R:/127.0.0.1:8080] WRITE: 1445B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000010| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000020| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000030| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000040| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000050| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000060| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000070| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000080| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000090| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000a0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000b0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000c0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000d0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000e0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000f0| 30 30 30 30 0a 31 31 31 31 31 31 31 31 31 31 31 |0000.11111111111|
|00000100| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000110| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000120| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000130| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000140| 31 31 31 31 31 31 31 31 31 31 31 31 31 0a 32 32 |1111111111111.22|
|00000150| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000160| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000170| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000180| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000190| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000001a0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000001b0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000001c0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000001d0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000001e0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000001f0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000200| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000210| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000220| 32 32 0a 33 33 33 33 33 33 33 33 33 33 33 33 33 |22.3333333333333|
|00000230| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000240| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000250| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000260| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000270| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000280| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000290| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000002a0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000002b0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000002c0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000002d0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000002e0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000002f0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000300| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000310| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000320| 0a 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |.444444444444444|
|00000330| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000340| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000350| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000360| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000370| 34 34 0a 35 35 35 35 35 35 35 35 35 35 35 35 35 |44.5555555555555|
|00000380| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|00000390| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|000003a0| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|000003b0| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|000003c0| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|000003d0| 35 35 35 35 35 35 35 0a 36 36 36 36 36 36 36 36 |5555555.66666666|
|000003e0| 36 36 36 36 36 36 0a 37 37 37 37 37 37 37 37 37 |666666.777777777|
|000003f0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000400| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000410| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000420| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000430| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000440| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000450| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000460| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000470| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000480| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000490| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000004a0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000004b0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000004c0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000004d0| 37 0a 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |7.88888888888888|
|000004e0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|000004f0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000500| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000510| 38 38 38 38 38 38 38 0a 39 39 39 39 39 39 39 39 |8888888.99999999|
|00000520| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000530| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000540| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000550| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000560| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000570| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000580| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000590| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|000005a0| 39 39 39 39 0a                                  |9999.           |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x45f4846a, L:/127.0.0.1:54341 - R:/127.0.0.1:8080] FLUSH

服務(wù)端輸出

14:27:25 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0xd4acc436] binding...
14:27:25 [DEBUG] [main] c.z.a.HelloWorldServer - [id: 0xd4acc436, L:/0:0:0:0:0:0:0:0:8080] bound...
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] REGISTERED
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] ACTIVE
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 244B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000010| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000020| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000030| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000040| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000050| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000060| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000070| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000080| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000090| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000a0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000b0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000c0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000d0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000e0| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000f0| 30 30 30 30                                     |0000            |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 88B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000010| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000020| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000030| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000040| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000050| 31 31 31 31 31 31 31 31                         |11111111        |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 212B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000010| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000020| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000030| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000040| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000050| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000060| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000070| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000080| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000090| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000000a0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000000b0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000000c0| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|000000d0| 32 32 32 32                                     |2222            |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 253B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000010| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000020| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000030| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000040| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000050| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000060| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000070| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000080| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000090| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000a0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000b0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000c0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000d0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000e0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000f0| 33 33 33 33 33 33 33 33 33 33 33 33 33          |3333333333333   |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 81B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000010| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000020| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000030| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000040| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000050| 34                                              |4               |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 100B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|00000010| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|00000020| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|00000030| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|00000040| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|00000050| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
|00000060| 35 35 35 35                                     |5555            |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 14B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 36 36 36 36 36 36 36 36 36 36 36 36 36 36       |66666666666666  |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 234B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000010| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000020| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000030| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000040| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000050| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000060| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000070| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000080| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000090| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000a0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000b0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000c0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000d0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000e0| 37 37 37 37 37 37 37 37 37 37                   |7777777777      |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 69B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000010| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000020| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000030| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000040| 38 38 38 38 38                                  |88888           |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ: 140B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000010| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000020| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000030| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000040| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000050| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000060| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000070| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000080| 39 39 39 39 39 39 39 39 39 39 39 39             |999999999999    |
+--------+-------------------------------------------------+----------------+
14:27:28 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8dfc81f2, L:/127.0.0.1:8080 - R:/127.0.0.1:54341] READ COMPLETE

缺點,處理字符數(shù)據(jù)比較合適,但如果內(nèi)容本身包含了分隔符(字節(jié)數(shù)據(jù)常常會有此情況),那么就會解析錯誤

方法4,預(yù)設(shè)長度

在發(fā)送消息前,先約定用定長字節(jié)表示接下來數(shù)據(jù)的長度

LengthFieldBasedFrameDecoder

  • lengthFieldOffset - 長度字段偏移量

  • lengthFieldLength - 長度字段長度

  • lengthAdjustment - 長度字段為基準(zhǔn),還要跳過幾個字節(jié)才是內(nèi)容

  • initialBytesTostrip - 從頭剝離幾個字節(jié)

// 最大長度,長度偏移,長度占用字節(jié),長度調(diào)整,剝離字節(jié)數(shù)
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 1, 0, 1));

測試示例,使用EmbeddedChannel模擬

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;public class TestLengthFieldDecoder {public static void main(String[] args) {// 注意處理器的添加順序EmbeddedChannel channel = new EmbeddedChannel(new LengthFieldBasedFrameDecoder(1024, 0, 4, 1, 5),new LoggingHandler(LogLevel.DEBUG));//  4 個字節(jié)的內(nèi)容長度, 實際內(nèi)容ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();// 模擬對方發(fā)送過來消息send(buffer, "Hello, world");send(buffer, "Hi!");// 把消息寫入channelchannel.writeInbound(buffer);}private static void send(ByteBuf buffer, String content) {// 實際內(nèi)容byte[] bytes = content.getBytes();// 實際內(nèi)容長度int length = bytes.length;// 寫入內(nèi)容長度buffer.writeInt(length);// 寫入版本號buffer.writeByte(1);// 寫入實際內(nèi)容buffer.writeBytes(bytes);}
}
// 輸出
/*
16:09:07 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
16:09:07 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
16:09:07 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 12B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64             |Hello, world    |
+--------+-------------------------------------------------+----------------+
16:09:07 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 3B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 69 21                                        |Hi!             |
+--------+-------------------------------------------------+----------------+
16:09:07 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
*/

2. 協(xié)議設(shè)計與解析

2.1 為什么需要協(xié)議?

TCP/IP 中消息傳輸基于流的方式,沒有邊界。

協(xié)議的目的就是劃定消息的邊界,制定通信雙方要共同遵守的通信規(guī)則

例如:在網(wǎng)絡(luò)上傳輸

下雨天留客天留我不留

是中文一句著名的無標(biāo)點符號句子,在沒有標(biāo)點符號情況下,這句話有數(shù)種拆解方式,而意思卻是完全不同,所以常被用作講述標(biāo)點符號的重要性

一種解讀

下雨天留客,天留,我不留

另一種解讀

下雨天,留客天,留我不?留

如何設(shè)計協(xié)議呢?其實就是給網(wǎng)絡(luò)傳輸?shù)男畔⒓由稀皹?biāo)點符號”。但通過分隔符來斷句不是很好,因為分隔符本身如果用于傳輸,那么必須加以區(qū)分。因此,下面一種協(xié)議較為常用

定長字節(jié)表示內(nèi)容長度 + 實際內(nèi)容

例如,假設(shè)一個中文字符長度為 3,按照上述協(xié)議的規(guī)則,發(fā)送信息方式如下,就不會被接收方弄錯意思了

0f下雨天留客06天留09我不留

小故事

很久很久以前,一位私塾先生到一家任教。雙方簽訂了一紙協(xié)議:“無雞鴨亦可無魚肉亦可白菜豆腐不可少不得束修金”。此后,私塾先生雖然認(rèn)真教課,但主人家則總是給私塾先生以白菜豆腐為菜,絲毫未見雞鴨魚肉的款待。私塾先生先是很不解,可是后來也就想通了:主人把雞鴨魚肉的錢都會換為束修金的,也罷。至此雙方相安無事。

年關(guān)將至,一個學(xué)年段亦告結(jié)束。私塾先生臨行時,也不見主人家為他交付束修金,遂與主家理論。然主家亦振振有詞:“有協(xié)議為證——無雞鴨亦可,無魚肉亦可,白菜豆腐不可少,不得束修金。這白紙黑字明擺著的,你有什么要說的呢?”

私塾先生據(jù)理力爭:“協(xié)議是這樣的——無雞,鴨亦可;無魚,肉亦可;白菜豆腐不可,少不得束修金?!?/p>

雙方唇槍舌戰(zhàn),你來我往,真?zhèn)€是不亦樂乎!

這里的束修金,也作“束脩”,應(yīng)當(dāng)是泛指教師應(yīng)當(dāng)?shù)玫降膱蟪?/p>

2.2 redis 協(xié)議舉例

使用netty編寫客戶端,連接redis,并且發(fā)送set aaa bbbget aaa 這2個命令

@Slf4j
public class TestRedis {/*set name zhangsan*3  (3個元素 \r\n)$3  (第一個元素3個字節(jié) \r\n)set (set 3個字節(jié))$4  (第二個元素4個字節(jié) \r\n)name(name 4個字節(jié))$8  (第三個元素8個字節(jié) \r\n)zhangsan(zhangsan 8個字節(jié))*/public static void main(String[] args) {NioEventLoopGroup worker = new NioEventLoopGroup();byte[] LINE = new byte[]{13, 10};System.out.println(LINE_BYTES[0] == '\r');System.out.println(LINE_BYTES[1] == '\n');try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new LoggingHandler());ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {// 會在連接 channel 建立成功后,會觸發(fā) active 事件@Overridepublic void channelActive(ChannelHandlerContext ctx) {// 執(zhí)行命令: set aaa bbbset(ctx);// 執(zhí)行命令: get aaaget(ctx);}private void get(ChannelHandlerContext ctx) {ByteBuf buf = ctx.alloc().buffer();buf.writeBytes("*2".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("get".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("aaa".getBytes());buf.writeBytes(LINE);ctx.writeAndFlush(buf);}private void set(ChannelHandlerContext ctx) {ByteBuf buf = ctx.alloc().buffer();buf.writeBytes("*3".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("set".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("aaa".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("bbb".getBytes());buf.writeBytes(LINE); // 注意:最后面的這個\r\n也不能省略ctx.writeAndFlush(buf); }@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;System.out.println(buf.toString(Charset.defaultCharset()));// 我覺得這里應(yīng)該需要釋放buf吧??}});}});ChannelFuture channelFuture = bootstrap.connect("localhost", 6379).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("client error", e);} finally {worker.shutdownGracefully(); // 優(yōu)雅關(guān)閉}}
}
// 輸出日志
/*12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1] REGISTERED
12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1] CONNECT: localhost/127.0.0.1:6379
12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1, L:/127.0.0.1:58542 - R:localhost/127.0.0.1:6379] ACTIVE
12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1, L:/127.0.0.1:58542 - R:localhost/127.0.0.1:6379] WRITE: 31B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 33 0d |*3..$3..set..$3.|
|00000010| 0a 61 61 61 0d 0a 24 33 0d 0a 62 62 62 0d 0a    |.aaa..$3..bbb.. |
+--------+-------------------------------------------------+----------------+
12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1, L:/127.0.0.1:58542 - R:localhost/127.0.0.1:6379] FLUSH
12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1, L:/127.0.0.1:58542 - R:localhost/127.0.0.1:6379] WRITE: 22B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 2a 32 0d 0a 24 33 0d 0a 67 65 74 0d 0a 24 33 0d |*2..$3..get..$3.|
|00000010| 0a 61 61 61 0d 0a                               |.aaa..          |
+--------+-------------------------------------------------+----------------+
12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1, L:/127.0.0.1:58542 - R:localhost/127.0.0.1:6379] FLUSH
12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1, L:/127.0.0.1:58542 - R:localhost/127.0.0.1:6379] READ: 14B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 2b 4f 4b 0d 0a 24 33 0d 0a 62 62 62 0d 0a       |+OK..$3..bbb..  |
+--------+-------------------------------------------------+----------------+
+OK
$3
bbb12:57:21 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x80c5b3b1, L:/127.0.0.1:58542 - R:localhost/127.0.0.1:6379] READ COMPLETE*/

2.3 http 協(xié)議舉例

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class TestHttp {public static void main(String[] args) throws InterruptedException {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap().group(boss, worker).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));// 配置http編解碼器(包括編碼、解碼)// 解碼 - 把發(fā)向服務(wù)端的請求ByteBuf解碼// 編碼 - 把發(fā)給客戶端的數(shù)據(jù)編碼成ByteBuf, 以便寫出字節(jié)數(shù)據(jù)// (HttpServerCodec繼承自CombinedChannelDuplexHandler<HttpRequestDecoder,//                                                   HttpResponseEncoder>)ch.pipeline().addLast(new HttpServerCodec());// 注意寫入的泛型, 表示該處理器只處理該類型的消息ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {// 獲取請求urilog.info(msg.uri());// 響應(yīng)對象DefaultFullHttpResponse response = new DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);byte[] content = "<h1>hello,world</h1>".getBytes();response.content().writeBytes(content);// 告訴瀏覽器, 響應(yīng)內(nèi)容就這么長, 不然瀏覽器會一直傻傻的等待接收后面的內(nèi)容// (網(wǎng)站圖標(biāo)位置一直在轉(zhuǎn)圈圈)response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.length);// 寫回響應(yīng)// (這個response將會經(jīng)過HttpServerCodec出站處理器,將它編碼了,//   再返回回去)ctx.writeAndFlush(response);}});/*// 當(dāng)瀏覽器發(fā)起1個get請求后, 該處理器的channelRead方法被調(diào)用了2次,//      - 第一次傳過來的msg是 DefaultHttpRequest(decodeResult: success,version: HTTP/1.1)//      - 第二次傳過來的是 EmptyLastHttpContentch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("接收到請求: {}", msg);// HttpServerCodec會把接收到的請求, // 分為 HttpRequest 和 HttpContent(對該handler進行了2次調(diào)用)// 如果只處理其中某一種類型的消息,可以如上使用// SimpleChannelInboundHandler,// 并指定泛型(比如上面就指定了HttpRequest)if (msg instanceof HttpRequest) { // 請求行 和 請求頭log.info("HttpRequest msg: {}", msg);} else if (msg instanceof HttpContent) { // 請求體log.info("HttpContent msg: {}", msg);}}});*/}});ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();} finally {boss.shutdownGracefully();worker.shutdownGracefully();}}
}

2.4 自定義協(xié)議要素

  • 魔數(shù),用來在第一時間判定是否是無效數(shù)據(jù)包
  • 版本號,可以支持協(xié)議的升級
  • 序列化算法,消息正文到底采用哪種序列化反序列化方式,可以由此擴展,例如:json、protobuf、hessian、jdk
  • 指令類型,是登錄、注冊、單聊、群聊… 跟業(yè)務(wù)相關(guān)
  • 請求序號,為了雙工通信,提供異步能力
  • 正文長度
  • 消息正文

編解碼器

根據(jù)上面的要素,設(shè)計一個登錄請求消息和登錄響應(yīng)消息,并使用 Netty 完成收發(fā)

MessageCodec
@Slf4j
public class MessageCodec extends ByteToMessageCodec<Message> {@Overridepublic void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {// 1. 4 字節(jié)的魔數(shù)out.writeBytes(new byte[]{1, 2, 3, 4});// 2. 1 字節(jié)的版本,out.writeByte(1);// 3. 1 字節(jié)的序列化方式 jdk 0 , json 1out.writeByte(0);// 4. 1 字節(jié)的指令類型out.writeByte(msg.getMessageType());// 5. 4 個字節(jié)out.writeInt(msg.getSequenceId());// 無意義,對齊填充,為了滿2^n固定字節(jié)數(shù), 正好湊夠16個固定字節(jié)數(shù)out.writeByte(0xff);/* 將Message對象先轉(zhuǎn)為字節(jié)數(shù)組(使用ByteArrayOutputStream + ObjectOutputStream),然后把字節(jié)數(shù)組寫到ByteBuf中 */// 6. 獲取內(nèi)容的字節(jié)數(shù)組ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(msg);byte[] bytes = bos.toByteArray();// 7. 長度out.writeInt(bytes.length);// 8. 寫入內(nèi)容out.writeBytes(bytes);log.info("message 編碼完成...{}", msg);}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {// 讀取 4 字節(jié)的魔數(shù)(讀指針向后移動4位)int magicNum = in.readInt();// 讀取 1 字節(jié)的版本byte version = in.readByte();// 讀取 1 字節(jié)的序列化方式byte serializerType = in.readByte();// 讀取 1 字節(jié)的指令類型byte messageType = in.readByte();// 讀取 4 個字節(jié) 序列idint sequenceId = in.readInt();// 讀取 無意義 的字節(jié)(將讀指針向后移動1位)in.readByte();// 讀取 長度int length = in.readInt();// 使用讀取到的長度創(chuàng)建字節(jié)數(shù)組byte[] bytes = new byte[length];// 將ByteBuf讀取到字節(jié)數(shù)組in.readBytes(bytes, 0, length);/* 這里將ByteBuf的數(shù)據(jù), 通過jdk反序列化的方式, 轉(zhuǎn)為java對象,那就要保證這個數(shù)據(jù)一定是對象完整的序列化字節(jié)數(shù)據(jù),如果不是一個完整的數(shù)據(jù)(即出現(xiàn)半包的情況), 那就廢了。因此,*/// jdk反序列化 將字節(jié)數(shù)組 轉(zhuǎn)為 Message對象ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));Message message = (Message) ois.readObject();log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);log.debug("message 解碼完成{}", message);// 將消息添加到out集合當(dāng)中out.add(message);}
}
TestMessageCodec
@Slf4j
public class TestMessageCodec {public static void main(String[] args) throws Exception {EmbeddedChannel channel = new EmbeddedChannel(new LoggingHandler(),// 參數(shù)的含義:數(shù)據(jù)的最大長度為1024個字節(jié)、從索引0開始,偏移12個字節(jié)開始,是實際內(nèi)容長度信息、//           實際內(nèi)容長度占4個字節(jié)、不做調(diào)整(即不偏移,實際內(nèi)容長度后面開始就是實際內(nèi)容)、//           不剝離前面的字節(jié)(因為在MessageCodec中有解析前面的字節(jié))// 幀解碼器,解決粘包&半包問題// (如果發(fā)現(xiàn)數(shù)據(jù)不完整(即發(fā)生了半包問題), 它是不會把消息傳遞給下一個處理器處理的,//   等數(shù)據(jù)完整了,它才會把完整的消息發(fā)給下一個處理器)// 此處可用來解決半包的問題: 也就是說, 實際內(nèi)容長度可能有239個字節(jié), 但是本次只收到了100個字節(jié),//                       那如果往后面讀的話, 就讀不滿239個字節(jié), 那就等接收到下一次消息后,//                       一直到滿了239個字節(jié), 再把這個完整的消息, 傳遞給下一個handlernew LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0),// 自定義編解碼器new MessageCodec());log.info("------------------測試編碼-----------------");// 測試encodeLoginRequestMessage message = new LoginRequestMessage("zhangsan", "123");// 向channel中寫出數(shù)據(jù)(MessageCodec會對message作編碼處理,寫入到ByteBuf)channel.writeOutbound(message);log.info("------------------測試解碼-----------------");// 測試decode// 先使用MessageCodec將message寫入到ByteBufByteBuf buf = ByteBufAllocator.DEFAULT.buffer();new MessageCodec().encode(null, message, buf);// 將buf切片成2部分, 模擬半包(先接收100字節(jié), 然后再接收剩下的字節(jié)),// 注意: 須使用LengthFieldBasedFrameDecoder處理半包問題ByteBuf s1 = buf.slice(0, 100);ByteBuf s2 = buf.slice(100, buf.readableBytes() - 100);s1.retain(); // 引用計數(shù)+1(即現(xiàn)在為2)// 這個方法默認(rèn)會釋放buf(release 1),接收半包后的數(shù)據(jù)時,buf被釋放了就會報錯(下面這句會報錯)// 因此上面就使用了s1.retain()讓引用計數(shù)+1了channel.writeInbound(s1);channel.writeInbound(s2);}
}
輸出

觀察下面的輸出

  1. 編碼的時候, 寫出了224個字節(jié)
  2. 解碼的時候,接受了2次,第一次接受收了100個字節(jié),第二次接收了124個字節(jié),并且只出現(xiàn)了1次解碼完成,這說明在出現(xiàn)了半包的情況下LengthFieldBasedFrameDecoder幫助我們解決了半包問題,并且它會接收一個完整的包后,才會傳給后面的handler
  3. 可以留意一下,在寫出的時候,第一行的字節(jié)數(shù)據(jù) 正好 符合 我們的編碼規(guī)則哦
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
21:50:22 [INFO ] [main] c.i.a.c.TestMessageCodec - ------------------測試編碼-----------------
21:50:22 [INFO ] [main] c.i.p.MessageCodec - message 編碼完成...LoginRequestMessage(super=Message(sequenceId=0, messageType=0), username=zhangsan, password=123)
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] WRITE: 214B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 01 00 00 00 00 00 00 ff 00 00 00 c6 |................|
|00000010| ac ed 00 05 73 72 00 25 63 6e 2e 69 74 63 61 73 |....sr.%cn.itcas|
|00000020| 74 2e 6d 65 73 73 61 67 65 2e 4c 6f 67 69 6e 52 |t.message.LoginR|
|00000030| 65 71 75 65 73 74 4d 65 73 73 61 67 65 a0 3f 71 |equestMessage.?q|
|00000040| cb 31 45 b5 88 02 00 02 4c 00 08 70 61 73 73 77 |.1E.....L..passw|
|00000050| 6f 72 64 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 |ordt..Ljava/lang|
|00000060| 2f 53 74 72 69 6e 67 3b 4c 00 08 75 73 65 72 6e |/String;L..usern|
|00000070| 61 6d 65 71 00 7e 00 01 78 72 00 19 63 6e 2e 69 |ameq.~..xr..cn.i|
|00000080| 74 63 61 73 74 2e 6d 65 73 73 61 67 65 2e 4d 65 |tcast.message.Me|
|00000090| 73 73 61 67 65 3d dd 19 a0 bc 07 47 cb 02 00 02 |ssage=.....G....|
|000000a0| 49 00 0b 6d 65 73 73 61 67 65 54 79 70 65 49 00 |I..messageTypeI.|
|000000b0| 0a 73 65 71 75 65 6e 63 65 49 64 78 70 00 00 00 |.sequenceIdxp...|
|000000c0| 00 00 00 00 00 74 00 03 31 32 33 74 00 08 7a 68 |.....t..123t..zh|
|000000d0| 61 6e 67 73 61 6e                               |angsan          |
+--------+-------------------------------------------------+----------------+
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] FLUSH
21:50:22 [INFO ] [main] c.i.a.c.TestMessageCodec - ------------------測試解碼-----------------
21:50:22 [INFO ] [main] c.i.p.MessageCodec - message 編碼完成...LoginRequestMessage(super=Message(sequenceId=0, messageType=0), username=zhangsan, password=123)
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 100B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 01 00 00 00 00 00 00 ff 00 00 00 c6 |................|
|00000010| ac ed 00 05 73 72 00 25 63 6e 2e 69 74 63 61 73 |....sr.%cn.itcas|
|00000020| 74 2e 6d 65 73 73 61 67 65 2e 4c 6f 67 69 6e 52 |t.message.LoginR|
|00000030| 65 71 75 65 73 74 4d 65 73 73 61 67 65 a0 3f 71 |equestMessage.?q|
|00000040| cb 31 45 b5 88 02 00 02 4c 00 08 70 61 73 73 77 |.1E.....L..passw|
|00000050| 6f 72 64 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 |ordt..Ljava/lang|
|00000060| 2f 53 74 72                                     |/Str            |
+--------+-------------------------------------------------+----------------+
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 114B+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 69 6e 67 3b 4c 00 08 75 73 65 72 6e 61 6d 65 71 |ing;L..usernameq|
|00000010| 00 7e 00 01 78 72 00 19 63 6e 2e 69 74 63 61 73 |.~..xr..cn.itcas|
|00000020| 74 2e 6d 65 73 73 61 67 65 2e 4d 65 73 73 61 67 |t.message.Messag|
|00000030| 65 3d dd 19 a0 bc 07 47 cb 02 00 02 49 00 0b 6d |e=.....G....I..m|
|00000040| 65 73 73 61 67 65 54 79 70 65 49 00 0a 73 65 71 |essageTypeI..seq|
|00000050| 75 65 6e 63 65 49 64 78 70 00 00 00 00 00 00 00 |uenceIdxp.......|
|00000060| 00 74 00 03 31 32 33 74 00 08 7a 68 61 6e 67 73 |.t..123t..zhangs|
|00000070| 61 6e                                           |an              |
+--------+-------------------------------------------------+----------------+
21:50:22 [DEBUG] [main] c.i.p.MessageCodec - 16909060, 1, 0, 0, 0, 198
21:50:22 [DEBUG] [main] c.i.p.MessageCodec - message 解碼完成LoginRequestMessage(super=Message(sequenceId=0, messageType=0), username=zhangsan, password=123)
21:50:22 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE

💡 什么時候可以加 @Sharable

  • 當(dāng) handler 不保存狀態(tài)時,就可以安全地在多線程下被共享
  • 但要注意對于編解碼器類,不能繼承 ByteToMessageCodec 或 CombinedChannelDuplexHandler 父類,他們的構(gòu)造方法對 @Sharable 有限制
  • 如果能確保編解碼器不會保存狀態(tài),可以繼承 MessageToMessageCodec 父類
@Slf4j
@ChannelHandler.Sharable
/*** 必須和 LengthFieldBasedFrameDecoder 一起使用,確保接到的 ByteBuf 消息是完整的*/
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {@Overrideprotected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {ByteBuf out = ctx.alloc().buffer();// 1. 4 字節(jié)的魔數(shù)out.writeBytes(new byte[]{1, 2, 3, 4});// 2. 1 字節(jié)的版本,out.writeByte(1);// 3. 1 字節(jié)的序列化方式 jdk 0 , json 1out.writeByte(0);// 4. 1 字節(jié)的指令類型out.writeByte(msg.getMessageType());// 5. 4 個字節(jié)out.writeInt(msg.getSequenceId());// 無意義,對齊填充out.writeByte(0xff);// 6. 獲取內(nèi)容的字節(jié)數(shù)組ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(msg);byte[] bytes = bos.toByteArray();// 7. 長度out.writeInt(bytes.length);// 8. 寫入內(nèi)容out.writeBytes(bytes);outList.add(out);}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {int magicNum = in.readInt();byte version = in.readByte();byte serializerType = in.readByte();byte messageType = in.readByte();int sequenceId = in.readInt();in.readByte();int length = in.readInt();byte[] bytes = new byte[length];in.readBytes(bytes, 0, length);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));Message message = (Message) ois.readObject();log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);log.debug("{}", message);out.add(message);}
}

3. 聊天室案例

3.1 聊天室業(yè)務(wù)介紹

/*** 用戶管理接口*/
public interface UserService {/*** 登錄* @param username 用戶名* @param password 密碼* @return 登錄成功返回 true, 否則返回 false*/boolean login(String username, String password);
}
/*** 會話管理接口*/
public interface Session {/*** 綁定會話* @param channel 哪個 channel 要綁定會話* @param username 會話綁定用戶*/void bind(Channel channel, String username);/*** 解綁會話* @param channel 哪個 channel 要解綁會話*/void unbind(Channel channel);/*** 獲取屬性* @param channel 哪個 channel* @param name 屬性名* @return 屬性值*/Object getAttribute(Channel channel, String name);/*** 設(shè)置屬性* @param channel 哪個 channel* @param name 屬性名* @param value 屬性值*/void setAttribute(Channel channel, String name, Object value);/*** 根據(jù)用戶名獲取 channel* @param username 用戶名* @return channel*/Channel getChannel(String username);
}
/*** 聊天組會話管理接口*/
public interface GroupSession {/*** 創(chuàng)建一個聊天組, 如果不存在才能創(chuàng)建成功, 否則返回 null* @param name 組名* @param members 成員* @return 成功時返回組對象, 失敗返回 null*/Group createGroup(String name, Set<String> members);/*** 加入聊天組* @param name 組名* @param member 成員名* @return 如果組不存在返回 null, 否則返回組對象*/Group joinMember(String name, String member);/*** 移除組成員* @param name 組名* @param member 成員名* @return 如果組不存在返回 null, 否則返回組對象*/Group removeMember(String name, String member);/*** 移除聊天組* @param name 組名* @return 如果組不存在返回 null, 否則返回組對象*/Group removeGroup(String name);/*** 獲取組成員* @param name 組名* @return 成員集合, 沒有成員會返回 empty set*/Set<String> getMembers(String name);/*** 獲取組成員的 channel 集合, 只有在線的 channel 才會返回* @param name 組名* @return 成員 channel 集合*/List<Channel> getMembersChannel(String name);
}

3.2 聊天室業(yè)務(wù)-登錄

@Slf4j
public class ChatServer {public static void main(String[] args) {NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);ch.pipeline().addLast(new SimpleChannelInboundHandler<LoginRequestMessage>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage msg) throws Exception {String username = msg.getUsername();String password = msg.getPassword();boolean login = UserServiceFactory.getUserService().login(username, password);LoginResponseMessage message;if(login) {message = new LoginResponseMessage(true, "登錄成功");} else {message = new LoginResponseMessage(false, "用戶名或密碼不正確");}ctx.writeAndFlush(message);}});}});Channel channel = serverBootstrap.bind(8080).sync().channel();channel.closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();}}
}
@Slf4j
public class ChatClient {public static void main(String[] args) {NioEventLoopGroup group = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);AtomicBoolean LOGIN = new AtomicBoolean(false);try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(group);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());
//                    ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);ch.pipeline().addLast("client handler", new ChannelInboundHandlerAdapter() {// 接收響應(yīng)消息@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.debug("msg: {}", msg);if ((msg instanceof LoginResponseMessage)) {LoginResponseMessage response = (LoginResponseMessage) msg;if (response.isSuccess()) {// 如果登錄成功LOGIN.set(true);}// 喚醒 system in 線程WAIT_FOR_LOGIN.countDown();}}// 在連接建立后觸發(fā) active 事件@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 負(fù)責(zé)接收用戶在控制臺的輸入,負(fù)責(zé)向服務(wù)器發(fā)送各種消息new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("請輸入用戶名:");String username = scanner.nextLine();System.out.println("請輸入密碼:");String password = scanner.nextLine();// 構(gòu)造消息對象LoginRequestMessage message = new LoginRequestMessage(username, password);// 發(fā)送消息ctx.writeAndFlush(message);System.out.println("等待后續(xù)操作...");try {WAIT_FOR_LOGIN.await();} catch (InterruptedException e) {e.printStackTrace();}// 如果登錄失敗if (!LOGIN.get()) {ctx.channel().close();return;}while (true) {System.out.println("==================================");System.out.println("send [username] [content]");System.out.println("gsend [group name] [content]");System.out.println("gcreate [group name] [m1,m2,m3...]");System.out.println("gmembers [group name]");System.out.println("gjoin [group name]");System.out.println("gquit [group name]");System.out.println("quit");System.out.println("==================================");String command = scanner.nextLine();String[] s = command.split(" ");switch (s[0]){case "send":ctx.writeAndFlush(new ChatRequestMessage(username, s[1], s[2]));break;case "gsend":ctx.writeAndFlush(new GroupChatRequestMessage(username, s[1], s[2]));break;case "gcreate":Set<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));set.add(username); // 加入自己ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], set));break;case "gmembers":ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));break;case "gjoin":ctx.writeAndFlush(new GroupJoinRequestMessage(username, s[1]));break;case "gquit":ctx.writeAndFlush(new GroupQuitRequestMessage(username, s[1]));break;case "quit":ctx.channel().close();return;}}}, "system in").start();}});}});Channel channel = bootstrap.connect("localhost", 8080).sync().channel();channel.closeFuture().sync();} catch (Exception e) {log.error("client error", e);} finally {group.shutdownGracefully();}}
}

3.3 聊天室業(yè)務(wù)-單聊

服務(wù)器端將 handler 獨立出來

登錄 handler

@ChannelHandler.Sharable
public class LoginRequestMessageHandler extends SimpleChannelInboundHandler<LoginRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage msg) throws Exception {String username = msg.getUsername();String password = msg.getPassword();boolean login = UserServiceFactory.getUserService().login(username, password);LoginResponseMessage message;if(login) {SessionFactory.getSession().bind(ctx.channel(), username);message = new LoginResponseMessage(true, "登錄成功");} else {message = new LoginResponseMessage(false, "用戶名或密碼不正確");}ctx.writeAndFlush(message);}
}

單聊 handler

@ChannelHandler.Sharable
public class ChatRequestMessageHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage msg) throws Exception {String to = msg.getTo();Channel channel = SessionFactory.getSession().getChannel(to);// 在線if(channel != null) {channel.writeAndFlush(new ChatResponseMessage(msg.getFrom(), msg.getContent()));}// 不在線else {ctx.writeAndFlush(new ChatResponseMessage(false, "對方用戶不存在或者不在線"));}}
}

3.4 聊天室業(yè)務(wù)-群聊

創(chuàng)建群聊

@ChannelHandler.Sharable
public class GroupCreateRequestMessageHandler extends SimpleChannelInboundHandler<GroupCreateRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupCreateRequestMessage msg) throws Exception {String groupName = msg.getGroupName();Set<String> members = msg.getMembers();// 群管理器GroupSession groupSession = GroupSessionFactory.getGroupSession();Group group = groupSession.createGroup(groupName, members);if (group == null) {// 發(fā)生成功消息ctx.writeAndFlush(new GroupCreateResponseMessage(true, groupName + "創(chuàng)建成功"));// 發(fā)送拉群消息List<Channel> channels = groupSession.getMembersChannel(groupName);for (Channel channel : channels) {channel.writeAndFlush(new GroupCreateResponseMessage(true, "您已被拉入" + groupName));}} else {ctx.writeAndFlush(new GroupCreateResponseMessage(false, groupName + "已經(jīng)存在"));}}
}

群聊

@ChannelHandler.Sharable
public class GroupChatRequestMessageHandler extends SimpleChannelInboundHandler<GroupChatRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupChatRequestMessage msg) throws Exception {List<Channel> channels = GroupSessionFactory.getGroupSession().getMembersChannel(msg.getGroupName());for (Channel channel : channels) {channel.writeAndFlush(new GroupChatResponseMessage(msg.getFrom(), msg.getContent()));}}
}

加入群聊

@ChannelHandler.Sharable
public class GroupJoinRequestMessageHandler extends SimpleChannelInboundHandler<GroupJoinRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupJoinRequestMessage msg) throws Exception {Group group = GroupSessionFactory.getGroupSession().joinMember(msg.getGroupName(), msg.getUsername());if (group != null) {ctx.writeAndFlush(new GroupJoinResponseMessage(true, msg.getGroupName() + "群加入成功"));} else {ctx.writeAndFlush(new GroupJoinResponseMessage(true, msg.getGroupName() + "群不存在"));}}
}

退出群聊

@ChannelHandler.Sharable
public class GroupQuitRequestMessageHandler extends SimpleChannelInboundHandler<GroupQuitRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupQuitRequestMessage msg) throws Exception {Group group = GroupSessionFactory.getGroupSession().removeMember(msg.getGroupName(), msg.getUsername());if (group != null) {ctx.writeAndFlush(new GroupJoinResponseMessage(true, "已退出群" + msg.getGroupName()));} else {ctx.writeAndFlush(new GroupJoinResponseMessage(true, msg.getGroupName() + "群不存在"));}}
}

查看成員

@ChannelHandler.Sharable
public class GroupMembersRequestMessageHandler extends SimpleChannelInboundHandler<GroupMembersRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupMembersRequestMessage msg) throws Exception {Set<String> members = GroupSessionFactory.getGroupSession().getMembers(msg.getGroupName());ctx.writeAndFlush(new GroupMembersResponseMessage(members));}
}

3.5 聊天室業(yè)務(wù)-退出

@Slf4j
@ChannelHandler.Sharable
public class QuitHandler extends ChannelInboundHandlerAdapter {// 當(dāng)連接斷開時觸發(fā) inactive 事件@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {SessionFactory.getSession().unbind(ctx.channel());log.debug("{} 已經(jīng)斷開", ctx.channel());}// 當(dāng)出現(xiàn)異常時觸發(fā)@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {SessionFactory.getSession().unbind(ctx.channel());log.debug("{} 已經(jīng)異常斷開 異常是{}", ctx.channel(), cause.getMessage());}
}

3.6 聊天室業(yè)務(wù)-空閑檢測

連接假死

原因

  • 網(wǎng)絡(luò)設(shè)備出現(xiàn)故障,例如網(wǎng)卡,機房等,底層的 TCP 連接已經(jīng)斷開了,但應(yīng)用程序沒有感知到,仍然占用著資源。
  • 公網(wǎng)網(wǎng)絡(luò)不穩(wěn)定,出現(xiàn)丟包。如果連續(xù)出現(xiàn)丟包,這時現(xiàn)象就是客戶端數(shù)據(jù)發(fā)不出去,服務(wù)端也一直收不到數(shù)據(jù),就這么一直耗著
  • 應(yīng)用程序線程阻塞,無法進行數(shù)據(jù)讀寫

問題

  • 假死的連接占用的資源不能自動釋放
  • 向假死的連接發(fā)送數(shù)據(jù),得到的反饋是發(fā)送超時

服務(wù)器端解決

  • 怎么判斷客戶端連接是否假死呢?如果能收到客戶端數(shù)據(jù),說明沒有假死。因此策略就可以定為,每隔一段時間就檢查這段時間內(nèi)是否接收到客戶端數(shù)據(jù),沒有就可以判定為連接假死
// 用來判斷是不是 讀空閑時間過長,或 寫空閑時間過長
// 5s 內(nèi)如果沒有收到 channel 的數(shù)據(jù),會觸發(fā)一個 IdleState#READER_IDLE 事件
ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
// ChannelDuplexHandler 可以同時作為入站和出站處理器
ch.pipeline().addLast(new ChannelDuplexHandler() {// 用來觸發(fā)特殊事件@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{IdleStateEvent event = (IdleStateEvent) evt;// 觸發(fā)了讀空閑事件if (event.state() == IdleState.READER_IDLE) {log.debug("已經(jīng) 5s 沒有讀到數(shù)據(jù)了");ctx.channel().close();}}
});

客戶端定時心跳

  • 客戶端可以定時向服務(wù)器端發(fā)送數(shù)據(jù),只要這個時間間隔小于服務(wù)器定義的空閑檢測的時間間隔,那么就能防止前面提到的誤判,客戶端可以定義如下心跳處理器
// 用來判斷是不是 讀空閑時間過長,或 寫空閑時間過長
// 3s 內(nèi)如果沒有向服務(wù)器寫數(shù)據(jù),會觸發(fā)一個 IdleState#WRITER_IDLE 事件
ch.pipeline().addLast(new IdleStateHandler(0, 3, 0));
// ChannelDuplexHandler 可以同時作為入站和出站處理器
ch.pipeline().addLast(new ChannelDuplexHandler() {// 用來觸發(fā)特殊事件@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{IdleStateEvent event = (IdleStateEvent) evt;// 觸發(fā)了寫空閑事件if (event.state() == IdleState.WRITER_IDLE) {// log.debug("3s 沒有寫數(shù)據(jù)了,發(fā)送一個心跳包");ctx.writeAndFlush(new PingMessage());}}
});
http://www.risenshineclean.com/news/511.html

相關(guān)文章:

  • 訂做網(wǎng)站/四川網(wǎng)站seo
  • 做網(wǎng)站怎么租個空間/違禁網(wǎng)站用什么瀏覽器
  • 行業(yè)垂直網(wǎng)站開發(fā)/網(wǎng)絡(luò)推廣seo教程
  • 做影視網(wǎng)站用主機還是用服務(wù)器/semseo是什么意思
  • 蘇州網(wǎng)站開發(fā)公司招聘/搜索網(wǎng)站有哪幾個
  • 企業(yè)網(wǎng)站制作 深圳/免費發(fā)布廣告的平臺
  • 有.net源碼如何做網(wǎng)站/網(wǎng)絡(luò)優(yōu)化培訓(xùn)要多少錢
  • 做動態(tài)的網(wǎng)站的參考資料有哪些/seo排名教程
  • 天津b2b網(wǎng)站建設(shè)公司哪家好/化工seo顧問
  • 做網(wǎng)站需要交印花稅/上海優(yōu)化營商環(huán)境
  • 網(wǎng)站建設(shè)屬于哪個專業(yè)/太原百度快速優(yōu)化排名
  • 駿域網(wǎng)站建設(shè)專家/seo排名資源
  • 國際外貿(mào)網(wǎng)站建設(shè)/公司網(wǎng)站模版
  • 自己做交友網(wǎng)站/愛站權(quán)重查詢
  • 備案時的網(wǎng)站名稱/百度推廣賬號注冊
  • wordpress 引入css/seo培訓(xùn)師
  • 保定市做網(wǎng)站公司地址電話/抖音推廣平臺聯(lián)系方式
  • 廣州網(wǎng)站建設(shè)c2c/寧波網(wǎng)絡(luò)推廣軟件
  • 甜品網(wǎng)站模板/圖片外鏈在線生成網(wǎng)址
  • 青島網(wǎng)站制作服務(wù)商/如何做好網(wǎng)站推廣優(yōu)化
  • 個人網(wǎng)站建設(shè)與維護/如何建站
  • wui網(wǎng)站建設(shè)/上海seo推廣平臺
  • 福田祥菱m2柴油版/seo高級教程
  • 靜態(tài)網(wǎng)站 apache/專業(yè)網(wǎng)站優(yōu)化排名
  • 查看注冊過的網(wǎng)站/nba最新交易信息
  • 做banner的在線網(wǎng)站/臨沂seo公司穩(wěn)健火星
  • asp網(wǎng)站偽靜態(tài)教程/常見的搜索引擎
  • 做網(wǎng)站的分辨率/祁陽seo
  • 做婚紗網(wǎng)站的意義/日本比分預(yù)測
  • wordpress國內(nèi)訪問/一個具體網(wǎng)站的seo優(yōu)化