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::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* 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();
    }
}

你可能感兴趣的:(WinSock Select模型)