[实战] Windows 文件读写函数 `ReadFile()` 和 `WriteFile()` 的阻塞与非阻塞操作详解(含完整C语言示例)
Windows 文件读写函数 ReadFile() 和 WriteFile() 的阻塞与非阻塞操作详解(含完整C语言示例) 在 Windows 平台进行文件或设备(如串口、管道)编程时
Windows 文件读写函数 ReadFile() 和 WriteFile() 的阻塞与非阻塞操作详解(含完整C语言示例)
在 Windows 平台进行文件或设备(如串口、管道)编程时,ReadFile() 和 WriteFile() 是最常用的两个 API 函数。它们既可以以阻塞方式运行,也可以通过设置标志实现非阻塞方式运行。本文将详细讲解这两种模式的区别,并提供完整的 C 语言示例代码。
一、概述:ReadFile() 和 WriteFile() 简介
这两个函数定义在 windows.h 头文件中:
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
hFile:文件或设备的句柄。lpBuffer:数据缓冲区地址。nNumberOfBytesTo(Read/Write):要读写的字节数。lpNumberOfBytes(Read/Written):实际读写的数据长度。lpOverlapped:重叠结构指针。为 NULL 表示同步(阻塞)操作;否则表示异步(非阻塞)操作。
二、阻塞操作 vs 非阻塞操作
特性阻塞操作(默认)非阻塞操作(异步)是否等待完成是,调用线程会被挂起直到操作完成否,函数立即返回,操作由系统后台完成是否需要 OVERLAPPED 结构否是是否支持事件通知否可结合事件对象进行通知是否支持 I/O 完成端口否是CPU 使用率较低(等待期间休眠)较高(需主动轮询或使用回调)实现复杂度简单复杂
三、阻塞方式示例
✅ 示例1:阻塞方式读写文件
#include
#include
int main() {
HANDLE hFile = CreateFile(
TEXT("testfile.txt"),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
printf("CreateFile failed (%d)\n", GetLastError());
return 1;
}
const char* buffer = "Hello, World!";
DWORD bytesWritten;
// 写入文件(阻塞)
if (!WriteFile(hFile, buffer, strlen(buffer), &bytesWritten, NULL)) {
printf("WriteFile failed (%d)\n", GetLastError());
CloseHandle(hFile);
return 1;
}
printf("Wrote %d bytes\n", bytesWritten);
// 移动文件指针到开头
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
char readBuffer[256];
DWORD bytesRead;
// 读取文件(阻塞)
if (!ReadFile(hFile, readBuffer, sizeof(readBuffer), &bytesRead, NULL)) {
printf("ReadFile failed (%d)\n", GetLastError());
CloseHandle(hFile);
return 1;
}
readBuffer[bytesRead] = '\0'; // 添加字符串结束符
printf("Read: %s\n", readBuffer);
CloseHandle(hFile);
return 0;
}
📌 编译命令(MinGW / GCC):
gcc -o blocking_example blocking_example.c -mwindows
四、非阻塞方式示例(异步操作)
使用 OVERLAPPED 结构和事件对象可以实现非阻塞读写。下面是一个完整的异步读取示例:
✅ 示例2:非阻塞方式读取文件(异步 + 事件通知)
#include
#include
int main() {
HANDLE hFile = CreateFile(
TEXT("testfile.txt"),
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, // 设置为异步操作
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
printf("CreateFile failed (%d)\n", GetLastError());
return 1;
}
char buffer[256];
DWORD bytesRead;
OVERLAPPED overlapped = {0};
overlapped.Offset = 0;
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建事件
// 异步读取
BOOL result = ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, &overlapped);
if (!result && GetLastError() != ERROR_IO_PENDING) {
printf("ReadFile error (%d)\n", GetLastError());
CloseHandle(hFile);
return 1;
}
printf("Waiting for asynchronous read to complete...\n");
// 等待事件触发(可替换为 WaitForMultipleObjects 或其他机制)
WaitForSingleObject(overlapped.hEvent, INFINITE);
// 获取最终结果
if (!GetOverlappedResult(hFile, &overlapped, &bytesRead, FALSE)) {
printf("GetOverlappedResult failed (%d)\n", GetLastError());
} else {
buffer[bytesRead] = '\0';
printf("Async read completed: %s\n", buffer);
}
CloseHandle(overlapped.hEvent);
CloseHandle(hFile);
return 0;
}
📌 编译命令:
gcc -o async_read async_read.c -mwindows
五、总结对比
功能阻塞方式非阻塞方式(异步)是否立即返回否是是否适合大量并发否是是否支持事件通知否是是否需要处理线程阻塞不需要需要配合事件、线程池或 I/O 完成端口适用场景简单文件读写、调试高性能网络服务、串口通信、异步I/O
六、扩展建议
对于高性能服务器程序,建议结合 I/O 完成端口(IOCP) 使用异步模型。如果需要同时处理多个异步请求,应使用 线程池 或 WaitForMultipleObjects。对于串口、管道等设备通信,通常推荐使用异步模式提升响应能力。
七、结语
Windows 提供了灵活的文件和设备读写接口,开发者可以根据需求选择阻塞模式或非阻塞异步模式。理解两者的区别及使用方法,是编写高效 Windows 应用的关键一步。希望本篇博客能帮助你更好地掌握 ReadFile() 和 WriteFile() 的使用技巧!
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)