之前我们使用了几种服务器模型,一个是单进程的, 同一时刻只能给一个客户端提供服务, 后来我们使用了多进程, 每个客户端fork新进程进行请求处理
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程,可以使用一个进程服务多个客户端.
多进程服务模型:
I/O 复用进程模型:
select 实现I/O 复用
select实现比较简单,主要使用select函数 函数原型:
代码语言:javascript复制#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set * readset, fd_set * writeset, fd_set * exceptset, const struct timeval * timeout);
成功时返回大于0的值, 失败时返回 -1
参数解释:
代码语言:javascript复制maxfd 监视对象文件描述符的数量,举例写法 sever_sock 1
readset 存储的待读取数据文件描述符
writeset 可传输无阻塞数据文件描述符
exceptset 发生异常的文件描述符
timeout 超时设置
select函数返回值, 如果返回大于0的整数, 说明相应数量的文件描述符发生了变化.
文件描述符管理
我们发现在 参数类型上有 fd_set类型,这是什么类型呢? fd_set结构是文件描述符对应的位存储数据格式, 当我们管理这些监控的文件描述符时, 可以以下宏来实现
代码语言:javascript复制FD_ZERO(fd_set * fdset)
将 fd_set 变量的所有位初始化位0
FD_SET(int fd, fd_set * fdset)
在参数 fd_set 指向的变量中注册文件描述符 fd 的信息
FD_CLR(int fd, fd_set * fdset)
从参数 fdset 指向的变量中清除文件描述符 fd 的信息
FD_ISSET(int fd, fd_set * fdset)
若参数 fdset 指向的变量中包含文件描述符 fd 的信息,则返回真
timeval 超时设置结构体
代码语言:javascript复制struct timeval
{
long tv_sec; //seconds
long tv_usec; //microseconds
}
select函数执行前后示例图
示例代码(回声)
select.c
代码语言:javascript复制#include <stdio.h>
#include <sys/select.h>
#define BUF_SIZE 1024
int main(int argc, char *argv[])
{
//监视的文件描述符
fd_set reads, temps;
struct timeval timeout;
int result, str_len;
char buf[BUF_SIZE];
FD_ZERO(&reads);
FD_SET(0, &reads); //0 is standard input(console)
while (1)
{
//因为每次select会重置监控句柄,所以赋值给临时
temps = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
// puts 只有事件发生或者发生超时才执行,否则select阻塞
puts("xxxx");
result = select(1, &temps, 0, 0, &timeout);
if (result == -1)
{
puts("select() error");
}
else if (result == 0)
{
puts("nothing event change..time out");
}
else
{
if (FD_ISSET(0, &temps))
{
str_len = read(0, buf, BUF_SIZE);
printf("message from consle: %sn", buf);
}
}
}
}
执行输出
代码语言:javascript复制gcc select.c -o select
./select
xxxx
123456
message from consle: 123456
xxxx
7890ha
message from consle: 7890ha
xxxx
nothing event change..time out
xxxx
777
message from consle: 777
xxxx
^C