IO复用

  我们首先来看看服务器编程的模型,客户端发来的请求服务端会产生一个进程来对其进行服务,每当来一个客户请求就产生一个进程来服务,然而进程不可能无限制的产生,因此为了解决大量客户端访问的问题,引入了IO复用技术。 
  即:一个进程可以同时对多个客户请求进行服务。

  也就是说IO复用的“介质”是进程(准确的说复用的是select和poll,因为进程也是靠调用select和poll来实现的),复用一个进程(select和poll)来对多个IO进行服务,虽然客户端发来的IO是并发的但是IO所需的读写数据多数情况下是没有准备好的,因此就可以利用一个函数(select和poll)来监听IO所需的这些数据的状态,一旦IO有数据可以进行读写了,进程就来对这样的IO进行服务。 
  IO多路复用指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。 
  IO多路复用适用如下场合: 
  1.当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。 
  2.当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。 
  3.如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。 
  4.如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。 
  5.如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

  与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

select函数

  该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个时间发生或经历一段指定的时间后才唤醒他。

#include <sys/select.h>
#include >sys/time.h> int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

返回值:

  若有就绪描述符返回其数目,若超时则为0,若出错则为-1

参数

第一个参数——int maxfdp1

  第一个参数maxfdp1指定待测试的描述字个数。 
  它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2…maxfdp1-1均将被测试。 
因为文件描述符是从0开始的。

fd_set *readset

fd_set *writeset

fd_set *exceptset

  中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。  
  如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

void FD_ZERO(fd_set *fdset);
//清空集合 void FD_SET(int fd, fd_set *fdset);
//将一个给定的文件描述符加入集合之中 void FD_CLR(int fd, fd_set *fdset);
//将一个给定的文件描述符从集合中删除 int FD_ISSET(int fd, fd_set *fdset);
// 检查集合中指定的文件描述符是否可以读写

const struct timeval *timeout

  timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};

这个参数有三种可能: 
1.永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。 
2.等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。 
3.根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

select函数的调用过程

(1)使用copy_from_user从用户空间拷贝fd_set到内核空间

(2)注册回调函数__pollwait

(3)遍历所有fd

  调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)

(4)以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。

