Netty In Action中文版 - 第八章:附帶的ChannelHandler和Codec

http://blog.csdn.net/abc_key/article/details/38061079

本章介紹

  • 使用SSL/TLS創建安全的Netty程序
  • 使用Netty創建HTTP/HTTPS程序
  • 處理空閒連接和超時
  • 解碼分隔符和基於長度的協議
  • 寫大數據
  • 序列化數據
        上一章講解了如何創建自己的編解碼器,我們現在可以用上一章的知識來編寫自己的編解碼器。不過Netty提供了一些標準的ChannelHandler和Codec。Netty提供了很多協議的支持,所以我們不必自己發明輪子。Netty提供的這些實現可以解決我們的大部分需求。本章講解Netty中使用SSL/TLS編寫安全的應用程序,編寫HTTP協議服務器,以及使用如WebSocket或Google的SPDY協議來使HTTP服務獲得更好的性能;這些都是很常見的應用,本章還會介紹數據壓縮,在數據量比較大的時候,壓縮數據是很有必要的。

8.1 使用SSL/TLS創建安全的Netty程序

        通信數據在網絡上傳輸一般是不安全的,因爲傳輸的數據可以發送純文本或二進制的數據,很容易被破解。我們很有必要對網絡上的數據進行加密。SSL和TLS是衆所周知的標準和分層的協議,它們可以確保數據時私有的。例如,使用HTTPS或SMTPS都使用了SSL/TLS對數據進行了加密。
        對於SSL/TLS,Java中提供了抽象的SslContext和SslEngine。實際上,SslContext可以用來獲取SslEngine來進行加密和解密。使用指定的加密技術是高度可配置的,但是這不在本章範圍。Netty擴展了Java的SslEngine,添加了一些新功能,使其更適合基於Netty的應用程序。Netty提供的這個擴展是SslHandler,是SslEngine的包裝類,用來對網絡數據進行加密和解密。
        下圖顯示SslHandler實現的數據流:

上圖顯示瞭如何使用ChannelInitializer將SslHandler添加到ChannelPipeline,看下面代碼:
public class SslChannelInitializer extends ChannelInitializer<Channel> {

	private final SSLContext context;
	private final boolean client;
	private final boolean startTls;

	public SslChannelInitializer(SSLContext context, boolean client, boolean startTls) {
		this.context = context;
		this.client = client;
		this.startTls = startTls;
	}

	@Override
	protected void initChannel(Channel ch) throws Exception {
		SSLEngine engine = context.createSSLEngine();
		engine.setUseClientMode(client);
		ch.pipeline().addFirst("ssl", new SslHandler(engine, startTls));
	}
}
        需要注意一點,SslHandler必須要添加到ChannelPipeline的第一個位置,可能有一些例外,但是最好這樣來做。回想一下之前講解的ChannelHandler,ChannelPipeline就像是一個在處理“入站”數據時先進先出,在處理“出站”數據時後進先出的隊列。最先添加的SslHandler會啊在其他Handler處理邏輯數據之前對數據進行加密,從而確保Netty服務端的所有的Handler的變化都是安全的。
        SslHandler提供了一些有用的方法,可以用來修改其行爲或得到通知,一旦SSL/TLS完成握手(在握手過程中的兩個對等通道互相驗證對方,然後選擇一個加密密碼),SSL/TLS是自動執行的。看下面方法列表:
  • setHandshakeTimeout(long handshakeTimeout, TimeUnit unit),設置握手超時時間,ChannelFuture將得到通知
  • setHandshakeTimeoutMillis(long handshakeTimeoutMillis),設置握手超時時間,ChannelFuture將得到通知
  • getHandshakeTimeoutMillis(),獲取握手超時時間值
  • setCloseNotifyTimeout(long closeNotifyTimeout, TimeUnit unit),設置關閉通知超時時間,若超時,ChannelFuture會關閉失敗
  • setHandshakeTimeoutMillis(long handshakeTimeoutMillis),設置關閉通知超時時間,若超時,ChannelFuture會關閉失敗
  • getCloseNotifyTimeoutMillis(),獲取關閉通知超時時間
  • handshakeFuture(),返回完成握手後的ChannelFuture
  • close(),發送關閉通知請求關閉和銷燬

