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. 电话号码 【trie树】

    电话号码 查看 提交 统计 提问 总时间限制: 1000ms 内存限制: 65536kB 描写叙述 给你一些电话号码,请推断它们是否是一致的,即是否有某个电话是还有一个电话的前缀. 比方: Emerg ...

  2. basePath 方便

    String path = request.getContextPath()+"/";String basePath = request.getScheme() + ": ...

  3. vue2 less less-loader 的用法

    LESS基础语法 我们一起来学习一下LESS的基础语法,LESS的基础语法基本上分为以下几个方面:变量.混合(Mixins).嵌套规则.运算.函数.作用域等.这些基础语法需要我们先牢牢的掌握住,然后才 ...

  4. Django-缓存的配置

    缓存的介绍 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作,都会消耗 ...

  5. Android 短信验证码控件

    Android 短信验证码控件,便于项目中使用统一样式,统一提示改动.个人觉得挺好用的 <span style="font-size:18px;">public cla ...

  6. Jquery 文字模拟输入效果

    https://github.com/mattboldt/typed.js/ 挺酷炫的

  7. 神经网络实现Discuz验证码识别

    最近自己尝试了网上的验证码识别代码项目,该小项目见以下链接: https://cuijiahua.com/blog/2018/01/dl_5.html 数据也就用了作者上传的60000张Discuz验 ...

  8. 官网下载kettle

    首先什么是kettle,引用下百度百科 Kettle是一款国外开源的ETL工具,纯java编写,可以在Window.Linux.Unix上运行,数据抽取高效稳定. Kettle 中文名称叫水壶,该项目 ...

  9. bzoj-2251 外星联络

    题意: 给出一个字符串,求出现次数超过1的子串的出现个数. 字符串长度<=3000: 题解: 题目问的是子串的个数.那么首先我们要找到全部的子串. 而字符串的全部后缀的前缀能够不重不漏的表示全部 ...

  10. mysql-connector-java与mysql版本的对应

    记录下mysql-connector-java与mysql版本的对应关系,已方便以后参考,这是最新版本对应, 时间:2017年5月23日 官网文档地址: https://dev.mysql.com/d ...