网编(11):服务器端的I/O复用
多进程服务器端的缺点和解决方法
为了构建并发服务器,只要有客户端连接请求就会创建新进程。这的确是实际操作中采用的一种方案,但并非十全十美,因为创建进程时需要付出极大代价。这需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)。各位应该也感到需要IPC时会提高编程难度。
多进程服务器端模型 I/O复用服务器端模型
用select 函数实现并行服务器端
select 函数的功能和调用顺序
使用select函数时可以将多个文件描述符集中到一起统一监视,项目如下。
- 是否存在套接字接收数据?
- 无需阻塞传输数据的套接字有哪些?
- 哪些套接字发生了异常?
select函数的调用方法和顺序
设置文件描述符
FD _ZERO(fd_set * fdset) ;//将fd_set变量的所有位初始化为0 。
FD_SET(int fd, fd_set * fdset) ;//在参数fdset指向的变量中注册文件描述符fd的信息。
FD_CLR(int fd, fd_set * fdset) ;//从参数fdset指向的变量中清除文件描述符fd 的信息。
FD_ISSET(int fd, fd_set * fdset) ;//若参数fdset指向的变量中包含文件描述符fd的信息,则返回“真” 。
设置检查( 监视) 范图及超时
#incl_ude <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 。
#maxfd 是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。
#readset 将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。
#writeset 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值。
#exceptset 将所有关注”是否发生异常"的文件描述符注册到fd_set型变量,并传递其地址值。
#timeout 调用select 函数后,为防止陷入无限阻塞的状态,传递超时( time-out ) 信息。
#返回值 发生错误时返回-1, 超时返回时返回0。因发生关注的事件返回时,返回大千0的值,该值是发生事件的文件描述符数。
struct timeval
{
long tv_sec; //seconds
long tv_usec; //microseconds
}
文伴描述符的变化
文件描述符变化是指监视的文件描述符中发生了相应的监视事件。例如,通过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(console)
/*
timeout. tv_ sec=S;
timeout . tv_usec=5000;
*/
while(1)
{
temps=reads;
timeout.tv_sec=5;
timeout.tv_usec=0;
result=select(l, &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;
}
第14 、1 5行看似复杂,实则简单。首先在第1 4行初始化fd_set变量,第1 5行将文件描述符0对应的位设置为1 。换言之,需要监视标准输入的变化。
第24行将准备好的fd_set变量reads 的内容复制到temps 变量,因为之前讲过,调用sel ect函数后,除发生变化的文件描述符对应位外,剩下的所有位将初始化为0 。因此,为了记住初始值,必须经过这种复制过程。这是使用select 函数的通用方法,希望各位牢记。
第18 、19行请观察被注释的代码,这是为了设置sel ect函数的超时而添加的。但不能在此时设置超时。因为调用select 函数后, 结构体timeval 的成员tv_sec 和tv_usec 的值将被替换为超时前剩余时间。因此,调用se lect 函数前,每次都需要初始化timeval结构体变量。
第25 、26行将初始化timeva l结构体的代码插入循环后,每次调用select 函数前都会初始化新值。
第27行调用select 函数。如果有控制台输入数据,则返回大于0 的整数;如果没有输入数据而引发超时,则返回0 。
第39~44行select 函数返回大于0 的值时运行的区域。验证发生变化的文件描述符是否为标准输入。若是, 则从标准输入读取数据并向控制台输出。
实现I/O复用服务器端
实例代码:
#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;//由于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)// connection request!
{
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{//read message!
str_len=read(i, buf, BUF_SIZE);
if(str_len==0) // close request!
{
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 * message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}