Lesson14 高级IO

news2024/11/25 12:45:17

前言

  • IO = 等待 + 数据拷贝,比如read/recv,write/send
  • 只要在单位事件里,让等的比重减低,IO的效率就越高

五种IO模型

钓鱼小案例

 阻塞式

  • 阻塞式: 张三拿着一根鱼竿,一直在岸边钓鱼,期间一直盯着鱼竿,等待鱼上钩

 非阻塞式轮询式

  • 非阻塞式轮询式: 李四拿着一根鱼竿,在岸边钓鱼,期间一会看手机,一会看鱼竿,等待鱼上钩

 信号驱动

  • 信号驱动: 王五拿着一根鱼竿,并鱼竿上挂着一个铃铛,在岸边钓鱼,期间一直是否有铃铛声,等待鱼上钩

多路转接

  • 多路复用,多路转接: 赵六拿着一堆鱼竿,在岸边钓鱼,期间一直盯着一堆鱼竿,等待鱼上钩

异步IO

  • 异步IO : 田七也钓鱼,但是他叫小王给他钓鱼,不要过程,只要结果

这五种IO效率最高的是 多路复用,多路转接

  • 单位事件里,等的比重最低,I

 如果一个进程/线程要参与IO,我们就称之为同步IO

  • IO = 等 + 拷贝,所谓的"参与",就只有三种情况: 等,拷贝,等 && 拷贝

 fcntl

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
#include <iostream>
#include <cstring>
#include <ctime>
#include <cassert>
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>

#include <sys/time.h>

bool SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL); // 在底层获取当前fd对应的文件读写标志位
    if (fl < 0)
        return false;
    fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 设置非阻塞
    return true;
}

int main()
{
     // 0
     SetNonBlock(0); //只要设置一次,后续就都是非阻塞了

     char buffer[1024];
     while (true)
     {
         sleep(1);
         errno = 0;
         ssize_t s = read(0, buffer, sizeof(buffer) - 1); 
         // 因为将0的文件读写标志位设置成了非阻塞,将不会等待输入了,read返回0,执行else语句
         if (s > 0)
         {
             buffer[s-1] = 0;
             std::cout << "echo# " << buffer << " errno[---]: " << errno << " errstring: " << strerror(errno) << std::endl;
         }
         else
         {
             // 如果失败的errno值是11,就代表其实没错,只不过是底层数据没就绪
             //std::cout << "read \"error\" " << " errno: " << errno << " errstring: " << strerror(errno) << std::endl;

             if(errno == EWOULDBLOCK || errno == EAGAIN)
             {
                 std::cout << "当前0号fd数据没有就绪, 请下一次再来试试吧" << std::endl;
                 continue;
             }
             else if(errno == EINTR)
             {
                 std::cout << "当前IO可能被信号中断,在试一试吧" << std::endl;
                 continue;
             }
             else
             {
                 //进行差错处理
             }
         }
     }
    return 0;
}
  • IO非阻塞,打开的时候,就可以指定非阻塞接口
  • 我们用统一的方式来进行非阻塞设置fcntl()

I/O多路转接之select

 解释timeval类型

select等待多个fd,等待策略可以选择:

  • 阻塞式 nullptr
  • 非阻塞式 {0,0}
  • 可以设置timeout时间,时间内阻塞,时间到,立马返回{5,0}
    • 等待时间内,如果有fd就绪,则timeout输出距离下一次timeout,剩余的时间

 解释fd_set类型

  • fd_set是一个位图结构,就是一个文件描述符集
  • void FD_CLR(int fd,fd_set * set);
  • int FD_ISSET(int fd,fd_set *set);
  • void FD_SET(int fd,fd_set *set);
  • void FD_ZERO(fd_set *set);

比如说: readfds参数

  • 输入时: 用户->内核,比特位中,比特位的位置: 表示文件描述符,比特位的内容表示:是否关心
  • 0000 1010,从右向左: 关心1号和3号文件描述符的
  • 输出时: 内核->用户,比特位中,比特位的文件: 表示文件描述符,比特位的内容表示:是否就绪 
  •  0000 1000,从右向左: 用户可以直接读取3号文件描述符,不会被阻塞 

