完成端口php,c# AcceptEx与完成端口(IOCP)结合的示例

26ce890a978794adb115cdd2e788ce10.png

前言

在windows平台下实现高性能网络服务器,iocp(完成端口)是唯一选择。编写网络服务器面临的问题有:

1 快速接收客户端的连接。

2 快速收发数据。

3 快速处理数据。本文主要解决第一个问题。

AcceptEx函数定义

BOOL AcceptEx(

SOCKET sListenSocket,

SOCKET sAcceptSocket,

PVOID lpOutputBuffer,

DWORD dwReceiveDataLength,

DWORD dwLocalAddressLength,

DWORD dwRemoteAddressLength,

LPDWORD lpdwBytesReceived,

LPOVERLAPPED lpOverlapped

);

为什么要用AcceptEx

传统的accept函数能满足大部分场景的需要;但在某些极端条件下,必须使用acceptEx来实现。两个函数的区别如下:

1)accept是阻塞的;在一个端口监听,必须启动一个专用线程调用accept。当然也可以用迂回的方式,绕过这个限制,处理起来会很麻烦,见文章单线程实现同时监听多个端口。acceptEx是异步的,可以同时对很多端口监听(监听端口的数量没有上限的限制)。采用迂回的方式,使用accept监听,一个线程最多监听64个端口。这一点可能不是AcceptEx最大优点,毕竟同时对多个端口监听的情况非常少见。

2)AcceptEx可以返回更多的数据。a)AcceptEx可以返回本地和对方ip地址和端口;而不需要调用函数getsockname和getpeername获取网络地址了。b)AcceptEx可以再接收到一段数据后,再返回。这种做法有利有弊,一般不建议这样做。

3)AcceptEx是先准备套接字(socket)后接收。为了应对突发的连接高峰,可以多次投放AcceptEx。accept是事后建立SOCKET,就是tcp三次握手完成后,accept调用才返回,再生成socket。生成套接字是相对比较耗时的操作,accept的方式无法及时处理突发连接。对于AcceptEx的处理方式为建议做如下处理:一个线程负责创建socket,一个线程负责处理AcceptEx返回。

以上仅仅通过文字说明了AcceptEx的特点。下面通过具体代码,逐一剖析。我将AcceptEx的处理封装到类IocpAcceptEx中。编写该类时,尽量做到高内聚低耦合,使该类可以方便的被其他模块使用。

IocpAcceptEx外部功能说明

class IocpAcceptEx

{

public:

IocpAcceptEx();

~IocpAcceptEx();

//设置回调接口。当accept成功,调用回调接口。

void SetCallback(IAcceptCallback* callback);

// 增加监听端口

void AddListenPort(UINT16 port);

//启动服务

BOOL Start();

void Stop();

。。。以下代码省略

}

#define POST_ACCEPT 1

//使用IocpAcceptEx类,必须实现该接口。接收客户端的连接

class IAcceptCallback

{

public:

virtual void OnAcceptClient(SOCKET hSocketClient, UINT16 nListenPort) = 0;

};

该类的调用函数很简单,对外接口也很明确。说明该类的职责很清楚,这也符合单一职责原则。

实现步骤说明

AcceptEx不但需要与监听端口绑定,还需要与完成端口绑定。所以程序的第一步是创建完成端口:

a)创建完成端口

m_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);

if (m_hIocp == NULL)

return FALSE;

b)监听端口创建与绑定

//生成套接字

SOCKET serverSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

if (serverSocket == INVALID_SOCKET)

{

return false;

}

//绑定

SOCKADDR_IN addr;

memset(&addr, 0, sizeof(addr));

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = INADDR_ANY ;

addr.sin_port = htons(port);

if (bind(serverSocket, (sockaddr *)&addr, sizeof(addr)) != 0)

{

closesocket(serverSocket);

serverSocket = INVALID_SOCKET;

return false;

}

//启动监听

if (listen(serverSocket, SOMAXCONN) != 0)

{

closesocket(serverSocket);

serverSocket = INVALID_SOCKET;

return false;

}

//监听端口与完成端口绑定

if (CreateIoCompletionPort((HANDLE)serverSocket, m_hIocp, (ULONG_PTR)this, 0) == NULL)

{

closesocket(serverSocket);

serverSocket = INVALID_SOCKET;

return false;

}

