Win32编程之异步完成IO(十)

一、文件的异步写入

#include <Windows.h>
#include <stdio.h>

int main() {
	HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		printf("无法打开文件。错误码:%d\n", GetLastError());
		return 0;
	}

	OVERLAPPED ol1 = { 0 };
	char buffer[] = "Hello World";
	DWORD writeCount = 0;
	BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
	if (!ret) {
		CloseHandle(hFile);
		printf("文件写入失败!\n");
		return 0;
	}

	CloseHandle(hFile);

	return 1;
} 

二、文件的异步读取  

#include <Windows.h>
#include <stdio.h>

int main() {
	HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		printf("无法打开文件。错误码:%d\n", GetLastError());
		return 0;
	}
OVERLAPPED ol2 = { 0 }; char readBuffer[255] = { 0 }; DWORD readCount = 0; ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2); if (!ret) { CloseHandle(hFile); printf("文件读取失败!\n"); return 0; } printf("readBuffer:%s\n", readBuffer); CloseHandle(hFile); return 1; }

 三、异步读写操作的判断方法

(1).异步文件写入操作的判断

OVERLAPPED ol1 = { 0 };
char buffer[] = "Hello World";
DWORD writeCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
if (!ret) {
	DWORD err = GetLastError();
	if (ERROR_IO_PENDING == err) {
		printf("正在进行异步写入操作!\n");
	}
	else {
		printf("文件写入失败!\n");
		CloseHandle(hFile);
		return 0;
	}
}

(2).异步文件读取操作的判断

OVERLAPPED ol2 = { 0 };
char readBuffer[255] = { 0 };
DWORD readCount = 0;
ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
if (!ret) {
	DWORD err = GetLastError();
	if (ERROR_IO_PENDING == err) {
		printf("正在进行异步读取操作!\n");
	}
	else {
		printf("文件读取失败!\n");
		CloseHandle(hFile);
		return 0;
	}
}
printf("readBuffer:%s\n", readBuffer);

三、异步IO完成通知的方法 

1.触发设备内核对象

触发设备内核对象:允许一个线程发出IO请求,另一个线程对结果进行处理,当向一个设备同时发出多个IO请求的时候,此方法无效

(1).等待文件写入完毕

OVERLAPPED ol1 = { 0 };
char buffer[] = "Hello World";
DWORD writeCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
if (!ret) {
	DWORD err = GetLastError();
	if (ERROR_IO_PENDING == err) {
		printf("正在进行异步写入操作!\n");
		WaitForSingleObject(hFile, INFINITE);
		printf("异步写入完毕!\n");

	}
	else {
		printf("文件写入失败!\n");
		CloseHandle(hFile);
		return 0;
		}
}

(2).等待文件读取完毕 

OVERLAPPED ol2 = { 0 };
char readBuffer[255] = { 0 };
DWORD readCount = 0;
ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
if (!ret) {
	DWORD err = GetLastError();
	if (ERROR_IO_PENDING == err) {
		printf("正在进行异步读取操作!\n");
		WaitForSingleObject(hFile, INFINITE);
		printf("异步读取完毕\n");
	}
	else {
		printf("文件读取失败!\n");
		CloseHandle(hFile);
		return 0;
	}
}
printf("readBuffer:%s\n", readBuffer);

2.触发事件内核对象

这种方法允许我们向一个设备同时发出多个IO请求,它允许一个线程发出IO请求,另一个线程对结果进行处理

示例代码:

#include <Windows.h>
#include <stdio.h>

int main() {
	HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		printf("无法打开文件。错误码:%d\n", GetLastError());
		return 0;
	}

	OVERLAPPED ol1 = { 0 };
	ol1.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	OVERLAPPED ol2 = { 0 };
	ol2.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	char buffer[] = "Hello World";
	char readBuffer[256] = { 0 };
	DWORD writeCount = 0;
	DWORD readCount = 0;
	BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
	ret = ReadFile(hFile, readBuffer, strlen(readBuffer), &readCount, &ol2);
	if (!ret) {
		DWORD err = GetLastError();
		if (ERROR_IO_PENDING == err) {
			HANDLE handle[2];
			handle[0] = ol1.hEvent;
			handle[1] = ol2.hEvent;
			DWORD objnum = WaitForMultipleObjects(2, handle, TRUE, INFINITE);
			switch (objnum) {
			case WAIT_OBJECT_0: {
				printf("文件写入操作完毕\n");				
			}
			case WAIT_OBJECT_0 + 1: {
				printf("文件读取操作完毕:%s\n", readBuffer);				
			}
			default:
				break;
			}		
			CloseHandle(hFile);
			return 0;
		}
		else {
			printf("File failed\n");
		}
	}

	CloseHandle(ol1.hEvent);
	CloseHandle(ol2.hEvent);
	CloseHandle(hFile);

	return 1;
}