注意: 用户和内核都会修改同一个位图结构,所以参数用了一次之后,一定需要进行重新设定 

快速编写select代码 

 selectServer.hpp

#ifndef __SELECT_SVR_H__
#define __SELECT_SVR_H__

#include <iostream>
#include <string>
#include <vector>
#include <sys/select.h>
#include <sys/time.h>
#include "Log.hpp"
#include "Sock.hpp"

#define BITS 8
#define NUM (sizeof(fd_set)*BITS)
#define FD_NONE -1

using namespace std;
// select 我们只完成读取,写入和异常不做处理 -- epoll(写完整)
class SelectServer
{
public:
    SelectServer(const uint16_t &port = 8080) : _port(port)
    {
        _listensock = Sock::Socket();
        Sock::Bind(_listensock, _port);
        Sock::Listen(_listensock);
        logMessage(DEBUG,"%s","create base socket success");
        for(int i = 0; i < NUM; i++) {
            _fd_array[i] = FD_NONE;
        }
        // 规定 : _fd_array[0] = _listensock;
        _fd_array[0] = _listensock;
    }

    void Start()
    {
        while (true)
        {
            DebugPrint();

            fd_set rfds;
            FD_ZERO(&rfds);
            int maxfd = _listensock;
            for(int i = 0; i < NUM; i++)
            {
                if(_fd_array[i] == FD_NONE) continue;
                FD_SET(_fd_array[i], &rfds);
                if(maxfd < _fd_array[i]) maxfd = _fd_array[i];
            }
            // rfds未来,一定会有两类sock,listensock,普通sock
            // 我们select中,就绪的fd会越来越多!
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (n)
            {
            case 0:
                // printf("hello select ...\n");
                logMessage(DEBUG, "%s", "time out...");
                break;
            case -1:
                logMessage(WARNING, "select error: %d : %s", errno, strerror(errno));
                break;
            default:
                // 成功的
                logMessage(DEBUG, "get a new link event..."); 
                // 为什么会一直打印连接到来呢?连接已经建立完成,就绪了,但是你没有取走,select要一直通知你!
                HandlerEvent(rfds);
                break;
            }
        }
    }

    ~SelectServer()
    {
        if (_listensock >= 0)
            close(_listensock);
    }
private:
    void HandlerEvent(const fd_set &rfds) // fd_set 是一个集合,里面可能会存在多个sock
    {
        for(int i = 0; i < NUM; i++)
        {
            // 1. 去掉不合法的fd
            if(_fd_array[i] == FD_NONE) continue;
            // 2. 合法的就一定就绪了?不一定
            if(FD_ISSET(_fd_array[i], &rfds))
            {
                //指定的fd,读事件就绪
                // 读事件就绪:连接时间到来,accept
                if(_fd_array[i] == _listensock) Accepter();
                else Recver(i);
            }
        }
    }
    void Accepter()
    {
        string clientip;
        uint16_t clientport = 0;
        // listensock上面的读事件就绪了,表示可以读取了
        // 获取新连接了
        int sock = Sock::Accept(_listensock, &clientip, &clientport); // 这里在进行accept会不会阻塞?不会!
        if(sock < 0)
        {
            logMessage(WARNING, "accept error");
            return;
        }
        logMessage(DEBUG, "get a new line success : [%s:%d] : %d", clientip.c_str(), clientport, sock);
        int pos = 1;
        for(; pos < NUM; pos++){
            if(_fd_array[pos] == FD_NONE) break;
        }
        if(pos == NUM){
            logMessage(WARNING, "%s:%d", "select server already full,close: %d", sock);
            close(sock);
        }else{
            _fd_array[pos] = sock;
        }
    }
    void Recver(int pos)
    {
        // 读事件就绪:INPUT事件到来、recv,read
        logMessage(DEBUG, "message in, get IO event: %d", _fd_array[pos]);
        // 暂时先不做封装, 此时select已经帮我们进行了事件检测,fd上的数据一定是就绪的,即 本次 不会被阻塞
        // 这样读取有bug吗?有的,你怎么保证以读到了一个完整包文呢?
        char buffer[1024];
        int n = recv(_fd_array[pos], buffer, sizeof(buffer)-1, 0);
        if(n > 0){
            buffer[n] = 0;
            logMessage(DEBUG, "client[%d]# %s", _fd_array[pos], buffer);
        }
        else if(n == 0){
            logMessage(DEBUG, "client[%d] quit, me too...", _fd_array[pos]);
            // 1. 我们也要关闭不需要的fd
            close(_fd_array[pos]);
            // 2. 不要让select帮我关心当前的fd了
            _fd_array[pos] = FD_NONE;
        }
        else{
            logMessage(WARNING, "%d sock recv error, %d : %s", _fd_array[pos], errno, strerror(errno));
            // 1. 我们也要关闭不需要的fd
            close(_fd_array[pos]);
            // 2. 不要让select帮我关心当前的fd了
            _fd_array[pos] = FD_NONE;
        }
    }