c)投递AcceptEx

struct AcceptOverlapped

{

OVERLAPPED overlap;

INT32 opType;

SOCKET serverSocket;

SOCKET clientSocket;

char lpOutputBuf[128];

DWORD dwBytes;

};

int IocpAcceptEx::NewAccept(SOCKET serverSocket)

{

//创建socket

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

AcceptOverlapped *ov = new AcceptOverlapped();

ZeroMemory(ov,sizeof(AcceptOverlapped));

ov->opType = POST_ACCEPT;

ov->clientSocket = _socket;

ov->serverSocket = serverSocket;

//存放网络地址的长度

int addrLen = sizeof(sockaddr_in) + 16;

int bRetVal = AcceptEx(serverSocket, _socket, ov->lpOutputBuf,

0,addrLen, addrLen,

&ov->dwBytes, (LPOVERLAPPED)ov);

if (bRetVal == FALSE)

{

int error = WSAGetLastError();

if (error != WSA_IO_PENDING)

{

closesocket(_socket);

return 0;

}

}

return 1;

}

AcceptEx是非阻塞操作,调用会立即返回。当有客户端连接时,怎么得到通知。答案是通过完成端口返回。注意有一个步骤:监听端口与完成端口绑定,就是serverSocket与m_hIocp绑定,所以当有客户端连接serverSocket时,m_hIocp会得到通知。需要生成线程,等待完成端口的通知。

d)通过完成端口,获取通知

DWORD dwBytesTransferred;

ULONG_PTR Key;

BOOL rc;

int error;

AcceptOverlapped *lpPerIOData = NULL;

while (m_bServerStart)

{

error = NO_ERROR;

rc = GetQueuedCompletionStatus(

m_hIocp,

&dwBytesTransferred,

&Key,

(LPOVERLAPPED *)&lpPerIOData,

INFINITE);

if (rc == FALSE)

{

error = 0;

if (lpPerIOData == NULL)

{

DWORD lastError = GetLastError();

if (lastError == WAIT_TIMEOUT)

{

continue;

}

else

{

assert(false);

return lastError;

}

}

}

if (lpPerIOData != NULL)

{

switch (lpPerIOData->opType)

{

case POST_ACCEPT:

{

OnIocpAccept(lpPerIOData, dwBytesTransferred, error);

}

break;

}

}

else

{

}

}

return 0;

DWORD WINAPI IocpAcceptEx::AcceptExThreadPool(PVOID pContext)

{

ThreadPoolParam *param = (ThreadPoolParam*)pContext;

param->pIocpAcceptEx->NewAccept(param->ServeSocket);

delete param;

return 0;

}

int IocpAcceptEx::OnIocpAccept(AcceptOverlapped *acceptData, int transLen, int error)

{

m_IAcceptCallback->OnAcceptClient(acceptData->clientSocket, acceptData->serverSocket);

//当一个AcceptEx返回,需要投递一个新的AcceptEx。

//使用线程池好像有点小题大做。前文已说过,套接字的创建相对是比较耗时的操作。

//如果不在线程池投递AcceptEx,AcceptEx的优点就被抹杀了。

ThreadPoolParam *param = new ThreadPoolParam();

param->pIocpAcceptEx = this;

param->ServeSocket = acceptData->serverSocket;

QueueUserWorkItem(AcceptExThreadPool, this, 0);

delete acceptData;

return 0;

}

后记

采用完成端口是提高IO处理能力的一个途径(广义上讲,通讯操作也是IO)。为了提高IO处理能力,windows提供很多异步操作函数,这些函数都与完成端口关联,所以这一类处理的思路基本一致。学会了AcceptEx的使用,可以做到触类旁通的效果。

