IOCP完成端口模型

//iocp.cpp
//VS2012
//主要参考windows网络编程第二版第五章及各个关于完成端口的博客解释
#include"iocp.h"

CIocp::CIocp(){
    m_nWorkerThread = new HANDLE[16];
    m_pkey = new COMPLETIONKEY;
}
void CIocp::main_process(){
    if(0!=wrap_CreateNewIoCompletionPort(&m_hIOCompletionPort))
        return;
    int m_nThreads = 2 * wrap_GetProcessorsNumber();//处理器数目2倍的线程
    for(int i=0;i<m_nThreads;i++){
        /*int* ID = new int;
        *ID = i;*/
        m_nWorkerThread[i]=(HANDLE)_beginthreadex(NULL,0,_WorkerThreads,this,0,0);
    }
    if(wrap_WSAStartup()!=0)
        return;
    m_sockListen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
    m_pkey->s = m_sockListen;
    //绑定完成端口
    CreateIoCompletionPort((HANDLE)m_sockListen,m_hIOCompletionPort,(ULONG_PTR)m_pkey,0);

    //addrinfo获取本机地址信息并绑定
    addrinfo *serverinfo,*p;
    if(0!=wrap_getaddrinfo(NULL,PORT,&serverinfo))
        return;
    for(p=serverinfo;p!=NULL;p=p->ai_next)
        if(SOCKET_ERROR!=bind(m_sockListen,serverinfo->ai_addr,serverinfo->ai_addrlen))
            break;
        else err_no("bind");
    freeaddrinfo(serverinfo);
    if(p==NULL) err_no("all bind");

    listen(m_sockListen,5);
    //这部分主要用于获得函数AcceptExnmm,避免直接调用AcceptEx时的WSAIoctl开销
    GUID guidAcceptEx = WSAID_ACCEPTEX;
    DWORD dwBytes = 0;
    if(WSAIoctl(m_sockListen,SIO_GET_EXTENSION_FUNCTION_POINTER,
         &guidAcceptEx,sizeof(guidAcceptEx),&m_lpfnAcceptEx,sizeof(m_lpfnAcceptEx),
         &dwBytes,NULL,NULL)!=0){
            err_no("wsaioctl");
    }

    //投递Accept,并且将标识此次投递的IO_OPERATION_DATA传入,取出时可凭此获得与该次投递有关的信息
    int i=200;
    while(i--){
        IO_OPERATION_DATA* pcompletionkey = new IO_OPERATION_DATA;
        ASY_accept(pcompletionkey);

    }
    //工作时间
    Sleep(100000);
    //对每个线程发送关闭信号
    for(int i =0;i<m_nThreads;i++){
        IO_OPERATION_DATA* pIOData = new IO_OPERATION_DATA;
        pIOData->t_info=0x00;
        PostQueuedCompletionStatus(m_hIOCompletionPort,-1,(ULONG_PTR)m_pkey,&pIOData->overlapped);
    }
    //线程数通常比较少,一个wait足够
    WaitForMultipleObjects(m_nThreads,m_nWorkerThread,true,INFINITE);
    closesocket(m_sockListen);
    CloseHandle(m_hIOCompletionPort);
}

unsigned int __stdcall CIocp::_WorkerThreads(PVOID pm){
    /*int* tmp = (int*)pm;
    int ID = *tmp;
    delete tmp;*/
    CIocp* CI  =(CIocp*)pm;
    PCOMPLETIONKEY  lpContext = NULL; 

    LPOVERLAPPED    pOverlapped = NULL; 
    DWORD       dwBytesTransfered = -1;
    IO_OPERATION_DATA *iod = NULL;
    //进入睡眠,等待请求完成时被唤醒,INFINITE将等待时间设定为无限长
    for(;;){
        BOOL bReturn  =  GetQueuedCompletionStatus(CI->GetIoCompletionPort(),&dwBytesTransfered,(PULONG_PTR)&lpContext,&pOverlapped,INFINITE); //key 用地址的指针
        //取出的结构里包含了投递这个请求时传入的信息,利用t_info知道是哪一个请求已经完成了
        iod = (IO_OPERATION_DATA*)pOverlapped;
        //这里的标志都是表示 已完成 的操作
        switch(iod->t_info){
        case ACCEPT:{//已收到新连接
                    //printf("%d:accept complete\n",ID);
                    //接着投递接收请求
                    CI->ASY_recv(iod->s,iod);
                    }
        case READ://已完成读请求
        //          printf("%d:read complete\n",ID);
        //          printf("%d:%s\n",iod->s,iod->buffer);
        //          //投递一个回复
        //          CI->ASY_send(iod->s,iod);
        //          break;
        //        
        //case WRITE://已完成写请求
        //          printf("%d:write complete\n\n",ID);
                    closesocket(iod->s);
                    if(!WSACloseEvent(iod->overlapped.hEvent))
                        err_no("close event");
                    CI->ASY_accept(iod);
                    break;

        default:
            printf("break\n");
            delete iod;
            return 0;
            break;
        }
    }
    return 0;

}