    void DebugPrint()
    {
        cout << "_fd_array[]: ";
        for(int i = 0; i < NUM; i++)
        {
            if(_fd_array[i] == FD_NONE) continue;
            cout << _fd_array[i] << " ";
        }
        cout << endl;
    }
private:
    uint16_t _port;
    int _listensock;
    int _fd_array[NUM];
};

#endif

select优缺点

  • 优点 --任何一个多路转接方案,都具备:
    • 效率高
    • 应用场景: 有大量的连接,但是只有少量是活跃的!,省资源
  •  缺点:
    • 为了维护第三方数组,select服务器会充满大量的遍历,OS底层帮我们关心fd的时候,也要遍历
    • 每一次都要对select输出参数进行重新设定
    • 能够同时管理的fd的个数是有上限的
    • 因为几乎每一个参数都是输入输出型的,select一定会频繁的进行用户到内核,内核到用户的参数数据拷贝
    • 编码比较复杂

 快速编写 poll代码 

参数说明

  • fds是一个poll函数监听的结构列表. 每一个元素中,
    包含了三部分内容:
    文件描述符, 监听的事件集合, 返回的事件集合.
  • nfds表示fds数组的长度.
  • timeout表示poll函数的超时时间, 单位是毫秒(ms)

 eventsrevents的取值:

 返回结果

  • 返回值小于0, 表示出错;
  • 返回值等于0, 表示poll函数等待超时;
  • 返回值大于0, 表示poll由于监听的文件描述符就绪而返回.

 pollServer.hpp

#ifndef __POLL_SVR_H__
#define __POLL_SVR_H__

#include <iostream>
#include <string>
#include <vector>
#include <poll.h>
#include <sys/time.h>
#include "Log.hpp"
#include "Sock.hpp"

#define FD_NONE -1

using namespace std;
// select 我们只完成读取,写入和异常不做处理 -- epoll(写完整)
class PollServer
{
public:
    static const int nfds = 100;
public:
    PollServer(const uint16_t &port = 8080) : _port(port), _nfds(nfds)
    {
        _listensock = Sock::Socket();
        Sock::Bind(_listensock, _port);
        Sock::Listen(_listensock);
        logMessage(DEBUG,"%s","create base socket success");

        _fds = new struct pollfd[_nfds];
        for(int i = 0; i < _nfds; i++) {
            _fds[i].fd = FD_NONE;
            _fds[i].events = _fds[i].revents = 0;
        }
        _fds[0].fd = _listensock;
        _fds[0].events = POLLIN;

        _timeout = 1000;
    }

    void Start()
    {
        while (true)
        {
            int n = poll(_fds, _nfds, _timeout);
            switch (n)
            {
            case 0:
                logMessage(DEBUG, "%s", "time out...");
                break;
            case -1:
                logMessage(WARNING, "select error: %d : %s", errno, strerror(errno));
                break;
            default:
                // 成功的
                HandlerEvent();
                break;
            }
        }
    }

