netty 解决TCP粘包与拆包问题(一)


1.什么是TCP粘包与拆包

首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线。当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包。得不到我们想要的效果。

所谓粘包:当你把A,B两个数据从甲发送到乙,本想A与B单独发送,但是你却把AB一起发送了,此时AB粘在一起,就是粘包了

所谓拆包: 如果发送数据的时候,你把A、B拆成了几份发,就是拆包了。当然数据不是你主动拆的,是TCP流自动拆的

 

2.TCP粘包与拆包产生原因

1.进行了MSS大小的TCP分段
2.以太网帧的plyload大与MTU进行了IP分片
3.应用程序write写入的字节大小大于套接口发送的缓冲区大小

 

3.解决方法

1.消息定长,比如把报文消息固定为500字节,不够用空格补位

2.在包尾增加回车换行符进行分割,例如FTP协议

3.将消息分为消息头和消息体,消息头中包含表示消息总长度的字段

4.更复杂的应用层协议

 

4.netty 普通解决方法

 

这个是服务端代码

复制代码
 1 package com.ming.netty.nio;  2  3 import java.net.InetSocketAddress;  4  5 import io.netty.bootstrap.ServerBootstrap;  6 import io.netty.channel.ChannelFuture;  7 import io.netty.channel.ChannelInitializer;  8 import io.netty.channel.ChannelOption;  9 import io.netty.channel.EventLoopGroup; 10 import io.netty.channel.nio.NioEventLoopGroup; 11 import io.netty.channel.socket.SocketChannel; 12 import io.netty.channel.socket.nio.NioServerSocketChannel; 13 import io.netty.handler.codec.LineBasedFrameDecoder; 14 import io.netty.handler.codec.string.StringDecoder; 15 16 public class TimeServer { 17 18 19 public static void main(String[] args) throws Exception{ 20 new TimeServer().bind("192.168.1.102", 8400); 21  } 22 23 24 public void bind(String addr,int port) { 25 //配置服务端的nio线程组 26 EventLoopGroup boosGroup=new NioEventLoopGroup(); 27 EventLoopGroup workerGroup=new NioEventLoopGroup(); 28 try { 29 ServerBootstrap b=new ServerBootstrap(); 30  b.group(boosGroup,workerGroup); 31 b.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024) 32 .childHandler(new ChildChannelHandler()); 33 //绑定端口,同步等待成功 34 ChannelFuture f=b.bind(new InetSocketAddress(addr, port)).sync(); 35 System.out.println("启动服务器:"+f.channel().localAddress()); 36 //等等服务器端监听端口关闭 37  f.channel().closeFuture().sync(); 38 } catch (Exception e) { 39 // TODO: handle exception 40 }finally{ 41  boosGroup.shutdownGracefully(); 42  workerGroup.shutdownGracefully(); 43  } 44  } 45 46 private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{ 47 48  @Override 49 protected void initChannel(SocketChannel ch) throws Exception { 50  System.out.println(ch.remoteAddress()); 51 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); 52 ch.pipeline().addLast(new StringDecoder());//增加解码器 53 ch.pipeline().addLast(new TimeServerHandler()); 54 55  } 56 57  } 58 59 60 } 61 package com.ming.netty.nio; 62 63 import java.nio.ByteBuffer; 64 import java.text.SimpleDateFormat; 65 import java.util.Date; 66 67 import io.netty.buffer.ByteBuf; 68 import io.netty.buffer.Unpooled; 69 import io.netty.channel.ChannelHandlerAdapter; 70 import io.netty.channel.ChannelHandlerContext; 71 72 public class TimeServerHandler extends ChannelHandlerAdapter { 73 74 private int counter; 75 76  @Override 77 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 78 String body=(String)msg; 79 System.out.println("服务端收到:"+body+",次数:"+ ++counter); 80 SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 81 String time=dateFormat.format(new Date()); 82 String res="来自与服务端的回应,时间:"+ time; 83 ByteBuf resp=Unpooled.copiedBuffer(res.getBytes()); 84 ctx.writeAndFlush(resp); 85 86 } 87 88 89 90 @Override 91 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 92 ctx.close(); 93 } 94 95 96 97 98 }
复制代码

 

这个是客服端的代码

复制代码
 1 package com.ming.netty.nio;  2  3 import io.netty.bootstrap.Bootstrap;  4 import io.netty.channel.ChannelFuture;  5 import io.netty.channel.ChannelInitializer;  6 import io.netty.channel.ChannelOption;  7 import io.netty.channel.EventLoopGroup;  8 import io.netty.channel.nio.NioEventLoopGroup;  9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioSocketChannel; 11 import io.netty.handler.codec.LineBasedFrameDecoder; 12 import io.netty.handler.codec.string.StringDecoder; 13 14 /** 15  * netty 客户端模拟 16  * @author mingge 17  * 18 */ 19 public class TimeClient { 20 21 22 public static void main(String[] args) throws Exception{ 23 new TimeClient().connect("192.168.1.102", 8400); 24  } 25 26 public void connect(String addr,int port) throws Exception{ 27 EventLoopGroup group=new NioEventLoopGroup(); 28 try { 29 Bootstrap b=new Bootstrap(); 30 b.group(group).channel(NioSocketChannel.class) 31 .option(ChannelOption.TCP_NODELAY, true) 32 .handler(new ChannelInitializer<SocketChannel>() { 33 public void initChannel(SocketChannel ch) throws Exception{ 34 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); 35 ch.pipeline().addLast(new StringDecoder()); 36 ch.pipeline().addLast(new TimeClientHandler()); 37  } 38  }); 39 ChannelFuture f=b.connect(addr,port).sync(); 40 System.out.println("连接服务器:"+f.channel().remoteAddress()+",本地地址:"+f.channel().localAddress()); 41 f.channel().closeFuture().sync();//等待客户端关闭连接 42 } catch (Exception e) { 43  e.printStackTrace(); 44 }finally{ 45 46  group.shutdownGracefully(); 47  } 48  } 49 } 50 package com.ming.netty.nio; 51 52 import io.netty.buffer.ByteBuf; 53 import io.netty.buffer.Unpooled; 54 import io.netty.channel.ChannelHandlerAdapter; 55 import io.netty.channel.ChannelHandlerContext; 56 57 public class TimeClientHandler extends ChannelHandlerAdapter { 58 59 private int counter; 60 61 byte[] req; 62 63 public TimeClientHandler() { 64 req=("我是请求数据哦"+System.getProperty("line.separator")).getBytes(); 65  } 66 67  @Override 68 public void channelActive(ChannelHandlerContext ctx) throws Exception { 69 ByteBuf message=null; 70 for(int i=0;i<100;i++){ 71 message=Unpooled.buffer(req.length); 72  message.writeBytes(req); 73  ctx.writeAndFlush(message); 74  } 75 76  } 77 78 @Override 79 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 80 ByteBuf buf=(ByteBuf)msg; 81 byte[] req=new byte[buf.readableBytes()]; 82 buf.readBytes(req); 83 String body=new String(req,"GBK"); 84 System.out.println("body:"+body+",响应次数:"+(++counter)); 85 } 86 87 @Override 88 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 89 //释放资源 90 ctx.close(); 91 } 92 93 94 }
复制代码

 

这次代码就是比上次的代码多了:LineBasedFrameDecoder,与StringDecoder的写法.

LineBasedFrameDecoder的原理是它依次遍历ByteBuf中的可读字节,判断是否有"\n"或"\r\n",如果有就以此为结束。它是以换行符为结束标志的解码器

StringDecoder的原理就是将接收到的对象转换为字符串,然后接着调用后面的handler。

LineBasedFrameDecoder+StringDecoder组合就是设计按行切换的文本解码器,被设计来支持TCP的粘包与拆包


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM