TCP 粘包和拆包

由於TCP無法理解上層的業務數據,所以底層無法保證數據包不被拆分和重組

所以我們需要在上層設計協議棧來解決這個問題。

主流的協議方案有

1. 消息定長

2. 在包尾部增加換行回車符

3. 將消息分成消息頭和消息體

4. 其他

 

爲了展現未考慮TCP粘包導致的功能異常情況,將上一篇文章的時間服務器的channelRead方法改造一下。

@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");
        body = body.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 java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
        currentTime = currentTime + System.getProperty("line.separator");
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.writeAndFlush(resp);
    }

每收到一條消息後,就計數一次,然後發送應答消息給客戶端。

 

TimClientHandler也改造成如下

當和服務器端建立好鏈接之後,循環發送100條消息。


public class TimeClientHandler extends ChannelHandlerAdapter {

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

    private int counter;
    private byte[] req;

    /**
     * Creates a client-side handler.
     */
    public TimeClientHandler() {
        req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ByteBuf message;
        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) {
        // 釋放資源
        logger.warning("Unexpected exception from downstream : " + cause.getMessage());
        ctx.close();
    }
}

 

那麼按照設計,服務器端應該會收到100條請求,並且counter會變成100.

 

但是實際上的運行結果是服務器端只收到了兩條消息,第一條包括57條“QUERY TIME ORDER"指令,第二條包括43條”QUERY TIME ORDER"指令,這說明發生了TCP粘包,程序並沒有像我們設計的那樣,100條QUERY  TIME  ORDER指令分開發送,而是“粘”在一起發送了。

 

如何解決這個問題,利用Netty提供的編碼解碼器即可。

 ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());
 private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel socketChannel) {
            socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
            socketChannel.pipeline().addLast(new StringDecoder());
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }

LineBasedFrameDecoder 按照換行符作爲結束標誌的解碼器

StringDecoder 將接受對象轉換成字符串。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章