Netty解决TCP粘包/拆包导致的半包读写问题

一.TCP粘包/拆包问题说明

  TCP是个“流”协议,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包拆分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

二.利用LinedBasedFrameDecoder解决TCP粘包问题

   为了解决TCP粘包/拆包导致的半包读写问题,netty默认提供了多种解码器用于处理半包,只要能熟练掌握这些类库使用,TCP粘包问题就会非常容易。

2.1 支持TCP粘包的服务端开发

2.1.1  TimeServer实现:

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;

importio.netty.channel.nio.NioEventLoopGroup;

importio.netty.channel.socket.SocketChannel;

importio.netty.channel.socket.nio.NioServerSocketChannel;

importio.netty.handler.codec.LineBasedFrameDecoder;

import io.netty.handler.codec.string.StringDecoder;

/*

 * 利用LineBasedFrameDecoder解决TCP粘包问题

 */

public class TimeServerNoStick {

        

         publicvoid bind(int port) throws Exception{

                   //配置服务端的NIO线程组

                   EventLoopGroupbossGroup =new NioEventLoopGroup();

                   EventLoopGroupworkerGroup =new NioEventLoopGroup();

         try{

                   //用于启动NIO服务器的辅助启动类,降低服务端的开发复杂度

                   ServerBootstrapb=new ServerBootstrap();

                   b.group(bossGroup,workerGroup)

                   .channel(NioServerSocketChannel.class)

                   .option(ChannelOption.SO_BACKLOG,1024)

                   .childHandler(newChildChannelHandler());

                   //绑定端口,同步等待成功

                   ChannelFuturef=b.bind(port).sync();

                   //等待服务端监听端口关闭

                   f.channel().closeFuture().sync();

        

                  

         }catch (Exception e) {

                   //TODO: handle exception

         }finally{

                   //优雅退出,释放线程池资源

                   bossGroup.shutdownGracefully();

                   workerGroup.shutdownGracefully();

         }

        

         }

        

         privateclass ChildChannelHandler extends ChannelInitializer {

                   @Override

                   protectedvoid initChannel(SocketChannel arg0) throws Exception{

     // 增加2个解码器:LineBasedFrameDecoder和StringDecoder

                            arg0.pipeline().addLast(newLineBasedFrameDecoder(1024));

                            arg0.pipeline().addLast(newStringDecoder());

                            arg0.pipeline().addLast(newTimeServerHandlerNoStick());

                   }

         }

        

         publicstatic void main(String[] args) throws Exception{

                   intport=8080;

                   if(args!=null&&args.length>0){

                            try{

                                     port=Integer.valueOf(args[0]);

                            }catch (NumberFormatException e) {

                                     //TODO: handle exception

                            }

                   }

                   newTimeServerNoStick().bind(port);

         }

 

}

2.1.2  TimeServerHandler实现

 

/*

 * 利用LineBasedFrameDecoder解决TCP粘包问题

 */

public class TimeServerHandlerNoStickextends ChannelHandlerAdapter{

     

         privateint counter;

         @Override

         publicvoid channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{

                  // 可以发现接收到的msg就是删除回车换行符后的请求消息,

                   //不需要额外考虑处理读半包问题,也不需要对请求消息进行编码

         Stringbody=(String) msg;

 System.out.println("The time server receiveorder:"+body+";the counter is"+ ++counter);

                   StringcurrentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?

                                     newjava.util.Date(System.currentTimeMillis()).toString():"BAD ORDER";

                            ByteBufresp=Unpooled.copiedBuffer(currentTime.getBytes());  

          

                            ctx.writeAndFlush(resp);

         }

 

        

         @Override

         publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause){

                   ctx.close();

         }

}

2.2 支持TCP粘包的客户端开发

2.2.1 TimeClient实现

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;

importio.netty.channel.socket.SocketChannel;

importio.netty.channel.socket.nio.NioSocketChannel;