8.2 使用Netty創建HTTP/HTTPS程序

        HTTP/HTTPS是最常用的協議之一,可以通過HTTP/HTTPS訪問網站,或者是提供對外公開的接口服務等等。Netty附帶了使用HTTP/HTTPS的handlers,而不需要我們自己來編寫編解碼器。

8.2.1 Netty的HTTP編碼器,解碼器和編解碼器

        HTTP是請求-響應模式,客戶端發送一個http請求,服務就響應此請求。Netty提供了簡單的編碼解碼HTTP協議消息的Handler。下圖顯示了http請求和響應:


如上面兩個圖所示,一個HTTP請求/響應消息可能包含不止一個,但最終都會有LastHttpContent消息。FullHttpRequest和FullHttpResponse是Netty提供的兩個接口,分別用來完成http請求和響應。所有的HTTP消息類型都實現了HttpObject接口。下面是類關係圖:

        Netty提供了HTTP請求和響應的編碼器和解碼器,看下面列表:
  • HttpRequestEncoder,將HttpRequest或HttpContent編碼成ByteBuf
  • HttpRequestDecoder,將ByteBuf解碼成HttpRequest和HttpContent
  • HttpResponseEncoder,將HttpResponse或HttpContent編碼成ByteBuf
  • HttpResponseDecoder,將ByteBuf解碼成HttpResponse和HttpContent
看下面代碼:
public class HttpDecoderEncoderInitializer extends ChannelInitializer<Channel> {

	private final boolean client;

	public HttpDecoderEncoderInitializer(boolean client) {
		this.client = client;
	}

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ChannelPipeline pipeline = ch.pipeline();
		if (client) {
			pipeline.addLast("decoder", new HttpResponseDecoder());
			pipeline.addLast("", new HttpRequestEncoder());
		} else {
			pipeline.addLast("decoder", new HttpRequestDecoder());
			pipeline.addLast("encoder", new HttpResponseEncoder());
		}
	}
}
        如果你需要在ChannelPipeline中有一個解碼器和編碼器,還分別有一個在客戶端和服務器簡單的編解碼器:HttpClientCodec和HttpServerCodec。
        在ChannelPipelien中有解碼器和編碼器(或編解碼器)後就可以操作不同的HttpObject消息了;但是HTTP請求和響應可以有很多消息數據,你需要處理不同的部分,可能也需要聚合這些消息數據,這是很麻煩的。爲了解決這個問題,Netty提供了一個聚合器,它將消息部分合併到FullHttpRequest和FullHttpResponse,因此不需要擔心接收碎片消息數據。

8.2.2 HTTP消息聚合

        處理HTTP時可能接收HTTP消息片段,Netty需要緩衝直到接收完整個消息。要完成的處理HTTP消息,並且內存開銷也不會很大,Netty爲此提供了HttpObjectAggregator。通過HttpObjectAggregator,Netty可以聚合HTTP消息,使用FullHttpResponse和FullHttpRequest到ChannelPipeline中的下一個ChannelHandler,這就消除了斷裂消息,保證了消息的完整。下面代碼顯示瞭如何聚合:
/**
 * 添加聚合http消息的Handler
 * 
 * @author c.k
 * 
 */
public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {

	private final boolean client;

	public HttpAggregatorInitializer(boolean client) {
		this.client = client;
	}

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ChannelPipeline pipeline = ch.pipeline();
		if (client) {
			pipeline.addLast("codec", new HttpClientCodec());
		} else {
			pipeline.addLast("codec", new HttpServerCodec());
		}
		pipeline.addLast("aggegator", new HttpObjectAggregator(512 * 1024));
	}

}
        如上面代碼,很容使用Netty自動聚合消息。但是請注意,爲了防止Dos攻擊服務器,需要合理的限制消息的大小。應設置多大取決於實際的需求,當然也得有足夠的內存可用。

