WinSock Select模型

项目地址
1.WinSock套接字有阻塞模式和非阻塞模式两种,可用 ioctlsocket函数设置,cmd使用FIONBIO,0为阻塞。
阻塞socket,又被称为锁定状态的socket。非阻塞socket,又被称为非锁定状态的socket。当调用一个Windows API时, API的调用会耗费一定的CPU时间。当一个操作完成之后才返回到用户态,称其为阻塞,反之,则为非阻塞。

2.WinSock套接字IO模型有:
select(选择)
WSAAsyncSelect(异步选择)
WSAEventSelect(事件选择)
Overlapped I/O(重叠式I / O)
Completion port(完成端口)

3.本篇主要介绍select模型
注:这里使用WinSock2.h

fd_set结构体作为socket组,使用FD_ISSET函数可检查某个组是否准备好,FD_CLR从某个组中移除某个socket,FD_SET用于将某个socket加入某个组,FD_ZERO将某个组清空。

select函数在WinSock2.h头文件内,它有五个参数。
第一个int参数是为了兼容Berkeley socket,一般忽略填0。
第二个参数fd_set是指向一组要检查可读性的socket的指针
第三个参数fd_set是指向一组要检查可写性socket的指针。
第四个参数fd_set是指向一组要检查异常条件的socket的指针。
第五个参数timeval是超时时间,超过时间没有任何一个组可用,select将返回一个int值
返回值为0时代表无可用操作,为-1时是出错。其他情况下可用FD_ISSET函数检测读写socket组,这三个socket组至少要有一个不为nullptr。

当某一组的某一个socket可用时,即可进行读写或异常处理操作。
服务端监听socket应加入检查可读性socket组中,如监听socket可读时,说明有客户端需要连接,应使用accept函数接受连接请求。
接受请求的socket也应加入读写和异常检查组中,当其中某个socket可读时,说明客户端发来了数据,应使用recv函数接受数据。
数据发送情况自行编写,由一个bool值isSending控制,当组中某个socket可写并且这个连接的isSending为true时,使用send函数发送数据。

注:使用TCP协议
具体实现可点击查看,服务端和客户端均可使用。

socket初始化,头文件需要加入:#pragma comment(lib,"Ws2_32.lib")

void TService::StartUp(SOCKADDR_IN & sockAddr, IOType type)
{
    this->error = WSAStartup(MAKEWORD(2, 2), &wsaData);//初始化WS2_32.dll
    if (error == SOCKET_ERROR)
    {
        std::cout << "WSAStartup faild!error=" << error << std::endl;
        delete this;
        return;
    }

    this->listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    error = bind(listenSocket, (struct sockaddr *) &sockAddr, sizeof(SOCKADDR_IN));//绑定完成
    if (error == SOCKET_ERROR)
    {
        std::cout << "bind faild!error=" << error << std::endl;
        delete this;
        return;
    }

    error = listen(listenSocket, maxListener);
    if (error == SOCKET_ERROR)
    {
        std::cout << "listen faild!error=" << error << std::endl;
        delete this;
        return;
    }
    RegisterHandler();
    std::cout << "server started successfully" << std::endl;

    this->ioTyple = type;
    switch (type)
    {
    case SelectMode:
        InitSelect();
        break;
    case WSAAsyncSelectMode:
        break;
    case WSAEventSelectMode:
        break;
    case OverlappedMode:
        break;
    case CompletionPortMode:
        break;
    default:
        break;
    }
}

Select模型初始化和运行

void TService::InitSelect()
{
    readfds = new fd_set();
    writefds = new fd_set();
    exceptfds = new fd_set();

    timeout.tv_sec = 0; //秒
    timeout.tv_usec = 30;   //微秒
    if (SetIOBlock(1) == SOCKET_ERROR)  //非阻塞
    {
        std::cout << "Error setting io block" << std::endl;
        return;
    }

    FD_SET(listenSocket,readfds);   //监听socket加入读组
}

