网络IO模型 01 select

select

The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.

Syntax

int select( _In_    int                  nfds, 
            _Inout_ fd_set               *readfds, 
            _Inout_ fd_set               *writefds, 
            _Inout_ fd_set               *exceptfds,
            _In_    const struct timeval *timeout);

Parameters
nfds [in]
Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.
Windows下忽略。Linux才有意义。

readfds [in, out]
An optional pointer to a set of sockets to be checked for readability.

writefds [in, out]
An optional pointer to a set of sockets to be checked for writability.

exceptfds [in, out]
An optional pointer to a set of sockets to be checked for errors.

timeout [in]
The maximum time for select to wait, provided in the form of a TIMEVAL structure. Set the timeout parameter to null for blocking operations.
select函数最大等待时间。
设置为NULL时阻塞

Return value
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.
正常返回fd_set结构中准备好的(可读、可写或者发生异常)socket句柄的总个数。
发生错误时,返回SOCKET_ERROR。
timeout时,返回0

/*
 * Structure used in select() call, taken from the BSD file sys/time.h.
 */
struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* and microseconds */ 
};
tv_usec单位为微秒,即一秒的1000000分之一。

FD_SET

FD_SET是什么
...\Microsoft SDKs\Windows\v7.0A\include\winsock2.h

#define FD_SETSIZE      64

typedef struct fd_set {
        u_int   fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

typedef struct fd_set FD_SET;

可看出FD_SET包含两部分:

  • fd_count
    SOCKET队列的长度。

  • fd_array
    SOCKET队列。最大为FD_SETSIZE。

FD_SET操作宏
FD_SET操作宏

FD_ISSET(s, *set)用于检测一个描述符是否是fd_set集合的可读或者可写成员,不在返回0,是返回非0.

select使用方法

在调用select函数对套接字进行监视之前,必须将要监视的套接字分配给上述三个数组中的一个。然后调用select函数,再次判断需要监视的套接字是否还在原来的集合中。就可以知道该集合是否正在发生IO操作。

例如:应用程序想要判断某个套接字是否存在可读的数据,需要进行如下步骤:
1:将该套接字加入到readfds集合。
2:以readfds作为第二个参数调用select函数。
3:当select函数返回时,应用程序判断该套接字是否仍然存在于readfds集合。
4:如果该套接字存在与readfds集合,则表明该套接字可读。此时就可以调用recv函数接收数据。否则,该套接字不可读。
http://blog.csdn.net/ithzhang/article/details/8363951

调用select函数后,留在set中的socket须指定条件。

In summary, a socket will be identified in a particular set when select returns if:

readfds:

  • If listen has been called and a connection is pending, accept will succeed. (新的连接请求)
  • Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
  • Connection has been closed/reset/terminated. (连接关闭/重置等)

writefds:

  • If processing a connect call (nonblocking), connection has succeeded.(当前socket通过connect连接其他tcp服务器成功)
  • Data can be sent.

exceptfds:

  • If processing a connect call (nonblocking), connection attempt failed.
  • OOB data is available for reading (only if SO_OOBINLINE is disabled).

select优缺点

优点:
int select( _In_    int                  nfds, 
            _Inout_ fd_set               *readfds, 
            _Inout_ fd_set               *writefds, 
            _Inout_ fd_set               *exceptfds,
            _In_    const struct timeval *timeout);
  • 跨平台性比较好
  • 可通过timeout设置超时时间
  • 可同时等待多个套接字
缺点:
  • 句柄数目受限
    select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE    1024  

表示select最多同时监听1024个fd

  • 随着FD的数目增长而效率降低

select() 接口并不是实现“事件驱动”的最好选择。
因为当需要探测的句柄值较大时,select() 接口本身需要消耗大量时间去轮询各个句柄
很多操作系统提供了更为高效的接口,如 linux 提供了 epoll
http://www.open-open.com/lib/view/open1346029754786.html

select调用小demo

// pxSelect.cpp : 定义控制台应用程序的入口点。
// Zhaoliang Guo
// 20170617
// A demo for select

#include "stdafx.h"

#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

FD_SET fdReadSet;

#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA wsaData;
    WORD    wVersionRequested;

    wVersionRequested = MAKEWORD(2, 2);  
    int ret = WSAStartup(wVersionRequested, &wsaData);  //加载套接字库  
    if(ret!=0)  
    {  
        printf("WSAStartup() failed!\n");  

        return -1;  
    }  

    // 确认WinSock DLL支持版本2.2  
    if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2)  
    {  
        WSACleanup();  
        printf("Invalid WinSock version!\n");

        return -1;  
    }  

    SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addrServ;
    memset(&addrServ, 0, sizeof(sockaddr_in));

    int nSockAddrLen = sizeof(SOCKADDR);

    addrServ.sin_family           = AF_INET;
    addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrServ.sin_port             = htons(6666);

    bind(sockServer, (sockaddr *)&addrServ, nSockAddrLen);

    listen(sockServer, 5);

    // 超时时间
    timeval tvTimeout;
    tvTimeout.tv_sec  = 2;
    tvTimeout.tv_usec = 0;

    SOCKET sockConnected;

    int nAddrLen = sizeof(sockaddr_in);

    char szSendBuffer[1024] = {0};
    char szRecvBuffer[1024] = {0};

    cout<<"waiting connection..."<<endl;

    while (true)
    {
        FD_ZERO(&fdReadSet);
        FD_SET(sockServer, &fdReadSet);

        int nRet = select(0, &fdReadSet, NULL, NULL, &tvTimeout);
        if (-1 == nRet)
        {
            cout<<"error. break..."<<endl;

            break;
        }
        else if (0 == nRet) // timeout
        {
            cout<<"timeout. select again..."<<endl;

            continue;
        }
        else
        {
            cout<<"connection coming..."<<endl;

            /*      
                如果套接字句柄还在fd_set里
                说明客户端已经有connect的请求发过来了
                马上可以accept成功
            */
            if (FD_ISSET(sockServer, &fdReadSet)) /*有新的客户端连接到来*/
            {
                sockaddr_in addrClient; // 用于保存客户端的网络节点的信息   

                sockConnected = accept(sockServer, (sockaddr *)&addrClient, &nSockAddrLen);

                if(sockConnected != INVALID_SOCKET) //创建成功
                {
                    ZeroMemory(szSendBuffer, 1024);

                    //inet_ntoa将结构转换为十进制的IP地址字符串
                    sprintf_s(szSendBuffer, 1024, "Welcome %s!", inet_ntoa(addrClient.sin_addr));

                    //成功建立连接后向客户端发送数据,结果将显示在客户端上
                    send(sockConnected, szSendBuffer, strlen(szSendBuffer) + 1, 0);

                    ZeroMemory(szRecvBuffer, 1024);
                    recv(sockConnected, szRecvBuffer, 1024, 0);

                    cout<<szRecvBuffer<<endl;

                    closesocket(sockConnected);  
                }
                else
                {
                    int nErrorCode = WSAGetLastError();
                    printf("the err code is:%d/n", nErrorCode);
                }
            }
        }   
    }

    closesocket(sockServer);
    WSACleanup();  

    return 0;
}

References:

http://blog.csdn.net/kikilizhm/article/details/8201512
https://msdn.microsoft.com/en-us/library/windows/desktop/ms740141(v=vs.85).aspx
http://blog.csdn.net/ithzhang/article/details/8363951
http://blog.csdn.net/ysu108/article/details/7570571

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容