以前在书上看过了IOCP,不过一直都没有写过代码。现在写的时候,着时对很多问题摸不着头脑。不过好在CSDN上有许多的对于IOCP问题的讨论帖,让我受益非浅啊,也把心中的一些迷茫解开了,下面给出的是可以运行的IOCP的C/S代码,自已试了在一个机器上开了一百来个客户端,跑起来暂时没出现问题(因为通信内容太简单了^-^)。
IOCP的三个函数:CreateIoCompletionPort、GetQueuedCompletionStatus、PostQueuedCompletionStatus;一个是用来创建想要的IOCP的HANDLE同时也是用来把我们想要的SOCKET绑定到这个HANDLE上,一个是获取IO这个HANDLE上对应的对列的状态,看有没有事件完成,一个是用来通知所有工作线程退出(这个函数我还没用到,关于这个功用是看资料上说的)。
我在写这个代码的时候,最主要的问题就是当通信完成了之后,是怎么样来判断是哪个SOCKET的哪个状态(SEND还是RECV)完成了。《WINDOWS网络编程》这本书里给的代码不是很全的哦,它的配套光盘又没有,不过好在CSDN里CB那块中有个朋友刚好帖出了这一章的代码。通过比较和一夜的思量,算是搞明白啦。主要的就是以下的数据:
1、在第二次CreateIoCompletionPort中,会传进去一个CompletionKey,这个就是要来关联到我们想要的SOCKET上的一些感兴趣的数据内容,当然最好是要一个SOCKET,也可以是其它,看自己程序的需要了。而通过GetQueueCompletionStatus的通过,就可以获得这些数据的地址了。
typedef struct _PER_HANDLE_DATA
{
SOCKET sock;
}PER_HANDLE_DATA,* LPPER_HANDLE_DATA;
2、第二个主要的数据结构就是这个了,现在真的是佩服当初设计这个结构的人啊(没办法,自己就是没想到这样利用法)。因为在POST操作(SEND或是RECV)是,都要一个OVERLAPPED,所以就把这个OVERLAPPED和要指明这次POST操作类型的代码OperationType(POST_SEND或POST_RECV)以及其它一些数据(比如接发收的缓冲)。这样子,在GetQueueCompletionStatus的时候,通过获取事件,也同时得到了OperationType和缓冲。这样,知道了通信类型,也得到了缓冲数据的缓冲区。这样就可以控制我们的通信了。
这个例子比较简单,没有复杂的数据处理过程(正在设计中,和大家交流交流)。用的是BCB的平台,不过写法上还是和VC里的一模一样的啊。
typedef struct _PER_IO_OPERATION_DATA
{
OVERLAPPED Overlapped;
WSABUF DataBuff[1];
char Buff[24];
BOOL OperationType;
}PER_IO_OPERATION_DATA,* LPPER_IO_OPERATION_DATA;
简单的客户端:
//---------------------------------------------------------------------------
#pragma hdrstop
#include <winsock2.h>
#include <stdio.h>
#include <iostream>
using namespace std;
//---------------------------------------------------------------------------
#pragma argsused
SOCKET sockClient;
struct sockaddr_in addrServer;
char buf[24];
int n = 0;
int Init();
int main(int argc, char* argv[])
{
if(Init() != 0)
goto theend;
sockClient = socket(AF_INET,SOCK_STREAM,0);
if(sockClient == INVALID_SOCKET)
{
cout<<"socket 失败"<<endl;
WSACleanup();
goto theend;
}
memset(&addrServer,0,sizeof(sockaddr_in));
addrServer.sin_family = AF_INET;
addrServer.sin_addr.s_addr = inet_addr("127.0.0.1");
addrServer.sin_port = htons(9090);
cout<<"连接服务器..."<<endl;
if(connect(sockClient,(const struct sockaddr *)&addrServer,sizeof(sockaddr)) != 0)
{
cout<<"connect 失败"<<endl;
WSACleanup();
goto theend;
}
cout<<"开始发送测试包"<<endl;
memset(buf,0,24);
while(true)
{
sprintf(buf,"第%d个包", n);
cout<<"发送:"<<buf<<endl;
if(send(sockClient,buf,strlen(buf),0) <= 0)
{
cout<<"send失败,可能连接断开"<<endl;
//break;
goto theend;
}
memset(buf,0,24);
//接收服务端应答
if(recv(sockClient,buf,24,0) <= 0)
{
cout<<"recv失败,可能连接断开"<<endl;
//break;
goto theend;
}
cout<<"服务器应答:"<<buf<<endl;
memset(buf,0,24);
Sleep(200);
n++;
}
theend:
WSACleanup();
getchar();
return 0;
}
//---------------------------------------------------------------------------
int Init()
{
WSAData wsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0)
{
cout<<"WSAStartup失败"<<endl;
return -1;
}
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
cout<<"SOCKET版本不对"<<endl;
WSACleanup();
return -1;
}
return 0;
}
服务端。
//---------------------------------------------------------------------------
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <memory.h>
#include <winsock2.h>
#include <iostream>
using namespace std;
#define RECV_POSTED 1001
#define SEND_POSTED 1002
int Init();
HANDLE hCompletionPort;
typedef struct _PER_HANDLE_DATA
{
SOCKET sock;
}PER_HANDLE_DATA,* LPPER_HANDLE_DATA;
typedef struct _PER_IO_OPERATION_DATA
{
OVERLAPPED Overlapped;
WSABUF DataBuff[1];
char Buff[24];
BOOL OperationType;
}PER_IO_OPERATION_DATA,* LPPER_IO_OPERATION_DATA;
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPort);
int main(int argc, char* argv[])
{
LPPER_HANDLE_DATA perHandleData;
LPPER_IO_OPERATION_DATA ioperdata;
SYSTEM_INFO siSys;
SOCKET sockListen;
struct sockaddr_in addrLocal;
char buf[24];
int nRet = 0;
DWORD nThreadID;
SOCKET sockAccept;
DWORD dwFlags;
DWORD dwRecvBytes;
int nReuseAddr = 1;
cout<<"初始环境..."<<endl;
if(Init() != 0)
goto theend;
//创建一个IO完成端口
cout<<"创建一个IO完成端口"<<endl;
hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
if(hCompletionPort == INVALID_HANDLE_VALUE)
{
cout<<"创建IO完成端口失败"<<endl;
goto theend;
}
//获取CPU数目
GetSystemInfo(&siSys);
//创建一定数目的工作者线程,本例中以一个处理器一个线程搭配
for(int i = 0;i<(int)siSys.dwNumberOfProcessors*2;i++)//NumberOfProcessors
{
HANDLE hThread;
hThread = CreateThread(NULL,0,ServerWorkerThread,(LPVOID)hCompletionPort,0,&nThreadID);
cout<<"创建工作者线程"<<i<<endl;
CloseHandle(hThread);
}
//创建监听SOCKET
cout<<"创建监听SOCKET"<<endl;
sockListen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
if(sockListen == SOCKET_ERROR)
{
cout<<"WSASocket错误"<<endl;
goto theend;
}
if(setsockopt(sockListen,SOL_SOCKET,SO_REUSEADDR,(const char *)&nReuseAddr,sizeof(int)) != 0)
{
cout<<"setsockopt错误"<<endl;
goto theend;
}
addrLocal.sin_family = AF_INET;
addrLocal.sin_addr.s_addr = htonl(INADDR_ANY);
addrLocal.sin_port = htons(9090);
if(bind(sockListen,(struct sockaddr *)&addrLocal,sizeof(sockaddr_in)) != 0)
{
cout<<"bind错误"<<endl;
int n = WSAGetLastError();
goto theend;
}
//准备监听
cout<<"准备监听"<<endl;
if(listen(sockListen,5)!=0)
{
cout<<"listen错误"<<endl;
goto theend;
}
while(true)
{
//接收用户连接,被和完成端口关联
sockAccept = WSAAccept(sockListen,NULL,NULL,NULL,0);
perHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
if(perHandleData == NULL)
continue;
cout<<"socket number "<<sockAccept<<"接入"<<endl;
perHandleData->sock = sockAccept;
ioperdata = (LPPER_IO_OPERATION_DATA)malloc(sizeof(PER_IO_OPERATION_DATA));
memset(&(ioperdata->Overlapped),0,sizeof(OVERLAPPED));
(ioperdata->DataBuff[0]).len = 24;
(ioperdata->DataBuff[0]).buf = ioperdata->Buff;
ioperdata->OperationType = RECV_POSTED;
if( ioperdata == NULL)
{
free(perHandleData);
continue;
}
//关联
cout<<"关联SOCKET和完成端口"<<endl;
if(CreateIoCompletionPort((HANDLE)sockAccept,hCompletionPort,(DWORD)perHandleData,1) == NULL)
{
cout<<sockAccept<<"createiocompletionport错误"<<endl;
free(perHandleData);
free(ioperdata);
continue;
}
//投递接收操作
cout<<"投递接收操作"<<endl;
WSARecv(perHandleData->sock,ioperdata->DataBuff,1,&dwRecvBytes,&dwFlags,&(ioperdata->Overlapped),NULL);
}
theend:
getchar();
return 0;
}
//---------------------------------------------------------------------------
int Init()
{
WSAData wsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0)
{
cout<<"WSAStartup失败"<<endl;
return -1;
}
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
cout<<"SOCKET版本不对"<<endl;
WSACleanup();
return -1;
}
return 0;
}
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPort)
{
HANDLE ComPort = (HANDLE)CompletionPort;
DWORD BytesTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD SendBytes,RecvBytes;
DWORD Flags;
BOOL bT;
while(TRUE)
{
//等待完成端口上SOCKET的完成
cout<<"等待完成端口上SOCKET的完成"<<endl;
bT = GetQueuedCompletionStatus(ComPort,
&BytesTransferred,(LPDWORD)&PerHandleData,
(LPOVERLAPPED *)&PerIoData,INFINITE);
//检查是否有错误产生
if(BytesTransferred == 0 &&
(PerIoData->OperationType == RECV_POSTED ||
PerIoData->OperationType == SEND_POSTED))
{
//关闭SOCKET
cout<<PerHandleData->sock<<"SOCKET关闭"<<endl;
closesocket(PerHandleData->sock);
free(PerHandleData);
free(PerIoData);
continue;
}
//为请求服务
if(PerIoData->OperationType == RECV_POSTED)
{
//处理
cout<<"接收处理"<<endl;
cout<<PerHandleData->sock<<"SOCKET :"<<PerIoData->Buff<<endl;
//回应客户端
ZeroMemory(PerIoData->Buff,24);
strcpy(PerIoData->Buff,"OK");
Flags = 0;
ZeroMemory((LPVOID)&(PerIoData->Overlapped),sizeof(OVERLAPPED));
PerIoData->DataBuff[0].len = 2;
PerIoData->DataBuff[0].buf = PerIoData->Buff;
PerIoData->OperationType = SEND_POSTED;
WSASend(PerHandleData->sock,PerIoData->DataBuff,
1,&SendBytes,0,&(PerIoData->Overlapped),NULL);
}
else //if(PerIoData->OperationType == SEND_POSTED)
{
//发送时的处理
cout<<"发送处理"<<endl;
Flags = 0;
ZeroMemory((LPVOID)&(PerIoData->Overlapped),sizeof(OVERLAPPED));
ZeroMemory(PerIoData->Buff,24);
PerIoData->DataBuff[0].len = 24;
PerIoData->DataBuff[0].buf = PerIoData->Buff;
PerIoData->OperationType = RECV_POSTED;
WSARecv(PerHandleData->sock,PerIoData->DataBuff,
1,&RecvBytes,&Flags,&(PerIoData->Overlapped),NULL);
}
}