write和writeAndFlush

write是写入消息,writeAndFlush是写入消息并发送

ctx.writeAndFlush(msg); 等价于 ctx.write(msg); ctx.flush();

Channel和 ChannelHandlerContext调用 write()/ writeAndFlush()

Channel:从整个 Pipeline 的“尾部”发数据,也就是会经过所有Handler
ChannelHandlerContext:从“当前 Handler 的位置”发数据,只经过当前Handler及当前Handler前面的Handler

不要在 EventLoop 线程里 sync / await

EventLoop 线程是“干活的线程”,sync() / await() 是“等结果的操作”;
在 EventLoop 里等待 EventLoop 自己完成任务,必然产生死锁。

Promise

概念

Promise = 可写的 ChannelFuture

ChannelFuture:只能查看异步结果,不可主动完成

Promise:可以主动设置结果,同时可以注册 Listener 等待或回调

创建

在 Netty 中,绝大多数情况下都是使用 DefaultPromise 作为 Promise 的实现类。

DefaultPromise promise = new DefaultPromise<>(eventExecutors);

这里的 Integer 表示 Promise 中保存的结果类型 。

使用都是我们自己给promise赋值,而不是使用promise来接收值

方法

promise.setSuccess(result);      // 成功完成
promise.setFailure(cause);       // 失败完成

promise.isSuccess();     // 是否成功
promise.isDone();        // 是否已完成(成功 or 失败)
promise.cause();         // 失败原因(成功时为 null)

promise.getNow();  // 非阻塞,没完成返回 null,失败不抛异常
promise.get();     // 阻塞等待完成,失败会抛异常

//监听完成(核心)
promise.addListener(future -> {
    if (future.isSuccess()) {
        Object result = future.getNow();
    }
});

promise.sync();   // 等待完成,失败会抛异常
promise.await();  // 等待完成,不抛异常

promise.get();//阻塞期间,如果线程被中断 → 抛 InterruptedException
promise.sync();//阻塞期间,如果线程被中断 → 抛 InterruptedException
promise.await();//阻塞期间,如果线程被中断 → 抛 InterruptedException
promise.awaitUninterruptibly();//阻塞但忽略中断,不抛异常;失败也不抛异常  

ByteBuf

注意

ByteBuf 既是 Netty Handler 之间的底层数据载体,也是客户端与服务器之间网络通信的最终数据格式。任何发送到网络的数据,最终都会被编码成 ByteBuf。

接收消息时的 ByteBuf,是 Netty 在 EventLoop 的 IO 读阶段、在数据进入 pipeline 之前创建的;Handler 只是接收并处理它,而不是创建它。

释放

ChannelInboundHandlerAdapter:默认不会释放ByteBuf,但是pipeline末尾有 TailContext,会自动释放 ByteBuf。
默认传给下一个 handler 不需要 retain

SimpleChannelInboundHandle:channelRead0() 处理完立即 release ByteBuf。
如果要继续传递:ctx.fireChannelRead(msg.retain()); // 保留引用计数

TailContext 是 pipeline 的最后一个节点
入站消息如果 没人消费,TailContext 会自动调用 ReferenceCountUtil.release(msg)
这保证了即使你忘记释放,Netty 也不会泄漏内存

解决半包粘包

FixedLengthFrameDecoder固定长度

//把收到的 ByteBuf 按固定长度拆包,每 8 个字节作为一条消息传递给下一个 Handler
//拆包器要放在 StringDecoder / 自定义 Handler 之前
ch.pipeline().addLast(new FixedLengthFrameDecoder(8));

LineBasedFrameDecoder固定分隔符

//服务端加入,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

//LineBasedFrameDecoder(int maxLength)
//LineBasedFrameDecoder(int maxLength, boolean stripDelimiter, boolean failFast)
//maxLength			一行最大长度,如果超过会抛 TooLongFrameException
//stripDelimiter	是否去掉行尾的换行符(默认 true)                   
//failFast			超过最大长度是否立即抛异常(true = 立即抛,false = 等到行尾)

//缺点,处理字符数据比较合适,但如果内容本身包含了分隔符(字节数据常常会有此情况),那么就会解析错误

LengthFieldBasedFrameDecoder预设长度

//构造方法
LengthFieldBasedFrameDecoder(
    int maxFrameLength,
    int lengthFieldOffset,
    int lengthFieldLength,
    int lengthAdjustment,
    int initialBytesToStrip
)
//maxFrameLength	消息最大长度,如果超过会抛 TooLongFrameException
//lengthFieldOffset	长度字段在消息中的偏移量(从 0 开始)(1 → 长度字段从消息的第 2 个字节开始)
//lengthFieldLength	长度字段占用字节数(1,2,3,4,8 都可以)
//lengthAdjustment	长度字段指的长度是否包括长度字段本身或消息头,需要调整
    //0表示消息长度字段只算消息体
    //-4表示消息长度字段减去4,才是消息体长度
//initialBytesToStrip	传给下一个 Handler 时,是否去掉长度字段前多少字节(通常去掉长度字段本身)

DelimiterBasedFrameDecoder指定分隔符

//构造方法
DelimiterBasedFrameDecoder(
    int maxFrameLength,
    boolean stripDelimiter,
    ByteBuf... delimiters
)
//maxFrameLength	消息最大长度,超过抛 TooLongFrameException
//stripDelimiter	是否去掉分隔符(true = 去掉)
//delimiters	一个或多个 ByteBuf 分隔符(支持多个分隔符)
    
//Netty 提供 Unpooled.copiedBuffer() 来生成分隔符:   
ByteBuf delimiter = Unpooled.copiedBuffer("\n", Charset.forName("UTF-8"));    
  
//例
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, true, Unpooled.copiedBuffer("\n", Charset.forName("UTF-8")));
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