Linux下IO多路复用(一)

select

select是跨平台的。

一、复用

  1. 复用的意思时不用每个进程/线程来操控单独的一个IO,只需一个进程/线程来操控多个IO;
  2. 内核空间不能直接解引用用户态的指针;
  3. 非阻塞IO。

二、select说明

  1. 一般用connect、accept、recv或recvfrom这类函数,程序阻塞,直至该套接字上接受到数据后程序才能继续运行。
  2. 但是使用select函数可以实现非阻塞方式的程序。它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
  3. 每次进行select调用都会线性扫描全部的fd集合。这样,效率就会呈现线性下降。
  4. 内核/用户空间内存拷贝问题:select在解决将fd消息传递给用户空间时采用了内存拷贝的方式,处理效率不高

select是无差别轮询。

函数原型

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

两个结构体

struct fd_set

  1. 可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件;
  2. fd_set集合可以通过一些宏由人为来操作
1
2
3
4
5
6
7
8
//清空集合
FD_ZERO(fd_set *)
//将一个给定的文件描述符加入集合之中
FD_SET(int ,fd_set*)
//将一个给定的文件描述符从集合中删除
FD_CLR(int,fd_set*)
//检查集合中指定的文件描述符是否可以读写
FD_ISSET(int ,fd_set* )

struct timeval

1
2
3
4
5
struct timeval
{
long tv_sec; // seconds
long tv_usec; // microseconds
}

函数参数

int maxfdp

是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1。

fd_set * readfds

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

fd_set * writefds

同上,监视文件写变化。

fd_set * errorfds

同上,监视文件错误异常。

struct timeval * timeout

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

返回值

  1. 负值:select错误
  2. 0:等待超时,没有可读写或错误的文件
  3. 正值:某些文件可读写或出错

三、伪代码

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
socket...;
bind...;
listen...;
fd_set fd_in;
struct timeval tv;
int sockfd[MAXCN]; //连接的fd
int count =0,i=0,maxfd=serverfd;
tv.tv_sec = 30;//最多等待30秒
tv.tv_usec = 0;
setNonBlocking(serverfd); //配置非阻塞模式
while (1)
{
//初始化文件描述符集合
FD_ZERO( &fd_in );//重置集合
FD_SET( serverfd, &fd_in );//监视serverfd的输入事件
//超时的设定
tv.tv_sec = 5;
tv.tv_usec =0;
//添加活动的连接
for(i=0;i<count;i++)
{
FD_SET(sockfd[i],&fd_in);
}
//如果文件描述符中有连接请求 会做相应的处理,实现I/O的复用 多用户的连接通讯
int ret = select( maxfd + 1, &fd_in, nullptr, nullptr, &tv );
if(ret < 0) //没有找到有效的连接 失败
//error
else if(ret ==0)// 指定的时间到,
//timeout
if(FD_ISSET(serverfd,&fd_in))
{
int clientfd=accept...;
if(count+1 >= MAXCN)
{
//超出链接数量
//关闭
}
//add fd to select
else
{
//加入select监视
setNonBlocking(clientfd); //配置非阻塞模式
}
}
//循环判断有效的连接是否有数据到达
for()
{
if(FD_ISSET(sockfd[i],&fd_in))
callReadBackFunC();
}
}

四、select完整代码示例