3.使用可提醒IO 

这种方法允许我们向一个设备发出多个IO请求,发出IO请求的线程必须对结果进行处理

示例代码:

#include <Windows.h>
#include <stdio.h>

char readBuffer[256] = { 0 };
char writeBuffer[256] = "1234567890";

VOID WriteFunc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
	printf("文件写入结束\n");
}

VOID ReadFunc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
	printf("%s\n", readBuffer);
}

int main() {
	HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		printf("无法打开文件。错误码:%d\n", GetLastError());
		return 0;
	}
	OVERLAPPED ol2 = { 0 };
	
	BOOL ret = WriteFileEx(hFile, writeBuffer, strlen(writeBuffer), &ol2, (LPOVERLAPPED_COMPLETION_ROUTINE)WriteFunc);
	SleepEx(1000, true);
	if (!ret) {
		DWORD err = GetLastError();
		if (ERROR_IO_PENDING == err) {
			printf("文件写入中!!!\n");
		}
		else {
			printf("文件写入失败!!!\n");
			CloseHandle(hFile);
			return 0;
		}
	}

	ret = ReadFileEx(hFile, readBuffer, sizeof(readBuffer), &ol2, (LPOVERLAPPED_COMPLETION_ROUTINE)ReadFunc);
	//SleepEx(1000, true);
	WaitForSingleObjectEx(hFile, INFINITE, true);
	if (!ret) {
		DWORD err = GetLastError();
		if (ERROR_IO_PENDING == err) {
			printf("文件读取中!!!\n");
		} else {
			printf("文件读取失败!!!\n");
			CloseHandle(hFile);
			return 0;
		}
	}
	
	CloseHandle(hFile);

	return 1;
} 

4.使用IO完成端口

这种方法允许我们向一个设备同时发出多个IO请求。它允许一个线程发出IO请求,另一个线程对结果进行处理,推荐使用,伸缩性和灵活性都很好,IO完成端口的初衷就是与线程池配合使用
示例代码:

#include <Windows.h>
#include <stdio.h>

int main() {
	char userName[256] = { 0 };
	HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	if (INVALID_HANDLE_VALUE == hFile) {
		printf("文件创建失败,错误码:%d\n", GetLastError());

		return 1;
	}
	HANDLE hCicp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (NULL == hCicp) {
		printf("创建CreateIoCompletionPort失败,错误码:%d\n", GetLastError());
		CloseHandle(hFile);

		return 1;
	}
	ULONG_PTR CK_READ = 0;
	CreateIoCompletionPort(hFile, hCicp, CK_READ, 0);

	OVERLAPPED ol = { 0 };
	ReadFile(hFile, userName, 256, NULL, &ol);
	
	DWORD transferedByte = 0;
	void* lpContext = NULL;
	OVERLAPPED* pOl = NULL;
	while (GetQueuedCompletionStatus(hCicp, &transferedByte, (LPDWORD)&lpContext, &pOl, INFINITE)) {
		printf("%s\n", userName);
	}

	CloseHandle(hFile);
	CloseHandle(hCicp);

	return 0;
}

四、串行模型与并发模型

CPU是通过时间片轮转的方式执行线程,比如说,A线程执行一段时间,然后切换到B线程,B线程执行一段时间,再切换到C线程,C线程执行一段时间,然后再切换到A线程;当CPU执行的线程过多时,可以发现,CPU处理排队线程的请求远远小于线程之间来回切换的时间,这个时候CPU貌似十分繁忙(忙着切换线程,没有去处理线程请求) 

1.IO完成端口

(1).CreateIoCompletionPort函数

CreateIoCompletionPort 函数是 Windows API 中的一个函数,用于创建输入/输出完成端口(I/O Completion Port),这是一种高效的异步 I/O 操作管理机制,常用于多线程的异步 I/O 编程。

函数原型:

HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,
    HANDLE ExistingCompletionPort,
    ULONG_PTR CompletionKey,
    DWORD NumberOfConcurrentThreads
);

参数解释:

  • ExistingCompletionPort:可选参数,如果要将文件句柄关联到现有的 I/O 完成端口,则传递现有的 I/O 完成端口句柄,否则传递 NULL
  • FileHandle:要与 I/O 完成端口关联的文件句柄。
  • CompletionKey:关联的完成键,是一个用户定义的值,通常用于标识关联的文件句柄。
  • NumberOfConcurrentThreads:指定可以同时执行 I/O 操作的线程数目。

返回值:

如果函数调用成功,将返回新创建的 I/O 完成端口的句柄(HANDLE)。如果函数失败,则返回 NULL,您可以通过调用 GetLastError 获取错误信息。