void CIocp::ASY_send(SOCKET s,IO_OPERATION_DATA* p){
    //重用已分配的内存,不要浪费
    p->t_info = WRITE;
    sprintf(p->buffer,"hallo");
    p->dataBuf.buf = p->buffer;
    p->dataBuf.len = sizeof(p->buffer);
    //重新设置为未传信状态
    WSAResetEvent(p->overlapped.hEvent);
    DWORD flags=0;
    DWORD size = sizeof(p->dataBuf);
    //再次投递recv请求,并且将信息传入
    //printf("send client:%d\n",p->s);
    if(0!=WSASend(p->s,&p->dataBuf,1,&size,flags,&p->overlapped,NULL)){
        int err;
        if((err=WSAGetLastError())==WSA_IO_PENDING)
            printf("send pending\n");
        else printf("send:%d\n",err);
    }
    else
    {
        printf("sended\n\n");
        closesocket(p->s);
        ASY_accept(p);
    }
}


void CIocp::ASY_recv(SOCKET s,IO_OPERATION_DATA* p){
    //基本和send一样
    p->t_info = READ;
    p->dataBuf.buf = p->buffer;
    p->dataBuf.len = sizeof(p->buffer);
    if(!WSAResetEvent(p->overlapped.hEvent))//重设事件状态
        err_no("reset event");
    DWORD flags=0;
    WSARecv(p->s,&p->dataBuf,1,NULL,&flags,&p->overlapped,NULL);
    if(WSAGetLastError()!=WSA_IO_PENDING)
        err_no("recv");
}

void CIocp::ASY_accept(IO_OPERATION_DATA* p){
    p->s = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
    p->t_info = ACCEPT;
    DWORD dwBytes = 0;
    memset(&p->overlapped, 0, sizeof (p->overlapped));
    //设置事件
    p->overlapped.hEvent = WSACreateEvent();
    if(p->overlapped.hEvent == WSA_INVALID_EVENT)
        err_no("create event");
    //通过指针调用AcceptEx
    if(0==m_lpfnAcceptEx(m_sockListen,p->s,p->buffer,BUFFER_SIZE-(sizeof(SOCKADDR_IN)+16)*2,sizeof(SOCKADDR_IN)+16,sizeof(SOCKADDR_IN)+16,&dwBytes,&p->overlapped)){
        if(WSAGetLastError()==WSA_IO_PENDING)
            printf("pending……\n");
        printf_time();
    }else
        printf("notpending***\n");
}
//iocp.h
#pragma once
#pragma warning(disable:4996)
#include"wrap_sock.h"
#include"err_no.h"
#include<iostream>
#include<process.h>
#include <mswsock.h>
#define PORT "3490"
//#define WSAID_ACCEPTEX \
//        {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
#define OPERATION_TYPE int
#define WRITE 0x1
#define READ 0x2
#define ACCEPT 0x3
#define BUFFER_SIZE 512


//typedef BOOL (WINAPI *LPFN_ACCEPTEX)(SOCKET, SOCKET, PVOID, DWORD, DWORD, DWORD, LPDWORD, LPOVERLAPPED);

typedef struct _completionKey  

{  

   SOCKET s;  

   SOCKADDR_IN clientAddr;  

}COMPLETIONKEY,*PCOMPLETIONKEY;  

typedef struct _io_operation_data  

