refer2 How to deal with the problem of packet sticking and unpacking during TCP transmission? - 编程知识
1)情况1: 服务器接收到2个数据包,没有拆包,也没有粘包问题;
2)情况2: 服务器只接收到一个数据包(存在粘包问题)
3)情况3: 接收者接收到2个冗余或不完整的数据包(粘包与拆包问题同时发生)
注意:
1)业务场景:客户端连续发送10条消息(字符串)到服务器,查看服务器接收情况;
2)客户端发送消息代码:
3)服务器接收消息代码:
3.1)服务器接收消息的打印效果:
- =================================
- 服务器收到的数据 hello server0
-
- 服务器累计收到 [1] 个消息包
- =================================
- 服务器收到的数据 hello server1
-
- 服务器累计收到 [2] 个消息包
- =================================
- 服务器收到的数据 hello server2
- hello server3
- hello server4
- hello server5
- hello server6
-
- 服务器累计收到 [3] 个消息包
- =================================
- 服务器收到的数据 hello server7
- hello server8
- hello server9
-
- 服务器累计收到 [4] 个消息包
【效果解说】
1)粘包原因:
2)拆包原因:
解决粘包拆包的关键在于 为每一个数据包添加界限标识,常用方法如下:
解决方法是:采用方法1,设置每个数据包的长度到报文头部;
- /**
- * @Description 协议数据包
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年09月10日
- */
- public class ProtocolMessage {
-
- private int length;
- private byte[] content;
-
- /**
- * @description 构造器
- * @author xiao tang
- * @date 2022/9/10
- */
- public ProtocolMessage() {
- }
-
- public int getLength() {
- return length;
- }
-
- public void setLength(int length) {
- this.length = length;
- }
-
- public byte[] getContent() {
- return content;
- }
-
- public void setContent(byte[] content) {
- this.content = content;
- }
- }
1)服务器 :
- public class ProtocolNettyServer89 {
-
- public static void main(String[] args) throws InterruptedException {
- EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .childHandler(new ProtocolNettyServerInitializer()); // 自定义一个初始化类
- // 自动服务器
- ChannelFuture channelFuture = serverBootstrap.bind(8089).sync();
- System.out.println("服务器启动成功");
- // 监听关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- bossGroup.shutdownGracefully();
- workerGroup.shutdownGracefully();
- }
- }
- }
2) 服务端初始化器:
- public class ProtocolNettyServerInitializer extends ChannelInitializer
{ - @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ChannelPipeline pipeline = ch.pipeline();
- // 添加入站解码器-把字节转为协议报文便于业务逻辑处理
- pipeline.addLast(new ProtocolMessageDecoder());
- // 添加出站编码器-把协议报文转为字节便于网络传输
- pipeline.addLast(new ProtocolMessageEncoder());
-
- // 添加业务逻辑handler
- pipeline.addLast(new ProtocolNettyServerHandler());
- }
- }
3)处理器:
- public class ProtocolNettyServerHandler extends SimpleChannelInboundHandler
{ -
- private int count = 0;
-
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, ProtocolMessage msg) throws Exception {
- // 接收到数据并处理
- int length = msg.getLength();
- String bodyStr = new String(msg.getContent(), StandardCharsets.UTF_8);
- System.out.println("====================================");
- System.out.println("服务器接收的消息如下:");
- System.out.println("报文长度:" + length);
- System.out.println("报文体内容: " + bodyStr);
- System.out.println("服务器累计接收到的消息包数量 = " + ++this.count);
-
- // 回复客户端
- byte[] body = ("我是服务器" + count).getBytes(StandardCharsets.UTF_8);
- int responseLen = body.length;
- // 构建一个响应协议包
- ProtocolMessage responseMsg = new ProtocolMessage();
- responseMsg.setLength(responseLen);
- responseMsg.setContent(body);
- ctx.writeAndFlush(responseMsg);
- }
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- ctx.close();
- }
- }
1)客户端:
- public class ProtocolNettyClient89 {
- public static void main(String[] args) throws InterruptedException {
- EventLoopGroup group = new NioEventLoopGroup();
- try {
- Bootstrap bootstrap = new Bootstrap();
- bootstrap.group(group)
- .channel(NioSocketChannel.class)
- .handler(new ProtocolNettyClientInitializer()); // 自定义一个初始化类
- // 连接服务器
- ChannelFuture channelFuture = bootstrap.connect("localhost", 8089).sync();
- channelFuture.channel().closeFuture().sync();
- } finally {
- group.shutdownGracefully();
- }
- }
- }
2)初始化器:
- public class ProtocolNettyClientInitializer extends ChannelInitializer
{ - @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ChannelPipeline pipeline = ch.pipeline();
- // 添加出站处理器- 协议报文转字节以便网络传输
- pipeline.addLast(new ProtocolMessageEncoder());
- // 添加入站解码器-把字节转为协议报文对象以便业务逻辑处理
- pipeline.addLast(new ProtocolMessageDecoder());
-
- // 添加一个自定义handler,处理业务逻辑
- pipeline.addLast(new ProtocolNettyClientHandler());
- }
- }
3)处理器:
- public class ProtocolNettyClientHandler extends SimpleChannelInboundHandler
{ - private int count;
-
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, ProtocolMessage msg) throws Exception {
- // 读取服务器响应报文
- int length = msg.getLength();
- byte[] body = msg.getContent();
- System.out.println("=============================");
- System.out.println("客户端接收的消息如下:");
- System.out.println("长度 = " + length);
- System.out.println("报文体 = " + new String(body, StandardCharsets.UTF_8));
- System.out.println("客户端累计接收的消息包数量 = " + ++count);
- }
-
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- // 发送10条数据到服务器
- for (int i = 1; i <= 5; i++) {
- byte[] body = ("你好服务器,我是客户端张三" + i).getBytes(StandardCharsets.UTF_8);
- // 创建协议包对象
- ProtocolMessage message = new ProtocolMessage();
- message.setContent(body);
- message.setLength(body.length);
- // 发送
- ctx.writeAndFlush(message);
- }
- }
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- ctx.close();
- }
- }
1)解码器
- /**
- * @Description 协议报文解码器
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年09月10日
- */
- public class ProtocolMessageDecoder extends ByteToMessageDecoder {
- @Override
- protected void decode(ChannelHandlerContext ctx, ByteBuf in, List throws Exception {
- System.out.println("ProtocolMessageDecoder.decode() 被调用");
- // 把字节 转为 协议报文
- int length = in.readInt();
- byte[] body = new byte[length];
- in.readBytes(body);
- // 封装成 ProtocolMessage,放入out,送入下一个 Handler处理
- ProtocolMessage protocolMessage = new ProtocolMessage();
- protocolMessage.setLength(length);
- protocolMessage.setContent(body);
- // 添加到out
- out.add(protocolMessage);
- }
- }
2)编码器 :
- /**
- * @Description 协议消息编码器
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年09月10日
- */
- public class ProtocolMessageEncoder extends MessageToByteEncoder
{ - @Override
- protected void encode(ChannelHandlerContext ctx, ProtocolMessage msg, ByteBuf out) throws Exception {
- System.out.println("ProtocolMessageEncoder.encode() 被调用");
- out.writeInt(msg.getLength());
- out.writeBytes(msg.getContent());
- }
- }
1)客户端发送5条消息到服务器:
2)服务器接收的数据包为 5个,如下(显然没有发生拆包粘包问题):
- ProtocolMessageDecoder.decode() 被调用
- ====================================
- 服务器接收的消息如下:
- 报文长度:40
- 报文体内容: 你好服务器,我是客户端张三1
- 服务器累计接收到的消息包数量 = 1
- ProtocolMessageEncoder.encode() 被调用
- ProtocolMessageDecoder.decode() 被调用
- ====================================
- 服务器接收的消息如下:
- 报文长度:40
- 报文体内容: 你好服务器,我是客户端张三2
- 服务器累计接收到的消息包数量 = 2
- ProtocolMessageEncoder.encode() 被调用
- ProtocolMessageDecoder.decode() 被调用
- ====================================
- 服务器接收的消息如下:
- 报文长度:40
- 报文体内容: 你好服务器,我是客户端张三3
- 服务器累计接收到的消息包数量 = 3
- ProtocolMessageEncoder.encode() 被调用
- ProtocolMessageDecoder.decode() 被调用
- ====================================
- 服务器接收的消息如下:
- 报文长度:40
- 报文体内容: 你好服务器,我是客户端张三4
- 服务器累计接收到的消息包数量 = 4
- ProtocolMessageEncoder.encode() 被调用
- ProtocolMessageDecoder.decode() 被调用
- ====================================
- 服务器接收的消息如下:
- 报文长度:40
- 报文体内容: 你好服务器,我是客户端张三5
- 服务器累计接收到的消息包数量 = 5
- ProtocolMessageEncoder.encode() 被调用