IO多路转接之select和poll

目录

一. IO多路转接的概念

二. 通过select实现IO多路转接

2.1 select接口

2.2 Select服务器的实现

2.3 select实现IO多路转接的优缺点

三. 通过poll实现IO多路转接

3.1 poll接口

3.2 Poll服务器的实现

3.3 poll实现IO多路转接的优缺点

四. 总结


一. IO多路转接的概念

在IO操作中,如果我们阻塞式的等待某个文件描述符资源就绪,那么在等待的过程中,就会浪费大量的时间,造成程序运行的效率低下。在实际的工程应用中,可能存在同时有多个文件描述符就绪的情况,这时如果阻塞等待其他未就绪的文件描述符,其它已经就绪的文件描述符也就暂时无法进行处理。

相比与单纯地阻塞式IO,IO多路转接能够实现这样的功能:当用户所关心的多个文件描述符的其中之一就绪时,就对这个就绪的进行处理。IO多路转接能够大大降低阻塞等待的时间,提高程序IO操作的效率。

图1.1 IO多路转接的实现逻辑

二. 通过select实现IO多路转接

2.1 select接口

函数原型:int select(int nfds, struct fd_set* readfds, struct fd_set* writefds, struct fd_set* exceptfds, struct timval* timeout)

头文件:#include <sys/select.h>

函数参数:

  • nfds:所关注的值最大的文件描述符值+1。
  • readfds/writefds/exceptfds:输入输出型参数,设置关注的读/写/异常文件描述符。
  • timeout:输入输出型参数,设置最长阻塞时间,获取剩余时间。

返回值:如果执行成功,返回就绪的文件描述符个数,等待超时返回0,等待失败返回-1。

在使用select函数时,有以下几点需要注意:

  • readfds/writefds/exceptfds均为输入输出型参数,作为输入型参数时告知内核需要关系哪些文件描述符,作为输出型参数时由内核告知用户哪些文件描述符已经就绪。因此,每次调用select之前,都需要对readfds/writefds/exceptfd重新进行设置。
  • readfds/writefds/exceptfds的底层是由位图实现的,但是,不可以通过简单的按位与1操作设置关心特定文件描述符,而是应当通过下面四个接口,来实现对某个fd_set对象的操作:
  1. FD_SET(int fd, fd_set* set):将指定fd添加到fd_set类型对象中去。
  2. FD_ISSET(int fd, fd_set* set):检查指定fd是否出现在fd_set对象中。
  3. FD_ZERO(fd_set* set):将set对象设置关注的文件描述符全部清空。
  4. FD_CLR(int fd, fd_set* set):清除fd_set对象中的指定文件描述符。
  • timeout为最长的阻塞等待时间,如果设置为nullptr则表示为一直阻塞,struct timeval类型的定义,如果select成功执行,那么timeout的值变为了剩余多长时间没有用,比如:设置了5s的最长等待时间,但是2s就有文件描述符就绪,还剩下3s,那么当select运行结束后,timeout就被设置为3s。
struct timeval 
{
    long tv_sec;    /* seconds */
    long tv_usec;   /* microseconds */
};

代码2.1展示了如何使用select接口,设置关注标准输入、标准输出和标准错误的读状态,设置最长阻塞时间1s,每次调用select前对fd_set对象和timeout重新设置,避免上一层调用select输出覆盖,在检查到select返回值>0时,还应使用FD_ISSET进一步检查所关注的文件描述符是否真正就绪,下面的代码真正所关系的文件描述符是标准输入0。

代码2.1:select接口的使用方法

#include <iostream>
#include <cstring>
#include <sys/select.h>
#include <unistd.h>