(2).GetQueuedCompletionStatuse函数

GetQueuedCompletionStatus 函数是 Windows API 中用于从 I/O 完成端口 (I/O Completion Port) 中获取已完成的 I/O 操作的函数。它通常用于与异步 I/O 操作相关的多线程编程,以便检查和处理已完成的 I/O 操作结果。

函数原型:

BOOL GetQueuedCompletionStatus(
  HANDLE       CompletionPort,
  LPDWORD      lpNumberOfBytesTransferred,
  PULONG_PTR   lpCompletionKey,
  LPOVERLAPPED *lpOverlapped,
  DWORD        dwMilliseconds
);

 参数解释:

  • CompletionPort:要从中获取已完成 I/O 操作的 I/O 完成端口句柄。
  • lpNumberOfBytesTransferred:用于接收已传输字节数的指针。如果不关心传输的字节数,可以传递 NULL
  • lpCompletionKey:用于接收已完成 I/O 操作关联的完成键的指针。完成键通常用于标识 I/O 操作的类型或源。
  • lpOverlapped:用于接收指向 OVERLAPPED 结构的指针,该结构与已完成的 I/O 操作相关联。如果不关心此参数,可以传递 NULL
  • dwMilliseconds:等待时间,以毫秒为单位。如果没有已完成的 I/O 操作,函数将阻塞等待指定的时间。如果传递零,函数将立即返回,如果传递 INFINITE,函数将无限期地等待。

返回值:

如果函数调用成功并且获取了已完成的 I/O 操作,它将返回 TRUE。如果函数调用失败或等待超时,它将返回 FALSE。您可以通过调用 GetLastError 获取错误信息。

(3).PostQueuedCompletionStatus函数

PostQueuedCompletionStatus 函数是 Windows API 中用于将已完成的 I/O 操作结果(或其他自定义完成状态)添加到 I/O 完成端口 (I/O Completion Port) 队列的函数。它通常用于多线程编程中,用于将异步操作的结果通知给使用 I/O 完成端口的线程。 

函数原型:

BOOL PostQueuedCompletionStatus(
  HANDLE       CompletionPort,
  DWORD        dwNumberOfBytesTransferred,
  ULONG_PTR    dwCompletionKey,
  LPOVERLAPPED lpOverlapped
);

参数解释:

  • CompletionPort:要将完成状态添加到的 I/O 完成端口句柄。
  • dwNumberOfBytesTransferred:指定已传输的字节数。通常用于通知 I/O 完成端口关于 I/O 操作的结果。
  • dwCompletionKey:指定完成状态的关键字,通常用于标识 I/O 操作的类型或源。
  • lpOverlapped:指向 OVERLAPPED 结构的指针,通常用于关联已完成的 I/O 操作。可以为 NULL

返回值:

如果函数调用成功,将返回 TRUE。如果函数调用失败,将返回 FALSE。您可以通过调用 GetLastError 获取错误信息。

示例代码:

#include <Windows.h>
#include <stdio.h>
#include <process.h>

HANDLE hCicp = NULL;
unsigned int ThreadFunc(void* arg) {
	getchar();
	ULONG_PTR key = 10;
	OVERLAPPED ol = { 0 };
	PostQueuedCompletionStatus(hCicp, 4, 10, &ol);

	return 0;
}

int main() {
	char userName[256] = { 0 };
	HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	if (INVALID_HANDLE_VALUE == hFile) {
		printf("文件创建失败,错误码:%d\n", GetLastError());

		return 1;
	}
	hCicp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (NULL == hCicp) {
		printf("创建CreateIoCompletionPort失败,错误码:%d\n", GetLastError());
		CloseHandle(hFile);

		return 1;
	}
	ULONG_PTR CK_READ = 0;
	CreateIoCompletionPort(hFile, hCicp, CK_READ, 0);
	unsigned int uiThreadID = 0;
	HANDLE hThread =  (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadFunc, NULL, 0, &uiThreadID);

	OVERLAPPED ol = { 0 };
	ReadFile(hFile, userName, 256, NULL, &ol);

	DWORD transferedByte = 0;
	void* lpContext = NULL;
	OVERLAPPED* pOl = NULL;
	while (GetQueuedCompletionStatus(hCicp, &transferedByte, (LPDWORD)&lpContext, &pOl, INFINITE)) {
		if (lpContext != NULL && 10 == (unsigned long)lpContext) {
			printf("IO端口完成退出!\n");
			break;
		}
		
		printf("%s\n", userName);
	}
	CloseHandle(hThread);
	CloseHandle(hFile);
	CloseHandle(hCicp);

	return 0;
}

 

posted @ 2023-09-17 16:19  TechNomad  阅读(147)  评论(0)    收藏  举报