閱讀574 返回首頁    go 阿裏雲 go 技術社區[雲棲]


實例:Netty 處理 TCP協議數據分包問題

一、Netty解決TCP協議數據分包問題思路


我們知道通過TCP協議發送接收數據時,如果數據過大,接收到的數據會是分包的,比如:
                                    +-----+-----+-----+
         發送數據是: | ABC | DEF | GHI |
                            +-----+-----+-----+
         而我們想接受到的數據是: | ABCDEFGHI |
                    
該如何處理這種情況呢?Netty提供了一個專門處理TCP協議數據的Handler:LengthFieldBasedFrameDecoder它的原理是服務器端和客戶端約定一個協議格式:數據包=協議長度+協議體

      --------------------------------數據包------------------------------

     | 協議長度部分(接收數據長度) | 協議體部分(要接收的數據)|

舉個例子,假如我們的TCP客戶端發送了10MB字節的數據,如何讓Netty服務器一次就接收到這10MB數據呢?那就需要客戶端告訴服務端我發送的數據大小是多少,即在發送的數據中加入一個“數據包長度”即可,上麵提到的Handler就是用來和客戶端約定這個協議格式的,它有幾個參數,下麵我介紹一下它的參數意義:
     int maxFrameLength:定義接收數據包的最大長度,如果發送的數據包超過此值,則拋出異常;
     int lengthFieldOffset:長度屬性部分的偏移值,0表示長度屬性位於數據包頭部;
     int lengthFieldLength:長度屬性的字節長度,如果設置為4,就是我們用4個字節存放數據包的長度;
     int lengthAdjustment:協議體長度調節值,修正信息長度,如果設置為4,那麼解碼時再向後推4個字節;
     int initialBytesToStrip:跳過字節數,如我們想跳過長度屬性部分。

二、實例-客戶端發送10MB字節的數據,Netty服務端一次接收到全部10MB數據

客戶端:定義一個消息體,用頭部四個字節存放數據包長度

public byte[] send(byte[] sendData) throws UnknownHostException, IOException {
		Socket socket = new Socket(serverIp, serverPort);
		OutputStream os = socket.getOutputStream();
		InputStream is = socket.getInputStream();
		byte resultArray[] = null;
		try {
			// 定義一個發送消息協議格式:|--header:4 byte--|--content:10MB--|
			// 獲取一個4字節長度的協議體頭
			byte[] dataLength = intToByteArray(4, sendData.length);
			// 和請求的數據組成一個請求數據包
			byte[] requestMessage = combineByteArray(dataLength, sendData);
			//發送數據-------------------------------
			os.write(requestMessage);
			os.flush();
			//接收數據-------------------------------
			resultArray = IOUtils.toByteArray(is);	
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			os.close();
			is.close();
			socket.close();
		}
		return resultArray;
	}
private static byte[] intToByteArray(int byteLength, int intValue) {
		return ByteBuffer.allocate(byteLength).putInt(intValue).array();
	}
private static byte[] combineByteArray(byte[] array1, byte[] array2) {
		byte[] combined = new byte[array1.length + array2.length];
		System.arraycopy(array1, 0, combined, 0, array1.length);
		System.arraycopy(array2, 0, combined, array1.length, array2.length);
		return combined;
	}

Netty服務端:定義一個LengthFieldBasedFrameDecoder(1024*1024*1024, 0, 4,0,4)),最大數據量是1GB,長度屬性位於數據包頭部,占4個字節,協議體調節值為0,跳過頭部協議長度四個字節
@Override
			public void initChannel(SocketChannel ch) throws Exception {
				ChannelPipeline pipeline = ch.pipeline();

				pipeline.addLast("framedecoder",new LengthFieldBasedFrameDecoder(1024*1024*1024, 0, 4,0,4));
				pipeline.addLast(new TCPServiceHandler());// 處理業務Handler
				

			}


三、總結:客戶端和服務端定義消息格式必須一致

最後更新:2017-04-03 20:19:27

  上一篇:go Picasso and Android-Universal-Image-Loader緩存框架
  下一篇:go Universal Image Loader for Android 使用實例