I/O多路转接之select

文章目录

  • I/O多路转接之select
    • 一、五种IO模型
    • 二、I/O多路转接之select原理

一、五种IO模型

  • 阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式

  • 非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码.非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询.这对CPU来说是较大的浪费

  • 信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作

  • IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态

  • 异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据

高级IO相关概念:

  • 同步通信 vs 异步通信

  • 同步和异步关注的是消息通信机制(注意和线程同步区分)

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得 到返回值了;换句话说,就是由调用者主动等待这个调用的结果;

  • 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

  • 阻塞 vs 非阻塞

  • 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回.

  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

二、I/O多路转接之select原理

  • 1.函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数 作用
nfds 是需要监视的最大文件描述值+1
fd_set *readfds 需要检测的可读文件描述符的集合,
fd_set *writefds 需要检测的可写文件描述符的集合
fd_set *exceptfds 需要检测的异常文件描述符的集合;
struct timeval *timeout 为结构timeval,用来设置select()的等待时间
  • 函数返回值
1.执行成功则返回文件描述符状态已经改变的个数;2.如果返回0代表在描述词状态改变之前已经超过 timeout时间,没有返回;3.当有错误发生时则返回-1,错误原因存于error,此时参数readfd、writefd、exceptfd和timeout的值变成不可预测。
  • 2.理解fd_set结构

  • 其实这个结构就是一个整型数组,更严格的说,是一个“位图”,使用位图中对应的位表示要监视的文件描述符

  • 用来消除描述词组 set中相关fd的位

void FD_CLR(int fd,fd_set *set);
  • 用来测试描述词组set中的相关fd的位是否为真;
void FD_ISSET(int fd,fd_set *set);
  • 用来设置描述词组set中相关的位;
void FD_SET(int fd,fd_set *set);
  • 用来消除描述词组set的全部位;
void FD_ZERO(int fd,fd_set *set);
  • 3.socket就绪条件

  • 读就绪:

      1.socket内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RCVLOWAT。此时就可以无阻塞的读该文件描述符,并且返回值大于0;2.socket TCP通信中,对端关闭连接,此时对该socket读,则返回0(四次挥手);3.监听的socket上有新的连接请求;4.socket上有未处理的错误。
    
  • 写就绪

      1.socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记SO_RCVLOWAT。此时可以无阻塞的写,并且返回值大于0;2.socket的写操作被关闭。对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号;3.socket使用非阻塞connect连接成功或失败之后;4.socket上有未读取的错误;
    
  • 异常就绪:

      socket上带外数据。关于带外数据,和TCP紧急模式相关(TCP协议头中的紧急指针字段)
    
  • 4.理解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被清空
  • 5.select优缺点

select的特点

  • 可监控的文件描述符个数取决与sizeof(fd_set)的值. 我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096.
  • 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,
  • 一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。
  • 二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得 fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数

select缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小
  • 6.select实现tcp服务器(支持多用户)
