IO多路复用之select篇
前言
在Linux
系统下,select()
为最早支持IO多路复用的系统调用。该接口相比poll()
、epoll()
来说,更为简单,但是效率较低,而且最大只支持FD_SETSIZE
(1024)个描述符。如果对并发数量的要求高于1024,请考虑使用poll()
或者epoll()
。
系统调用
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select()
监控多个文件描述符,直到其中的一个或多个文件描述符变为就绪状态。就绪状态代表该描述符可读、可写或者异常状态。select()
监控的描述符个数要求小于FD_SETSIZE
(1024),poll()
和epoll()
没有这个限制。
描述符集合
3个描述符集合,既是传入参数,同时又是传出参数。调用者可以将要监听的描述符加入到集合中,传递给内核,内核在感知到该描述符就绪事件后,再通过该集合,将该描述符传出。
3个描述符集合分别代表:
- readfds:可读集合。将描述符加入该集合,代表调用者对该描述符的读事件感兴趣,具体描述见下面小节;
- writefds:可写集合。将描述符加入该集合,代表调用者对该描述符的写事件感兴趣,具体描述见下面小节;
- exceptfds:异常集合。将描述符加入该集合,代表调用者对该描述符的异常事件感兴趣,具体描述见下面小节。
因为这3个集合,既是传入参数,又是传出参数,内核会修改这3个集合的值,所以在一个循环中使用select()
的时候,要每次调用之前都要重新初始化这3个集合。根据man select
的介绍,这个应该是个设计方面的失误。
那么如何操作描述符集合呢?比如将一个描述符添加到集合中,或者从集合中删除一个描述符。系统提供了4个宏来对描述符集合进行操控:
FD_ZERO()
:这个宏将指定的集合置空。这是初始化一个集合的第一步;FD_SET()
:这个宏将指定的文件描述符添加到集合中。FD_CLR()
:这个宏将指定的文件描述符从集合中移除。FD_ISSET()
:测试指定的文件描述符是否在集合中。因为select()
返回的时候,会修改集合的值,所以可以用此宏来测试描述符是否仍在集合中,如果在的话,返回非零值,如果不在的话,返回0。
参数
select()
函数的5个参数如下所述:
- readfds:可读集合。当调用者对某一个文件描述符的可读事件感兴趣的时候,可以将该描述符加入集合。当
select()
函数返回后,判断该描述符是否在这个集合中,如果在这个集合中,代表该描述符可读。此处的可读不仅仅包括从缓冲区读取数据,也包括读取EOF,即正常结束。 - writefds:可写集合。当调用者对某一个文件描述符的可写事件感兴趣的时候,可以将该描述符加入集合。当
select()
函数返回后,判断该描述符是否在这个集合中,如果在这个集合中,代表该描述符可写。 - execeptfds:异常集合。
- nfds:这个参数指定三个集合中,最大的那个文件描述符数字+1。此参数告诉内核,最大遍历到哪个描述符位置,避免无效遍历。
timeout:超时参数,该参数为
timeval
类型。该参数指定select()
为阻塞状态,或者满足如下条件:- 某一个文件描述符变为就绪状态;
- 系统调用被某一信号中断;
发生了超时。
该结构的定义如下:
struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
该参数的取值有如下几种情况:
- 如果两个字段都为0,则
select()
立即返回。 - 如果该参数指定为
NULL
,则select()
会一直等,直到某个文件描述符就绪。 - 如果其中的字段值不为0,则
select()
最长会等待指定的时间。
- 如果两个字段都为0,则
返回值
- 大于0:成功。返回值代表就绪的文件描述符的个数,该个数为三个集合中描述符之和。
- 0:超时返回。
- -1:发生错误。
errno
指定错误详细原因。
代码示例
用select()
写一个回射服务器端,代码示例如下:
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXLINE 4096
int main(int argc, char *argv[])
{
int listenfd, maxfd, nready, clientlen;
struct sockaddr_in servaddr, clientaddr;
fd_set rset, allset;
int sockclients[FD_SETSIZE];
char buf[MAXLINE];
int maxindex;
int n;
if (argc != 2)
{
printf("usage: ./a.out <ListenPort>\n");
return 0;
}
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0)
{
perror("socket error");
return 0;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[1]));
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind error.");
return 0;
}
listen(listenfd, SOMAXCONN);
FD_ZERO(&rset);
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
maxfd = listenfd;
maxindex = 0;
for (size_t i = 0; i < FD_SETSIZE; i++)
{
sockclients[i] = -1;
}
for (;;)
{
rset = allset;
if ((nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0)
{
if (errno == EINTR)
{
continue;
}
else
{
perror("select error.");
return 0;
}
}
else if (nready == 0)
{
printf("select timeout\n");
continue;
}
else // nready > 0
{
if (FD_ISSET(listenfd, &rset)) // new connection
{
clientlen = sizeof(clientaddr);
int connfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientlen);
if (connfd < 0)
{
perror("accept error.");
continue;
}
printf("new connection from %s:%d\n",
inet_ntop(AF_INET, &clientaddr.sin_addr, buf, sizeof(buf)),
ntohs(clientaddr.sin_port));
size_t i;
for (i = 0; i < sizeof(FD_SETSIZE); i++)
{
if (sockclients[i] == -1)
{
sockclients[i] = connfd;
break;
}
}
if (i >= FD_SETSIZE)
{
perror("too many clients connection.");
close(connfd);
continue;
}
maxindex = (i > maxindex ? i : maxindex);
maxfd = (maxfd > connfd ? maxfd : connfd);
FD_SET(connfd, &allset);
if (--nready == 0)
{
continue;
}
}
if (nready > 0) // connfd can read
{
for (size_t i = 0; i <= maxindex; i++)
{
int connfd;
if((connfd = sockclients[i]) < 0)
{
continue;
}
if (FD_ISSET(connfd, &rset))
{
if ((n = read(connfd, buf, sizeof(buf))) > 0)
{
write(connfd, buf, n);
}
else if (n == 0) // EOF
{
clientlen = sizeof(clientaddr);
if(getpeername(connfd, (struct sockaddr*)&clientaddr, &clientlen) == 0)
{
printf("client(%s:%d) close the connection.\n",
inet_ntop(AF_INET, &clientaddr.sin_addr, buf, sizeof(buf)),
ntohs(clientaddr.sin_port));
}
sockclients[i] = -1;
FD_CLR(sockclients[i], &allset);
break;
}
else // read error
{
clientlen = sizeof(clientaddr);
if(getpeername(connfd, (struct sockaddr*)&clientaddr, &clientlen) == 0)
{
printf("read error from client(%s:%d).\n",
inet_ntop(AF_INET, &clientaddr.sin_addr, buf, sizeof(buf)),
ntohs(clientaddr.sin_port));
}
sockclients[i] = -1;
FD_CLR(sockclients[i], &allset); // 输出,打印信息
break;;
}
}
}
}
}
}
}
怎么收藏这篇文章?
不错不错,我喜欢看