21xrx.com
2024-12-22 20:09:23 Sunday
登录
文章检索 我的文章 写文章
C++ IOCP完整代码实例
2023-07-05 14:29:48 深夜i     --     --
C++ IOCP 完整代码 实例

在多线程服务器的开发中,网络IO操作是一个重要的组成部分。为了提高服务器的网络IO性能,Microsoft Windows平台提供了一种高性能的网络IO模型——IOCP。

IOCP(Input/Output Completion Ports)是一种异步IO模型,与Linux上的Epoll类似。在Windows平台下,通过完成端口(Completion Port)来管理异步IO操作。使用IOCP模型的服务端程序,可以在单线程的情况下响应大量的客户端请求。

本文将介绍一个基于IOCP模型的C++网络服务器完整代码实例。

首先,我们需要定义一些基本的数据结构:


struct ClientContext {

  SOCKET  socket;  // 客户端套接字

  char   buffer[1024]; // 数据缓冲区

  OVERLAPPED overlapped; // 重叠结构体

  WSABUF  wbuf;   // WSABUF结构体

  DWORD  dwBytes;  // 接收/发送字节数

  DWORD  dwFlag;  // 标志位

};

struct WorkContext

  HANDLE  hIOCP;  // 完成端口句柄

  SOCKET  socket;  // 监听套接字

  WSAEVENT hEvent;  // WSAEvent句柄

  HANDLE  hThread; // 工作线程句柄

  volatile bool isRunning; // 工作线程状态

;

上述代码定义了两个结构体,ClientContext和WorkContext。其中,ClientContext包含客户端套接字、数据缓冲区、重叠结构体、WSABUF结构体、接收/发送字节数和标志位等信息;WorkContext包含完成端口句柄、监听套接字、WSAEvent句柄、工作线程句柄和工作线程状态等信息。

接下来,我们来创建TCP监听套接字,并调用CreateIoCompletionPort函数将其与完成端口关联起来:


SOCKET CreateListenSocket(const char* ip, int port) {

  SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  if (listenSocket == INVALID_SOCKET)

    return INVALID_SOCKET;

  

  sockaddr_in addr;

  addr.sin_family = AF_INET;

  addr.sin_port = htons(port);

  if (ip == nullptr) {

    addr.sin_addr.s_addr = htonl(INADDR_ANY);

  } else {

    addr.sin_addr.S_un.S_addr = inet_addr(ip);

  }

  if (bind(listenSocket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {

    closesocket(listenSocket);

    return INVALID_SOCKET;

  }

  if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {

    closesocket(listenSocket);

    return INVALID_SOCKET;

  }

  // 创建完成端口

  HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

  if (hIOCP == NULL) {

    closesocket(listenSocket);

    return INVALID_SOCKET;

  }

  // 将监听套接字关联到完成端口

  if (CreateIoCompletionPort((HANDLE)listenSocket, hIOCP, (ULONG_PTR)listenSocket, 0) == NULL) {

    closesocket(listenSocket);

    CloseHandle(hIOCP);

    return INVALID_SOCKET;

  }

  // 创建工作线程

  DWORD dwThreadId;

  HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, &dwThreadId);

  if (hThread == NULL) {

    closesocket(listenSocket);

    CloseHandle(hIOCP);

    return INVALID_SOCKET;

  }

  // 创建WorkContext对象

  WorkContext* pContext = new WorkContext();

  pContext->hIOCP = hIOCP;

  pContext->socket = listenSocket;

  pContext->hEvent = WSACreateEvent();

  pContext->hThread = hThread;

  pContext->isRunning = true;

  // 等待工作线程启动

  while (InterlockedExchangeAdd((LONG*)(&pContext->isRunning), 0) == true) {

    Sleep(10);

  }

  return listenSocket;

}

上述代码实现了TCP监听套接字的创建、绑定和监听操作,并创建了完成端口,并将监听套接字与完成端口关联起来。还启动了一个工作线程,用来处理IOCP的事件。

接下来,我们可以在工作线程中处理IOCP事件:


DWORD WINAPI WorkerThread(LPVOID lpParam) {

  WorkContext* pContext = (WorkContext*)lpParam;

  if (InterlockedExchangeAdd((LONG*)(&pContext->isRunning), 0) == false)

    return 0;

  

  InterlockedExchangeAdd((LONG*)(&pContext->isRunning), -1);

  DWORD dwBytesTransferred;

  DWORD dwCompletionKey;

  LPOVERLAPPED lpOverlapped;

  ClientContext* pClientContext;

  while (GetQueuedCompletionStatus(pContext->hIOCP, &dwBytesTransferred, (PULONG_PTR)&dwCompletionKey, &lpOverlapped, INFINITE)) {

    // 处理IOCP事件

    pClientContext = CONTAINING_RECORD(lpOverlapped, ClientContext, overlapped);

    if (dwBytesTransferred == 0) {

      // 客户端已断开

      closesocket(pClientContext->socket);

      delete pClientContext;

      continue;

    }

    if (pClientContext->dwFlag == 1)

      // 发送完成

      delete pClientContext;

      continue;

    

    // 处理接收到的数据

    pClientContext->wbuf.len = dwBytesTransferred;

    pClientContext->dwBytes = 0;

    pClientContext->dwFlag = 1;

    // 将数据发送回客户端

    WSASend(pClientContext->socket, &(pClientContext->wbuf), 1, &(pClientContext->dwBytes), 0, &(pClientContext->overlapped), NULL);

  }

  return 0;

}

上述代码实现了一个无限循环,用来不断处理IOCP事件。如果dwBytesTransferred等于0,则说明客户端已断开连接;如果pClientContext->dwFlag等于1,则说明数据已发送完成;否则,需要将数据发送回客户端。

最后,我们可以在主函数中调用WSARecv函数接收客户端连接:


void main() {

  // 创建监听套接字

  SOCKET listenSocket = CreateListenSocket("127.0.0.1", 1234);

  if (listenSocket == INVALID_SOCKET)

    return;

  

  sockaddr_in clientAddr;

  int clientAddrLen = sizeof(clientAddr);

  while (true) {

    // 接受客户端连接

    SOCKET clientSocket = accept(listenSocket, (sockaddr*)&clientAddr, &clientAddrLen);

    if (clientSocket == INVALID_SOCKET)

      continue;

    

    // 创建ClientContext对象

    ClientContext* pContext = new ClientContext();

    memset(pContext, 0, sizeof(ClientContext));

    pContext->socket = clientSocket;

    pContext->wbuf.buf = pContext->buffer;

    pContext->wbuf.len = sizeof(pContext->buffer);

    pContext->dwFlag = 0;

    // 接收客户端的数据

    WSARecv(pContext->socket, &(pContext->wbuf), 1, &(pContext->dwBytes), &(pContext->dwFlag), &(pContext->overlapped), NULL);

  }

}

上述代码使用accept函数接收客户端的连接,接收到连接后,创建一个ClientContext对象,并调用WSARecv函数接收客户端的数据。

到这里,基于IOCP模型的C++网络服务器完整代码实例就介绍完了。在实际实现中,还需要考虑很多细节,如异常处理、内存泄漏等,但基本原理与代码实现与上述类似。通过使用IOCP模型,可以很好地提高服务器的网络IO性能。

  
  

评论区

{{item['qq_nickname']}}
()
回复
回复