Netty 4.0 中文文档

一 处理基于流的传输(Scoket Buffer的一点附加说明)

对于例如TCP/IP这种基于流的传输协议实现,接收到的数据会被存储在socket的接受缓冲区内。不幸的是,基于流的传输不是一个包队列而是一个字节队列。在一个开放的系统中,这意味着即使我们发送了两条消息分别包含在两个数据包里,接收方不会当作两条消息来对待,而是将其放在同一个字节独列中。因此,传输不能保证收到的消息与发送的消息一致。

对于时间客户端的例子,一个32位的int数据量非常小,一般不会被分片(链路层限制一个package大小一般为1500字节),但是问题是它确实有可能被分成多片,分片的概率随着网络的繁忙而增加。最简单的解决办法就是增加一个内部的累加缓冲,等累计满4个字节时再向上提交数据。

package io.netty.example.time;

import java.util.Date;

public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    private ByteBuf buf;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        buf = ctx.alloc().buffer(4); // (1)
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        buf.release(); // (1)
        buf = null;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf m = (ByteBuf) msg;
        buf.writeBytes(m); // (2)
        m.release();

        if (buf.readableBytes() >= 4) { // (3)
            long currentTimeMillis = (buf.readInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMillis));
            ctx.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

(1)ChannelHandler有两个存活期方法:handlerAdded()和handlerRemoved(),这两个方法允许我们自己构造一个初始化任务或结束任务。

(2)首先,所有的接受的数据先放到累计缓存里。

(3)然后,handler必须检查是否有了足够的data,如在本例中须足够4个字节,然后执行实际的业务逻辑。若数据不足,当更多的数据到达时,netty会再次执行channelReade()方法直到累计到4个字节。

 

第二个解决办法

当字段便多时,第一种解决方案会变得非常复杂且不可维护,可以通过向ChannelPipeline中增加多个ChannelHandler的方法,将一个大的ChannelHandler分解成多个模块来降低应用的复杂性。例如,

package io.netty.example.time;

public class TimeDecoder extends ByteToMessageDecoder { // (1)
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2)
        if (in.readableBytes() < 4) {
            return; // (3)
        }

        out.add(in.readBytes(4)); // (4)
    }
}

(1)ByteToMessageDecoder是一ChannelInboundHandler的实现类,可以非常容易的处理分片问题。

(2)当新的数据到达时,ByteToMessageDecoder讲数据存储在一个内在的累积buffer中,调用decode()方法进行处理

(3)decode()根据接收到的字节大小进行判定,若满4个字节则增加一个对象到list。

(4)如果decode()增加了个一个out的对象,意味着decoder编码成功。ByteToMessageDecoder会丢弃累积buffer中已经读过的部分。若out.add(null),decoder即停止。

由于ChannlePipeline中增加了一个handler,因此我们必须修改ChannelInitializer为:

b.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());
    }
});

如果你是个冒险者,你可能会尝试ReplayingDecoder。该handler进一步简化了decoder。

public class TimeDecoder extends ReplayingDecoder<VoidEnum> {
    @Override
    protected void decode(
            ChannelHandlerContext ctx, ByteBuf in, List<Object> out, VoidEnum state) {
        out.add(in.readBytes(4));
    }
}

另外关于decode的例子可以参考一下两个包

利用POJO代替ByteBuf

  目前我们看到的例子均是使用ByteBuf作为协议消息的数据结构。在这一节,我们将使用POJO来代替ByteBuf来改善Time协议的客户端和服务器。

  使用POJO的有点是十分明显的,通过分离出解析ByteBuf中数据的代码,handler会变得更加可维护和重用。在Time的客户端和服务器的例子中,我们只读32字节的integer且这并不是一个主要的直接应用ByteBuf的案例。然而,你会发现当你实现一个真正的协议时,做这样的分离是十分必要的。

首先,我们先定义一个新类型,UnixTime

package io.netty.example.time;

import java.util.Date;

public class UnixTime {

    private final int value;

    public UnixTime() {
        this((int) (System.currentTimeMillis() / 1000L + 2208988800L));
    }

