基于I/O 多路复用技术的并发服务器
定在某一个特定套接字地址上的服务器。因此,服务器必须满足并发的需求。如果不采用并
发技术,当服务器处理一个客户请求时,会拒绝其他客户端请求,造成其他客户要不断的请
求并长期等待。
在Linux(Unix)系统中并发服务器有三种设计方式:
(1)多进程
进程是执行中的计算机程序,可以认为是一个程序的一次运行。它是一个动态的实体,
是独立的任务。每个单独的进程运行在自己的虚拟地址空间中,并且它只能通过安全的内核
管理机制和其它进程交互。若是一个进程崩溃不会引起其它进程崩溃。
在Linux(Unix)系统中,多个进程可以同时执行相同的代码,从而支持并发。
对于单个CPU 系统而言,CPU 一次只能执行一个进程,但操作系统可通过分时处理,
每个进程在每个时间段中执行,因此对于用户而言,这些进程在同时执行。
(2)多线程
线程与进程类似,也支持并发执行。与进程不同的一点,在同一进程中所有线程共享
相同的全程变量以及系统分配给进程的资源。因此,线程占用较少的系统资源,并且线程之
间切换更快。
(3)I/O 多路复用(select 和poll 函数)
另一种支持并发的方法是I/O 多路复用。select()函数是系统提供的,它可以在多个描
述符中选择被激活的描述符进行操作。
例如:一个进程中有多个客户连接,即存在多个TCP 套接字描述符。select()函数阻塞
直到任何一个描述符被激活,即有数据传输。从而避免了进程为等待一个已连接上的数据而
无法处理其他连接。因而,这是一个时分复用的方法,从用户角度而言,它实现了一个进程
或线程中的并发处理。
I/O 多路复用技术的最大优势是系统开销小,系统不必创建进程、线程,也不必维护这
些进程/线程,从而大大减少了系统的开销。
select()函数用于实现I/O 多路复用,它允许进程指示系统内核等待多个事件中的任何一
个发生,并仅在一个或多个事情发送或经过某指定的时间后才唤醒进程。
它的原型如下,
#include<sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set * errorfds, struct timeval *timeout);
ndfs: select() 函数监视描述符数的最大值。根据进程中打开的描述符数而定,一般设为要
监视的描述符的最大数加1。
readfds: select() 函数监视的可读描述符集合。
writefds: select()函数监视的可写描述符集合。
errorfds: select()函数监视的异常描述符集合。
timeout: select()函数超时结束时间
返回值。如果成功返回总的位数,这些位对应已准备好的描述符。否则返回-1,并在errno
中设置相应的错误码。
FD_ZERO(fd_set *fdset):清空fdset 与所有描述符的联系
FD_SET(int fd, fd_set *fdset):建立描述符fd 与fdset 的联系
FD_CLR(int fd, fd_set *fdset):撤销描述符fd 与fdset 的联系
FD_ISSET(int fd,fd_set *fdset) ::检查与fdset 联系的描述符fd 是否可读写,返回非0表示可读写。
采用select()函数实现I/O 多路复用的基本步骤如下:
(1) 清空描述符集合
(2) 建立需要监视的描述符与描述符集合的联系
(3) 调用select()函数
(4) 检查所有需要监视的描述符,利用FD_ISSET 判断是否准备好
(5) 对已准备好的描述符进行I/O 操作
为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用I/O模型.
首先介绍一个函数select
int select(int nfds,fd_set *readfds,fd_set *writefds,
fd_set *except fds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)
一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足. 比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读 (通信的对方还没有 发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.如果我们不 希望阻塞,我们的一个选择是用select系统调用. 只要我们设置好select的各个参数,那么当文件可以读写的时候select回"通知"我们 说可以读写了. readfds所有要读的文件文件描述符的集合
writefds所有要的写文件文件描述符的集合
exceptfds其他的服要向我们通知的文件描述符
timeout超时设置.
nfds所有我们监控的文件描述符中最大的那一个加1
在我们调用select时进程会一直阻塞直到以下的一种情况发生. 1)有文件可以读.2)有文件可以写.3)超时所设置的时间到.
为了设置文件描述符我们要使用几个宏. FD_SET将fd加入到fdset
FD_CLR将fd从fdset里面清除
FD_ZERO从fdset中清除所有的文件描述符
FD_ISSET判断fd是否在fdset集合中
int use_select(int *readfd,int n)
{
fd_set my_readfd;
int maxfd;
int i;
maxfd=readfd[0];
for(i=1;i
if(readfd[i]>maxfd) maxfd=readfd[i];
while(1)
{
/* 将所有的文件描述符加入 */
FD_ZERO(&my_readfd);
for(i=0;i
FD_SET(readfd[i],*my_readfd);
/* 进程阻塞 */
select(maxfd+1,& my_readfd,NULL,NULL,NULL);
/* 有东西可以读了 */
for(i=0;i
if(FD_ISSET(readfd[i],&my_readfd))
{
/* 原来是我可以读了 */
we_read(readfd[i]);
}
}
}
使用select后我们的服务器程序就变成了.
初始话(socket,bind,listen);
while(1)
{
设置监听读写文件描述符(FD_*);
调用select;
如果是倾听套接字就绪,说明一个新的连接请求建立
{
建立连接(accept);
加入到监听文件描述符中去;
}
否则说明是一个已经连接过的描述符
{
进行操作(read或者write);
}
}