IOCP客户端版本,异步connect

之前在网上看到一个服务端的IOCP模块,比较小巧,感觉还不错,后来在工作中,需要开发一个挂号的程序,监视大量服务器运行情况,初期连接数大概六七百,我就把这个IOCP模块改造成了一个客户端版本。后来发现由于是同步的connect,有时候会卡在connect过程很久,也不方便设置connect的超时,想到使用ConnectEx做异步连接,感觉ConnectEx过于繁琐,还得自己获取函数指针,必须要先调用bind等,断开连接要调用DisconnectEx。后来我自己想到一种办法,在调用connect之前,用ioctlsocket把socket先设置为非阻塞模式,然后在连接成功后再设置回阻塞模式,但这有一个问题,IOCP里面设置为非阻塞模式,怎么判断连接成功、失败、超时呢?

我是这么做的,调用connect成功之后,投递事件,在connect事件里,调用getsockopt(clt->fd, SOL_SOCKET, 0x700C/*SO_CONNECT_TIME*/, (char*)&Connect_Time, &len)来检测连接时间,如果返回-1表示连接没有成功,然后判断是否超时,如果超时直接失败,否则断续投递事件,直到连接成功或者超时,下面直接上代码,关键代码段在:int connect()函数和case T::EV_CONNECT: 段:

用getsockopt检测是否连接成功这块可能不是很常规的做法,可以改用select实现。

#ifndef iocptcpclient_h__
#define iocptcpclient_h__
#include <Winsock2.h>
#include <windows.h>
#include <MSTCPiP.h>
 
#pragma comment(lib, "Ws2_32.lib")
 
namespace iocp
{
    template<typename T>
    class Scheduler
    {
    public:
        void start();
        void stop();
        void push(T * clt);
    public:
        int scheds;
        HANDLE iocp;
    };
 
