《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型

news2025/3/29 1:31:02

《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型

  • 《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型
    • 理解重叠 I/O 模型
      • 重叠 I/O
      • 本章讨论的重叠 I/O 的重点不在于 I/O
    • 创建重叠 I/O 套接字
    • 执行重叠 I/O 的 WSASend 函数
    • 进行重叠 I/O 的 WSARecv 函数
    • 重叠 I/O 的 I/O 完成确认
      • 使用事件对象
      • 使用 Completion Routine 函数

《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型

理解重叠 I/O 模型

第 21 章异步处理的并非 I/O,而是“通知”。本章讲解的才是以异步方式处理 I/O 的方法。

重叠 I/O

同一线程内部向多个目标传输数据引起的 I/O 重叠现象称为“重叠I/O”。为了完成这项任务,调用的 I/O 函数应立即返回,只有这样才能发送后续数据。从结果来看,利用上述模型收发数据时,最重要的前提条件就是异步 I/O(调用的 I/O 函数应以非阻塞模式工作)。

在这里插入图片描述

本章讨论的重叠 I/O 的重点不在于 I/O

重叠 I/O 的重点并非 I/O 本身,而是如何确认 I/O 完成时的状态。

非阻塞模式的输入输出需要另外确认执行结果。

Windows 平台下重叠 I/O 模型由非阻塞异步 I/O 函数和确认 I/O 完成状态的方法组成。

创建重叠 I/O 套接字

首先要创建适用于重叠I/O的套接字,可以通过如下函数完成:

#include <winsock2.h>

SOCKET WSASocket(
	int af, 
	int type, 
	int protocol, 
	LPWSAPROTOCOL_INFO loProtocolInfo,
	GROUP g,
	DWORD dwFlags
);

参数:

  • af:协议族信息
  • type:套接字数据传输方式
  • protocol:2 个套接字之间使用的协议信息
  • lpProtocolInfo:包含创建的套接字信息的WSAPROTOCOL_INFO结构体变量地址值,不需要时传递 NULL。
  • g:为扩展函数而预约的参数,可以使用 0
  • dwFlags:套接字属性信息

成功时返回套接字句柄,失败时返回 INVALID_SOCKET。

各位对前 3 个参数比较熟悉,第四个和第五个参数与目前的工作无关,可以简单设置为 NULL 和 0。可以向最后一个参数传递 WSA_FLAG_OVERLAPPED,赋予创建出的套接字重叠 I/O 特性。

可以通过如下函数调用创建出可以进行重叠 I/O 的非阻塞模式的套接字。

WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

执行重叠 I/O 的 WSASend 函数

创建出具有重叠 I/O 属性的套接字后,接下来 2 个套接字(服务器端/客户端之间的)连接过程与一般的套接字连接过程相同,但 I/O 数据时使用的函数不同。

先介绍重叠 I/O 中使用的数据输出函数:

#include <winsock2.h>