int main()
{
    // 读取缓冲区
    char buffer[1024] = { 0 };

    while(true)
    {
        // 每次调用select之前,重新设置fd_set类型参数和Timeval阻塞时间
        fd_set rfd;
        FD_SET(0, &rfd);
        FD_SET(1, &rfd);
        FD_SET(2, &rfd);    // 设置关心标准输入、标准输出和标准错误

        // 设置最长阻塞时间为1s
        struct timeval timeout;
        timeout.tv_sec = 1; timeout.tv_usec = 0;

        // 调用select进行IO多路转接
        int n = select(3, &rfd, nullptr, nullptr, &timeout);
        if(n > 0)
        {
            // 如果标准输入没有就绪,那么直接到下一轮循环中去
            if(!FD_ISSET(0, &rfd))
            {
                continue;
            }

            ssize_t sz = read(0, buffer, 1023);
            buffer[sz - 1] = '\0';
            std::cout << "Show message# " << buffer << std::endl;
            std::cout << "Remain time: " << timeout.tv_sec << " seconds," << timeout.tv_usec << " microseconds" << std::endl;

            if(strcmp(buffer, "quit") == 0)
            {
                break;
            }
        }
        else if(n == 0)
        {
            std::cout << "WARNING, Time out!" << std::endl;
        }
    }

    return 0;
}

2.2 Select服务器的实现

本文实现一个基于TCP协议,可以从客户端读取数据的Select服务器。Select服务器的声明见代码2.2,其中包含基本的构造函数和析构函数,还有Handler函数在检测到有就绪的文件描述符后进行处理、Accepter函数用于接受对端连接请求、Reciever函数用于从指定文件描述符中读取数据。

代码2.2:SelectServer的声明(SelectServer.hpp头文件)

#pragma once

#include "Sock.hpp"
#include <vector>
#include <sys/select.h>
#include <unistd.h>

static const int FD_CAPACITY = 8 * sizeof(fd_set);
static const int NON_FD = -1;

class SelectServer
{
public:
    SelectServer(uint16_t port, const std::string& ip = "");   // 构造函数
    void start();     // Select服务器启动运行函数
    ~SelectServer();  // 析构函数

private:
    void Handler(fd_set& rfd);  // 处理就绪文件描述符函数
    void Reciever(int pos);     // 内容读取函数
    void Accepter();            // 链接接收函数
    void ShowFdArray();         // 文件描述符输出函数 -- 用于DEBUG
 
    int _listenSock;    // 监听套接字fd
    uint16_t _port;     // 服务器端口号
    std::string _ip;    // ip地址
    std::vector<int> _fd_array;    // 文件描述符序列
};

下面为Select服务器每个成员函数的实现需要注意的一些事项:

  • 在Class SelectServer中,需要有一个_fd_array数组,其中记录需要关注的文件描述符,用于每次调用select之前设置fd_set对象,其中_fd_array可以是C语言数组或顺序表vector。在构造函数中要为_fd_array预先开辟好一块空间,并将每个位置的值设置为一个负数值NON_FD,表示这个位置没有存放关注的文件描述符fd。
  • 在构造函数中,要执行基于TCP协议服务器的基本操作:获取listen套接字、绑定端口号、设置监听状态。
  • start函数为服务器运行函数,由于服务器是一个常驻进程,因此start执行while死循环,在每轮循环中,都遍历_fd_array来设置fd_set对象,并且要检查select的返回值是否大于0,即:检查是否有就绪的文件描述符。如果有,就调用handler函数进行处理。
  • Handler的功能是处理已经就绪的文件描述符,在Handler中要遍历_fd_array的每个fd,通过FD_ISSET检查是否就绪,如果就就绪,还要分为listen文件描述符和普通文件描述符两种情况来讨论。
  • Accepter函数用于接收客户端的连接请求,Reciever用于读取客户端发送的数据。

代码2.3:日志打印函数的实现(log.hpp头文件)

#pragma once
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
 
#define DEBUG  0
#define NORMAL 1
#define WARING 2
#define ERROR  3
#define FATAL  4

// 日志等级
static const char* g_levelMap[5] = 
{
    "DEBUG",
    "NORMAL",
    "WARING",
    "ERROR",
    "FATAL"
};

