送上事例前先回忆一下IOCP的步骤
1、创建一个完完成端口
2、创建一个线程A
3、A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果,这个函数是阻塞函数
4、主线程循环调用accept等待客户端连接上来。
5、主线程里accept返回新连接建立以后,把这个新的套接字句柄用CreateIoCompletionPort关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用。 因为是异步函数,WSASend/WSARecv会马上返回, 实际的发送或者接收数据的操作有WINDOWS系统去做
6、主线程继续下一次循环,阻塞在accept这里等待客户端的连接
7、WINDOWS系统完成WSASend或者WSARecv的擦做, 把结果发送到完成端口。
8、A线程里的GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。
9、在A线程里对这些数据进行处理(如果处理过程很耗时,需要新开线程处理),然后接着发出WSASend/WSARecv , 并继续下一次循环阻塞在GetQueuedCompletionStatues()这里。
归根结底概括完成端口模型一句话:
我们不停的发出异步的WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成, WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO都完成了, 那么就在完成端口那里排成一个队列)。我们在另一个线程里从完成端口不断地取得IO操作结果,然后根据需要再发出WSASend/WSARecv IO操作。
以下献上一个不是很完善的例子, 但是是已经可以通讯的了。 简单示意了IOCP模型的基本步骤
服务端
#include <iostream>
#include <winsock2.h>
#include <winsock.h>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "kernel32.lib")
HANDLE g_hIOCP;
enum IO_OPERATION
{
IO_READ,
IO_WRITE
};
struct IO_DATA
{
OVERLAPPED Overlapped;
WSABUF Wsabuf;
int nBytes;
IO_OPERATION opCode;
SOCKET client;
};
char buffer[1024];
// 这是一个很简单的IOCP通讯。 里面有很多不和逻辑的地方。 仅供参考
DWORD WINAPI WorkerThread(LPVOID WorkThreadContext)
{
IO_DATA* lpIOContext = NULL;
DWORD nBytes = 0;
DWORD dwFlags = 0;
int nRet = 0;
DWORD dwIoSize = 0;
void* lpCompletionKey = NULL;
LPOVERLAPPED lpOverlapped = NULL;
while (true)
{
// 此处是一个阻塞函数, 当完成端口处理完client发来的数据并通知这个线程,并把数据存储到lpOverlapped里, 继续往下执行
GetQueuedCompletionStatus(g_hIOCP, &dwIoSize, (LPDWORD)&lpCompletionKey, (LPOVERLAPPED*)&lpOverlapped, INFINITE);
lpIOContext = (IO_DATA*)lpOverlapped;
if ( dwIoSize == 0 )
{
cout << "Client disonnect" << endl;
closesocket(lpIOContext->client);
delete lpIOContext;
continue;
}
if ( lpIOContext->opCode == IO_READ)
{
ZeroMemory(&lpIOContext->Overlapped, sizeof(lpIOContext->Overlapped));
lpIOContext->Wsabuf.buf = buffer;
lpIOContext->Wsabuf.len = strlen(buffer) + 1;
lpIOContext->opCode = IO_WRITE;
lpIOContext->nBytes = strlen(buffer) + 1;
dwFlags = 0;
nBytes = strlen(buffer) + 1;
nRet = WSASend(
lpIOContext->client,
&lpIOContext->Wsabuf, 1, &nBytes, dwFlags,
&(lpIOContext->Overlapped), NULL);
if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
{
cout << "WSASend failed::Reason Code::" << WSAGetLastError() << endl;
closesocket(lpIOContext->client);
delete lpIOContext;
continue;
}
memset(buffer, NULL, sizeof(buffer));
}else if ( lpIOContext->opCode == IO_WRITE )
{
// 当recv一次之后要重新recv
lpIOContext->opCode = IO_READ;
nBytes = 1024;
dwFlags = 0;
lpIOContext->Wsabuf.buf = buffer;
lpIOContext->Wsabuf.len = nBytes;
lpIOContext->nBytes = nBytes;
ZeroMemory(&lpIOContext->Overlapped, sizeof(lpIOContext->Overlapped));
// 此处是异步接收消息的
nRet = WSARecv(
lpIOContext->client,
&lpIOContext->Wsabuf, 1, &nBytes,
&dwFlags, &lpIOContext->Overlapped, NULL);
if ( nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
{
cout << "WSARecv Failed::Reason Code1::" << WSAGetLastError() << endl;
closesocket(lpIOContext->client);
delete lpIOContext;
continue;
}
// 这个打印有点问题, 可能是因为异步的问题 这里的buf打印的是错误的
cout << lpIOContext->Wsabuf.buf << endl;
}
}
return 0;
}
void main()
{
/*
为了在应用程序当中调用任何一个WinSock API函数, 首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化
此步作用就是完成对Winsock服务的初始化
*/
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建套接字
SOCKET m_socket = WSASocket( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(6000);
server.sin_addr.S_un.S_addr = htons(INADDR_ANY);
bind(m_socket, (sockaddr*)&server, sizeof(server));
listen(m_socket, 8);
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
int g_ThreadCount = sysInfo.dwNumberOfProcessors * 2;
g_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, g_ThreadCount);
for (int i = 0; i < g_ThreadCount; ++i )
{
HANDLE hThread;
DWORD dwThreadId;
hThread = CreateThread(NULL, 0, WorkerThread, 0, 0, &dwThreadId);
CloseHandle(hThread);
}
while (true)
{
// 监听服务端的socket 如果监听到将监听到的socket返回出来
SOCKET client = accept(m_socket, NULL, NULL);
cout << "Client connected." << endl;
// 将监听到的客户端的socket与完成端口关联起来。
if ( CreateIoCompletionPort((HANDLE)client, g_hIOCP, 0, 0) == NULL)
{
cout << "Binding Client Socket to IO Completion Port Failed::Reason Code :: " << GetLastError() << endl;
closesocket(client);
}
else
{
IO_DATA* data = new IO_DATA;
memset(buffer, NULL, sizeof(buffer));
memset(&data->Overlapped, 0, sizeof(data->Overlapped));
data->opCode = IO_READ;
data->nBytes = 0;
data->Wsabuf.buf = buffer;
data->Wsabuf.len = sizeof(buffer);
data->client = client;
DWORD nBytes = 1024, dwFlags = 0;
// 此处是异步接收消息的, 让关联的客户端发来消息时为给Wsabuf内复制,并通知完成端口。 完成端口会把处理的结果发送到线程。
int nRet = WSARecv(client, &data->Wsabuf, 1, &nBytes, &dwFlags, &data->Overlapped, NULL);
if ( nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
{
cout << "WSARecv Failed::Reason Code::" << WSAGetLastError() << endl;
closesocket(client);
delete data;
}
// 这个打印有点问题, 可能是因为异步的问题 这里的buf打印的是错误的
cout << data->Wsabuf.buf << endl;
}
}
closesocket(m_socket);
WSACleanup();
}
客户端:
#include <iostream>
#include <WinSock2.h>
#include <string.h>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
void main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(6000);
server.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int flag;
flag = connect(client, (sockaddr*)&server, sizeof(server));
if ( flag < 0 )
{
cout << "error!" << endl;
return;
}
while (true)
{
cout << "sent hello!!!" << endl;
char buffer[1024];
//strcpy(buffer, "hello");
strcpy_s(buffer, 10, "hello");
send(client, buffer, 1024, 0);
memset(buffer, NULL, sizeof(buffer));
cout << "recv" << endl;
int rev = recv(client, buffer, 1024, 0);
if ( rev == 0 )
{
cout << "recv nothing!" << endl;
}
cout << buffer << endl;
Sleep(1000);
}
closesocket(client);
WSACleanup();
}