一个简单的IOCP(IO完成端口)服务器/客户端类(中文版)

一个简单的IOCPIO完成端口)服务器/客户端类

——A simple IOCP Server/Client Class By spinoza

原文【选自CodeProject

http://www.codeproject.com/Articles/10330/A-simple-IOCP-Server-Client-Class

源代码:

http://www.codeproject.com/KB/IP/iocp_server_client/IOCP-Demo.zip

http://www.codeproject.com/KB/IP/iocp_server_client/IOCP-SRC.zip

 

——译: Ocean Email: [email protected]

http://blog.csdn.net/witch_soya/article/details/6978856

http://blog.csdn.net/dongzhongshu/article/details/5645179

http://blog.csdn.net/babelan/article/details/4588608

http://820808.blog.51cto.com/328558/66664

http://820808.blog.51cto.com/328558/68200

This source code uses the advanced IOCP technology which can efficiently serve multiple clients. It also presents some solutions to practical problems that arise with the IOCP programming API, and provides a simple echo client/server with file transfer.

 

1.1要求

l  本文希望读者对C++TCP/IPSocket编程,MFC以及多线程比较熟悉

l  源代码使用Winsock2.0以及IOCP技术,因此需要:

Windows NT/2000 or later: Requires Windows NT 3.5 or later.

Windows 95/98/ME: Not supported.

Visual C++ .NET, or a fully updated Visual C++ 6.0.

1.2 摘要

当你开发不同类型的软件时,你总会需要进行C/S的开发。完成一个完善的C/S代码对于编码人员来说是一件困难的事情。本文给出了一个简单的但是却是却十分强大的C/S源代码,他可以扩展成任何类型的C/S程序。源代码使用了IOCP技术,该技术可以有效地处理多客户端。 IOCP 对于“一个客户端一个线程”所有面临的瓶颈(或者其他)问题提出了一种有效的解决方案,他只使用少量的执行线程以及异步的输入输出、接受发送。IOCP计数被广泛的用于各种高性能的服务器,如Apache等。源代码同时也提供了一组用于处理通信的常用功能以及在C/S软件中经常用到功能,如文件接受/传输功能以及逻辑线程池操作。本文将主要关注一种围绕IOCP API在实际中的解决方案,以及呈现源代码的完整文档。随后,我将展示一个可以处理多连接和文件传输的echo C/S程序。

2.1 介绍

本文阐述了一个类,他可以被同时用于客户端和服务器端代码。这个类使用IOCP(Input Output Completion Ports)以及异步(non-blocking) 功能调用。源代码是基于很多其他源代码和文章的。

 

使用此源代码,你可以:

·为多主机进行链接、或者链接到多主机的客户端和服务器

·异步的发送和接受文件

·创建和管理一个逻辑工作线程池,他可以处理繁重的C/S请求或计算

 

找到一段完善的却又简单的、可以处理C/S通信的代码是一件困难的事情。在网络上找到的代码要么太过于复杂(可能多于20个类),或者不能提供有效的效率。本代码就是以简单为设计理念的,文档也尽可能的完善。在本文中,我们可以很简单的使用由Winsock 2.0提供的IOCP技术,我也会谈到一些在编码时会遇到的棘手的问题以及他们的解决方法。

2.2 异步输入输出完成端口(IOCP)的介绍

一个服务器程序要是不能同时处理多客户端,那么我们可以说这个程序是毫无意义的,而我们为此一般会使用异步I/O调用或者多线程技术去实现。从定义上来看,一个异步I/O调用可以及时返回而让I/O挂起。在同一时间点上,I/O异步调用必须与主线程进行同步。这可以使用各种方式,同步主要可以通过以下实现:

·使用事件(events) 只要异步调用完成,一个Signal就会被Set。这种方式主要的缺点是线程必须去检查或者等待这个eventSet

·使用GetOverlappedResult功能。这种方式和上面的方式有同样的缺点。

·使用异步例程调用(APC)。对于这种方式有几个缺点。首先,APC总是在调用线程的上下文被调用的,其次,为了执行APCs,调用线程必须被挂起,这被成为alterable wait state

·使用IOCP。这种方式的缺点是很多棘手的编码问题必须得以解决。编写IOCP可能一件让人持续痛苦的事情。

2.2.1 为什么使用IOCP?

使用IOCP,我们可以克服”一个客户端一个线程”的问题。我们知道,这样做的话,如果软件不是运行在一个多核及其上性能就会急剧下降。线程是系统资源,他们既不是无限制的、也不是代价低廉的。