// 日志打印哈数,level为日志等级,后面为格式化可变参数
static void logMessage(int level, const char *format, ...)
{
    // 1. 输出常规部分
    time_t timeStamp = time(nullptr);
    struct tm *localTime = localtime(&timeStamp);
    printf("[%s]  %d-%d-%d, %02d:%02d:%02d\n", g_levelMap[level], localTime->tm_year, localTime->tm_mon, \
                localTime->tm_mday, localTime->tm_hour, localTime->tm_min, localTime->tm_sec);
    
    // 2. 输出用户自定义部分
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

代码2.4:网络通信Socket相关函数的实现(Sock.hpp头文件)

#pragma once

#include "log.hpp"
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class Sock
{
public:
    // 创建socket文件描述符
    static int Socket()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if(sock < 0)
        {
            logMessage(FATAL, "socket error, sock:%d\n", sock);
            return -1;
        }

        logMessage(NORMAL, "socket success, sock:%d\n", sock);
        return sock;
    }

    // 绑定端口号
    static int Bind(int sock, const std::string &ip, uint16_t port)
    {
        struct sockaddr_in tmp;
        memset(&tmp, 0, sizeof(tmp));
        tmp.sin_family = AF_INET;    // 网络协议族
        tmp.sin_addr.s_addr = ip.empty() ? INADDR_ANY : inet_addr(ip.c_str());  // ip地址
        tmp.sin_port = htons(port);  // 端口号
        socklen_t len = sizeof(tmp);

        if(bind(sock, (struct sockaddr *)&tmp, len) < 0)
        {
            logMessage(FATAL, "bind error!\n");
            return -1;
        }

        logMessage(NORMAL, "bind success!\n");
        return 0;
    }

    // 设置监听状态
    static int Listen(int sock, int backlog = 10)
    {
        if(listen(sock, backlog) < 0)
        {
            logMessage(FATAL, "listen error!\n");
            return -1;
        }

        logMessage(NORMAL, "listen success!\n");
        return 0;
    }

    // 接受连接
    static int Accept(int sock, std::string& ip, uint16_t& port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);

        int fd = accept(sock, (struct sockaddr *)&peer, &len);
        if(fd < 0) return -1;

        ip = inet_ntoa(peer.sin_addr);
        port = ntohs(peer.sin_port);
        logMessage(NORMAL, "accept success, [%s-%d]\n", ip.c_str(), port);

        return fd;
    }

    // 连接对方
    static int Connect(int sock, const std::string &ip, uint16_t port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));

        peer.sin_family = AF_INET;
        peer.sin_addr.s_addr = inet_addr(ip.c_str());
        peer.sin_port = htons(port);

        socklen_t len = sizeof(peer);

        if(connect(sock, (const struct sockaddr *)&peer, len) < 0)
        {
            logMessage(FATAL, "conect error!\n");
            return -1;
        }
        logMessage(NORMAL, "connect success!\n");

        return 0;
    }
};

代码2.5:SelectServer的实现(SelectServer.cc源文件)

#include "SelectServer.hpp"

SelectServer::SelectServer(uint16_t port, const std::string& ip)
        : _listenSock(-1), _port(port), _ip(ip), _fd_array(FD_CAPACITY, NON_FD)
{
    // 获取监听套接字
    _listenSock = Sock::Socket();         
    if(_listenSock < 0){
        exit(2);
    }
    
    // 绑定端口号
    if(Sock::Bind(_listenSock, ip, _port) < 0){
        exit(3);
    }

    // 设置监听状态
    if(Sock::Listen(_listenSock) < 0){
        exit(4);
    }         
}

// Select服务器启动运行函数
void SelectServer::start()
{
    while(true)
    {
        fd_set rfd;     // select所关注的读取fd
        FD_ZERO(&rfd);  // 将文件描述符清零
        _fd_array[0] = _listenSock;   // 默认设置_fd_array[0]为listenSock

        ShowFdArray();

        // 将_fd_array中的有效文件描述符记录到rfd中去
        int maxFd = _listenSock;   // 最大文件描述符
        for(const auto fd : _fd_array)
        {
            if(fd != NON_FD)
            {
                FD_SET(fd, &rfd);   // 添加文件描述符
                if(fd > maxFd) maxFd = fd;   // 更新最大放大
            }
        }

        // 设置select,监视文件描述符就绪状态(暂时设置为阻塞)
        int n = select(maxFd + 1, &rfd, nullptr, nullptr, nullptr);

        switch(n)
        {
        case 0:     // 没有文件描述符就绪
            logMessage(DEBUG, "Time out, without any interest fd prepared!\n");
            break;
        case -1:    // select发生错误
            logMessage(ERROR, "Select error, errno:%d, errMsg:%s\n", errno, strerror(errno));
            break;
        default:    // 有至少一个文件描述符就绪
            Handler(rfd);
            break;
        }
    }
}

