当前位置: 首页 > news >正文

【高级IO】- 五种 IO 模型 | 多路转接 - select


目录

IO的基本概念

什么是高效的IO?

五种IO模型

阻塞IO

非阻塞IO

信号驱动IO

IO多路转接

异步IO

同步通信VS异步通信(synchronous communication / asynchronous communication)

同步通信VS同步与互斥

阻塞VS非阻塞

其他高级IO

阻塞IO

非阻塞IO 

实现函数 SetNoBlock

以非阻塞轮询释放读取标准输入

 I/O多路转接之select

初识 select 

关于 fd_set 结构

​编辑 关于 timeval 结构

socket 就绪条件 

socket 基本工作流程

select 服务器

selectServer 类

timeout 时间测试

事件处理 

select 的优缺点

select可监控的fd个数


IO的基本概念

 I/O(Input / output)就是输入和输出,在冯诺依曼体系中,将数据从输入设备拷贝到内存叫做输入,将数据从内存拷贝到输出设备叫做输出。

冯·诺依曼体系结构 -- 理解_waves_K的博客-CSDN博客

· 对文件进行的读写操作本质就是一种IO,文件IO对应的外设就是磁盘。
· 对网络进行的读写操作本质也就是一种IO,网络IO对应的外设就是网卡。

什么是高效的IO?

IO主要分为两个步骤:

· 第一步是等,就是等待IO条件的就绪。
· 第二步就是拷贝,就是IO条件就绪后将数据拷贝到内存或者外设中。

所以 IO = 等 + 数据拷贝,但是实际中 “等” 消耗的时间往往比 “拷贝” 消耗的时间多,因此让 IO 变得更高效,就是要减少 “等” 的时间。

五种IO模型

阻塞IO

阻塞IO就是内核将数据准备好之前,系统调用会一直等待,所有套接字,默认都是阻塞方式,也是最常见的IO模型:

 · 在 recvfrom 函数等待数据就绪期间,在用户看来该进程或者线程被阻塞住了,本质是操作系统,将该进程或者线程的状态设置为了某种非 R 状态,然后放入等待队列当中,当数据就绪后,操作系统再将其从等待队列中唤醒,然后该进程或者线程再将数据从内核拷贝到用户空间。

· 阻塞IO,在 “等” + “数据拷贝” 期间都不会返回,在用户看来就像是阻塞住了。

非阻塞IO

如果内核还未将数据准备好,系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码:

非阻塞IO往往需要程序猿自己循环反复尝试读写文件描述符,这个过程称为轮询,这对CPU来说是较大的浪费,一般只有特定场景下使用。 

· 每次调用 recvfrom 函数读取数据时,就算底层数据没有就绪,recvfrom 函数也会立马返回错误信息,后续还需要继续不断调用 recvfrom 函数,直到底层有数据就绪,在用户看来该进程或者线程就没有被阻塞住,称为非阻塞IO。

阻塞IO 和 非阻塞IO 区别:

阻塞IO:当数据没有就绪时,后续检测数据是否就绪工作是操作系统发起的。

非阻塞IO:当数据没有就绪时,后续检测数据是否就绪工作是用户发起的。

信号驱动IO

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

当底层数据就绪的时候,会向当前进程或者线程发送SIGIO信号,因此可以通过signal或者sigaction函数将SIGIO的信号处理程序自定义为需要进行的IO操作,当底层数据就绪时就会自动执行对应的IO操作。

信号的产生是异步的,但信号驱动IO是同步IO的一种:

· 信号的产生是异步的,因为信号在任何时刻都可能会产生。
· 信号驱动IO是同步IO的一种,当底层数据就绪时,当前进程或者线程需要停下来正在做的事情,转而进行数据的拷贝操作,因此当前进程或者线程仍然需要参加IO的过程。

· 一个IO过程是同步还是异步,看当前进程或者线程是否参与到IO的过程中,参与了是同步IO,没有参与就是异步IO。

IO多路转接

也叫做IO多路复用,最核心在与能够同时等待多个文件描述符的就绪状态。

 IO多路转接思想:

· 使用 recvfrom 等接口需要进行等,可以将所有的 “等” 的工作交给多路转接接口(select,poll,epoll)
· 多路转接接口一次 “等” 多个文件描述符,因此能够将 “等” 的时间进行重叠,当数据就绪后再调用对应的 recvfrom 等函数进行数据的拷贝,此时这些函数就能直接进行拷贝,而不需要进行 “等” 操作。

异步IO

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

· 进行异步IO需要调用一些异步IO的接口,异步IO接口调用后立马进行返回。
· 当IO完成后,操作系统会通知应用程序,因此进行异步IO的进程或者线程并不参与IO的所有细节。


同步通信VS异步通信(synchronous communication / asynchronous communication)

同步和异步关注的是消息的通信机制

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

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

同步通信VS同步与互斥

· 进程 / 线程同步:在保证数据安全的前提下,让进程 / 线程 能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,谈论的是进程 / 线程间的一种工作关系。
· 而同步 IO:进程 / 线程 与操作系统之间的关系,谈论的是 进程 / 线程 是否需要主动参与IO过程。

