文章目录
epoll模型原理图
- 调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
- 调用epoll_ctl向event_epoll对象的红黑树添加相关节点,并指明对哪些事件感兴趣[比如netty层面的accept事件,读写事件等]
- 红黑树中的每个节点会与网卡建立回调机制,当网卡发生相应感兴趣事件,则将相关事件对应的节点加入下图的list_head双向链表中
- 调用epoll_wait获取双向链表中[事件集合][netty层面的selector,select]
优点: 无需遍历红黑树,从双向链表中立刻获取到网卡所有被激活的channel,通过空间换时间
水平触发[LT]与边缘触发[ET]
- 水平触发.发生网络事件,不处理之后会继续触发
- 边缘触发 发生网络事件,不处理之后不会继续触发
reactor线程模型
单线程模型
多线程模型
主从线程模型
reactor在netty配置双eventLoopGroup的思考
参考笔者在其他博客下的留言
netty学习之旅系列文章
根据netty权威指南中对reactor线程模型的定义,我更倾向于netty使用了多线程模型而非主从模型,当然这只是理论性的知识点,实际还要是掌握netty自身的运行机制,只是理解不同,并无对错之分
NIO编程实战
关键代码 | 作用 |
---|---|
serverSocketChannel.configureBlocking(false) | IO多路复用必须是非阻塞模式 |
selectedKey.channel或read | 处理IO事件或者连接等感兴趣事件,由于底层模式为水平触发,不处理事件会导致事件每次select都会获取到 |
selectedKeys.remove() | 原因如下 |
因为 select 在事件发生后,就会将相关的 key 放入 selectedKeys 集合,但不会在处理完后从 selectedKeys 集合中移除,需要我们自己编码删除。例如
- 第一次触发了 ssckey 上的 accept 事件,没有移除 ssckey
- 第二次触发了 sckey 上的 read 事件,但这时 selectedKeys 中还有上次的 ssckey ,在处理时因为没有真正的 serverSocket 连上了,此时accept返回null
public static void startServer() throws IOException {
1、获取Selector选择器
Selector selector = Selector.open();
2、获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
3.设置为非阻塞 想要绑定到选择器上面的通道必须为非阻塞模式 否则会报错
serverSocketChannel.configureBlocking(false);
4、绑定连接
serverSocketChannel.bind(new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_PORT));
Logger.info("服务器启动成功");
5、将通道注册到选择器上,并注册的IO事件为:“接收新连接”
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
6、轮询感兴趣的I/O就绪事件(选择键集合)
while (selector.select() > 0) {
7、获取选择键集合
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
8、获取单个的选择键,并处理
SelectionKey selectedKey = selectedKeys.next();
9、判断key是具体的什么事件
if (selectedKey.isAcceptable()) {
10、若选择键的IO事件是“连接就绪”事件,就获取客户端连接
由于是水平触发这里必须处理,否则下次还需获取到
SocketChannel socketChannel = /* selectedKey.channel() */serverSocketChannel.accept();
11、切换为非阻塞模式
socketChannel.configureBlocking(false);
12、将该通道注册到selector选择器上
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectedKey.isReadable()) {
13、若选择键的IO事件是“可读”事件,读取数据
SocketChannel socketChannel = (SocketChannel) selectedKey.channel();
14、读取数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int length = 0;
while ((length = socketChannel.read(byteBuffer)) >0) {
byteBuffer.flip();
Logger.info(new String(byteBuffer.array(), 0, length));
byteBuffer.clear();
}
socketChannel.close();
}
15、移除选择键
selectedKeys.remove();
}
}
关闭连接
serverSocketChannel.close();
}
总结
- 本文介于netty源码分析之前,主要使读者了解selectorIO复用的底层模型epoll[省略select和poll模型]
- 简介了水平触发和边缘触发,netty[epoll]基于水平触发,发生的事件必须处理,否则会一直触发
- 通过介绍epoll的底层数据结构,阐述空间换时间后epoll高性能之密
- 介绍reactor线程模型,netty本身可以通过参数配置在单线程,多线程,主从之前切换
- 介绍NIO编程易错点