原文地址:Netty中的异步任务TaskQueue – 编程屋
接上一篇博客:
前面我们说过NioEventLoopGroup相当于一个事件循环组,这个组中包含多个事件循环,每一个事件循环都是EventLoopGroup,而EventLoopGroup中有2个重要的属性:Selector和TaskQueue。
事件循环(NioEventLoop) 的过程中,我们会在 pipline 中调用 Handler 来处理我们的业务,那么假如在某一个 Handler有一个长时间的操作,这就势必会造成 pipiline 的阻塞,这是我们就可以将这个耗时的处理提交到 TaskQueue 进行异步的执行。
问题代码演示:
服务端代码:
package com.liubujun.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @Author: liubujun
* @Date: 2023/2/6 17:05
*/
public class NettyServer {
public static void main(String[] args) throws Exception {
//1.创建2个线程组bossGroup和workerGroup
//2 bossGroup只是处理连接请求,workerGroup真正的和客户端进行业务处理
//3 两个都是无限循环
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class) //使用nioSocketChannel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128)//设置线程队列得到连接
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
});//给workerGroup的EventLoop对应的管道设置处理器
System.out.println("....服务器 is ready...");
//绑定一个端口并且同步,生成了一个ChannelFuture对象
//启动服务器(并绑定端口)
ChannelFuture cf = bootstrap.bind(6668).sync();
//对关联通道进行监听
cf.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端处理器代码:在channelRead()方法中睡眠5秒充当耗时业务代码
package com.liubujun.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import java.time.LocalDateTime;
/**
* @Author: liubujun
* @Date: 2023/2/8 13:25
*/
/**
* 说明:
* 1 说明自定义一个handle 需要继承netty 规定好的某个HandlerAdapter
* 2 这时继承一个Handleer,才能称之为一个Handler
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据的事件(这里读取客户端发送的消息)
/**
*
* @param ctx ChannelHandlerContext ctx:上下文对象,含有管道pipeline,通道channel,地址
* @param msg 就是客户端发送的消息
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(" server ctx = "+ctx);
ByteBuf buf = (ByteBuf)msg;
System.out.println("客户端发送的消息:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:"+ctx.channel().remoteAddress());
Thread.sleep(5000); //模拟耗时代码
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer(LocalDateTime.now()+" :hello,客户端~",CharsetUtil.UTF_8));
}
//处理异常,一般需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户端和客户端处理器代码在上篇博客,此处就不在粘贴
结果:此时肯定是5秒耗时代码完成之后,客户端才接受消息,输出“hello,客户端~”
解决方案:用户自定义普通代码
服务端处理器代码:
package com.liubujun.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import java.time.LocalDateTime;
/**
* @Author: liubujun
* @Date: 2023/2/8 13:25
*/
/**
* 说明:
* 1 说明自定义一个handle 需要继承netty 规定好的某个HandlerAdapter
* 2 这时继承一个Handleer,才能称之为一个Handler
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据的事件(这里读取客户端发送的消息)
/**
*
* @param ctx ChannelHandlerContext ctx:上下文对象,含有管道pipeline,通道channel,地址
* @param msg 就是客户端发送的消息
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(" server ctx = "+ctx);
ByteBuf buf = (ByteBuf)msg;
System.out.println("客户端发送的消息:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:"+ctx.channel().remoteAddress());
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.writeAndFlush(Unpooled.copiedBuffer(LocalDateTime.now()+":第一条异步消息",CharsetUtil.UTF_8));
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 值得注意的是:write 操作只是将消息存入到消息发送环形数组中,并没有真正被发送,只有调用flush 操作才会被写入到Channel 中,发送给对方。
ctx.writeAndFlush(Unpooled.copiedBuffer(LocalDateTime.now()+":第二条异步消息",CharsetUtil.UTF_8));
}
});
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer(LocalDateTime.now()+" :hello,客户端~",CharsetUtil.UTF_8));
}
//处理异常,一般需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户端打印:
![](https://img-blog.csdnimg.cn/img_convert/d1ff32f840155a6cf0f5651595877c0c.png)
发现此时是在先输出了hello,客户端,然后在输出了异步消息。
以上只是部分内容,为了维护方便,本文已迁移到新地址:Netty中的异步任务TaskQueue – 编程屋