【Netty4.x】Netty TCP粘包/拆包问题的解决办法(二)
来源:互联网 发布:破解软件盒子下载 编辑:程序博客网 时间:2024/05/20 17:59
上一篇:【Netty4.X】Unity客户端与Netty服务器的网络通信(一)
一、什么是TCP粘包/拆包
如图所示,假如客户端分别发送两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4中情况:
- 第一种情况:Server端分别读取到D1和D2,没有产生粘包和拆包的情况。
- 第二种情况:Server端一次接收到两个数据包,D1和D2粘合在一起,被称为TCP粘包。
- 第三种情况:Server端分2次读取到2个数据包,第一次读取到D1包和D2包的部分内容D2_1,第二次读取到D2包的剩余内容,被称为TCP拆包。
- 第四中情况:Server端分2次读取到2个数据包,第一次读取到D1包的部分内容D1_1 ,第二次读取到D1包的剩余内容D1_2和D2包的整包。
二、重现TCP粘包
2.1 代码示例(服务器端)
修改上一篇【Netty4.X】Unity客户端与Netty服务器的网络通信(一)的ServerHandler类代码。在类中申明一个计数常量count,当每读到一条消息后,就count++,然后发送应答消息给客户端,代码如下:
@Overridepublic 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,"UTF-8").substring(0, req.length - System.getProperty("line.separator").length());count++;System.out.println("body"+body+";"+ ++count);String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis()).toString():"BAD ORDER";ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());ctx.writeAndFlush(resp);}
2.2 代码示例(客户端)
修改HttpClient的send()方法,当客户端与服务器链路建立成功之后,循环发送100条消息类代码。
public void Send(){if(client == null){start ();}byte[] buffer = Encoding.UTF8.GetBytes("userName:"+userName.text+" password"+password.text);for(int i = 0;i < 100;i++){client.Send(buffer);}}
控制台(服务器):+------------------------------------------------------------------+七月 07, 2016 8:09:35 下午 com.game.lll.net.HttpServer main信息: 服务已启动...ad4ea569进来了bodyuserName:aaa password:bbb...此处省略36条userName:aaa password:b;count:1userName:aaa password:bbb...此处省略36条userName:aaa password;count:2userName:aaa password:bbb...此处省略22条userName:aaa password:bbb;count:3+------------------------------------------------------------------+服务端运行结果表明它只接收到三条消息,三条加起来一共是100条(如下图)。我们期待的是收到100条消息,每条消息都会包含一条“count:”.这说明发生了TCP粘包。
2.3 控制台(客户端)
按照设计初衷,客户端应该收到100条AD ORDER消息,但实际上只收到了一条。
2.4 粘包问题的解决办法
粘包的解决办法有很多,可以归纳如下。
- 消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格。
- 在包尾增加回车换行符进行分割,例如FTP协议。
- 将消息分为消息头和消息体,消息头中包含消息长度的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度
在本案例中,我使用的是第2个解决办法在包尾增加回车换行符进行分割。
2.5 代码修改(服务器端)
package com.game.lll.net;import io.netty.channel.ChannelInitializer;import io.netty.channel.socket.SocketChannel;import io.netty.handler.codec.LineBasedFrameDecoder;import io.netty.handler.codec.string.StringDecoder;public class ServerChannelHandler extends ChannelInitializer<SocketChannel>{public static void main(String[] args) throws Exception {int port = 8844;if(args!=null&&args.length>0){try {port = Integer.valueOf(args[0]);} catch (Exception e) {// TODO: handle exception}}System.out.println(port);new HttpServer().bind(port);}@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new ServerHandler());}}
package com.game.lll.net;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;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;public class HttpServer { private static Log log = LogFactory.getLog(HttpServer.class); public void bind(int port) throws Exception { log.info("服务器已启动"); ////配置服务端的NIO线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ServerChannelHandler()); } }).option(ChannelOption.SO_BACKLOG, 128) //最大客户端连接数为128 .childOption(ChannelOption.SO_KEEPALIVE, true); //绑定端口,同步等待成功 ChannelFuture f = b.bind(port).sync(); //等待服务端监听端口关闭 f.channel().closeFuture().sync(); } finally { //优雅退出,释放线程池资源 workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } }}
package com.game.lll.net;import java.util.Date;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;public class ServerHandler extends ChannelInboundHandlerAdapter{private static Log log = LogFactory.getLog(ServerHandler.class);private int count = 0;@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {super.handlerAdded(ctx);System.out.println(ctx.channel().id()+"进来了");}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {super.handlerRemoved(ctx);System.out.println(ctx.channel().id()+"离开了");}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {String body = (String)msg;System.out.println("body"+body+";count:"+ ++count);String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis()).toString():"BAD ORDER";currentTime = currentTime+System.getProperty("line.separator");ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());ctx.writeAndFlush(resp);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {// TODO Auto-generated method stubctx.close();}}
直接看第33行,修改前后代码比较。修改前:
ByteBuf buf = (ByteBuf)msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req,"UTF-8");
修改后:
String body = (String)msg;
2.6 代码修改(客户端端)
byte[] buffer = Encoding.UTF8.GetBytes("userName:"+userName.text+" password:"+password.text+"\r\n");在每一条消息尾巴后添加“\r\n’”
控制台(服务器):+------------------------------------------------------------------+8844七月 07, 2016 8:37:58 下午 com.game.lll.net.HttpServer bind信息: 服务器已启动04d575ff进来了bodyuserName:aaa password:bbb;count:1此处省略很多条......bodyuserName:aaa password:bbb;count:99bodyuserName:aaa password:bbb;count:100+------------------------------------------------------------------+
本章参考书籍
<<Netty权威指南(第2版)>>
作者:小毛驴,一个游戏人
梦想:世界和平
原文地址:http://blog.csdn.net/liulongling
本博客中未标明转载的文章归作者小毛驴所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
4 0
- 【Netty4.x】Netty TCP粘包/拆包问题的解决办法(二)
- 聊一聊Netty TCP粘包/拆包问题的解决办法
- Netty4学习笔记(二) TCP黏包/拆包解决办法
- Netty实践(二):TCP拆包、粘包问题
- Netty解决TCP粘包/拆包导致的半包读写问题
- Netty初探-解决TCP粘包/拆包问题
- 【Netty入门】TCP 粘包/拆包问题产生原因
- Netty -- TCP粘包/拆包
- netty的tcp粘包与拆包
- Netty学习总结(5)——Netty之TCP粘包/拆包问题的解决之道
- Netty解决半包(TCP粘包/拆包导致)读写问题
- Netty解决半包(TCP粘包/拆包导致)读写问题
- Netty解决半包(TCP粘包/拆包导致)读写问题
- netty的粘包 解包问题
- Netty(二)TCP粘包、拆包和UDP通信
- netty源码分析(二十四)TCP粘包与拆包实例演示及分析
- Linux TCP 粘/拆包 与 Netty TCP粘/拆包的 区别
- Netty权威指南 第2版学习笔记4——TCP粘包/拆包问题的解决之道
- IntelliJ IDEA SSM(Spring + Spring MVC + Mybatis)框架+shiro引入jquery easy-ui的正确方式
- 原生SQL查询解析支持
- IOS实用图片无限轮播 点击图片进行跳转
- MySQL 去除字段中的换行和回车符
- log4net使用说明
- 【Netty4.x】Netty TCP粘包/拆包问题的解决办法(二)
- IP地址和子网掩码的关系
- nginx随记1
- pod计算资源管理
- 画图板
- Java知识点集锦
- 1try
- Mobile Service_纪中1327_dp
- mapreduce(六):MapReduce原理