Windows Select

select IO模型学习

  • 问题由来

    单进程单线程下,最普通的socket是阻塞连接的,即server与一个client连接后,不能与其他的client通信。

    如果采取多线程解决这个问题,每来一个client socket连接,就创建一个线程的话,浪费时间。

  • fd_set 一个数据类型,四个函数

    • fd_set 类型 一个套接字socket的数组

      typedef struct fd_set {
              u_int fd_count;               /* how many are SET? */
            SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
      } fd_set;
      
    • FD_ZERO(fd_set*) ;将传入的套接字集合置空

    • FD_SET(fd, fd_set*) 将套接字fd放入套接字集合中

    • FD_CLR(fd, fd_set*) 将套接字集合中的套接字fd 去除掉

    • FD_ISSET(fd, fd_set*)查询套接字fd是否在套接字集合中

  • select

//VS2015 f12查找定义的结果  定义在 #include <WinSock2.h> 下
  int
  WSAAPI
  select(
      _In_ int nfds,
      _Inout_opt_ fd_set FAR * readfds,  //类型 _Inout_opt_ 表示输入输出参数
      _Inout_opt_ fd_set FAR * writefds,
      _Inout_opt_ fd_set FAR * exceptfds,
      _In_opt_ const struct timeval FAR * timeout
      );
  -----------------------------
  
  参数:
      
  nfds
  忽略。包含nfds参数只是为了与Berkeley套接字兼容。
  注意:这个参数在linux中要有特殊的意义和操作,表示最大的套接字内容,但是windows下可以直接用0
  
  readfds
  套接字集合指针类型,检查套接字集合的可读性
  可读性监视,可读性指有连接到来、有数据到来、连接已关闭、重置或终止
      
  writefds
  套接字集合指针类型,检查套接字集合的可写性
  1如果处理一个connect调用(非阻塞),连接成功。数据可以发送。

  exceptfds
  套接字集合指针类型,检查套接字集合错误
  
timeout
  select等待的最长时间,以TIMEVAL结构的形式提供。对于阻塞操作,请将timeout参数设置为null。
      
  返回值:
  返回状态发生的套接字的个数  失败返回SOCKET_ERROR

个人理解select函数的作用

  • 把想要监控状态的套接字集合传给select的相应部分(是否可读可写是否出错),然后内核会自动维护这个套接字集合:通过输入输出参数 (&)的方式。维护的结果就是,除了有期待状态发生的socket,其他的都会踢出集合。

重要:sockSer可读表示有链接到来,sockCli可读表示有数据到来

  • select缺点与epoll比较

    elect监控的文件描述符有上限
    每次调用都需要手动的设置文件描述符集合,使用非常不便
    每次调用都要把文件描述符从用户态拷贝到内核态,开销比较大
    当就绪的文件描述符好后,需要循环遍历来进行判断,效率不好

  • 与多进程多线程服务器的优缺点比较

    • 优点

      1)不需要建立多个线程、进程就可以实现一对多的通信。
      2)可以同时等待多个文件描述符,效率比起多进程多线程来说要高很多。
      3)select()的可移植性更好,在某些Unix系统上不支持poll()
      4)select() 对于超时值提供了更好的精度:微秒,而poll是毫秒

    • 缺点

      1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 ,循环次数有点多;
      2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 。
      3)select支持的文件描述符数量太小了,默认是1024;

  • 一个小实现,同时连接多个客户端并且读取各个客户端发来的消息
//*************服务器端*************************
//=========================================================头文件

#include <WinSock2.h>//是网络编程socket相关部分API
#include <WS2tcpip.h>//inet_ntop()
#include <iostream>
#include <sstream>//std::to_string
#include <cstring>
#pragma comment(lib,"ws2_32.lib")//这是链接API相关连的Ws2_32.lib静态库
using namespace std;

//==========================================================静态变量

const int BUF_SIZE = 2048;//缓冲区变量长度

                          //==========================================================全局变量

SOCKET sockSer;//定义用于监听的套接口
SOCKET sockCli;//定义用于连接客户端和收发信息的套接口
SOCKADDR_IN addrSer, addrCli;//定义两端的地址(IP地址加端口号)
int naddr = sizeof(SOCKADDR_IN);

char sendBuf[BUF_SIZE];//发送的字符串缓冲区
char recvBuf[BUF_SIZE];//接收内容缓冲区