// 析构函数
SelectServer::~SelectServer()
{
    if(_listenSock >= 0)
        close(_listenSock);
}

// 就绪文件描述符处理函数
void SelectServer::Handler(fd_set& rfd)
{
    // 遍历_fd_array,检查有哪个fd就绪了,进行处理
    for(int i = 0; i < FD_CAPACITY; ++i)
    {
        if(_fd_array[i] != NON_FD && FD_ISSET(_fd_array[i], &rfd))
        {
            // 分为listen套接字和普通套接字来处理
            if(_fd_array[i] == _listenSock) Accepter();
            else Reciever(i);
        }
    }
}

// 数据读取函数
void SelectServer::Reciever(int pos)
{
    char buffer[1024];

    ssize_t n = recv(_fd_array[pos], buffer, 1023, 0);
    if(n > 0)   // 读取成功
    {
        buffer[n] = '\0';
        printf("Recieve message from Client:%s\n", buffer);
    }
    else if(n == 0)   // 对端关闭
    {
        logMessage(DEBUG, "Client closed, fd:%d\n", _fd_array[pos]);
        close(_fd_array[pos]);
        _fd_array[pos] = NON_FD;
    }
    else  // 读取失败
    {
        logMessage(ERROR, "Recv error, errno:%d, errMsg:%s\n", errno, strerror(errno));
    }
}

// 链接接收函数
void SelectServer::Accepter()
{
    std::string cli_ip;
    uint16_t cli_port;   // 客户端ip和端口号
    
    int fd = Sock::Accept(_listenSock, cli_ip, cli_port);

    // 连接获取失败 -- fd < 0 
    if(fd < 0)
    {
        logMessage(ERROR, "Aeecpt fail, errno:%d, errMsg:%s\n", errno, strerror(errno));
    }
    else  // 连接获取成功
    {
        // 将获取到的新连接的fd添加到_fd_array中去
        int index = 0;
        for(; index < FD_CAPACITY; ++index)
        {
            if(_fd_array[index] == NON_FD)
            {
                _fd_array[index] = fd;
                break;
            }

            if(index == FD_CAPACITY)
            {
                logMessage(DEBUG, "_fd_array is already full, insert new fd fail, fd:%d\n", fd);
            }
            else
            {
                logMessage(NORMAL, "Insert new fd success, fd:%d\n", fd);
            }
        }
    }
}

// 打印输出_fd_array
void SelectServer::ShowFdArray()
{
    std::cout << "_fd_array[]: " << std::flush;
    for(const auto fd : _fd_array)
    {
        if(fd != NON_FD) std::cout << fd << " ";
    }
    std::cout << std::endl;
}

2.3 select实现IO多路转接的优缺点

缺点:

  • 每一次调用select之前,都需要重新设置fd_set对象的值,因为调用select会覆盖掉原来的值。
  • 用户向内核传递关注的文件描述符信息时,需要从用户态转换到内核态,存在较大开销。
  • select返回时,由内核将关注的文件描述符的状态告知用户,需要从内核态转换到用户态,存在较大开销。
  • fd_set对象底层是位图结构,位图中能够记录的文件描述符数量存在限制,不能同时关注太多的文件描述符,能够管理的资源受限。

优点:适用于存在大量fd,但是只要少量处于活跃状态的场景。

三. 通过poll实现IO多路转接

3.1 poll接口

函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout)

头文件:#include <poll.h>

函数参数:

  • fds:struct pollfd类型数组,更确切的应当写为struct pollfd fds[]
  • nfds:所关注的文件描述符数量。
  • timeout:最长阻塞等待时间,以秒为单位,如果传-1表示一直阻塞等待直到有fd就绪。

