天天看点

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,但是实现方式“扭曲”,效率低下,不推荐使用,尤其是服务端。

继续阅读