阻塞VS非阻塞

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

· 阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。
· 非阻塞调用指在不能立即得到结果之前,该调用不会阻塞当前线程。

其他高级IO

非阻塞IO,记录锁,系统V流机制,I/O多路转接(I/O多路复用),readv和writev函数以及存储映射IO(mmap)。

阻塞IO

使用read函数从标准输入当中读取数据:

#include <iostream>
#include <unistd.h>
int main()
{// 阻塞IOchar buffer[1024];while (true){printf("Please#: ");fflush(stdout);ssize_t n = read(0, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n - 1] = 0;std::cout << "echo#: " << buffer << std::endl;}else if (n == 0){std::cout << "me too quit!" << std::endl;break;}else{std::cout << "read error" << std::endl;break;}}
}

 将程序运行,如果不进行输入操作,此时该进程就会阻塞住,根本原因是底层数据没有进行就绪(无数据),此时read函数在进行阻塞等待:

 此时进行输入操作,read函数检测到底层数据就绪时,立马将数据读取从内核拷贝到buffer数组中,进而将数据进行输出:

非阻塞IO 

打开文件时默认都是以阻塞的方式打开的,如果要以非阻塞的方式打开某个文件,需要在使用open函数打开文件时,需要携带 O_NONBLOCK,或者 O_NDELAY 选项,此时就能够以非阻塞的方式打开文件。

 如果要将已经打开的某个文件或者套接字设置为非阻塞,需要用到 fcntl 函数。

• fd:已经打开的文件描述符

• cmd:需要进行的操作

• ... :可变参数,传入的cmd不同,后面追加的参数也不同。


fcntl 函数有5种功能:

• 复制一个现有的描述符(cmd = F_DUPFD)

• 获得 / 设置文件描述符标记(cmd = F_GETFD 或 F_SETFD)

• 获得 / 设置文件状态标记(cmd = F_GETFL 或 F_SETFL)

• 获得 / 设置异步 I/O 所有权(cmd = F_GETOWN 或 F_SETOWN)

• 获得 / 设置记录锁(cmd = F_GETLK,F_SETLK 或 F_SETLKW)

如果函数调用成功,返回值取决于具体的进行对操作;调用失败,返回 -1,错误码被设置。

实现函数 SetNoBlock

基于 fcntl,实现一个SetNoBlock函数,将文件描述符设置为非阻塞:

