java BIO、NIO、多路复用

BIO操作系统实现

1.调用socket 返回int(fd文件描述符)
2.bind(fd,8090)
3.listen(fd)
4.accept(fd,) = 5 (5代表客户端的连接),这个方法会阻塞
5.recv(5,)从5读取数据,会阻塞

任何语言,BIO,NIO和组件(nginx)开启socket都是这个流程

主线程可以clone出一个新的thread来处理读取请求.recv(5,)

BIO:

每线程,每连接,
优势:可以接受很多的连接,
问题:线程内存浪费;cpu调度消耗
根源:blocking 阻塞,accept和recv
解决方案:NonBlocking 非阻塞

NIO:

java中是New IO (selector,channel,byteBuffer)
操作系统是NonBlocking IO

NIO优势:规避多线程问题
弊端:假设有1w个连接,只有一个发来数据,每循环一次,其实你必须向内核发送1W次的recv的系统调用,那么这里有9999次是无意义的,消耗时间和资源,

如果程序自己读取I/O,那么这个I/O模型,无论BIO,NIO,多路复用器(select,poll,epoll):同步IO模型


多路复用器:


优势:通过一次系统调用,把fds传递给内核,内核进行遍历,这种遍历减少了系统调用的次数

select:

使用fd_set结构体告诉内核同时监控那些文件句柄,使用逐个排查方式去检查是否有文件句柄就绪或者超时。该方式有以下缺点:文件句柄数量是有上线的,逐个检查吞吐量低,
每次调用都要重复初始化fd_set。
 select的几大缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大(解决方案:内核开辟空间保存fds)

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

poll:

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
@对于select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,
由内核修改后,再传出到用户空间中
select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,
只能监听 0~1023 的文件描述符
poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。


epoll:


epoll 提供了三个函数:

int epoll_create(int size);
建立一个epoll 对象,并传回它的fd

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
@事件注册函数,将需要监听的事件和需要监听的fd交给epoll对象,epoll对象内部维护了一棵红黑树,经过网卡的数据会产生网卡中断,放数据到内核的缓冲区,此时还做了一个延申
@处理:
@根据socket得到四元组,然后可以得到fd,再去红黑树里去找,并把找到的有状态的fd copy到链表里
@select和poll没有这个延申处理,所以每次调用select和poll都会循环查一遍内核缓冲区,看看传递的fds有没有相匹配的数据到达

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待注册的事件被触发或者timeout发生,这里实际一直去上一步链表中取

socket->3
bind(3,8090)
listen(3)
epoll_create() = 7
epoll_ctl(7,ADD,3,EPOLLIN) ≈ accept
epoll_wait()  这个方法是阻塞的
accept(3) = 8
epoll_ctl(7,ADD,8,EPOLLIN) ≈ read
epoll_wait() 这个方法可以返回多个事件

@epoll支持两种事件触发模式,分别是边缘触发(edge-triggered,ET)和水平触发(level-triggered,LT)。

这两个术语还挺抽象的,其实它们的区别还是很好理解的。
使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,
因此我们程序要保证一次性将内核缓冲区的数据读取完;
使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;
举个例子,你的快递被放到了一个快递箱里,如果快递箱只会通过短信通知你一次,即使你一直没有去取,它也不会再发送第二条短信提醒你,这个方式就是边缘触发;
如果快递箱发现你的快递没有被取出,它就会不停地发短信通知你,直到你取出了快递,它才消停,这个就是水平触发的方式。

  • 17
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值