概述
本文介紹什么是 TCP 粘包和拆包現象,并通過 Netty 編寫詳細的案例來重現 TCP 粘包問題,最后再通過一個 Netty 的 demo 來解決這個問題。具體內容如下
- 什么是 TCP 粘包和拆包現象
- 重現 TCP 粘包和拆包現象
- Netty 解決 TCP 粘包和拆包現象帶來的問題
什么是 TCP 粘包和拆包現象
TCP 編程底層都有粘包和拆包機制,因為我們在C/S這種傳輸模型下,以TCP協議傳輸的時候,在網絡中的byte其實就像是河水,TCP就像一個搬運工,將這流水從一端轉送到另一端,這時又分兩種情況:
- 如果客戶端的每次制造的水比較多,也就是我們常說的客戶端給的包比較大,TCP這個搬運工就會分多次去搬運
- 如果客戶端每次制造的水比較少的話,TCP可能會等客戶端多次生產之后,把所有的水一起再運輸到另一端
- 對于第一種情況,TCP 會再客戶端先進行拆包,在另一端接收的時候,需要把多次獲取的結果組合在一起,變成我們可以理解的信息
- 對于第二種情況,TCP 會在客戶端先進行粘包,在另一端接收的時候,就必須進行拆包處理,因為每次接收的信息,可能是另一個遠程端多次發送的包,被TCP粘在一起的
重現 TCP 粘包和拆包現象
- 通過在客戶端 1 次發送超大數據包給服務器端來重現 TCP 拆包現象
- 通過在客戶端分 10 次發送較小的數據包給服務器端來重現 TCP 粘包現象
下面通過 Netty 重現 TCP 粘包和拆包現象。
Netty maven 依賴
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.76.Final</version>
</dependency>
通過 Netty 重現 TCP 拆包現象
- Netty 客戶端啟動類:NettyClient
package com.ckJAVA.test.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NIOSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class NettyClient {
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
public static void main(String[] args) throws Exception {
// 初始化客戶端事件組
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
// 初始化通道
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
// 初始化通道處理器
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(new NettyClientHandler());
}
});
b.connect(HOST, PORT).addListener(future -> {
log.info(String.format("連接服務器端:%s:%s 成功!", HOST, PORT));
}).await();
} catch (Exception e) {
log.error("啟動客戶端出現異常", e);
}
}
}
- Netty 客戶端通道處理類:NettyClientHandler
package com.ckjava.test.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final AtomicInteger countRef = new AtomicInteger(0);
//客戶端讀取服務器發送的信息
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer);
String message = new String(buffer, StandardCharsets.UTF_8);
log.info(String.format("客戶端接收到消息:[%s]", message));
log.info(String.format("客戶端接收到消息的次數:%s", countRef.accumulateAndGet(1, Integer::sum)));
log.info("---------------------------------------------------");
}
// 重寫 channelActive, 當客戶端啟動的時候 自動發送數據給服務端
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 客戶端只發送一次,但是本次數據量很大
// tcp 會將數據拆分成多份后依次進行發送
String data = "ckjava";
StringBuilder stringBuilder = new StringBuilder(data);
for (int i = 0; i < 10000; i++) {
stringBuilder.Append(data);
}
ByteBuf buffer = Unpooled.copiedBuffer("數據" + stringBuilder.toString(), StandardCharsets.UTF_8);
ctx.writeAndFlush(buffer);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
其中關鍵的代碼如下
// 重寫 channelActive, 當客戶端啟動的時候 自動發送數據給服務端
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 客戶端只發送一次,但是本次數據量很大
// tcp 會將數據拆分成多份后依次進行發送
String data = "ckjava";
StringBuilder stringBuilder = new StringBuilder(data);
for (int i = 0; i < 10000; i++) {
stringBuilder.append(data);
}
ByteBuf buffer = Unpooled.copiedBuffer("數據" + stringBuilder.toString(), StandardCharsets.UTF_8);
ctx.writeAndFlush(buffer);
}
- Netty 客戶端啟動類:NettyClient
package com.ckjava.test.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
@Slf4j
@Component
public class NettyServer {
private final int port;
public NettyServer(int port) {
this.port = port;
}
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap sbs = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new NettyServerHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 綁定端口,開始接收進來的連接
sbs.bind(port).addListener(future -> {
log.info(String.format("服務器端啟動成功,開放端口:%s", port));
});
} catch (Exception e) {
log.error("啟動服務器端出現異常", e);
}
}
public static void main(String[] args) {
int port = 8080;
new NettyServer(port).start();
}
}
- Netty 服務器端通道處理類:NettyServer
package com.ckjava.test.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int counter;
private int count;
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer);
//將buffer轉為字符串
String message = new String(buffer, StandardCharsets.UTF_8);
System.out.println("服務器收到的數據=" + message);
System.out.println("服務器收到的數據次數=" + (++this.count));
//服務器回送數據給客戶端 回送一個隨機id
ByteBuf buffer1 = Unpooled.copiedBuffer(UUID.randomUUID().toString(), StandardCharsets.UTF_8);
ctx.writeAndFlush(buffer1);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 分別先啟動服務器端后,再啟動客戶端,服務器端的輸出如下
- 客戶端的輸出如下
17:03:24.474 [nioEventLoopGroup-2-1] INFO com.ckjava.test.client.NettyClient - 連接服務器端:127.0.0.1:8080 成功!
17:03:24.535 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[c471a239-abe5-4401-93aa-b3d5e432c422021b6ae3-4939-4d59-b451-235af6c9e2190536b0aa-3b53-4b03-bb68-b0637d619d0f]
17:03:24.537 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:1
17:03:24.537 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
- 從服務器端和客戶端的輸出結果來看:客戶端只發送了 1 次數據,但是服務器端卻收到了 3 次數據,說明 tcp 在客戶端拆包后分 3 次發送了;并且客戶端之后只收到了一次數據,說明服務器的回復數據在服務器端也出現了粘包現象,并且導致了數據無法區分的問題。
通過 Netty 重現 TCP 粘包現象
- 還用上面的例子,將客戶端通道處理類:NettyClientHandler 中的 channelActive 方法修改成如下的方式
// 重寫 channelActive, 當客戶端啟動的時候 自動發送數據給服務端
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 使用客戶端分10次發送,每次數據量少
// tcp 會等客戶端多次生產后,一次性進行發送
for (int i = 0; i < 10; i++) {
ByteBuf buffer = Unpooled.copiedBuffer("ckjava" + i + " ", StandardCharsets.UTF_8);
ctx.writeAndFlush(buffer);
}
}
- 分別先啟動服務器端后,再啟動客戶端,服務器端的輸出如下
17:12:27.239 [nioEventLoopGroup-2-1] INFO com.ckjava.test.server.NettyServer - 服務器端啟動成功,開放端口:8080
服務器收到的數據=ckjava0 ckjava1 ckjava2 ckjava3 ckjava4 ckjava5 ckjava6 ckjava7 ckjava8 ckjava9
服務器收到的數據次數=1
- 客戶端的輸出如下
17:12:36.917 [nioEventLoopGroup-2-1] INFO com.ckjava.test.client.NettyClient - 連接服務器端:127.0.0.1:8080 成功!
17:12:36.961 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[31b25c25-bd32-4ff1-b390-0c31b2558d12]
17:12:36.962 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:1
17:12:36.962 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
- 從服務器端和客戶端的輸出結果來看:客戶端只發送了 10 次數據,但是服務器端卻收到了 1 次數據,說明 tcp 在客戶端粘包后一次性發送了全部的數據。
Netty 解決 TCP 粘包和拆包現象帶來的問題
TCP 粘包和拆包現象帶來的問題
從上面的案例可以發現當出現 TCP 粘包和拆包現象后會出現下面的問題:
- tcp 在粘包的時候,數據混合后,接收方不能正確區分數據的頭尾,如果是文件類型的數據,會導致文件破壞。
- tcp 在拆包的時候,數據拆分后,接收方不能正確區分數據的頭尾,導致收到的消息錯亂,影響語義。
如何解決 TCP 粘包和拆包現象帶來的問題
由于 TCP 粘包和拆包現象會導致不能正確區分數據的頭尾,那么解決的辦法也挺簡單的,通過 特殊字符串 來分隔消息體或者使用 定長消息 就能夠正確區分數據的頭尾。
目前的主流解決方式有以下幾種:
- 使用定長消息,Client 和 Server 雙方約定報文長度,Server 端接受到報文后,按指定長度解析;
- 使用特定分隔符,比如在消息尾部增加分隔符。Server 端接收到報文后,按照特定的分割符分割消息后,再解析;
- 將消息分割為消息頭和消息體兩部分,消息頭中指定消息或者消息體的長度,通常設計中使用消息頭第一個字段 int32 表示消息體的總長度;
Netty 中也提供了基于分隔符實現的半包解碼器和定長的半包解碼器:
- LineBasedFrameDecoder 使用"n"和"rn"作為分割符的解碼器
- DelimiterBasedFrameDecoder 使用自定義的分割符的解碼器
- FixedLengthFrameDecoder 定長解碼器
通過 Netty 的 DelimiterBasedFrameDecoder 解碼器 來解決 TCP 粘包和拆包現象帶來的問題
使用
DelimiterBasedFrameDecoder 可以確保收到的數據會自動通過 自定義的分隔符 進行分隔。發送的時候消息的后面只需要增加上 自定義的分隔符 即可。
- 基于上面的例子,服務器端 NettyServer 改動如下
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap sbs = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) {
// 使用分隔符"$_"的半包解碼器
ByteBuf byteBuf = Unpooled.copiedBuffer(DELIMITER.getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, byteBuf));
ch.pipeline().addLast(new NettyServerHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 綁定端口,開始接收進來的連接
sbs.bind(port).addListener(future -> {
log.info(String.format("服務器端啟動成功,開放端口:%s", port));
});
} catch (Exception e) {
log.error("啟動服務器端出現異常", e);
}
}
- 服務器端 NettyServerHandler 改動如下
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer);
//將buffer轉為字符串
String message = new String(buffer, StandardCharsets.UTF_8);
System.out.println("服務器收到的數據=" + message);
System.out.println("服務器收到的數據次數=" + (++this.count));
//服務器回送數據給客戶端 回送一個隨機id
String replyData = UUID.randomUUID().toString();
ByteBuf buffer1 = Unpooled.copiedBuffer(replyData.concat(NettyServer.DELIMITER), StandardCharsets.UTF_8);
ctx.writeAndFlush(buffer1);
System.out.println("服務器回復數據=" + replyData);
}
- 客戶端 NettyClient 改動如下
public static void main(String[] args) throws Exception {
// 初始化客戶端事件組
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
// 初始化通道
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
// 初始化通道處理器
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
// 使用分隔符"$_"的半包解碼器
ByteBuf byteBuf = Unpooled.copiedBuffer(DELIMITER.getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, byteBuf));
p.addLast(new NettyClientHandler());
}
});
b.connect(HOST, PORT).addListener(future -> {
log.info(String.format("連接服務器端:%s:%s 成功!", HOST, PORT));
}).await();
} catch (Exception e) {
log.error("啟動客戶端出現異常", e);
}
}
- 客戶端 NettyClientHandler 中接收數據的部分不變,發送數據的地方改動如下
// 重寫 channelActive, 當客戶端啟動的時候 自動發送數據給服務端
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// tcp 粘包現象
// 使用客戶端分10次發送,每次數據量少
// tcp 會等客戶端多次生產后,一次性進行發送
for (int i = 0; i < 10; i++) {
String data = "ckjava" + i;
log.info(String.format("客戶端發送消息:[%s]", data));
ByteBuf buffer = Unpooled.copiedBuffer(data.concat(NettyClient.DELIMITER), StandardCharsets.UTF_8);
ctx.writeAndFlush(buffer);
}
}
- 服務器端輸出如下
18:14:33.627 [nioEventLoopGroup-2-1] INFO com.ckjava.test.server.NettyServer - 服務器端啟動成功,開放端口:8080
服務器收到的數據=ckjava0
服務器收到的數據次數=1
服務器回復數據=c6129b89-c869-4e06-97ca-55518c55aff7
服務器收到的數據=ckjava1
服務器收到的數據次數=2
服務器回復數據=bc3426cb-072f-4cb9-9f69-d2797863c9e4
服務器收到的數據=ckjava2
服務器收到的數據次數=3
服務器回復數據=43790702-1978-462b-a865-15c0ff2803af
服務器收到的數據=ckjava3
服務器收到的數據次數=4
服務器回復數據=4eb3e4e6-0c6a-4cef-a639-d6c40ebc27d2
服務器收到的數據=ckjava4
服務器收到的數據次數=5
服務器回復數據=6a9f02f9-9e0d-4eae-a380-605c3ba410d2
服務器收到的數據=ckjava5
服務器收到的數據次數=6
服務器回復數據=7ab9e20e-a86b-4f68-8673-5bc024643274
服務器收到的數據=ckjava6
服務器收到的數據次數=7
服務器回復數據=3b6b68cf-c066-4e32-8b5a-961c995fdd6d
服務器收到的數據=ckjava7
服務器收到的數據次數=8
服務器回復數據=cf2a5c51-96d9-4309-8f05-1c09abbe04f2
服務器收到的數據=ckjava8
服務器收到的數據次數=9
服務器回復數據=4d586684-be55-4c10-8071-a88dad5f0684
服務器收到的數據=ckjava9
服務器收到的數據次數=10
服務器回復數據=22fd511e-e65a-4f10-9426-f14b4524d4d0
- 客戶端輸出如下
18:14:50.056 [nioEventLoopGroup-2-1] INFO com.ckjava.test.client.NettyClient - 連接服務器端:127.0.0.1:8080 成功!
18:14:50.058 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava0]
18:14:50.075 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava1]
18:14:50.076 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava2]
18:14:50.076 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava3]
18:14:50.076 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava4]
18:14:50.076 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava5]
18:14:50.076 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava6]
18:14:50.077 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava7]
18:14:50.077 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava8]
18:14:50.077 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端發送消息:[ckjava9]
18:14:50.104 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[c6129b89-c869-4e06-97ca-55518c55aff7]
18:14:50.105 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:1
18:14:50.105 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.105 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[bc3426cb-072f-4cb9-9f69-d2797863c9e4]
18:14:50.105 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:2
18:14:50.105 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[43790702-1978-462b-a865-15c0ff2803af]
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:3
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[4eb3e4e6-0c6a-4cef-a639-d6c40ebc27d2]
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:4
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[6a9f02f9-9e0d-4eae-a380-605c3ba410d2]
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:5
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[7ab9e20e-a86b-4f68-8673-5bc024643274]
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:6
18:14:50.106 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[3b6b68cf-c066-4e32-8b5a-961c995fdd6d]
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:7
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[cf2a5c51-96d9-4309-8f05-1c09abbe04f2]
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:8
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[4d586684-be55-4c10-8071-a88dad5f0684]
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:9
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息:[22fd511e-e65a-4f10-9426-f14b4524d4d0]
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - 客戶端接收到消息的次數:10
18:14:50.107 [nioEventLoopGroup-2-1] INFO c.c.test.client.NettyClientHandler - ---------------------------------------------------
- 從上面的例子可以看出 DelimiterBasedFrameDecoder 會幫自動幫我們把消息切割好,確保收到的數據都是基于 自定義分隔符 分隔好的數據,但是不要忘記在發送數據的時候添加上 自定義分隔符。