netty 解决TCP粘包与拆包问题(一)
2016-05-21 16:46
567 查看
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 普通解决方法
这个是服务端代码
这个是客服端的代码
这次代码就是比上次的代码多了:LineBasedFrameDecoder,与StringDecoder的写法.
LineBasedFrameDecoder的原理是它依次遍历ByteBuf中的可读字节,判断是否有"\n"或"\r\n",如果有就以此为结束。它是以换行符为结束标志的解码器
StringDecoder的原理就是将接收到的对象转换为字符串,然后接着调用后面的handler。
LineBasedFrameDecoder+StringDecoder组合就是设计按行切换的文本解码器,被设计来支持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 普通解决方法
这个是服务端代码
package com.ming.netty.nio; import java.net.InetSocketAddress; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; 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 io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; public class TimeServer { public static void main(String[] args) throws Exception{ new TimeServer().bind("192.168.1.102", 8400); } public void bind(String addr,int port) { //配置服务端的nio线程组 EventLoopGroup boosGroup=new NioEventLoopGroup(); EventLoopGroup workerGroup=new NioEventLoopGroup(); try { ServerBootstrap b=new ServerBootstrap(); b.group(boosGroup,workerGroup); b.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024) .childHandler(new ChildChannelHandler()); //绑定端口,同步等待成功 ChannelFuture f=b.bind(new InetSocketAddress(addr, port)).sync(); System.out.println("启动服务器:"+f.channel().localAddress()); //等等服务器端监听端口关闭 f.channel().closeFuture().sync(); } catch (Exception e) { // TODO: handle exception }finally{ boosGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{ @Override protected void initChannel(SocketChannel ch) throws Exception { System.out.println(ch.remoteAddress()); ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder());//增加解码器 ch.pipeline().addLast(new TimeServerHandler()); } } } package com.ming.netty.nio; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Date; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class TimeServerHandler extends ChannelHandlerAdapter { private int counter; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body=(String)msg; System.out.println("服务端收到:"+body+",次数:"+ ++counter); SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time=dateFormat.format(new Date()); String res="来自与服务端的回应,时间:"+ time; ByteBuf resp=Unpooled.copiedBuffer(res.getBytes()); ctx.writeAndFlush(resp); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
这个是客服端的代码
package com.ming.netty.nio; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; 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.NioSocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; /** * netty 客户端模拟 * @author mingge * */ public class TimeClient { public static void main(String[] args) throws Exception{ new TimeClient().connect("192.168.1.102", 8400); } public void connect(String addr,int port) 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>() { public void initChannel(SocketChannel ch) throws Exception{ ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new TimeClientHandler()); } }); ChannelFuture f=b.connect(addr,port).sync(); System.out.println("连接服务器:"+f.channel().remoteAddress()+",本地地址:"+f.channel().localAddress()); f.channel().closeFuture().sync();//等待客户端关闭连接 } catch (Exception e) { e.printStackTrace(); }finally{ group.shutdownGracefully(); } } } package com.ming.netty.nio; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class TimeClientHandler extends ChannelHandlerAdapter { private int counter; byte[] req; public TimeClientHandler() { req=("我是请求数据哦"+System.getProperty("line.separator")).getBytes(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf message=null; for(int i=0;i<100;i++){ message=Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf=(ByteBuf)msg; byte[] req=new byte[buf.readableBytes()]; buf.readBytes(req); String body=new String(req,"GBK"); System.out.println("body:"+body+",响应次数:"+(++counter)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //释放资源 ctx.close(); } }
这次代码就是比上次的代码多了:LineBasedFrameDecoder,与StringDecoder的写法.
LineBasedFrameDecoder的原理是它依次遍历ByteBuf中的可读字节,判断是否有"\n"或"\r\n",如果有就以此为结束。它是以换行符为结束标志的解码器
StringDecoder的原理就是将接收到的对象转换为字符串,然后接着调用后面的handler。
LineBasedFrameDecoder+StringDecoder组合就是设计按行切换的文本解码器,被设计来支持TCP的粘包与拆包
当然,我们还可以用其余的分隔符来做,详情看下篇。觉得还行点个赞吧
生活就是拿来受虐的......
相关文章推荐
- 试题库问题[网络流24题之7]
- 深度卷积网络CNN与图像语义分割
- 使用dnspod进行简单的HTTP dns解析(防劫持)
- 使用dnspod进行简单的HTTP dns解析(防劫持)
- http://www.oschina.net/code/snippet_12_13918
- TCP/UDP的客户端/服务器编程
- 【BZOJ-3931】网络吞吐量 最短路 + 最大流
- Applet HttpURLConnect & HttpClient
- 2016年华为网络技术精英大赛复赛试题
- http://www.classicdosgames.com/
- TCP之socket编程
- socket编程:简单TCP服务器/客户端编程
- 分享Fresco缓存中的图片
- 利用Cisco Packet Tracer仿真配置网络
- AFHTTPSessionManager网络下载示例
- socket编程——TCP
- 网络拦截和抓包
- https://segmentfault.com/a/1190000002620961
- 套接字编程--TCP
- 网络黑客攻防学习平台之基础关第十一题