IOCP提供了一种只使用一些(I/O worker)线程去“相对公平地”完成多客户端的”输入输出”。线程会一直被挂起,而不会使用CPU时间片,直到有事情做为止。

2.3 什么是IOCP?

我们已经提到IOCP 只不过是一个线程同步对象,和信号量(semaphore)相似,因此IOCP并不是一个复杂的概念。一个IOCP 对象是与多个I/O对象关联的,这些对象支持挂起异步IO调用。知道一个挂起的异步IO调用结束为止,一个访问IOCP的线程都有可能被挂起。

3. IOCP是如何工作的?

要获得更多的信息,我推荐其他的一些文章(译者注,在CodeProject

当使用IOCP时,你必须处理三件事情:将一个Socket关联到完成端口,创建一个异步I/O调用,与线程进行同步。为了获得异步IO调用的结果,比如,那个客户端执行了调用,你必须传入两个参数:the CompletionKey 参数, OVERLAPPED结构。

3.1 CompletionKey参数

第一个参数是CompletionKey,一个DWORD类型值。你可以任何你希望的标识值,这将会和对象关联。一般的,一个包含一些客户端特定对象的结构体或者对象的指针可以使用此参数传入。在源代码中,一个指向ClientContext结构被传到CompletionKey参数中。

3.2 OVERLAPPED 参数

这个参数一般用于传入被异步IO调用使用的内存buffer。我们必须主意这个数据必须是锁在内存的,不能被换页出物理内存。我们稍后会讨论这个。

3.3 socket与完成端口绑定

一旦一个完成端口创建,我们就可以使用CreateToCompletionPort方法去将socket绑定到完成端口,这看起来像这面这样:

BOOL IOCPS::AssociateSocketWithCompletionPort(SOCKET socket,

              HANDLE hCompletionPort, DWORD dwCompletionKey)

   {

      HANDLE h = CreateIoCompletionPort((HANDLE) socket,

            hCompletionPort, dwCompletionKey, m_nIOWorkers);

      return h == hCompletionPort;

   }

3.4 创建异步IO调用

创建真正的异步调用:可以调用WSASend,WSARecv。他们也需要一个WSABUF参数,这个参数包含一个指向被使用的buffer的指针。首要规则是,当服务器/客户端试图调用一个IO操作时,他们不是直接的操作,而是先被传送到完成端口,这将会被IO工作线程去完成操作。之所以要这样做,我们是想要CPU调用更加公平。 IO调用可以通过发送(Post)状态到完成端口来实现,看一下代码:

         BOOL bSuccess = PostQueuedCompletionStatus(m_hCompletionPort,

                      pOverlapBuff->GetUsed(),

                      (DWORD) pContext, &pOverlapBuff->m_ol);

3.5 与线程同步

IO工作线程同步是通过GetQueuedCompletionStatus方法完成的(代码如下)。该方法也提供了CompleteKey参数以及OVERLAPPED参数:

BOOL GetQueuedCompletionStatus(

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
源码,有注释 // IOCPServer.h: interface for the CIOCPServer class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_) #define AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #pragma once #pragma comment(lib,"Ws2_32.lib") #pragma comment(lib,"Mswsock.lib") #include <winsock2.h> #include <Mswsock.h> #define BUFFER_SIZE 1024*4//I/O请求的缓冲区大小 #define MAX_THREAD 2 //I/O服务器线程数量 //缓冲区对象,它包含了在套接字上处理I/O操作的必要信息 struct CIOCPBuffer { WSAOVERLAPPED ol; SOCKET sClient; //AcceptEx接收的客户套接字 char *buff; //I/0操作使用的缓冲区 int nLen; //buff缓冲区(使用的)大小 ULONG nSequenceNumber;//此I/O的序列号 int nOperation; //操作型 CIOCPBuffer *pNext; }; //per-Handle数据,它包含了一个套接字的信息 struct CIOCPContext { SOCKET s; //套接字句柄 SOCKADDR_IN addrLocal; //连接的本地地址 SOCKADDR_IN addrRemote;//连接的远程地址 BOOL bClosing; //套接字是否关闭 int nOutStandingRecv; //此套接字上抛出的重叠操作的数量 int nOutStandingSend; ULONG nReadSequence; //安排给接受的下一个序列号 ULONG nCurrentReadSequence;//当前要读的序列号 CIOCPBuffer *pOutOfOrderReads;//记录没有按顺序完成的读I/O CRITICAL_SECTION Lock; //保护这个结构 CIOCPContext *pNext; }; class CIOCPServer //处理线程 { public: CIOCPServer(void); ~CIOCPServer(void); //开始服务 BOOL Start(int nPort=4567,int nMaxConnections=2000, int nMaxFreeBuffers=200,int nMaxFreeContexts=100,int nInitialReads=4); //停止服务 void Shutdown(); //关闭一个连接和关闭所有连接 void CloseAConnection(CIOCPContext *pContext); void CloseAllConnection(); //取得当前的连接数量 ULONG GetCurrentConnection() { return m_nCurrentConnection; }; //向指定客户发送文本 BOOL SendText(CIOCPContext *pContext,char *pszText,int nLen); protected: //申请和释放缓冲区对象 CIOCPBuffer*AllocateBuffer(int nLen); void ReleaseBuffer(CIOCPBuffer *pBuffer); //申请和释放套接字上下文 CIOCPContext *AllocateContext(SOCKET s); void ReleaseContext(CIOCPContext *pContext); //释放空闲缓冲区对象列表和空闲上下文对象列表 void FreeBuffers(); void FreeContexts(); //向连接列表中添加一个连接 BOOL AddAConnection(CIOCPContext *pContext); //插入和移除未决的接受请求 BOOL InsertPendingAccept(CIOCPBuffer *pBuffer); BOOL RemovePendingAccept(CIOCPBuffer *pBuffer); //取得下一个要读取的 CIOCPBuffer *GetNextReadBuffer(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //投递接受I/O,发送I/0,接受I/O BOOL PostAccept(CIOCPBuffer *pBuffer); BOOL PostSend(CIOCPContext *pContext,CIOCPBuffer *pBuffer); BOOL PostRecv(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //事件通知函数 void HandleIO(DWORD dwKey,CIOCPBuffer *pBuffer,DWORD dwTrans,int nError); //建立一个新的连接 virtual void OnConnectionEstablished(CIOCPContext *pContext,CIOCPBuffer*); //一个连接关闭 virtual void OnConnectionClosing(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上发生错误 virtual void OnConnectionError(CIOCPContext *pContext,CIOCPBuffer*,int nError); //在一个连接上的读操作完成 virtual void OnReadCompleted(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上写操作完成 virtual void OnWriteCompleted(CIOCPContext *pContext,CIOCPBuffer*); protected: //记录空闲结构信息 CIOCPBuffer *m_pFreeBufferList; CIOCPContext *m_pFreeContextList; int m_nFreeBufferCount; int m_nFreeContextCount; CRITICAL_SECTION m_FreeBufferListLock; CRITICAL_SECTION m_FreeContextListLock; //记录抛出的Accept请求 CIOCPBuffer *m_pPendingAccepts; long m_nPendingAcceptCount; CRITICAL_SECTION m_PendingAcceptsLock; //记录连接列表 CIOCPContext *m_pConnectionList; int m_nCurrentConnection; CRITICAL_SECTION m_ConnectionListLock; //用于投递Accept请求 HANDLE m_hAcceptEvent; HANDLE m_hRepostEvent; LONG m_nRepostCount; //服务器监听端口 int m_nPort; int m_nInitialAccepts; int m_nInitialReads; int m_nMaxAccepts; int m_nMaxSends; int m_nMaxFreeBuffers; int m_nMaxFreeContexts; int m_nMaxConnections; //监听线程 HANDLE m_hListenThread; //完成端口句柄 HANDLE m_hCompletion; //监听套接字句柄 SOCKET m_sListen; //AcceptEx函数地址 LPFN_ACCEPTEX m_lpfnAcceptEx; //GetAcceptExSockaddrs函数地址 LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockaddrs; //用于通知监听线程退出 BOOL m_bShutDown; //记录服务是否启动 BOOL m_bServerStarted; private://线程函数 static DWORD WINAPI _ListenThreadProc(LPVOID lpParam); static DWORD WINAPI _WorkerThreadProc(LPVOID lpParam); }; #endif // !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_)
IOCP(Input/Output Completion Port)是一种高效的异步I/O模型,可用于实现高性能的服务器。在Windows系统中,可以使用IOCP API来创建一个IOCP服务器。 下面是一个简单IOCP服务器的示例代码: ```c #include <stdio.h> #include <winsock2.h> #include <windows.h> #define PORT 12345 #define MAX_CLIENTS 10 #define BUFFER_SIZE 1024 typedef struct { OVERLAPPED overlapped; SOCKET socket; WSABUF wsaBuf; CHAR buffer[BUFFER_SIZE]; DWORD bytesTransferred; DWORD flags; } PER_IO_DATA, *LPPER_IO_DATA; VOID CALLBACK CompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransfered, LPOVERLAPPED lpOverlapped, DWORD dwFlags); int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建监听套接字 SOCKET listenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (listenSocket == INVALID_SOCKET) { printf("Error creating listen socket: %d\n", WSAGetLastError()); return 1; } // 绑定端口号 SOCKADDR_IN serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr.sin_port = htons(PORT); if (bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { printf("Error binding port: %d\n", WSAGetLastError()); return 1; } // 监听连接 if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) { printf("Error listening: %d\n", WSAGetLastError()); return 1; } // 创建IOCP句柄 HANDLE iocpHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (iocpHandle == NULL) { printf("Error creating IOCP handle: %d\n", GetLastError()); return 1; } // 将监听套接字绑定到IOCP句柄上 if (CreateIoCompletionPort((HANDLE)listenSocket, iocpHandle, (ULONG_PTR)listenSocket, 0) == NULL) { printf("Error binding listen socket to IOCP handle: %d\n", GetLastError()); return 1; } // 创建客户端套接字 SOCKET clientSockets[MAX_CLIENTS]; for (int i = 0; i < MAX_CLIENTS; i++) { clientSockets[i] = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (clientSockets[i] == INVALID_SOCKET) { printf("Error creating client socket: %d\n", WSAGetLastError()); return 1; } } // 接受连接 for (;;) { SOCKADDR_IN clientAddr; int clientAddrLen = sizeof(clientAddr); SOCKET clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddr, &clientAddrLen); if (clientSocket == INVALID_SOCKET) { printf("Error accepting connection: %d\n", WSAGetLastError()); continue; } // 将客户端套接字绑定到IOCP句柄上 if (CreateIoCompletionPort((HANDLE)clientSocket, iocpHandle, (ULONG_PTR)clientSocket, 0) == NULL) { printf("Error binding client socket to IOCP handle: %d\n", GetLastError()); closesocket(clientSocket); continue; } // 分配PER_IO_DATA结构体 LPPER_IO_DATA perIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA)); perIoData->socket = clientSocket; perIoData->wsaBuf.buf = perIoData->buffer; perIoData->wsaBuf.len = BUFFER_SIZE; perIoData->flags = 0; // 开始接收数据 DWORD bytesRecv; if (WSARecv(clientSocket, &perIoData->wsaBuf, 1, &bytesRecv, &perIoData->flags, &perIoData->overlapped, CompletionRoutine) == SOCKET_ERROR) { if (WSAGetLastError() != WSA_IO_PENDING) { printf("Error starting receive: %d\n", WSAGetLastError()); GlobalFree(perIoData); closesocket(clientSocket); continue; } } } // 关闭监听套接字 closesocket(listenSocket); // 释放资源 for (int i = 0; i < MAX_CLIENTS; i++) { closesocket(clientSockets[i]); } WSACleanup(); return 0; } VOID CALLBACK CompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransfered, LPOVERLAPPED lpOverlapped, DWORD dwFlags) { LPPER_IO_DATA perIoData = (LPPER_IO_DATA)lpOverlapped; if (dwErrorCode == 0 && dwBytesTransfered > 0) { printf("Received %d bytes from socket %d\n", dwBytesTransfered, perIoData->socket); } else { printf("Error receiving data: %d\n", dwErrorCode); } GlobalFree(perIoData); closesocket(perIoData->socket); } ``` 在这个示例中,我们首先创建一个监听套接字,绑定端口号并开始监听连接。然后,我们创建一个IOCP句柄,并将监听套接字绑定到IOCP句柄上。 接下来,我们创建一些客户端套接字,并在接受连接时将它们绑定到IOCP句柄上。每当有一个客户端连接到服务器时,我们为其分配一个PER_IO_DATA结构体,并使用WSARecv函数开始接收数据。在WSARecv函数中,我们将PER_IO_DATA结构体的地址传递给了overlapped参数,这样就可以在数据接收完成后得到它。 当数据接收完成后,系统会调用CompletionRoutine回调函数。在这个函数中,我们可以检查数据接收是否成功,并释放PER_IO_DATA结构体和客户端套接字。 需要注意的是,在IOCP服务器中,所有的I/O操作都是异步的,因此我们需要使用OVERLAPPED结构体来描述每一个I/O操作,并在每个I/O操作完成后启动回调函数。在这个示例中,我们使用了PER_IO_DATA结构体来扩展OVERLAPPED结构体,并包含了一些额外的信息,例如套接字、接收缓冲区等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值