返回值:如果执行成功返回就绪的文件描述符个数,返回0表示等待超时,执行失败返回-1。

下面是struct pollfd的定义式,其中成员fd表示文件描述符,events表示请求事件,即用户告知内核需要关注哪些文件描述符,revents为响应时间,即内核告知用于哪些文件描述符已经就绪。

events是用户传给内核的信息,revents是内核传给用户的信息,他们互不干扰,因此即使这里的的fds依旧是输入输出型参数,也不需要每次调用poll之前重新设定struct poll对象的值,这是poll相对于select的一大优势。

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

events和revents的值及其对于的含义见表3.2,如果为0表示不关注某个fd或这个fd尚未就绪,如果非0,events用于用户告知OS内核需要关注fd的哪些操作(读/写/异常),revents用户OS内核告知用户fd的哪些状态已经就绪。

表4.3 events/revents的值及对应含义
events/revents含义
POLLIN数据(包括普通数据和高优先级数据)可读。
POLLNORMAL普通数据可读。
POLLPRI高优先级数据可读,如TCP带有紧急指针的报文。
POLLOUT数据(包括普通数据和高优先级数据)可写。
POLLOUTNORMAL普通数据可写。
POLLRDHUP对方关闭TCP,或对端关闭写操作。
POLLERR发生错误。
POLLNVAL文件描述符没有打开。

3.2 Poll服务器的实现

Poll服务器的实现与Select服务器的实现十分类似,代码3.1为Poll服务器类的声明,与Select不同的是,其中有一个struct pollfd* _fd_array成员变量,这个成员变量为struct pollfd类型数组,用于告知内核哪些fd需要关心,哪些fd已经就绪。其余包括构造函数、析构函数、服务器运行函数start、就绪文件描述符处理函数Handler、获取客户端连接函数Accepter、读取数据函数Reciever。

代码3.1:PollServer服务器声明(PollServer.hpp头文件)

#pragma once

#include "Sock.hpp"
#include <poll.h>
#include <unistd.h>

static const int FD_CAPACITY = 100;
static const int NON_FD = -1;

class PollServer
{
public:
    PollServer(uint16_t port, const std::string& ip = "");
    void Start();   // 服务器启动函数
    ~PollServer();  // 析构函数

private:
    void Handler();          // 就绪文件描述符处理函数
    void Reciever(int pos);  // 接收信息函数
    void Accepter();         // 接收连接请求
    void ShowFdArray();      // _fd_array打印函数 -- 用于DEBUG

    int _listenSock;    // 监听套接字
    uint16_t _port;     // 服务器进程端口号
    std::string _ip;    // 服务器ip
    struct pollfd* _fd_array;   // 文件描述符序列
};

关于Poll服务器的实现,有以下几点需要注意:

  • Poll是基于TCP协议的,在构造函数中,要获取listen套接字、绑定端口号、设置监听状态。
  • 在start函数中,要死循环调用poll,检查是否有就绪的文件描述符,如果有就调用Handler函数来处理就绪文件描述符。在Handler函数中,遍历_fd_array检查就绪的文件描述符,在后续处理中分为listen文件描述符和普通文件描述符处理。
  • Accepter用于接收连接,新获取的文件描述符要添加到_fd_array中去,Reciever用于读取数据,如果检测到对端关闭,要调用close关闭对应fd,并将其在_fd_array中清除。

代码3.2:PollServer的实现(PollServer.cc源文件)

#include "PollServer.hpp"

// 构造函数
PollServer::PollServer(uint16_t port, const std::string& ip)
    : _listenSock(-1), _port(port), _ip(ip)
    , _fd_array(new pollfd[FD_CAPACITY])
{
    // 获取listen套接字
    _listenSock = Sock::Socket();
    if(_listenSock < 0) {
        exit(2);
    }

    // 绑定端口号
    if(Sock::Bind(_listenSock, _ip, _port) < 0) {
        exit(3);
    }

    // 设置监听状态
    if(Sock::Listen(_listenSock) < 0) {
        exit(4);
    }

    // 初始化pollfd序列
    for(int i = 0; i < FD_CAPACITY; ++i)
    {
        _fd_array[i].fd = -1;
        _fd_array[i].events = _fd_array[i].revents = 0;
    }

    // 对listenSock设置读取关心状态
    _fd_array[0].fd = _listenSock;
    _fd_array[0].events = POLLIN;
}