8.2.3 HTTP壓縮

        使用HTTP時建議壓縮數據以減少傳輸流量,壓縮數據會增加CPU負載,現在的硬件設施都很強大,大多數時候壓縮數據時一個好主意。Netty支持“gzip”和“deflate”,爲此提供了兩個ChannelHandler實現分別用於壓縮和解壓。看下面代碼:
	@Override
	protected void initChannel(Channel ch) throws Exception {
		ChannelPipeline pipeline = ch.pipeline();
		if (client) {
			pipeline.addLast("codec", new HttpClientCodec());
			//添加解壓縮Handler
			pipeline.addLast("decompressor", new HttpContentDecompressor());
		} else {
			pipeline.addLast("codec", new HttpServerCodec());
			//添加解壓縮Handler
			pipeline.addLast("decompressor", new HttpContentDecompressor());
		}
		pipeline.addLast("aggegator", new HttpObjectAggregator(512 * 1024));
	}

8.2.4 使用HTTPS

        網絡中傳輸的重要數據需要加密來保護,使用Netty提供的SslHandler可以很容易實現,看下面代碼:
/**
 * 使用SSL對HTTP消息加密
 * 
 * @author c.k
 * 
 */
public class HttpsCodecInitializer extends ChannelInitializer<Channel> {

	private final SSLContext context;
	private final boolean client;

	public HttpsCodecInitializer(SSLContext context, boolean client) {
		this.context = context;
		this.client = client;
	}

	@Override
	protected void initChannel(Channel ch) throws Exception {
		SSLEngine engine = context.createSSLEngine();
		engine.setUseClientMode(client);
		ChannelPipeline pipeline = ch.pipeline();
		pipeline.addFirst("ssl", new SslHandler(engine));
		if (client) {
			pipeline.addLast("codec", new HttpClientCodec());
		} else {
			pipeline.addLast("codec", new HttpServerCodec());
		}
	}

}

8.2.5 WebSocket

        HTTP是不錯的協議,但是如果需要實時發佈信息怎麼做?有個做法就是客戶端一直輪詢請求服務器,這種方式雖然可以達到目的,但是其缺點很多,也不是優秀的解決方案,爲了解決這個問題,便出現了WebSocket。
        WebSocket允許數據雙向傳輸,而不需要請求-響應模式。早期的WebSocket只能發送文本數據,然後現在不僅可以發送文本數據,也可以發送二進制數據,這使得可以使用WebSocket構建你想要的程序。下圖是WebSocket的通信示例圖:

        在應用程序中添加WebSocket支持很容易,Netty附帶了WebSocket的支持,通過ChannelHandler來實現。使用WebSocket有不同的消息類型需要處理。下面列表列出了Netty中WebSocket類型:
  • BinaryWebSocketFrame,包含二進制數據
  • TextWebSocketFrame,包含文本數據
  • ContinuationWebSocketFrame,包含二進制數據或文本數據,BinaryWebSocketFrame和TextWebSocketFrame的結合體
  • CloseWebSocketFrame,WebSocketFrame代表一個關閉請求,包含關閉狀態碼和短語
  • PingWebSocketFrame,WebSocketFrame要求PongWebSocketFrame發送數據
  • PongWebSocketFrame,WebSocketFrame要求PingWebSocketFrame響應
        爲了簡化,我們只看看如何使用WebSocket服務器。客戶端使用可以看Netty自帶的WebSocket例子。
        Netty提供了許多方法來使用WebSocket,但最簡單常用的方法是使用WebSocketServerProtocolHandler。看下面代碼:
/**
 * WebSocket Server,若想使用SSL加密,將SslHandler加載ChannelPipeline的最前面即可
 * @author c.k
 *
 */
public class WebSocketServerInitializer extends ChannelInitializer<Channel> {

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ch.pipeline().addLast(new HttpServerCodec(), 
				new HttpObjectAggregator(65536),
				new WebSocketServerProtocolHandler("/websocket"),
				new TextFrameHandler(),
				new BinaryFrameHandler(),
				new ContinuationFrameHandler());
	}

	public static final class TextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
			// handler text frame
		}
	}
	
	public static final class BinaryFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame>{
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
			//handler binary frame
		}
	}
	
	public static final class ContinuationFrameHandler extends SimpleChannelInboundHandler<ContinuationWebSocketFrame>{
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception {
			//handler continuation frame
		}
	}
}