void SetNonBlock(int fd)
{// 将fd设置为非阻塞int fl = fcntl(fd, F_GETFL); // 获取一下fd的状态(GETFD 获取值)if (fl < 0){std::cerr << "fcntl: " << strerror(errno) << std::endl;return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

• 使用 F_GETFL 将当前的文件描述符的属性取出来(这是一个位图)

• 再使用 F_SETFL 将文件描述符设置回去,设置回去的同时,加上一个 O_NONBLOCK参数。

以非阻塞轮询释放读取标准输入

在调用read函数之前,先调用SetNonBlock函数将0号文件描述符设置为非阻塞:

#include "util.hpp"
#include <cstdlib>
#include <vector>
#include <functional>
using namespace std;using func_t = function<void()>;#define INIT(v)                  \do                           \{                            \v.push_back(printlog);   \v.push_back(installlog); \} while (0);#define EXITLOG(v)              \do                          \{                           \for (const auto &e : v) \{                       \e();                \}                       \} while (0);void printlog()
{std::cout << "this is a print log!" << std::endl;
}void installlog()
{std::cout << "this is a install log!" << std::endl;
}int main()
{// 非阻塞式IO  在非阻塞期间能做自己的事情SetNonBlock(0); // 将fd设置为非阻塞vector<func_t> vf;INIT(vf);// 阻塞IOchar buffer[1024];while (true){// printf("Please#: ");// fflush(stdout);ssize_t n = read(0, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n - 1] = 0;std::cout << "echo#: " << buffer << std::endl;}else if (n == 0){std::cout << "me too quit!" << std::endl;break;}else{// 读取出现错误  显示资源未就绪 错误?根据错误码分析是否是真的read错误,还是无资源// std::cout << "strerror: " << strerror(errno) << "errno: " << errno << std::endl;// break;if(errno == EAGAIN){//非阻塞cout << "此时只是无数据,没有错误!" << endl;EXITLOG(vf);}else if(errno == EINTR){cout<<"接受到了阻断信号,也不是read错误!"<<endl;continue;}else{cout << "strerror: " << strerror(errno) << "errno: " << errno << endl;break;}}sleep(1);}return 0;
}

 此时,read 函数以非阻塞方式读取标准输入时,底层数据不就绪,read 函数就会立即返回(以错误的形式),错误码被设置为 EAGAIN 或 EWOULDBLOCK:

 在以非阻塞方式进行读取时,在read 函数返回值是 -1 的情况下,还需要根据错误码进一步判断,如果错误码是 EAGAIN 或者 EWOULDBLOCK,说明此时底层数据还没有就绪,还需要进行轮询检测,read 函数在读取到数据之前可能会被其他信号中断,也会以错误的形式返回,错误码被设置为 EINTR,此时应该重新执行 read 函数进行数据读取:

 运行程序,此时没有输入数据,程序就会不断调用 read 函数检测底层数据是否就绪:

当进行输入操作后,read 函数就会轮询检测,read 函数立马将数据读取从内核拷贝到 buffer 数组中,并进行输出:


 I/O多路转接之select

初识 select 

select 的函数原型:

 系统提供 select 函数来实现多路复用输入 / 输出模型

• select 系统调用是用来让程序监视多个文件描述符的状态变化的。

• 程序会停在 select 这里等待,直到被监视在文件描述符有一个或者多个发生了状态改变。

 参数介绍:

• nfds:需要监视的文件描述符当中,最大的文件描述符值 +1。

• readfds:输入输出型参数,调用时用户告知内核需要检视哪些文件描述符的读事件是否就绪,返回时内核告知用户哪些文件描述符的读事件已经就绪。

• writefds:输入输出型参数,调用时用户告知内核需要检视哪些文件描述符的读事件是否就绪,返回时内核告知用户哪些文件描述符的写事件已经就绪。

• exceptfds:输入输出型参数,调用时用户告知内核需要检视哪些文件描述符的读事件是否就绪,返回时内核告知用户哪些文件描述符的异常事件已经就绪。

• timeout:输入输出型参数,调用时由用户设置 select 的等待时间,返回时表示 timeout 的剩余时间。

参数 timeout 的取值:

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

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

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

返回值说明:

• 如果函数调用成功,则返回有事件就绪的文件描述符个数,调用失败,返回 -1,错误码被设置。

 • timeout时间耗尽,则返回0。

关于 fd_set 结构

 本质是一个位图结构,用位图中对应的位置来表示要监视的文件描述符:

调用 select 函数之前,需要定义fd_set对应的文件描述符集,然后将需要监视的文件描述符添加到文件描述符集合当中,系统提供了一组操作 fd_set 的接口,方便操作位图:

 关于 timeval 结构

 select 的最后一个参数 timeout,是一个指向 timeval 结构的指针,timeval 结构用于描述一段时间长度,该结构中有两个成员变量,tv_sec 表示秒,tv_usec 表示微秒。

socket 就绪条件 

读就绪:

• socket 内核中,接收缓冲区中的字节数,大于等于低水位标记 SO_RECVLOWAT,此时可以无阻塞的读该文件描述符,并且返回值大于0

• socket TCP通信中,对端关闭连接,此时对该socket读,则返回0

• 监听的socket上有新的连接请求

• socket 上有未处理的错误

写就绪:

• socket 内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于水位标记 SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于0

• socket 的写操作被关闭(close 或者 shutdown)对一个写操作被关闭的 socket 进行写操作,会触发SIGPIPE信号

• socket 使用非阻塞 connect 连接成功或失败之后

• socket 上有未读取的错误

异常就绪:

• socket 上收到带外数据(带外数据和TCP的紧急模式相关,TCP报头当中的URG标志位和16位紧急指针搭配使用,就能发送和接收带外数据)

socket 基本工作流程

 实现一个简单的 select 服务器,该服务器做的内容就是读取客户端发来的数据进行输出打印即可:

• 初始化服务器,套接字的创建,绑定端口号和IP地址等,设置监听状态

• 定义一个 _fdarray 数组用于保存监听套接字和已经与客户端建立好连接的套接字,一开始只有监听套接字就绪,先把监听套接字添加到 _fdarray 数组中

• 服务器开始运行,一直轮询调用 select 函数,检测读事件是否就绪,有事件就绪了就执行对应的操作

• 在每次调用 select 函数之前,需要定义一个文件描述符集 rfds,把 _fdarray 中的文件描述符依次添加到集合中,让 select 函数检测这些文件描述符中哪些文件描述符的读事件是否就绪。

• 当 select 检测到数据就绪时,就会将读事件就绪的文件描述符设置进入到 rfds 中,这个参数是一个输入输出型参数,就可以知道哪些文件描述符就绪了,并对这些文件描述符进行对应的操作

• 如果就绪的是监听套接字,就调用 accept 函数从底层全连接队列获取已经建立好的连接,并且将对应的套接字添加到 _fdarray 数组中

• 如果就绪的是与客户端建立连接的套接字,就调用 read 函数将客户端发送到数据进行接收,并进行打印输出

• 服务器与客户端建立连接的套接字读事件就绪,也有可能是因为客户端将连接的套接字关闭了,此时服务器应该调用 close 关闭该套接字,并将该套接字从 _fdarray 数组中移除

细节:

1、select 函数除了 nfds之外,其他都是输入输出型参数,当 select 返回时,这些参数的值都已经被修改了,每次调用时 select 函数需要对参数进行重新设置,timeout 也一样需要设置

2、select 需要传入的被监视的文件描述符中最大描述符值 +1,在遍历 _fdarray 时,需要记录最大文件描述符的值。

select 服务器

socket 类:

编写一个 Socket 类,对套接字的接口进行一定程度的封装,为了外部通过作用域直接调用Socket 类当中的封装函数,将这些接口函数定义成为静态成员函数:

// 进行封装 TCP socket 编写
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "logMessage.hpp"
#include "error.hpp"#define gbacklog 5class Sock
{
public:static int Socket(){// 1.创建套接字 面向字节流int listensock = socket(AF_INET, SOCK_STREAM, 0);if (listensock < 0){logMessage(FATAL, "Create Socket Fail!");exit(SOCKET_ERR);}logMessage(NORMAL, "Create Socket Success!");// 处理一下 Time_Wait 导致无法绑定端口号问题int opt = 1;setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));return listensock;}static void Bind(int sock, int port){// 2.bindstruct sockaddr_in peer;bzero(&peer, sizeof(peer));peer.sin_addr.s_addr = INADDR_ANY; // IP地址绑定任意的peer.sin_family = AF_INET;peer.sin_port = htons(port);if (bind(sock, (struct sockaddr *)&peer, sizeof(peer)) < 0){logMessage(FATAL, "Bind Socket Fail!");exit(BIND_ERR);}logMessage(NORMAL, "Bind Socket Success!");}static void Listen(int sock){// 3.Socket 设置为监听状态if (listen(sock, gbacklog) < 0){// 设置失败logMessage(FATAL, "Listen Socket Fail!");exit(LISTEN_ERR);}logMessage(NORMAL, "Listen Socket Success!");}// 获取新连接static int Accpet(int listensock, uint16_t *clientPort, std::string *clientIP){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(listensock, (struct sockaddr *)&peer, &len);if (sock < 0){logMessage(FATAL, "Accept Socket Fail!");exit(ACCEPT_ERR);}else{logMessage(NORMAL, "Accpet Socket Success,get a new sock: %d", sock);*clientPort = ntohs(peer.sin_port);   // 网络转主机*clientIP = inet_ntoa(peer.sin_addr); // 网络转主机,再转字符串}return sock;}
};

selectServer 类

所编写的服务器在绑定时,不需要显示绑定IP地址,直接设置为 INADDR_ANY 即可,在类的成员变量中,只需要包含监听套接字,和端口号即可。

运行服务器:

select 服务器要做的是不断调用select 函数,当有事件就绪时,做出处理动作

• 需要将数组中所有位置的值初始化为无效,并将监听套接字添加到数组的第一个位置

• select 函数返回后,如果返回0,说明 timeout 时间耗尽,直接进行下一次的 select 调用即可,返回 -1,根据返回的错误码来进一步判断,是否需要下一次继续调用 select 函数,返回值大于0,调用成功,已经有文件描述符的读事件就绪,对就绪事件进一步处理

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <functional>#include "sock.hpp"namespace Server
{using func_t = std::function<std::string(const std::string &)>;const uint16_t defaultPort = 8080;const int fdnum = sizeof(fd_set) * 8;const int defaultnum = -1;class SelectServer{public:SelectServer(func_t f, int port): _func(f), _listensock(-1), _port(port){}~SelectServer(){if (_listensock){close(_listensock);}if (_fdarray){delete[] _fdarray;}}void InitServer(){_listensock = Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_fdarray = new int[fdnum];for (int i = 0; i < fdnum; i++){_fdarray[i] = defaultnum;}_fdarray[0] = _listensock;}void Start(){// 等 + 处理数据  select返回的是fd的个数for (;;){fd_set rfd;// 初始化rfdFD_ZERO(&rfd);int maxfd = _fdarray[0]; // 最大fd数 +1for (int i = 0; i < fdnum; i++){if (_fdarray[i] == defaultnum){continue;}// 将这个fd 设置进入rfd中FD_SET(_fdarray[i], &rfd);if (_fdarray[i] > maxfd){maxfd = _fdarray[i];}}logMessage(NORMAL, "max fd is: %d\n", maxfd);// 非阻塞 每隔一秒,询问一下//struct timeval _timeval = {1, 0};// int n = select(maxfd + 1, &rfd, nullptr, nullptr, &_timeval);int n = select(maxfd + 1, &rfd, nullptr, nullptr, nullptr);switch (n){case 0:// 超时logMessage(NORMAL, "time out ...");break;case -1:logMessage(WARNING, "select errno code: %d, select errno message: %s", errno, strerror(errno));break;default:// 走到这里说明有fd就绪了,需要进行处理,但是只有 listensock 就绪logMessage(NORMAL, "have event ready!\n");// 进行业务逻辑处理Handerevent(rfd);break;}}}private:int _port;int _listensock;int *_fdarray; // 维护的accpet的fd数组func_t _func;};
}

timeout 时间测试

在运行服务器时,需要实例化一个对象,对select 服务器进行初始化并调用 Start 函数运行服务器:

#include <iostream>
#include <string>
#include <memory>#include "error.hpp"
#include "selectServer.hpp"void Usage(std::string arg)
{std::cout << "\n Usage: \n\t" << arg << " port"<< "\n\t" << std::endl;
}// ./main 8080
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);}std::unique_ptr<Server::SelectServer> us(new Server::SelectServer(transmition, atoi(argv[1])));us->InitServer();us->Start();return 0;
}

此时的服务器 select 函数将 timeout 设置为了 nullptr,因此select 函数调用时会阻塞等待,服务器在第一次调用 select 时,只监视监听套接字(listensock),运行服务器后,客户端没有请求发送过来请求连接,读事件就不会就绪,服务器第一次调用 select 函数中就会进行阻塞等待:

 使用 telnet 工具向服务器发起连接请求,此时 select 函数会立马检测到监听套接字的读事件就绪,然后对套接字做出对应的处理逻辑:

如果此时将timeout的值设置为0,select 函数调用后就会进行非阻塞等待,无论被监视的文件描述符是否就绪,检测后都会立马返回,如果有事件就绪,select 函数的返回值就会大于0,没有事件就绪,返回值就会等于0:

 struct timeval _timeval = {0, 0};int n = select(maxfd + 1, &rfd, nullptr, nullptr, &_timeval);switch (n){case 0:// 超时logMessage(NORMAL, "time out ...");break;case -1:logMessage(WARNING, "select errno code: %d, select errno message: %s", errno, strerror(errno));break;default:// 走到这里说明有fd就绪了,需要进行处理,但是只有 listensock 就绪logMessage(NORMAL, "have event ready!\n");// 进行业务逻辑处理Handerevent(rfd);break;}

此时如果没有客户端发送连接请求,select 函数就会一致进行轮询检测,每次检测的读事件都不就绪,返回结果都是0,就会造成 time out ... 现象。 

 如果将 timeout 时间设置为特定的时间,比如这里设置为5s,那么select 函数调用后5秒内会进行阻塞等待,5秒后依旧没有读事件就绪,就会超时返回:

struct timeval _timeval = {5, 0};

运行服务器,此时无客户端发送到连接请求,select 函数调用5秒后都会超时返回:

如果在5秒内有读事件就绪,那么 timeout 就会返回剩余的时间,所以在每次调用select 函数时,都需要对 timeout 时间重新设置。

事件处理 

当select 检测到有文件描述符就绪时并成功返回后,就需要对就绪事件进行处理:

• 需要遍历整个 _fdarray 数组当中的文件描述符,依次判断各个文件描述符的都事件是否就绪,如果就绪进行处理

• 如果文件描述符就绪后,还需要判断该文件描述符是否是监听套接字,如果是监听套接字就绪,就需要调用 accept 函数,将底层的连接获取上来,并添加到 _fdarray 数组当中,在下一次调用select 函数之前,将 _fdarray 中的文件描述符设置进入到 rfds 中

• 如果是客户端建立的连接对应的读事件就绪,就需要调用 read 函数读取客户端发来的数据,如果读取成功就将读到的数据进行在服务器端打印,如果读取失败或者客户端关闭了连接,那么服务器就调用close 函数关闭对应的连接,并且将对应的文件描述符从 _fdarray 中移除

   void Accpeter(int listensock){// 获取新连接后,直接添加进入到 _fdarray 数组中logMessage(NORMAL, "Accpeter begin ...\n");uint16_t clientPort = 0;std::string clientIP;int sock = Sock::Accpet(listensock, &clientPort, &clientIP);if (sock < 0){return;}int i = 0;for (; i < fdnum; i++){if (_fdarray[i] != defaultnum){continue;}else{break;}}// 找到位置if (i == fdnum){// 说明已经满了logMessage(WARNING, "server is full,please wait ...\n");}else{_fdarray[i] = sock;}// 进行打印Print();logMessage(NORMAL, "Accpeter end ...\n");}void Recver(int sock, int i){// 通过sock这个fd进行接受数据logMessage(NORMAL, "Recver begin ...\n");char buffer[1024];ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0); // 这里读取不会阻塞,只有sock就绪了,才进来if (n > 0){buffer[n - 1] = 0;logMessage(NORMAL, "client# %s", buffer);}else if (n < 0){// 读取错误close(sock);_fdarray[i] = defaultnum;logMessage(WARNING, "recv error# %s", strerror(errno));return;}else // == 0{// client 退出了close(sock);_fdarray[i] = defaultnum;logMessage(NORMAL, "client quit,me too ...");return;}// 此时数据都在buffer当中,处理 requeststd::string response = _func(buffer);write(sock, response.c_str(), response.size());logMessage(NORMAL, "Recver end ...\n");}// 处理逻辑void Handerevent(fd_set &rfd){// 判断是listensock,还是普通sock的for (int i = 0; i < fdnum; i++){if (_fdarray[i] == defaultnum){continue; // 后面需要进行置空,不能break}if (_fdarray[i] == _listensock && FD_ISSET(_fdarray[i], &rfd)){// 需要进行accpetAccpeter(_fdarray[i]);}else if (FD_ISSET(_fdarray[i], &rfd)){// 其他fd 而且就绪Recver(_fdarray[i], i);}else{}}}

select 的优缺点

优点:

• 可以同时等待多个文件描述符,并且只负责等待,实际的IO操作由accept,read,write 函数来完成,这些接口在进行IO操作时不会被阻塞。

• select 同时等待多个文件描述符,因此可以将“等”的事件重叠,提高IO的效率。

缺点:

• 每次调用select,都需要手动设置fd集合,从接口使用角度来说非常不方便

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

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

• select支持的文件描述符数量太小


select可监控的fd个数

fd_set结构本质是一个位图,用每一个比特位来标记一个文件描述符,select可监控的文件描述符个数取决于fd_set类型的比特位个数,通过计算(sizeof(fd_set))* 8 可得总共1024个字节,说明可监控的文件描述符个数为1024,在初始化 _fdarray 数组时,需要将数组大小定义为 1024即可:

const int fdnum = sizeof(fd_set) * 8;

相关文章:

【高级IO】- 五种 IO 模型 | 多路转接 - select

目录 IO的基本概念 什么是高效的IO&#xff1f; 五种IO模型 阻塞IO 非阻塞IO 信号驱动IO IO多路转接 异步IO 同步通信VS异步通信&#xff08;synchronous communication / asynchronous communication&#xff09; 同步通信VS同步与互斥 阻塞VS非阻塞 其他高级IO …...

在Linux搭建GitLab私有仓库配置实现远程访问私有仓库Gitlab ——【内网穿透】

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《高效编程技巧》《cpolar》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留…...

ChatGPT应用于高职教育的四大潜在风险

目前&#xff0c;ChatGPT还是一种仍未成熟的技术&#xff0c;当其介入高职教育生态后&#xff0c;高职院校师生在享受ChatGPT带来的便利的同时&#xff0c;也应该明白ChatGPT引发的风险也会随之进入高职教育领域&#xff0c;如存在知识信息、伦理意识与学生主体方面的风险与挑战…...

uni-app在组件中内嵌webView,实现自定义webView的大小,并处理页面中有webview时其他元素点击事件失效的问题

uni-app在组件中内嵌webView&#xff0c;实现自定义webView的大小&#xff0c;并处理页面中有webview时其他元素点击事件失效的问题 uni-app在组件中内嵌webView&#xff0c;实现自定义webView的大小 setWebviewTop() {// #ifdef APP-PLUSvar currentWebview this.$scope.$g…...

档案开发:增加查询和打卡按钮

档案开发&#xff1a;增加查询和打卡按钮 和单据开发的不同点 没有单据类型不是右击–>特性–>单据主表/单据子表&#xff0c;而是右击–>特性–>选择想要的接口访问器类型是NCVO不需要映射不是项目右键–>新建–>其他–>主子表单据结点&#xff0c;而是…...

redis基础细心讲解,一篇了解常用的缓存技术!

今日内容 redis 1. 概念 2. 下载安装 3. 命令操作1. 数据结构 4. 持久化操作 5. 使用Java客户端操作redis 6. 在ssm项目中使用缓冲进行CRUD操作Redis 1. 概念 redis是一款高性能的NOSQL系列的非关系型数据库 1.1.什么是NOSQL ​ NoSQL(NoSQL = Not Only SQL),意即“不仅仅…...

Three.js之几何体、高光材质、渲染器设置、gui

参考资料 阵列立方体和相机适配体验Threejs常见几何体简介…gui.js库(可视化改变三维场景) 知识点 注&#xff1a;基于Three.jsv0.155.0 长方体&#xff1a;oxGeometry球体&#xff1a;SphereGeometry圆柱&#xff1a;CylinderGeometry矩形平面&#xff1a;PlaneGeometry圆…...

UE4如何连接dmx---摇头矩阵灯具的创建

UE4如何连接dmx---摇头矩阵灯具的创建 开始创建库&#xff01; 然后我们开始创建多少个灯珠&#xff08;注意了&#xff1a;这是矩阵灯&#xff0c;是看灯珠的&#xff09; 那么这里我们创建6X6灯珠 下面设置灯珠的属性&#xff0c;灯珠有什么属性呢&#xff0c;只有颜色属性&…...

网络聊天室

一、项目要求 利用UDP协议&#xff0c;实现一套聊天室软件。服务器端记录客户端的地址&#xff0c;客户端发送消息后&#xff0c;服务器群发给各个客户端软件。 问题思考 客户端会不会知道其它客户端地址&#xff1f; UDP客户端不会直接互连&#xff0c;所以不会获知其它客…...

ChatGPT只是玩具:生成式人工智能在不同行业的应用

源自&#xff1a;IT经理网 生成式人工智能的十一个行业用例 打开生成式 AI的正确姿势 声明:公众号转载的文章及图片出于非商业性的教育和科研目的供大家参考和探讨&#xff0c;并不意味着支持其观点或证实其内容的真实性。版权归原作者所有&#xff0c;如转载稿涉及版权等问题&…...

RestFul的风格是什么

RestFul的风格是什么&#xff1f; 当我们谈论RESTful风格时&#xff0c;它指的是一种设计和构建网络应用程序的原则和约定。以下是RESTful风格的一些主要特点&#xff1a; 资源&#xff1a;将应用程序的功能封装为资源&#xff0c;每个资源都有一个唯一的标识符&#xff08;U…...

【自制C/C++小项目JuLongEditor】使用Windows控制台API来制作一个简单的文本编辑器

2023年8月22日&#xff0c;周二下午 昨天花了一个下午和晚上来制作的&#xff0c; 实现了一些基本的功能&#xff0c; 但由于代码只有130行&#xff0c;所以存在很多不足之处 GitHub&#xff1a;GitHub - JuLongZhiLu/JuLongEditor: C/C小项目&#xff0c;使用Windows控制台…...

中国芯,寻找新赛道迫在眉睫

北京华兴万邦管理咨询有限公司 商瑞 陈皓 近期国内半导体行业的热点可以用两个“有点多”来描述&#xff0c;一个是中国芯群体中上市公司股价闪崩的有点多&#xff0c;另一个是行业和企业的活动有点多。前者说明了许多国内芯片设计企业&#xff08;fabless商业模式&#xff09;…...

C++ 好用的格式化库--fmt

背景 fmt 库是一个开源的 C 格式化库&#xff0c;它提供了一种简洁、安全和高效的方式来进行字符串格式化。该库的设计目标是提供与 Python 的字符串格式化语法类似的功能&#xff0c;同时保持 C 的类型安全性和性能。 下载与安装 官网下载 fmt 官网地址&#xff1a;https:…...

微信小程序教学系列(3)

微信小程序教学系列 第三章&#xff1a;小程序高级开发技巧 1. 小程序API的使用 小程序API简介 小程序API是小程序提供的一系列接口&#xff0c;用于实现各种功能和操作。通过调用小程序API&#xff0c;可以实现页面跳转、数据存储、网络请求等功能。 使用小程序API的步骤…...

ORB-SLAM系列算法演进

ORB-SLAM算法是特征点法的代表&#xff0c;当前最新发展的ORB-SLAM3已经将相机模型抽象化&#xff0c;适用范围非常广&#xff0c;虽然ORB-SLAM在算法上的创新并不是很丰富&#xff0c;但是它在工程上的创新确实让人耳目一新&#xff0c;也能更好的为AR、机器人的算法实现落地。…...

solidity0.8.0的应用案例11:透明代理合约

选择器冲突 智能合约中,函数选择器(selector)是函数签名的哈希的前4个字节。例如mint(address account)的选择器为bytes4(keccak256("mint(address)")),也就是0x6a627842. 由于函数选择器仅有4个字节,范围很小,因此两个不同的函数可能会有相同的选择器,例如…...

最新消息:谷歌将在Chromebook上运用UWB技术,无线通信更上一层

超宽带&#xff08;UWB&#xff09;技术是一种创新的短距离无线通信技术&#xff0c;具有高速数据传输和精确定位物体位置的优势。尽管该技术已经存在一段时间&#xff0c;但最近开始广泛应用于各种设备中。据最新报道&#xff0c;Pixel Watch 2可能会搭载UWB模块&#xff0c;这…...

php+echarts实现数据可视化实例3

效果 全部代码 <?php include(includes/session.inc); include(includes/SQL_CommonFunctions.inc); ?> <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" …...

ubuntu下安装Sphinx,编译pdf

安装WSL2&#xff1a; 以管理员身份打开PowerShellwsl --install 来安装其他 Linux 发行版wsl --list --verbose 查看安装在 Windows 计算机上的 Linux 发行版列表 安装sphinx&#xff1a; sudo apt-get updatesudo apt-get install python3-sphinxsudo apt-get install lat…...

vue2.x项目从0到1(七)之用户权限

此章节偏理论知识 对于小一点的项目 比如说角色都是平级的 那我们直接像之前 vue2.x项目从0到1&#xff08;二&#xff09;之后台管理侧边栏&#xff08;动态渲染路由以及高亮&#xff09;_vue动态渲染侧边栏_关忆北_的博客-CSDN博客这样渲染就行了 但是一旦项目大了 …...

上传镜像到阿里云的ACR

1、开通阿里云ACR 2、在ACR 中创建命名空间 3、本地安装docker 4、登录到 开通ACR&#xff0c;需要配置访问凭证 [rootmaster ~]# docker login --username***lb registry.cn-beijing.aliyuncs.com Password: 5、给镜像打标签 [rootmaster ~]# docker images REPOSITORY …...

ahooks.js:一款强大的React Hooks库及其API使用教程(五)

一、ahooks.js简介二、ahooks.js安装三、继续ahooks.js API的介绍与使用教程61. useEventTarget62. useExternal63. useFavicon64. useMutationObserver65. useLongPress66. useScroll67. useResponsive68. useFocusWithin69. useControllableValue70. useEventEmitter 一、aho…...

MySQL TCL 事务控制

文章目录 1.事务四大特性2.事务并发问题3.事务隔离级别4.隔离级别查看与设置5.动提交事务5.1 查看自动提交事务5.2 关闭或开启自动提交事务 6.事务执行的基本流程7.设置事务的保存点参考文献 说到事务控制&#xff0c;先说一下数据库的事务是什么以及 MySQL 中我们必知的知识点…...

【Ubuntu】从Graylog到Grafana Loki:构建更强大的网络设备管理和监控系统

在将Graylog部署到生产环境时&#xff0c;我们遇到了一些问题&#xff0c;其中最主要的是无法安装MongoDB并且无法随时重启机器去修改BIOS设置来修复问题 【WARNING: MongoDB 5.0 requires a CPU with AVX support, and your current system does not appear to have that! 】。…...

[JavaWeb]【八】web后端开发-Mybatis

目录 一 介绍 二 Mybatis的入门 2.1 快速入门 2.1.1 准备SpringBoot工程 2.1.2 创建数据库mybatis以及对应库表user 2.1.3 创建User实体类 2.1.4 配置application.properties数据库连接信息 2.1.5 编写sql语句&#xff08;注解方式&#xff09; 2.1.6 测试运行 2.1.7 配…...

Flink源码之Checkpoint执行流程

Checkpoint完整流程如上图所示&#xff1a; JobMaster的CheckpointCoordinator向所有SourceTask发送RPC触发一次CheckPointSourceTask向下游广播CheckpointBarrierSouceTask完成状态快照后向JobMaster发送快照结果非SouceTask在Barrier对齐后完成状态快照向JobMaster发送快照结…...

【工具使用】Git的使用

dev代表开发版 1. git clone 命令 通过 git add <name> 对文件进行跟踪&#xff0c;把<name>加入到暂存区 git commit -m XXXXXXX 提交修改并补充XXXXX作为注释 “暂存”状态&#xff1a;出现了一些修改&#xff0c;但是还没有提交 对于Java来说&#xff0c;.cl…...

无涯教程-PHP Installation on Windows NT/2000/XP with IIS函数

在Windows Server上运行IIS的PHP的安装比在Unix上简单得多,因为它涉及的是预编译的二进制文件而不是源代码。 如果您打算在Windows上安装PHP,那么这是先决条件列表- 运行中的PHP支持的Web服务器。一个正确安装的PHP支持的数据库,如MySQL或Oracle等。(如果您打算使用的话) PHP…...

EureKa快速入门

EureKa快速入门 远程调用的问题 多个服务有多个端口&#xff0c;这样的话服务有多个&#xff0c;硬编码不太适合 eureKa的作用 将service的所有服务的端口全部记录下来 想要的话 直接从注册中心查询对于所有服务 每隔一段时间需要想eureKa发送请求 保证服务还存活 动手实践 …...

影响网站打开速度/百度投放广告怎么收费

在5月23日举行的云栖大会?成都峰会上&#xff0c;阿里云联合思科、SAP、Informatica、NetApp、中标软件、用友畅捷通、泛微、Fortinet、联想云等国内外知名软件企业共同开启云市场软件品牌馆&#xff0c;并推出商业软件15天免费试用计划&#xff0c;希望带动100万中小企业实现…...

广州网站模板建站/广州谷歌seo公司

Extending QML - Object and List Property Types Example 扩展QML-对象和列表属性类型示例 Exporting C Properties. 导出C属性。 This example builds on: 此示例基于&#xff1a; Extending QML - Adding Types Example The Object and List Property Types example s…...

济南网站建设求职简历/百度seo收录软件

【log4j:WARN No appenders could be found for logger 解决方案】 【解决】 我们在使用Log4j的时候&#xff0c;总是出现&#xff1a; Java代码log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory). log4j:WARN Please initialize …...

tp做网站/seo整站优化费用

一&#xff0c;自定义String #include <iostream> #include <string.h>using namespace std;class String{ private:char *m_data; public:String(); //默认构造函数String(const char *); //使用C格式的字符串初始…...

如何做微网站/北京seo教师

汇总一些管理、压缩、缩小网站资源的工具在这里供大家各取所需。 1️⃣django-compressor 将链接和内联的 JavaScript 或 CSS 压缩到一个单独的缓存文件中。 它支持 coffeescript&#xff0c;LESS 和 SASS等编译器&#xff0c;并且可以通过自定义处理步骤进行扩展。 Django Com…...

怎样装修公司网站/怎么引流到微信呢

一.常量概述 常量是一个固定值,在编译器就确定结果.声明时必须赋值且结果不可以改变.因为常量在编译器就确定,可以防止程序运行过程中意外修改常量关键字const常量定义完可以不使用Go语言中常量定义没有明确语法要求,可以不全大写,驼峰即可.很多内容可以定义成常量 人名圆周率电…...