標籤:ext text 求和 ima star 應用 std cli ted
一、前言
Netty 為許多通用協議提供了編×××和處理器,幾乎可以開箱即用, 這減少了你在那些相當繁瑣的事務上本來會花費的時間與精力。另外,這篇文章中,就不涉及 Netty 對 WebSocket協議 的支援了,因為涉及的篇幅有點大,會在下一篇文章做一個具體的介紹。
回到頂部
二、SSL 協議
SSL 協議是安全性通訊協定,層疊在其他協議之上。為了支援 SSL/TLS, Java 提供了 javax.net.ssl 包,它的 SSLContext 和 SSLEngine 類使得實現解密和加密相當簡單直接。 Netty 通過一個名為 SslHandler 的 ChannelHandler 實現利用了這個 API, 其中 SslHandler 在內部使用 SSLEngine 來完成實際的工作。描述的是 SslHandler 的資料流。
複製代碼br/>@Override
ByteBufAllocator byteBufAllocator = ch.alloc();
//對於每個 SslHandler 執行個體,都使用 Channel 的 ByteBufAllocator 從 SslContext 擷取一個新的 SSLEngine
SSLEngine sslEngine = context.newEngine(byteBufAllocator);
//伺服器端模式,用戶端模式設定為true
sslEngine.setUseClientMode(false);
//不需要驗證用戶端,用戶端不設定該項
sslEngine.setNeedClientAuth(false);
//要將 SslHandler 設定為第一個 ChannelHandler。這確保了只有在所有其他的 ChannelHandler 將他們的邏輯應用程式到資料之後,才會進行加密。
//startTls 如果為true,第一個寫入的訊息將不會被加密(用戶端應該設定為true)
ch.pipeline().addFirst("ssl",new SslHandler(sslEngine, startTls));
}
複製代碼
tips:對於 ChannelPipeline 鏈中 ChannelHandler 執行的順序 —— 入站事件順序執行、出站事件逆序執行。
回到頂部
三、HTTP 協議
HTTP 是基於請求/響應模式的:用戶端向伺服器發送一個 HTTP 要求,然後伺服器將會返回一個 HTTP 響應。 展示了 Netty 中 HTTP請求和響應的組成部分:
Netty 對 HTTP 協議的支援主要提供了以下 ChannelHandler:
HttpResponseDecoder:×××,用於用戶端,解碼來自服務端的響應。
HttpRequestEncoder:編碼器,使用者用戶端,編碼向服務端發送的請求。
HttpRequestDecoder:×××,用於服務端,解碼來自用戶端的請求。
HttpResponseEncoder:編碼器,用於服務端,編碼向用戶端的響應。
HttpClientCodec:編×××,使用者用戶端,效果等於 HttpResponseDecoder + HttpRequestEncoder。
HttpServerCodec:編×××,使用者服務端,效果等於 HttpRequestDecoder + HttpResponseEncoder。
HttpObjectAggregator:彙總器,由於 HTTP 的請求和響應可能由許多部分組成,需要彙總它們以形成完整的訊息,HttpObjectAggregator 可以將多個訊息部分合并為 FullHttpRequest 或者 FullHttpResponse 訊息。
HttpContentCompressor:壓縮,使用者服務端,壓縮要傳輸的資料,支援 gzip 和 deflate 壓縮格式。
HttpContentDecompressor:解壓縮,用於用戶端,解壓縮服務端傳輸的資料。
複製代碼br/>@Override
ChannelPipeline pipeline = ch.pipeline();
SSLEngine sslEngine = sslContext.newEngine(ch.alloc());
if (isClient) {
//使用 HTTPS,添加 SSL 認證
pipeline.addFirst("ssl", new SslHandler(sslEngine, true));
pipeline.addLast("codec", new HttpClientCodec());
//1、建議開啟壓縮功能以儘可能多地減少傳輸資料的大小
//2、用戶端處理來自伺服器的壓縮內容
pipeline.addLast("decompressor", new HttpContentDecompressor());
}else {
pipeline.addFirst("ssl", new SslHandler(sslEngine));
//HttpServerCodec:將HTTP用戶端請求轉成HttpRequest對象,將HttpResponse對象編碼成HTTP響應發送給用戶端。
pipeline.addLast("codec", new HttpServerCodec());
//服務端,壓縮資料
pipeline.addLast("compressor", new HttpContentCompressor());
}
//目的多個訊息轉換為一個單一的FullHttpRequest或是FullHttpResponse
//將最大的訊息為 512KB 的HttpObjectAggregator 添加到 ChannelPipeline
//在訊息大於這個之後會拋出一個 TooLongFrameException 異常。
pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024));
}
複製代碼
tips:當使用 HTTP 時,建議開啟壓縮功能以儘可能多地減小傳輸資料的大小。雖然壓縮會帶來一些 CPU 刻度上的開銷。
回到頂部
四、拆包和粘包的解決方案
TCP 傳輸過程中,用戶端發送了兩個資料包,而服務端卻只收到一個資料包,用戶端的兩個資料包粘連在一起,稱為粘包;
TCP 傳輸過程中,用戶端發送了兩個資料包,服務端雖然收到了兩個資料包,但是兩個資料包都是不完整的,或多了資料,或少了資料,稱為拆包;發生TCP粘包、拆包主要是由於下面一些原因:
1、應用程式寫入的資料大於通訊端緩衝區大小,這將會發生拆包。
2、應用程式寫入資料小於通訊端緩衝區大小,網卡將應用多次寫入的資料發送到網路上,這將會發生粘包。
3、進行MSS(最大報文長度)大小的TCP分段,當TCP報文長度-TCP頭部長度>MSS的時候將發生拆包。
4、接收方法不及時讀取通訊端緩衝區資料,這將發生粘包。
Netty 預定義了一些×××用於解決粘包和拆包現象,其中大體分為兩類:
基於分隔字元的協議:在資料包之間使用定義的字元來標記訊息或者訊息段的開頭或者結尾。這樣,接收端通過這個字元就可以將不同的資料包拆分開。
基於長度的協議:發送端給每個資料包添加包頭部,頭部中應該至少包含資料包的長度,這樣接收端在接收到資料後,通過讀取包頭部的長度欄位,便知道每一個資料包的實際長度了。
基於分隔字元的協議
複製代碼
public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
@Overrideprotected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( // 將提取到的楨轉寄給下一個Channelhandler new LineBasedFrameDecoder(64 * 1024), // 添加 FrameHandler 以接收幀 new FrameHandler() );}public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { //Do something with the data extracted from the frame }}
}
複製代碼
基於長度的協議
LengthFieldBasedFrameDecoder 是 Netty 基於長度協議解決拆包粘包問題的一個重要的類,主要結構就是 header+body 結構。我們只需要傳入正確的參數就可以發送和接收正確的資料,那嗎重點就在於這幾個參數的意義。下面我們就具體瞭解一下這幾個參數的意義。先來看一下LengthFieldBasedFrameDecoder主要的構造方法:
public LengthFieldBasedFrameDecoder(
int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip)
maxFrameLength:最大幀長度。也就是可以接收的資料的最大長度。如果超過,此次資料會被丟棄。
lengthFieldOffset:長度域位移。就是說資料開始的幾個位元組可能不是表示資料長度,需要後移幾個位元組才是長度域。
lengthFieldLength:長度域位元組數。用幾個位元組來表示資料長度。
lengthAdjustment:資料長度修正。因為長度域指定的長度可以使 header+body 的整個長度,也可以只是body的長度。如果表示header+body的整個長度,那麼我們需要修正資料長度。
initialBytesToStrip:跳過的位元組數。如果你需要接收 header+body 的所有資料,此值就是0,如果你只想接收body資料,那麼需要跳過header所佔用的位元組數。
複製代碼
public class LengthBasedInitializer extends ChannelInitializer<Channel> {
@Overrideprotected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( new LengthFieldBasedFrameDecoder(64 * 1024, 0, 8), new FrameHandler() );}public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { //處理楨的資料 }}
}
複製代碼
tips:UDP協議不會發生沾包或拆包現象, 因為UDP是基於報文發送的,在UDP首部採用了16bit來指示UDP資料報文的長度,因此在應用程式層能很好的將不同的資料報文區分開。
回到頂部
五、其他
由於網路飽和的可能性,如何在非同步架構中高效地寫大塊的資料是一個特殊的問題。Netty 通過一個 FileRegion 介面來實現,其在 Netty 的API 文檔中的定義是:"通過支援零拷貝的檔案傳輸的 Channel 來發送的檔案地區"。但是該介面只適用於檔案內容的直接傳輸,不包括應用程式對檔案資料的任何處理。
View Code
如果大塊的資料要從檔案系統複製到使用者記憶體中時,可以安裝一個 ChunkedWriteHandler,並用 ChunkedInput 實現寫入檔案資料。 它支援非同步寫大型資料流,而又不會導致大量的記憶體消耗。
ChunkedWriteHandlerInitializer.java
Netty提供的用於和JDK進行互操作的序列化類別 :Netty提供的用於和 JBoss Marshalling 進行互操作的序列化類別 :
MarshallingInitializer.java
Netty提供的用於和 Protocol Buffers 進行互操作的序列化類別 :
小編帶你瞭解Netty 系列七(那些開箱即用的 ChannelHandler).