Channel
文章目录
- Channel
- channelFuture
- **处理结果**
- **处理关闭**
- 为什么要异步
channel 的主要作用:
- close()
- 用于关闭channel
- closeFuture()
- 用于处理channel 的关闭close操作 之后的善后处理
- sync方法 是同步等待channel 的关闭
- 用addListener 方法是异步等待channel 的关闭
- pipline()
- 添加数据流的处理器
- write()
- 将数据写入
- 由于netty 的缓冲机制所以写入channel 后不会立马发出
- 需要达到一定数据量
- 需要执行flush 方法
- writeAndFlush()
- 将数据写入缓冲区并立即刷出
- 保证数据写入channel 后立马发出
channelFuture
就是用于处理连接建立后的结果(主线程同步等待连接建立成果然后分配连接建立后的工作还是给一个回调对象异步分配操作)
带future 或者promise 的类型都是和异步方法进行使用的
处理结果
客户端中 connect 方法的返回值是一个ChannelFuture 对象,如果不调用channelFuture.sync()的话后面的代码就无法正常执行:
分析:
/*connect 是异步非阻塞方法,main方法只是调用,真正执行connect的是NIO线程*/
.connect(new InetSocketAddress("localhost", 8080));
// future.sync();
Channel channel = future.channel();
channel.writeAndFlush("Hello,world");
为什么不调用ChannelFuture 对象的sync 方法就无法继续执行?
connect 是异步非阻塞,main线程发起调用之后,因为并不是主线程去做连接服务器,所有主线程会向下执行立即返回,真正执行底层connect 的是另一个线程,是一个NIO线程,如果此时没有调用sync 主线程无阻塞的会继续向下执行,下面的channel 对象会建立成功,但实际上的tcp连接并没有建立成功所以没有爆空指针错误,而是代码执行无效。
主线程因为异步非阻塞,其实并不知道channel 下的连接是否建立好,因为建立连接的是NIO线程而不是主线程
这里connection 的建立需要一些事件,必须阻塞直到连接建立成功,再去继续执行才能执行成功。
正确处理:
问题的关键在于如何让主线程等待EventLoop中的子线程的tcp连接建立后,主线程正确的拿到channel对象(一般带有future/promise 都是异步方法配套使用的)
- 方法一:channelFuture.sync() 同步处理结果
- 发起的连接建立请求的线程会等待去建立的线程,知道它建立好了才会继续向下运行
- 可以通过使用sync方法来同步结果
- 阻塞住当前线程(调用sync方法的线程),直到nio线程连接建立完毕
- 相当于主线程主动的去等待建立结果,实际执行针对连接建立后对象操作的还是主线程
- 方法二:channelFuture.addListener(回调对象) 异步处理结果
- 等结果的也不是主线程,而是将等待连接建立,等一系列的问题交给其他线程来处理
- 回调对象传递给NIO线程,NIO线程的连接建立好了,NIO线程就会主动去调用operationComplete 方法,进行主线程分给的剩下的操作
- 这里的ChannelFuture 对象和调用时是同一个
future.sync();
// Channel channel = future.channel();
// channel.writeAndFlush("Hello,world");
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
/*执行这里代码的还是NIO线程*/
Channel channel = channelFuture.channel();
channel.writeAndFlush("Hello,world");
}
});
处理建立连接完成之后操作的是NIO线程而不是主线程(处理连接建立的是netty 自己配置完成的NIO线程)
处理关闭
ChannelFuture closeFuture = channel.closeFuture();
需求:在channel 调用close方法之后对于连接进行善后的处理
close方法是一个异步操作,如果直接进行针对关闭后的逻辑操作,实际上此时Channel 并没有关闭,close 是需要时间的一个操作,优雅的处理关闭,就是要等待channel 真正的关闭之后再进行处理。
@Slf4j
public class CloseFutureClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup()
ChannelFuture channelFuture = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
/*用于查看Netty 内部的信息,需要配置logback.xml*/
nioSocketChannel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
nioSocketChannel.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost",8080));
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();
break;
}
channel.writeAndFlush(line);
}
},"start").start();
/**
* 处理关闭操作
* 1)获取CloseFuture 对象
* 2)同步处理关闭
* 3)异步处理关闭
*/
ChannelFuture closeFuture = channel.closeFuture();
/*方法一:同步关闭
System.out.println("Waiting Close!");
主线程会阻塞到此处,直到子线程调用了channel.close 方法之后才继续向下执行
closeFuture.sync();
System.out.println("处理关闭之后的操作");
*/
/*方法二:异步关闭 主线来实现一个回调函数,给需要的子线程再完成相应的条件后进行调用,而主线程继续向下执行*/
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
/*这里就不是主线程在打印了,而是NIO线程在打印*/
/*通过回调对象*/
log.info("关闭之后的操作");
/*关闭事件循环组,此时程序也就结束了
优雅关闭:拒绝接受新的任务,将未完成的任务运行完,没法的数据发完,而不是立刻停止
*/
group.shutdownGracefully(); //将Group中线程也关闭,并且释放资源
}
});
}
}
为什么要异步
为什么要用异步的方式?在主线程中建立连接,在子线程中处理连接?netty 使用异步提升了什么
通过流水线异步操作对于整体进行优化:
要点:
- 异步操作在这里主要提高的是吞吐量
- 提高单位时间内处理请求的个数
- 单线程没有异步提高效率,必须配合多线程,多核CPU才能发挥异步的优势
- 异步实际上没有缩短响应时间,反而是对响应时间有所增加
- 合理将任务进行拆分,是利用异步进行操作的关键