#pragma once
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/select.h>
#include "tcp_socket.hpp"// 必要的调试函数inline void PrintFdSet(fd_set* fds, int max_fd)
{printf("select fds: ");for (int i = 0; i < max_fd + 1; ++i) {if (!FD_ISSET(i, fds)) {continue;}printf("%d ", i);}printf("\n");
}typedef std::function<void (const std::string& req, std::string* resp)> Handler;class Selector
{public:Selector() {// [注意!] 初始化千万别忘了!!max_fd_ = 0;FD_ZERO(&read_fds_);}bool Add( TcpSocket& sock) {int fd = sock.GetFd();printf("[Selector::Add] %d\n", fd);if (fd_map_.find(fd) != fd_map_.end()) {printf("Add failed! fd has in Selector!\n");return false;}fd_map_[fd] = sock;FD_SET(fd, &read_fds_);if (fd > max_fd_) {max_fd_ = fd;}return true;}bool Del( TcpSocket& sock) {int fd = sock.GetFd();printf("[Selector::Del] %d\n", fd);if (fd_map_.find(fd) == fd_map_.end()) {printf("Del failed! fd has not in Selector!\n");return false;}fd_map_.erase(fd);FD_CLR(fd, &read_fds_);// 重新找最大的文件描述符, 从右往左找比较快for (int i = max_fd_; i >= 0; --i) {if (!FD_ISSET(i, &read_fds_)) {continue;}max_fd_ = i;break;}return true;}// 返回读就绪的文件描述符集bool Wait(std::vector<TcpSocket>* output) {output->clear();// [注意] 此处必须要创建一个临时变量, 否则原来的结果会被覆盖掉fd_set tmp = read_fds_;PrintFdSet(&tmp, max_fd_);int nfds = select(max_fd_ + 1, &tmp, NULL, NULL, NULL);if (nfds < 0) {perror("select");return false;}// [注意!] 此处的循环条件必须是 i < max_fd_ + 1for (int i = 0; i < max_fd_ + 1; ++i) {if (!FD_ISSET(i, &tmp)) {continue;}output->push_back(fd_map_[i]);}return true;}private:fd_set read_fds_;int max_fd_;// 文件描述符和 socket 对象的映射关系std::unordered_map<int, TcpSocket> fd_map_;
};class TcpSelectServer
{public:TcpSelectServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port) {}bool Start(Handler handler) const {//1. 创建 socketTcpSocket listen_sock;bool ret = listen_sock.Socket();if (!ret) {return false;}// 2. 绑定端口号ret = listen_sock.Bind(ip_, port_);if (!ret) {return false;}// 3. 进行监听ret = listen_sock.Listen(5);if (!ret) {return false;}// 4. 创建 Selector 对象Selector selector;selector.Add(listen_sock);// 5. 进入事件循环for (;;) {std::vector<TcpSocket> output;bool ret = selector.Wait(&output);if (!ret) {continue;}// 6. 根据就绪的文件描述符的差别, 决定后续的处理逻辑for (size_t i = 0; i < output.size(); ++i) {if (output[i].GetFd() == listen_sock.GetFd()) {// 如果就绪的文件描��符是 listen_sock, 就执行 accept, 并加入到 select 中TcpSocket new_sock;listen_sock.Accept(&new_sock, NULL, NULL);selector.Add(new_sock);}else {// 如果就绪的文件描述符是 new_sock, 就进行一次请求的处理std::string req, resp;bool ret = output[i].Recv(&req);if (!ret) {selector.Del(output[i]);// [注意!] 需要关闭 socketoutput[i].Close();continue;}// 调用业务函数计算响应handler(req, &resp);// 将结果写回到客户端output[i].Send(resp);}} // end for} // end for (;;)return true;}private:std::string ip_;uint16_t port_;
};

I/O多路转接之select相关推荐

  1. Linux下I/O多路转接之select --fd_set

    fd_set 你终于还是来了,能看到这个标题进来的,我想,你一定是和我遇到了一样的问题,一样的疑惑,接下来几个小时,我一定竭尽全力,写出我想说的,希望也正是你所需要的: 关于Linux下I/O多路转接 ...

  2. 详解I/O多路转接之select

    什么是多路转接IO 对大量的描述符进行I/O事件监控-可以告诉进程现在有哪些描述符就绪了,然后进行就可以只针对就绪了的描述符进行响应操作,避免对没有就绪的I/O操作所导致的效率降低和流程阻塞. IO事 ...

  3. I/O多路转接之 select

    系统提供select函数来实现多路复用输入/输出模型. 作用:select系统调用是用来让我们的程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了 ...

  4. Linux:I/O多路转接之select(有图有代码有真相!!!)

    一.select引入 一次 I/O 分为两个部分:1)等待数据就绪      2)进行数据转移 1.select 原理: select的原理就是减少等待数据就绪的比重,巧妙的利用等待队列机制让用户进程 ...

  5. 高级IO(多路转接之select、poll、epoll->核反应堆模式)

    ꧁ 大家好,我是 兔7 ,一位努力学习C++的博主~ ꧂ ☙ 如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步❧

  6. IO多路转接 ——— select、poll、epoll

    文章目录 I/O多路转接之select select初识 select函数 socket就绪条件 select基本工作流程 select服务器 select的优点 select的缺点 select的适 ...

  7. select poll epoll 高效IO 多路转接

    目录 五种常见IO模型 高效IO的概念 阻塞 vs 非阻塞 非阻塞IO fcntl函数 I/O多路转接之select 初识select select函数原型 select操作接口 tcp_server ...

  8. 高级IO--1 ---(五种典型IO,阻塞IO,非阻塞IO,信号驱动IO,异步IO, IO多路转接)

    高级IO: 五种典型IO: 阻塞IO/非阻塞IO/信号驱动IO/异步IO/IO多路转接 IO多路转接模型:select/poll/epoll 五种典型IO 阻塞IO IO操作的流程:等待IO操作条件具 ...

  9. epoll(eventpoll)是干嘛的?IO多路转接技术(相较select、poll的优点)

    首先我们要知道epoll是用来干什么的(定义):epoll是一种IO多路转接技术,在LINUX网络编程中,经常用来做事件触发,即当有特定事件到来时,能够检测到,而不必阻塞进行监听,基于事件驱动的IO多 ...

最新文章

  1. LeetCode简单题之删除字符使字符串变好
  2. 测试代码发布到博客效果(Windows Live Writer发布)
  3. 为何Redis要比Memcached好用(转)
  4. R语言apropos函数查找包含特定字符的函数、find函数查找函数所在的位置实战
  5. 通过源码告诉你,阿里的RocketMQ事务消息到底牛逼在哪?
  6. PHP源码分析-数组
  7. 【Tiny4412】制作最小文件系统脚本
  8. python向数据库中添加参数_第四篇:python操作数据库时的传参问题
  9. LeetCode 280. 摆动排序
  10. iis7 您无权使用所提供的凭据查看此目录或页面。_使用 Spring Cloud 和 Docker 轻松构建微服务架构!...
  11. 关于Keil4 转到 Keil5以后的一些错误解决
  12. JdbcTemplate实验
  13. selenium+testng+reprotng+ant配置
  14. python画画excel_用Python在Excel里画出蒙娜丽莎的方法示例
  15. 内存管理中的 RSS 和 VSZ意思
  16. 【环境搭建】机械革命 Z3 Air 双系统 原生Windows10 +Ubuntu 20.04.2.0 LTS 桌面版 配置
  17. VFP 二维数组声明和ALEN() ASCAN() AFIELD() ADEL()函数
  18. MySQL实战——表、索引创建与优化
  19. 人脸验证:Joint Bayesian
  20. Excel:第十一天求和函数(Sum函数)

热门文章

  1. 01LaTeX学习系列之---TeX的介绍与认识
  2. ElasticSearch6.5.0 【安装IK分词器】
  3. PHP mail()可能导致的问题
  4. [转]ASP.NET Core 指定环境发布(hosting environment)
  5. -bash:/etc/profile Permission Denied
  6. JavaScript语法详解(三)
  7. UVA 12166 Equilibrium Mobile
  8. 【CCNA】思科PPP身份验证(PAP单向认证与CHAP单向认证)
  9. [转]C#中多路IP摄像机的视频监控系统
  10. Flex网站作品“妙句网”简化版推出(服务端为.Net WebService)