目录
IOCP(Input/Output Completion Ports,输入输出完成端口)是Windows操作系统中一种高效的网络和文件I/O处理机制。它允许应用程序以异步方式处理多个I/O操作,同时能够减少线程的使用和提高系统的可扩展性。
一、工作原理
IOCP的核心思想是将I/O操作与具体的线程解耦,使用一个或多个完成端口来管理I/O操作的完成通知。当I/O操作(如socket的读写、文件的读写等)完成时,操作系统会将完成通知发送到与socket或文件句柄关联的完成端口。然后,一个或多个工作线程(Worker Threads)会从完成端口中取出这些完成通知,并对其进行处理。
二、关键组件
- 完成端口(Completion Port):一个系统级的队列,用于存储I/O操作的完成通知。
- 工作线程(Worker Threads):负责从完成端口中取出完成通知,并处理相应的I/O操作。
- I/O操作:如socket的accept、read、write等,以及与文件相关的读写操作。
- 关联:通过特定的API(如
CreateIoCompletionPort
)将句柄(如socket或文件句柄)与完成端口关联起来。
三、使用步骤
- 创建完成端口:使用
CreateIoCompletionPort
函数创建一个新的完成端口,或者将一个已存在的句柄(如无效句柄INVALID_HANDLE_VALUE)与完成端口关联。 - 关联句柄:使用
CreateIoCompletionPort
函数将需要异步处理的句柄(如socket句柄或文件句柄)与完成端口关联。 - 启动工作线程:创建并启动一个或多个工作线程,这些线程将循环调用
GetQueuedCompletionStatus
函数从完成端口中取出完成通知。 - 执行I/O操作:通过相应的API(如
WSARecv
、WSASend
等)执行I/O操作,并指定完成端口作为操作的完成通知目标。 - 处理完成通知:在工作线程的
GetQueuedCompletionStatus
调用中,当I/O操作完成时,该函数会返回,并带有完成通知的相关信息(如完成状态、字节传输数等)。工作线程根据这些信息处理I/O操作的结果。
四、IOCP示例
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <Mswsock.h>
#define MAXNUM 64
#define ONEPAGE 4096
#define NT_ACCEPT 0
#define NT_READ 1
struct MySocket{
OVERLAPPED m_olp;//事件
SOCKET m_socket; //接收链接的socket
char m_szBuffer[ONEPAGE]; //缓冲区
char m_nType; //网络事件的类型
};
// //sock--包大小,缓冲区,偏移量
//struct stru_pack{
// int m_nPackSize;
// char *m_pszbuf;
// int m_noffset;
//};
class TCPServer
{
public:
TCPServer();
~TCPServer();
public:
bool initNetWork(const char* szip ="127.0.0.1",short nport = 1234);
void unInitNetWork(const char *szerr = "");
bool sendData(SOCKET sock,const char* szbuf,int nlen);
bool postAccept();
void postRecv(MySocket *);
public:
static DWORD WINAPI ThreadProc(LPVOID lpvoid);
private:
SOCKET m_socklisten;
std::list<HANDLE> m_lstThread;
std::list<MySocket*> m_lstSocket;
bool m_bFlagQuit;
HANDLE m_hIOCP;
public:
};
#endif // TCPSERVER_H
tcpserver.cpp
#include "tcpserver.h"
TCPServer::TCPServer()
{
m_socklisten = 0;
m_bFlagQuit = true;
}
TCPServer::~TCPServer()
{
}
bool TCPServer::initNetWork(const char *szip, short nport)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return false;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
unInitNetWork("Could not find a usable version of Winsock.dll\n");
return false;
}
else
printf("The Winsock 2.2 dll was found okay\n");
m_socklisten = socket(AF_INET,SOCK_STREAM,0);
if(INVALID_SOCKET == m_socklisten){
unInitNetWork("socket err\n");
return false;
}
sockaddr_in addrserver;
addrserver.sin_family = AF_INET;
addrserver.sin_port = htons(nport);
addrserver.sin_addr.s_addr = 0;
if(SOCKET_ERROR == bind(m_socklisten,(const struct sockaddr*)&addrserver,sizeof(addrserver))){
unInitNetWork("bind err\n");
return false;
}
if(SOCKET_ERROR == listen(m_socklisten,10)){
unInitNetWork("listen err\n");
return false;
}
//1.创建IOCP
m_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
//2.将listen交给IOCP管理
CreateIoCompletionPort((HANDLE)m_socklisten,m_hIOCP,m_socklisten,0);
//3.创建空闲的socket
SYSTEM_INFO si;
GetSystemInfo(&si);
for(unsigned int i = 0;i < si.dwNumberOfProcessors*2;i++){
postAccept();
}
//4.创建线程池
// //创建线程
for(unsigned int i = 0;i < si.dwNumberOfProcessors*2;i++){
HANDLE hThread = CreateThread(0,0,&ThreadProc,this,0,0);
if(hThread)
m_lstThread.push_back(hThread);
}
return true;
}
bool TCPServer::postAccept()
{
MySocket *p= new MySocket;
p->m_socket = socket(AF_INET,SOCK_STREAM,0);
p->m_nType =NT_ACCEPT;
p->m_olp.hEvent = WSACreateEvent();
DWORD dwRecvNum;
if(!AcceptEx(m_socklisten,
p->m_socket,
p->m_szBuffer,
0,
sizeof(sockaddr_in)+16,
sizeof(sockaddr_in)+16,
&dwRecvNum,
&p->m_olp
)){
if(WSAGetLastError() !=ERROR_IO_PENDING){
closesocket(p->m_socket);
WSACloseEvent(p->m_olp.hEvent);
delete p;
return false;
}
}
m_lstSocket.push_back(p);
return true;
}
DWORD TCPServer::ThreadProc(LPVOID lpvoid)
{
TCPServer *pthis = (TCPServer*)lpvoid;
DWORD dwNumberOfBytesTransferred;
SOCKET sock;
MySocket* psockex;
DWORD dwFlag;
while(pthis->m_bFlagQuit){
//观察IOCP的状态
dwFlag = GetQueuedCompletionStatus(pthis->m_hIOCP,
&dwNumberOfBytesTransferred,
(PULONG_PTR )&sock,
(LPOVERLAPPED *)&psockex,
INFINITE
);
if(!dwFlag){
//如果客户端下线,需要将socket从链表中删掉
auto ite = pthis->m_lstSocket.begin();
while(ite !=pthis->m_lstSocket.end()){
if((*ite) == psockex){
closesocket(psockex->m_socket);
WSACloseEvent(psockex->m_olp.hEvent);
delete psockex;
pthis->m_lstSocket.erase(ite);
break;
}
ite++;
}
continue;
}
if(!sock ||
!psockex )
continue;
switch (psockex->m_nType) {
case NT_ACCEPT:
{
//创建空闲额socktet
pthis->postAccept();
//主动投递接收数据请求
pthis->postRecv(psockex);
//将socket 交给IOCP管理
CreateIoCompletionPort((HANDLE)psockex->m_socket,pthis->m_hIOCP,psockex->m_socket,0);
}
break;
case NT_READ:
printf("%s\n",psockex->m_szBuffer);
pthis->postRecv(psockex);
break;
default:
break;
}
}
return 0;
}
void TCPServer::postRecv(MySocket *psockex)
{
psockex->m_nType = NT_READ;
WSABUF wb;
wb.buf = psockex->m_szBuffer;
wb.len = sizeof(psockex->m_szBuffer);
DWORD dwNumberOfBytesRecvd;
DWORD dwFlag = 0;
if(WSARecv(psockex->m_socket,
&wb,
1,
&dwNumberOfBytesRecvd,
&dwFlag,
&psockex->m_olp,
0)){
if(WSAGetLastError() != WSA_IO_PENDING ){
}
}
}
void TCPServer::unInitNetWork(const char *szerr )
{
m_bFlagQuit = false;
int nThreadNum =m_lstThread.size();
while(nThreadNum-- >0){
PostQueuedCompletionStatus(m_hIOCP,0,0,0);
}
for (auto& ite:m_lstThread) {
if(WAIT_TIMEOUT == WaitForSingleObject(ite,100))
TerminateThread(ite,-1);
if(ite){
CloseHandle(ite);
ite = NULL;
}
}
m_lstThread.clear();
printf(szerr);
if(m_socklisten){
closesocket(m_socklisten);
m_socklisten = 0;
}
auto ite = m_lstSocket.begin();
while(ite !=m_lstSocket.end()){
closesocket((*ite)->m_socket);
WSACloseEvent((*ite)->m_olp.hEvent);
delete (*ite);
ite++;
}
m_lstSocket.clear();
WSACleanup();
}
bool TCPServer::sendData(SOCKET sock, const char *szbuf, int nlen)
{
if(!szbuf || nlen <=0)
return false;
//粘包
//发送包大小
if(send(sock,(char*)&nlen,sizeof(int),0)<=0)
return false;
//发送包内容
if(send(sock,szbuf,nlen,0)<=0)
return false;
return true;
}
main.cpp
#include <QCoreApplication>
#include "network/tcpserver.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
TCPServer ts;
if(ts.initNetWork())
printf("server is running......\n");
else
printf("server err\n");
return a.exec();
}
五、优点
- 高效性:IOCP能够显著减少线程的使用,因为多个I/O操作可以共享少量的工作线程。这减少了线程上下文切换的开销,并提高了系统的可扩展性。
- 灵活性:IOCP允许应用程序根据需要动态地调整工作线程的数量,以适应不同的负载情况。
- 易用性:虽然IOCP的实现相对复杂,但一旦设置好,它可以为应用程序提供强大且灵活的异步I/O处理能力。
六、缺点
- 复杂性:IOCP的实现相对复杂,需要深入理解Windows的I/O模型和线程同步机制。
- 调试难度:由于IOCP的异步特性和多线程环境,调试和排查问题可能会比较困难。
七、应用场景
IOCP适用于需要处理大量并发I/O操作的应用程序,如高性能的Web服务器、数据库服务器、文件服务器等。它可以帮助这些应用程序提高吞吐量、降低延迟,并更好地利用系统资源。