I/O多路复用-select函数

Nov 3, 2014


函数申明

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

参数

  • nfds 最大文件描述符加1. (FD_SETSIZE ,大于等于系统支持的最大文件描述符number)
  • readfds 检查可读性的描述符集合,函数调用后可能被覆盖为可读描述符的结果返回
  • writefds 检查可写的描述符集合,函数调用后可能被覆盖为可写描述符的结果返回
  • errorfds 检查异常的描述符集合
  • timeout 时间结构体指针

是否阻塞

timeout 参数决定select是否阻塞
当 timeout 为 NULL 时, select 函数会一直阻塞直到监听的文件描述符发生变化.
当 timeout 为 0 时, select 函数立刻返回.
当 timeout > 0 时, select 函数阻塞特定时间返回.

返回值

对于 select 的返回值, 负值表示 select 函数执行中发生错误. 0 表示超时结束. 正值表示有可读描述符.

示例代码

代码源地址

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define PORT    5555
#define MAXMSG  512

int
read_from_client (int filedes)
{
  char buffer[MAXMSG];
  int nbytes;

  nbytes = read (filedes, buffer, MAXMSG);
  if (nbytes < 0)
    {
      /* Read error. */
      perror ("read");
      exit (EXIT_FAILURE);
    }
  else if (nbytes == 0)
    /* End-of-file. */
    return -1;
  else
    {
      /* Data read. */
      fprintf (stderr, "Server: got message: `%s'\n", buffer);
      return 0;
    }
}

int
main (void)
{
  extern int make_socket (uint16_t port);
  int sock;
  fd_set active_fd_set, read_fd_set;
  int i;
  struct sockaddr_in clientname;
  size_t size;

  /* Create the socket and set it up to accept connections. */
  sock = make_socket (PORT);
  if (listen (sock, 1) < 0)
    {
      perror ("listen");
      exit (EXIT_FAILURE);
    }

  /* Initialize the set of active sockets. */
  FD_ZERO (&active_fd_set);
  FD_SET (sock, &active_fd_set);

  // 以上为常规 socket 初始化, 监听特定端口, 置空描述符集合变量 active_fd_set read_fd_set
  // active_fd_set 是服务端 accept 的所有 socket 描述符, read_fd_set 是数据操作临时变量

  while (1)
    {
      /* Block until input arrives on one or more active sockets. */
      read_fd_set = active_fd_set;

      // 阻塞直到 read_fd_set 中有可读的 socket 描述符号
      // 覆盖 read_fd_set 变量,将可读的 socket 描述符集合赋值给 read_fd_set 变量
      // 返回可读描述符集合 read_fd_set 中描述符累加的和

      if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
        {
          perror ("select");
          exit (EXIT_FAILURE);
        }

      /* Service all the sockets with input pending. */

      // 这个循环好吓人 没用更好的办法 遍历 read_fd_set 集合?

      for (i = 0; i < FD_SETSIZE; ++i)
        // 判断 read_fd_set 集合中指是否存在 i
        if (FD_ISSET (i, &read_fd_set))
          {

            // 新的 socket 请求则加入到 active_fd_set 集合中
            // 已存在文件描述符则直接读

            if (i == sock)
              {
                /* Connection request on original socket. */
                int new;
                size = sizeof (clientname);
                new = accept (sock,
                              (struct sockaddr *) &clientname,
                              &size);
                if (new < 0)
                  {
                    perror ("accept");
                    exit (EXIT_FAILURE);
                  }
                fprintf (stderr,
                         "Server: connect from host %s, port %hd.\n",
                         inet_ntoa (clientname.sin_addr),
                         ntohs (clientname.sin_port));
                FD_SET (new, &active_fd_set);
              }
            else
              {
                /* Data arriving on an already-connected socket. */
                if (read_from_client (i) < 0)
                  {
                    close (i);
                    FD_CLR (i, &active_fd_set);
                  }
              }
          }
    }
}

-->