关于非阻塞模式下的SOCKET设定处理---select模式

时间:2022-09-09 08:36:55
下面范例是一个关于非阻塞模式下的SOCKET设定处理---select模式。使用的是UDP协议。Client02首先启动,将本机的1207端口进行SOCKET绑定,并将该SOCKET模式设定为非阻塞模式,
此模式下不可直接调用recvfrom。
理由: 
阻塞模式下,如果直接调用recvfrom从指定的SOCKET读取数据,如果还没有接受到来自Client01的数据,函数recvfrom会一直等待,直到有数据可以读出为止。当然,也可以创建一个独立的线程来专门调用recvfrom,这样可以不影响主线程的处理。
非阻塞模式下,recvfrom函数会立即返回,如果此时没有数据到达,该函数调用必然失败,返回值为WSAEWOULDBLOCK,表明当前状态是非阻塞模式的调用,且没有数据到达。这时,通常的做法是,创建一个独立的线程调用recvfrom,判断返回值(如果是WSAEWOULDBLOCK)进行循环调用recvfrom,直到有数据到达为止,这样可以,但是开销大,不好。如果,用MSG_PEEK来查询,其实开销也很大,也不推荐。
或者用select模式进行处理。(select应该是专门用来对付非阻塞模式的一种办法)
select函数察看要求进行处理的SOCKET, 同时设有超时Value, 这个函数本身会等待(也就是本身其实是个阻塞的函数),直到有可用的SOCKET为止,然后,进行处理,再进行select等待,如此反复执行。
////////////////////////////////////////////////////////////////////
//
//         UDP Client 02
//         IP: 127.0.0.1
//         PORT: 1207
//
////////////////////////////////////////////////////////////////////
#define CONNECT_IP           "127.0.0.1"
#define CONNECT_PORT         1207
#define DEFAULT_BUFFER_LEN   256
char* getBlockMode(int nMode)
{
    return nMode==0? "阻塞模式设定" : "非阻塞模式设定" ;
}