    public UnixTime(int value) {
        this.value = value;
    }

    public int value() {
        return value;
    }

    @Override
    public String toString() {
        return new Date((value() - 2208988800L) * 1000L).toString();
    }
}

我们重新编写TimeDecoder来禅师一个UnixTIme。

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    if (in.readableBytes() < 4) {
        return;
    }

    out.add(new UnixTime(in.readInt()));
}

然后我们更新decoder,TimeClientHandler就不再使用ByteBuf了

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    UnixTime m = (UnixTime) msg;
    System.out.println(m);
    ctx.close();
}

是不是非常的简洁优雅。Server端同样如此。首先更新ServerHandler

@Override
public void channelActive(ChannelHandlerContext ctx) {
    ChannelFuture f = ctx.writeAndFlush(new UnixTime());
    f.addListener(ChannelFutureListener.CLOSE);
}

现在需要编写解码部分,encoder是ChannelOutbountHandler的实现类,将UnixTIme转化成下层的ByteBuf,编写encoder要比编写decoder简单的多,因为此时不必考虑tcp包分片的问题。

 

package io.netty.example.time;

public class TimeEncoder extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        UnixTime m = (UnixTime) msg;
        ByteBuf encoded = ctx.alloc().buffer(4);
        encoded.writeInt(m.value());
        ctx.write(encoded, promise); // (1)
    }
}

(1)这一行中有些十分重要的内容

  首先,我们将原始的ChannelPromis找原来的样子传输,以保证Netty在写入链路时能够正确的标记成功或失败。

  其次,并不调用ctx.flush().handler有一个默认的flush方法,若想每次写都flush则须:ctx.write(encoded,false,promise);或ctx.writeAndFlush(encode,promise);

转载于:https://www.cnblogs.com/legendary/p/3641282.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Netty 4.0 和 Netty 3.0 的区别有以下几点: 1. 支持的 Java 版本:Netty 4.0 支持的 Java 版本是 Java 7 及以上,而 Netty 3.0 只支持 Java 6 及以上。 2. 架构改变:Netty 4.0 在架构上做了很大的改变,使用了与 3.0 不同的组件和 API。 3. 性能提升:Netty 4.0 在性能上有了很大的提升,比 3.0 快得多。 4. 异步改进:Netty 4.0 对异步操作进行了改进,使用了更加高效的方式来处理异步任务。 5. 新的编解码器:Netty 4.0 新增了一些编解码器,支持更多的协议。 6. 更多的优化:Netty 4.0 还进行了更多的优化,比如支持堆外内存、增强的零拷贝等。 ### 回答2: Netty 4.0和Netty 3.0是Java网络编程框架Netty的两个版本。它们之间存在以下区别: 1. 性能提升:Netty 4.0相较于Netty 3.0在性能方面有显著提升。Netty 4.0经过了全面的优化和重构,引入了许多新的特性和改进,使其在处理网络通信时更加高效。 2. API的变化:Netty 4.0对API进行了重构和简化,使得编程接口更加易用和灵活。比如,Netty 4.0引入了ChannelPipelineHandler的概念,可以更加方便地添加和管理多个处理器。 3. 事件处理模型:Netty 4.0采用了新的事件处理模型,即Reactor模型,使得在高并发场景下的事件处理更加高效。而Netty 3.0采用的是NIO Selector模型。 4. 内存管理:Netty 4.0引入了更高级的内存管理机制,即ByteBuf和Recycler。ByteBuf提供了更加灵活的内存分配和回收方式,减少了内存的创建和销毁开销。Recycler可以对对象进行重用,减少了频繁的对象创建和垃圾回收开销。 5. 异常处理:Netty 4.0对异常处理进行了优化,提供了更加丰富的异常类型和处理方式。Netty 3.0在异常处理方面相对简单。 总的来说,Netty 4.0相较于Netty 3.0在性能、API设计、事件处理模型、内存管理和异常处理方面都有较大的改进和优化。因此,Netty 4.0更加适合开发高性能、高并发的网络应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值