个人随笔
目录
netty(五)、netty解决TCP粘包/拆包导致的半包读写问题的三种方案
2021-01-27 21:21:36

这篇文章直接用netty默认提供的三种解决TCP粘包/拆包的三种方案:

LineBasedFrameDecoder
DelimiterBasedFrameDecoder
FixedLengthFrameDecoder

下面各举个例子,这里都是配合StringDecoder来处理,这样子就可以直接用字符串做例子。

不使用解码器解决粘包/拆包情况

1、服务端

  1. public class NettyServer {
  2. public static void main(String[] args) throws Exception{
  3. //1、创建一个线程组,接收客户端连接
  4. EventLoopGroup bossGroup = new NioEventLoopGroup();
  5. //2、创建一个线程组,处理网络操作
  6. EventLoopGroup workerGroup = new NioEventLoopGroup();
  7. try {
  8. //3、创建服务端启动助手来配置参数
  9. ServerBootstrap b = new ServerBootstrap();
  10. b.group(bossGroup, workerGroup)//4、设置两个线程组
  11. .channel(NioServerSocketChannel.class)//5、使用NioServerSocketChannel作为服务器端通道的实现
  12. .option(ChannelOption.SO_BACKLOG, 128)//6、设置线程队列中等待连接的个数
  13. .childOption(ChannelOption.SO_KEEPALIVE, true)//7、保持活动连接状态
  14. .childHandler(new ChannelInitializer<SocketChannel>() {//8、创建一个通道初始化对象
  15. @Override
  16. protected void initChannel(SocketChannel sc) throws Exception {
  17. //把网络字节流自动解码为 String 对象,属于入站处理器
  18. sc.pipeline().addLast(new StringDecoder());
  19. //9、往pipeline链中添加自定义的handler
  20. sc.pipeline().addLast(new NettyServerHandler());
  21. }
  22. });
  23. System.out.println("【服务器已经启动】");
  24. ChannelFuture cf = b.bind(9999).sync();//10、绑定端口,bind方法是异步的,sync方法是同步阻塞的
  25. //11、关闭通道,关闭线程组
  26. cf.channel().closeFuture().sync();//关闭连接(异步非阻塞)
  27. }finally {
  28. bossGroup.shutdownGracefully();
  29. workerGroup.shutdownGracefully();
  30. }
  31. }
  32. }
  1. public class NettyServerHandler extends ChannelInboundHandlerAdapter{
  2. //读取数据事件
  3. @Override
  4. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  5. System.out.println("接收到客户端的消息:"+msg);
  6. }
  7. //异常发生事件
  8. @Override
  9. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  10. //日志:远程主机强迫关闭了一个现有的连接。
  11. System.out.println(cause.getMessage());
  12. ctx.close();
  13. }
  14. }

2、客户端

  1. public class NettyClient {
  2. public static void main(String[] args)throws Exception {
  3. //1、创建一个线程组
  4. EventLoopGroup group = new NioEventLoopGroup();
  5. try {
  6. //2、创建客户端启动助手,完成相关配置
  7. Bootstrap b = new Bootstrap();
  8. b.group(group)//3、设置线程组
  9. .channel(NioSocketChannel.class)//4、设置客户端通道的实现类
  10. .handler(new ChannelInitializer<SocketChannel>() {//创建一个初始化通道对象
  11. @Override
  12. protected void initChannel(SocketChannel sc) throws Exception {
  13. //对 String 对象自动编码,属于出站站处理器
  14. sc.pipeline().addLast(new StringEncoder());
  15. sc.pipeline().addLast(new NettyClientHandler());
  16. }
  17. });
  18. System.out.println("【客户端已启动】");
  19. //7、启动客户端去连接服务器端 connect方法是异步的,sync方法是同步阻塞的
  20. ChannelFuture cf =b.connect("127.0.0.1", 9999).sync();
  21. System.out.println("---"+cf.channel().remoteAddress()+"------");
  22. //8、关闭连接(异步非阻塞)
  23. for(int i=0;i<200;i++) {
  24. String msg="你是狗!";
  25. cf.channel().writeAndFlush(msg);
  26. }
  27. cf.channel().closeFuture().sync();
  28. System.out.print("Client is end.....");
  29. }finally {
  30. group.shutdownGracefully();
  31. }
  32. }
  33. }
  1. public class NettyClientHandler extends ChannelInboundHandlerAdapter{
  2. //异常发生事件
  3. @Override
  4. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  5. //日志:远程主机强迫关闭了一个现有的连接。
  6. System.out.println(cause.getMessage());
  7. ctx.close();
  8. }
  9. }

3、运行测试

服务端输出如下:

可以看出,在没有加入任何处理方案的情况下发生了粘包现象。

解决方案1:LineBasedFrameDecoder

用换行符来解决粘包拆包问题

1、服务端

加上LineBasedFrameDecoder

  1. @Override
  2. protected void initChannel(SocketChannel sc) throws Exception {
  3. sc.pipeline().addLast(new LineBasedFrameDecoder(1024));
  4. //把网络字节流自动解码为 String 对象,属于入站处理器
  5. sc.pipeline().addLast(new StringDecoder());
  6. sc.pipeline().addLast(new NettyServerHandler());
  7. }

2、客户端

加上换行符

  1. for(int i=0;i<200;i++) {
  2. String msg="你是狗!\n";
  3. cf.channel().writeAndFlush(msg);
  4. }

3、运行测试

可以看出没有发生粘包了。

解决方案2:DelimiterBasedFrameDecoder

用分隔符来解决粘包拆包问题

1、服务端

加上DelimiterBasedFrameDecoder

  1. @Override
  2. protected void initChannel(SocketChannel sc) throws Exception {
  3. ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
  4. sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));
  5. //把网络字节流自动解码为 String 对象,属于入站处理器
  6. sc.pipeline().addLast(new StringDecoder());
  7. sc.pipeline().addLast(new NettyServerHandler());
  8. }

2、客户端

加上约定的分隔符

  1. for(int i=0;i<200;i++) {
  2. String msg="你是狗!$_";
  3. cf.channel().writeAndFlush(msg);
  4. }

3、运行测试


可以看出没有发生粘包了。

解决方案3:FixedLengthFrameDecoder

用固定长度来解决粘包拆包的问题

1、服务端

FixedLengthFrameDecoder是根据约定的长度来拆包,在我测试环境win下默认用的是GBK,所以一个汉字是2个字节,这里我要发送”你是狗!”那么就是8个字节,当然你也可以设置为UTF-8编码,那么就是三个字节一个汉字就要12个字节。

  1. @Override
  2. protected void initChannel(SocketChannel sc) throws Exception {
  3. sc.pipeline().addLast(new FixedLengthFrameDecoder(8));
  4. //把网络字节流自动解码为 String 对象,属于入站处理器
  5. sc.pipeline().addLast(new StringDecoder());
  6. sc.pipeline().addLast(new NettyServerHandler());
  7. }

2、客户端

  1. for(int i=0;i<200;i++) {
  2. String msg="你是狗!";
  3. cf.channel().writeAndFlush(msg);
  4. }

3、运行测试


可以看出没有发生粘包了,但是这种情况只能发送8个字节的内容,不然就会解析出错。

 803

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2