BIO和NIO以及零拷贝
2021-01-12 14:27:42 0 举报
BIO和NIO及零拷贝
作者其他创作
大纲/内容
无序客户端的链接是无序的,会导致多个客户端的计算也是无序的
linsten监听端口
new Socket() 文件描述符fd6
并行
时间复杂度O(n)
网卡
用户空间
计算client2
epoll空轮训,cpu 100% bugJDK若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU使用率100%在NIO的selector中,即使是关注的select轮询事件的key为0的话,NIO照样不断的从select本应该阻塞的情况中wake up出来Netty的解决办法1.对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数2.若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。3.重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。
kernel空间
client
非阻塞读取
listen监听端口
1.系统调用
连接消息传输事件
从连接中读取消息
与server建立连接或消息交互触发fd变更
就绪的fd列表
new thread()
将fd7中监听的事件列表中变更的时间挪到该空间
client2
socket create返回socket文件描述符
返回有时间变更的文件描述符
fd.register(selector)
accept
bind
read client2
阻塞读取
读取磁盘传输数据
Iterator<SelectionKey>
client1
epoll_wait
不需要每次copy所有的文件描述符到内存遍历。连接建立的时候copy一次,且无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。无最大连接数限制。可以一个cpu处理连接和消息事件,另一个cpu处理io业务,充分利用资源
磁盘
Selector.open()selector文件描述符 fd7
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间
mmap()存储映射
kernel
新连接事件返回新连接的文件描述符fd8
BIO
缺点:accept获取建立连接和从连接中read读取数据都是阻塞的。每连接都需要开启线程处理业务,否则数据读取时会阻塞新连接的建立。线程资源是有限的,JDK1.5+线程默认分配内存大小为1M,1000个线程即需要耗费1G的栈空间
clone克隆线程
创建多路复用selector返回文件描述符fd7
configureBlocking(false)
while(true)循环遍历是否有连接
NIO
epoll_create是epoll_create1老版本的实现,已废弃
bind 6379
write client2
负载均衡有序的操作路由到同一个链接
redis io线程
redis 5.x
文件的映射区域
read
内存
业务处理
redis 6.x io threads
read从建立连接的文件描述符读取消息
write client1
new Socket()
将socket的文件描述符fd6添加到多路复用的fd7中
将socket的文件描述符注册到selector中
server
len
selector.select
while(true)循环
epoll
read client1
有新连接进入
将selector 所有注册的文件描述符传入内核遍历
将socket的文件描述符fd6添加到多路复用的fd7中epoll_ctl add
accept客户端连接进来,返回连接的文件描述符
fd8
select poll
零拷贝
创建多路复用selector返回文件描述符fd7epoll_create1
可以在一个线程内处理多个socket的io,每次处理有事件变化的连接。如果循环1w次socket的连接才有一个连接的数据传递,很多无用循环缺点:select每次需要将所有的文件描述符集合传到内核遍历,筛选出有事件变化的fd返回。内存复制开销大,轮询遍历效率较低,同时每个进程可监视的fd数量被限制,即能监听端口的大小有限(32位机默认是1024个。64位机默认是2048)
3.内核buffer复制
硬盘->内核->socket缓冲区(内核)->协议引擎
4.数据写出到网络连接中socket缓冲区copy到相关协议引擎
进程地址空间
key的类型
4.用户处理完数据,响应结果
off
2.数据读取,从磁盘copy到内核空间
有文件描述符有变更通知
redis 单线程worker
计算client1
5.数据写出到网络连接中
write
#开启socket 返回文件描述符 6socket = fd 6#绑定6379端口bind 6379 #监听listenwhile(true){#阻塞获取连接,没有连接一直会阻塞accept(fd) = 9#有链接进入,返回文件描述符 9new Thread( e -> {#开启一个线程,处理获取连接的信息#阻塞读取消息,如果没有消息一直阻塞fd9 .read()}}
cpu0处理
select操作内核遍历fd7中所有的文件描述符,返回有时间变更的文件描述符
映射文件
select是数组存放文件描述符,有大小限制。poll基于链表,无最大连接数限制
read write
socket.register(selector)
fd9
设置非阻塞连接
3.将数据从内核空间拷贝到用户空间
cpu1处理
2.数据读取,从网络连接中cpoy到内核空间
直接返回就绪的fd列表
文件
硬盘->内核->用户->socket缓冲区(内核)->协议引擎
将新建立的连接设置成非阻塞同时将文件描述符添加到selector上epoll_ctl add
将新建立的连接设置成非阻塞同时将文件描述符添加到selector上
0 条评论
下一页
为你推荐
查看更多