Netty编程(三)—— Channel
Channel常用方法
- close() 可以用来关闭Channel
- closeFuture() 用来处理 Channel 的关闭事件
- sync 方法作用是同步等待 Channel 关闭
- 而 addListener 方法是异步等待 Channel 关闭
- pipeline() 方法用于添加处理器
- write() 方法将数据写入
- 因为缓冲机制,数据被写入到 Channel 中的缓冲区以后,不会立即被发送
- 只有当缓冲满了或者调用了flush()方法后,才会将数据通过 Channel 发送出去
- writeAndFlush() 方法将数据写入并立即发送(刷出)
为什么需要sync()
在前两篇博客中《Netty编程(一)—— 初识Netty+超全注释》以及 《Netty编程(二)—— EventLoop》多次出现客户端部分的代码,而在其中有一句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方法,下面以一个例子来解释这个方法:
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真正关闭后,再执行其他操作
// 获得closeFuture对象 ChannelFuture closeFuture = channel.closeFuture(); // 同步等待NIO线程执行完close操作 closeFuture.sync();
-
调用closeFuture.addListener方法,添加close的后续操作
closeFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { // 等待channel关闭后才执行的操作 System.out.println("关闭之后执行一些额外操作..."); // 关闭EventLoopGroup,优雅关闭:拒绝连接,发送剩余数据,不是立刻停止 group.shutdownGracefully(); } });