《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
- 《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
- 同步与异步
- 同步
- 异步
- 对比
- 同步 I/O 的缺点
- 异步 I/O 的优点
- 理解异步通知 I/O 模型
- 实现异步通知 I/O 模型
- WSAEventSelect 函数和通知
- manual-reset 模式事件的其他创建方法
- 验证是否发生事件
- 区分事件类型
- 利用异步通知 I/O 模型实现回声服务器端
- 习题
- (1)结合 send & recv 函数解释同步和异步方式的 I/O。并请说明同步 I/O 的缺点,以及怎样通过异步 I/O 进行解决。
- (2)异步 I/O 并不是所有情况下的最佳选择。它具有哪些缺点?何种情况下同步 I/O 更优?可以参考异步 I/O 相关源代码,亦可结合线程进行说明。
- (3)判断下列关于 select 模型描述的正误。
- (4)请从源代码的角度说明 select 函数和 WSAEventSelect 函数在使用上的差异
- (5)第 17 章的 epoll 可以在条件触发和边缘触发这 2 种方式下工作。哪种方式更适合异步 I/O 模型?为什么?请概括说明。
- (6)Linux 中的 epoll 同样属于异步 I/O 模型。请说明原因。
- (7)如何获取 WSAWaitForMultipleEvents 函数可以监视的最大句柄数?请编写代码读取该值。
- (8)为何异步通知 I/O 模型中的事件对象必须是 manual-reset 模式?
- (9)请在本章的通知 I/O 模型的基础上编写聊天服务器端。要求该服务器端能够结合第 20 章的聊天客户端 chat_clnt_win.c 运行。
《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
同步与异步
同步
同步指的是任务按照顺序执行,一个任务必须等待前一个任务完成后才能继续。换句话说,在进行同步操作时,程序会阻塞在某个操作上,直到该操作完成后才会继续往下执行。
同步方式的数据 I/O:
可以通过下图解析上述两句话的含义:
异步
异步是指任务并行执行,程序发起一个操作后不等待其完成,而是继续执行其他任务。程序可以在等待操作完成的同时执行其他操作,最终通过回调、事件、信号等方式获取操作结果。
异步 I/O 是指 I/O 函数的返回时刻与数据接收的完成时刻不一致。
对比
同步 I/O 的缺点
进行 I/O 的过程中函数无法返回,所以不能执行其他任务。
异步 I/O 的优点
无论数据是否完成交换都返回函数,这就意味着可以执行其他任务。
异步方式能够比同步方式更有效使用 CPU。
理解异步通知 I/O 模型
通知 I/O 的含义:
顾名思义,通知 I/O 是指发生了I/O相关的特定情况。典型的通知 I/O 模型是 select 方式,但这种通知是以同步方式进行的,原因在于,需要 I/O 或可以进行 I/O 的时间点(简言之就是 I/O 相关事件发生的时间点)与 select 函数的返回时间点一致。
异步通知 I/O 模型意为通知 I/O 是以异步方式工作的。与“select 函数只在需要或可以进行 I/O 的情况下返回”不同,异步通知 I/O 模型中函数的返回与 I/O 状态无关。
本章的 WSAEventSelect 函数就是 select 函数的差异版本。
可能有人疑问:“既然函数的返回与I/O状态无关,那是否需要监视 I/O 状态变化?”
当然需要!异步通知 I/O 中,指定 I/O 监视对象的函数和实际验证状态变化的函数是相互分离的。因此,指定监视对象后可以离开执行其他任务,最后再回来验证状态变化。
实现异步通知 I/O 模型
异步通知 I/O 模型的实现方法有 2 种:一种是稍后介绍的 WSAEventSelect 函数,第二种是使用 WSAAsyncSelect 函数,第二种方法是 UI 相关内容,不进行介绍,需要了解可自行查阅资料。
WSAEventSelect 函数和通知
如前所述,告知 I/O 状态变化的操作就是“通知”。I/O的状态变化可以分为不同情况:
- 套接字的状态变化:套接字的I/O状态变化。
- 发生套接字相关事件:发生套接字I/O相关事件。
这 2 种情况都意味着发生了需要或可以进行 I/O 的事件,我将根据上下文适当混用这些概念。
WSAEventSelect 是 Windows Sockets API(Winsock)中的一个函数,用于将一个 Windows 事件对象与一个套接字(socket)关联,以便在套接字状态发生变化时获得通知。这个函数常用于异步网络编程,特别是在处理多个套接字时。
#include<winsock2.h>
int WSAEventSelect(SOCKET s,
HANDLE hEvent,
long lNetworkEvents
);
参数:
- s:监视对象的套接字句柄。
- hEventObject:传递事件对象句柄以验证事件发生与否。
- INetworkEvents:希望监视的事件类型信息。
成功时返回 0,失败时返回 SOCKET_ERROR。
传入参数 s 的套接字内只要发生 INetworkEvents 中指定的事件之一,WSAEventSelect 函数就将 hEventObject 句柄所指内核对象改为 signaled 状态。因此,该函数又称“连接事件对象和套接字的函数”。该函数以异步通知方式工作。无论事件发生与否,WSAEventSelect 函数调用后都会直接返回。
下面介绍作为该函数第三个参数的事件类型信息,可以通过位或运算同时指定多个信息。
- FD_READ:是否存在需要接收的数据?
- FD_WRITE:能否以非阻塞方式传输数据?
- FD_OOB:是否收到带外数据?
- FD_ACCEPT:是否有新的连接请求?
- FD_CLOSE:是否有断开连接的请求?
以上就是 WSAEventSelect 函数的调用方法。
仅从概念上看,WSAEventSelect函数 的功能偏弱。但使用该函数时,没必要针对多个套接字进行调用。从 select 函数返回时,为了验证事件的发生需要再次针对所有句柄调用函数,但通过调用 WSAEventSelect 函数传递的套接字信息已注册到操作系统,所以无需再次调用。这反而是 WSAEventSelect 函数比 select 函数的优势所在。
从函数说明中可以看出,我们还需要知道以下内容:
- WSAEventSelect 函数的第二个参数中用到的事件对象的创建方法。
- 调用 WSAEventSelect 函数后发生事件的验证方法。
- 验证事件发生后事件类型的查看方法。
manual-reset 模式事件的其他创建方法
之前创建事件对象是利用 CreateEvent 函数。 CreateEvent 函数在创建事件对象时,可以在 auto-reset 模式和 manual-reset 模式中任选其一。但是我们只需要 manual-reset 模式 non-signaled 状态的事件对象,所以利用下面的函数创建比较方便。
#include <winsock2.h>
WSAEVENT WSACreateEvent(void);
成功时返回事件对象句柄,失败时返回 WSA_INVALID_EVENT。
上述声明中返回类型 WSAEVENT 的定义如下:
#define WSAEVENT HANDLE
实际上就是我们熟悉的内核对象句柄。
另外,可使用如下函数销毁上述函数创建的事件对象:
#include <winsock2.h>
BOOL WSACloseEvent(WSAEVENT hEvent);
成功时返回 TRUE, 失败时返回 FALSE。
验证是否发生事件
区分事件类型
利用异步通知 I/O 模型实现回声服务器端
服务器端:
客户端:
在这里插入代码片
编译:
在这里插入代码片
运行结果:
习题
(1)结合 send & recv 函数解释同步和异步方式的 I/O。并请说明同步 I/O 的缺点,以及怎样通过异步 I/O 进行解决。
(2)异步 I/O 并不是所有情况下的最佳选择。它具有哪些缺点?何种情况下同步 I/O 更优?可以参考异步 I/O 相关源代码,亦可结合线程进行说明。
(3)判断下列关于 select 模型描述的正误。
- select 模型通过函数的返回值通知 I/O 相关事件,故可视为通知 I/O 模型。(√)
- select 模型中 I/O 相关事件的发生时间点和函数返回的时间点一致,故不属于异步模型。(√)
- WSAEventSelect 函数可视为 select 方式的异步模型,因为该函数的 I/O 相关事件的通知方式。(√)