    ~PollServer()
    {
        if (_listensock >= 0)
            close(_listensock);
        if (_fds) delete [] _fds;
    }
private:
    void HandlerEvent() // fd_set 是一个集合,里面可能会存在多个sock
    {
        for(int i = 0; i < _nfds; i++)
        {
            // 1. 去掉不合法的fd
            if(_fds[i].fd == FD_NONE) continue;
            // 2. 合法的就一定就绪了?不一定
            if(_fds[i].revents & POLLIN)
            {
                //指定的fd,读事件就绪
                // 读事件就绪:连接事件到来,accept
                if(_fds[i].fd == _listensock) Accepter();
                else Recver(i);
            }
        }
    }
    void Accepter()
    {
        string clientip;
        uint16_t clientport = 0;
        // listensock上面的读事件就绪了,表示可以读取了
        // 获取新连接了
        int sock = Sock::Accept(_listensock, &clientip, &clientport); // 这里在进行accept会不会阻塞?不会!
        if(sock < 0)
        {
            logMessage(WARNING, "accept error");
            return;
        }
        logMessage(DEBUG, "get a new line success : [%s:%d] : %d", clientip.c_str(), clientport, sock);
        int pos = 1;
        for(; pos < _nfds; pos++){
            if(_fds[pos].fd == FD_NONE) break;
        }
        if(pos == _nfds){
            // 对struct pollfd进行自动扩容
            logMessage(WARNING, "%s:%d", "poll server already full,close: %d", sock);
            close(sock);
        }else{
            _fds[pos].fd = sock;
            _fds[pos].events = POLLIN;
        }
    }
    void Recver(int pos)
    {
        // 读事件就绪:INPUT事件到来、recv,read
        logMessage(DEBUG, "message in, get IO event: %d", _fds[pos]);
        // 暂时先不做封装, 此时select已经帮我们进行了事件检测,fd上的数据一定是就绪的,即 本次 不会被阻塞
        // 这样读取有bug吗?有的,你怎么保证以读到了一个完整包文呢?
        char buffer[1024];
        int n = recv(_fds[pos].fd, buffer, sizeof(buffer)-1, 0);
        if(n > 0){
            buffer[n] = 0;
            logMessage(DEBUG, "client[%d]# %s", _fds[pos].fd, buffer);
        }
        else if(n == 0){
            logMessage(DEBUG, "client[%d] quit, me too...", _fds[pos].fd);
            // 1. 我们也要关闭不需要的fd
            close(_fds[pos].fd);
            // 2. 不要让select帮我关心当前的fd了
            _fds[pos].fd = FD_NONE;
            _fds[pos].events = 0;
        }
        else{
            logMessage(WARNING, "%d sock recv error, %d : %s", _fds[pos].fd, errno, strerror(errno));
            // 1. 我们也要关闭不需要的fd
            close(_fds[pos].fd);
            // 2. 不要让select帮我关心当前的fd了
            _fds[pos].fd = FD_NONE;
            _fds[pos].events = 0;
        }
    }

    void DebugPrint()
    {
        cout << "_fd_array[]: ";
        for(int i = 0; i < _nfds; i++)
        {
            if(_fds[i].fd == FD_NONE) continue;
            cout << _fds[i].fd << " ";
        }
        cout << endl;
    }
private:
    uint16_t _port;
    int _listensock;
    struct pollfd *_fds;
    int _nfds;
    int _timeout;
};

#endif

poll的优点:

  • 效率高
  • 有大量的连接,但是只有少量的是活跃的,节省资源
  • 输入输入参数分离,不需要进行大量的重置
  • poo参数级别没有可以管理fd的上限

 poll的缺点:

  • poll依旧需要不少的遍历,在用户层检测时间就绪,与内核检测fd就绪,都是一样的(用户还是要维护数组)
  • poo需要内核到用户的拷贝--少不了的
  • poll的代码也比较复杂--比select容易

小结

  1. 无论是select还是poll,都是需要用户自己维护一个数组,来进行保存fd特定事件的 -- 成本 -- 用户程序员

  2. select or poll都要遍历

  3. select or poll 工作模式

    1. 通过select or poll,用户告诉内核,你要帮我关系那些fd上的那些event

    2.  通过select or poll 返回,内核告诉用户,那些fd上的那些event已经发生了

I/O多路转接之epoll

epoll的相关系统调用

