您的位置:首页 > 其它

并发服务器:多路复用I/O

2013-09-19 20:53 295 查看

基于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);
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: