1. 程式人生 > >TCP粘包/拆包

TCP粘包/拆包

主要內容:

  • TCP粘包/拆包的基礎知識
  • 沒考慮TCP粘包/拆包的問題案例
  • 使用Netty解決讀半包問題

1.TCP粘包/拆包

  TCP是個“流”協議,所謂流,就是沒有界限的一串資料。TCP底層並不瞭解上層業務資料的具體含義,它會根據TCP緩衝區的實際情況進行包的劃分,所以在業務上認為,一個完整的包可能會被TCP拆分成多個包進行傳送,也有可能把多個小的包封裝成一個大的資料包傳送,這就是所謂的TCP粘包和拆包的問題。

1.1 TCP粘包/拆包問題說明

 我們通過下圖對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包接收完全,期間發生多次拆包。

1.2 TCP粘包/拆包發生的原因

  問題產生的原因有三個:

  1. 應用程式write寫入的位元組大小大於套接字傳送快取區大小;
  2. 進行MSS大小的TCP分段;
  3. 乙太網幀的payload大於MTU進行IP分片。

1.3 粘包問題的解決策略

  由於底層的TCP無法理解上層的業務資料,所以在底層是無法保證資料包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,可以歸納如下。

  1. 訊息定長,例如每個報文的大小和固定長度200位元組,如果不夠,空位補空格;
  2. 在包尾增加回車換行符進行分割,例如FTP協議;
  3. 將訊息分為訊息頭和訊息體,訊息頭中包含表示訊息總長度(或者訊息體長度)的欄位,通常設計思路為訊息頭的第一個欄位使用int32來表示訊息的總長度;
  4. 更復雜的應用層協議。

  下面我們就通過實際示例來看看如何使用Netty提供的半包解碼器來解決TCP粘包/拆包問題。

2.未考慮TCP粘包導致功能異常案例

  在前面的時間伺服器示例中,我們多次強調並沒有考慮讀半包問題,這在功能測試時往往沒有問題,但是一旦壓力上來,或者傳送大報文之後,就會存在粘包/拆包問題。如果程式碼沒有考慮,往往就會出現解碼錯位或者錯誤,導致程式不能正常工作。下面我們以前面的Netty(一)——Netty入門程式為例,模擬故障場景,然後看看如何正確使用Netty的半包解碼器來解決TCP粘包/拆包問題。

2.1TimeServer的改造

 

package joanna.yan.netty;

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 TimeServer {
    
    public static void main(String[] args) throws Exception {
        int port=9090;
        if(args!=null&&args.length>0){
            try {
                port=Integer.valueOf(args[0]);
            } catch (Exception e) {
                // 採用預設值
            }
        }
        
        new TimeServer().bind(port);
    }
    
    public void bind(int port) throws Exception{
        /*
         * 配置服務端的NIO執行緒組,它包含了一組NIO執行緒,專門用於網路事件的處理,實際上它們就是Reactor執行緒組。
         * 這裡建立兩個的原因:一個用於服務端接受客戶端的連線,
         * 另一個用於進行SocketChannel的網路讀寫。
         */
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try {
            //ServerBootstrap物件,Netty用於啟動NIO服務端的輔助啟動類,目的是降低服務端的開發複雜度。
            ServerBootstrap b=new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 1024)
             /*
              * 繫結I/O事件的處理類ChildChannelHandler,它的作用類似於Reactor模式中的handler類,
              * 主要用於處理網路I/O事件,例如:記錄日誌、對訊息進行編解碼等。
              */
             .childHandler(new ChildChannelHandler());
            /*
             * 繫結埠,同步等待成功(呼叫它的bind方法繫結監聽埠,隨後,呼叫它的同步阻塞方法sync等待繫結操作完成。
             * 完成之後Netty會返回一個ChannelFuture,它的功能類似於JDK的java.util.concurrent.Future,
             * 主要用於非同步操作的通知回撥。)
             */
            ChannelFuture f=b.bind(port).sync();
            
            //等待服務端監聽埠關閉(使用f.channel().closeFuture().sync()方法進行阻塞,等待服務端鏈路關閉之後main函式才退出。)
            f.channel().closeFuture().sync();
        }finally{
            //優雅退出,釋放執行緒池資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{

        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
//            arg0.pipeline().addLast(new TimeServerHandler());
            
            //模擬粘包/拆包故障場景
            arg0.pipeline().addLast(new TimeServerHandler1());
        }
    }
}

 

 

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * 用於對網路事件進行讀寫操作
 * 模擬粘包/拆包故障場景
 * @author Joanna.Yan
 * @date 2017年11月8日下午6:54:35
 */
public class TimeServerHandler1 extends ChannelInboundHandlerAdapter{
    
    private int counter;

    @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, "UTF-8").substring(0, req.length-System.getProperty("line.separator").length());
        System.out.println("The time server receive order : "+body+" ;the counter is :"+ ++counter);
        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.write(resp);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
    }        
}

 

2.2TimeClient的改造

 

package joanna.yan.netty;

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;

public class TimeClient {
    public static void main(String[] args) throws Exception {
        int port=9090;
        if(args!=null&&args.length>0){
            try {
                port=Integer.valueOf(args[0]);
            } catch (Exception e) {
                // 採用預設值
            }
        }
        new TimeClient().connect(port, "127.0.0.1");
    }
    
    public void connect(int port,String host) throws Exception{
        //配置客戶端NIO執行緒組
        EventLoopGroup group=new NioEventLoopGroup();
        
        try {
            Bootstrap b=new Bootstrap();
            b.group(group)
            .channel(NioSocketChannel.class)
            .option(ChannelOption.TCP_NODELAY, true)
            .handler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
//                    ch.pipeline().addLast(new TimeClientHandler());
                    
                    //模擬粘包/拆包故障場景
                    ch.pipeline().addLast(new TimeClientHandler1());
                }
            });
            //發起非同步連線操作
            ChannelFuture f=b.connect(host, port).sync();
            //等待客戶端鏈路關閉
            f.channel().closeFuture().sync();
        }finally{
            //優雅退出,釋放NIO執行緒組
            group.shutdownGracefully();
        }
    }
}

 

 

package joanna.yan.netty;

import java.util.logging.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
 * 模擬粘包/拆包故障場景
 * @author Joanna.Yan
 * @date 2017年11月10日下午2:18:51
 */
public class TimeClientHandler1 extends ChannelInboundHandlerAdapter{
    
    private static final Logger logger=Logger.getLogger(TimeClientHandler1.class.getName());
    private int counter;
    private byte[] req;
    
    public TimeClientHandler1(){
        req=("QUER TIME ORDER"+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, "UTF-8");
        System.out.println("Now is :"+body+" ;the counter is :" + ++counter);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        //釋放資源
        logger.warning("Unexpected exception from downstream : "+cause.getMessage());
        ctx.close();
    }
}

 

2.3執行結果

  分別執行服務端和客戶端,執行結果如下:

  服務端執行結果如下:

 

The time server receive order : QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QU ;the counter is :1
The time server receive order :  TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER
QUER TIME ORDER ;the counter is :2

 

  服務端執行結果表明它只接收到了兩條訊息,總數正好是100條,我們期待的是收到100條訊息,每條包括一條“QUERY TIME ORDER”指令。這說明發生了TCP粘包。

  客戶端執行結果如下:

Now is : BAD ORDER
BAD ORDER
; the counter is : 1

  按照設計初衷,客戶端應該收到100條當前系統時間的訊息,但實際上只收到了一條。這不難理解,因為服務端只收到了2條請求訊息,所以實際服務端只發送了2條應答,由於請求訊息不滿足查詢條件,所以只返回了2條“BAD ORDER”應答訊息。但是實際上客戶端只收到了一條包含2條“BAD ORDER”指令的訊息,說明服務端返回的應答訊息也傳送了粘包。

  由於上面的例程沒有考慮TCP的粘包/拆包,所以當發生TCP粘包時,我們的程式就不能正常工作。

  下面我們通過Netty的LineBaseFrameDecoder和StringDecoder來解決TCP粘包問題。

3.利用LineBasedFrameDecoder解決TCP粘包問題

  為了解決TCP粘包/拆包導致的半包讀寫問題,Netty預設提供了多種編解碼器用於處理半包,只要能熟練掌握這些類庫的使用,TCP粘包問題從此會變得非常容易,你甚至不需要關心它們,這也是其他NIO框架和JDK原生的NIO API所無法匹敵的。

  下面我們對時間服務進行修改。

3.1支援TCP粘包的TimeServer

 

