Linux系统编程-网络编程-select、poll、epoll之间的区别

发布时间 2023-09-28 01:21:06作者: skyycj

12.2. 同步I/O

在操作系统中,程序运行的空间分为内核空间和用户空间,用户空间所有对io操作的代码(如文件的读写、socket的收发等)都会通过系统调用进入内核空间完成实际的操作。

而且我们都知道CPU的速度远远快于硬盘、网络等I/O。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到I/O操作,如读写文件、发送网络数据时,就需要等待 I/O 操作完成,才能继续进行下一步操作,这种情况称为同步 I/O。

在某个应用程序运行时,假设需要读写某个文件,此时就发生了 I/O 操作,在I/O操作的过程中,系统会将当前线程挂起,而其他需要CPU执行的代码就无法被当前线程执行了,这就是同步I/O操作,因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们可以使用 多线程或者多进程 来并发执行代码,当某个线程/进程被挂起后,不会影响其他线程或进程。

多线程和多进程虽然解决了这种并发的问题,但是系统不能无上限地增加线程/进程。由于系统切换线程/进程的开销也很大,所以,一旦线程/进程数量过多,CPU的时间就花在线程/进程切换上了,真正运行代码的时间就少了,这样子的结果也导致系统性能严重下降。

多线程和多进程只是解决这一问题的一种方法,另一种解决I/O问题的方法是异步I/O,当然还有其他解决的方法。

 

12.3. 异步I/O

当程序需要对I/O进行操作时,它只发出I/O操作的指令,并不等待I/O操作的结果,然后就去执行其他代码了。一段时间后,当I/O返回结果时,再通知CPU进行处理。这样子用户空间中的程序不需要等待内核空间中的 I/O 完成实际操作,就可执行其他任务,提高CPU的利用率。

简单来说就是,用户不需要等待内核完成实际对io的读写操作就直接返回了。

 

12.4. 阻塞I/O

在linux中,默认情况下所有的socket都是阻塞的,一个典型的读操作流程大概是这样

 

当用户进程调用了 read()/recvfrom() 等系统调用函数,它会进入内核空间中,当这个网络I/O没有数据的时候,内核就要等待数据的到来,而在用户进程这边,整个进程会被阻塞,直到内核空间返回数据。当内核空间的数据准备好了,它就会将数据从内核空间中拷贝到用户空间,此时用户进程才解除阻塞的的状态,重新运行起来。

所以,阻塞I/O的特点就是在IO执行的两个阶段(用户空间与内核空间)都被阻塞了。

 

12.5. 非阻塞I/O

linux下,可以通过设置socket使其变为非阻塞模式,这种情况下,当内核空间并无数据的时候,它会马上返回结果而不会阻塞,此时用户进程可以根据这个结果自由配置,比如继续请求数据,或者不再继续请求。当对一个非阻塞socket执行读操作时,流程是这个样子:

 

 

 

12.6. 多路复用I/O

多路复用I/O就是我们说的 select,poll,epoll 等操作,复用的好处就在于 单个进程 就可以同时处理 多个 网络连接的I/O,能实现这种功能的原理就是 select、poll、epoll 等函数会不断的 轮询 它们所负责的所有 socket ,当某个 socket 有数据到达了,就通知用户进程。

一般来说I/O复用多用于以下情况:

  1. 当客户处理多个描述符时。

  2. 服务器在高并发处理网络连接的时候。

  3. 服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

  4. 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

  5. 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

与多进程和多线程技术相比, I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程 ,从而大大减小了系统的开销。但select,poll,epoll本质上都是同步I/O,因为他们都需要 在读写事件就绪后自己负责进行读写 ,也就是说这个读写过程是 阻塞 的,而 异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间 。

 

12.6.3. epoll

12.6.3.1. epoll的原理

其实相对于 select 和 poll 来说, epoll 更加灵活,但是核心的原理都是当socket描述符就绪(可读、可写、出现异常),就会通知应用进程,告诉他哪个socket描述符就绪,只是通知处理的方式不同而已。

epoll使用一个 epfd (epoll文件描述符)管理多个socket描述符,epoll不限制socket描述符的个数, 将用户空间的socket描述符的事件存放到内核的一个事件表中 ,这样在用户空间和内核空间的copy只需一次。当epoll记录的socket产生就绪的时候,epoll会通过callback的方式来激活这个fd,这样子在epoll_wait便可以收到通知,告知应用层哪个socket就绪了,这种通知的方式是可以直接得到那个socket就绪的,因此相比于 select 和 poll ,它不需要遍历socket列表,时间复杂度是O(1),不会因为记录的socket增多而导致开销变大。