// 服务器启动函数
void PollServer::Start()
{
    while(true)
    {
        ShowFdArray();
        int n = poll(_fd_array, FD_CAPACITY + 1, -1);
        switch(n)
        {
        case 0:     // 尚无就绪的文件描述符
            logMessage(DEBUG, "No fd has prepared!\n");
            break;
        case -1:    // poll失败
            logMessage(ERROR, "Poll error, errno:%d, errMsg:%s\n", errno, strerror(errno));
            break;
        default:    // 有文件描述符就绪
            Handler();  
            break;
        }
    }
}

// 析构函数
PollServer::~PollServer()
{
    if(_listenSock >= 0) 
        close(_listenSock);
    delete[] _fd_array;
}


// 就绪文件描述符处理函数
void PollServer::Handler()   
{
    // 遍历查找,有哪一个fd处于就绪状态
    for(int i = 0; i < FD_CAPACITY; ++i)
    {
        if(_fd_array[i].fd != NON_FD && _fd_array[i].revents == POLLIN)
        {
            // 分接收连接请求和读取信息两种情况讨论
            if(_fd_array[i].fd == _listenSock) Accepter();
            else Reciever(i);
        }
    }
}

// 接收信息函数
void PollServer::Reciever(int pos)
{
    char buffer[1024];
    ssize_t n = recv(_fd_array[pos].fd, buffer, 1023, 0);

    // 信息读取成功
    if(n > 0)
    {
        buffer[n] = '\0';
        logMessage(NORMAL, "PollServer recieve message success!\n");
        printf("Client Message# %s\n", buffer);
    }
    else if(n == 0)   // 对端关闭
    {
        logMessage(DEBUG, "Client closed, fd:%d", _fd_array[pos].fd);
        close( _fd_array[pos].fd);
        _fd_array[pos].fd = NON_FD;
        _fd_array[pos].events = _fd_array[pos].revents = 0;
    }
    else  // 读取失败
    {
        logMessage(ERROR, "Get message from Client[%d] success, errno:%d, errMsg:%s\n", 
                    _fd_array[pos].fd, errno, strerror(errno));
    }
}

// 接收连接请求
void PollServer::Accepter()
{
    std::string cli_ip;  
    uint16_t cli_port;   
    int fd = Sock::Accept(_listenSock, cli_ip, cli_port);
    if(fd < 0) {
        exit(5);
    }

    // 将新的fd添加到_fd_array中去
    int index = 0;
    for(; index < FD_CAPACITY; ++index)
    {
        // 检查_fd_array的空缺位置
        if(_fd_array[index].fd == NON_FD)
        {
            _fd_array[index].fd = fd;
            _fd_array[index].events = POLLIN;
            break;
        }
    }

    if(index == FD_CAPACITY) { 
        logMessage(DEBUG, "The fd_array has already full, insert new fd fail, fd:%d\n", fd);
    }
    else {
        logMessage(NORMAL, "Insert new fd success, _fd_array[%d]:%d\n", index, fd);
    }
}

// _fd_array打印函数 -- 用于DEBUG
void PollServer::ShowFdArray()
{
    std::cout << "_fd_array[] " << std::flush;
    for(int i = 0; i < FD_CAPACITY; ++i)
    {
        if(_fd_array[i].fd != NON_FD)
            std::cout << _fd_array[i].fd << " ";
    }
    std::cout << std::endl;
}

3.3 poll实现IO多路转接的优缺点

优点:

  • 使用struct pollfd替代select使用fd_set进行传参和返回信息,不需要再每次调用poll之前都对输入输出型参数重新进行设置。
  • 相比于select,poll可以管理的文件描述符没有上限。