package joanna.yan.netty.sp;

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 {
        int port=9090;
        if(args!=null&&args.length>0){
            try {
                port=Integer.valueOf(args[0]);
            } catch (Exception e) {
                // 採用預設值
            }
        }
        new TimeServer().bind(port);
    }
    
    public void bind(int port) throws Exception{
        //配置服務端的NIO執行緒組
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try {
            ServerBootstrap b=new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 1024)
             .childHandler(new ChildChannelHandler());
            
            //繫結埠,同步等待成功
            ChannelFuture f=b.bind(port).sync();
            
            //等待服務端監聽埠關閉
            f.channel().closeFuture().sync();
            
        }finally{
            //優雅退出,釋放執行緒池資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{

        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
            arg0.pipeline().addLast(new LineBasedFrameDecoder(1024));
            arg0.pipeline().addLast(new StringDecoder());
            arg0.pipeline().addLast(new TimeServerHandler());
        }    
    }
}

 

 

package joanna.yan.netty.sp;

import java.util.Date;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class TimeServerHandler extends ChannelInboundHandlerAdapter{
    private int counter;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        String body=(String) msg;
        System.out.println("The time server receive order : "+body+" ;the counter is : "+ ++counter);
        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);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
    }

}

 

3.2支援TCP粘包的TimeClient

 

package joanna.yan.netty.sp;

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;

public class TimeClient {
    
    public static void main(String[] args) throws Exception {
        int port=9090;
        if(args!=null&&args.length>0){
            try {
                port=Integer.valueOf(args[0]);
            } catch (Exception e) {
                // 採用預設值
            }
        }
        new TimeClient().connect(port, "127.0.0.1");
    }
    
    public void connect(int port,String host) throws Exception{
        //配置客戶端NIO執行緒組
        EventLoopGroup group=new NioEventLoopGroup();
        
        try {
            Bootstrap b=new Bootstrap();
            b.group(group)
            .channel(NioSocketChannel.class)
            .option(ChannelOption.TCP_NODELAY, true)
            .handler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    //直接在TimeClientHandler之前新增LineBasedFrameDecoder和StringDecoder解碼器
                    ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                    ch.pipeline().addLast(new StringDecoder());
                    ch.pipeline().addLast(new TimeClientHandler());
                }
            });
            
            //發起非同步連線操作
            ChannelFuture f=b.connect(host, port).sync();
            //等待客戶端鏈路關閉
            f.channel().closeFuture().sync();
        }finally{
            //優雅退出,釋放NIO執行緒組
            group.shutdownGracefully();
        }
    }
}

複製程式碼

複製程式碼

package joanna.yan.netty.sp;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.logging.Logger;

public class TimeClientHandler extends ChannelInboundHandlerAdapter{
    
    private static final Logger logger=Logger.getLogger(TimeClientHandler.class.getName());
    private int counter;
    private byte[] req;
    
