网络IO模型核心原理介绍

 

1 socket四元组与其对应的文件描述符的关系介绍

客户端和服务器建立连接是在各自的内核中完成的,建立连接之后产生的socket四元组也分别保存在各自的内核中,并且客户端和服务器都会将socket四元组映射成一个文件描述符供用户空间使用。下面是简图

图1 socket四元组与其对应的文件描述符关系图

2 BIO模型

存在的问题:

如果连接上来的客户端一直不发送数据,就阻塞了,那么后面新的客户端的连接就得不到处理。退一步,即便是连接上来的客户端发送了数据,但是对数据的处理逻辑复杂,耗时多,那么后续新的客户端连接也得不到及时处理。

因此势必要使用多线程,上面的例子采用极端的,一个连接开辟一个线程,那么线程的调度,内存的占用,等待调度的等待时间造成的延迟都会造成问题。

因此势必要使用线程池,使用线程池时的问题是,线程池创建多少个线程?C10k问题依然存在,如果线程数太少,那么等待调度的时间就越长,响应不及时,隔靴搔痒不能解决问题;线程数过多,内存不够用,且线程切换的开销依然不容忽视。

3 Java的NIO模型介绍

NIO有两层含义,在jdk中是new IO的意思,通过FileChannel和SocketChannel的方式进行IO;

在Linux OS层面是指Non Blocking非阻塞的意思,也就是说Linux内核提供了非阻塞的系统调用。

将上面的代码抽象图形,如图2所示。

图2 NIO模型

相对于传统的BIO,Java提供了新的IO接口,通过channel的方式与内核的非阻塞的IO系统调用打交道。解决了BIO模型中阻塞造成的问题。但是NIO模型依然存在很大的问题:

对于C10K问题,每循环一遍都有O(n)的复杂度在调用Linux Kernel提供的recv方法(c.read(buffer)),要不停地在内核态和用户态之间切换;但是对于这10k个连接,可能能读到数据的客户端连接很少,造成极大的资源浪费;由于是单线程串行化运行,下一个客户端连接进来必须等待for循环处理完这10K个连接的recv系统调用。

总结起来最大弊端是在于:频繁无用的 read系统调用

3.1系统调用对性能的损耗

在正式介绍多路复用器之前,有必要介绍一下“系统调用”对性能的损耗,否则怎么能体现多路复用器在IO上的优势呢~

用户进程要访问硬件,必须经过内核同意。比如要读取磁盘、网卡上的数据时,必须通过系统调用的方式。系统调用的开销远大于普通的方法调用,一方面是因为内核需要检查用户传递参数是否合法,更主要的是要进行用户态和内核态的切换产生的开销。

举例: Java中为什么BufferedOutputStream比FileOutputStream快?

用BufferedOutputStream和FileOutputStream分别对字符串“ashdfkjahsdkfjahskldfn”进行1000000次写入,比较它们所用的时间。

3.2 运行结果

3.3 原因分析

查看BufferedOutputStream的构造方法:

发现BufferedOutputStream默认使用了8k的缓冲区,实际上这个缓冲区的作用减少了写文件(write)系统调用的次数,默认只有当buf满了才产生一次系统调用, 而FileOutputStream没有使用缓冲区来减少系统调用的次数,因此BufferedOutputStream比FileOutputStream快。

4 Select/Poll/Epoll多路复用器模型

图3 多路复用器模型抽象

4.1 select和poll模型

主要用来解决NIO模型中频繁的无用系统调用问题。application通过一次系统调用到多路复用器上,由多路复用器返回每个IO通道上是否可读/写的状态给app,然后app针对可以读/写的IO通道进行相应的recv/write系统调用。

要补充的是,select和poll每次调用传递关注的多个文件描述符(fds),通过1次系统调用,由内核遍历fds,修改fds中的每个fd的状态,并将可读/写的fd并返回给app。

4.2 epoll模型

select和poll主要存在以下2个问题:

1)每次调用都会传递fds,存在重复传递fds的拷贝问题;

2)每次内核被调用之后,针对这次调用,都会触发一次fds的全量遍历复杂度。

epoll通过空间换时间的思想,在内核中新开辟了两块内存空间,1是用来保存该进程所有的socket的文件描述符的红黑树,2是保存所有有数据可读(有状态)的socket文件描述构成的链表。当要需要查询有状态的通道时,不再传递一批文件描述符,也不需要全量遍历内核中的fds,而是直接返回链表中的所有的文件描述符。 由3个Linux内核函数支撑起epoll功能,分别是epoll_create,epoll_ctl,epoll_wait.

- epoll_create: 产生一个epoll专门的文件描述符epfd,epfd维护的是一棵红黑树;

- epoll_ctl: 可以向epfd维护的红黑树中添加fd、修改fd、删除fd;

- epoll_wait: epoll_wait系统调用可以立即获得当前调用“有状态的”所有文件描述符,比如有数据可读的socket 文件描述符。epoll模型和select/poll模型的对比如图4所示。

图4 epoll vs select/poll

4.3 Java对多路复用器的包装

5 未完待续

上面多路复用器的例子使用的是单线程,对于多核cpu来说,不用多线程就太浪费了!关于java中多路复用器的多线程模型、netty的IO架构且听下回分解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值