• Netty的TCP粘包/拆包(源码二)


    假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器端一次读取到的字节数是不确定的,所以可能发生四种情况:

      1、服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包。

      2、服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包

      3、服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包

      4、服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包

      如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。  

    那么在Netty中可使用LineBasedFrameDecoderStringDecoder

      LineBasedFrameDecoder的工作原理是一次遍历ByteBuf中的可读字节,判断看是否有" "或者" ",如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。

      StringDecoder将接收到的对象转换成字符串,然后继续调用后面的handler。

      利用LineBasedFrameDecoder解决TCP粘包问题:

     1 package netty;
     2 
     3 import io.netty.bootstrap.ServerBootstrap;
     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.NioServerSocketChannel;
    11 import io.netty.handler.codec.LineBasedFrameDecoder;
    12 import io.netty.handler.codec.string.StringDecoder;
    13 
    14 
    15 public class TimeServer {
    16     
    17     public void bind(int port) throws Exception{
    18         //配置服务端的NIO线程组
    19         EventLoopGroup bossGroup = new NioEventLoopGroup();
    20         EventLoopGroup workerGroup = new NioEventLoopGroup();
    21         try{
    22             ServerBootstrap b = new ServerBootstrap();
    23             b.group(bossGroup,workerGroup)
    24             .channel(NioServerSocketChannel.class)
    25             .option(ChannelOption.SO_BACKLOG, 1024)
    26             .childHandler(new ChildChannelHandler());
    27             //绑定端口,同步等待成功
    28             ChannelFuture f = b.bind(port).sync();
    29             //等待服务器监听端口关闭
    30             f.channel().closeFuture().sync();
    31         }finally{
    32             //优雅退出,释放线程池资源
    33             bossGroup.shutdownGracefully();
    34             workerGroup.shutdownGracefully();
    35         }
    36     }
    37     
    38     private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
    39         @Override
    40         protected void initChannel(SocketChannel arg0) throws Exception{
    41             arg0.pipeline().addLast(new LineBasedFrameDecoder(1024));
    42             arg0.pipeline().addLast(new StringDecoder());
    43             arg0.pipeline().addLast(new TimeServerHandler());
    44         }
    45     }
    46     
    47     public static void main(String args[]) throws Exception{
    48         int port = 10001;
    49         if(args != null && args.length > 0){
    50             try{
    51                 port = Integer.valueOf(args[0]);
    52             }catch(NumberFormatException e){
    53                 //采用默认值
    54             }
    55         }
    56         new TimeServer().bind(port);
    57     }
    58 }

    TimeServerHandler, msg是删除回车换行符后的请求消息,不需要额外考虑处理半包问题,也不需要对请求消息进行编码:

     1 import java.io.IOException;
     2 import io.netty.buffer.ByteBuf;
     3 import io.netty.buffer.Unpooled;
     4 import io.netty.channel.ChannelHandlerAdapter;
     5 import io.netty.channel.ChannelHandlerContext;
     6 
     7 public class TimeServerHandler extends ChannelHandlerAdapter{
     8     
     9     private int counter;
    10     
    11     public void channelRead(ChannelHandlerContext ctx,Object msg) throws IOException{
    12 
    13         String body = (String)msg;
    14         System.out.println("The time server receive order:" + body + "; the counter is :" + ++counter);
    15         String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)? new java.util.Date(
    16                 System.currentTimeMillis()).toString() : "BAD ORDER";
    17         currentTime = currentTime + System.getProperty("line.separator");
    18         ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
    19         ctx.writeAndFlush(resp);
    20     }
    21     
    22     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
    23         ctx.flush();
    24     }
    25     
    26     @Override
    27     public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
    28         ctx.close();
    29     }
    30 }

    TimeClient,在TimeClientHandler之前新增lineBasedFrameDecoder和StringDecoder解码器:

     1 import io.netty.bootstrap.Bootstrap;
     2 import io.netty.channel.ChannelFuture;
     3 import io.netty.channel.ChannelInitializer;
     4 import io.netty.channel.ChannelOption;
     5 import io.netty.channel.EventLoopGroup;
     6 import io.netty.channel.nio.NioEventLoopGroup;
     7 import io.netty.channel.socket.SocketChannel;
     8 import io.netty.channel.socket.nio.NioSocketChannel;
     9 import io.netty.handler.codec.LineBasedFrameDecoder;
    10 import io.netty.handler.codec.string.StringDecoder;
    11 
    12 public class TimeClient {
    13     
    14     public void connect(int port,String host) throws Exception{
    15         //创建客户端处理I/O读写的NioEventLoopGroup Group线程组
    16         EventLoopGroup group = new NioEventLoopGroup();
    17         try{
    18             //创建客户端辅助启动类Bootstrap
    19             Bootstrap b = new Bootstrap();
    20             b.group(group).channel(NioSocketChannel.class)
    21             .option(ChannelOption.TCP_NODELAY, true)
    22             .handler(new ChannelInitializer<SocketChannel>(){
    23                 //将ChannelHandler设置到ChannelPipleline中,用于处理网络I/O事件
    24                 @Override
    25                 protected void initChannel(SocketChannel ch) throws Exception {
    26                     ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
    27                     ch.pipeline().addLast(new StringDecoder());
    28                     ch.pipeline().addLast(new TimeClientHandler());
    29                 }
    30             }); 
    31             //发起异步连接操作,然后调用同步方法等待连接成功。
    32             ChannelFuture f = b.connect(host,port).sync();
    33             
    34             //等待客户端链路关闭
    35             f.channel().closeFuture().sync();
    36         }finally{
    37             //优雅退出,释放NIO线程组
    38             group.shutdownGracefully();
    39         }
    40     }
    41     
    42     public static void main(String[] args) throws Exception{
    43         int port = 10001;
    44         if(args != null && args.length > 0){
    45             try{
    46                 port = Integer.valueOf(args[0]);
    47             }catch(NumberFormatException e){
    48                 //采用默认值
    49             }
    50         }
    51         new TimeClient().connect(port, "0.0.0.0");
    52     }
    53     
    54 }

    TimeClientHandler,拿到的msg已经是解码成字符串之后的应答消息:

     1 import io.netty.buffer.ByteBuf;
     2 import io.netty.buffer.Unpooled;
     3 import io.netty.channel.ChannelHandlerAdapter;
     4 import io.netty.channel.ChannelHandlerContext;
     5 
     6 import java.util.logging.Logger;
     7 
     8 public class TimeClientHandler extends ChannelHandlerAdapter{
     9     
    10     private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
    11         
    12     private int counter;
    13     
    14     private byte[]req;
    15     
    16     public TimeClientHandler(){
    17          req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
    18     }
    19     
    20     //当客户端与服务端TCP链路简历成功后,Netty的NIO线程会调用该方法,发送查询时间的指令给服务器
    21     public void channelActive(ChannelHandlerContext ctx){
    22         //将请求消息发送给服务端
    23         ByteBuf message = null;
    24         for(int i = 0;i<100;i++){
    25             message = Unpooled.buffer(req.length);
    26             message.writeBytes(req);
    27             ctx.writeAndFlush(message);
    28         }
    29     }
    30     
    31     //当服务器返回应答消息时,该方法被调用
    32     public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
    33         String body = (String)msg;
    34         System.out.println("Now is:" + body + "; the counter is :" + ++counter);
    35     }
    36     
    37     public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
    38         
    39         //释放资源
    40         logger.warning("Unexpected exception from downstream :" + cause.getMessage());
    41         ctx.close();
    42     }
    43 }

     运行结果:

    发现。。就木有粘包或拆包的问题啦~~~~

     机缘巧合,同事也一起实现了Scala版~

    clientHandler:

     1 package main.nettyscala
     2 
     3 import io.netty.buffer.{ByteBuf, Unpooled}
     4 import io.netty.channel.{ChannelInboundHandlerAdapter, ChannelHandlerContext, ChannelHandlerAdapter}
     5 
     6 /**
     7  * Created by root on 2016/11/18.
     8  */
     9 class ClientHandler extends ChannelInboundHandlerAdapter {
    10   override def channelActive(ctx: ChannelHandlerContext): Unit = {
    11     println("channelActive")
    12     //val content = "hello server"
    13     val content = Console.readLine()
    14     ctx.writeAndFlush(Unpooled.copiedBuffer(content.getBytes("UTF-8")))
    15     //发送case class 不在发送字符串了,封装一个字符串
    16     //    ctx.writeAndFlush(RegisterMsg("hello server"))
    17   }
    18 
    19   override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
    20     println("channelRead")
    21     val byteBuf = msg.asInstanceOf[ByteBuf]
    22     val bytes = new Array[Byte](byteBuf.readableBytes())
    23     byteBuf.readBytes(bytes)
    24     val message = new String(bytes, "UTF-8")
    25     println(message)
    26   }
    27 
    28   override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
    29     println("channeReadComplete")
    30     ctx.flush()
    31   }
    32   //发送异常时关闭
    33   override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {
    34     println("exceptionCaught")
    35     ctx.close()
    36   }
    37 
    38 }

    NettyClient:

     1 package main.nettyscala
     2 
     3 import io.netty.bootstrap.Bootstrap
     4 import io.netty.channel.ChannelInitializer
     5 import io.netty.channel.nio.NioEventLoopGroup
     6 import io.netty.channel.socket.SocketChannel
     7 import io.netty.channel.socket.nio.{NioSocketChannel, NioServerSocketChannel}
     8 import io.netty.handler.codec.serialization.{ClassResolvers, ObjectDecoder, ObjectEncoder}
     9 
    10 
    11 object NettyClient {
    12   def main(args: Array[String]) {
    13     val host = args(0)
    14     val port = args(1).toInt
    15     val client = new NettyClient
    16     client.connect(host, port)
    17   }
    18 }
    19 
    20 class NettyClient {
    21   def connect(host: String, port: Int): Unit = {
    22     //创建客户端NIO线程组
    23     val eventGroup = new NioEventLoopGroup
    24     //创建客户端辅助启动类
    25     val bootstrap = new Bootstrap
    26     try {
    27       //将NIO线程组传入到Bootstrap
    28       bootstrap.group(eventGroup)
    29         //创建NioSocketChannel
    30         .channel(classOf[NioSocketChannel])
    31         //绑定I/O事件处理类
    32         .handler(new ChannelInitializer[SocketChannel] {
    33         override def initChannel(ch: SocketChannel): Unit = {
    34           ch.pipeline().addLast(
    35             //            new ObjectEncoder,
    36             //            new ObjectDecoder(ClassResolvers.cacheDisabled(getClass.getClassLoader)),
    37             new ClientHandler
    38           )
    39         }
    40       })
    41       //发起异步连接操作
    42       val channelFuture = bootstrap.connect(host, port).sync()
    43       //等待服务关闭
    44       channelFuture.channel().closeFuture().sync()
    45     } finally {
    46       //优雅的退出,释放线程池资源
    47       eventGroup.shutdownGracefully()
    48     }
    49   }
    50 }

    NettyServer:

     1 package main.nettyscala
     2 
     3 /**
     4  * Created by root on 12/8/16.
     5  */
     6 import io.netty.bootstrap.ServerBootstrap
     7 import io.netty.channel.ChannelInitializer
     8 import io.netty.channel.nio.NioEventLoopGroup
     9 import io.netty.channel.socket.SocketChannel
    10 import io.netty.channel.socket.nio.NioServerSocketChannel
    11 import io.netty.handler.codec.serialization.{ClassResolvers, ClassResolver, ObjectDecoder, ObjectEncoder}
    12 
    13 
    14 object NettyServer {
    15   def main(args: Array[String]) {
    16     val host = args(0)
    17     val port = args(1).toInt
    18     val server = new NettyServer
    19     server.bind(host, port)
    20   }
    21 }
    22 class NettyServer {
    23   def bind(host: String, port: Int): Unit = {
    24     //配置服务端线程池组
    25     //用于服务器接收客户端连接
    26     val bossGroup = new NioEventLoopGroup()
    27     //用户进行SocketChannel的网络读写
    28     val workerGroup = new NioEventLoopGroup()
    29 
    30     try {
    31       //是Netty用户启动NIO服务端的辅助启动类,降低服务端的开发复杂度
    32       val bootstrap = new ServerBootstrap()
    33       //将两个NIO线程组作为参数传入到ServerBootstrap
    34       bootstrap.group(bossGroup, workerGroup)
    35         //创建NioServerSocketChannel
    36         .channel(classOf[NioServerSocketChannel])
    37         //绑定I/O事件处理类
    38         .childHandler(new ChannelInitializer[SocketChannel] {
    39         override def initChannel(ch: SocketChannel): Unit = {
    40           ch.pipeline().addLast(
    41             //            new ObjectEncoder,
    42             //            new ObjectDecoder(ClassResolvers.cacheDisabled(getClass.getClassLoader)),
    43             new ServerHandler
    44           )
    45         }
    46       })
    47       //绑定端口,调用sync方法等待绑定操作完成
    48       val channelFuture = bootstrap.bind(host, port).sync()
    49       //等待服务关闭
    50       channelFuture.channel().closeFuture().sync()
    51     } finally {
    52       //优雅的退出,释放线程池资源
    53       bossGroup.shutdownGracefully()
    54       workerGroup.shutdownGracefully()
    55     }
    56   }
    57 }

    ServerHandler:

     1 package main.nettyscala
     2 
     3 /**
     4  * Created by root on 12/8/16.
     5  */
     6 import io.netty.buffer.{Unpooled, ByteBuf}
     7 import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter}
     8 
     9 /**
    10  * Created by root on 2016/11/18.
    11  */
    12 class ServerHandler extends ChannelInboundHandlerAdapter {
    13   /**
    14    * 有客户端建立连接后调用
    15    */
    16   override def channelActive(ctx: ChannelHandlerContext): Unit = {
    17     println("channelActive invoked")
    18   }
    19 
    20   /**
    21    * 接受客户端发送来的消息
    22    */
    23   override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
    24     println("channelRead invoked")
    25     val byteBuf = msg.asInstanceOf[ByteBuf]
    26     val bytes = new Array[Byte](byteBuf.readableBytes())
    27     byteBuf.readBytes(bytes)
    28     val message = new String(bytes, "UTF-8")
    29     println(message)
    30     val back = "received message: " + message
    31     val resp = Unpooled.copiedBuffer(back.getBytes("UTF-8"))
    32     println(msg)
    33     ctx.write(resp)
    34   }
    35 
    36   /**
    37    * 将消息对列中的数据写入到SocketChanne并发送给对方
    38    */
    39   override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
    40     println("channekReadComplete invoked")
    41     ctx.flush()
    42   }
    43 
    44 
    45 }

    RegisterMsg:

    1 package main.nettyscala
    2 
    3 /**
    4  * Created by root on 12/8/16.
    5  */
    6 case class RegisterMsg(content: String) extends Serializable

    运行结果:

  • 相关阅读:
    linux系统编程程序员必备
    postgresql查询优化之GIN(Generalized Inverted Index)索引与全文检索
    postgresql并行执行
    lightdb for postgresql日志详解
    PostgreSQL 使用数组类型
    postgresql wal文件误删恢复
    汉语词性标注集
    postgresql----JSON类型、函数及全文检索的支持
    postgresql等待事件之wait_event为空解析
    lightdb for pg查看锁
  • 原文地址:https://www.cnblogs.com/yangsy0915/p/6141278.html
Copyright © 2020-2023  润新知