IO多路转接之select和poll

news2025/1/19 17:14:52

目录

一. 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不需要每次调用前都设置参数,且可以管理大量的文件描述符,但在处理就绪文件描述符时依然躲不掉遍历操作。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

App测试入门

App测试基础知识 App测试&#xff0c;是指对移动应用软件&#xff08;如手机app、平板app等&#xff09;进行全面和系统的测试&#xff0c;以确保其功能、性能、安全性、稳定性、兼容性等方面能满足用户的使用需求和期望。 App常见运行系统 IOS系统&#xff1a; IOS系统是苹果公…

CUDA编程一、基本概念和cuda向量加法

目录 一、cuda编程的基本概念入门 1、GPU架构和存储结构 2、cuda编程模型 3、cuda编程流程 二、cuda向量加法实践 1、代码实现 2、代码运行和结果 有一段时间对模型加速比较感兴趣&#xff0c;其中的一块儿内容就是使用C和cuda算子优化之类一起给模型推理提速。之前一直…

适用于 Windows 的 10 个最佳视频转换器:快速转换高清视频

您是否遇到过由于格式不兼容而无法在您的设备上播放视频或电影的情况&#xff1f;您想随意播放从您的相机、GoPro 导入的视频&#xff0c;还是以最合适的格式将它们上传到媒体网站&#xff1f;您的房间里是否有一堆 DVD 光盘&#xff0c;想将它们转换为数字格式以便于播放&…

算法 LeetCode 题解 | 有效的括号

大家好&#xff0c;我是木川 一、题目描述 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。…

掌握Shell:从新手到编程大师的Linux之旅

1 shell介绍 1.1 shell脚本的意义 1.记录命令执行的过程和执行逻辑&#xff0c;以便以后重复执行 2.脚本可以批量处理主机 3.脚本可以定时处理主机 1.2 脚本的创建 #!/bin/bash # 运行脚本时候执行的环境1.3 自动添加脚本说明信息 /etc/vimrc # vim主配置文件 ~/.vimrc # 该…

Java之线程的概念及方法的学习

线程创建 方法一 直接使用Thread public class demo {public static void main(String[] args) {new Thread(){Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}.start();System.out.println(Thread.currentThread().getName());} } main…

深信服AC应用控制技术

拓扑图 目录 拓扑图 一.上班时间不允许使用qq(假设上班时间是上午9到12&#xff0c;下午14到18) 1.新增上班时间不允许使用qq访问权限策略 2.将策略应用到组&#xff0c;例如修仙部 3.验证 上班时间发现登录不了 下班时间可以登录 二.上班时间不允许访问视频网站(假设上班时…

2023年优化算法之之霸王龙优化算法(TROA),原理公式详解,附matlab代码

霸王龙优化算法&#xff08;Tyrannosaurus optimization&#xff0c;TROA&#xff09;是一种新的仿生优化算法&#xff0c;该算法模拟霸王龙的狩猎行为&#xff0c;具有搜索速度快等优势。该成果于2023年发表在知名SCI期刊e-Prime-Advances in Electrical Engineering, Electro…

Go vs Rust:文件上传性能比较

在本文中&#xff0c;主要测试并比较了Go—Gin和Rust—Actix之间的多部分文件上传性能。 设置 所有测试都在配备16G内存的 MacBook Pro M1 上执行。 软件版本为&#xff1a; Go v1.20.5Rust v1.70.0 测试工具是一个基于 libcurl 并使用标准线程的自定义工具&#xff0c;能…

【双指针】复写0

复写0 1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上…

ZYNQ_project:LCD

模块框图&#xff1a; 时序图&#xff1a; 代码&#xff1a; /* // 24h000000 4324 9Mhz 480*272 // 24h800000 7084 33Mhz 800*480 // 24h008080 7016 50Mhz 1024*600 // 24h000080 4384 33Mhz 800*480 // 24h800080 1018 70Mhz 1280*800 */ module rd_id(i…

html网页设计 01基础标签

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body> <!-- 标题标签 h1最大 --><h1>最大标签</h1><h2>二级标签</h2><h3>三级标签</h3><…

JavaScript管理HTMLDOM元素(增删改查)

本文主要讲解JavaScript如何通过管理HTML上的DOM元素&#xff0c;其中包括如何查询、创建、修改以及删除具体功能和源码讲解。 增加 首先我们准备一个HTML框架和简单CSS样式&#xff0c;我对其中元素作用和关系进行一个简单说明。 <!DOCTYPE html> <html><he…

OpenCV C++ 图像 批处理 (批量调整尺寸、批量重命名)

文章目录 图像 批处理(调整尺寸、重命名)图像 批处理(调整尺寸、重命名) 拿着棋盘格,对着相机变换不同的方角度,采集十张以上(以10~20张为宜);或者棋盘格放到桌上,拿着相机从不同角度一通拍摄。 以棋盘格,第一个内焦点为坐标原点,便于计算世界坐标系下三维坐标; …

提升 Python 执行速度:Codon、C/C++、Rust、Numba(JIT)、Taichi、Nuitka、MatxScript

几种流行的 Python 性能加速方案对比&#xff1a;https://zhuanlan.zhihu.com/p/604519817 对于一般通用场景用户&#xff0c;对性能没有那么强烈的诉求&#xff0c;紧跟官方步伐&#xff0c;升级到最新版本的 Python 既可&#xff0c;或者使用 PyPy。Numba、Codon、Taichi 等这…

IoC DI

Spring 的两大核心思想 : IoC 和 AOP 我们要将对象的控制权交给Spring ,我们就需要告诉 Spring 哪些对象是需要帮我们进行创建的,这里有两类注解可以实现 : 类注解(Controller Service Repository Component Configuration)和方法注解(Bean) 这五大注解都表示把这个对象交给…

【MySQL】InnoDB和MyISAM区别详解(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

【Java】ArrayList和LinkedList使用不当,性能差距会如此之大!

文章目录 前言源码分析ArrayList基本属性初始化新增元素删除元素遍历元素 LinkedList实现类基本属性节点查询新增元素删除元素遍历元素 分析测试 前言 在面试的时候&#xff0c;经常会被问到几个问题&#xff1a; ArrayList和LinkedList的区别&#xff0c;相信大部分朋友都能回…

C++之set/multise容器

C之set/multise容器 set基本概念 set构造和赋值 #include <iostream> #include<set> using namespace std;void PrintfSet(set<int>&s) {for(set<int>::iterator it s.begin();it ! s.end();it){cout<<*it<<" ";}cout&l…

保姆级 | Nginx编译安装

0x00 前言 Nginx 是一个 HTTP 和反向代理服务器&#xff0c; 邮件代理服务器&#xff0c; 和通用 TCP/UDP 代理服务器&#xff0c; 最初由伊戈尔西索耶夫&#xff08;Igor Sysoev&#xff09;撰写。采用编译安装可以根据自身需要自定义配置&#xff0c;让服务器有更高的安全性和…