非阻塞式IO

本文详细阐述了套接字在阻塞与非阻塞模式下的工作原理,包括输入输出操作、连接接受及发起等场景的具体行为,为网络编程提供实用指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:

1。 在学习网络编程时,对于缺省socket()函数相关的操作不确定其阻塞与否;

所以,从UNP卷一:套接字联网API(第三版)中的第16章“非阻塞式IO”中摘录了一段概括性的总结保存在这里,方便了有问题时候可以查阅。

解决办法:

1。 摘录的文章内容如下:


套接字的默认状态是阻塞的。这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待相应操作完成。可能阻塞的套接字调用可分为以下四类。

(1)输入操作,包括read, readv, recv,recvfrom和recvmsg共5个函数。如果某个进程对一个阻塞的TCP套接字(默认设置)调用这些输入函数之一,而且该套接字的接收缓冲区中没有数据可读,该进程将被投入睡眠,直到有一些数据到达。既然TCP是字节流协议,该进程的唤醒就是只要有一些数据到达,这些数据既可以是单个字节,也可以是一个完整的TCP分节中的数据。如果想等到某个固定数目的数据可读为止,那么可以调用readn函数或者指定MSG_WAITALL标志。

既然UDP是数据报协议,如果一个阻塞的UDP套接字的接收缓冲区为空,对它调用输入函数的进程将被投入睡眠,直到有UDP数据报到达。
对于非阻塞套接字,如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据可读,对于UDP套接字即有一个完整的数据包可读),相应调用将立即返回一个EWOULDBLOCK错误。

(2)输出操作,包括write, writev, send,sendto和sendmsg共5个函数。对于一个TCP套接字,内核将从应用进程的缓冲区到该套接口的发送缓冲区拷贝数据。对于阻塞的套接字,如果其发送缓冲区中没有空间,进程将被投入睡眠,直到有空间为止。
对于一个非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,输出函数调用将立即返回一个EWOULDBLOCK错误。如果其发送缓冲区中有一些空间,返回值将是内核能够拷贝到该缓冲区中的字节数。这个字节数也称为不足计数(short count)。
UDP套接字不存在真正的发送缓冲区。内核只是拷贝应用进程数据并把它沿协议栈向下传输,渐次冠以UDP头部和IP头部。因此对一个阻塞的UDP套接口(缺省设置),输出函数调用将不会因与TCP套接字一样的原因而阻塞,不过有可能会因为其他的原因而阻塞。

(3)接受外来连接,即accept函数。如果对一个阻塞的套接字调用accept函数,并且尚无新的连接到达,调用进程将被投入睡眠。
如果对一个非阻塞的套接口调用accept函数,并且尚无新的连接到达,accept调用将立即返回一个EWOULDBLOCK错误。

(4)发起外出连接,即用于TCP的connect函数。(该函数同样可以用于UDP,不过不能使建立一个“真正”的连接建立起来,只是使内核保存对端的IP地址和端口号。)TCP连接的建立涉及一个三路握手过程,而且connect函数一直要等到客户收到对于自己的SYN的ACK为止才返回。这一点意味着TCP的每个connect总是阻塞其调用进程至少一个到服务器的RTT时间。
如果对一个非阻塞的TCP套接口调用connect,并且连接不能立即建立,那么连接的建立照样发起(譬如送出TCP三路握手的第一个分组),不过返回一个EINPROGRESS错误,该错误不同于上述三个情形中返回的错误。另注意有些连接可以立即建立,通常发生在服务器和客户端处于同一个主机的情况下。因此即使对于一个非阻塞的connect,我们也得预备connect成功返回的情况发生。


当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的TCP三路握手继续进行。我们接着使用select检测这个连接或成功或失败的已建立条件,非阻塞的connect有三个用途:
    1    我们可以把三路握手叠加在其他处理上,完成一个connect要花费一个RTT时间上的时间,而RTT波动范围很大,从局域网上的几个毫秒到几百个毫秒甚至是广域网上的几秒。这段时间内也许有我们想要执行的其他处理工作可以执行。

    2    我们可以使用这个技术同时建立多个连接。这个用途已随着Web浏览器变得流行起来。
    3   既然使用select等待连接的建立,我们可以给select指定一个时间限制,使得我们能够缩短connect的超时。许多实现有着从75秒钟到数分钟的connect超时时间。应用程序有时需要一个更短的超时,实现办法之一就是使用非阻塞connect。


非阻塞版本几乎比select加阻塞时I/O版本快出一倍。fork版本比非阻塞式版本稍慢,然而考虑到非阻塞式版本代码相比fork版本代码的复杂性,我们推荐简单得多的fork版本。


### Redis 中基于 Epoll 的非阻塞 IO 多路复用工作原理 Redis 是一种高性能的内存数据库,其核心之一在于通过高效的 IO 模型支持大量的并发连接。为了实现这一点,Redis 利用了 IO 多路复用技术中的 `epoll` 机制,在 Linux 平台上提供了高效的通知方式。 #### 1. **Epoll 基本概念** `epoll` 是 Linux 提供的一种高效的 IO 复用接口,相比传统的 `select` 和 `poll` 更加适合大规模文件描述符的场景。它通过三个主要操作来完成事件监听和通知的功能: - `epoll_create`: 创建一个 epoll 实例。 - `epoll_ctl`: 注册/修改/删除感兴趣的文件描述符及其对应的事件类型。 - `epoll_wait`: 等待注册的文件描述符上的事件发生并返回。 这些特性使得 `epoll` 能够有效地减少轮询开销,并且只关注那些真正发生了变化的文件描述符[^3]。 #### 2. **Redis 中的 Epoll 应用** 在 Redis 中,所有的客户端连接都被抽象成文件描述符 (FD),并通过 `epoll` 进行统一管理。以下是具体的工作流程: - **初始化阶段**: 当服务器启动时,会调用 `aeCreateFileEvent` 函数创建一个新的文件事件处理器,并将其绑定到指定的文件描述符上。此时,Redis 将该 FD 添加到内部维护的一个全局数组中以便后续跟踪。 - **事件循环**: 主线程进入无限循环模式 (`mainLoop`) 来持续监控所有已注册的文件描述符的状态变更情况。在这个过程中,主线程会定期调用 `epoll_wait` 方法等待任何可读或可写的条件满足后再继续执行下一步逻辑处理动作[^1]。 - **事件分发与回调**: 如果某个特定时间点上有新的数据到达或者可以发送更多消息出去,则相应的 handler function 就会被触发去实际完成具体的业务功能;如果没有匹配项则保持休眠状态直到下一个周期到来为止[^2]。 #### 3. **优势分析** 使用 `epoll` 可以为 Redis 带来以下几个方面的提升效果显著: - 性能优化: 对于大数量级的同时在线活跃链接数来说,相比于其他两种传统方法(select/poll),epoll 明显具备更低延迟率以及更高吞吐能力. - 资源节约: 它只需要消耗少量额外内存用于保存关心列表即可而无需像前者那样每次都需要重新构建整个集合结构体实例化对象. ```c // 示例代码展示如何设置 epoll 监听器 int fd = socket(AF_INET, SOCK_STREAM, 0); struct epoll_event event; event.events = EPOLLIN | EPOLLET; // 边缘触发模式 event.data.fd = fd; if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1){ perror("epoll set insertion error"); } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值