epoll_create

int epoll_create(int size);

创建一个epoll的句柄.

  •  自从linux2.6.8之后,size参数是被忽略的.
  • 用完之后, 必须调用close()关闭

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event) ; 

epoll的事件注册函数 

  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
  • 第一个参数是epoll_create()的返回值(epoll的句柄).
  • 第二个参数表示动作,用三个宏来表示.
  • 第三个参数是需要监听的fd.
  • 第四个参数是告诉内核需要监听什么事

第二个参数的取值:

  • EPOLL_CTL_ADD :注册新的fd到epfd中;
  • EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL :从epfd中删除一个fd

struct epoll_event结构如下

typedef union epoll_data
{
	void* ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;

struct epoll_event
{
	uint32_t events;// Epoll events
	epoll_data_t data;// user data varibale
}__EPOLL_PACKED;
events可以是以下几个宏的集合
  •  EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里.

epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 

收集在epoll监控的事件中已经发送的事件

  • 参数events是分配好的epoll_event结构体数组.
  • epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存).
  • maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
  • 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
  • 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败 

epoll的工作原理 

  •  红黑树的时候,是要用key值的,文件描述符

  • 用户只需要设置关系,获取结果即可,不用在关心任何对fd与event管理

  • epoll比select和poll的优势是,不需要维护数组,不需要遍历,所以高效

  •  底层只要有fd就绪了,OS自己会给我构建节点,连入到就绪队列中
    上层只需要不断的从就绪队列中将数据拿走,就完成了获取就绪事件的任务
    • 生产者消费者模型!共享资源 - epoll已经保证所有的epoll接口都是线程安全的
  • 如果底层没有就绪事件,我们的上层就只能阻塞等待

Epoll.hpp

#pragma once

#include <iostream>
#include <sys/epoll.h>
#include <unistd.h>

class Epoll
{
public:
    static const int gsize = 256;
public:
    static int CreateEpoll()
    {
        int epfd = epoll_create(gsize);
        if(epfd > 0) return epfd;
        exit(5);
    }
    static bool CtlEpoll(int epfd, int oper, int sock, uint32_t events)
    {
        struct epoll_event ev;
        ev.events = events;
        ev.data.fd = sock;
        int n = epoll_ctl(epfd, oper, sock, &ev);
        return n == 0;
    }
    static int WaitEpoll(int epfd, struct epoll_event revs[], int num, int timeout)
    {
        // 细节1:如果底层就绪的sock非常多,revs承装不下,怎么办??不影响!一次拿不完,就下一次再拿
        // 细节2:关于epoll_wait的返回值问题:有几个fd上的事件就绪,就返回几,epoll返回的时候,会将所有
        //       就绪的event按照顺序放入到revs数组中!一共有返回值个!
        return epoll_wait(epfd, revs, num, timeout);
    }
};

EpollServer.hpp

#ifndef __EPOLL_SERVER_HPP__
#define __EPOLL_SERVER_HPP__

#include <iostream>
#include <string>
#include <functional>
#include <cassert>
#include "Log.hpp"
#include "Sock.hpp"
#include "Epoll.hpp"

namespace ns_epoll
{
    const static int default_port = 8080;
    const static int gnum = 64;