{  

   OVERLAPPED overlapped;  

   WSABUF dataBuf;  

   CHAR   buffer[BUFFER_SIZE];  
   int    t_info;
   SOCKET s;
}IO_OPERATION_DATA;  


class CIocp{
public:
    CIocp();
    void main_process();
    void ASY_send(SOCKET s,IO_OPERATION_DATA* p);
    void ASY_recv(SOCKET s,IO_OPERATION_DATA* p);
    void ASY_accept(IO_OPERATION_DATA* pcompletionkey);
    LPFN_ACCEPTEX GetAcceptFunc()const{return m_lpfnAcceptEx;}
    HANDLE GetIoCompletionPort()const{return m_hIOCompletionPort;}
private:
    PHANDLE m_nWorkerThread;
    HANDLE m_hIOCompletionPort;
    LPFN_ACCEPTEX m_lpfnAcceptEx;
    PCOMPLETIONKEY m_pkey;
    SOCKET m_sockListen;

    static unsigned int __stdcall _WorkerThreads(PVOID pm);
};
//wrap_sock.h
//主要包裹了错误报告
#include<WinSock2.h>
#include<WS2tcpip.h>
#include"err_no.h"
#pragma comment(lib,"ws2_32.lib")


int wrap_CreateNewIoCompletionPort(HANDLE* iocp);

int wrap_GetProcessorsNumber();

int wrap_WSAStartup();

//set hints and get info
int wrap_getaddrinfo(const char* servername,const char* port,PADDRINFOA* m_serverinfo);

int wrap_socket(SOCKET* sock,int af,int type,int protocol);

//if failed,print and closesocket
int wrap_connect(SOCKET serverfd,const sockaddr* addr,int namelen);

//if failed,print and closesocket
int wrap_recv(SOCKET serverfd,char* recvbuf,int bufsize,int flag);

//if failed,print and closesocket
int wrap_send(SOCKET serverfd,const char* recvbuf,int bufsize,int flag);
//wrap_sock.h
#include"wrap_sock.h"

int wrap_CreateNewIoCompletionPort(HANDLE* iocp){
    *iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
    if(*iocp==0){
        err_no("create IOCP");
        return -1;
    }
    else return 0;
}

int wrap_GetProcessorsNumber(){
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    return si.dwNumberOfProcessors;
}

int wrap_WSAStartup(){
    WSAData m_wd;
    if(0!=WSAStartup(MAKEWORD(1,1),&m_wd)){
        err_no("WSAStartup");
        return -1;
    }
    return 0;
}


int wrap_getaddrinfo(const char* servername,const char* port,PADDRINFOA* serverinfo){
    addrinfo hints;
    //set hints
    memset(&hints,0,sizeof(addrinfo));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if(0!=getaddrinfo(servername,port,&hints,serverinfo)){
        err_no("getaddrinfo failed");
        return -1;
    }
    return 0;
}


int wrap_recv(SOCKET serverfd,char* recvbuf,int bufsize,int flag){
    if(-1==recv(serverfd,recvbuf,bufsize,flag)){
        //printf("no:%d\n",tID);
        err_no("recv failed");
        closesocket(serverfd);
        return -1;
    }
    return 0;
}

int wrap_send(SOCKET serverfd,const char* recvbuf,int bufsize,int flag){
    if(-1==send(serverfd,recvbuf,bufsize,flag)){
        //printf("no:%d\n",tID);
        err_no("send failed");
        closesocket(serverfd);
        return -1;
    }
    return 0;
}

int wrap_connect(SOCKET serverfd,const sockaddr* addr,int namelen){
    if(0!=connect(serverfd,addr,namelen)){
        err_no("connect failed");
        closesocket(serverfd);
        return -1;
    }
    return 0;
}
int wrap_socket(SOCKET* sock,int af,int type,int protocol){
    SOCKET tmp;
    if((tmp = socket(af,type,protocol))==-1){
            err_no("socket failed");
            return -1;
    }
    *sock = tmp;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最近有项目要做一个高性能网络服务器,决定下功夫搞定完成端口IOCP),最终花了一个星期终于把它弄清楚了,并用C++写了一个版本,效率很不错。 但,从项目的总体需求来考虑,最终决定上.net平台,因此又花了一天一夜弄出了一个C#版,在这与大家分享。 一些心得体会: 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值