前景提示:在用netty进行websocket客户端、服务端消息传递时,发现服务端使用netty的channel.writeAndFlush不是立马写回,而是等待后面几条消息后一起返回的,出现"netty消息发送成功至客户端"日志扎堆出现;监听回调代码:
channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(returnHuaWei))).addListener((ChannelFutureListener) future -> {
if (future.cause() != null) {
log.error("netty消息发送失败:", future.cause());
} else if (future.isSuccess()) {
log.info("netty消息发送成功至客户端 returnHuaWei:{}", JSON.toJSONString(StringUtil.jsonStringReplace(JSON.parseObject(JSON.toJSONString(returnHuaWei)), "audio", "")));
} else {
log.error("Message send failed for an unknown reason.");
}
});
原因:
该段代码的作用是根据当前线程是否在事件循环中,决定如何执行写操作:
- 如果该线程在事件循环线程中:直接执行写操作,并根据
flush
参数选择是否刷新数据。- 如果该线程不在事件循环线程中:则将写操作封装为异步任务 (
WriteTask
),并将该任务提交到事件循环中执行。
flush
的值控制着数据是否需要立即刷新,而promise
用于跟踪异步操作的状态。这段代码是为了确保在高并发环境下(如网络通信中),写操作能够正确地提交和执行,同时保证线程安全和异步执行。
在 Netty 中,所有的网络操作(比如写数据、读数据)都是异步执行的。为了高效地处理大量的请求,Netty 将这些任务分配到专门的 事件循环线程 上执行。
当你发起一个网络操作时,Netty 会判断当前的操作是:
- 在事件循环线程上执行:也就是说,当前的线程是负责处理网络任务的专用线程。这样可以直接在当前线程上执行网络操作。
- 不在事件循环线程上执行:这意味着你可能是在其他线程(比如工作线程或主线程)上发起操作,这时就需要把任务交给事件循环线程来执行。
我在主线程中通过 countDownLatch.await(2, TimeUnit.MINUTES)等待消息发送完成,countDownLatch释放时机为onMeessage接收到最后一帧数据时,通过finally来处理关闭;这是不合理的,会导致主线程一直阻塞,主线程阻塞可能导致线程资源竞争 或 I/O 操作延迟,则可能间接影响事件循环线程的任务调度;
解决方案为右侧代码,连接之后用countDownLatch.await等待,但是在连接打开的onOpen方法里面释放,从而让主线程结束,到onMessage的任务线程时,接收到最后一帧消息后关闭连接,这样就不会导致主线程阻塞;