importio.netty.handler.codec.LineBasedFrameDecoder;

importio.netty.handler.codec.string.StringDecoder;

 

/*

 * 利用LineBasedFrameDecoder解决TCP粘包问题

 */

public class TimeClientNoStick {

        

         publicvoid connect(int port,String host) throws Exception{

                   //配置客户端NIO线程组

                   EventLoopGroupgroup=new NioEventLoopGroup();

                   try{

                            Bootstrapb=new Bootstrap();

                            b.group(group).channel(NioSocketChannel.class)

                            .option(ChannelOption.TCP_NODELAY,true)

                            .handler(newChannelInitializer() {

                                     @Override

                                     publicvoid initChannel(SocketChannel ch) throws Exception{

                                               ch.pipeline().addLast(newLineBasedFrameDecoder(1024));

                                               ch.pipeline().addLast(newStringDecoder());

                                               ch.pipeline().addLast(newTimeClientHandlerNoStick(1024));

                                     }

                            });

                            //发起异步连接操作

                            ChannelFuturef=b.connect(host,port).sync();

                            //等待客户端链路关闭

                            f.channel().closeFuture().sync();

                   }catch (Exception e) {

                            //TODO: handle exception

                   }finally{

                            //优雅退出,释放NIO线程组

                            group.shutdownGracefully();

                   }

         }

         publicstatic void main(String[] args) throws Exception{

                   intport=8080;

                   if(args!=null&&args.length>0){

                            try{

                                     port=Integer.valueOf(args[0]);

                                    

                            }catch (NumberFormatException e) {

                                     //TODO: handle exception

                                    

                            }

                            newTimeClient().connect(port,"127.0.0.1");

                   }

         }

 

}

2.2.2 TimeClientHandler的实现

import java.util.logging.Logger;

import io.netty.buffer.ByteBuf;

import io.netty.buffer.Unpooled;

importio.netty.channel.ChannelHandlerAdapter;

importio.netty.channel.ChannelHandlerContext;

/*

 * 利用LineBasedFrameDecoder和StringDecoder解决TCP粘包问题

 */

 

public class TimeClientHandlerNoStickextends ChannelHandlerAdapter{

         privatestatic final Logger logger=Logger.getLogger(TimeClientHandler.class.getName());

         privateint counter;

         privatebyte[] req;

        

         publicTimeClientHandlerNoStick(){

                   req=("QUERYTIME ORDER"+System.getProperty("line.separator")).getBytes();

         }

        

         @Override

         publicvoid channelActive(ChannelHandlerContext ctx){

                   ByteBufmessage=null;

                   for(inti=0;i<100;i++){

                            message=Unpooled.buffer(req.length);

                            message.writeBytes(req);

                            ctx.writeAndFlush(message);

                   }

         }

        

         @Override

         publicvoid channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{

                   //此时msg已经是解码成字符串之后的应答消息

                   Stringbody=(String) msg;

                   System.out.println("Nowis:"+body+";the counter is"+ ++counter);

         }

        

         @Override

         publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause){

                   //释放资源

                   logger.warning("Unexpectedexception from downstream:"+cause.getMessage());

                   ctx.close();

         }

}

三.运行支持TCP粘包的时间服务器程序

    为了尽量模拟TCP粘包的半包场景,采用简单的压力测试,链路建立成功之后,客户端连续发送100条消息给服务端,然后查看服务端和客户端运行结果,分别运行TimeServer和TimeClient,服务端执行结果为:

 Netty解决TCP粘包/拆包导致的半包读写问题_第1张图片

 。。。


    客户端执行结果为:

Netty解决TCP粘包/拆包导致的半包读写问题_第2张图片

。。。

Netty解决TCP粘包/拆包导致的半包读写问题_第3张图片

      运行结果完全符合预期,说明通过使用LinedBasedFrameDecoder和StringDecoder成功解决了TCP粘包导致的读半包问题。

你可能感兴趣的:(Netty)