前两个博客磕磕绊绊的写完了客户端和服务端的例子,今天就来研究一点更深入的东西。

TCP的粘包和拆包

这个是我在网上找到的解释粘包和拆包的博客,看着还可以:解释拆包粘包。

下面咱们介绍一下书上咋说的,TCP是个流协议,什么是流,就是一大串没有边界的数据,大家可以想象一下河里的水。TCP底层不了解咱们上层业务数据的具体含义,它会根据TCP缓冲区的实际情况对数据进行划分,所以在业务上可以认为,一个完整的业务数据可能会被TCP拆分成多个包进行发送,也可能把多个小包合成一个包进行发送,这就是TCP的拆包和粘包。

图示TCP粘包拆包

书上是下面这样解释上图的

TCP拆包粘包发生的原因

  1. 应用程序写入的大小大于套接字缓冲区的大小
  2. 进行MSS大小的分片
  3. 以太网帧的payload大于MTU进行ip分片

图示:

如何解决粘包拆包

  1. 消息定长
  2. 消息末尾添加换行符或指定标识
  3. 将消息分为消息头和消息体消息头中含有消息体的长度
  4. 更复杂的应用层协议

下面咱们在代码里看看(还是写代码舒服)

TimeServerHandler:加了一个请求计数器

package NettyTestTCP;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;import java.text.SimpleDateFormat;
import java.util.Date;public class TimeServerHandler extends ChannelInboundHandlerAdapter {private int counter = 0;/*** 服务端读取到网络数据后的处理*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf byteBuf = (ByteBuf) msg;byte[] bytes = new byte[byteBuf.readableBytes()];byteBuf.readBytes(bytes);String body = new String(bytes,"UTF-8");System.out.println("服务端得到的请求次数:"+ ++counter);String currentTime = "获取当前时间".equals(body)?new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()):"指令有误";ByteBuf buf = Unpooled.copiedBuffer(currentTime.getBytes());//将消息写到缓冲数组中ctx.write(buf);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//将消息推到SocketChannel中发送给客户端ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

TimeClientHandler:加了一个返回计数器

package NettyTestTCP;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;public class TimeClientHandler extends SimpleChannelInboundHandler<ByteBuf> {private final ByteBuf sendMsg;private int counter = 0;public TimeClientHandler() {byte[] bytes = "获取当前时间".getBytes();sendMsg = Unpooled.buffer(bytes.length);sendMsg.writeBytes(bytes);}//读取到服务器端的数据后的操作@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {byte[] bytes = new byte[byteBuf.readableBytes()];byteBuf.readBytes(bytes);String body = new String(bytes,"UTF-8");System.out.println("当前时间是:"+body +",-------"+ ++counter);System.out.println();}/*客户端被通知channel活跃以后,做事*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//往服务器写数据for (int i = 0;i<100;i++){byte[] bytes = "获取当前时间".getBytes();ByteBuf sendMsg = Unpooled.buffer(bytes.length);sendMsg.writeBytes(bytes);ctx.writeAndFlush(sendMsg);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {cause.printStackTrace();ctx.close();}
}

结果如下所示

服务端应该接收到100次请求但是只有30多次

客户端应该得到100次响应但是只有三次

利用LineBasedFrameDecoder解决TCP粘包问题

为了解决TCP 的粘包和拆包问题Netty提供了许多种编解码器用来处理,只要咱们掌握了这些类库的使用,粘包拆包问题就贼简单,甚至咱们都不需要知道它是咋搞的这也是其他NIO框架无法比拟的。下面开始搞咱们上边的问题还是以时间服务器来说

TimeClient:

package NettyTestTCPNew;import LineBase.LineBaseClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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;public class TimeClient {public void connect(int port, String host)  {//配置客户端的NIO线程EventLoopGroup eventLoopGroup = new NioEventLoopGroup();try {//服务器端是ServerBootstrapBootstrap bootstrap = new Bootstrap();bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)//.option(ChannelOption.TCP_NODELAY,true).remoteAddress("127.0.0.1",9999).handler(new ChannelInitializerImp());//发起异步连接ChannelFuture  channelFuture = bootstrap.connect().sync();//等待客户端的连接关闭channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();}finally {//释放IO线程组eventLoopGroup.shutdownGracefully();}}private static class ChannelInitializerImp extends ChannelInitializer<Channel> {@Overrideprotected void initChannel(Channel ch) throws Exception {//跟服务端一样加了这个编解码器ch.pipeline().addLast(new LineBasedFrameDecoder(1024*100000));// socketChannel.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new TimeClientHandler());}}public static void main(String[] args) {new TimeClient().connect(8989,"127.0.0.1");}
}
TimeClientHandler:
package NettyTestTCPNew;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;import java.util.concurrent.atomic.AtomicInteger;public class TimeClientHandler extends SimpleChannelInboundHandler<ByteBuf> {private AtomicInteger counter = new AtomicInteger(0);//读取到服务器端的数据后的操作@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
//        byte[] bytes = new byte[byteBuf.readableBytes()];
//        byteBuf.readBytes(bytes);
//        String body = new String(bytes,"UTF-8");System.out.println("当前时间是:"+byteBuf.toString(CharsetUtil.UTF_8) +",-------"+ counter.incrementAndGet());}/*客户端被通知channel活跃以后,做事*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//往服务器写数据ByteBuf sendMsg = null;String msg = "获取当前时间";for (int i = 0;i<100;i++){sendMsg = Unpooled.buffer(msg.length());sendMsg.writeBytes(msg.getBytes());ctx.writeAndFlush(sendMsg);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {cause.printStackTrace();ctx.close();}
}
TimeServer:
package NettyTestTCPNew;import LineBase.LineBaseEchoServer;
import LineBase.LineBaseServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;import java.net.InetSocketAddress;public class TimeServer {public void bind() throws Exception {//两个线程组,各包含一组NIO线程EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workGroup = new NioEventLoopGroup();try {//ServerBootstrap是Netty的启动NIO的辅助类,目的降低服务端开发的难度ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/.localAddress(new InetSocketAddress(9999))/*指定服务器监听端口*//*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,所以下面这段代码的作用就是为这个子channel增加handle*///绑定一个处理请求的类  我这里用一个匿名内部类写了.childHandler(new ChannelInitializerImp());//绑定端口     ChannelFuture异步回调 一直阻塞直到连接完成ChannelFuture future = bootstrap.bind().sync();//等待服务器监听端口关闭System.out.println("服务启动了");future.channel().closeFuture().sync();}finally {//关系线程池bossGroup.shutdownGracefully();workGroup.shutdownGracefully();}}private static class ChannelInitializerImp extends ChannelInitializer<Channel> {@Overrideprotected void initChannel(Channel ch) throws Exception {ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new TimeServerHandler());}}public static void main(String[] args) throws Exception {int port = 8989;new TimeServer().bind();}
}
TimeServerHandler:
package NettyTestTCPNew;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;import java.text.SimpleDateFormat;
import java.util.Date;public class TimeServerHandler extends ChannelInboundHandlerAdapter {private int counter = 0;/*** 服务端读取到网络数据后的处理*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf byteBuf = (ByteBuf) msg;byte[] bytes = new byte[byteBuf.readableBytes()];byteBuf.readBytes(bytes);String body = new String(bytes,"UTF-8");System.out.println("服务端得到的请求:"+ body+" ,===== "+ ++counter);String currentTime = "获取当前时间".equals(body)?new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()):"指令有误";ByteBuf buf = Unpooled.copiedBuffer(currentTime.getBytes());//将消息发给客户端ctx.writeAndFlush(buf);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

具体的实现方法如下所示:

LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断看是否有 "\n” 或者 "\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以回车换行符为结束标记的解码器,支持配置单行的最大长度,如果连续读取到最大长度后仍然没有发现换行符,会抛出异常,同时忽略掉之前读取到的异常码流。

StringDecoder 解码器工作原理将接收到的对象转换成字符串,然后继续调用后面的 Hander。

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

1)LineBasedFrameDecoder(final int maxLength)

2)LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast)

Netty学习之读netty权威指南(三)相关推荐

  1. 1、张龙netty学习 第一个netty服务端

    张龙netty学习 第一个netty服务端 public class TestServer {public static void main(String[] args) throws Excepti ...

  2. Netty学习笔记(二)Netty服务端流程启动分析

    先贴下在NIO和Netty里启动服务端的代码 public class NioServer { /*** 指定端口号启动服务* */public boolean startServer(int por ...

  3. Netty学习二:Netty整体框架

    一.Netty的整体结构和源码结构 1. Core层 提供底层网络通信的通用抽象和实现,包括可扩展的事件模型.通用的通信API和支持零拷贝的ByteBuf等. common模块是Netty的核心基础包 ...

  4. Netty学习笔记(一)Netty客户端源码分析

    最近在学些BIO,NIO相关的知识,也学习了下Netty和它的源码,做个记录,方便以后继续学习,如果有错误的地方欢迎指正 如果不了解BIO,NIO这些基础知识,可以看下我的如下博客 IO中的阻塞.非阻 ...

  5. WebRTC 学习资源 电子书 WebRTC权威指南 Learning WebRTC

    webRTC源码下载地址:https://pan.baidu.com/s/18CjClvAuz3B9oF33ngbJIw 提取码:wl1e 1.<WebRTC权威指南>第三版 中文版 本书 ...

  6. 读HTTP权威指南的体会

    国庆期间,我读了HTTP权威指南一书,现在我把总节为大家讲一下: Web 浏览器.服务器和相关的Web 应用程序都是通过HTTP 相互通信的.HTTP 是 现代全球因特网中使用的公共语言. 是对HTT ...

  7. 读hadoop权威指南关于hive

    关于hive 以下内容为边读边总结,用于后期快速学习 17.6 表 hive的表在逻辑上由存储的数据和描述表中数据形式的相关元数据组成.数据一般存放在hdfs中,也可存放其他hadoop文件系统.元数 ...

  8. 读jquery 权威指南[7]-性能优化与最佳实践

    一.优化选择器执行速度 1. 优先使用ID选择器和标记选择器 使用选择器时应该首选ID选择器($("#id")),其次是标记选择器($("div")),最后再选 ...

  9. 读jquery 权威指南[4]-Ajax

    一.获取异步数据 jQuery可以从服务器异步获得静态数据. ①load() $.load(url,data,callback) url要加载的页面地址, data发送到服务器的数据key/value ...

  10. 读jquery 权威指南[3]-动画

    一. 显示与隐藏--hide(),show() 1. 方法: hide(speed,[callback]); show(speed,[callback]); 说明:这两个方法还可以实现带动画效果的显示 ...

最新文章

  1. ***“出更”---获取源码的***
  2. Mysql在离线安装时提示:error: Found option without preceding group in config file
  3. 启明云端分享| 86盒串口屏烧录说明
  4. Web Security——英语写作与教学评价系统(iWrite)解决写作时禁止复制粘贴问题解决方案
  5. 感知机(python实现)
  6. Linux:建立内核代码树
  7. 【Infragistics教程】在javascript构造函数中创建基本继承
  8. iOS之instancetype
  9. [语]××语录@××--第1篇
  10. 蒙太奇经典例子_剧本中如何写好蒙太奇?这15个硬核案例告诉你!
  11. 【C++ 与 STL】栈:stack
  12. deprecations - 极不赞成的写法
  13. Android View onVisibilityChanged onAttachedToWindow onDetachedFromWindow
  14. 图书管理系统(I/O)
  15. mysql假死_win7系统假死的5种情况和处理方法
  16. 前端 img标签显示 base64格式的 图片
  17. 性能测试模型初探及应用方法分析
  18. Android 学习第10课,Android的布局
  19. 当当笔试题(有n个人成一圈,顺序排号(编号为1到n),从第一个人开始报数1到3报数),凡报到3的人出圈子,从下个人开始继续报数,直到最后一个人,问最后留下在是第几号?)
  20. 洛克希德·马丁定义的“杀伤链”

热门文章

  1. FastDFS文件存储系统
  2. 围棋AI kataGo下载
  3. ubuntu 添加证书
  4. VS运行程序时遇到0xc0150002的问题
  5. buuctf [GhostScript]CVE-2018-16509
  6. Firefox 中文语言包安装方法
  7. 2021爱分析·产业数字化峰会圆满落幕:加快推动产业数字化,构建产业共生生态
  8. java读取服务器文件_JAVA读取服务器端文件
  9. AB余商c语言编程答案,C语言课后题编程答案
  10. 史上最全的谷歌公司那些黑科技