8.2.6 SPDY

        SPDY(讀作“SPeeDY”)是Google開發的基於TCP的應用層協議,用以最小化網絡延遲,提升網絡速度,優化用戶的網絡使用體驗。SPDY並不是一種用於替代HTTP的協議,而是對HTTP協議的增強。新協議的功能包括數據流的多路複用、請求優先級以及HTTP報頭壓縮。谷歌表示,引入SPDY協議後,在實驗室測試中頁面加載速度比原先快64%。
        SPDY的定位:
  • 將頁面加載時間減少50%。
  • 最大限度地減少部署的複雜性。SPDY使用TCP作爲傳輸層,因此無需改變現有的網絡設施。
  • 避免網站開發者改動內容。 支持SPDY唯一需要變化的是客戶端代理和Web服務器應用程序。
        SPDY實現技術:
  • 單個TCP連接支持併發的HTTP請求。
  • 壓縮報頭和去掉不必要的頭部來減少當前HTTP使用的帶寬。
  • 定義一個容易實現,在服務器端高效率的協議。通過減少邊緣情況、定義易解析的消息格式來減少HTTP的複雜性。
  • 強制使用SSL,讓SSL協議在現存的網絡設施下有更好的安全性和兼容性。
  • 允許服務器在需要時發起對客戶端的連接並推送數據。
        SPDY具體的細節知識及使用可以查閱相關資料,這裏不作贅述了。

8.3 處理空閒連接和超時

        處理空閒連接和超時是網絡應用程序的核心部分。當發送一條消息後,可以檢測連接是否還處於活躍狀態,若很長時間沒用了就可以斷開連接。Netty提供了很好的解決方案,有三種不同的ChannelHandler處理閒置和超時連接:
  • IdleStateHandler,當一個通道沒有進行讀寫或運行了一段時間後出發IdleStateEvent
  • ReadTimeoutHandler,在指定時間內沒有接收到任何數據將拋出ReadTimeoutException
  • WriteTimeoutHandler,在指定時間內有寫入數據將拋出WriteTimeoutException
最常用的是IdleStateHandler,下面代碼顯示瞭如何使用IdleStateHandler,如果60秒內沒有接收數據或發送數據,操作將失敗,連接將關閉:
public class IdleStateHandlerInitializer extends ChannelInitializer<Channel> {

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ChannelPipeline pipeline = ch.pipeline();
		pipeline.addLast(new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS));
		pipeline.addLast(new HeartbeatHandler());
	}

	public static final class HeartbeatHandler extends ChannelInboundHandlerAdapter {
		private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer(
				"HEARTBEAT", CharsetUtil.UTF_8));

		@Override
		public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
			if (evt instanceof IdleStateEvent) {
				ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
			} else {
				super.userEventTriggered(ctx, evt);
			}
		}
	}
}

8.4 解碼分隔符和基於長度的協議

        使用Netty時會遇到需要解碼以分隔符和長度爲基礎的協議,本節講解Netty如何解碼這些協議。

8.4.1 分隔符協議

        經常需要處理分隔符協議或創建基於它們的協議,例如SMTP、POP3、IMAP、Telnet等等;Netty附帶的handlers可以很容易的提取一些序列分隔:
  • DelimiterBasedFrameDecoder,解碼器,接收ByteBuf由一個或多個分隔符拆分,如NUL或換行符
  • LineBasedFrameDecoder,解碼器,接收ByteBuf以分割線結束,如"\n"和"\r\n"
下圖顯示了使用"\r\n"分隔符的處理:

下面代碼顯示使用LineBasedFrameDecoder提取"\r\n"分隔幀:
/**
 * 處理換行分隔符消息
 * @author c.k
 *
 */