(5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。

(6)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。

(7)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。

(8)把fd_set从内核空间拷贝到用户空间。

select睡眠和唤醒过程

  select巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。

select睡眠过程

  select会循环遍历它所监测的fd_ set内的所有文件描述符对应的驱动程序的poll函数。 
  驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。  
  当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后,如果没有一个资源可用(即没有一个文件可供操作),则select让该进程睡眠,一直等到有资源可用为止,进程被唤醒(或者timeout)继续往下执行。

select唤醒过程

  唤醒该进程的过程通常是在所监测文件的设备驱动内实现的。 
  驱动程序维护了针对自身资源读写的等待队列。当设备驱动发现自身资源变为可读写并且有进程睡眠在该资源的等待队列上时,就会唤醒这个资源等待队列上的进程。

select的缺点

   1.每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 
  2.同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 
  3.select支持的文件描述符数量太小了,默认是1024

使用select函数写的服务器代码如下:

chata

 #include "func.h"

 int main(int argc,char* argv[])
{
if(argc!=)
{
printf("error args\n");
return -;
}
int fdr,fdw;
fdr=open(argv[],O_RDONLY);
fdw=open(argv[],O_WRONLY);
printf("fdr=%d,fdw=%d\n",fdr,fdw);
char buf[]={};
//当管道里没有数据时,read会阻塞
int ret;
fd_set rdset;
while()
{
FD_ZERO(&rdset);//清空集合
FD_SET(,&rdset);
FD_SET(fdr,&rdset);
ret=select(fdr+,&rdset,NULL,NULL,NULL);
if(ret>)
{
if(FD_ISSET(fdr,&rdset))
{
memset(buf,,sizeof(buf));
ret=read(fdr,buf,sizeof(buf));
if(==ret)
{
printf("byebye\n");
break;
}
printf("%s\n",buf);
}
if(FD_ISSET(,&rdset))
{
memset(buf,,sizeof(buf));
ret=read(,buf,sizeof(buf));
if(ret==)
{
printf("byebye\n");
break;
}
write(fdw,buf,strlen(buf)-);
}
}
}
close(fdr);
close(fdw);
return ;
}

chatb

 #include "func.h"

 int main(int argc,char* argv[])
{
if(argc!=)
{
printf("error args\n");
return -;
}
int fdw,fdr,ret;
fdw=open(argv[],O_WRONLY);
fdr=open(argv[],O_RDONLY);
printf("fdw=%d,fdr=%d\n",fdw,fdr);
char buf[]={};
fd_set rdset;
while()
{
FD_ZERO(&rdset);//清空集合
FD_SET(,&rdset);
FD_SET(fdr,&rdset);
ret=select(fdr+,&rdset,NULL,NULL,NULL);
if(ret>)
{
if(FD_ISSET(fdr,&rdset))
{
memset(buf,,sizeof(buf));
ret=read(fdr,buf,sizeof(buf));
if(==ret)
{
printf("byebye\n");
break;
}
printf("%s\n",buf);
}
if(FD_ISSET(,&rdset))
{
memset(buf,,sizeof(buf));
ret=read(,buf,sizeof(buf));
if(ret==)
{
printf("byebye\n");
break;
}
write(fdw,buf,strlen(buf)-);
}
}
}
return ;
}

当数据量足够多时,slect模型的资源消耗会大幅提升,poll模型和slect差不多,因此多数时候选择epoll模型,可参见下一篇……

原博客来源:https://blog.csdn.net/lixungogogo/article/details/52219951

IO多路复用模型之select()函数详解的更多相关文章

  1. linux select函数详解

    linux select函数详解 在Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核: •我们所关心的文件描述符 •对每个描述符,我们所关心的状 ...

  2. linux select函数详解【转】

    转自:http://www.cnblogs.com/ccsccs/articles/4224253.html 在Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数 ...

  3. select函数详解(转)

    Select函数在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect. accept.recv或recvfrom这样的阻塞 ...

  4. Linux C select函数详解

    select IO复用机制: http://www.cnblogs.com/hjslovewcl/archive/2011/03/16/2314330.html http://blog.csdn.ne ...

  5. select函数详解及应用

    Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect. accept.recv或recvfrom这样的阻塞程序 ...

  6. select()函数详解

    Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是 习惯写诸如connect. accept.recv或recvfrom这样的阻塞程 ...

  7. select函数详解

    网络编程中一个很重要的函数,没有整理,直接转过来,讲的还是蛮详细的. 转自:http://blog.csdn.net/zhw888888/archive/2009/03/29/4034515.aspx ...

  8. Netsuite Formula > Oracle函数列表速查(PL/SQL单行函数和组函数详解).txt

    PL/SQL单行函数和组函数详解 函数是一种有零个或多个参数并且有一个返回值的程序.在SQL中Oracle内建了一系列函数,这些函数都可被称为SQL或PL/SQL语句,函数主要分为两大类: 单行函数 ...

  9. MYSQL常用内置函数详解说明

    函数中可以将字段名当作变量来用,变量的值就是该列对应的所有值:在整理98在线字典数据时(http://zidian.98zw.com/),有这要一个需求,想从多音字duoyinzi字段值提取第一个拼音 ...

随机推荐

  1. urllib(最基本的)库的应用

    Urllib库 python内置的http请求库 1.urllib.request 请求模块 2.urllib.error 异常处理模块(try,catch) 3.urllib.parse url解析 ...

  2. mvc用UpdateModel报错

    项目中使用UpdateModel时报错:未能更新类型“XXXXXX”的模型. 原因如下:表单Post时,有的参数为空,如a=1&b=2&=3.

  3. ffmpeg 内存池

    ffmpeg 部分内存管理采用 了内存池技术.基本的接口在libavutil目录下的buffer.c文件中实现: 1. av_buffer_pool_init 初始化 内存池 2 av_buffer_ ...

  4. python--文件操作补充

    文件操作补充 f = open('file',encoding='utf-8')其中打开时不写模式默认只读f是文件句柄,文件操作符,不能使数字相当于content = f.read()content是 ...

  5. 认识Gulp

    gulp详细入门教程:http://www.ydcss.com/archives/18 安装gulp 前提:已经安装node.js.npm $ npm install gulp --save-dev ...

  6. 软考考点---CPU

    软考考点---CPU         中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Un ...

  7. PHP 5.6编译安装

    yum install openssl openssl-devel libxml2-devel libxml2 bzip2 bzip2-devel curl-devel php-mcrypt libm ...

  8. 关于erlang解析json数据

    JSON(JavaScript Object Notation)是一种轻量级的数据交换语言,以文字为基础,且易于让人阅读.json的数据格式是文本文档格式的一种.在erlang中可以参考mochiwe ...

  9. POJ 1952 BUY LOW, BUY LOWER 动态规划题解

    Description The advice to "buy low" is half the formula to success in the bovine stock mar ...

  10. jQuery功能强大的图片查看器插件

    简要教程 viewer是一款功能强大的图片查看器jQuery插件.它可以实现ACDsee等看图软件的部分功能.它可以对图片进行移动,缩放,旋转,翻转,可以前后浏览一组图片.该图片查看器还支持移动设备, ...