int _tmain(int argc, _TCHAR* argv[])
{
    printf(">>>>>UDP Client启动<<<<<<<\n");
    WSAData         wsData;
    SOCKET          sClient;
    SOCKADDR_IN    addrSender;
    SOCKADDR_IN    addrRecviver;
    int            addrSenderLen = sizeof(addrSender);
    char           sendBuff[DEFAULT_BUFFER_LEN] = {0};
    char           recvBuff[DEFAULT_BUFFER_LEN] = {0};
    DWORD       nMode = 0;
    int             nError = -1;
    printf("-启动SOCKET库\n");
    WSAStartup( MAKEWORD(2,2), &wsData );
    addrRecviver.sin_family = AF_INET;
    addrRecviver.sin_addr.S_un.S_addr = inet_addr( CONNECT_IP );
    addrRecviver.sin_port = htons( CONNECT_PORT );
    printf("-设定地址等信息完了: %s:%d\n", inet_ntoa(addrRecviver.sin_addr), ntohs(addrRecviver.sin_port));

    printf("-创建SOCKET\n");
    sClient = socket( AF_INET, SOCK_DGRAM , IPPROTO_UDP );
    if( sClient == INVALID_SOCKET )
    {
        printf("!!! socket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }
    ////////////////////////
    //阻塞/非阻塞模式设定
    ////////////////////////
    nMode = 1; //阻塞模式/非阻塞模式
    nError = ioctlsocket( sClient, FIONBIO, &nMode );
    if( nError!=0 )
    {
        printf("!!! ioctlsocket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }
    printf("-设定SOCKET模式: %s\n", getBlockMode(nMode));
    printf("-绑定SOCKET用于接受数据\n");
    nError = bind( sClient, (const sockaddr *)&addrRecviver, sizeof(addrRecviver) );
    if( nError == SOCKET_ERROR )
    {
        printf("!!! bind failed: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }

    ////////////////////////////////////////////
    //
    // select 模型用于监视制定socket 非阻塞模式设定中
    //
    ////////////////////////////////////////////
    fd_set   fdRead;
    struct   timeval tv = {5,0}; //{second, milisec}
    while( true )
    {
        FD_ZERO( &fdRead );                   //首先清空fd集
        FD_SET( sClient, &fdRead );         //加入要查看的SOCKET
        printf("-select 监视中\n");
        nError = select( 0, &fdRead, NULL, NULL, &tv );   //调用select进行SOCKET查看
        ;;;;;if( nError == 0 )
        {
            printf("-!!! select 超时: %d sec\n", tv.tv_sec);
            continue;
        }
        else if( nError < 0 )
        {
            printf("!!! select failed: %d\n", WSAGetLastError());
            WSACleanup();
            return -1;
        }
        printf("-select 查找可读的SOCKET\n");
        if( FD_ISSET(sClient, &fdRead) )                        //查找可用的SOCKET,不可用的SOCKET会从fd集里删除掉
        {//可读入数据了
            printf("-找到一个可以读入的SOCKET\n");
            nError = recvfrom( sClient, recvBuff, sizeof(recvBuff), 0, (sockaddr *)&addrSender, &addrSenderLen );
            if( nError == SOCKET_ERROR )
            {
                printf("!!! recvfrom failed: %d\n", WSAGetLastError());
                closesocket( sClient );
                WSACleanup();
                return -1;
            }
            printf("-收到数据: %s [%s:%d]\n", recvBuff, inet_ntoa(addrSender.sin_addr), ntohs(addrSender.sin_port));
        }
        else{
            printf("-没有找到一个可以读入的SOCKET\n");
        }
    }//while(true)    printf("-Socket关闭\n");
    closesocket( sClient );
    WSACleanup();
return 0;
}

////////////////////////////////////////////////////////////////////
//
//         UDP Client 01
//         IP: 127.0.0.1
//         PORT: 1207
//
////////////////////////////////////////////////////////////////////
#define CONNECT_IP           "127.0.0.1"
#define CONNECT_PORT         1207
#define DEFAULT_BUFFER_LEN   256
char* getBlockMode(int nMode)
{
    return nMode==0? "阻塞模式设定" : "非阻塞模式设定" ;
}

int _tmain(int argc, _TCHAR* argv[])
{
    printf(">>>>>UDP Client启动<<<<<<<\n");
    WSAData          wsData;
    SOCKET             sClient;
    SOCKADDR_IN    addrSender;
    SOCKADDR_IN    addrRecvier;
    int             addrRecvierLen = sizeof(addrRecvier);
    char           sendBuff[DEFAULT_BUFFER_LEN] = {0};
    char           recvBuff[DEFAULT_BUFFER_LEN] = {0};
    DWORD       nMode = 0;
    int               nError = -1;
    printf("-启动SOCKET库\n");
    WSAStartup( MAKEWORD(2,2), &wsData );
    addrSender.sin_family = AF_INET;
    addrSender.sin_addr.S_un.S_addr = inet_addr( CONNECT_IP );
    addrSender.sin_port = htons( CONNECT_PORT );
    printf("-设定地址等信息完了: %s:%d\n", inet_ntoa(addrSender.sin_addr), ntohs(addrSender.sin_port));

    printf("-创建SOCKET\n");
    sClient = socket( AF_INET, SOCK_DGRAM , IPPROTO_UDP );
    if( sClient == INVALID_SOCKET )
    {
        printf("!!! socket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }
    ////////////////////////
    //阻塞/非阻塞模式设定
    ////////////////////////
    nMode = 0; //阻塞模式/非阻塞模式
    nError = ioctlsocket( sClient, FIONBIO, &nMode );
    if( nError!=0 )
    {
        printf("!!! ioctlsocket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }
    printf("-设定SOCKET模式: %s\n", getBlockMode(nMode));
    strcpy( sendBuff, "message from Client01" );
    printf("-送信设定: %s [%s:%d]\n", sendBuff, inet_ntoa(addrSender.sin_addr), ntohs(addrSender.sin_port));
    nError = sendto( sClient, sendBuff, sizeof(sendBuff), 0, (const sockaddr *)&addrSender, sizeof(addrSender) );
    if( nError == SOCKET_ERROR )
    {
        printf("!!! sendto failed: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }
    printf("-Socket关闭\n");
    closesocket( sClient );
    WSACleanup();
return 0;
}