public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ch.pipeline().addLast(new LineBasedFrameDecoder(65 * 1204), new FrameHandler());
	}

	public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
			// do something with the frame
		}
	}
}
        如果框架的東西除了換行符還有別的分隔符,可以使用DelimiterBasedFrameDecoder,只需要將分隔符傳遞到構造方法中。如果想實現自己的以分隔符爲基礎的協議,這些解碼器是有用的。例如,現在有個協議,它只處理命令,這些命令由名稱和參數形成,名稱和參數由一個空格分隔,實現這個需求的代碼如下:
/**
 * 自定義以分隔符爲基礎的協議
 * @author c.k
 *
 */
public class CmdHandlerInitializer extends ChannelInitializer<Channel> {

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ch.pipeline().addLast(new CmdDecoder(65 * 1024), new CmdHandler());
	}

	public static final class Cmd {
		private final ByteBuf name;
		private final ByteBuf args;

		public Cmd(ByteBuf name, ByteBuf args) {
			this.name = name;
			this.args = args;
		}

		public ByteBuf getName() {
			return name;
		}

		public ByteBuf getArgs() {
			return args;
		}
	}

	public static final class CmdDecoder extends LineBasedFrameDecoder {

		public CmdDecoder(int maxLength) {
			super(maxLength);
		}

		@Override
		protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
			ByteBuf frame = (ByteBuf) super.decode(ctx, buffer);
			if (frame == null) {
				return null;
			}
			int index = frame.indexOf(frame.readerIndex(), frame.writerIndex(), (byte) ' ');
			return new Cmd(frame.slice(frame.readerIndex(), index), frame.slice(index + 1, frame.writerIndex()));
		}
	}

	public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> {
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception {
			// do something with the command
		}
	}

}

8.4.2 長度爲基礎的協議

        一般經常會碰到以長度爲基礎的協議,對於這種情況Netty有兩個不同的解碼器可以幫助我們來解碼:
  • FixedLengthFrameDecoder
  • LengthFieldBasedFrameDecoder
下圖顯示了FixedLengthFrameDecoder的處理流程:

如上圖所示,FixedLengthFrameDecoder提取固定長度,例子中的是8字節。大部分時候幀的大小被編碼在頭部,這種情況可以使用LengthFieldBasedFrameDecoder,它會讀取頭部長度並提取幀的長度。下圖顯示了它是如何工作的:

        如果長度字段是提取框架的一部分,可以在LengthFieldBasedFrameDecoder的構造方法中配置,還可以指定提供的長度。FixedLengthFrameDecoder很容易使用,我們重點講解LengthFieldBasedFrameDecoder。下面代碼顯示如何使用LengthFieldBasedFrameDecoder提取8字節長度:
public class LengthBasedInitializer extends ChannelInitializer<Channel> {

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65*1024, 0, 8))
			.addLast(new FrameHandler());
	}
	
	public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf>{
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
			//do something with the frame
		}
	}
}

8.5 寫大數據

        寫大量的數據的一個有效的方法是使用異步框架,如果內存和網絡都處於飽滿負荷狀態,你需要停止寫,否則會報OutOfMemoryError。Netty提供了寫文件內容時zero-memory-copy機制,這種方法再將文件內容寫到網絡堆棧空間時可以獲得最大的性能。使用零拷貝寫文件的內容時通過DefaultFileRegion、ChannelHandlerContext、ChannelPipeline,看下面代碼:
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		File file = new File("test.txt");
		FileInputStream fis = new FileInputStream(file);
		FileRegion region = new DefaultFileRegion(fis.getChannel(), 0, file.length());
		Channel channel = ctx.channel();
		channel.writeAndFlush(region).addListener(new ChannelFutureListener() {
			
			@Override
			public void operationComplete(ChannelFuture future) throws Exception {
				if(!future.isSuccess()){
					Throwable cause = future.cause();
					// do something
				}
			}
		});
	}
        如果只想發送文件中指定的數據塊應該怎麼做呢?Netty提供了ChunkedWriteHandler,允許通過處理ChunkedInput來寫大的數據塊。下面是ChunkedInput的一些實現類:
  • ChunkedFile
  • ChunkedNioFile
  • ChunkedStream
  • ChunkedNioStream
看下面代碼: 
public class ChunkedWriteHandlerInitializer extends ChannelInitializer<Channel> {
	private final File file;

