1) 用户程序自定义的普通任务 [举例说明]
2) 用户自定义定时任务
3) 非当前 Reactor 线程调用 Channel 的各种方法
例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。最终的 Write 会提交到任务队列中后被异步消费
- 在NettyServerHandler类用模拟1个很费事的操作
# 添加如下代码
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
}
}
# 测试
# 启动服务端,再启动客户端
# 服务端控制台打印如下
.....服务器 is ready...
server ctx =ChannelHandlerContext(NettyServerHandler#0, [id: 0xe4f5ca0b, L:/127.0.0.1:6668 - R:/127.0.0.1:56416])
客户端发送消息是:hello, server: (>^ω^<)喵
客户端地址:/127.0.0.1:56416
# 客户端控制台打印如下
客户端 ok..
client ChannelHandlerContext(NettyClientHandler#0, [id: 0xc6a008e7, L:/127.0.0.1:56416 - R:/127.0.0.1:6668])
服务器回复的消息:hello, 客户端~(>^ω^<)喵2
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵1
服务器的地址: /127.0.0.1:6668
# 客户端连接到服务端后,阻塞了5秒才打印 《客户端~(>^ω^<)喵2》,最后后服务端返回《客户端~(>^ω^<)喵1》
# 添加如下
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据实际(这里我们可以读取客户端发送的消息)
/*
1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
2. Object msg: 就是客户端发送的数据 默认Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交该channel 对应的
//NIOEventLoop 的 taskQueue中,
//解决方案1 用户程序自定义的普通任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
});
// 使用方案1,再处理1个任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵3", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
});
//解决方案2 : 用户自定义定时任务 -》 该任务是提交到 scheduleTaskQueue中
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵4", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
}, 5, TimeUnit.SECONDS);
}
}
# 测试
# 启动服务端,再启动客户端
# 服务端控制台打印如下
.....服务器 is ready...
server ctx =ChannelHandlerContext(NettyServerHandler#0, [id: 0xafd22374, L:/127.0.0.1:6668 - R:/127.0.0.1:56738])
客户端发送消息是:hello, server: (>^ω^<)喵
客户端地址:/127.0.0.1:56738
# 客户端控制台打印如下
客户端 ok..
client ChannelHandlerContext(NettyClientHandler#0, [id: 0xaf11456f, L:/127.0.0.1:56738 - R:/127.0.0.1:6668])
服务器回复的消息:hello, 客户端~(>^ω^<)喵1
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵2
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵3
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵4
服务器的地址: /127.0.0.1:6668
# 测试结果:这次,任务2、任务3、任务4并没有影响任务1的执行,任务1先执行后,任务2阻塞5秒后执行,任务3阻塞5秒后执行,任务4阻塞5秒后执行
# NettyServer类中可以获取到socketchannel的hash码
# 可以使用一个集合管理SocketChannel,再推送消息时,可以将业务加入到各个channel对应的NIOEventLoop的taskQueue或者scheduleTaskQueue
@Override
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("客户socketchannel hashcode=" + ch.hashCode());
ch.pipeline().addLast(new NettyServerHandler());
}
# 在NettyServerHandler中同样可以拿到channel的hash码
System.out.println("channel code=" + ctx.channel().hashCode());
# 完整代码中查看
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交该channel 对应的
//NIOEventLoop 的 taskQueue中,
//解决方案1 用户程序自定义的普通任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵3", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
});
//解决方案2 : 用户自定义定时任务 -》 该任务是提交到 scheduleTaskQueue中
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵4", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}
}, 5, TimeUnit.SECONDS);
}
}
1) Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。
2) NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个selector,用于监听绑定在其上的 socket 网络通道。
3) NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由IO 线程 NioEventLoop 负责
• NioEventLoopGroup 下包含多个 NioEventLoop
• 每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
• 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
• 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
• 每个 NioChannel 都绑定有一个自己的 ChannelPipeline