1. Linux下的五种I/O模型

1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O复用(select 和poll) (I/O multiplexing)
4)信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))

前四种都是同步,只有最后一种才是异步IO。

五种I/O模型的比较:

2.多路复用--select

系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

select函数

int select(int maxfd,fd_set *rdset,fd_set *wrset, \  fd_set *exset,struct timeval *timeout);

参数说明:

参数maxfd是需要监视的最大的文件描述符值+1;rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

下面的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位

参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

struct timeval
{  time_t tv_sec;//second  time_t tv_usec;//minisecond
};

如果参数timeout设为:

NULL,则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。

0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

函数返回值:

执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。

理解select模型:

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

基于上面的讨论,可以轻松得出select模型的特点:

  • 可监控的文件描述符个数取决与sizeof(fd_set)的值。

  • 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个 参数。

  • 可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

select缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小,默认是1024

3.多路复用--poll

poll与select非常相似,不同之处在于,select使用三个位图来表示三种不同的事件,而poll使用一个 pollfd的指针实现。

poll函数

#include <poll.h>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

参数说明:

fds:是一个struct pollfd结构类型的数组,其结构如下:

struct pollfd {    int fd; /* file descriptor */short events; /* requested events to watch */short revents; /* returned events witnessed */};

该结构用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于 socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select() 函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因 此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;

nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;

timeout:是poll函数调用阻塞的时间,单位:毫秒;如果timeout>0那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回。如果timeout==0,那么 poll() 函数立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发 生是才返回,如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;

返回值:

>0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;

==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒;换句话说,如果所检测的 socket描述符上没有任何事件发生的话,

-1:  poll函数调用失败,同时会自动设置全局变量errno;

poll() 函数的功能和返回值的含义与 select() 函数的功能和返回值的含义是完全一样的,两者之间的差别就是内部实现方式不一样。

4.select实例之网络服务器(poll实现类似)

