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 = 7777;
static const int BUFFER_LENGTH = 128 ;

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


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


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

    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, 3) ;


    _beginthreadex(NULL, 0, WorerkThread, NULL, 0, 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 0 ;
}

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

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

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

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


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

        if (ret == 0)
        {
            printf("超时\n") ;
            continue ;
        }

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

                    if(i != g_TotalConn -1)
                    {
                        g_ClientSocket[i--] = g_ClientSocket[g_TotalConn -1] ;
                    }
                    g_TotalConn-- ;
                }
                else
                {
                    szMessage[ret] = '\0';
                    send(g_ClientSocket[i], szMessage, strlen(szMessage), 0);
                    printf("发送数据给Client:%s\n", szMessage) ; 
                }
            }

            if (FD_ISSET(g_ClientSocket[i], &fdwrite))
            {
                //可以发送数据
            }
        }
    }

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

    return 0 ;
}

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

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

特点:
1、程序可能阻塞在select处一段指定的时间。
2、使用轮循的方式,检测Socket是否可读或可写、效率低下。

点评:虽然实现了异步IO,但是实现方式“扭曲”,效率低下,不推荐使用,尤其是服务端。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值