• Linux C select 的学习


    一. select 系统调用

    1. 函数说明

    #include 
    #include 
    
    int select(int nfds, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
    
    • 1
    • 2
    • 3
    • 4
    • nfds: 是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加 1。在linux系统中,select 的默认最大值为1024 。设置这个值的目的是为了不用每次都去轮询这1024fd,假设我们只需要几个套接字,我们就可以用最大的那个套接字的值加上1作为这个参数的值,当我们在等待是否有套接字准备就绪时,只需要监测nfds+1个套接字就可以了,这样可以减少轮询时间以及系统的开销。

    • readfds: 用来检测输入是否准备就绪的文件描述符集合(从内核读,用的最多)

    • writefds: 用来检测输出是否准备就绪的文件描述符集合(往内核写,比较少用)

    • exceptset; 很少用到,我没用过。用来检测异常情况是否发生的文件描述符集合。

    • timeout: 堵塞的超时时间,如果为 NULL 则堵塞。

    系统调用 select() 会一直堵塞,直到一个或者多个文件描述符集合称为就绪态,select 就会往下执行。

    套接字可写的条件

    这个概念很常见,跟套接字可读一样。

    1. 使用 select 监听,如果是可读套接字的 fd_set,那么套接字对应的内核中,一旦收到数据,那么就会使 select 返回,fd_set准备就绪。
    2. 同样道理,使用 select 监听,如果是可写的套接字的 fd_set,那么套接字对应的内核中,一旦数据的缓冲区有空余的位置写数据,那么就会使 select 返回,fd_set准备就绪。这句话有些难理解。

    具体的说:

    一、 满足下列四个条件中的任何一个时,一个套接字准备好读。

    1. 该套接字接收缓冲区中的数据字节数大于等于套接字接收缓存区低水位。 对于TCP和UDP套接字而言,缓冲区低水位的值默认为1。那就意味着,默认情况下,只要缓冲区中有数据,那就是可读的。我们可以通过使用SO_RCVLOWAT套接字选项(参见setsockopt函数)来设置该套接字的低水位大小。此种描述符就绪(可读)的情况下,当我们使用read/recv等对该套接字执行读操作的时候,套接字不会阻塞,而是成功返回一个大于0的值(即可读数据的大小)。
    2. 该连接的读半部关闭(也就是接收了FIN的TCP连接)。对这样的套接字的读操作,将不会阻塞,而是返回0(也就是EOF)。
    3. 该套接字是一个listen的监听套接字,并且目前已经完成的连接数不为0。对这样的套接字进行accept操作通常不会阻塞。
    4. 有一个错误套接字待处理。对这样的套接字的读操作将不阻塞并返回-1(也就是返回了一个错误),同时把errno设置成确切的错误条件。这些待处理错误(pending error)也可以通过指定SO_ERROR套接字选项调用getsockopt获取并清除。

    二、满足下列四个条件中的任何一个时,一个套接字准备好写。

    1. 该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓存区低水位标记时,并且该套接字已经成功连接(UDP套接字不需要连接)。对于TCP和UDP而言,这个低水位的值默认为2048,而套接字默认的发送缓冲区大小是8k,这就意味着一般一个套接字连接成功后,就是处于可写状态的。我们可以通过SO_SNDLOWAT套接字选项(参见setsockopt函数)来设置这个低水位。此种情况下,我们设置该套接字为非阻塞,对该套接字进行写操作(如write,send等),将不阻塞,并返回一个正值(例如由传输层接受的字节数,即发送的数据大小)。
    2. 该连接的写半部关闭(主动发送FIN包的TCP连接)。对这样的套接字的写操作将会产生SIGPIPE信号。所以我们的网络程序基本都要自定义处理SIGPIPE信号。因为SIGPIPE信号的默认处理方式是程序退出。
    3. 使用非阻塞的connect套接字已建立连接,或者connect已经以失败告终。即connect有结果了。
    4. 有一个错误的套接字待处理。对这样的套接字的写操作将不阻塞并返回-1(也就是返回了一个错误),同时把errno设置成确切的错误条件。这些待处理的错误也可以通过指定SO_ERROR套接字选项调用getsockopt获取并清除。

    在这里插入图片描述

    • 后面有一个例子,是关于一个readfdsfd_set 是一个标准输入,一个 writefdsfd_set 是标准输出。使用 select 堵塞等待。
    • 当键盘有输入时 readfds 中是标准输出, select 会立马返回就绪
    • 当监听writefds时,因为里面放的是标准输入,因为标准输入在正常情况下就是可写的,所以 select 会立马返回并就绪,不会堵塞。

    2. 数据类型 fd_set

    这个网上资料很多,细节很多。我有空回来再补

    操作 fd_set 的四个宏:

    #include    
    
    int FD_ZERO(int fd, fd_set *fdset);    // 初始化:相当于将 fd_set 清零
    int FD_CLR(int fd, fd_set *fdset);     // 将文件描述符 fd 从 fd_set 中移除
    int FD_SET(int fd, fd_set *fd_set);    // 将文件描述符 fd 加入 fd_set 中
    int FD_ISSET(int fd, fd_set *fdset);   // 如果文件描述符 fd 是 fd_set 所指向的成员,返回 true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. timeout 参数

    • 类型: struct timeval
    #include 
    
    struct timeval{
            long tv_sec;   /*秒 */
            long tv_usec;  /*微秒 */   
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面说,timeoutNULL 时,select 会堵塞。如果 timeout 的两个值都为 0,不会堵塞。select直接立即返回

    4. select 返回值

    这个看多了用多了代码就会了,大部分使用select 的代码都会出现判断它的所有返回值的:

    while(1)
    {
        ...
        ret = select(nfds, readfds, ...);
        if (ret > 0) {}
        else if (ret == 0) {}
        else if (ret < 0) {}
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    返回值:

    • 大于 0:返回一个或多个已经就绪的文件描述符
    • 小于 0: 有错误发生:一般是 EBADFEINTR 。具体上网搜
    • 等于 0: 超时,每个文件描述符集合会清空。

    5. example

    "t_select.h" 在文末获取。

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include "t_select.h"
    
    
    /**
     * example:
     *      ./t_select 10 0r 1w
     * 
     * comment:
     *      10: timeout(second)
     *      0r: 以读形式读取标准输入
     *      1w: 以写形式去写标准输出
    */
    
    int main(int argc, char *argv[])
    {
        fd_set readfds, writefds;
        int ready, nfds, fd, numRead, j;
        struct timeval timeout;
        struct timeval *pto;
        char buf[10]; /* Large enough to hold "rw\0" */
    
        if (argc < 2 || strcmp(argv[1], "--help") == 0)
            usageError(argv[0]);
    
        /* Timeout for select() is specified in argv[1] */
    
        // "-": 代表堵塞; 否则就是超时时间
        if (strcmp(argv[1], "-") == 0)
        {
            pto = NULL; /* Infinite timeout */
        }
        else
        {
            pto = &timeout;
            timeout.tv_sec = getLong(argv[1], 0, "timeout");
            timeout.tv_usec = 0; /* No microseconds */
        }
    
        /* Process remaining arguments to build file descriptor sets */
    
        nfds = 0;
        FD_ZERO(&readfds);
        FD_ZERO(&writefds);
    
        for (j = 2; j < argc; j++)
        {
            numRead = sscanf(argv[j], "%d%2[rw]", &fd, buf);
            if (numRead != 2)
                usageError(argv[0]);
            if (fd >= FD_SETSIZE)
                printf("file descriptor exceeds limit (%d)\n", FD_SETSIZE);
    
            if (fd >= nfds)
                nfds = fd + 1; /* Record maximum fd + 1 */
            if (strchr(buf, 'r') != NULL)
                FD_SET(fd, &readfds);
            if (strchr(buf, 'w') != NULL)
                FD_SET(fd, &writefds);
        }
    
        /* We've built all of the arguments; now call select() */
    
        // 第一个参数:套接字返回
        ready = select(nfds, &readfds, &writefds, NULL, pto);
        /* Ignore exceptional events */
        if (ready == -1)
            usageError("select");
    
        /* Display results of select() */
    
        printf("ready = %d\n", ready);
        for (fd = 0; fd < nfds; fd++)
            printf("%d: %s%s\n", fd, FD_ISSET(fd, &readfds) ? "r" : "",
                   FD_ISSET(fd, &writefds) ? "w" : "");
    
        if (pto != NULL)
            printf("timeout after select(): %ld.%03ld\n",
                   (long)timeout.tv_sec, (long)timeout.tv_usec / 1000);
        exit(EXIT_SUCCESS);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    文末

    /**************************************************************************/
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define GET_NUM_H
    
    #define GN_NONNEG 01 /* Value must be >= 0 */
    #define GN_GT_0 02   /* Value must be > 0 */
    
    /* By default, integers are decimal */
    #define GN_ANY_BASE 0100 /* Can use any base - like strtol(3) */
    #define GN_BASE_8 0200   /* Value is expressed in octal */
    #define GN_BASE_16 0400  /* Value is expressed in hexadecimal */
    
    /* Print a diagnostic message that contains a function name ('fname'),
       the value of a command-line argument ('arg'), the name of that
       command-line argument ('name'), and a diagnostic error message ('msg'). */
    static void
    gnFail(const char *fname, const char *msg, const char *arg, const char *name)
    {
        fprintf(stderr, "%s error", fname);
        if (name != NULL)
            fprintf(stderr, " (in %s)", name);
        fprintf(stderr, ": %s\n", msg);
        if (arg != NULL && *arg != '\0')
            fprintf(stderr, "        offending text: %s\n", arg);
    
        exit(EXIT_FAILURE);
    }
    /* Convert a numeric command-line argument ('arg') into a long integer,
       returned as the function result. 'flags' is a bit mask of flags controlling
       how the conversion is done and what diagnostic checks are performed on the
       numeric result; see get_num.h for details.
    
       'fname' is the name of our caller, and 'name' is the name associated with
       the command-line argument 'arg'. 'fname' and 'name' are used to print a
       diagnostic message in case an error is detected when processing 'arg'. */
    
    static long
    getNum(const char *fname, const char *arg, int flags, const char *name)
    {
        long res;
        char *endptr;
        int base;
    
        if (arg == NULL || *arg == '\0')
            gnFail(fname, "null or empty string", arg, name);
    
        base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? 8
                                       : (flags & GN_BASE_16)  ? 16
                                                               : 10;
    
        errno = 0;
        res = strtol(arg, &endptr, base);
        if (errno != 0)
            gnFail(fname, "strtol() failed", arg, name);
    
        if (*endptr != '\0')
            gnFail(fname, "nonnumeric characters", arg, name);
    
        if ((flags & GN_NONNEG) && res < 0)
            gnFail(fname, "negative value not allowed", arg, name);
    
        if ((flags & GN_GT_0) && res <= 0)
            gnFail(fname, "value must be > 0", arg, name);
    
        return res;
    }
    /* Convert a numeric command-line argument string to a long integer. See the
       comments for getNum() for a description of the arguments to this function. */
    
    long getLong(const char *arg, int flags, const char *name)
    {
        return getNum("getLong", arg, flags, name);
    }
    /* Convert a numeric command-line argument string to an integer. See the
       comments for getNum() for a description of the arguments to this function. */
    
    int getInt(const char *arg, int flags, const char *name)
    {
        long res;
    
        res = getNum("getInt", arg, flags, name);
    
        if (res > INT_MAX || res < INT_MIN)
            gnFail("getInt", "integer out of range", arg, name);
    
        return res;
    }
    /***********************************************************************************************************/
    
    static void usageError(const char *progName)
    {
        fprintf(stderr, "Usage: %s {timeout|-} fd-num[rw]...\n", progName);
        fprintf(stderr, "      - means infinite timeout; \n");
        fprintf(stderr, "      r = monitor for read\n");
        fprintf(stderr, "      w = monitor for write\n\n");
        fprintf(stderr, "      e.g.: %s - 0rw 1w\n", progName);
    
        exit(EXIT_FAILURE);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
  • 相关阅读:
    防火墙基础实验配置
    Kubernetes v1.25 搭建单节点集群用于Debug K8S源码
    vue-pdf打印出现方字块乱码问题
    在K8S1.24使用Helm3部署Alluxio2.8.1
    【华为云云耀云服务器L实例评测】- 云原生实践,快捷部署人才招聘平台容器化技术方案!
    【初学者入门C语言】之习题篇(二)
    腾讯148道面试题,(程序员必备学习方向)全会拿45Koffer没问题
    如何打造一个可躺赚的网盘项目,每天只需要2小时
    pytest(13)-多线程、多进程执行用例
    精益(Lean)与ERP实施
  • 原文地址:https://blog.csdn.net/yong1585855343/article/details/133779873