基于I/O复用的服务器端
多进程服务器端的缺点
创建进程时需要付出极大代价,这需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法。
I/O复用服务器端
select函数及实现服务器端
select函数调用过程
设置文件描述符
在 *Unix 系统当中, 前三个文件描述符0, 1, 2 默认为 stdin stdout stderr。
首先需要将要监视的文件描述符集中到一起。集中时也要按照监视项(接收、传输、异常)进行区分。
最左端的位置表示文件描述符0(所在位置),如果该位设置为1,表示该文件描述符是监视对象。图中的文件描述符1和3是监视对象。
简化fd_set的源码:
typedef struct{
long int fds_bits[32];
}fd_set;
可以看出fd_set可以表示的位数是1024位(32×32)
但是在ubuntu20.04下的测试,个人觉得是(64×16)
//1152921504606846976 -> 00010000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
FD_ZERO(&set);
FD_SET(60,&set);
FD_ZERO(&set);
FD_SET(64,&set);
设置检查(监视)范围及超时
程序猿通过调用这个函数可以委托内核帮助我们检测若干个文件描述符的状态,其实就是检测这些文件描述符对应的读写缓冲区的状态:
读缓冲区:检测里边有没有数据,如果有数据该缓冲区对应的文件描述符就绪
写缓冲区:检测写缓冲区是否可以写 (有没有容量),如果有容量可以写,缓冲区对应的文件描述符就绪
读写异常:检测读写缓冲区是否有异常,如果有该缓冲区对应的文件描述符就绪。
select函数用来验证3种监视项的变化情况。根据监视项声明3个fd_set型变量,分别向其注册文件描述符信息,并把变量的地址值传递到上述函数的第二到第四个参数。
select函数要求通过第一个参数传递监视对象文件描述符的数量,只需将最大的文件描述符值加1再传递到select函数即可。select函数的超时时间与select函数的最后一个参数有关。
timeout:超时时长,用来强制解除 select () 函数的阻塞的
NULL:函数检测不到就绪的文件描述符会一直阻塞。
等待固定时长(秒):函数检测不到就绪的文件描述符,在指定时长之后强制解除阻塞,函数返回 0
不等待:函数不会阻塞,直接将该参数对应的结构体初始化为 0 即可。
本来select函数只有在监视的文件描述符发生变化时才返回,如果未发生变化,就会进入阻塞状态。将该结构体的地址值传递给select函数的最后一个参数,即使文件描述符未发生变化,只要过了指定时间,也可以从函数中返回。可以通过返回值了解返回原因。如果不想设置超时,则传递NULL参数。
调用select函数后查看结果
select函数调用完成后,原来为1的所有位均变为0,但发生变化的文件描述符对应位除外,即值仍为1的位置上的文件描述符发生了变化。
内核在遍历这个读集合的过程中,如果被检测的文件描述符对应的读缓冲区中没有数据,内核将修改这个文件描述符在读集合 fd_set 中对应的标志位,改为 0,如果有数据那么这个标志位的值不变,还是 1。当 select() 函数解除阻塞之后,被内核修改过的读集合通过参数传出,此时集合中只要标志位的值为 1,那么它对应的文件描述符肯定是就绪的。
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
fd_set reads, temps; //保存要监视的文件描述符
int result, str_len; //
char buf[BUF_SIZE];
struct timeval timeout; //超时时间
FD_ZERO(&reads);
FD_SET(0, &reads); // 0 is standard input(console)
/*
timeout.tv_sec=5;
timeout.tv_usec=5000;
*/
//调用select函数后,结构体timeval的成员tv_sec和tv_usec的值将被替换为超时前剩余时间!
//因此,调用select函数前,每次都需要初始化timeval结构体变量
while(1)
{
temps=reads;
timeout.tv_sec=5;
timeout.tv_usec=0;
result=select(1, &temps, 0, 0, &timeout); //传入NULL(0)值,表示不关系此特性的变化
if(result==-1)
{
puts("select() error!");
break;
}
else if(result==0)
{
puts("Time-out!");
}
else
{
if(FD_ISSET(0, &temps)) //若参数fdset指向的变量中包含文件描述符fd的信息,则返回为"真"
{
str_len=read(0, buf, BUF_SIZE);
buf[str_len]=0;
printf("message from console: %s", buf);
}
}
}
return 0;
}