    //只处理读取
    class EpollServer
    {
        using func_t = std::function<void(std::string)>;
    public:
        EpollServer(func_t HandlerRequest, const int &port = default_port) 
        : _port(port), _revs_num(gnum), _HandlerRequest(HandlerRequest)
        {
            // 0. 申请对应的空间
            _revs = new struct epoll_event[_revs_num];
            // 1. 创建listensock
            _listensock = Sock::Socket();
            Sock::Bind(_listensock, _port);
            Sock::Listen(_listensock);
            // 2. 创建epoll模型
            _epfd = Epoll::CreateEpoll();
            logMessage(DEBUG, "init success, listensock: %d, epfd: %d", _listensock, _epfd); // 3, 4
            // 3. 将listensock,先添加到epoll中,让epoll帮我们管理起来
            if (!Epoll::CtlEpoll(_epfd, EPOLL_CTL_ADD, _listensock, EPOLLIN))
                exit(6);
            logMessage(DEBUG, "add listensock to epoll success."); // 3, 4
        }
        void Accepter(int listensock)
        {
            std::string clientip;
            uint16_t clientport;
            int sock = Sock::Accept(listensock, &clientip, &clientport);
            if(sock < 0)
            {
                logMessage(WARNING, "accept error!");
                return;
            }
            // 能不能直接读取?不能,因为你并不清楚,底层是否有数据!
            // 将新的sock,添加给epoll
            if (!Epoll::CtlEpoll(_epfd, EPOLL_CTL_ADD, sock, EPOLLIN)) return;
            logMessage(DEBUG, "add new sock : %d to epoll success", sock);   
        }
        void Recver(int sock)
        {
            // 1. 读取数据
            char buffer[10240];
            ssize_t n = recv(sock, buffer, sizeof(buffer)-1, 0);
            if(n > 0)
            {
                //假设这里就是读到了一个完整的报文 // 如何保证??
                buffer[n] = 0;
                _HandlerRequest(buffer); // 2. 处理数据
            }
            else if(n == 0)
            {
                // 1. 先在epoll中去掉对sock的关心
                bool res = Epoll::CtlEpoll(_epfd, EPOLL_CTL_DEL, sock, 0);
                assert(res);
                (void)res;
                // 2. 在close文件
                close(sock);
                logMessage(NORMAL, "client %d quit, me too...", sock);
            }
            else
            {
                // 1. 先在epoll中去掉对sock的关心
                bool res = Epoll::CtlEpoll(_epfd, EPOLL_CTL_DEL, sock, 0);
                assert(res);
                (void)res;
                // 2. 在close文件
                close(sock);
                logMessage(NORMAL, "client recv %d error, close error sock", sock);
            }
        }
        void HandlerEvents(int n)
        {
            assert(n > 0);
            for(int i = 0; i < n; i++)
            {
                uint32_t revents = _revs[i].events;
                int sock = _revs[i].data.fd;
                // 读事件就绪
                if(revents & EPOLLIN)
                {
                    if(sock == _listensock) Accepter(_listensock); // 1. listensock 就绪
                    else Recver(sock);                             // 2. 一般sock 就绪 - read   
                }
                if(revents & EPOLLOUT)
                {
                    //TODO?
                }
            }
        }
        void LoopOnce(int timeout)
        {
            int n = Epoll::WaitEpoll(_epfd, _revs, _revs_num, timeout);
            //if(n == _revs_num) //扩容
            switch (n)
            {
            case 0:
                logMessage(DEBUG, "timeout..."); // 3, 4
                break;
            case -1:
                logMessage(WARNING, "epoll wait error: %s", strerror(errno));
                break;
            default:
                // 等待成功
                logMessage(DEBUG, "get a event");
                HandlerEvents(n);
                break;
            }
        }
        void Start()
        {
            int timeout = -1;
            while(true)
            {
                LoopOnce(timeout);
            }
        }
        ~EpollServer()
        {
            if (_listensock >= 0)
                close(_listensock);
            if (_epfd >= 0)
                close(_epfd);
            if (_revs)
                delete[] _revs;
        }

    private:
        int _listensock;
        int _epfd;
        uint16_t _port;
        struct epoll_event *_revs;
        int _revs_num;
        func_t _HandlerRequest;
    };

} // namespace name

#endif

 main.cc

#include "EpollServer.hpp"
#include <memory>

using namespace std;
using namespace ns_epoll;

void change(std::string request)
{
    //完成业务逻辑
    std::cout << "change : " << request.c_str() << std::endl;
}

int main()
{
    unique_ptr<EpollServer> epoll_server(new EpollServer(change));
    epoll_server->Start();

    return 0;
}

epoll的工作模式

  • LT模式: 有数据就会一直通知
  • ET模式: 有数据就只会通知一次

为什么ET模式更高效:

  •  更少的返回次数
  • ET模式会倒逼程序员尽快将接收缓冲区中的数据全部取走,
    应用层尽快的取走了缓冲区的数据,那么在单位时间下,该模式下工作的服务器,
    就可以给发送方一个更大的接收窗口,所以对方就可以有更大的滑动窗口,一次向我们发送更多的数据,提高IO吞吐

