0%

Linux系统编程05-IO复用

Linux系统调用accept原理

accept 函数是网络编程中用于接受客户端连接请求的关键系统调用。当一个 socket 被设置为监听模式后,它会在其对应的队列中积累来自客户端的连接请求。accept 函数的作用是从这个队列中取出一个已经建立的连接,并返回一个新的 socket 文件描述符,该描述符与客户端的连接相关联。

​ 当没有连接请求时,accept 会阻塞调用它的进程或线程,直到有新的连接请求到达。这个过程涉及到操作系统内核中的网络协议栈,具体来说,是 TCP 协议栈的处理机制。当 accept 被调用时,内核会检查监听 socket 的完全连接队列,如果队列为空,则进程或线程进入阻塞状态;如果队列中有待处理的连接,则 accept 返回一个新的 socket,进程或线程继续执行。

非阻塞IO模式

在非阻塞 I/O 模式下,当 accept 函数没有准备好返回时(即没有连接请求可以接受),它不会阻塞调用它的进程或线程,而是立即返回一个错误。这种模式允许程序在等待连接时继续执行其他任务,从而提高程序的响应性和效率。

设置socket为非阻塞模式

要将 socket 设置为非阻塞模式,可以使用 fcntl 函数来修改 socket 的属性。

IO复用的概念

在网络编程中,IO 复用(I/O Multiplexing)是一种让程序可以同时监视多个文件描述符(在网络编程里通常是套接字描述符)的技术,以此来判断这些文件描述符上是否有读写等事件发生,从而实现高效的 I/O 操作。

一个应用场景:在传统的单线程网络编程中,当调用 accept 函数时,如果没有新的连接请求到达,程序会被阻塞,这意味着在等待新连接的过程中,程序无法处理其他已经建立的连接上的读写操作。

理解select函数并实现服务端:

1、是否存在套接字接收数据?

​ 要判断是否存在套接字接收数据,可以使用 IO 复用技术(如 selectpollepoll)。这些技术允许程序同时监视多个套接字的状态,当某个套接字有数据可读时,就能得知该套接字正在接收数据。

2、无需阻塞传输数据的套接字有哪些?

​ 在网络编程中,实现非阻塞传输数据通常可以通过将套接字设置为非阻塞模式,或者使用异步 I/O 技术。

3、哪些套接字发生了异常?

​ 同样可以使用 IO 复用技术来检测套接字是否发生异常。在 selectpollepoll 等函数中,都有相应的机制来处理异常事件。

select模型以及实战案例

最早出现的IO复用系统调用,几乎所有的操作系统都支持。

程序创建一个文件描述符集合,将需要监视的文件描述符添加到该集合中,然后调用 select 函数。select 函数会阻塞,直到有文件描述符发生指定的事件或超时。返回时,select 会修改文件描述符集合,只保留那些发生了事件的文件描述符,程序可以通过遍历集合来处理这些事件。

多进程服务器的缺点和解决办法

多进程服务器:1、需要大量的运算 2、大量的内存空间

理解select函数并实现服务端

1、是否存在套接字接收数据?

2、无需阻塞传输数据的套接字有哪些?

3、哪些套接字发生了异常?

Select模型具体步骤

1、设置文件描述符:select函数监视多个(不超过1024个)文件描述符

EPOLL 模型

eventpoll是 linux 内核实现IO多路转接/复用(IO multiplexing)的一个实现。IO多路转接的意思是在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。epoll是select和poll的升级版,相较于这两个前辈,epoll改进了工作方式,因此它更加高效。

·对于待检测集合select和poll是基于线性方式处理的,epoll是基于红黑树来管理待检测集合的。

select和poll每次都会线性扫描整个待检测集合,集合越大速度越慢,epoll使用的是回调机制,效率高,处理效率也不会随着检测集合的变大而下降
select和poll工作过程中存在内核/用户空间数据的频繁拷贝问题,在epoll中内核和用户区使用的是共享内存(基于mmap内存映射区实现),省去了不必要的内存拷贝。
程序猿需要对select和poll返回的集合进行判断才能知道哪些文件描述符是就绪的,通过epoll可以直接得到已就绪的文件描述符集合,无需再次检测
使用epoll没有最大文件描述符的限制,仅受系统中进程能打开的最大文件数目限制
当多路复用的文件数量庞大、IO流量频繁的时候,一般不太适合使用select()和poll(),这种情况下select()和poll()表现较差,推荐使用epoll()。