int main()
{

    //加载socket库,WSAStartup(Sock版本,版本信息返回)
    WSADATA wsadata;
    if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
    {
        //输出出错信息
        cout << "载入socket失败" << endl;

        system("pause");//暂停一下给用户反映时间
        return 0;
    }
    //else { cout << "载入socket成功" << endl; }


    //创建socket
    sockSer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);基于TCP的socket编程是采用的流式套接字。


    //初始化SOCKET_IN各个成员变量
    //addrSer.sin_addr.s_addr= inet_addr("202.98.105.139");
    addrSer.sin_addr.S_un.S_addr = INADDR_ANY;
    //inet_addr十进制ip转二进制ip
    addrSer.sin_family = AF_INET;//指定协议簇
    addrSer.sin_port = htons(20000);//htons()端口转换二进制


                                    //绑定socket:bind(socket套接字,地址,)
    if (bind(sockSer, (SOCKADDR*)&addrSer, sizeof(SOCKADDR)) == -1)
    {
        cout << "bind fail" << endl;
    }
    else { cout << "bind success" << endl; }


    //监听请求listen(服务端套接字,等待队列最大容量(3-5))
    if (listen(sockSer, 5) == -1)
    {
        cout << "listen fail" << endl;
    }
    else { cout << "listen success" << endl; }


    /*
    //正常一对一
    sockCli = accept(sockSer, (SOCKADDR*)&addrCli, &naddr);

    //请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字sockCli

    if (sockCli != INVALID_SOCKET) {//连接成功
        cout << "连接客户端成功!" << endl;

        strncpy_s(sendBuf, "Hello",50);
        send(sockCli, sendBuf, sizeof(sendBuf), 0);//发送数据到客户端

    }
    else { cout << "连接客户端失败!" << endl; }
    
    system("pause");
    */



    //allSocketsSet存放sockSer+所有的已连接的Client Sockets
    fd_set allSocketsSet;
    FD_ZERO(&allSocketsSet);

    //先把sockSer放进去,如果sockSer可读(FD_SET(sockCli, &allSocketsSet);)表示有客户端连接
    FD_SET(sockSer, &allSocketsSet);

    while (true)
    { 
    
        fd_set readSet;
        //每次select前要存储已经连接的描述符
        readSet = allSocketsSet;
        int ret = select(0, &readSet, 0, 0, 0);
        if (ret == SOCKET_ERROR)
        {
            cout << "select error" << endl;
            break;
        }

//不断等待客户端请求的到来,到来了就加allSocketsSet,之后跳出循环
        if (FD_ISSET(sockSer, &readSet))
        {//有客户端连接,就给他保存到allSocketsSet中

            sockCli = accept(sockSer, (SOCKADDR*)&addrCli, &naddr);
            if (sockCli == INVALID_SOCKET)
            {
                cout << "accept error" << endl;
                break;
            }
            else 
            {
                cout << "accept连接客户端成功!" << endl;
                //将连接成功的sockCli放入allSocketsSet
                FD_SET(sockCli, &allSocketsSet);
                continue;
            }
        }
//没客户端到来,再检查allSocketsSet中的其他客户端的sockets是否可读,可读表示有数据到来。
        for (u_int i = 0; i < allSocketsSet.fd_count; ++i)
        {
            SOCKET socket = allSocketsSet.fd_array[i];
            if (FD_ISSET(socket, &readSet))
            {//可读,有数据到来
                int result = recv(socket, recvBuf, BUF_SIZE, 0);
                //返回值分析
                if (result == SOCKET_ERROR)
                {//读的过程中出错
                    DWORD err = WSAGetLastError();
                    if (err == WSAECONNRESET)       // 客户端的socket没有被正常关闭,即没有调用closesocket
                    {
                        cout << "客户端被强行关闭" << endl;
                    }
                    else
                    {
                        cout << "recv() error" << endl;
                    }

                    closesocket(socket);
                    FD_CLR(socket, &allSocketsSet);

                    //std::cout << "目前客户端的数量为:" << allSockSet.fd_count - 1 << std::endl;
                    cout << "目前客户端的数量为:" << std::to_string(allSocketsSet.fd_count - 1) << endl;
                    break;
                }
                else if (result == 0)
                {//客户端的socket调用closesocket正常关闭
                    closesocket(socket);
                    FD_CLR(socket, &allSocketsSet);
                    //-1是因为减去sockSer
                    cout << "客户端已经退出,目前客户端的数量为:" << std::to_string(allSocketsSet.fd_count - 1) << endl;
                    break;
                }
                if (result > 0)
                {//正常读取了结果
                    cout << recvBuf << endl;
                    
                    /*
                    *
                    *
                    此处可添加对客户端发来消息的处理。
                    *
                    */
                }
            }

        }
    }
    closesocket(sockSer);
    closesocket(sockCli);
    return 0;
}

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