	public ChunkedWriteHandlerInitializer(File file) {
		this.file = file;
	}

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ch.pipeline().addLast(new ChunkedWriteHandler())
			.addLast(new WriteStreamHandler());
	}

	public final class WriteStreamHandler extends ChannelInboundHandlerAdapter {
		@Override
		public void channelActive(ChannelHandlerContext ctx) throws Exception {
			super.channelActive(ctx);
			ctx.writeAndFlush(new ChunkedStream(new FileInputStream(file)));
		}
	}
}

8.6 序列化數據

        開發網絡程序過程中,很多時候需要傳輸結構化對象數據POJO,Java中提供了ObjectInputStream和ObjectOutputStream及其他的一些對象序列化接口。Netty中提供基於JDK序列化接口的序列化接口。

8.6.1 普通的JDK序列化

        如果你使用ObjectInputStream和ObjectOutputStream,並且需要保持兼容性,不想有外部依賴,那麼JDK的序列化是首選。Netty提供了下面的一些接口,這些接口放在io.netty.handler.codec.serialization包下面:
  • CompatibleObjectEncoder
  • CompactObjectInputStream
  • CompactObjectOutputStream
  • ObjectEncoder
  • ObjectDecoder
  • ObjectEncoderOutputStream
  • ObjectDecoderInputStream

8.6.2 通過JBoss編組序列化

        如果你想使用外部依賴的接口,JBoss編組是個好方法。JBoss Marshalling序列化的速度是JDK的3倍,並且序列化的結構更緊湊,從而使序列化後的數據更小。Netty附帶了JBoss編組序列化的實現,這些實現接口放在io.netty.handler.codec.marshalling包下面:
  • CompatibleMarshallingEncoder
  • CompatibleMarshallingDecoder
  • MarshallingEncoder
  • MarshallingDecoder
看下面代碼:
/**
 * 使用JBoss Marshalling
 * @author c.k
 *
 */
public class MarshallingInitializer extends ChannelInitializer<Channel> {
	private final MarshallerProvider marshallerProvider;
	private final UnmarshallerProvider unmarshallerProvider;

	public MarshallingInitializer(MarshallerProvider marshallerProvider, UnmarshallerProvider unmarshallerProvider) {
		this.marshallerProvider = marshallerProvider;
		this.unmarshallerProvider = unmarshallerProvider;
	}

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ch.pipeline().addLast(new MarshallingDecoder(unmarshallerProvider))
			.addLast(new MarshallingEncoder(marshallerProvider))
			.addLast(new ObjectHandler());
	}

	public final class ObjectHandler extends SimpleChannelInboundHandler<Serializable> {
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, Serializable msg) throws Exception {
			// do something
		}
	}
}

8.6.3 使用ProtoBuf序列化

        最有一個序列化方案是Netty附帶的ProtoBuf。protobuf是Google開源的一種編碼和解碼技術,它的作用是使序列化數據更高效。並且谷歌提供了protobuf的不同語言的實現,所以protobuf在跨平臺項目中是非常好的選擇。Netty附帶的protobuf放在io.netty.handler.codec.protobuf包下面:
  • ProtobufDecoder
  • ProtobufEncoder
  • ProtobufVarint32FrameDecoder
  • ProtobufVarint32LengthFieldPrepender
看下面代碼:
/**
 * 使用protobuf序列化數據,進行編碼解碼
 * 注意:使用protobuf需要protobuf-java-2.5.0.jar
 * @author Administrator
 *
 */
public class ProtoBufInitializer extends ChannelInitializer<Channel> {

	private final MessageLite lite;

	public ProtoBufInitializer(MessageLite lite) {
		this.lite = lite;
	}

	@Override
	protected void initChannel(Channel ch) throws Exception {
		ch.pipeline().addLast(new ProtobufVarint32FrameDecoder())
			.addLast(new ProtobufEncoder())
			.addLast(new ProtobufDecoder(lite))
			.addLast(new ObjectHandler());
	}

	public final class ObjectHandler extends SimpleChannelInboundHandler<Serializable> {
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, Serializable msg) throws Exception {
			// do something
		}
	}
}

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