本章主要内容
- SSL/TLS加密Netty应用
- 构建HTTP/HTTPS应用
- 处理空闲连接和超时问题
- 解码分隔符和以长度为基础的协议
- 写大量数据
- 序列化大量数据
Netty提供了很多常用协议(例如HTTP)的实现,所以开发者不需要重复造这些轮子。这一章我们会学习如何使用SSL/TLS加密Netty应用,怎么样编写可扩展的HTTP服务器,怎么使用联合协议如WebSockets或谷歌的SPDY构建高性能服务端。这些都是很常见,很多应用中都必须用的组件。本章还会介绍一些压缩知识,当数据比较大时,这个也是很影响应用性能的。
一、使用SSL/TLS加密Netty应用
网络传输数据默认情况下是不安全的,传输的数据一般都是纯文本或是很容易被解码的二进制数据。当需要传输一些私密的数据的时候就可能会出现问题,毕竟黑客不是吃干饭的。
加密算法的出现就是解决这种问题的。TLS和SSL是比较知名的应用在网络应用层协议上的加密协议。例如我们常用的HTTPS和SMTPS就是使用它们加密的。因为TLS和SSL经常是组合到应用层协议之上的,所以应用开发者很少注意到它们,但实际上很多地方都已经使用了它们。
对于SSL/TLS,Java也提供了抽象层,就是SslContext和SslEngine。实际上,SslContext可以用来获取一个SslEngine,SslEngine可以用来加密和解密。它的高度可配置性用于支持指定的密码或其他的,不过这不是本书将知识点,感兴趣的同学可以下面去查查相关资料。
Netty继承了Java的SSL引擎并增加很多功能以更适应基于Netty的应用。Netty提供了一个名字叫SslHandler的ChannelHandler,它包装了一个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中的位置都是第一个。可能会有一些其他情况,不过一般情况都是这个规则。前面的ChannelHandlers章节我们已经学习过了,ChannelPipeline收到数据时处理流程类似一个LIFO(后进先出)队列,发送数据时处理流程类似FIFO(先进先出)队列。所以把SslHandler放到第一位,可以保证其他ChannelHandler在数据加密之前进行处理,而数据通过网络传输时又是已经加密的。
SslHandler有很多有用的方法,如下表。可以通过这些方法修改它的行为或者获取SSL/TLS握手完成的通知,握手就是双方互相验证并选择双方支持的加密密码。SSL/TLS的握手会自动执行。
名称 | 描述 |
setHandshakeTimeout(...) |
|
setHandshakeTimeoutMillis(...) |
|
getHandshakeTimeoutMillis() | 设置/获取握手超时时间 |
setCloseNotifyTimeout(...) |
|
setCloseNotifyTimeoutMillis(...) |
|
getCloseNotifyTimeoutMillis() | 设置/获取关闭通知超时时间 |
handshakeFuture(...) | 获取一个握手的Future,一旦握手完成就会获得通知 |
close(...) | 发送close_notify到指定Channel,不过这个方法已经过时, 官方建议用Channel的close方法或ChannelHandlerContext的close方法 |
二、构建Netty的HTTP/HTTPS应用
无论是PC机还是现在的移动端,HTTP/HTTPS都是非常成功非常常用的协议。几乎每一个公司都有一个可以通过HTTP/HTTPS访问的主页,不过HTTP/HTTPS的作用远不止此。例如现在很多云服务也都是提供HTTP/HTTPS的API对外服务的。
Netty也提供了很多关于HTTP的ChannelHandler,所以使用它们的时候不需要开发者编写编解码器等。
2.1、Netty的HTTP编解码器
HTTP使用的是请求-响应的模式,也就是客户端发出一个HTTP请求,然后服务端回复一个HTTP响应。Netty提供了各种各样的HTTP的编码器和解码器,所以开发HTTP的应用变得很容易。下面两幅图展示了完整的HTTP请求和响应的结构。
从上图可以看出来,完整的HTTP请求和响应包含的消息不止一个。它们往往以LastHttpContent结尾。上面这些HTTP的消息类型都是实现了HttpObject接口的。下表列出了常用的HTTP解码器和编码器。
名称 | 描述 |
HttpRequestEncoder | 编码HttpRequest和HttpContent转成字节 |
HttpResponseEncoder | 编码HttpResponse和HttpContent转成字节 |
HttpRequestDecoder | 解码字节转成HttpRequest和HttpContent |
HttpResponseDecoder | 解码字节转成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("encoder", new HttpRequestEncoder());
} else {
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
}
}
}
如果需要在ChannelPipeline编码和解码,有更简单易用的编解码器完成这个工作。可以使用HttpClientCodec或HttpServerCodec代替上面的编码器和解码器。
当ChannelPipeline中准备好了HTTP的编码器和解码器,应用就可以处理各种不同的HttpObject消息了。不过HTTP请求和响应都包含多个消息,需要处理各个部分并且还可能需要组合它们。这就比较麻烦了。为了解决这个问题,Netty提供了一个组合器,将消息组合到FullHttpRequest和FullHttpResponse中,所以开发者不用担心收到的消息不完整。
2.2、HTTP消息组合
当内存有余量时,我们往往只想处理完整的HTTP消息。因此,Netty提供了一个HttpObjectAggregator。通过HttpObjectAggregator,Netty会将HTTP消息组合到FullHttpResponse和FullHttpRequest中,在下一个ChannelHandler中只需要处理它们就可以了。这样就不用担心消息不完整,因为处理的都是完整消息对象。
消息组合很简单,就是添加一个ChannelHandler而已。
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就可以帮助我们组合HTTP消息。不过要注意的是,为了保护你的服务端例如DoS攻击,应该为消息设置一个最大上限。上限取多少,取决于应用的实际情况了,比如日访问量,并发量以及内存容量等。
2.3、HTTP压缩
一般我们使用HTTP的时候,为了减小数据传输量,都会采取一定的压缩方法,特别是在移动端,流量还是比较珍贵的。当然,压缩数据也是有代价的,会提供CPU的负载。不过很多时候,其实我们的服务器CPU都是有富裕的,所以很多应用中使用压缩技术是一个正确的选择。
Netty提供了gzip和deflate的实现,一个用来压缩,一个用来解压缩。压缩与解压代码如下。
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());
pipeline.addLast("decompressor",
new HttpContentDecompressor());
} else {
pipeline.addLast("codec", new HttpServerCodec());
pipeline.addLast("compressor",
new HttpContentCompressor());
}
}
}
2.4、使用HTTPS
HTTP是明文传输的,有的时候我们保护我们传输的数据,通过HTTPS就可以了。
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 {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine engine = context.createSSLEngine();
engine.setUseClientMode(client);
pipeline.addFirst("ssl", new SslHandler(engine));
if (client) {
pipeline.addLast("codec", new HttpClientCodec());
} else {
pipeline.addLast("codec", new HttpServerCodec());
}
}
}
上面的例子也可以看出来,Netty提供的ChannelPipeline很方面我们扩展应用。Netty提供的这些HTTP相关的实现帮助我们很方便的开发出基于HTTP协议的应用。下一小节我们将要学习一个扩展了HTTP协议的协议:WebSocket。
2.5、使用WebSocket
不可否认,HTTP是非常不错的,但是如果服务端想要实时发布自己的数据怎么办呢?可能之前大家了解过这方面的解决办法,比如AJAX轮询请求等,但这些方法都不是最优的,而且性能比较差。
这也就是为什么会出现WebSocket协议的原因。WebSocket允许服务端和客户端直接交换数据,不需要使用请求-响应的模式。刚开是WebSocket只能传输纯本文数据,不过现在不一样了,它也可以传输二进制数据,这样利用WebSocket就可以构建出很多丰富功能的应用。
本书不会太过详细讲解WebSocket,感兴趣的同学可以查查其他资料。不过,为了方便大家学习,这里简单介绍一下WebSocket服务端和客户端传输数据的基本结构,如下图。
握手还是通过HTTP完成的,最大的区别就是握手之后传输数据就不用再发送HTTP标识相关的数据了,一次握手,互相可以发送很多数据。
Netty也提供了很多WebSocket的实现,来简化我们的开发工作。使用WebSocket要处理很多不同类型的消息,如下表。
名称 | 说明 |
BinaryWebSocketFrame | 二进制数据消息类型 |
TextWebSocketFrame | 文本数据消息类型 |
ContinuationWebSocketFrame | 二进制或文本数据,一般用于有多个消息类型的情况 |
CloseWebSocketFrame | 关闭请求消息类型 |
PingWebSocketFrame | 类似请求的消息类型 |
PongWebSocketFrame | 类似响应的消息类型 |
为了节省时间,这里只简单讲解一下使用Netty在WebSocket服务端方面的应用,因为一般情况下,都不会使用Netty编写客户端应用,最常用的客户端还是浏览器,至于Netty的WebSocket客户端应用,和服务端差不多,具体可以参考Netty源码。
Netty提供了很多种方式使用WebSocket,不过最常用的还是WebSocketServerProtocolHandler。
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
public void channelRead0(ChannelHandlerContext ctx,
TextWebSocketFrame msg) throws Exception {
}
}
public static final class BinaryFrameHandler extends
SimpleChannelInboundHandler<BinaryWebSocketFrame> {
@Override
public void channelRead0(ChannelHandlerContext ctx,
BinaryWebSocketFrame msg) throws Exception {
}
}
public static final class ContinuationFrameHandler extends
SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
@Override
public void channelRead0(ChannelHandlerContext ctx,
ContinuationWebSocketFrame msg) throws Exception {
}
}
}
可以看到,只是添加了WebSocket相关的Handler即可。当然如果需要加密,也就再添加一个SslHandler即可,不过记住,加密的要放到第一个位置。
2.6、SPDY
SPDY是谷歌为了提供网页响应速度而基于HTTP的增强协议,通过压缩、加密等方式提高网页响应速度,提升用户体验。但是,IETF对SPDY进行了标准化,很多好东西都移到了HTTP/2中,所以谷歌宣布放弃了对SPDY的支持,转去支持HTTP/2了。很多浏览器、服务器应用开发商都放弃了SPDY了,如Chrome,Nginx。但是本书原著有这一小节,所以我翻译的时候还是提一下,但相关知识不再翻译了,因为以后也没人用了,就不要浪费时间学这些过时的知识了。
三、空闲连接与超时处理
有些时候我们的应用需要处理空闲连接的情况和超时问题。很多时候我们对于连接都会有一个心跳监测机制,也就是每隔一段时间向对方发送一个消息包,用来检查对方是否还存活。另一种情况是针对空闲太久的连接直接断开。
所以来说,很多完整的应用处理空闲连接也是一个比较核心的部分,还好Netty提供了几个类用来处理这个问题。下表列出了Netty常用的处理空闲连接和超时问题的ChannelHandler。
名称 | 描述 |
IdleStateHandler | 如果连接空闲时间过长会触发IdleStateEvent |
ReadTimeoutHandler | 超过时间没收到数据,这个就会抛出一个ReadTimeoutException, 并关闭Channel |
WriteTimeoutHandler | 超过时间写操作没完成就会抛出WriteTimeoutException 并关闭Channel |
下面的代码展示了如果超过60秒没有收到数据或发送数据,IdleStateHandler如何得到通知的。这个例子中会发送一个心跳检测给对方,如果检测失败就关闭连接。
public class IdleStateHandlerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//添加IdleStateHandler
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.ISO_8859_1));
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
//发送心跳检测,失败就关闭Channel
ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate())
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
//不是IdleStateEvent就传递给下一个ChannelHandler
super.userEventTriggered(ctx, evt);
}
}
}
}
四、解码以分隔符和内容长度为基础的协议
使用Netty开发网络应用,难免会遇到需要解码分隔符或内容长度为基础的协议。这一小节就来介绍下Netty提供了哪些类来帮助开发者处理这种协议。
4.1、分隔符基础的协议
如果需要处理分隔符基础的协议或在它之上构建应用。例如SMTP, POP3, IMAP和Telnet等协议都是这种类型的。Netty为此提供了一些ChannelHandler,可以帮助开发者很容易根据分隔符提取数据序列。
名称 | 描述 |
DelimiterBasedFameDecoder | 根据指定的分隔符分割数据 |
LineBasedFrameDecoder | 根据\r \n符号分割数据,也就是按行分割, 它的性能比DelimiterBasedFameDecoder快 |
public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(65 * 1024));
pipeline.addLast(new FrameHandler());
}
public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead0(ChannelHandlerContext ctx,
ByteBuf msg) throws Exception {
}
}
}
如果你的应用的分割比较特殊,就可以选择使用
DelimiterBasedFrameDecoder,它和LineBasedFrameDecoder用法差不多,只不过在创建实例的时候需要传入分隔符。
这个类也可以用来实现自己的分隔符协议。假设我们一个应用需要处理命令行指令,命令是由命令名称和参数组成。名称和参数通过空格分割。下面我们通过继承LineBasedFrameDecoder来实现我们自己的分割器。
public class CmdHandlerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new CmdDecoder(65 * 1024));
pipeline.addLast(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 name() {
return name;
}
public ByteBuf args() {
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
public void channelRead0(ChannelHandlerContext ctx, Cmd msg)
throws Exception {
}
}
}
4.2、长度为基础的协议
开发网络应用经常也会遇到以长度为基础的协议。为此Netty也提供了两个不同的分割器帮助开发者提取数据。
名称 | 描述 |
FixedLengthFrameDecoder | 根据固定长度提取数据 |
LengthFieldBasedFrameDecoder | 根据头信息中的长度数据分割数据 |
从上图可以很明显的看出来,FixedLengthFrameDecoder是按固定长度提取的,每个数据块都是8字节。
另外一些时候,协议中会将数据长度封装到头信息中,这个时候可以使用LengthFieldBasedFrameDecoder,它会从头信息中读取长度,然后按此长度提取数据。
如果长度信息是头数据中的一部分,也可以通过LengthFieldBasedFrameDecoder的构造函数配置。可以指定长度信息位置和具体长度,具体可以参考API文档。
FixedLengthFrameDecoder是非常易用的,所以这里我们简单举一个LengthFieldBasedFrameDecoder的例子。
public class LengthBasedInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//指定长度信息在前8字节
pipeline.addLast(new LengthFieldBasedFrameDecoder(65 * 1024, 0, 8));
pipeline.addLast(new FrameHandler());
}
public static final class FrameHandler
extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
}
}
}
五、写大量数据
在异步框架中以有效的方式写大量数据通常都是一个难点,例如需要在网络网络饱和的时候停止写数据,又或者你把内存使用完了会出现OutOfMemoryError的错误。
Netty允许使用零内存复制的技术发送文件内容,也就是说以最大的性能,通过内核空间直接将文件系统中的数据放到网络栈中。这个功能只适用于直接发送文件内容而不需要应用去操作文件内容;当你的应用要对文件内容进行操作,就需要将内容复制到用户空间去。至于上面的工作都是由Netty去完成,开发者不用关心底层具体细节。通过零内存复制技术发送文件内容,需要将一个DefaultFileRegion写到Channel,ChannelHandlerContext或ChannelPipeline,如下面的代码。
FileInputStream in = new FileInputStream(file);
FileRegion region = new DefaultFileRegion(in.getChannel(), 0, file.length());
channel.writeAndFlush(region)
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
if (!future.isSuccess()) {
Throwable cause = future.cause();
}
}
});
但是当你需要发送大量非文件内容的时候该怎么办呢?
Netty提供了一个特殊的ChannelHandler,名字叫ChunkedWriteHandler,它可以处理ChunkedInput的实现的大量数据,Netty提供了下表这些实现。
名称 | 描述 |
ChunkedFile | 可以写文件,一般是在操作系统不支持零内存复制的时候才用它 |
ChunkedNioFile | 也是文件,只不过换成NIO的方式,也是在操作系统不支持零内存复制的时候才用它 |
ChunkedNioStream | 将ReadableByteChannel的内容转到它里面 |
ChunkedStream | 可以将InputStream的内容转到它里面 |
ChunkedStream是最常用的,下面举个简单例子。
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 {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChunkedWriteHandler());
pipeline.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)));
}
}
}
六、序列化数据
当你需要在网络中传输Java对象时,Java提供了ObjectOutputStream和ObjectInputStream来序列化对象。不过也有一些其他方法可以做到,下面来看一下Netty提供的方式。
6.1、JDK序列化
如果你的应用的对端使用ObjectOutputStream和ObjectInputStream并且你要保持兼容性,或者你又不想依赖别的Java库,JDK序列化是一个不错的选择。
名称 | 描述 |
CompatibleObjectDecoder | 兼容JDK的反序列化器,对端也不需要使用Netty |
CompatibleObjectEncoder | 兼容JDK的序列化器,对端也不需要使用Netty |
CompactObjectDecoder | 基于JDK反序列化的自定义实现,一般来说只有在想提高性能又不想依赖外部库的 |
CompactObjectEncoder | 基于JDK序列化的自定义实现,一般来说只有在想提高性能又不想依赖外部库的 |
6.2、JBoss Marshalling序列化
如果想使用外部的序列化库,JBoss的Marshalling是一个不错的选择。它的速度比JDK原生的大概快3倍。
Netty提供了一种兼容JDK序列化的Marshalling序列化类,性能也不错;另一种就是对端也使用Marshalling,这样就会最大化性能。
名称 | 描述 |
CompatibleMarshallingDecoder | 兼容JDK反序列化,对端不需要使用Netty |
CompatibleMarshallingEncoder | 兼容JDK序列化,对端不需要使用Netty |
MarshallingDecoder | Marshalling反序列化 |
MarshallingEncoder | Marshalling序列化 |
public class MarshallingInitializer extends ChannelInitializer<Channel> {
private final MarshallerProvider marshallerProvider;
private final UnmarshallerProvider unmarshallerProvider;
public MarshallingInitializer(UnmarshallerProvider unmarshallerProvider,
MarshallerProvider marshallerProvider) {
this.marshallerProvider = marshallerProvider;
this.unmarshallerProvider = unmarshallerProvider;
}
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new MarshallingDecoder(unmarshallerProvider));
pipeline.addLast(new MarshallingEncoder(marshallerProvider));
pipeline.addLast(new ObjectHandler());
}
public static final class ObjectHandler extends SimpleChannelInboundHandler<Serializable> {
@Override
public void channelRead0(ChannelHandlerContext channelHandlerContext,
Serializable serializable) throws Exception {
}
}
}
6.3、ProtoBuf序列化
Netty也提供了ProtoBuf序列化的相关类来简化开发。ProtoBuf是谷歌开源的一个序列化与反序列化框架,它支持很多不同开发语言,所以有很好的夸平台性。
名称 | 描述 |
ProtobufDecoder | 反序列化 |
ProtobufEncoder | 序列化 |
ProtobufVarint32FrameDecoder | 根据谷歌协议的长度128字段反序列化 |
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 {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new ProtobufDecoder(lite));
pipeline.addLast(new ObjectHandler());
}
public static final class ObjectHandler extends SimpleChannelInboundHandler<Object> {
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg)
throws Exception {
}
}
}
七、总结
本章主要介绍了Netty提供的常用的ChannelHandler和编解码器。这些都可以帮助开发者快速开发出自己的网络应用,减少重复造轮子的代码。
通过本章的例子,也基本了解了如何组合这些ChannelHandler和编解码器,来完成应用需求。Netty提供的这些ChannelHandler和编解码器也是经过很多测试和验证的,是比较可靠强大的。
当然本章也只是简单介绍了一些常用的类,很有很多其他的,要介绍完估计都可以令写一本书了,所以如果感兴趣的同学可以在下面参考详细的API文档。
下一章主要学习如何启动服务并组合ChannelHandler完成业务需求。