Netty编程(三)—— Channel

这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

Channel常用方法

  • close() 可以用来关闭Channel
  • closeFuture() 用来处理 Channel 的关闭事件
    • sync 方法作用是同步等待 Channel 关闭
    • 而 addListener 方法是异步等待 Channel 关闭
  • pipeline() 方法用于添加处理器
  • write() 方法将数据写入
    • 因为缓冲机制,数据被写入到 Channel 中的缓冲区以后,不会立即被发送
    • 只有当缓冲满了或者调用了flush()方法后,才会将数据通过 Channel 发送出去
  • writeAndFlush() 方法将数据写入并立即发送(刷出)

为什么需要sync()

在前两篇博客中Netty编程(一)—— 初识Netty+超全注释 - 掘金 (juejin.cn) 以及 Netty编程(二)—— EventLoop - 掘金 (juejin.cn)多次出现客户端部分的代码,而在其中有一句sync()方法当时说是用来阻塞,在这一篇博客中首先解释一下这一句的必要性。

首先我们先给出来客户端的代码,和之前不用的是,这次不完全使用链式编码方式:

在这里插入图片描述

分析原因

如果我们将第31行的channelFuture.sync()注释掉,服务端是接收不到发送的”hello world”的,下面来分析一下原因:

这是因为建立连接(connect)的过程是异步非阻塞的,

  • 异步:调用connect的线程不关心结果
  • 非阻塞:调用完connect就可以继续向下执行,不同等结果)

connect方法由main线程发起,但真正执行的是NioEventLoopGroup其中的某一个nio线程,主线程会继续向下执行。如果不通过sync()方法阻塞主线程去等待连接真正建立,那么通过channelFuture.channel()拿到的 Channel 对象,并不是真正与服务器建立好连接的 Channel,因为此时还没有成功建立连接,也就没法将信息正确的传输给服务器端。

解决方法

解决上面的方法有两种:

  • 第一种方法就是一直使用channelFuture.sync()方法,阻塞住主线程,同步处理结果,等待连接真正建立好以后,再去获得 Channel 传递数据。使用该方法,获取 Channel 和发送数据的线程都是主线程
  • 第二种方法可以使用addListener(回调对象)方法,它用于异步获取建立连接后的 Channel 和发送数据,使得执行这些操作的线程是 NIO 线程(去执行connect操作的线程),nio线程连接建立好了以后就会调用回调对象的operationComplete方法,下面以一个例子来解释这个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码public class MyClient {
public static void main(String[] args) throws IOException, InterruptedException {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new StringEncoder());
}
})
// 该方法为异步非阻塞方法,主线程调用后不会被阻塞,真正去执行连接操作的是NIO线程
// NIO线程:NioEventLoop 中的线程
.connect(new InetSocketAddress("localhost", 8080));

// 当connect方法执行完毕后,也就是连接真正建立后
channelFuture.addListener(new ChannelFutureListener() {
//在nio线程连接建立好了以后,会调用
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
Channel channel = channelFuture.channel();
channel.writeAndFlush("hello world");
}
});
System.in.read();
}
}

当客户端与服务端成功建立连接后,会执行addListener方法中的operationComplete方法,其中他传入的参数channelFuture是调用addListener方法的channelFuture,之后执行其中的方法。值得注意的是,operationComplete方法是在NIO线程中执行的,即由事件循环组负责。

处理关闭

当我们要关闭channel时,可以调用channel.close()方法进行关闭。但是该方法是一个异步方法。真正的关闭操作并不是在调用该方法的线程中执行的,而是在NIO线程中执行真正的关闭操作

如果我们想在channel真正关闭以后,执行一些额外的操作,可以选择以下两种方法来实现:

  • 通过channel.closeFuture()方法获得对应的ChannelFuture对象,然后调用sync()方法阻塞执行操作的线程,等待channel真正关闭后,再执行其他操作
1
2
3
4
5
ini复制代码// 获得closeFuture对象
ChannelFuture closeFuture = channel.closeFuture();

// 同步等待NIO线程执行完close操作
closeFuture.sync();
  • 调用closeFuture.addListener方法,添加close的后续操作
1
2
3
4
5
6
7
8
9
csharp复制代码closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
// 等待channel关闭后才执行的操作
System.out.println("关闭之后执行一些额外操作...");
// 关闭EventLoopGroup,优雅关闭:拒绝连接,发送剩余数据,不是立刻停止
group.shutdownGracefully();
}
});

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%