天天看點

Windows網絡程式設計之(二)Socket通信非阻塞模式Select(TCP和UDP)

阻塞式的Socket很容易了解,但是使用起來非常别扭。Windows提供了選擇(Select)I/O模型,進行異步Socket通信.

Select模型

int select(
  _In_    int    nfds,//忽略. 此參數為了與Berkeley sockets相容.
  _Inout_ fd_set *readfds,//檢查可讀性fd_set指針.
  _Inout_ fd_set *writefds,//檢查可寫性fd_set指針.
  _Inout_ fd_set *exceptfds,//檢查錯誤fd_set指針
  _In_    const struct timeval *timeout//逾時
);
           

本函數用于确定一個或多個套接口的狀态。對每一個套接口,調用者可查詢它的可讀性、可寫性及錯誤狀态資訊。用fd_set結構來表示一組等待檢查的套接口。在調用傳回時,這個結構存有滿足一定條件的套接口組的子集,并且select()傳回滿足條件的套接口的數目。

下面是代碼:

#include <Winsock2.h>
#include <stdio.h>
#include <process.h>

static const int PORT = ;
static const int BUFFER_LENGTH =  ;

static int  g_TotalConn =  ;
static SOCKET g_ClientSocket[FD_SETSIZE] ;


unsigned int __stdcall WorerkThread(void *);
bool InitWSA() ;


int main()
{
    if(!InitWSA())
    {
        return - ;
    }

    SOCKET sockListen = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ;

    SOCKADDR_IN addrSrv ;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK) ;
    addrSrv.sin_family = AF_INET ;
    addrSrv.sin_port = htons(PORT) ;

    bind (sockListen, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR)) ;

    listen (sockListen, ) ;


    _beginthreadex(NULL, , WorerkThread, NULL, , NULL) ;

    SOCKADDR_IN addrClient ;
    int len = sizeof (addrClient) ;

    while (true)
    {
        SOCKET client = accept(sockListen, (SOCKADDR *)&addrClient, &len) ;

        g_ClientSocket[g_TotalConn++] = client ;
    }

    closesocket(sockListen) ;
    WSACleanup () ;

    return  ;
}

unsigned int __stdcall WorerkThread(void *)
{
    printf("線程begin:\n") ;

    int            i;
    fd_set         fdread;
    fd_set         fdwrite;
    int            ret;
    struct timeval tv = {, };
    char           szMessage[BUFFER_LENGTH];

    while (true)
    {
        FD_ZERO(&fdread);
        FD_ZERO(&fdwrite);
        for (i = ; i < g_TotalConn; i++)
        {
            FD_SET(g_ClientSocket[i], &fdread) ;
            FD_SET(g_ClientSocket[i], &fdwrite) ;
        }

        ret = select(, &fdread, &fdwrite, NULL, &tv);


        /*
        if(g_TotalConn > 0 && ret == SOCKET_ERROR)
        {
            printf("Socket:an error occurred!\n") ;
        }
        */

        if (ret == )
        {
            printf("逾時\n") ;
            continue ;
        }

        for (i = ; i < g_TotalConn; i++)
        {
            if (FD_ISSET(g_ClientSocket[i], &fdread))
            {
                ret = recv(g_ClientSocket[i], szMessage, BUFFER_LENGTH, );
                if (ret ==  || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
                {
                    printf("Client socket %d closed.\n", i);
                    closesocket(g_ClientSocket[i]);
                    g_ClientSocket[i] =  ;

                    if(i != g_TotalConn -)
                    {
                        g_ClientSocket[i--] = g_ClientSocket[g_TotalConn -] ;
                    }
                    g_TotalConn-- ;
                }
                else
                {
                    szMessage[ret] = '\0';
                    send(g_ClientSocket[i], szMessage, strlen(szMessage), );
                    printf("發送資料給Client:%s\n", szMessage) ; 
                }
            }

            if (FD_ISSET(g_ClientSocket[i], &fdwrite))
            {
                //可以發送資料
            }
        }
    }

    printf("線程end:\n") ;

    return  ;
}

bool InitWSA()
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( ,  );
    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err !=  ) 
    {                                 
        return false ;
    }
    if ( LOBYTE( wsaData.wVersion ) !=  ||
        HIBYTE( wsaData.wVersion ) !=  ) 
    {
        WSACleanup();
        return false ; 
    }
    return true ;
}
           

特點:

1、程式可能阻塞在select處一段指定的時間。

2、使用輪循的方式,檢測Socket是否可讀或可寫、效率低下。

點評:雖然實作了異步IO,但是實作方式“扭曲”,效率低下,不推薦使用,尤其是服務端。

繼續閱讀