以前在书上看过了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);
        }
    }