程序员要一次把数据全部拿走,就必须一直循环读取,在最后一次正常读取完毕,我们势必还要进行下一次读取(无法确定是否读取完成),则必须把sock设置成非阻塞

而为了保证正确读取,则每一个sock都需要有属于自己的缓冲区

相关代码:Reactor 

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

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

相关文章

Weblogic RCE合集

文章目录 CVE-2023-21839(T3/IIOP JNDI注入)前言漏洞简单分析漏洞复现防护措施 CVE-2020-2551(RMI-IIOP RCE)漏洞简单分析漏洞复现防护措施 CVE-2017-3506(wls-wsat组件XMLDecoder反序列化漏洞)漏洞简单分析漏洞复现防护措施 CVE-2020-14882&CVE-2020-14883漏洞简单分析 CV…

2023.05.11 c高级 day3

编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的和当前用户说“hello 用户名”显示您的机器名 hostname显示上一级目录中的所有文件的列表显示变量PATH和HOME的值显示磁盘使用情况用id命…

算法修炼之练气篇——练气十五层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

来领略一下带头双向循环链表的风采吧

&#x1f349; 博客主页&#xff1a;阿博历练记 &#x1f4d6;文章专栏&#xff1a;数据结构与算法 &#x1f68d;代码仓库&#xff1a;阿博编程日记 &#x1f339;欢迎关注&#xff1a;欢迎友友们点赞收藏关注哦 文章目录 &#x1f344;前言&#x1f37c;双向循环链表&#x1…

Qt使用星空图作为窗口背景,点击键盘的WASD控制小飞机在上面移动。

事件函数的使用依托于Qt的事件机制&#xff0c;一个来自于外部事件的传递机制模型如下所示 信号槽虽然好用&#xff0c;但是无法包含所有的情况&#xff0c;事件函数可以起到对信号槽无法覆盖的一些时机进行补充&#xff0c;事件函数的使用无需连接。 常用的事件函数如下所示。…

设计模式5—抽象工厂模式

5.抽象工厂模式 概念 抽象工厂模式&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定他们具体的类。抽象工厂又称为Kit模式&#xff0c;属于对象创建型模式。 抽象工厂可以将统一产品族的单独工厂封装起来&#xff0c;在正常使用中&#xff0c…

计算机网络笔记——网络层、传输层、应用层(方老师408课程)(持续更新)

文章目录 前言网络层网络层提供的两种服务网际协议——IP虚拟互联网络IP数据报格式逐一理解整体理解IP数据报分片与长度精算 IP地址IP地址概述分类的IP地址——ABCDE分类IP的子网划分不分类的IP地址——CIDRIP地址总结 IP分组的转发网际控制报文协议——ICMP下一代网络协议——…

我用 ChatGPT 干的 18 件事!【人工智能中文站创始人:mydear麦田访谈】

新建了一个网站 https://ai.weoknow.com/ 每天给大家更新可用的国内可用chatGPT 你确定你可以使用ChatGPT吗&#xff1f; 今天我整理了18种ChatGPT的使用方法&#xff0c;让大家看看你可以使用哪些。 1.语法修正 2.文本翻译 3.语言转换 4.代码解释 5.修复代码错误 6.作为百科…

初识HTML的基础知识点!!!

初识HTML&#xff01;&#xff01;&#xff01; 一、系统构架 1.B/S构架 &#xff08;1&#xff09;B/S构架&#xff08;Browser / Server) 就是&#xff08;浏览器/服务器的交互形式&#xff09; Browser支持HTML、CSS、JavaScript &#xff08;2&#xff09;优缺点 优点…

UI--基本组件

目录 1. Designer 设计师 2. Layout 布局 3. 基本组件 3.1 QWidget 3.2 ui指针 3.3 QLabel 标签&#xff08;掌握&#xff09; 示例代码&#xff1a; dialog.h dialog.cpp 3.4 QAbstractButton 按钮类&#xff08;掌握&#xff09; 示例代码&#xff1a; dialog.ui dialog.h di…

