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的粘包与拆包