select_selector - CSDN
select 订阅
select是一个计算机函数,位于头文件#include 。该函数用于监视文件描述符的变化情况——读写或是异常。 展开全文
select是一个计算机函数,位于头文件#include 。该函数用于监视文件描述符的变化情况——读写或是异常。
信息
外文名
select
头文件
#include
属    性
Linux 网络编程
返回值
>0、-1、0
select头文件
#include /* 根据POSIX.1 - 2001 *//*根据早期的标准*/#include#include#include
收起全文
精华内容
参与话题
  • select函数详解

    千次阅读 2018-04-01 15:10:35
    select函数的功能和调用顺序 使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。 非阻塞方式:non-block,就是进程或线程执行此函数时不必非要等待...

    select函数的功能和调用顺序

    使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
    非阻塞方式:non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生,则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。
    select函数用来统一监视多个文件描述符的:
    1、 是否存在套接字接收数据?
    2、 无需阻塞传输数据的套接字有哪些?
    3、 哪些套接字发生了异常?

    select函数调用过程:
    这里写图片描述
    由上图知,调用select函数需要一些准备工作,调用后还需要查看结果。

    设置文件描述符

    select可以同时监视多个文件描述符(套接字)。
    此时需要先将文件描述符集中到一起。集中时也要按照监视项(接收,传输,异常)进行区分,即按照上述3种监视项分成三类。
    使用fd_set数组变量执行此项操作,该数组是存有0和1的位数组。
    这里写图片描述
    数组是从下标0开始,最左端的位表示文件描述符0。如果该位值为1,则表示该文件描述符是监视对象。
    图上显然监视对象为fd1和fd3。

    “是否应当通过文件描述符的数字直接将值注册到fd_set变量?”
    当然不是!操作fd_set的值由如下宏来完成:

    FD_ZERO(fd_set* fdset): 将fd_set变量的所有位初始化为0。
    FD_SET(int fd, fd_set* fdset):在参数fd_set指向的变量中注册文件描述符fd的信息。
    FD_CLR(int fd, fd_set* fdset):参数fd_set指向的变量中清除文件描述符fd的信息。
    FD_ISSET(int fd, fd_set* fdset):若参数fd_set指向的变量中包含文件描述符fd的信息,则返回真。
    这里写图片描述

    设置监视范围及超时

    select函数:

    #include <sys/select.h>
    #include <sys/time.h>
    int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, 
    const struct timeval* timeout);

    select函数共有5个参数,其中参数和返回值:
    maxfd:监视对象文件描述符数量。
    readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值。
    writeset: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值。
    exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值。
    timeout:调用select后,为防止陷入无限阻塞状态,传递超时信息。
    返回值:错误返回-1,超时返回0。当关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数。

    select函数用来验证3种监视项的变化情况。根据监视项声明3个fd_set变量,分别向其注册文件描述符信息,并把变量的地址传递到函数的第二到第四个参数。但是,在调用select函数前需要决定2件事:
    “文件描述符的监视范围是?”
    “如何设定select函数的超时时间?”

    第一,文件描述符的监视范围与第一个参数有关。实际上,select函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在fd_set变量中的文件描述符数。但每次新建文件描述符时,其值都会增1,故只需将最大的文件描述符值加1再传递到select函数即可。(加1是因为文件描述符的值从0开始)
    第二,超时时间与最后一个参数有关。其中timeval结构体如下:

    struct timeval
    {
        long tv_sec;
        long tv_usec;
    };

    本来select函数只有在监视文件描述符发生变化时才返回,未发生变化会进入阻塞状态。指定超时时间就是为了防止这种情况发生。
    将上述结构体填入时间值,然后将结构体地址值传给select函数的最后一个参数,此时,即使文件描述符中未发生变化,只要过了指定时间,也可以从函数返回。不过这种情况下,select函数返回0。 不想设置超时最后一个参数只需要传递NULL。

    调用select函数后查看结果

    如果select返回值大于0,说明文件描述符发生了变化。

    关于文件描述符变化:
    文件描述符变化是指监视的文件描述符中发生了相应的监视事件。
    例如通过select的第二个参数传递的集合中存在需要读取数据的描述符时,就意味着文件描述符发生变化。

    怎样获知哪些文件描述符发生了变化?向select函数的第二到第四个参数传递的fd_set变量中将产生变化,如下图:
    这里写图片描述
    select函数调用完成后,向其传递的fd_set变量中将发生变化。原来为1的所有位均变为0,但发生变化的文件描述符对应位除外。因此,可以认为值为1的位置上的文件描述符发生了变化。

    select函数调用实例

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <sys/select.h>
    
    #define BUF_SIZE 30
    
    int main(int argc, char* argv[])
    {
        fd_set reads,temps;
        int result, str_len;
        char buf[BUF_SIZE];
        struct timeval timeout;
        FD_ZERO(&reads);
        FD_SET(0, &reads);//监视文件描述符0的变化, 即标准输入的变化
        /*超时不能在此设置!
        因为调用select后,结构体timeval的成员tv_sec和tv_usec的值将被替换为超时前剩余时间.
        调用select函数前,每次都需要初始化timeval结构体变量.
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;*/
        while(1)
        {
            /*将准备好的fd_set变量reads的内容复制到temps变量,因为调用select函数后,除了发生变化的fd对应位外,
            剩下的所有位都将初始化为0,为了记住初始值,必须经过这种复制过程。*/
            temps = reads;
            //设置超时
            timeout.tv_sec = 5;
            timeout.tv_usec = 0;
    
            //调用select函数. 若有控制台输入数据,则返回大于0的整数,如果没有输入数据而引发超时,返回0.
            result = select(1, &temps, 0, 0, &timeout);
            if(result == -1)
            {
                perror("select() error");
                break;
            }
            else if(result == 0)
            {
                puts("timeout");
            }
            else
            {
                //读取数据并输出
                if(FD_ISSET(0, &temps))
                {
                    str_len = read(0, buf, BUF_SIZE);
                    buf[str_len] = 0;
                    printf("message from console: %s", buf);
                }
            }
        }
    
        return 0;
    }

    程序运行结果:

    nihao
    message from console: nihao
    goodbye
    message from console: goodbye
    timeout
    timeout

    转载自:
    https://blog.csdn.net/y396397735/article/details/55004775

    展开全文
  • select用法&原理详解(源码剖析)

    万次阅读 多人点赞 2018-04-02 20:36:13
    最近刚接触Linux下的select用法,查阅了很多资料终于懂得了一丁点,故将自己查阅后有用的资料整理在这下面。博客链接都是很有价值,写的很好的文章。在研读源码时主要看的是这篇文章:深入select多路复用内核源码加...

    最近刚接触Linux下的select用法,查阅了很多资料终于懂得了一丁点,故将自己查阅后有用的资料整理在这下面。博客链接都是很有价值,写的很好的文章。在研读源码时主要看的是这篇文章:深入select多路复用内核源码加驱动实现 自己能力精力有限,没有办法自己写一篇完完整整的文章,故只能当个搬运工了,文章先后顺序尽量按照了知识点的先后~~如果有什么问题欢迎一起探讨学习~


    前期知识

    在开始接触select之前,你需要先对IO的同步,异步,阻塞,非阻塞有个基本的了解,知道什么是IO多路复用。下面这篇文章可以帮助你快速区分这几种模型:IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)

    要了解select,你必须得先接触过socket编程,了解什么是文件描述符(fd),文件描述符表,文件指针,可以参阅下面这三篇博文: Linux的SOCKET编程详解 Linux下 文件描述符(fd)与 文件指针(FILE*) file结构体详解

    select的使用场景:

    select需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
    在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。


    select用法

    select的函数格式:

    int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

    解释一下各个参数的意思
    1. int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

    2. struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄。fd_set集合可以通过一些宏由人为来操作。

      FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。 
      FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。 
      FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。 
      FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。 
    3. struct timeval用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。 若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

      struct timeval{      
      
              long tv_sec;   /*秒 */
      
              long tv_usec;  /*微秒 */   
      
          }
    4. 三个fd_set分别监视文件描述符的读写异常变化,如果有select会返回一个大于0的值。如果没有则在timeout的时间后select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读/写/异常变化。

    参考:linux中select()函数分析

    为什么linux select函数的第一个参数总应该是fdmax + 1

    表示的是文件描述符的数量,从0开始所以比最大的描述符多1,详情参考博客: 为什么linux select函数的第一个参数总应该是fdmax + 1 ?——poll和epoll不需要+1


    简单理解select模型

    理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

    (1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

    (2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

    (3)若再加入fd=2,fd=1,则set变为0001,0011

    (4)执行select(6,&set,0,0,0)阻塞等待

    (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

    所以,我们可以得到select模型的特点:
    (1) 文件描述符个数有限,一般来说这个数目和系统内存关系很大。select使用位域的方式来传递关心的文件描述符,位域就有最大长度。select使用位域的方式传回就绪的文件描述符,调用者需要循环遍历每一个位判断是否就绪,当文件描述符个数很多,但是空闲的文件描述符大大多于就绪的文件描述符的时候,效率很低。

    (2) 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个 参数。

    (3) 可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

    参考:
    Linux中select IO复用机制&使用代码实例
    select处理带外数据&解决socket中多用户问题代码


    Linux的socket 事件wakeup callback机制

    Linux通过socket睡眠队列来管理所有等待socket的某个事件的process,同时通过wakeup机制来异步唤醒整个睡眠队列上等待事件的process,通知process相关事件发生。通常情况,socket的事件发生的时候,其会顺序遍历socket睡眠队列上的每个process节点,调用每个process节点挂载的callback函数。在遍历的过程中,如果遇到某个节点是排他的,那么就终止遍历,总体上会涉及两大逻辑:(1)睡眠等待逻辑;(2)唤醒逻辑。

    参考:大话 Select、Poll、Epoll


    select源码分析

    函数调用图

    asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp) {
        //从用户进程拷贝超时时间,将超时时间换成jiffies。
        //使用转化后的时间调用ret = core_sys_select(n, inp, outp, exp, &timeout)函数
        //将剩余时间拷贝回用户空间进程
    }
    staticint core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s64 *timeout) {
        //读取当前进程的文件描述符表,如果传入的n大于当前进程最大的文件描述符,给予修正。
        //尝试使用栈分配内存,不够则用堆。需要使用6倍于最大描述符的描述符个数
        //get_fd_set调用copy_from_user从用户空间拷贝了fd_set
        //执行ret = do_select(n, &fds, timeout);
        //将修改后的fd_set写回用户空间
    }

    do_select函数中,遍历所有n个fd,对每一个fd调用对应驱动程序中的poll函数。poll函数调用poll_wait函数,poll_wait函数调用__pollwait(),这个函数会初始化等待队列项(有个pollwake函数),并将该等待队列项添加到从驱动程序中传递过来的等待队列头中去。驱动程序在得知设备有IO事件时(通常是该设备上IO事件中断),会调用wakeup,wakeup –> _wake_up_common -> curr->func(即pollwake)。pollwake函数里面调用_pollwake函数, 通过pwq->triggered = 1将进程标志为唤醒。再调用default_wake_function(&dummy_wait, mode, sync, key)这个默认的通用唤醒函数唤醒调用select的进程。 请注意,poll函数会返回一个mask码值,通过这个值我们可以判断是否可读写。更详细的必须看 do_select源码。

    参考:
    分析源码时看的是这篇博客
    Linux内核select源码剖析
    这篇比较简单


    为什么 select 慢

    1. 在第一次所有监听都没有事件时,调用 select 都需要把进程挂到所有监听的文件描述符一次。

    2. 有事件到来时,不知道是哪些文件描述符有数据可以读写,需要把所有的文件描述符都轮询一遍才能知道。

    3. 通知事件到来给用户进程,需要把整个 bitmap 拷到用户空间,让用户空间去查询。


    有用的扩展链接:

    socket、端口、进程的关系

    从glibc源码看系统调用原理

    堆区和栈区内存分配区别


    可能的问题

    在上面的简单理解select模型中,我觉得表述可能有点问题,fd_set的大小是1024,那么能表示的应该是0-1023才对。个人觉得,如果fd = 5的话,可能是在第六位。具体怎样才是正确得之后有机会试一下。

    参考:一种linux下扩展select模型管理能力的方法

    展开全文
  • select()函数的作用

    万次阅读 2018-08-16 17:43:01
    select()在SOCKET编程中还是比较重要的,可是对于初学SOCKET的人来说都不太爱用select()写程序,他们只是习惯写诸如 conncet()、accept()、recv()或recvfrom()这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程...

    select()在SOCKET编程中还是比较重要的,可是对于初学SOCKET的人来说都不太爱用select()写程序,他们只是习惯写诸如 conncet()、accept()、recv()或recvfrom()这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用select()就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况。如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下!

      select()函数的格式(我所说的是Unix系统下的Berkeley Socket编程,和Windows下的有区别,一会儿说明):

     

      int select(int maxfdp, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);

     

      先说明两个结构体:

     

      第一:struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以,毫无疑问,一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合:FD_ZERO(fd_set*),将一个给定的文件描述符加入集合之中FD_SET(int, fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int,   fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int, fd_set*)。一会儿举例说明。

     

      第二:struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个毫秒数。

     

      具体解释select的参数:

     

      int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数值无所谓,可以设置不正确。

     

      fd_set* readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

     

      fd_set* writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

     

      fe_set* errorfds同上面两个参数的意图,用来监视文件错误异常。

     

      struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态。

     

      第一:若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;

     

      第二:若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;

     

      第三:timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

     

      返回值:

     

      负值:select错误

     

      正值:某些文件可读写或出错

     

      0:等待超时,没有可读写或错误的文件

     

      在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。

     

    ---------------------------无连接    

    例子:

      int main()

      {

          int sock;

          FILE* fp;

          struct fd_set fds;

          struct timeval timeout = {3, 0}; //select 等待3秒,3秒轮询, 要非阻塞就置0

          char buffer[256] = {0}; //256字节的接收缓冲区

          /*假设已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开

          sock = socket(...);

          bind(...);

          fp = fopen(...); */

          while(1)

          {

              FD_ZERO(&fds); //每次循环都要清空,否则不能检测描述符变化

              FD_SET(sock, &fds); //添加描述符

              FD_SET(fp, &fds); //同上

              maxfdp = sock>fp?sock+1:fp+1; //描述符最大值加1

              switch(select(maxfdp, &fds, &fds, NULL, &timeout)) //select使用

              {

                  case SOCKET_ERROR: exit(-1); break; //select错误,退出程序

                  case 0: break; //再次轮询

                  default:

                      if(FD_ISSET(sock, &fds)) //测试sock是否可读,即是否网络上有数据

                      {

                          recvfrom(sock, buffer, 256, .... ); //接受网络数据

                           if(FD_ISSET(fp, &fds)) //测试文件是否可写

                          fwrite(fp, buffer...); //写入文件

                          buffer清空;

                      } //end if break

              } //end switch

          } //end while

      } //end main

     

    ---------------------------面向连接

       #include <winsock.h>

       #include <stdio.h>

       #define PORT       5150

       #define MSGSIZE     1024

       #pragma comment(lib, "ws2_32.lib")

       int     g_iTotalConn = 0;

       SOCKET g_CliSocketArr[FD_SETSIZE];

       DWORD WINAPI WorkerThread(LPVOID lpParameter);

       int main()

       {   

           WSADATA     wsaData;   

           SOCKET       sListen, sClient;   

           SOCKADDR_IN local, client;   

           int         iaddrSize = sizeof(SOCKADDR_IN);   

           DWORD       dwThreadId;   

           // Initialize Windows socket library   

           WSAStartup(0x0202, &wsaData);   

           // Create listening socket   

           sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   

           // Bind           

           local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

           local.sin_family = AF_INET;

           local.sin_port = htons(PORT);   

           bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));   

           // Listen   listen(sListen, 3);   

           // Create worker thread   

           CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);     

           while (TRUE)   

           {               // Accept a connection     

               sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);     

               printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));     

               // Add socket to g_CliSocketArr     

               g_CliSocketArr[g_iTotalConn++] = sClient;   

           }     

           return 0;

       }

       DWORD WINAPI WorkerThread(LPVOID lpParam)

       {   

           int             i;   

           fd_set         fdread;   

           int             ret;   

           struct timeval tv = {1, 0};   

           char           szMessage[MSGSIZE];     

           while (TRUE)   

           {     

               FD_ZERO(&fdread);     

               for (i = 0; i < g_iTotalConn; i++)

               {

                   FD_SET(g_CliSocketArr, &fdread);

               }                     // We only care read event

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

               if (ret == 0)

               {       // Time expired

                   continue;

               }

               for (i = 0; i < g_iTotalConn; i++)

               {

                   if (FD_ISSET(g_CliSocketArr, &fdread))

                     {         // A read event happened on g_CliSocketArr

                         ret = recv(g_CliSocketArr, szMessage, MSGSIZE, 0);

                         if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))

                           {

                               // Client socket closed           

                               printf("Client socket %d closed.\n", g_CliSocketArr);

                               closesocket(g_CliSocketArr);

                               if (i < g_iTotalConn - 1)

                               {

                                   g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];

                               }

                           }

                           else

                           {

                                 // We received a message from client

                                 szMessage[ret] = '\0';

                                 send(g_CliSocketArr, szMessage, strlen(szMessage), 0);

                           }

                     } //if

               }//for

           }//while     

           return 0;

       }

       服务器的几个主要动作如下:

       1.创建监听套接字,绑定,监听;

       2.创建工作者线程;

       3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;

       4.接受客户端的连接。

       这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的ccept,服务器应该根据当前的连接数来决定

    是否接受来自某个客户端的连接。一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。

       如下所示:

       int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR *

    g,DWORD dwCallbackData)

       {

           if (当前连接数 < FD_SETSIZE)

               return CF_ACCEPT;

           else   

               return CF_REJECT;

       }

       工作者线程里面是一个死循环,一次循环完成的动作是:

       1.将当前所有的客户端套接字加入到读集fdread中;

       2.调用select函数;

       3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)。

       除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。

       关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.

       在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,且对战类游戏的网络连接量并不大. 对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.

     

    原文:https://blog.csdn.net/zhubosa/article/details/44729247

    展开全文
  • 细谈select函数(C语言)

    万次阅读 多人点赞 2010-11-07 08:33:00
    Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行...

          Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。

     

    Select的函数格式:

    int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);

    先说明两个结构体:

    第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。

    第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

    具体解释select的参数:
    int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

    fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

    fd_set * writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

    fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。

    struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

    返回值:返回状态发生变化的描述符总数。
    负值:select错误

    正值:某些文件可读写或出错

    0:等待超时,没有可读写或错误的文件

     

    select举例:

    ①读取键盘输入值,超时间隔2.5秒,输出用户输入的字符个数。

     

     

     

    ②利用select而不是fork来解决socket中的多客户问题。

     

    服务器端:

     

    客户端:

      

    展开全文
  • SELECT

    2019-07-17 20:39:28
    SELECT 基本结构 --语句顺序 SELECT COLUNM_NAME --COLUNM_NAME 字段名 FROM TABLE_NAME --表名 WHERE --限制条件 GROUP BY --分组 HAVING --对分组后的结果进行筛选 ORDER BY ...
  • C# Select SelectMany 区别

    千次阅读 2019-01-07 13:19:50
    string[] text = { "Today is 2018-06-06", "weather is sunny"... var tokens = text.Select(s =&gt; s.Split(' ')); var tokens2 = text.SelectMany(s =&gt; s.Split(' '));  
  • SelectMany 和 Select的区别

    千次阅读 2018-10-24 19:28:47
    如果我们看这两个扩展函数的定义很容易明白——Select是把要遍历的集合IEnumerable逐一遍历,每次返回一个T,合并之后直接返回一个IEnumerable,而SelectMany则把原有的集合IEnumerable每个元素遍历一遍,每次返回一...
  • selectselectMany区别

    2019-06-25 10:03:49
    string[] text = { "Albert was here", "Burke slept late", "Connor is happy" }; var tokens = text.Select(s => s.Split(' ')); foreach (string[] line in tokens) ...
  • SelectSelectMany的区别

    2019-07-04 12:01:19
    Select() 和 SelectMany() 的工作都是依据源值生成一个或多个结果值。 Select() 为每个源值生成一个结果值。因此,总体结果是一个与源集合具有相同元素数目的集合。与之相反,SelectMany() 将生成单一总体结果...
  • Select & SelectMany

    千次阅读 2012-09-02 16:38:23
    Select() 和 SelectMany() 的工作都是依据源值生成一个或多个结果值。Select() 为每个源值生成一个结果值。因此,总体结果是一个与源集合具有相同元素数目的集合。与之相反,SelectMany() 将生成单一总体结果,...
  • SQL中SELECT语句详解

    万次阅读 多人点赞 2018-06-04 17:12:25
    本篇文章讲述SQL语句中的SELECT查询语句,以供参考,如有错误或不当之处还望大神们告知。 简单查询SELECT-FROM 用于无条件查询单张表中的行或列 假设有表如图所示 查询名字叫 ‘叶清逸’ 的记录: select...
  • 实时中位数

    2016-07-20 20:29:30
    题目描述 现有一些随机生成的数字要将其依次传入,请设计一个高效算法,对于每次传入一个数字后,算出当前所有传入数字的中位数。(若传入了偶数个数字则令中位数为第n/2小的数字,n为已传入数字个数)。...
  • jquery插件select2的所有事件,包括清除,删除,打开等
  • SQL 数据库 省市区三级表 建立语句

    万次阅读 2012-01-11 09:44:12
    SQL省市区三级表 -- 表的结构 area DROP TABLE area; CREATE TABLE area (  id int NOT NULL ,  areaID int NOT NULL,  area varchar(200) NOT NULL,  fatherID int NOT NULL, ...DRO
  • 比如:select a,(select b from B) b from A,这样写应该注意什么呢?什么情况下使用这种写法?
  • select2 使用教程(简)

    万次阅读 2020-09-23 23:41:41
    用了这么久的Select2插件,也该写篇文章总结总结。当初感觉Select2不是特别好用,但又找不到比它更好的下拉框插件。 在我的印象里Select2有2个版本,最新版本有一些新的特性,并且更新了一下方法参数,比最初版本要...
  • select into from 和 insert into select都是用来复制表,两者的主要区别为: select into from 要求目标表不存在,因为在插入时会自动创建。insert into select from 要求目标表存在 ...
  • 全国地区+邮编的数据库脚本

    万次阅读 2014-04-18 15:05:12
    create database DB_Pro_City_PostCode on (name=DB_Pro_City_PostCode_dat, filename='D:\省市邮编区号数据库\DB_Pro_City_PostCode.mdf', size=10000KB, filegrowth=5%) LOG on (name=DB_Pro_City_PostCode_...
  • jQuery Select2使用js赋值

    万次阅读 2016-05-20 11:02:43
    select2把select标签画成了别的的东西,常规的select对象被jquery藏了起来,所以修改值的时候使用dom对象的触发器才行。 $("#month").val(“1”).trigger("change");
  • mysql中update和select结合使用

    万次阅读 多人点赞 2018-02-28 17:52:26
    在遇到需要update设置的参数来自从其他表select出的结果时,需要把update和select结合使用,不同数据库支持的形式不一样,在mysql中如下:update A inner join(select id,name from B) c on A.id = c.id set A.name = c....
1 2 3 4 5 ... 20
收藏数 2,845,816
精华内容 1,138,326
关键字:

select