I/O 完成端口实现

I/O 完成端口实现

这篇文章是继承上篇《Windows 同步设备 I/O 与异步设备 I/O》,未读过的读者可以去看看再来看这篇文章哈。

I/O完成端口接口封装

  1. 创建新的I/O完成端口

I/O完成可能是最复杂的windows内核对象了,为了创建一个I/O完成端口我们需要调用CreateIoCompletionPort函数:

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

这个函数执行两项不同的任务:它不仅会创建一个I/O完成端口,而且会将一个设备与一个I/O完成端口关联起来。为了只创建一个I/O完成端口我们可以对上面的函数进行封装:

 HANDLE CreateNewCompletionPort(DWORD threadSize)
 {
   return (CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadSize));
 }

这个函数只有一个参数threadSize,它调用了CreateIoCompletionPort来创建一个新I/O完成端口。为了只创建一个I/O完成端口前三个参数传入固定值INVALID_HANDLE_VALUE, NULL和0,第四个参数告诉I/O完成端在统一时间最多有多少线程处于可运行的状态。如果threadSize传入0,那么I/O端口会使用默认值也就是CPU数量。这个通常也是我们想要的。

  1. 将设备和I/O完成端口关联

当我们创建一个I/O完成端口的时候,系统内核实际上会创建5个不同的数据结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FQJ3urmZ-1607852667079)(https://imgkr2.cn-bj.ufileos.com/2d1e1331-ee16-490a-bf10-d1c30f620267.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=NfrwmFUykaoOYWgvNVXx1FFCWXA%253D&Expires=1607929713)]

我们封装一个函数将I/O完成端口和设备关联:

BOOL DeviceWithCopletionPort(HANDLE hCompletionPort, HANDLE hDevice, DWORD  dwCompletionKey)
{
  HANDLE h = CreateIoCompletionPort(hDevice, hCompletionPort, dwCompletionKey, 0);
  return (h == hCompletionPort);
}

hCompletionPort 为CreateNewCompletionPort创建完成端口的句柄、hDevice为设备句柄、dwCompletionKey 为完成键当I/O完成时会返回我们设置的dwCompletionKey的值。这个值操作系统并不关心但是我们自己需要关心。

  1. 如何使线程等待I/O完成通知

线程通过调用GetQueuedCompletionStatus可以将自己切换到睡眠状态,来等待设备I/O请求完成并进入完成端口。

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

CompletionPort表示线程希望对哪个完成端口进行监视。lpNumberOfBytesTransferred 为已传输的字节、lpCompletionKey为完成键、lpOverlapped 为OVERLAPPED结构地址。

使用I/O完成端口例子

  1. 直接上代码
//新建工程IOComplet工程, 拷贝代码到main.cpp
#include <Windows.h>
#include <stdio.h>
#include <thread>
#define VIOSERIAL_PORT_PATH L"E:\\code\\IOComplet\\Debug\\IOComplet1.pdb"

#define Max_Size 20
char buf[Max_Size];
HANDLE hPort;
HANDLE h;

HANDLE CreateNewCompletionPort(DWORD threadSize)
{
	return (CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadSize));
}

BOOL DeviceWithCopletionPort(HANDLE hCompletionPort, HANDLE hDevice, DWORD  dwCompletionKey)
{
	HANDLE h = CreateIoCompletionPort(hDevice, hCompletionPort, dwCompletionKey, 0);
	return (h == hCompletionPort);
}

class MyIO : public OVERLAPPED
{
public:
	MyIO(){}
	enum IOType
	{
		IO,
		IO_READ,
		IO_WRITE
	} m_ioType;
};

void func()
{
	while (true)
	{
		printf("while (true)\n");
		DWORD dwNumBytes;
		ULONG_PTR completionKey;
		MyIO * myIO;
		BOOL ret = GetQueuedCompletionStatus(hPort, &dwNumBytes, &completionKey, 
			(OVERLAPPED**)&myIO, INFINITE);
		DWORD err = GetLastError();
		if (ret)
		{
			printf("GetQueuedCompletionStatus err = %d\n", ret);
			if (completionKey == MyIO::IOType::IO)
			{
				printf("completionKey == MyIO::IOType::IO\n");
				if (myIO != NULL)
				{
					if (myIO->m_ioType == MyIO::IOType::IO_READ)
					{
						printf("myIO->m_ioType == MyIO::IOType::IO_READ\n");
						MyIO myIORead;
						ZeroMemory(&myIORead, sizeof(MyIO));
						myIORead.m_ioType = MyIO::IOType::IO_READ;
						ZeroMemory(buf, Max_Size);
						ReadFile(h, buf, Max_Size, NULL, &myIORead);
					}
				}
			}
		}
		printf("end\n");
	}
	printf("end end\n");
}

int main()
{
	h = CreateFile(VIOSERIAL_PORT_PATH, GENERIC_READ | GENERIC_WRITE, 0, NULL,
		OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); // 设备句柄
	if (h == INVALID_HANDLE_VALUE)
	{
		printf("h = %d, error=%d\n", h, GetLastError());
	}
	else
	{
		hPort = CreateNewCompletionPort(0);
		BOOL ret = DeviceWithCopletionPort(hPort, h, MyIO::IOType::IO);
		if (ret == FALSE)
		{
			printf("DeviceWithCopletionPort err = %d\n", ret);
		}

		std::thread proc(func);
		proc.detach();
		Sleep(1000);
		MyIO myIO;
		myIO.hEvent = NULL;
		myIO.Internal = 0;
		myIO.InternalHigh = 0;
		myIO.Offset = 0;
		myIO.OffsetHigh = 0;
		myIO.m_ioType = MyIO::IOType::IO_READ;
		DWORD readSize;
		BOOL retR = ReadFile(h, buf, Max_Size, &readSize, &myIO);
		if (retR == FALSE)
		{
			DWORD errRet = GetLastError();
			if (errRet == ERROR_IO_PENDING)
			{
				printf("retR == ERROR_IO_PENDING\n");
			}
		}

	}
	SleepEx(INFINITE, FALSE);
	return 0;
}

这个简单例子是为了读者能更好理解IO端口,首先这个程序以读写打开了一个文件句柄,随后创建了I/O完成端口并将文件句柄和它关联,最后利用thread创建了一个线程等待I/O完成。在线程函数func中调用了GetQueuedCompletionStatus等待I/O完成。读者应该注意到这里MyIO类继承了OVERLAPPED添加了成员m_ioType这是为了区分I/O完成的类型。这个例子只是为了读者理解I/O完成端口,在实际使用中应该穿件一个线程池来等待I/O完成。只要读者理解I/O完成端口的原理,我想读者应该不难实现一个伸缩性好的I/O完成端口的服务程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值