以上就是c# AcceptEx与完成端口(IOCP)结合的示例的详细内容,更多关于c# AcceptEx与完成端口(IOCP)结合的资料请关注云海天教程其它相关文章!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IOCP(Input/Output Completion Ports)是一种Windows系统下的高性能网络编程方式,通过异步I/O模型和多线程技术来提升服务器的吞吐量和性能。下面是一个简单的IOCP服务器代码示例: ```c++ #include <iostream> #include <Winsock2.h> #include <Windows.h> #pragma comment(lib, "ws2_32.lib") #define MAX_BUFFER_SIZE 4096 #define SERVER_PORT 8080 struct ClientContext { OVERLAPPED overlapped; SOCKET socket; CHAR buffer[MAX_BUFFER_SIZE]; WSABUF wsaBuf; }; DWORD WINAPI ServerWorkerThread(LPVOID lpParam) { HANDLE iocp = (HANDLE)lpParam; DWORD numBytes = 0; ULONG_PTR completionKey = 0; LPOVERLAPPED overlapped = NULL; ClientContext* clientContext = NULL; while (TRUE) { if (!GetQueuedCompletionStatus(iocp, &numBytes, &completionKey, &overlapped, INFINITE)) { std::cout << "IOCP Error: " << GetLastError() << std::endl; continue; } clientContext = CONTAINING_RECORD(overlapped, ClientContext, overlapped); if (numBytes == 0) { closesocket(clientContext->socket); delete clientContext; continue; } std::cout << "Received data: " << clientContext->buffer << std::endl; // Process client request // Send response to client // Prepare for next I/O operation ZeroMemory(&(clientContext->overlapped), sizeof(OVERLAPPED)); clientContext->wsaBuf.buf = clientContext->buffer; clientContext->wsaBuf.len = MAX_BUFFER_SIZE; DWORD flags = 0; DWORD recvBytes = 0; int ret = WSARecv(clientContext->socket, &(clientContext->wsaBuf), 1, &recvBytes, &flags, &(clientContext->overlapped), NULL); if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { std::cout << "WSARecv Error: " << WSAGetLastError() << std::endl; closesocket(clientContext->socket); delete clientContext; } } return 0; } int main() { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cout << "Failed to initialize Winsock" << std::endl; return -1; } // Create socket SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0); if (listenSocket == INVALID_SOCKET) { std::cout << "Failed to create socket" << std::endl; return -1; } // Bind socket SOCKADDR_IN serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT); serverAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { std::cout << "Failed to bind socket" << std::endl; return -1; } // Listen if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) { std::cout << "Failed to listen" << std::endl; return -1; } // Create IOCP HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (iocp == NULL) { std::cout << "Failed to create IOCP" << std::endl; return -1; } SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); int numThreads = systemInfo.dwNumberOfProcessors * 2; for (int i = 0; i < numThreads; i++) { HANDLE threadHandle = CreateThread(NULL, 0, ServerWorkerThread, iocp, 0, NULL); CloseHandle(threadHandle); } // Associate listen socket with IOCP CreateIoCompletionPort((HANDLE)listenSocket, iocp, 0, 0); while (TRUE) { // Accept incoming client connections SOCKADDR_IN clientAddr; int addrLen = sizeof(clientAddr); SOCKET clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen); if (clientSocket == INVALID_SOCKET) { std::cout << "Failed to accept client connection" << std::endl; continue; } // Register client socket with IOCP ClientContext* clientContext = new ClientContext(); clientContext->socket = clientSocket; ZeroMemory(&(clientContext->overlapped), sizeof(OVERLAPPED)); clientContext->wsaBuf.buf = clientContext->buffer; clientContext->wsaBuf.len = MAX_BUFFER_SIZE; DWORD flags = 0; DWORD recvBytes = 0; int ret = WSARecv(clientSocket, &(clientContext->wsaBuf), 1, &recvBytes, &flags, &(clientContext->overlapped), NULL); if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { std::cout << "WSARecv Error: " << WSAGetLastError() << std::endl; closesocket(clientSocket); delete clientContext; continue; } } closesocket(listenSocket); WSACleanup(); return 0; } ``` 以上是一个简单的IOCP服务器代码示例,通过IOCP模型和多线程技术实现异步高性能的网络通信。服务器在循环中等待客户端连接请求,并通过AcceptEx函数接受客户端连接,并将相关的客户端Socket与IOCP关联。每个线程通过WaitForMultipleObjects函数等待来自客户端的I/O完成事件,并进行相应的处理。对于已连接的客户端,通过WSARecv函数接收客户端发送的数据,并根据具体业务逻辑进行处理,然后通过WSASend函数发送响应给客户端,最后继续等待下一个I/O操作的完成。这样的方式可以充分利用系统资源,提高服务器的并发性能和吞吐量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值