缺点:

  • 与select相同,poll在返回后需要轮询检测_fd_array来确定哪个文件描述符就绪,消耗较大。
  • 在向poll传参和poll返回时,需要进行 用户态 -> 内核态、内核态 -> 用户态的切换,频繁进行状态切换会消耗资源。
  • 当管理的fd数目较多时,会降低程序的性能。

四. 总结

  • 相比于阻塞式IO,多路转接能够在有其中一个文件描述符就绪的情况下就进行对应的处理,能大幅提高IO的效率。
  • selet和poll是实现多路转接IO的两种方式。
  • select使用fd_set类型来管理文件描述符,缺点是每次调用select都需要重新设置参数,可且管理的文件描述符数量受限,适用于连接多、但处于活跃状态的连接少的场景。
  • poll相比于select不需要每次调用前都设置参数,且可以管理大量的文件描述符,但在处理就绪文件描述符时依然躲不掉遍历操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/162145.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python uiautomation获取微信内容!聊天记录、聊天列表、全都可获取

Python uiautomation 是一个用于自动化 GUI 测试和操作的库&#xff0c;它可以模拟用户操作来执行各种任务。 通过这个库&#xff0c;可以使用Python脚本模拟人工点击&#xff0c;人工操作界面。本文使用 Python uiautomation 进行微信电脑版的操作。 以下是本次实验的版本号。…

C语言从入门到精通之【其他运算符】

sizeof运算符和size_t sizeof运算符以字节为单位返回运算对象的大小。 例如 &#xff1a;sizeof(int) 打印转换说明&#xff0c;使用C99新增的**%zd转换说明 – 如果编译器不支持%zd&#xff0c;请将其改 成%u或%lu**。 C 语言规定&#xff0c;sizeof 返回 size_t 类型的值…

安装银河麒麟linux系统docker(docker-compose)环境,注意事项(一定能解决,有环境资源)

1:安装docker环境必须使用麒麟的版本如下 2:使用docker-compse up -d启动容器遇到的文件 故障1:如果运行docker-compose up 报“Cannot create redo log files because data files are corrupt or the database was not shut down cleanly after creating the data files”…

基于单片机教室人数实时检测系统仿真及源程序

一、系统方案 1、本设计采用51单片机作为主控器。 2、红外传感器检测进出人数&#xff0c;液晶1602显示。 3、按键最多容纳人数&#xff0c;烟雾报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 void lcd_init() { lcd_write_com(0x38…

为什么选择B+树作为数据库索引结构?

背景 首先&#xff0c;来谈谈B树。为什么要使用B树&#xff1f;我们需要明白以下两个事实&#xff1a; 【事实1】 不同容量的存储器&#xff0c;访问速度差异悬殊。以磁盘和内存为例&#xff0c;访问磁盘的时间大概是ms级的&#xff0c;访问内存的时间大概是ns级的。有个形象…

mongodb使用简单文档

1、mongodb安装与卸载 1.1、安装 python -m pip install pymongo 或 pip install pymongo如果要安装指定版本&#xff1a; python -m pip install pymongo3.5.1对已有的版本进行升级&#xff1a; python -m pip install --upgrade pymongo1.2、卸载 pip uninstall pymongo…

性能测试常见问题总结

01 硬件上的性能瓶颈 指的是CPU、内存、I/O读写速率&#xff0c;磁盘空间方面的问题。 02 网络上的性能瓶颈 指的网络带宽&#xff0c;网络波动&#xff0c;延时&#xff0c;丢包等。 03 应用程序上的性能瓶颈 指的是开发人员新开发出来的应用程序。 04 数据库的性能瓶颈…

Arduino驱动LM35线性温度传感器(温湿度传感器)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 LM35半导体的温度传感器,可以用来对环境温度进行定性的检测。LM35半导体温度传感器是美国国家半导体公司生产的线性温度传感器。其测温范围是-40℃到150℃,灵敏度为10mV/℃,输出电压与温度成正比。

WebGl-Blender:建模 / 想象成形 / Blender概念词汇表 / 快捷键

一、理解Blender 欢迎来到Blender&#xff01;Blender是一款免费开源的3D创作套件。 使用Blender&#xff0c;您可以创建3D可视化效果&#xff0c;例如建模、静态图像&#xff0c;3D动画&#xff0c;VFX&#xff08;视觉特效&#xff09;快照和视频编辑。它非常适合那些受益于…