【MyBaits】SpringBoot整合MyBatis之动态SQL

目录 一、背景 二、if标签 三、trim标签 四、where标签 五、set标签 六、foreach标签 一、背景 如果我们要执行的SQL语句中不确定有哪些参数&#xff0c;此时我们如果使用传统的就必须列举所有的可能通过判断分支来解决这种问题&#xff0c;显示这是十分繁琐的。在Spring…

linux查看服务端口号、查看端口(netstat、lsof)以及PID对应服务

linux查看服务端口号、查看端口&#xff08;netstat、lsof&#xff09; netstat - atulnp会显示所有端口和所有对应的程序&#xff0c;用grep管道可以过滤出想要的字段 -a &#xff1a;all&#xff0c;表示列出所有的连接&#xff0c;服务监听&#xff0c;Socket资料 -t &…

说服审稿人,只需牢记这 8 大返修套路!

本文作者&#xff1a;雁门飞雪 如果说科研是一场修炼&#xff0c;那么学术界就是江湖&#xff0c;投稿就是作者与审稿人或编辑之间的高手博弈。 在这一轮轮的对决中&#xff0c;有时靠的是实力&#xff0c;有时靠的是技巧&#xff0c;然而只有实力和技巧双加持的作者才能长久立…

Qt--项目打包

项目打包 一款正常的软件产品应该在任何的计算机中运行&#xff0c;不需要单独安装Qt的开发环境&#xff0c;因此需要把之前的项目打包成一个安装包。 1. 设置应用图标 设置应用程序图标的操作步骤如下所示。 1. 下载一个图标图片&#xff0c;格式要求png。&#xff08;png包含…

学习Python的day.13

输入和输出 一、输入 标准输入&#xff1a;从键盘输入 input(promptNone) # prompt: 输入的提示符,可以为空 Read a string from standard input --- 译为&#xff1a;从标准输入读入一个字符串&#xff0c;输入读取的一定是字符串&#xff0c;返回值就是一个字符串 那我们…

基于知识图谱的个性化学习资源推荐系统的设计与实现(论文+源码)_kaic

摘 要 最近几年来&#xff0c;伴随着教育信息化、个性化教育和K12之类的新观念提出,一如既往的教育方法向信息化智能化的转变&#xff0c;学生群体都对这种不受时间和地点约束的学习方式有浓厚的兴趣。而现在市面上存在的推荐系统给学生推荐资料时不符合学生个人对知识获取的…

多态与虚函数

多态与虚函数 多态的引入多态与虚函数多态编译时多态运行时多态 多态的原理静态联编和动态联编 多态的引入 学过C继承的话应该都知道在继承中存在一种菱形继承&#xff0c;假设存在一个类&#xff08;person&#xff09;&#xff0c;其派生出两个子类&#xff0c;分别是studen…

Template Method模式

文章目录 &#x1f4a1;前言分类优点 &#x1f4a1;问题引入&#x1f4a1;概念&#x1f4a1;例子&#x1f4a1;总结 &#x1f4a1;前言 此文是第一篇讲解设计模式的文章&#xff0c;而笔者我又不想另起一篇来概述设计模式的分类&#xff0c;作用&#xff0c;以及优点&#xff…

MySQL笔记(四) 函数、变量、存储过程、游标、索引、存储引擎、数据库维护、指定字符集、锁机制

MySQL笔记&#xff08;四&#xff09; 文章目录 MySQL笔记&#xff08;四&#xff09;函数文本处理函数日期和时间处理函数数值处理函数类型转换函数流程控制函数自定义函数基本语法 局部变量全局变量聚集函数 aggregate functionDISTINCT 存储过程为什么要使用使用创建 删除建…

调用api实现ChatGPT接口余额查询

先打个广告&#xff1a; 推荐一款不用科学上网就可以使用的ChatGPT工具&#xff1a;智能聊天助手 体验版入口&#xff1a;智能聊天助手体验版 在ChatGPT官网可以查询接口使用额度&#xff0c;但是官方并没有提供相应的API给开发者调用。但是可以通过破解的方式找到它的API。方法…