int WSASend(
	SOCKET s,
	LPWSABUF lpBuffers,
	DWORD dwBufferCount,
	LPDWORD lpNumberOfBytesSent,
	DWORD dwFlags,
	LPWSAOVERLAPPED lpOverlapped,
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

参数:

  • s:套接字句柄,传递具有重叠 I/O 属性的套接字句柄时,以重叠 I/O 模型输出。
  • IpBuffers:WSABUF 结构体变量数组的地址值,WSABUF 中存有待传输数据。
  • dwBufferCount:第二个参数中数组的长度。
  • IpNumberOfBytesSent:用于保存实际发送字节数的变量地址值
  • dwFlags:用于便改数据传输特性,如传递 MSG_OOB 时发送 OOB 模式的数据。
  • IpOverlapped:WSAOVERLAPPED 结构体变量的地址值,使用事件对象,用于确认完成数据传输。
  • IpCompletionRoutine:传入 Completion Routine 函数的入口地址值,可以通过该函数确认是否完成数据传输。

成功时返回 0,失败时返回 SOCKET_ERROR。

接下来介绍上述函数的第二个结构体参数类型,该结构体中存有待传输数据的地址和大小等信息。

typedef struct __WSABUF
{
    u_long len; // 待传输数据的大小
    char FAR * buf; // 缓冲地址值
} WSABUF, *LPWSABUF;

利用上述函数和结构体,传输数据时可以按如下方式编写代码:

WSAEVENT event;
WSAOVERLAPPED overlapped;
WSABUF dataBuf;
char buf[BUF_SIZE] = {"待传输的数据"};
int revcBytes = 0;
......
event = WSACreateEvent();
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = event;
dataBuf.len = sizeof(buf);
dataBuf.buf = buf;
WSASend(hSocket, &dataBuf, 1, &recvBytes, 0, &overlapped, NULL);
......

调用 WSASend 函数时将第三个参数设置为 1,因为策二个参数中待传输数据的缓冲个数为 1。另外,多余参数均设置为 NULL 或 0,其中需要注意第六个和第七个参数。

第六个参数中的 WSAOVERLAPPED 结构体定义如下:

typedef struct _WSAOVERLAPPED
{ 
    DWORD Internal;
    DWORD InternalHigh;
    DWORD Offset;
    DWORD OffsetHigh;
    WSAEVENT hEvent;
} WSAOVERLAPPED, *LPWSAOVERLAPPED;

Internal、InternalHigh 成员是进行重叠 I/O 时操作系统内部使用的成员,而 Offset、OffsetHigh 同样属于具有特殊用途的成员。所以各位实际只需要关注 hEvent 成员。

关于 WSAOVERLAPPED 结构体有 3 点需要注意:

  1. 为了进行重叠 I/O,WSASend 函数的 lpOverlapped 参数中应该传递有效的结构体变量地址值,而不是 NULL。
  2. 若向 lpOverlapped 传递 NULL,WSASend 函数的第一个参数中的句柄所指的套接字将以阻
    塞模式工作。
  3. 利用 WSASend 函教同时向多个目标传输数据时,需要分别构建传入第六个参数的 WSAOVERLAPPED 结构体变量。这是因为,进行重叠 I/O 的过程中,操作系统将使用 WSAOVERLAPPED 结构体变量。

WSASend 函数调用过程中,函数返回时间点和数据传输完成时间点并非总不一致。分为以下两种情况:

  • 如果输出缓冲是空的,且传输的数据并不大,那么函数调用后可以立即完成数据传输。此时,WSASend 函数将返回 0,lpNumberOfBytesSent 中将保存实际传输的数据大小的信息。
  • 反之,WSASend 函数返回后仍需要传输数据时,将返回 SOCKET_ERROR,并将 WSA_IO_PENDING 注册为错误代码,该代码可以通过 WSAGetLastError 函数(稍后再介绍)得到。这时应该通过如下函效获取实际传输的数据大小。
#include <winsock2.h>

BOOL WSAGetOverlappedResult(
	SOCKET s,
	LPWSAOVERLAPPED lpOverlapped,
	LPDWORD lpcbTransfer,
	BOOL fWait,
	LPDWORD lpdwFlags
);

参数:

  • s:进行重叠 I/O 的套接字句柄。
  • IpOverlapped:进行重叠 I/O 时传递的 WSAOVERLAPPED 结构体变量的地址值。
  • lpcbTransfer:用于保存实际传输的字节数的变量地址值。
  • fWait:如果调用该函数时仍在进行 I/O,fWait 为 TRUE 时等待 I/O 完成,fWait 为 FALSE 时将返回 FALSE 并跳出函数。
  • IpdwFlags:调用 WSARecv 函数时,用于获取附加信息(例如 OOB 消息)。如果不需要,可以传递 NULL。

成功时返回 TRUE,失败时返回 FALSE。

通过此函数不仅可以获取数据传输结果,还可以验证接收数据的状态。

进行重叠 I/O 的 WSARecv 函数

#include <winsock2.h>

int WSARecv(
	SOCKET s,
	LPWSABUF lpBuffers,
	DWORD dwBufferCount,
	LPDWORD lpNumberOfBytesRecvd,
	LPDWORD lpFlags,
	LPWSAOVERLAPPED lpOverlapped,
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

参数:

  • s:具有重叠 I/O 属性套接字句柄。
  • IpBuffers:用于保存接收数据的 WSABUF 结构体变量数组的地址值。
  • dwBufferCount:第二个参数中数组的长度。
  • lpNumberOfBytesRecvd:用于保存接收字节数的变量地址值。
  • lpFlags:用于设置或读取传输特性信息。
  • IpOverlapped:WSAOVERLAPPED 结构体变量地址值。
  • IpCompletionRoutine:Completion Routine 函数地址值。

成功时返回 0,失败时返回 SOCKET_ERROR。

Gather 输出指将多个缓冲中的数据累积到一定程度后一次性输出,Scatter 输入指将接收的数据分批保存。
重叠 I/O 的 WSASend 和 WSARecv 函数可以获得 writev & readv 函数的 Gather/Scatter I/O 功能。

重叠 I/O 的 I/O 完成确认

重叠 I/O 中有 2 种方法确认 I/O 的完成并获取结果。

  • 利用 WSASend、WSARecv 函数的第六个参数,基于事件对象。
  • 利用 WSASend、WSARecv 函数的第七个参数,基于 Completion Routine。

只有理解了这 2 种方法,才能算是掌握了重叠 I/O。首先介绍利用第六个参数的方法。

使用事件对象

直接给出示例。希望各位通过该示例验证如下 2 点:

  • 完成 I/O 时,WSAOVERLAPPED 结构体变量引用的事件对象将变为 signaled 状态。
  • 为了验证 I/O 的完成和完成结果,需要调用 WSAGetOvrlappedResult 函数。

发送端代码:

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

void ErrorHandling(char *msg);

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    SOCKET hSocket;
    SOCKADDR_IN sendAdr;

    WSABUF dataBuf;
    char msg[] = "Network is Computer!";
    int sendBytes = 0;

    WSAEVENT evObj;
    WSAOVERLAPPED overlapped;

    if (argc != 3)
    {
        printf("Usage : %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error");

    hSocket = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    memset(&sendAdr, 0, sizeof(sendAdr));
    sendAdr.sin_family = AF_INET;
    sendAdr.sin_addr.s_addr = inet_addr(argv[1]);
    sendAdr.sin_port = htons(atoi(argv[2]));

    if (connect(hSocket, (SOCKADDR *)&sendAdr, sizeof(sendAdr)) == SOCKET_ERROR)
        ErrorHandling("connect() error");

    evObj = WSACreateEvent();
    memset(&overlapped, 0, sizeof(overlapped));
    overlapped.hEvent = evObj;
    dataBuf.len = strlen(msg) + 1;
    dataBuf.buf = msg;

    if (WSASend(hSocket, &dataBuf, 1, &sendBytes, 0, &overlapped, NULL) == SOCKET_ERROR)
    {
        if (WSAGetLastError() == WSA_IO_PENDING)
        {
            puts("Background data send");
            WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
            WSAGetOverlappedResult(hSocket, &overlapped, &sendBytes, FALSE, NULL);
        }
        else
        {
            ErrorHandling("WSASend() error");
        }
    }

    printf("Send data size: %d \n", sendBytes);

    WSACloseEvent(evObj);
    closesocket(hSocket);
    WSACleanup();
    return 0;
}

void ErrorHandling(char *msg)
{
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
}

上述示例调用的 WSAGetLastError 函数定义如下。调用套接字相关函数后,可以通过该函数获取错误信息。

#include<winsock2.h>

int WSAGetLastError(void); // 返回错误代码(表示错误原因)

上述示例中该函数的返回值为 WSA_IO_PENDING,由此可以判断 WSASend 函数的调用结果并非发生了错误,而是尚未完成的状态。

下面介绍与上述示例配套使用的接收端代码:

在这里插入代码片

使用 Completion Routine 函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2321816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python每日十题(10)

在Python语言中&#xff0c;源文件的扩展名&#xff08;后缀名&#xff09;一般使用.py。 保留字&#xff0c;也称关键字&#xff0c;是指被编程语言内部定义并保留使用的标识符。Python 3.x有35个关键字&#xff0c;分别为&#xff1a;and&#xff0c;as&#xff0c;assert&am…

LabVIEW液压振动锤控制系统

在现代工程机械领域&#xff0c;液压振动锤的高效与精准控制日益显得重要。本文通过LabVIEW软件&#xff0c;展开液压振动锤启停共振控制技术的研究与应用&#xff0c;探讨如何通过改进控制系统来优化液压振动锤的工作性能&#xff0c;确保其在复杂工况下的稳定性与效率。 ​ …

简单介绍My—Batis

1.什么是My—Batis&#xff1f; My—Batis是一个持久层框架&#xff0c;提供了sql映射功能&#xff0c;能方便的将数据库表和java对象进行映射&#xff0c;通过My—Batis可以将项目中的数据存储在数据库中&#xff0c;以便我们进行调用。值得注意的是My—Batis和spring不是一回…

ALTER TABLE SHRINK SPACE及MOVE的区别与适用场景

以下是 ‌Oracle 数据库‌中三个收缩表空间命令的对比&#xff1a; 1. ALTER TABLE table_name SHRINK SPACE;‌ ‌作用‌&#xff1a;直接重组表数据并移动高水位线&#xff08;HWM&#xff09;&#xff0c;释放未使用的空间到表空间‌。 影响‌&#xff1a; 会锁表&#…

docker远程debug

1. 修改 Java 启动命令 在 Docker 容器中启动 Java 程序时&#xff0c;需要添加 JVM 调试参数&#xff0c;jdk8以上版本 java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 -jar your-app.jar jdk8及以下版本&#xff1a; java -Xdebug -Xrunjdwp:tra…

rosbag|ROS中.bag数据包转换为matlab中.mat数据类型

代码见代码 msg_dict中设置自定义消息类型 test_config中设置需要记录的具体的值 test_config中topic_name以及message_type照搬plotjuggler打开时的参数 最后生成.mat文件在matlab中进行使用

pytest-xdist 进行高效并行自动化测试

pytest-xdist 的核心功能是通过多进程分发测试任务&#xff0c;每个进程独立运行测试&#xff0c;确保测试隔离。2025 年 3 月 25 日&#xff0c;pytest-xdist 在 GitHub 上已有超过 1,200,000 次下载&#xff0c;表明其在测试社区中的广泛接受。 在自动化测试中&#xff0c;随…

位置编码再思考

最近在做多模态&#xff0c;发现基于 transformer 的多模态&#xff0c;position embedding 是一个非常重要的内容&#xff0c;而且还没有统一方案&#xff0c;先暂做记录&#xff0c;几篇还不错的博客&#xff1a; Transformer学习笔记一&#xff1a;Positional Encoding&…

Deepseek API+Python 测试用例一键生成与导出 V1.0.3

** 功能详解** 随着软件测试复杂度的不断提升,测试工程师需要更高效的方法来设计高覆盖率的测试用例。Deepseek API+Python 测试用例生成工具在 V1.0.3 版本中,新增了多个功能点,优化了提示词模板,并增强了对文档和接口测试用例的支持,极大提升了测试用例设计的智能化和易…

[c语言日寄MAX]深度解析:大小端字节序

【作者主页】siy2333 【专栏介绍】⌈c语言日寄MAX⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还…

Android ADB工具使用教程(从安装到使用)

目录 ADB工具介绍 什么是ADB&#xff1f; 组成 主要功能 ADB工具安装与连接设备 WIFI连接&#xff0c;提示计算机积极拒绝10061 WIFI成功连接后&#xff0c;拔掉数据线显示offline 提示adb版本不一致​编辑 ADB工具使用 ★日志操作命令 adb logcat:抓取日志 日志格式…

基于SSM框架的线上甜品销售系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就很关键。因此网上销售信息的…

Redis原理:Monitor 实现

在调用 Redis 的 MONITOR 命令后&#xff0c;可以在对应的客户端上实时查看服务器的执行情况。今天&#xff0c;我们将从源码的角度来深入探讨 MONITOR 机制是如何处理这些请求以及如何将数据反馈给用户的。 MONITOR 命令的实现 Redis 中所有命令的具体实现细节都可以在其源代…

计算机工具基础(七)——Git

Git 本系列博客为《Missing in CS Class(2020)》课程笔记 Git是一种分布式版本控制系统&#xff0c;被其跟踪的文件可被查询精细到行的修改记录、回退版本、建立分支等 模型 一般流程&#xff1a;工作区 → \to →暂存区 → \to →仓库(本地 → \to →远端) 工作区&#xff1…

23种设计模式-创建型模式-工厂方法

文章目录 简介场景问题1. 直接依赖具体实现2. 违反开闭原则3. 条件分支泛滥4. 代码重复风险 解决根本问题完整类图完整代码说明核心优势代码优化静态配置表动态策略 总结 简介 工厂方法是一种创建型设计模式&#xff0c;它提供了在父类中创建对象的接口&#xff0c;但允许子类…

142. 环形链表 II——考察数学,难!

142. 环形链表 IIhttps://leetcode.cn/problems/linked-list-cycle-ii/ 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,…

从零开始:使用Luatools工具高效烧录Air780EPM核心板项目的完整指南

本文将深入讲解如何使用Luatools工具烧录一个具体的项目到Air780EPM开发板中。如何使用官方推荐的Luatools工具&#xff08;一款跨平台、命令行驱动的烧录利器&#xff09;&#xff0c;通过“环境配置→硬件连接→参数设置→一键烧录”四大步骤&#xff0c;帮助用户实现Air780E…

一套云HIS系统源码,系统融合HIS与EMR,基于云端部署,采用B/S架构与SaaS模式

云HIS系统完全基于云端部署&#xff0c;采用B/S架构&#xff0c;并通过软件即服务&#xff08;SaaS&#xff09;的形式面向二级及以下医院可快速交付、便捷运维、云化的医院核心业务平台产品。融合医院HIS和EMR两大主营系统&#xff0c;构建涵盖患者、费用、医嘱、电子病历等核…

C++数据结构(搜索二叉树)

1.二叉树搜索的概念 二叉搜索数也成为二叉排序树&#xff0c;它或者是一颗空树&#xff0c;或者是满足以下性质的树&#xff1a; 1.若他的左子树不为空&#xff0c;则左子树上的所有节点的值都小于等于根节点的值。 2.若他的右子树不为空&#xff0c;则右子树上的所有节点的值…

OpenCV图像拼接(6)图像拼接模块的用于创建权重图函数createWeightMap()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::detail::createWeightMap 是 OpenCV 库中用于图像拼接模块的一个函数&#xff0c;主要用于创建权重图。这个权重图在图像拼接过程中扮演着重…