Netty4实战第八章:Netty提供的ChannelHandler和编解码器

本章主要内容

  • SSL/TLS加密Netty应用
  • 构建HTTP/HTTPS应用
  • 处理空闲连接和超时问题
  • 解码分隔符和以长度为基础的协议
  • 写大量数据
  • 序列化大量数据
  上一章我们学习了如何编写自己的编解码器。通过那些知识,我们知道了如何为自己的Netty应用编写编解码器。但是,如果Netty本身就提供了一些标准的ChannelHandler和编解码器岂不是更好,开发者开箱即用,实际工作中就不用重复造轮子了。
  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

解码字节转成HttpResponseHttpContent

        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

  使用最多的就是 IdleStateHandler了,下面我们会仔细研究一下它。

  下面的代码展示了如果超过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的。


  从上图可以很明显的看出来,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完成业务需求。

  


  


  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值