**select**是用于I/O多路转接的一个系统调用函数。

在C程序中，该系统调用在 sys/select.h 或 unistd.h 中声明，语法如下：

```
int select(int nfds, fd\_set\* readfds, fd\_set\* writefds, fd\_set\* errorfds, struct timeval\* timeout);

```

| 参数 | 描述 |
| --- | --- |
| *nfds* | sets的文件描述符的最大值 |
| *readfds* | fd\_set 类型，包含了需要检查是否可读的描述符，输出时表示哪些描述符可读。可为 NULL。 |
| *writefds* | fd\_set 类型，包含了需要检查是否可写的描述符，输出时表示哪些描述符可写。可为 NULL。 |
| *errorfds* | fd\_set 类型，包含了需要检查是否出错的描述符，输出时表示哪些描述符出错。可为 NULL。 |
| *timeout* | struct timeval 类型的结构体，表示等待检查完成的最长时间。 |

为了维护fd\_set类型的参数，会使用下面四个宏：FD\_SET(), FD\_CLR(), FD\_ZERO() 和 FD\_ISSET()。

**返回值**：

```
      这个函数将返回描述符集的个数， 如果超时返回为0，错误则返回-1。
```

**参看**：

* select(2)
* poll(2)

select与epoll的区别
---------------

|  | epoll | select |
| --- | --- | --- |
| 概述 | epoll是个模块，由三个系统调用组成，内核中由用文件系统实现 | select是个系统调用 |
| 结构体定义 | 
```
typedef union epoll\_data {
 void \*ptr;
 int fd;
 \_\_uint32\_t u32;
 \_\_uint64\_t u64;
} epoll\_data\_t;

struct epoll\_event { 
 \_\_uint32\_t events; // epoll 监听的事件类型
 epoll\_data\_t data; /\* User data variable \*/
};

```
 | 
```
struct timeval{
 long tv\_sec;//second
 long tv\_usec;//minisecond
}

typedef struct fd\_set {
 u\_int fd\_count;
 int fd\_array[FD\_SETSIZE];
} //fd\_array可SIZE\*8个socket

```
 |
| 可用的事件 | EPOLLIN ：表示对应的文件描述符可以读；
EPOLLOUT：表示对应的文件描述符可以写；
EPOLLPRI： 表示对应的文件描述符有紧急的数据可读；
EPOLLERR： 表示对应的文件描述符发生错误；
EPOLLHUP：表示对应的文件描述符被挂断；
EPOLLET： ET的epoll工作模式； | fd\_set有三种类型： 
readfds,
writefds,
exceptionfds

 |
| 操作函数 | 三个系统调用：epoll\_create epoll\_ctl epoll\_wait | 一个系统调用：select  四个宏: FD\_ZERO FD\_SET FD\_CLR FD\_ISSET |
| 运行模式 | 边沿触发 (ET)、状态触发 (LT) |  状态触发 |
| 运行过程 | 
```
int fd = epoll\_create(1); // 创建一个 epoll 实例，参数可以是任意正整数
struct epoll\_event events[xxxB];// epoll 实例将发生的事件写入该数组
while(1){
 int nfds = epoll\_wait( ); // 等待事件发生
 for(int i=0; i<nfds; i++){
 …
 }//end for
}//end while

```
 | 
```
struct timeval tv;
fd\_set rfds;
tv={5,0}; // 设置超时
while(1){
 FD\_ZERO(&rfds);
 if (!select()) continue;
 for(int i=0;i<maxfds; i++){
 ...
 } // 结束 for 循环
} // 结束 while 循环

```
 |
| 优点 | 1）epoll\_wait返回的都是有效数据，可直接从struct epoll\_event[]中获取事件，效率高。 |  |
| 缺点 |  | 每次select有数据要遍历全部socket |
| 注意事项 | 每次取事件后，要重新注册此socket的事件epoll。(epoll\_ctl) | 每次select之前要重置rfds的值。(FD\_ZERO) |

**说明**：以上无论epoll\_create, fd\_set都受限于系统中单个进程能够打开的文件句柄数。

示例
--

```
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define PORT "9421"

/\* function prototypes \*/
void die(const char\*);

int main(int argc, char \*\*argv)
{
 int sockfd, new, maxfd, on = 1, nready, i;

 struct addrinfo \*res0, \*res, hints;

 char buffer[BUFSIZ];

 fd\_set master, readfds;

 ssize\_t nbytes;

 (void)memset(&hints, '\0', sizeof(struct addrinfo));

 hints.ai\_family = AF\_INET;
 hints.ai\_socktype = SOCK\_STREAM;
 hints.ai\_protocol = IPPROTO\_TCP;
 hints.ai\_flags = AI\_PASSIVE;

 if(-1 == (getaddrinfo(NULL, PORT, &hints, &res0)))
 die("getaddrinfo()");

 for(res = res0; res; res = res->ai\_next)
 {
 if(-1 == (sockfd = socket(res->ai\_family, res->ai\_socktype, res->ai\_protocol)))
 {
 perror("socket()");
 continue;
 }

 if(-1 == (setsockopt(sockfd, SOL\_SOCKET, SO\_REUSEADDR, (char\*)&on, sizeof(int))))
 {
 perror("setsockopt()");
 continue;
 }

 if(-1 == (bind(sockfd, res->ai\_addr, res->ai\_addrlen)))
 {
 perror("bind");
 continue;
 }

 break;

 }

 if(-1 == sockfd)
 exit(EXIT\_FAILURE);

 freeaddrinfo(res0);

 if(-1 == (listen(sockfd, 32)))
 die("listen()");

 if(-1 == (fcntl(sockfd, F\_SETFD, O\_NONBLOCK)))
 die("fcntl()");

 FD\_ZERO(&master);
 FD\_ZERO(&readfds);
 
 FD\_SET(sockfd, &master);

 maxfd = sockfd;

 while(1)
 {
 memcpy(&readfds, &master, sizeof(master));

 (void)printf("running select()\n");

 if(-1 == (nready = select(maxfd+1, &readfds, NULL, NULL, NULL)))
 die("select()");

 (void)printf("Number of ready descriptor: %d\n", nready);

 for(i=0; i<=maxfd && nready>0; i++)
 {
 if(FD\_ISSET(i, &readfds))
 {
 nready--;

 if(i == sockfd)
 {
 (void)printf("Trying to accept() new connection(s)\n");

 if(-1 == (new = accept(sockfd, NULL, NULL)))
 {
 if(EWOULDBLOCK != errno)
 die("accept()");

 break;
 }
 
 else
 {

 if(-1 == (fcntl(new, F\_SETFD, O\_NONBLOCK)))
 die("fcntl()");

 FD\_SET(new, &master);

 if(maxfd < new)
 maxfd = new;
 }
 }

 else
 {
 (void)printf("recv() data from one of descriptors(s)\n");

 nbytes = recv(i, buffer, sizeof(buffer), 0);
 if(nbytes <= 0)
 {
 if(EWOULDBLOCK != errno)
 die("recv()");

 break;
 }

 buffer[nbytes] = '\0';
 printf("%s", buffer);
 
 (void)printf("%zi bytes received.\n", nbytes);

 close(i);
 FD\_CLR(i, &master);

 }
 }

 }

 }

 return 0;
}

void die(const char \*msg)
{
 perror(msg);
 exit(EXIT\_FAILURE);
}

```

参见
--

* Berkeley套接字
* 轮询
* epoll
* kqueue
* IOCP

外部链接
----

* `select(2)` – Linux程序员手册页 – 系统调用（System Calls）
* `select\_tut(2)` – Linux系统调用（System Calls）手册页