1、粘包與半包 啥也不說了,直接上代碼是不是有點不太友好,我所謂了,都快過年了,還要啥自行車 我上來就是一段代碼猛如虎 1.1 伺服器代碼 public class StudyServer { static final Logger log = LoggerFactory.getLogger(Stu ...
1、粘包與半包
啥也不說了,直接上代碼是不是有點不太友好,我所謂了,都快過年了,還要啥自行車
我上來就是一段代碼猛如虎
1.1 伺服器代碼
public class StudyServer {
static final Logger log = LoggerFactory.getLogger(StudyServer.class);
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>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 連接建立時會執行該方法
log.debug("connected {}", ctx.channel());
super.channelActive(ctx);
}
@Override
public 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());
// 關閉channel
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stopped");
}
}
public static void main(String[] args) {
new StudyServer().start();
}
}
1.2 粘包現象
客戶端代碼
public class StudyClient {
static final Logger log = LoggerFactory.getLogger(StudyClient.class);
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>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
log.debug("connected...");
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("sending...");
// 每次發送16個位元組的數據,共發送10次
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();
}
}
}
伺服器接收結果
7999 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x5b43ecb0, L:/127.0.0.1:8080 - R:/127.0.0.1:53797] 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 |................|
+--------+-------------------------------------------------+----------------+
可見雖然客戶端是分別以 16 位元組為單位,通過 channel 向伺服器發送了 10 次數據,可是 伺服器端卻只接收了一次,接收數據的大小為 160B,即客戶端發送的數據總大小,這就是粘包現象
1.3 半包現象
將客戶端 - 伺服器之間的 channel 容量進行調整
伺服器代碼
// 調整channel的容量
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
註意
serverBootstrap.option (ChannelOption.SO_RCVBUF, 10) 影響的底層接收緩衝區(即滑動視窗)大小,僅決定了 netty 讀取的最小單位,netty 實際每次讀取的一般是它的整數倍
伺服器接收結果
5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] 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 |.... |
+--------+-------------------------------------------------+----------------+
5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B
+-------------------------------------------------+
| 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 |........ |
+--------+-------------------------------------------------+----------------+
5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000010| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000020| 0c 0d 0e 0f 00 01 02 03 |........ |
+--------+-------------------------------------------------+----------------+
5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B
+-------------------------------------------------+
| 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 |........ |
+--------+-------------------------------------------------+----------------+
5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 4B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f |.... |
+--------+-------------------------------------------------+----------------+
可見客戶端每次發送的數據,因 channel 容量不足,無法將發送的數據一次性接收,便產生了半包現象
1.4 現象分析
1.4.1 粘包
- 現象
- 發送 abc def,接收 abcdef
- 原因
- 應用層
- 接收方 ByteBuf 設置太大(Netty 預設 1024);接收方緩衝數據,一起發送
- 傳輸層 - 網路層
- 滑動視窗:假設發送方 256 bytes 表示一個完整報文,但由於接收方處理不及時且 視窗大小足夠大(大於 256 bytes),這 256 bytes 位元組就會緩衝在接收方的滑動視窗中, 當滑動視窗中緩衝了多個報文就會粘包
- Nagle 演算法:會造成粘包
- 應用層
1.4.2 半包
- 現象
- 發送 abcdef,接收 abc def
- 原因
-
應用層
- 接收方 ByteBuf 小於實際發送數據量
-
傳輸層 - 網路層
- 滑動視窗:假設接收方的視窗只剩了 128 bytes,發送方的報文大小是 256 bytes,這時接收方視窗中無法容納發送方的全部報文,發送方只能先發送前 128 bytes,等待 ack 後才能發送剩餘部分,這就造成了半包
-
數據鏈路層
- MSS 限制:當發送的數據超過 MSS 限制後,會將數據切分發送,就會造成半包
-
1.4.3 本質
發生粘包與半包現象的本質是因為 TCP 是流式協議,消息無邊界
1.5 解決方案
1.5.1 短鏈接
客戶端每次向伺服器發送數據以後,就與伺服器斷開連接,此時的消息邊界為連接建立到連接斷開。這時便無需使用滑動視窗等技術來緩衝數據,則不會發生粘包現象。但如果一次性數據發送過多,接收方無法一次性容納所有數據,還是會發生半包現象,所以 短鏈接無法解決半包現象
客戶端代碼改進
修改 channelActive 方法
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("sending...");
ByteBuf buffer = ctx.alloc().buffer(16);
buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
ctx.writeAndFlush(buffer);
// 使用短鏈接,每次發送完畢後就斷開連接
ctx.channel().close();
}
將發送步驟整體封裝為 send () 方法,調用 10 次 send () 方法,模擬發送 10 次數據
public static void main(String[] args) {
// 發送10次
for (int i = 0; i < 10; i++) {
send();
}
}
運行結果
6452 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x3eb6a684, L:/127.0.0.1:8080 - R:/127.0.0.1:65024] ACTIVE
6468 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x3eb6a684, L:/127.0.0.1:8080 - R:/127.0.0.1:65024] READ: 16B
+-------------------------------------------------+
| 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 |................|
+--------+-------------------------------------------------+----------------+
6468 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x3eb6a684, L:/127.0.0.1:8080 ! R:/127.0.0.1:65024] INACTIVE
6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7dcc31ff, L:/127.0.0.1:8080 - R:/127.0.0.1:65057] ACTIVE
6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7dcc31ff, L:/127.0.0.1:8080 - R:/127.0.0.1:65057] READ: 16B
+-------------------------------------------------+
| 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 |................|
+--------+-------------------------------------------------+----------------+
6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7dcc31ff, L:/127.0.0.1:8080 ! R:/127.0.0.1:65057] INACTIVE
...
客戶端先於伺服器建立連接,此時控制台列印 ACTIVE,之後客戶端向伺服器發送了 16B 的數據,發送後斷開連接,此時控制台列印 INACTIVE,可見 未出現粘包現象
1.5.2 定長解碼器
客戶端於伺服器 約定一個最大長度,保證客戶端每次發送的數據長度都不會大於該長度 。若發送數據長度不足則需要 補齊 至該長度
伺服器接收數據時,將接收到的數據按照約定的最大長度進行拆分,即使發送過程中產生了粘包,也可以通過定長解碼器將數據正確地進行拆分。服務端需要用到 FixedLengthFrameDecoder 對數據進行定長解碼,具體使用方法如下
ch.pipeline().addLast(new FixedLengthFrameDecoder(16));
客戶端代碼
客戶端發送數據的代碼如下
// 約定最大長度為16
final int maxLength = 16;
// 被髮送的數據
char c = 'a';
// 向伺服器發送10個報文
for (int i = 0; i < 10; i++) {
ByteBuf buffer = ctx.alloc().buffer(maxLength);
// 定長byte數組,未使用部分會以0進行填充
byte[] bytes = new byte[maxLength];
// 生成長度為0~15的數據
for (int j = 0; j < (int)(Math.random()*(maxLength-1)); j++) {
bytes[j] = (byte) c;
}
buffer.writeBytes(bytes);
c++;
// 將數據發送給伺服器
ctx.writeAndFlush(buffer);
}
伺服器代碼
使用 FixedLengthFrameDecoder 對粘包數據進行拆分,該 handler 需要添加在 LoggingHandler 之前,保證數據被列印時已被拆分
// 通過定長解碼器對粘包數據進行拆分
ch.pipeline().addLast(new FixedLengthFrameDecoder(16));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
運行結果
8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 00 00 00 00 00 00 00 00 00 00 00 00 |aaaa............|
+--------+-------------------------------------------------+----------------+
8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 00 00 00 00 00 00 00 00 00 00 00 00 00 |bbb.............|
+--------+-------------------------------------------------+----------------+
8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |cc..............|
+--------+-------------------------------------------------+----------------+
...
1.5.3 行解碼器
行解碼器的是 通過分隔符對數據進行拆分 來解決粘包半包問題的
可以通過 LineBasedFrameDecoder(int maxLength) 來拆分以換行符 (\n) 為分隔符的數據,也可以通過 DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) 來指定通過什麼分隔符來拆分數據(可以傳入多個分隔符)
兩種解碼器 都需要傳入數據的最大長度 ,若超出最大長度,會拋出 TooLongFrameException 異常
以換行符 \n 為分隔符
客戶端代碼
// 約定最大長度為 64
final int maxLength = 64;
// 被髮送的數據
char c = 'a';
for (int i = 0; i < 10; i++) {
ByteBuf buffer = ctx.alloc().buffer(maxLength);
// 生成長度為0~62的數據
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int j = 0; j < (int)(random.nextInt(maxLength-2)); j++) {
sb.append(c);
}
// 數據以 \n 結尾
sb.append("\n");
buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));
c++;
// 將數據發送給伺服器
ctx.writeAndFlush(buffer);
}
伺服器代碼
// 通過行解碼器對粘包數據進行拆分,以 \n 為分隔符
// 需要指定最大長度
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(64));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
運行結果
4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaa |
+--------+-------------------------------------------------+----------------+
4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 11B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 62 62 62 62 62 62 62 62 |bbbbbbbbbbb |
+--------+-------------------------------------------------+----------------+
4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 63 |cc |
+--------+-------------------------------------------------+----------------+
...
以自定義分隔符 \c 為分隔符
客戶端代碼
...
// 數據以 \c 結尾
sb.append("\\c");
buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));
...
伺服器代碼
// 將分隔符放入ByteBuf中
ByteBuf bufSet = ch.alloc().buffer().writeBytes("\\c".getBytes(StandardCharsets.UTF_8));
// 通過行解碼器對粘包數據進行拆分,以 \c 為分隔符
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(64, ch.alloc().buffer().writeBytes(bufSet)));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
運行結果
8246 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x86215ccd, L:/127.0.0.1:8080 - R:/127.0.0.1:65159] READ: 14B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaa |
+--------+-------------------------------------------------+----------------+
8247 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x86215ccd, L:/127.0.0.1:8080 - R:/127.0.0.1:65159] READ: 3B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 |bbb |
+--------+-------------------------------------------------+----------------+
...
1.5.4 長度欄位解碼器
在傳送數據時可以在數據中 添加一個用於表示有用數據長度的欄位 ,在解碼時讀取出這個用於表明長度的欄位,同時讀取其他相關參數,即可知道最終需要的數據是什麼樣子的
LengthFieldBasedFrameDecoder 解碼器可以提供更為豐富的拆分方法,其構造方法有五個參數
public LengthFieldBasedFrameDecoder(
int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip)
參數解析
- maxFrameLength 數據最大長度
- 表示數據的最大長度(包括附加信息、長度標識等內容)
- lengthFieldOffset 數據長度標識的起始偏移量
- 用於指明數據第幾個位元組開始是用於標識有用位元組長度的,因為前面可能還有其他附加信息
- lengthFieldLength 數據長度標識所占位元組數(用於指明有用數據的長度)
- 數據中用於表示有用數據長度的標識所占的位元組數
- lengthAdjustment 長度表示與有用數據的偏移量
- 用於指明數據長度標識和有用數據之間的距離,因為兩者之間還可能有附加信息
- initialBytesToStrip 數據讀取起點
- 讀取起點,不讀取 0 ~ initialBytesToStrip 之間的數據
參數圖解
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0 (= do not strip header)
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
從 0 開始即為長度標識,長度標識長度為 2 個位元組
0x000C 即為後面 HELLO, WORLD 的長度
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2 (= the length of the Length field)
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
+--------+----------------+ +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
+--------+----------------+ +----------------+
從 0 開始即為長度標識,長度標識長度為 2 個位元組,讀取時從第二個位元組開始讀取(此處即跳過長度標識)
因為跳過了用於表示長度的 2 個位元組,所以此處直接讀取 HELLO, WORLD
lengthFieldOffset = 2 (= the length of Header 1)
lengthFieldLength = 3
lengthAdjustment = 0
initialBytesToStrip = 0
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content |
| 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
長度標識前面還有 2 個位元組的其他內容(0xCAFE),第三個位元組開始才是長度標識,長度表示長度為 3 個位元組 (0x00000C)
Header1 中有附加信息,讀取長度標識時需要跳過這些附加信息來獲取長度
lengthFieldOffset = 0
lengthFieldLength = 3
lengthAdjustment = 2 (= the length of Header 1)
initialBytesToStrip = 0
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content |
| 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
從 0 開始即為長度標識,長度標識長度為 3 個位元組,長度標識之後還有 2 個位元組的其他內容(0xCAFE)
長度標識 (0x00000C) 表示的是從其後 lengthAdjustment(2 個位元組)開始的數據的長度,即 HELLO, WORLD,不包括 0xCAFE
lengthFieldOffset = 1 (= the length of HDR1)
lengthFieldLength = 2
lengthAdjustment = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
長度標識前面有 1 個位元組的其他內容,後面也有 1 個位元組的其他內容,讀取時從長度標識之後 3 個位元組處開始讀取,即讀取 0xFE HELLO, WORLD
使用
通過 EmbeddedChannel 對 handler 進行測試
public class EncoderStudy {
public static void main(String[] args) {
// 模擬伺服器
// 使用EmbeddedChannel測試handler
EmbeddedChannel channel = new EmbeddedChannel(
// 數據最大長度為1KB,長度標識前後各有1個位元組的附加信息,長度標識長度為4個位元組(int)
new LengthFieldBasedFrameDecoder(1024, 1, 4, 1, 0),
new LoggingHandler(LogLevel.DEBUG)
);
// 模擬客戶端,寫入數據
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
send(buffer, "Hello");
channel.writeInbound(buffer);
send(buffer, "World");
channel.writeInbound(buffer);
}
private static void send(ByteBuf buf, String msg) {
// 得到數據的長度
int length = msg.length();
byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
// 將數據信息寫入buf
// 寫入長度標識前的其他信息
buf.writeByte(0xCA);
// 寫入數據長度標識
buf.writeInt(length);
// 寫入長度標識後的其他信息
buf.writeByte(0xFE);
// 寫入具體的數據
buf.writeBytes(bytes);
}
}
運行結果
146 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 11B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| ca 00 00 00 05 fe 48 65 6c 6c 6f |......Hello |
+--------+-------------------------------------------------+----------------+
146 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 11B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| ca 00 00 00 05 fe 57 6f 72 6c 64 |......World |
+--------+-------------------------------------------------+----------------+
本文由
傳智教育博學谷
教研團隊發佈。如果本文對您有幫助,歡迎
關註
和點贊
;如果您有任何建議也可留言評論
或私信
,您的支持是我堅持創作的動力。轉載請註明出處!