    public TimeClientHandler(){
        req=("QUERY TIME ORDER"+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 {
        //拿到的msg已經是解碼成字串之後的應答訊息了,相比於之前的程式碼簡潔明瞭很多。
        String body=(String) msg;
        System.out.println("Now is :"+body+" ;the counter is :" + ++counter);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        //釋放資源
        logger.warning("Unexpected exception from downstream : "+cause.getMessage());
        ctx.close();
    }
}

 

3.3執行支援TCP粘包的時間伺服器程式

  為了儘量模擬TCP粘包和半包場景,採用簡單的壓力測試,鏈路建立成功之後,客戶端連續傳送100條訊息給服務端,然後檢視服務端和客戶端的執行結果。

  服務端執行結果如下:

The time server receive order : QUER TIME ORDER ;the counter is : 1
The time server receive order : QUER TIME ORDER ;the counter is : 2
The time server receive order : QUER TIME ORDER ;the counter is : 3
The time server receive order : QUER TIME ORDER ;the counter is : 4
The time server receive order : QUER TIME ORDER ;the counter is : 5
The time server receive order : QUER TIME ORDER ;the counter is : 6
The time server receive order : QUER TIME ORDER ;the counter is : 7
The time server receive order : QUER TIME ORDER ;the counter is : 8
The time server receive order : QUER TIME ORDER ;the counter is : 9
The time server receive order : QUER TIME ORDER ;the counter is : 10
The time server receive order : QUER TIME ORDER ;the counter is : 11
The time server receive order : QUER TIME ORDER ;the counter is : 12
The time server receive order : QUER TIME ORDER ;the counter is : 13
The time server receive order : QUER TIME ORDER ;the counter is : 14
The time server receive order : QUER TIME ORDER ;the counter is : 15
The time server receive order : QUER TIME ORDER ;the counter is : 16
The time server receive order : QUER TIME ORDER ;the counter is : 17
The time server receive order : QUER TIME ORDER ;the counter is : 18
The time server receive order : QUER TIME ORDER ;the counter is : 19
The time server receive order : QUER TIME ORDER ;the counter is : 20
The time server receive order : QUER TIME ORDER ;the counter is : 21
The time server receive order : QUER TIME ORDER ;the counter is : 22
The time server receive order : QUER TIME ORDER ;the counter is : 23
The time server receive order : QUER TIME ORDER ;the counter is : 24
The time server receive order : QUER TIME ORDER ;the counter is : 25
The time server receive order : QUER TIME ORDER ;the counter is : 26
The time server receive order : QUER TIME ORDER ;the counter is : 27
The time server receive order : QUER TIME ORDER ;the counter is : 28
The time server receive order : QUER TIME ORDER ;the counter is : 29
The time server receive order : QUER TIME ORDER ;the counter is : 30
The time server receive order : QUER TIME ORDER ;the counter is : 31
The time server receive order : QUER TIME ORDER ;the counter is : 32
The time server receive order : QUER TIME ORDER ;the counter is : 33
The time server receive order : QUER TIME ORDER ;the counter is : 34
The time server receive order : QUER TIME ORDER ;the counter is : 35
The time server receive order : QUER TIME ORDER ;the counter is : 36
The time server receive order : QUER TIME ORDER ;the counter is : 37
The time server receive order : QUER TIME ORDER ;the counter is : 38
The time server receive order : QUER TIME ORDER ;the counter is : 39
The time server receive order : QUER TIME ORDER ;the counter is : 40
The time server receive order : QUER TIME ORDER ;the counter is : 41
The time server receive order : QUER TIME ORDER ;the counter is : 42
The time server receive order : QUER TIME ORDER ;the counter is : 43
The time server receive order : QUER TIME ORDER ;the counter is : 44
The time server receive order : QUER TIME ORDER ;the counter is : 45
The time server receive order : QUER TIME ORDER ;the counter is : 46
The time server receive order : QUER TIME ORDER ;the counter is : 47
The time server receive order : QUER TIME ORDER ;the counter is : 48
The time server receive order : QUER TIME ORDER ;the counter is : 49
The time server receive order : QUER TIME ORDER ;the counter is : 50
The time server receive order : QUER TIME ORDER ;the counter is : 51
The time server receive order : QUER TIME ORDER ;the counter is : 52
The time server receive order : QUER TIME ORDER ;the counter is : 53
The time server receive order : QUER TIME ORDER ;the counter is : 54
The time server receive order : QUER TIME ORDER ;the counter is : 55
The time server receive order : QUER TIME ORDER ;the counter is : 56
The time server receive order : QUER TIME ORDER ;the counter is : 57
The time server receive order : QUER TIME ORDER ;the counter is : 58
The time server receive order : QUER TIME ORDER ;the counter is : 59
The time server receive order : QUER TIME ORDER ;the counter is : 60
The time server receive order : QUER TIME ORDER ;the counter is : 61
The time server receive order : QUER TIME ORDER ;the counter is : 62
The time server receive order : QUER TIME ORDER ;the counter is : 63
The time server receive order : QUER TIME ORDER ;the counter is : 64
The time server receive order : QUER TIME ORDER ;the counter is : 65
The time server receive order : QUER TIME ORDER ;the counter is : 66
The time server receive order : QUER TIME ORDER ;the counter is : 67
The time server receive order : QUER TIME ORDER ;the counter is : 68
The time server receive order : QUER TIME ORDER ;the counter is : 69
The time server receive order : QUER TIME ORDER ;the counter is : 70
The time server receive order : QUER TIME ORDER ;the counter is : 71
The time server receive order : QUER TIME ORDER ;the counter is : 72
The time server receive order : QUER TIME ORDER ;the counter is : 73
The time server receive order : QUER TIME ORDER ;the counter is : 74
The time server receive order : QUER TIME ORDER ;the counter is : 75
The time server receive order : QUER TIME ORDER ;the counter is : 76
The time server receive order : QUER TIME ORDER ;the counter is : 77
The time server receive order : QUER TIME ORDER ;the counter is : 78
The time server receive order : QUER TIME ORDER ;the counter is : 79
The time server receive order : QUER TIME ORDER ;the counter is : 80
The time server receive order : QUER TIME ORDER ;the counter is : 81
The time server receive order : QUER TIME ORDER ;the counter is : 82
The time server receive order : QUER TIME ORDER ;the counter is : 83
The time server receive order : QUER TIME ORDER ;the counter is : 84
The time server receive order : QUER TIME ORDER ;the counter is : 85
The time server receive order : QUER TIME ORDER ;the counter is : 86
The time server receive order : QUER TIME ORDER ;the counter is : 87
The time server receive order : QUER TIME ORDER ;the counter is : 88
The time server receive order : QUER TIME ORDER ;the counter is : 89
The time server receive order : QUER TIME ORDER ;the counter is : 90
The time server receive order : QUER TIME ORDER ;the counter is : 91
The time server receive order : QUER TIME ORDER ;the counter is : 92
The time server receive order : QUER TIME ORDER ;the counter is : 93
The time server receive order : QUER TIME ORDER ;the counter is : 94
The time server receive order : QUER TIME ORDER ;the counter is : 95
The time server receive order : QUER TIME ORDER ;the counter is : 96
The time server receive order : QUER TIME ORDER ;the counter is : 97
The time server receive order : QUER TIME ORDER ;the counter is : 98
The time server receive order : QUER TIME ORDER ;the counter is : 99
The time server receive order : QUER TIME ORDER ;the counter is : 100

  客戶端執行結果如下:

Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :1
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :2
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :3
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :4
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :5
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :6
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :7
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :8
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :9
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :10
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :11
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :12
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :13
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :14
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :15
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :16
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :17
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :18
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :19
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :20
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :21
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :22
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :23
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :24
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :25
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :26
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :27
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :28
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :29
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :30
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :31
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :32
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :33
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :34
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :35
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :36
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :37
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :38
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :39
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :40
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :41
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :42
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :43
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :44
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :45
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :46
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :47
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :48
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :49
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :50
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :51
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :52
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :53
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :54
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :55
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :56
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :57
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :58
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :59
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :60
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :61
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :62
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :63
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :64
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :65
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :66
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :67
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :68
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :69
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :70
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :71
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :72
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :73
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :74
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :75
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :76
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :77
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :78
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :79
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :80
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :81
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :82
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :83
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :84
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :85
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :86
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :87
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :88
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :89
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :90
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :91
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :92
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :93
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :94
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :95
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :96
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :97
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :98
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :99
Now is :Wed Aug 30 16:38:34 CST 2017 ;the counter is :100

  程式的執行結果完全符合預期,說明通過使用LineBasedFrameDecoder和StringDecoder成功解決了TCP粘包導致的讀半包問題。對於使用者來說,只要將支援半包解碼的handler新增到ChannelPipeline中即可,不需要寫額外的程式碼,使用者使用起來非常簡單。

3.4 LineBasedFrameDecoder和StringDecoder原理分析

  LineBasedFrameDecoder的工作原理是它依次遍歷ByteBuf中的可讀位元組,判斷看是否有"\n"或者“\r\n”,如果有,就以此位置為結束位置,從可讀索引到結束位置區間的位元組就組成了一行。它是以換行符為結束標誌的解碼器,支援攜帶結束符或者不攜帶結束符兩種解碼方式。同時支援配置單行的最大長度。如果連續讀取到的最大長度後仍沒有發現換行符,就會丟擲異常,同時忽略掉之前督導的異常碼流。

  StringDecoder的功能非常簡單,就是將收到到的物件轉換成字串,然後繼續呼叫後面的handler。LineBasedFrameDecoder+StringDecoder組合就是按行切換的文字解碼器,它被設計用來支援TCP的粘包和拆包。

  疑問:如果傳送的訊息不是以換行符結束的該怎麼辦呢?或者沒有回車換行符,靠訊息頭中的長度欄位來分包怎麼辦?是不是需要自己寫半包解碼器?答案是否定的,Netty提供了多種支援TCP粘包/拆包的解碼器,用來滿足使用者的不同訴求。