void TService::Select()
{
    fd_set read = *readfds;
    fd_set write = *writefds;
    fd_set except = *exceptfds;

    int res = select(0, &read, &write, &except, &timeout);
    if (res == SOCKET_ERROR)
    {
        std::cout << "select error, code is " << WSAGetLastError() << std::endl;
        return;
    }
    if (res == 0)
    {
        return;//表示当前无状态可控的socket
    }

    if (FD_ISSET(listenSocket, &read))
    {
        //listensocket可读,表示连接到达
        SOCKET acp = accept(this->listenSocket, NULL, NULL);
        if (acp == INVALID_SOCKET)
        {
            std::cout << "accept error,error code " << WSAGetLastError() << std::endl;
            return;
        }
        //设置新到达socket为非阻塞模式,并加入socks以及fdset
        if (ioctlsocket(acp, FIONBIO, &unblock) == SOCKET_ERROR)
        {
            std::cout << "ioctlsocket(acp) error,error code " << WSAGetLastError() << std::endl;
            return;
        }

        FD_SET(acp, readfds);   //新连接加入读写组
        FD_SET(acp, writefds);
        AddChannel(acp);
    }

    for (std::map<const Long, TChannel*>::iterator iter = idChannels->begin(); iter != idChannels->end(); iter++)
    {
        TChannel *channel = iter->second;
        SOCKET socket = channel->GetSocket();

        if (FD_ISSET(socket, &read))
        {
            int error = channel->Receive();
            if (error <= 0)
            {
                break;
            }
        }
        if (FD_ISSET(socket, &write) && channel->isSending)
        {
            std::list<NetDataWriter*>* sendWrites = channel->sendWrites;
            while (sendWrites->empty() == false)
            {
                NetDataWriter* sendWrite = sendWrites->front();
                sendWrites->pop_front();
                byte* sendData = &(sendWrite->GetData());
                if (send(socket, (Byte*)sendData, sendWrite->GetWritePos(), 0) == SOCKET_ERROR)
                {
                    channel->OnError("send data error");
                }
                delete sendWrite;
            }
            sendWrites->clear();
            channel->isSending = false;
        }
        if (FD_ISSET(socket, &except))
        {
            std::cout << "except socket error";
        }
    }
}

数据发送,接收和分包处理

void TChannel::Send(const char* method, NetDataWriter* dataWrite)
{
    int bodySize = dataWrite->GetWritePos();        //获取body写入大小
    int packetSize = bodySize + NetPacket::GetHeadSize();       //加入head部分总长度
    byte& bodyData = dataWrite->GetData();          //获取body部分数据
    NetDataWriter *completeData = new NetDataWriter(packetSize);

    byte* size = BitConvert::Int2Bytes(packetSize);     //数据包总长度
    byte* module = BitConvert::Short2Bytes(100);        //获取module和cmd,两者组合为method,用于数据处理模块
    byte* cmd = BitConvert::Short2Bytes(200);
    completeData->Put(size, 0, 4);  
    completeData->Put(module, 0, 2);
    completeData->Put(cmd, 0, 2);
    completeData->Put(new byte[16], 0, 16);     //IV,加密使用,占16字节,暂未使用
    completeData->Put(&bodyData, 0, bodySize);

    sendWrites->push_back(completeData);        //加入待发送list,下一次轮询时发送
    isSending = true;
    delete dataWrite;
}

int TChannel::Receive()
{
     isRecving = true;
     int len=recv(m_socket, receRaw, MAX_RAWSIZE, 0);       //每次接受8192字节
     if (len == 0)
     {
         OnError("connection has been closed ");
         service->RemoveChannel(id);
         return 0;
     }
     else if (len == SOCKET_ERROR)
     {
         OnError("recv error");
         service->RemoveChannel(id);
         return SOCKET_ERROR;
     }
     else
     {
         std::cout << "rece data len=" << std::to_string(len) << std::endl;
     }

     Subpackage(len);   //分包
     isRecving = false;
     return 1;
}

void TChannel::Subpackage(int len)
{
    byte* receData = (byte*)receRaw;
    if (totalSize == 0) //最新的数据包
    {
        totalSize = BitConvert::ToInt32(receData, offsetSize);  //获取数据包大小
    }

    int size = totalSize - offsetSize >= len ? len : totalSize - offsetSize;
    if (size == 0)
    {
        OnError("recv data length is zero");
        return;
    }
    recvWrite->Put(receData, 0, size);//从缓存中写入数据包内

    if (totalSize - offsetSize <= len)
    {
        byte* data = recvWrite->CopyData();
        if (readCallBack)
        {
            readCallBack(this, data);   //读取完毕调用回调函数
        }

        messageDispatcher->Dispatch(this, recvWrite->CopyData());//这个数据包已经接收完毕,分发处理
        recvWrite->Reset();     //数据包分发完毕,重置偏移

        int remainSize = len - totalSize - offsetSize;  //剩余大小
        totalSize = 0;
        offsetSize = 0; //临时值清零

        if (remainSize > 0) //下一个数据包数据已到达,这里可能是多个数据包
        {
            Subpackage(remainSize);
        }
    }
    else
    {
        offsetSize += size;          //这里需要继续接收数据,已接收数据包偏移值更新
        Receive();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容