尚硅谷netty课程
netty 文章
https://www.bbsmax.com/A/WpdKYYemJV/
https://www.freesion.com/article/2828427687/
https://www.cnblogs.com/xuewangkai/p/11158576.html (select/poll/epoll对比)
这里学习一下Netty中的异步操作与任务队列
同步与异步
NIO本质上也是同步的,Netty框架是基于NIO的,不过Netty自己完成了异步操作
同步与异步
同步:一个进程执行任务时,会一直等待到当前任务完成才会进行下一个任务
异步:是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理
异步的效率明显会优于同步,网页的AJAX技术就是异步执行的
NIO虽然是多路复用,但是当线程执行某一个操作,其他操作都要进行等待
ChannelFuture为异步操作
netty nio
Bio : 用流读取
inputStream.read
Nio: 用channel读取
FileInputstream,getChannel().read()
Selector类的方法:
select() 返回当前有事件发生的selectkey,至少要有一个IO事件发生否则会导致成为阻塞状态
selectkey() 返回所有的selectkey
Selector.select() //阻塞方法
Selector.keys() 返回所有的key
Selector.selectedKeys() 返回当前注册事件的key
Netty模型
NioEventLoop包含selector和taskQueue,1个NioEventLoop对应1个selector
ctx可以得到pipeline,pipeline和channel互相包含
NioEventLoop包含selector和taskQueue,与模型框图对应
第三种例子没讲清楚,需要搜索继续学习
byteBuffer.rewind(); //倒带 position = 0 mark 作废
第2、4条第一句话注意
netty中所有的io操作都是异步的
channel出站、入站
编码器是outBoundHandler
解码器是InBoundHandler
netty源码分析
bind()方法
bind–>doBind()–>initAndRigister()–>init() -->init(Channel channel)—拿到serverBootStrap中的属性值加在channel的pipeline中,将EventLoop注册到channel立即返回channelFuture结果(无论成功或者失败),后续操作系统会查询channelFuture最终结果(EventLoop注册到channel结果)返回;
bind–>doBind()–>doBind0() 在上一步的注册完成后,bind端口或者ip/域名+端口,并附带返回channelFuture结果
ChannelInitialize中泛型为SocketChannel的原因
fireChannelRead 个人理解是转到下一个Channel 中去read
pipeline为双头队列
入站(栈) 从socketchannel 获取给到pipeline
出站(栈) 从pipeline 获取给到socketchannel
netty io异步执行方法
注意如下直接在channelRead中处理有可能导致线程阻塞,耗时的方法需要添加在异步操作中,如下边1和2
异步方案1代码:相当于把整个hanler类对象加入线程池,开销和占用大
//创建业务线程池
//这里我们就创建2个子线程
static final EventExecutorGroup group = new DefaultEventExecutorGroup(2);
ChannelPipeline p = ch.pipeline(); //ch为SocketChannel的对象
p.addLast(group, new EchoServerHandler());
或者
//在handler类的方法中将任务提交到 group线程池,相当于单独把某个方法加入线程池,开销和占用小
static final EventExecutorGroup group = new DefaultEventExecutorGroup(2);
group.submit(new Callable() {
@Override
public Object call() throws Exception {
//接收客户端信息
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String body = new String(bytes, "UTF-8");
//休眠10秒
Thread.sleep(10 * 1000);
System.out.println("group.submit 的 call 线程是=" + Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
return null;
}
});
异步方案 2:
static final EventExecutorGroup group = new DefaultEventExecutorGroup(2);
//EchoServerHandler()加入到group中的一个线程
p.addLast(group, new EchoServerHandler());
ChannelInitializer的中EchoServerHandler的方法中加入如下
//普通方式
//接收客户端信息
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String body = new String(bytes, “UTF-8”);
//休眠10秒
Thread.sleep(10 * 1000);
System.out.println(“普通调用方式的 线程是=” + Thread.currentThread().getName());
ctx.writeAndFlush(Unpooled.copiedBuffer(“hello, 客户端~(>ω<)喵2”, CharsetUtil.UTF_8));
- 将任务加入ctx下的pipeline下的channel下的eventLoop下的SchduledTaskQueue中
3.非当前REACTOR线程调用CHANNEL的方法
例如:
在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。最终的 Write 会提交到任务队列中后被异步消费
可以使用一个集合管理所有的SocketChannel,当我们想要推送消息时,将业务加入到该Channel对应的NioEventLoop的TaskQueue或者ScheduleTaskQueue
其实操作与上面两种类似
服务器使用集合保存管理所有的Channel,ctx.channel()获得想要的通道, ctx.channel().eventLoop().schedule()或者.execute()操作调用Write方法向该用户推送消息
(其实我们前面已经往客户端推送消息了,这里只是对推送的通道做一定的限制)
上述截图普通任务方案1和定时2代码如下(非异步)
//解决方案1 用户程序自定义的普通任务,这种方法式和几个System.out.println(“EchoServer Handler 的线程是=” + Thread.currentThread().getName());打印出来显示是同一个线程,不是异步,这种方案不行
System.out.println(“EchoServer Handler 的线程是=” + Thread.currentThread().getName());
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("EchoServer Handler 的线程是=" + Thread.currentThread().getName());
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());
System.out.println("EchoServer Handler 的线程是=" + Thread.currentThread().getName());
} 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);