C++ 网络编程:完成端口IOCP

IOCP(I/O Completion Ports)是Windows操作系统提供的一种高效的异步I/O模型,主要用于处理大量并发I/O请求的应用程序。

IOCP通过以下几个关键概念来实现高效的异步I/O:

  1. Completion Port:这是一个内核对象,多个I/O操作可以与其关联。每个完成端口可以关联多个线程,用于处理I/O完成事件。
  2. I/O Request Packet (IRP):当一个异步I/O请求被发出时,操作系统会创建一个IRP来跟踪这个I/O操作。完成端口会在I/O操作完成时接收到一个包含I/O结果的消息。
  3. Worker Threads:在使用IOCP的应用程序中,通常会创建一个线程池。这些工作线程会等待从完成端口获取I/O完成的通知,然后处理相应的I/O操作。
  4. Asynchronous I/O:使用IOCP的应用程序通常会发出异步I/O请求,而不是阻塞的同步I/O请求。这允许应用程序在等待I/O操作完成的同时继续处理其他任务,从而提高了并发性能。

视频教程:【2024最新C++后端开发教程,这绝对是你看过的最好的入门到实战教程】

IOCP工作流程

  • 创建完成端口:使用CreateIoCompletionPort创建一个完成端口。
  • 关联文件句柄:使用CreateIoCompletionPort将文件句柄(如套接字或文件)与完成端口关联。
  • 发出异步I/O请求:使用异步I/O函数(如ReadFile、WriteFile)发出I/O请求,并提供OVERLAPPED结构。
  • 等待I/O完成:工作线程使用GetQueuedCompletionStatus等待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操作相对较少或可以接受阻塞等待的情况。

异步I/O

异步I/O操作不会阻塞调用线程。调用线程在发出I/O请求后可以立即继续执行其他任务,而操作系统会在I/O操作完成时通知应用程序。

特点
  • 非阻塞:调用线程不必等待I/O操作完成,可以并行处理其他任务,提高了并发性能。
  • 复杂:编程模型相对复杂,需要处理I/O完成的通知和结果。
  • 适用场景:适用于高并发、高吞吐量的应用场景,如服务器和网络编程。

windows异步IO接口

1. CreateIoCompletionPort

CreateIoCompletionPort用于创建一个新的完成端口或将一个文件句柄(如套接字或文件)关联到现有的完成端口。

HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,
    HANDLE ExistingCompletionPort,
    ULONG_PTR CompletionKey,
    DWORD NumberOfConcurrentThreads
);
  • FileHandle:要关联的文件句柄。如果为INVALID_HANDLE_VALUE,则创建一个新的完成端口。
  • ExistingCompletionPort:现有的完成端口句柄。如果创建新完成端口,此参数应为NULL。
  • CompletionKey:与文件句柄关联的完成键,完成端口通知时将返回此键。
  • NumberOfConcurrentThreads:建议并发线程数。通常设置为系统的CPU核心数。

2. ReadFile 和 WriteFile

ReadFile和WriteFile是常用的异步读写函数。它们的异步操作需要结合OVERLAPPED结构。

BOOL ReadFile(
    HANDLE hFile,
    LPVOID lpBuffer,
    DWORD nNumberOfBytesToRead,
    LPDWORD lpNumberOfBytesRead,
    LPOVERLAPPED lpOverlapped
);

BOOL WriteFile(
    HANDLE hFile,
    LPCVOID lpBuffer,
    DWORD nNumberOfBytesToWrite,
    LPDWORD lpNumberOfBytesWritten,
    LPOVERLAPPED lpOverlapped
);

3. GetQueuedCompletionStatus

GetQueuedCompletionStatus用于从完成端口获取I/O完成通知。调用此函数的线程将被阻塞,直到一个I/O操作完成或超时。

BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort,
    LPDWORD lpNumberOfBytesTransferred,
    PULONG_PTR lpCompletionKey,
    LPOVERLAPPED* lpOverlapped,
    DWORD dwMilliseconds
);
  • CompletionPort:完成端口句柄。
  • lpNumberOfBytesTransferred:接收传输的字节数。
  • lpCompletionKey:接收完成键。
  • lpOverlapped:接收指向OVERLAPPED结构的指针。
  • dwMilliseconds:等待的超时时间,以毫秒为单位。如果为INFINITE,则无限等待。

4. PostQueuedCompletionStatus

PostQueuedCompletionStatus用于将一个完成包投递到完成端口。这在模拟I/O操作或控制工作线程方面很有用。

BOOL PostQueuedCompletionStatus(
    HANDLE CompletionPort,
    DWORD dwNumberOfBytesTransferred,
    ULONG_PTR dwCompletionKey,
    LPOVERLAPPED lpOverlapped
);
  • CompletionPort:完成端口句柄。
  • dwNumberOfBytesTransferred:传输的字节数。
  • dwCompletionKey:完成键。
  • lpOverlapped:指向OVERLAPPED结构的指针。

5. ReadFileEx 和 WriteFileEx

ReadFileEx和WriteFileEx提供了另一种异步I/O接口,它们通过回调函数通知I/O操作的完成。

BOOL ReadFileEx(
    HANDLE hFile,
    LPVOID lpBuffer,
    DWORD nNumberOfBytesToRead,
    LPOVERLAPPED lpOverlapped,
    LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

BOOL WriteFileEx(
    HANDLE hFile,
    LPCVOID lpBuffer,
    DWORD nNumberOfBytesToWrite,
    LPOVERLAPPED lpOverlapped,
    LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

C++后端开发 学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!~点击文章底部点击学习资料领取(需要自取)

最近有项目要做一个高性能网络服务器,决定下功夫搞定完成端口IOCP),最终花了一个星期终于把它弄清楚了,并用C++写了一个版本,效率很不错。 但,从项目的总体需求来考虑,最终决定上.net平台,因此又花了一天一夜弄出了一个C#版,在这与大家分享。 一些心得体会: 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值