服务器端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <string.h>#define _MAX_LISTEN_ 5
#define _MAX_SIZE_ 10
#define _BUF_SIZE_ 1024int fd_arr[_MAX_SIZE_];
int max_fd = -1;static void init_fd_arr()
{int i = 0;for(; i < _MAX_SIZE_; ++i){fd_arr[i] = -1;}
}static int add_fd_arr(int fd)
{int i = 0;for(; i < _MAX_SIZE_; ++i){if(fd_arr[i] == -1){fd_arr[i] = fd;return 0;}}return 1;
}static void remove_fd_arr(int fd)
{int i = 0;for(; i < _MAX_SIZE_; ++i){if(fd_arr[i] == fd){fd_arr[i] = -1;break;}}
}static void reload_fd_arr(fd_set* pset)
{int i = 0;max_fd = -1;for(; i < _MAX_SIZE_; ++i){if(fd_arr[i] != -1){FD_SET(fd_arr[i], pset);if(fd_arr[i] > max_fd)max_fd = fd_arr[i];}}
}static printf_msg(int i, const char* msg)
{printf("client %d # %s\n", fd_arr[i], msg);
}void Usage(const char* proc)
{printf("%s usage: [ip] [port]\n", proc);
}int startup(const char* _ip, const char* _port)
{int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock < 0){perror("socket");exit(1);}int opt = 1;if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {perror("setsockopt");exit(2);}  struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(atoi(_port));local.sin_addr.s_addr = inet_addr(_ip);if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){perror("bind");exit(3);}if(listen(sock, _MAX_LISTEN_) < 0){perror("listen");exit(4);}return sock;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);return 1;}int listen_sock = startup(argv[1], argv[2]);init_fd_arr();add_fd_arr(listen_sock);fd_set rfds;FD_ZERO(&rfds);struct timeval tv = {5, 0};while(1){reload_fd_arr(&rfds);int fds = select(max_fd+1, &rfds, NULL, NULL, NULL);switch(fds){case -1:perror("select");exit(5);break;case 0:printf("time out\n");break;default:{int index = 0;for(; index < _MAX_SIZE_; ++index){if(fd_arr[index] == listen_sock && FD_ISSET(fd_arr[index], &rfds)) //new accept{struct sockaddr_in peer;socklen_t len = sizeof(peer);int new_fd = accept(listen_sock, (struct sockaddr* )&peer, &len);if(new_fd < 0){perror("accept");exit(6);}printf("get a new client %d -> ip: %s port: %d\n", new_fd, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));if(1 == add_fd_arr(new_fd)){perror("fd_arr is full\n");close(new_fd);exit(7);}continue;}if(fd_arr[index] != -1 && FD_ISSET(fd_arr[index], &rfds)) //new read fd{char buf[_BUF_SIZE_];memset(buf, '\0', sizeof(buf));ssize_t _s = read(fd_arr[index], buf, sizeof(buf)-1);if(_s > 0){buf[_s] = '\0';printf_msg(index, buf);}else if(_s == 0) //client closed{printf("client %d is closed...\n", fd_arr[index]);FD_CLR(fd_arr[index], &rfds);close(fd_arr[index]); // must before remove!!!remove_fd_arr(fd_arr[index]);}else{}}}}break;}}return 0;
}

客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>void Usage(const char* proc)
{printf("usage: %s [ip] [port]\n", proc);
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int conn_sock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in conn;conn.sin_family = AF_INET;conn.sin_port = htons(atoi(argv[2]));conn.sin_addr.s_addr = inet_addr(argv[1]);if(connect(conn_sock, (const struct sockaddr*)&conn, sizeof(conn)) < 0){perror("connect");exit(2);}char buf[1024];memset(buf, '\0', sizeof(buf));while(1){printf("please enter # ");fflush(stdout);ssize_t _s = read(0, buf, sizeof(buf)-1);if(_s > 0){buf[_s-1] = '\0';write(conn_sock, buf, strlen(buf));}}return 0;
}

程序演示

使用Telnet测试

使用客户端测试

使用浏览器测试


转载于:https://blog.51cto.com/11418774/1836323

Linux的I/O多路复用机制之--selectpoll相关推荐

  1. Redis的多路复用机制

    Redis是单线程还是多线程? 通常我们所说的Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程.但 Red ...

  2. linux I/O--I/O多路复用--详解(四)

    IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程.IO多路复用适用如下场合: 当客户处理多个描述符时(一般是交互式输入和网络套接口),必须使用I/O复用. 当一个客户 ...

  3. linux I/O--I/O多路复用--介绍(二)

    一.概念引入 I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相 ...

  4. 多路复用机制--Redis为什么这么快

    IO 多路复用机制,核心思想是让单个线程去监视多个连接,一旦某个连接就绪, 也就是触发了读/写事件. 就通知应用程序,去获取这个就绪的连接进行读写操作. 也就是在应用程序里面可以使用单个线程同时处理多 ...

  5. Redis IO 多路复用机制

    Redis IO 多路复用机制 基于linux select/epoll select:最大支持1024个文件描述符,在描述符较多情况下性能较差,水平触发 poll:poll与select基本相同,只 ...

  6. IO多路复用机制详解

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking IO):默认创建的s ...

  7. 20155301 滕树晨linux基础——linux进程间通信(IPC)机制总结

    20155301 滕树晨linux基础--linux进程间通信(IPC)机制总结 共享内存 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在 ...

  8. linux kernel and user space通信机制,Linux内核与用户空间通信机制研究.pdf

    ISSN 1009-3044 E-mail:info@CCCC.net.CR ComputerKnowledgeandTechnology电脑知识与技术 http://www.dnzs.net.cn ...

  9. Linux内部的时钟处理机制全面剖析

    Linux内部的时钟处理机制全面剖析 在 Linux 操作系统中,很多活动都和时间有关,例如:进程调度和网络处理等等.所以说,了解 Linux 操作系统中的时钟处理机制有助于更好地了解 Linux 操 ...

最新文章

  1. LeetCode刷题-7
  2. 求数的绝对值一定是正数_「口袋数学」绝对值的几何意义探究及应用,培优课程...
  3. Python中str()与repr()函数的区别——repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用...
  4. 神经网络与机器学习 笔记—基本知识点(上)
  5. 浅谈 instanceof 和 typeof 的实现原理
  6. 【Python】 tempfile模块 临时文件和目录的处理
  7. MyBatis和hibernate本质区别与应用场景
  8. GAN生成对抗网络-GAN原理与基本实现-入门实例02
  9. mysql执行计划(explain)
  10. 当自动化测试遇到邮箱
  11. 科技爱好者周刊:第 103 期
  12. 腾讯云、声网、快手抢跑视频云
  13. “内存型”网游外挂的刑事责任辨析
  14. google 新功能 快讯
  15. MATLAB主题设置配色方案
  16. HTTPS之SNI介绍与Nginx多域名支持
  17. 主机开启后,显示器显示NO SIGNAL,无信号
  18. The Multiversity 的 “非常重要的生命体” NFT 推出
  19. Android x86的arm兼容库移植--初步分析及尝试手动移植houdini/ndk_translation
  20. 利用 matplotlib 制作条形图

热门文章

  1. 使用 TiKV 构建分布式类 Redis 服务
  2. Bluetooth Low Energy 嗅探
  3. 8K投影仪+大银幕,日本系统Sphere5.2不用头显也能体验VR
  4. TCP三次握手和四次断开
  5. CentOS 6.8 部署腾讯蓝鲸运维平台
  6. [LeetCode][Java] 3Sum Closest
  7. (转)Windows重启延迟删除,重命名技术原理
  8. (ZT)大学里如何学习 ?
  9. Good Bye G.cn
  10. 对DBF的操作建议用微软的驱动和新的链接字符串。