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 <windows.h>
#include <stdio.h>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 <windows.h>
#include <stdio.h>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()
的使用技巧!
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)