Visio是什么软件,有哪些好用的Visio平替软件推荐?

1. 什么是Visio&#xff1f; Visio是一款由微软开发的流程图和矢量绘图软件&#xff0c;它可以帮助用户创建各种类型的图表、图示和流程图&#xff0c;从而更好地呈现和传达信息。Visio的功能强大&#xff0c;适用于各种领域&#xff0c;如项目管理、系统设计、流程优化等。…

linux环境下启动应用的不同方式对比分析

大家好&#xff0c;我是G探险者。 平时我们在Linux环境下启动Java应用程序时。可能会选择在前台或后台运行它们。但是这两者启动命令的各种参数含义是什么意思呢&#xff0c;今天我们就来聊聊&#xff0c;并分析一下他们的特点。 1. 前台启动 参数&#xff1a; java: Java程…

我认为除了HelloWorld之外,Python的三大数据转换实例可以作为开始学习Python的入门语言。

Python的三大数据转换实例 一、反转三位数 class Solution:def funtcion(self,number):hint(number/100)tint(number%100/10)zint(number%10)return 100*z10*th if __name____main__:solution Solution()num123new_num solution.funtcion(num)print("输入:{}".fo…

在做题中学习(30):字符串相加

思路&#xff1a; 相加时要转换成对应的数字&#xff0c;所以让字符数字-0 如‘9’-‘0’&#xff08;ASCII&#xff09;57-489 9110&#xff0c;会进1&#xff0c;把进位保存起来&#xff0c;只取0头插到新串里。 头插时要转换对应字符数字&#xff0c;所以让对应的数字‘…

各类软件docker安装

docker Docker 要求 CentOS 系统的内核版本高于 3.10 &#xff0c;通过 uname -r 命令查看你当前的内核版本&#xff1a; uname -r 3.10.0-1062.1.2.el7.x86_64 安装 Docker&#xff1a; 安装 Docker&#xff1a;yum -y install dockerkafka和zookeeper docker pull wurstmei…

Unity 6 是下一个 LTS 版本即将发布

Unity 公司宣布&#xff0c;即将发布 Unity 6&#xff0c;并表示其为下一个长期支持版本 (LTS)。 Unity 在大会上演示了全新的 Unity 6引擎&#xff0c;并通过 Syncy Studios 采用 Unity 6 制作的《幻想王国&#xff08;Fantasy Kingdom&#xff09;》Demo 进行了演示&#xff…

OpenGL_Learn13(材质)

1. 材质 cube.vs #version 330 core layout (location 0) in vec3 aPos; layout (location 0 ) in vec3 aNormal;out vec3 FragPos; out vec3 Normal;uniform mat4 model; uniform mat4 view; uniform mat4 projection;void main() {FragPosvec3(model*vec4(aPos,1.0));Norma…

【MATLAB】史上最全的5种数据插值算法全家桶

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 大家吃一顿火锅的价格便可以拥有5种数据插值算法&#xff0c;绝对不亏&#xff0c;知识付费是现今时代的趋势&#xff0c;而且都是我精心制作的教程&#xff0c;有问题可随时反馈~也可单独获取某一算法的代码&#xff08…

Gem5模拟器学习之旅

安装gem5 模拟器 翻译自官网&#xff08;https://www.gem5.org/documentation/learning_gem5/part1/building/&#xff09; 支持的操作系统和环境 gem5的设计考虑到了Linux环境。我们定期在 Ubuntu 18.04、Ubuntu 20.04 和 Ubuntu 22.04 上进行测试&#xff0c;以确保 gem5 在…

zyj-ha 安装过程及使用部署

一&#xff0e;安装过程排坑 1. 硬件环境准备 排坑 1 首先&#xff0c;服务器至少需要 2 台&#xff0c;每台服务器至少需要 2 块网卡&#xff0c;并且必须有预留 心跳线网口&#xff0c;不能被其他业务占用&#xff0c;否则容易出现脑裂。 2. 通过配置管理工具导入安装包 …