Channel 的主要作用:
close()
可以用来关闭 ChannelcloseFuture()
可以用来处理 Channel 关闭
sync()
方法作用是同步等待 Channel 关闭addListener()
方法是异步等待 Channel 关闭pipeline()
方法添加处理器write()
方法将数据写入flush()
方法将数据刷出writeAndFlush()
方法将数据写入并刷出带有
Future
和Promise
的类型都是和 异步方法配套使用,用来处理结果
调用 connect()
方法建立连接,该方法是异步非阻塞的,执行该方法的是 NIO 的线程,main 线程代码会继续向下运行。而建立连接需要一定的时间,继续向下执行的获取 Channel 和 发送数据的代码就不符合逻辑了。
@Slf4j
public static void main(String[] args) throws InterruptedException {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override // 连接建立后被调用
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 1、连接到服务器
// connect 方法是异步非阻塞。main 发起了调用,真正执行 connect 是 NIO 线程
.connect(new InetSocketAddress("localhost", 8888)); // 建立连接需要一定的时间,非阻塞就直接向下运行
// 将 sync() 注释掉,服务器没接收到 消息
// channelFuture.sync();
// 无阻塞的向下执行 channel(),
Channel channel = channelFuture.channel();
log.info("{}", channel);
// 2、向服务器发送数据
channel.writeAndFlush("Hello World!");
}
1、使用 sync()
方法来同步处理结果
channelFuture.sync(); // 阻塞住当前线程,直到 NIO 线程建立完毕
// 无阻塞的向下执行 channel(),
Channel channel = channelFuture.channel();
log.info("{}", channel);
// 向服务器发送数据
channel.writeAndFlush("Hello World!");
2、使用 addListener(回调对象)
方法来同步处理结果
channelFuture.addListener(new ChannelFutureListener() {
@Override // 在 NIO 线程连接建立好了,会调用 operationComplete
public void operationComplete(ChannelFuture future) throws Exception {
Channel channel = future.channel();
log.info("{}", channel);
channel.writeAndFlush("Hello World!");
}
});
问题
....
// 2、向服务器发送数据
Channel channel = channelFuture.sync().channel();
log.debug("{}", channel);
new Thread(()->{
Scanner scanner = new Scanner(System.in);
while (true){
String line = scanner.nextLine();
if ("q".equals(line)){
channel.close(); // 异步操作,需要一定的时间
log.debug("处理关闭之后的操作..."); // 不能在这里善后
break;
}
channel.writeAndFlush(line);
}
}, "input").start();
// log.debug("处理关闭之后的操作..."); // 不能在这里善后
使用 Netty 的日志调试
加上 LoggingHandler
:
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override // 连接建立后被调用
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); // <=====
ch.pipeline().addLast(new StringEncoder());
}
})
log4j2.xml
文件加上以下配置,配个 Lombox 的注解 @Slf4j
注解,就可以使用日志打印了:
...
...
获取 CloseFuture
对象
1、同步处理关闭
// 获取 CloseFuture 对象
ChannelFuture closeFuture = channel.closeFuture();
log.debug("waiting close....");
// 调用 channel.close(); 完成后才执行
closeFuture.sync();
log.debug("处理关闭之后的操作...");
2、异步处理关闭
// 2、异步处理 (和调用 close 方法的是同一个线程)
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
log.debug("处理关闭之后的操作...");
}
});
NioEventLoopGroup
关闭连接时的处理
需要将 group 提取变量
NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();
在关闭之后调用一下方法:
// 在一段时间内没有新的任务提交就关闭
nioEventLoopGroup.shutdownGracefully();
思考下面的场景,4 个医生给人看病,每个病人花费 20 分钟,而且医生看病的过程中是以病人为单位的,一个病人看完了,才能看下一个病人。假设病人源源不断地来,可以计算一下 4 个医生一天工作 8 小时,处理的病人总数是:4 * 8 * 3 = 96
经研究发现,看病可以细分为四个步骤,经拆分后每个步骤需要 5 分钟,如下
因此可以做如下优化,只有一开始,医生 2、3、4 分别要等待 5、10、15 分钟才能执行工作,但只要后续病人源源不断地来,他们就能够满负荷工作,并且处理病人的能力提高到了 4 * 8 * 12
效率几乎是原来的四倍
要点
没有提升总效率,提高的是单线程的吞吐量(单位时间内能够处理的请求数)。