《Netty实战》读书笔记(二)

282 阅读3分钟

《Netty实战》读书笔记(二)

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

主要内容

  1. EnumbedChannel和单元测试
  2. 编码器和解码器
  3. SSL /TLS强化
  4. 心跳检测
  5. 其他扩展协议

测试入站信息

使用EmbeddedChannel测试ChannelHandler

自定一个解码器:FixedLengthFrameDecoder

public class FramLengthFrameDecoder extends ByteToMessageDecoder {
    private final int frameLength;

    public FramLengthFrameDecoder(int frameLength) {
        this.frameLength = frameLength;
    }


    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        while(in.readableBytes() >= frameLength){
            ByteBuf buf = in.readBytes(frameLength);
            out.add(buf);
        }
    }
}

使用单元测试测试如下情况

public class FixedLengthFrameDecoderTest {

    @Test
    public void testFixedLengthFrameDecoder() {
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < 9; i++) {
            buf.writeByte(i);

        }
        ByteBuf input = buf.duplicate();
        EmbeddedChannel embeddedChannel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
        assertTrue(embeddedChannel.writeInbound(input.retain()));
        // 标记为完成的状态
        assertTrue(embeddedChannel.finish());

        // 测试是否为3个划分的
        assertEquals(buf.readSlice(3), embeddedChannel.readInbound());
        assertEquals(buf.readSlice(3), embeddedChannel.readInbound());
        assertEquals(buf.readSlice(3), embeddedChannel.readInbound());

        assertNull(embeddedChannel.readInbound());
    }

    @Test
    public void testFiexdLengthFrameDecoder2(){
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < 9; i++) {
            buf.writeByte(i);

        }
        ByteBuf input = buf.duplicate();
        EmbeddedChannel embeddedChannel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
        assertFalse(embeddedChannel.writeInbound(input.readBytes(2)));

    }
}

测试出站信息

使用AbsIntegerEncoder

    /**
     * 自定义编码器
     * @param ctx
     * @param msg
     * @param out
     * @throws Exception
     */
    protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception {
        while(buf.readableBytes() > 4){
            int abs = Math.abs(buf.readInt());
            out.add(abs);
        }
    }
}

注意一个int 占4个Byte, 如果不够字节长度无法处理

启用单元测试

public class AbsIntegerEncoderTest {

    @Test
    public void testAbsIntegerEndocer(){
        ByteBuf buffer = Unpooled.buffer();
        for (int i = -1; i > -9; i--) {
            buffer.writeInt(i);
        }
        EmbeddedChannel embeddedChannel = new EmbeddedChannel(new AbsIntegerEncoder());
        Assert.assertTrue(embeddedChannel.writeOutbound(buffer));
        assertTrue(embeddedChannel.finish());
        // 查看绝对值是否相等
        for (int i = 1; i < 8; i++) {
            assertEquals(i, embeddedChannel.readOutbound());
        }
    }
}

异常处理

我们限制了使用指定的规格去读取数据,如果超出了数据范围,我们需要舍弃并且抛出异常

改进之后的代码如下:

    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//        while(in.readableBytes() >= frameLength){
//            ByteBuf buf = in.readBytes(frameLength);
//            out.add(buf);
//        }
        int readableBytes = in.readableBytes();
        if(readableBytes > frameLength){
            in.clear();
            throw new TooLongFrameException();
        }
        // 否则,读取对应的字节数据
        ByteBuf byteBuf = in.readBytes(readableBytes);
        out.add(byteBuf);

    }
}

注意,这里使用抛出异常的方式来确定问题点

解码器

将字节信息解码为消息: ByteToMessageDecoder 和 ReplayingDecoder

将消息进行特殊转换:MessageToMessageDecoder

根据行尾的字符进行处理(如换行符):io.netty.handler.codec.LineBasedFrameDecoder

Http 解码器:io.netty.handler.codec.http.HttpObjectDecoder

编码器

消息编码为字节:MessageToByteEncoder

消息编码为消息:MessageToMessageEncoder

编解码复合

字节转为消息:ByteToMessageCodec

消息转消息:`MessageToMessageCodec``

特殊的解决重用问题:CombinedChannelDuplexHandler

Character转回字节:CharToByteEncoder

通过SSL/TLS 强化Netty

Netty的OpenSSL/SSLEngine实现

Java:javax.net.ssl

Netty在此基础上使用:名为SslHandler ChannelHandler

会根据OpenSSL配置支持选择netty或者jdk 的实现

netty SSL 调用流程:

  1. 拦截入站数据
  2. 对数据解密,定向到入站端
  3. 通过sslHandler传递出站数据
  4. 数据进行加密,然后传出出站

Http完整处理流程

添加一个Http编解码器的示范代码

if (client) {  ← --  如果是客户端,则添加HttpResponseDecoder 以处理来自服务器的响应
pipeline.addLast("decoder"new HttpResponseDecoder()); 
pipeline.addLast("encoder"new HttpRequestEncoder());   ← --  如果是客户端,则添加
HttpRequestEncoder以向服务器发送请求
} else {
pipeline.addLast("decoder"new HttpRequestDecoder());   ← -- 如 果 是 服 务 器 , 则 添 加
HttpRequestDecoder以接收来自客户端的请求
pipeline.addLast("encoder"new HttpResponseEncoder());  ← -- 如 果 是 服 务 器 , 则 添 加
HttpResponseEncoder以向客户端发送响应
}

压缩Http

客户端:HttpContentDecompressor

服务端:HttpContentCompressor

如果是jdk6版本以前,需要加入依赖 JZlib

<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
<version>1.1.3</version>
</dependency>

Websocket支持

核心handler:WebSocketServerProtocolHandler

注意:websocket是建立在Http协议的基础之上的,意味着不能直接使用websocket协议,必须事先经过升级

心跳检测:

心跳检测使用的频率比较高,这里给出一个代码案例:

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

    // ← -- 实现userEven t-Triggered()方法以发送心跳消息
    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){
                ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate())
                        .addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }else{
                // 如果不是进行传递
                super.userEventTriggered(ctx, evt);
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class)
                .group(eventLoopGroup)
                .localAddress(new InetSocketAddress(8888))
        .childHandler(new IdleStateHandlerInitializer());
        serverBootstrap.bind().sync();
    }

其他协议

基于分隔符的协议:

使用LineBasedFrameDecoder 进行分隔符的切割操作

他的扩展有下面几种:

Cmd CmdDecoder CmdHandler 
 CmdHandlerInitializer 
 ChannelInitializer

这五种对应的扩展

基于长度协议

LengthFieldBasedFrameDecoder

传输大型数据

ChunkedStream

序列化数据

jdk的序列化接口介绍:

  • CompatibleObjectDecoder
  • CompatibleObjectEncoder
  • ObjectDecoder
  • ObjectEncoder

基本满足了任何实现了序列化接口的序列化类

推荐的序列化API:

  1. JBoss
Marshalling

    比JDK快3倍,并且更为紧凑

  2. Protocol
Buffers

    谷歌开源的支持多语言的序列化API