**Epoll的三大函数:epoll_createepoll_waitepoll_ct**

int epoll_create(int size); size必须要大于0 这个函数返回文件描述符

epoll_ctl

EPOOL 模型和 select 模型的对比

  1. 性能方面
    • 系统调用开销:
      • select模型中,每次调用select函数时,都需要将用户空间的文件描述符集合拷贝到内核空间。当文件描述符数量较多时,这种拷贝操作会带来较大的开销。例如,在一个有大量并发连接的服务器场景中,如果有 1000 个文件描述符,每次select调用都要拷贝这 1000 个文件描述符相关的信息。
      • epoll使用了内核与用户空间共享的数据结构。epoll_create创建epoll实例后,epoll_ctl函数用于在内核维护的红黑树中添加、删除或修改文件描述符及其对应的事件。在epoll_wait调用时,不需要像select那样频繁地拷贝大量文件描述符集合,减少了系统调用的开销。
    • 事件触发方式的效率:
      • select模型采用的是水平触发(Level - Triggered)方式。这意味着只要文件描述符对应的缓冲区中有数据可读或者可写,select就会一直通知应用程序该文件描述符可读或可写。例如,在处理网络套接字时,如果应用程序没有及时处理完缓冲区中的数据,select在下一次调用时仍然会通知该套接字可读,这可能导致一些不必要的重复处理。
      • epoll可以采用边缘触发(Edge - Triggered)方式(如代码中设置EPOLLET)。边缘触发是在文件描述符状态发生变化(如从不可读到可读,或者从不可写到可写)的瞬间触发事件通知。这样,只有当有新的数据到来或者连接状态真正改变时才会触发事件,避免了像select那样因为缓冲区数据未处理完而导致的重复通知,从而提高了处理效率。
  2. 可扩展性方面
    • 文件描述符数量限制:
      • select模型对文件描述符数量有比较严格的限制。在不同的操作系统中,这个限制可能不同,但通常是一个较小的固定值。例如,在一些旧版本的 Unix 系统中,select最多能处理 1024 个文件描述符。这在处理大量并发连接的现代网络应用场景中是远远不够的。
      • epoll没有这种固定的、较小的文件描述符数量限制。它可以处理的文件描述符数量主要取决于系统的资源(如内存大小等)。这使得epoll能够更好地适应高并发场景,如大型的 Web 服务器或者消息队列服务器,这些服务器可能需要同时处理成千上万个客户端连接。
  3. 代码复杂度方面
    • 事件处理的代码逻辑:
      • select模型中,应用程序需要遍历所有的文件描述符来检查哪些文件描述符产生了事件。例如,每次select返回后,需要在一个循环中逐个检查文件描述符集合中的每个文件描述符是否在可读或可写集合中。如果文件描述符数量较多,这种遍历检查的代码会比较复杂且效率低下。
      • epoll返回的是已经发生事件的文件描述符集合,应用程序只需要直接处理这些发生事件的文件描述符即可。在代码逻辑上,epoll更加简洁明了,不需要像select那样进行大量的遍历和判断操作,降低了代码的复杂度和出错的概率。

边缘触发和条件触发

条件触发(level-triggered,也被称为水平触发)LT:

只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通知你)

边缘触发(edge-triggered)ET:

每当状态变化时,触发一个事件
“举个读socket的例子,假定经过长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个通知应用程序可读。应用程序读了50个字节,然后重新调用api等待io事件。

这时水平触发的api会因为还有50个字节可读从而立即返回用户一个read ready notification。

而边缘触发的api会因为可读这个状态没有发生变化而陷入长期等待。 因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则这个socket就算废了。而使用条件触发的api 时,如果应用程序不需要写就不要关注socket可写的事件,否则就会无限次的立即返回一个write ready notification。

select属于典型的条件触发。