第十二章 I/O复用
使用select函数:
#include<sys/select.h>
#include<time.h>
int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
maxfd 监视对象文件描述符数量
readset 将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值
writeset 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值
exceptset 将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值
timeout 调用select函数后,为防止陷入无线阻塞的状态,传递超时信息
返回值 发生错误返回-1,超时返回0。因发生关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数
select函数调用示例:
#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
while(1){
temps = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
result = select(1, &temps, 0, 0, &timeout);
if(result == -1){
puts("select() error!");
break;
}else if(result == 0){
puts("Time-out!");
}else{
if(FD_ISSET(0,&temps)){
str_len = read(0, buf, BUF_SIZE);
buf[str_len] = 0;
printf("message from console:%s",buf);
}
}
}
return 0;
}
运行结果:
[root@VM_0_10_centos IO]# gcc select.c -o select
[root@VM_0_10_centos IO]# ./select
HITime-out!
HI
message from console:HIHI
hi
message from console:hi
ok
message from console:ok
nihaoa
message from console:nihaoa
Time-out!
Time-out!
hehehe
message from console:hehehe
Time-out!
基于I/O复用的回声服务器:
echo_selectserv.c :
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<sys/select.h>
#define BUF_SIZE 100
void error_handling(char * buf);
int main(int argc,char* argv[]){
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
struct timeval timeout;
fd_set reads, cpy_reads;
socklen_t adr_sz;
int fd_max, str_len, fd_num, i;
char buf[BUF_SIZE];
if(argc != 2){
printf("Usage:%s <port>\n",argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){
error_handling("bind() error");
}
if(listen(serv_sock, 5) == -1){
error_handling("listen() error");
}
FD_ZERO(&reads);
FD_SET(serv_sock, &reads);
fd_max = serv_sock;
while(1){
cpy_reads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1){
break;
}
if(fd_num == 0){
continue;
}
for(i=0;i<fd_max+1;++i){
if(FD_ISSET(i, &cpy_reads)){
if(i == serv_sock){
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
FD_SET(clnt_sock, &reads);
if(fd_max < clnt_sock){
fd_max = clnt_sock;
}
printf("connected client: %d\n",clnt_sock);
}else{
str_len = read(i, buf, BUF_SIZE);
if(str_len == 0){
FD_CLR(i, &reads);
close(i);
printf("closed client: %d\n",i);
}else{
write(i, buf, str_len); //echo!
}
}
}
}
}
close(serv_sock);
return 0;
}
void error_handling(char * buf){
fputs(buf, stderr);
fputc('\n', stderr);
exit(1);
}
课后题:
(1)请解释复用技术的通用含义,并说明何为I/O复用。
复用技术指为了提高物理设备的效率,用最少的物理要素传递最多数据时使用的技术。同样,I/O复用是指将需要I/O的套接字捆绑在一起,利用最少限度的资源来收发数据的技术
(2)多进程并发服务器的缺点有哪些?如何在I/O复用服务器端中弥补?
多进程并发服务器的服务方式是,每当客户端提出连接要求时,就会追加生成进程。但构建进程是一项非常有负担的工作,因此,向众多客户端提供服务存在一定的局限性。而复用服务器则是将套接字的文件描述符捆绑在一起管理的方式,因此可以一个进程管理所有的I/O操作
(3)判断题,有个选项拿不准:
若已通过select函数注册为监视对象,则后续调用select函数时无需重复注册?
(4)select函数的观察对象中应包含服务器端套接字(监听套接字),那么应将其包含到哪一类监听对象集合?请说明原因
服务器套接字的作用是监听有无连接请求,即判断接收的连接请求是否存在?应该将其包含到“读”类监听对象的集合中
(5)select函数使用的fd_set结构体在Windows和Linux中具有不同的声明。请说明区别,同时解释存在区别的必然性
Linux的文件描述符从0开始递增,因此可以找出当前文件描述符数量和最后生成的文件描述符之间的关系。但Windows的套接字句柄并非从0开始,并且句柄的整数值之间并无规律可循,因此需要直接保存句柄的数组和记录句柄数的变量