1. 程式人生 > >Netty權威指南_札記04_TCP粘包/拆包問題解決

Netty權威指南_札記04_TCP粘包/拆包問題解決

文章目錄


Netty權威指南_札記04_TCP粘包/拆包問題解決

1. TCP粘包/拆包

概念:
TCP是個“流”協議,是沒有界限的一串資料。因為TCP底層不瞭解上層業務資料的具體含義,它會根據TCP換從去的實際情況進行包的拆分,所以在業務上人為,一個完整的包可能會被TCP拆分成多個包進行傳送,也有可能把多個小的包封裝成一個大的資料包傳送。

1.1 TCP粘包/拆包問題說明

假設客戶端分別傳送兩個資料包D1和D2給伺服器,由於伺服器一次讀取的位元組數是不確定的,故可能存在以下四種情況:

  1. 服務端分兩次讀取到了兩個獨立的資料包,分別是D1和D2,沒有粘包和拆包;
  2. 服務端一次接收到了兩個資料包,D1和D2粘合在一起,被稱為TCP粘包;
  3. 服務端分兩次讀取到了兩個資料包,第一次讀取到了完整的D1包和D2的部分內容,第二次讀取到了D2包的部分內容,被稱為TCP拆包;
  4. 伺服器分兩次讀取到了兩個資料包,第一次讀取到了D1包的部分內容,第二次讀取到了D1的剩餘內容和D2整包。

注:其實就是類似於滑塊問題

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

  1. 應用程式write寫入的子節大小大於套介面傳送緩衝區大小;
  2. 進行MSS大小的TCP分段;
  3. 乙太網幀的payload大於MTU進行IP分片。

1.3 粘包問題解決策略

  1. 訊息定長;
  2. 在包尾增加回車換行符進行分割,例如FTP協議;
  3. 將訊息分為訊息頭和訊息體,訊息頭中包含訊息總長度或者訊息體長度;
  4. 更復雜的應用層協議。

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

2.1 服務端

Netty時間伺服器服務端 TimeServer
程式碼改造:

 ServerBootstrap bootstrap =
new ServerBootstrap(); bootstrap.group(boss, work) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //增加部分 start socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024)); socketChannel.pipeline().addLast(new StringDecoder()); //增加部分 end socketChannel.pipeline().addLast(new TimeServerHandler()); } });

2.2 客戶端

Netty時間伺服器客戶端 TimeClient
程式碼改造:

 ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                         	//增加部分 start
                            socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                            socketChannel.pipeline().addLast(new StringDecoder()); 
                            //增加部分 end
                            socketChannel.pipeline().addLast(new TimeClientHandler());
                        }
                    });