    template<typename T>
    void Scheduler<T>::start()
    {
        iocp = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, scheds);
        if (NULL == iocp)
        {
            throw (int)::WSAGetLastError();
        }
    }
 
    template<typename T>
    void Scheduler<T>::stop()
    {
    }
 
    template<typename T>
    void Scheduler<T>::push(T * clt)
    {
        ::PostQueuedCompletionStatus(iocp, 0, (ULONG_PTR)clt, NULL);
    }
 
    template<typename T>
    class Processor
    {
    public: 
        void start();
        void stop();
    public:
        static DWORD WINAPI run(LPVOID param);
 
    public: 
        int threads;
        Scheduler<T> * scheder;
    };
 
    template<typename T>
    void Processor<T>::start()
    {
        for (int i = 0; i < threads; i++)
        {
            DWORD tid;
            HANDLE thd = ::CreateThread(NULL,
                0,
                (LPTHREAD_START_ROUTINE)run,
                this,
                0,
                &tid);
            if (NULL == thd)
            {
                throw (int)::GetLastError();
            }
            ::CloseHandle(thd);
        }
    }
 
    template<typename T>
    void Processor<T>::stop()
    {
    }
 
    template<typename T>
    DWORD WINAPI Processor<T>::run(LPVOID param)
    {
        Processor<T>& procor = *(Processor<T> *)param;
        Scheduler<T>& scheder = *procor.scheder;
        HANDLE iocp = scheder.iocp;
 
        DWORD ready;
        ULONG_PTR key;
        WSAOVERLAPPED * overlap;
        while (true)
        {
            ::GetQueuedCompletionStatus(iocp, &ready, &key, (LPOVERLAPPED *)&overlap, INFINITE);
 
            T * clt = (T *)key;
            switch (clt->event)
            {
            case T::EV_RECV:
                {
                    if (0 >= ready)
                    {
                        clt->event = T::EV_DISCONNECT;
                        ::PostQueuedCompletionStatus(iocp, 0, (ULONG_PTR)clt, NULL);
                    }
                    else
                    {
                        clt->OnRecv(ready);
                    }
                }
                break;
            case T::EV_CONNECT:
                {
                    int Connect_Time;
                    int len = sizeof(Connect_Time);
                    int result = getsockopt(clt->fd, SOL_SOCKET, 0x700C/*SO_CONNECT_TIME*/, (char*)&Connect_Time, &len);
                    if (Connect_Time == -1){
                        if (GetTickCount() - clt->dwConnTime >= clt->maxConnTime){
                            clt->OnConnectFailed();
                            ::closesocket(clt->fd);
                            clt->fd = INVALID_SOCKET;
                        }
                        else
                        {
                            Sleep(1);
                            ::PostQueuedCompletionStatus(iocp, 0, (ULONG_PTR)clt, NULL);
                        }
                    }
                    else
                    {
                        unsigned long ul = 0;
                        ioctlsocket(clt->fd, FIONBIO, &ul); //设置为阻塞模式*/
                        if (NULL == ::CreateIoCompletionPort((HANDLE)clt->fd, iocp, (ULONG_PTR)clt, 0))
                        {
                            clt->OnConnectFailed();
                            ::closesocket(clt->fd);
                            clt->fd = INVALID_SOCKET;
                            //delete clt;
                        }
                        else
                        {
                            clt->OnConnect();
                        }
                    }
                }
                break;
            case T::EV_DISCONNECT:
                {
                    clt->OnDisconnect();
                    ::closesocket(clt->fd);
                    clt->fd = INVALID_SOCKET;
                    //delete clt;
                }
                break;
            case T::EV_SEND:
                break;
            }
        }
 
        return 0;
    }
 
    class Client
    {
    public:
        enum EVENT
        {
            EV_CONNECT,
            EV_DISCONNECT,
            EV_RECV,
            EV_SEND
        };
 
        Client(){
            fd = INVALID_SOCKET;
            maxConnTime = 5000;
        }
        virtual ~Client(){};
 
        int connect(){
            this->event = EV_CONNECT;
            dwConnTime = GetTickCount();
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_addr.s_addr = ip;
            addr.sin_port = htons(port);
 
            DWORD dwError = 0, dwBytes = 0;
            tcp_keepalive sKA_Settings = { 0 }, sReturned = { 0 };
            sKA_Settings.onoff = 1;
            sKA_Settings.keepalivetime = 30000;    // Keep Alive in 30 sec.
            sKA_Settings.keepaliveinterval = 3000; // Resend if No-Reply
            if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &sKA_Settings,
                sizeof(sKA_Settings), &sReturned, sizeof(sReturned), &dwBytes,
                NULL, NULL) != 0)
            {
                dwError = WSAGetLastError();
            }
            unsigned long ul = 1;
            ioctlsocket(fd, FIONBIO, &ul); //设置为非阻塞模式
            int ret = -1;
            if (::connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1 && WSAGetLastError() == WSAEWOULDBLOCK)
            {
                ret = 0;
            }
            return ret;
        }
 
        void send(const char * buff, int len){
            ::send(fd, buff, len, 0);
        }
 
        void recv(char * buff, int len){
            this->event = EV_RECV;
 
            ::memset(&overlap, 0, sizeof(overlap));
            WSABUF buf;
            buf.buf = buff;
            buf.len = len;
            DWORD ready = 0;
            DWORD flags = 0;
            if (0 != ::WSARecv(fd, &buf, 1, &ready, &flags, &overlap, NULL)
                && WSA_IO_PENDING != WSAGetLastError())
            {
                this->event = EV_DISCONNECT;
                ::PostQueuedCompletionStatus(iocp, 0, (ULONG_PTR)this, NULL);
            }
        }
        void close(){
            ::shutdown(fd, SD_BOTH);
        }
 
        virtual void OnConnect(){};//连接成功
 
        virtual void OnConnectFailed(){};//连接失败
 
        virtual void OnDisconnect(){};  //连接断开
 
        virtual void OnRecv(int len){};
 
        virtual void OnSend(int len){};
    public:
        int ip;
        int port;
        void * srv;
        HANDLE iocp;
        EVENT event;
        SOCKET fd;
        DWORD maxConnTime;
        DWORD dwConnTime;
        WSAOVERLAPPED overlap;
    };
 
    template<typename T>
    class TCPClt
    {
    public:
        void start();
        void stop();
        bool addclt(T* clt, int ip, int port);
    public:
        int scheds;
        int threads;
 
        iocp::Scheduler<T> scheder;
        iocp::Processor<T> procor;
    };
 
    template<typename T>
    void TCPClt<T>::start()
    {
        WSADATA wsadata;
        int wsaversion = WSAStartup(MAKEWORD(2, 2), &wsadata);
 
        if (threads <= 0)
        {
            threads = 1;
        }
 
        scheder.scheds = scheds;
        scheder.start();
 
        procor.threads = threads;
        procor.scheder = &scheder;
        procor.start();
    }
 
    template<typename T>
    void TCPClt<T>::stop()
    {
    }
 
    template<typename T>
    bool TCPClt<T>::addclt(T* clt, int ip, int port){
        clt->ip = ip;
        clt->port = port;
        clt->iocp = scheder.iocp;
        clt->fd = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);
        if (clt->fd == INVALID_SOCKET)
        {
            return false;
        }
        if (clt->connect() != 0)
        {
            closesocket(clt->fd);
            return false;
        }
        scheder.push(clt);
        return true;
    }
}
#endif // iocptcpclient_h__
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容