epoll实现(ET)

news2024/11/15 17:35:44

说来惭愧,编程也有一年半的时间了,今天在实现epoll这个多路转接的代码时,写了个bug,个人认为还是很不好发现的一个错误。

首先,在这里先给大家说说多路转接。所谓的多路转接就是在IO的时候提高了效率,原来我们在写套接字编程的时候或是管道等等,都需要等对方,而现在虽然也要等,但是单位时间内的效率是很高的。像管道,他只能等一个文件描述符,而多路转接可以等多个文件描述符。所以,这里明显提高了效率。

大概知道了什么是多路转接以后,在整体给大家说说思路,其实很简单,就是在客户端不断地链接服务端的时候,我们只需要这些套接字依次放到多路转接的等待时间就绪的数组中就行。这就是思路。很简单,但是实现起来的时候还是有些难度的。

先跟大家分享一下我在这里犯的几个错误吧!希望大家不要犯这种错误!

首先就是:为了提高代码的可读性我们一般都会把每个模块的代码强解耦,这样不仅是可读性高,而且在出现错误的时候很好改,只需要改某一个错误的模块就行。而这样的行为就会让我们不断地封装,这里就出现了以前总说定义不要再头文件。

其次,就是智能指针,像epoll中为了正确的读写,我们给每一个sock都配对了一个属于自己的缓冲区,而此时为了更好的管理我们new出来的缓冲区,我们最好用智能指针,但是这里应该用什么呢?

后面会说。先来看看代码吧!

#pragma once
#include <memory>
#include <unordered_map>
#include <cassert>
#include "sock.hpp"
#include "epoll_operator.hpp"
#define NUM 256
// 声明函数与类
struct IO;
class EpollSock;
void handevent(epoll_event *event, int num, int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Func(int sock, std::string &request, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Accept(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Recv(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Write(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
bool setblock(int sock);
using fun_c = std::function<void(epoll_event *, int, int, std::unordered_map<int, std::unique_ptr<IO> &> &)>;
using fun_t = std::function<void(int, std::string &, std::unordered_map<int, std::unique_ptr<IO> &> &)>;
struct IO
{
    std::string _inbuffer;  // 读缓冲区
    std::string _outbuffer; // 写缓冲区
    Sock *p;                // 服务器
    EpollOper *ep;          // epoll操作
    EpollSock *esp;         // epoll服务器
};
class EpollSock
{
public:
    EpollSock() : _handl(handevent)
    {
        // 创建套接字
        _serve.Bind();
        _sock = _serve.Listen();
        // 插入到epoll中
        _epoll.EpollCtl(EPOLL_CTL_ADD, _sock, EPOLLET | EPOLLIN);
        // 建立对应关系
        IO *p = new IO;
        static std::unique_ptr<IO> s(p);
        s->p = &_serve;
        s->ep = &_epoll;
        s->esp = this;
        std::pair<int, std::unique_ptr<IO> &> op(_sock, s);
        _mapsock.insert(op);
    }
    void Change(int sock,bool answe)
    {
        if(answe)
        {
            _epoll.EpollCtl(EPOLL_CTL_MOD, sock, EPOLLIN | EPOLLOUT | EPOLLET);
        }
        else
        {
            _epoll.EpollCtl(EPOLL_CTL_MOD, sock, EPOLLIN | EPOLLET);
        }
    }
    void start()
    {
        while (true)
        {
            int n = _epoll.EpollWit(_arr_event, NUM, 1000);
            switch (n)
            {
            case -1:
                std::cout << "epoll_wait fail:" << strerror(errno) << std::endl;
                break;
            case 0:
                std::cout << "timeout ..." << std::endl;
                break;
            default:
                _handl(_arr_event, n, _sock, _mapsock);
                break;
            }
        }
    }

private:
    int _sock;
    Sock _serve;
    fun_c _handl;
    EpollOper _epoll;
    epoll_event _arr_event[NUM];
    std::unordered_map<int, std::unique_ptr<IO> &> _mapsock;
};
#pragma once
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/epoll.h>
//封装epoll函数
class EpollOper
{
public:
    EpollOper() : _flag(126)
    {
        // 创建epfd
        _epfd = epoll_create(_flag);
        if (_epfd < 0)
        {
            std::cout << "epoll_create fail:" << strerror(errno) << std::endl;
            exit(6);
        }
    }
    void EpollCtl(int oper, int sock, uint32_t event)
    {
        epoll_event node;
        node.data.fd = sock;
        node.events = event;
        int ret = epoll_ctl(_epfd, oper, sock, &node);
        if (ret < 0)
        {
            std::cout << "epoll_ctl fail:" << strerror(errno) << std::endl;
            exit(7);
        }
    }
    int EpollWit(epoll_event *event, int maxnum, int timeout)
    {
        int n = epoll_wait(_epfd, event, maxnum, timeout);
        if (n < 0)
        {
            std::cout << "epoll_wait fail:" << strerror(errno) << std::endl;
            exit(8);
        }
        return n;
    }
    ~EpollOper()
    {
        if(_epfd>0) close(_epfd);
    }
private:
    int _epfd;
    const int _flag;
};
#include "eopllsever.hpp"
void Accept(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
    // 找到对应的节点
    auto cur = ptr.find(sock);
    assert(cur != ptr.end());
    // 构建映射关系
    IO *pp = new IO;
    static std::unique_ptr<IO> s(pp);
    s->p = cur->second->p;
    s->ep = cur->second->ep;
    s->esp = cur->second->esp;
    // 正确读取
    while (true)
    {
        int serversock = cur->second->p->Accept();
        if (serversock >= 0)
        {
            std::pair<int, std::unique_ptr<IO> &> pa(serversock, s);
            // 首先设置非阻塞读取
            setblock(serversock);
            // 插入对应关系
            ptr.insert(pa);
            // 插入epoll模型
            s->ep->EpollCtl(EPOLL_CTL_ADD, serversock, EPOLLET | EPOLLIN);
            std::cout << "insert success sock:" << serversock << std::endl;
        }
        if (serversock < 0)
        {
            if (errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "没数据,请再试一次" << std::endl;
                break;
            }
            else if (errno == EINTR)
            {
                std::cout << "被中断,请再试一次" << std::endl;
                continue;
            }
            else
            {
                std::cout << "accept fail:" << strerror(errno) << std::endl;
                break;
            }
        }
    }
}
void Recv(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
    auto cur = ptr.find(sock);
    assert(cur != ptr.end());
    while (true)
    {
        char buffer[1024];
        ssize_t s = recv(sock, buffer, 1023, 0);
        if (s > 0)
        {
            buffer[s] = '\0';
            cur->second->_inbuffer += buffer;
        }
        if (s == 0)
        {
            std::cout << "客户端退出" << std::endl;
            // 从epoll中删除节点
            cur->second->ep->EpollCtl(EPOLL_CTL_DEL, sock, 0);
            // 删除对应关系
            ptr.erase(sock);
            // 关闭文件描述符
            close(sock);
            break;
        }
        if (s < 0)
        {
            if (errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "没数据,请再试一次" << std::endl;
                fun_t fun(Func);
                fun(sock, cur->second->_inbuffer, ptr);
                break;
            }
            else if (errno == EINTR)
            {
                std::cout << "被中断,请再试一次" << std::endl;
                continue;
            }
            else
            {
                std::cout << "recv fail:" << strerror(errno) << std::endl;
                break;
            }
        }
    }
}
void Write(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
    auto cur = ptr.find(sock);
    assert(cur != ptr.end());
    while (true)
    {
        ssize_t s = send(sock, cur->second->_outbuffer.c_str(), cur->second->_outbuffer.size(), 0);
        if (s > 0)
        {
            std::cout << "send success" << std::endl;
            cur->second->_outbuffer.erase(0, s);
            if (cur->second->_outbuffer.empty())
                break;
        }
        else
        {
            if (errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "没空间" << std::endl;
                break;
            }
            else if (errno == EINTR)
            {
                std::cout << "被中断" << std::endl;
                continue;
            }
            else
            {
                std::cout << "Write fail:" << strerror(errno) << std::endl;
                break;
            }
        }
    }
}
void handevent(epoll_event *event, int num, int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
    for (size_t i = 0; i < num; i++)
    {
        std::cout << event[i].events << std::endl;
        // 读事件就绪
        if ((ptr.find(event[i].data.fd) != ptr.end()) && (event[i].events & EPOLLIN))
        {
            if (event[i].data.fd == sock)
            {
                Accept(event[i].data.fd, ptr);
            }
            else
            {
                Recv(event[i].data.fd, ptr);
            }
        }
        // 写事件就绪
        if ((ptr.find(event[i].data.fd) != ptr.end()) && (event[i].events & EPOLLOUT))
        {
            Write(event[i].data.fd, ptr);
            auto cur = ptr.find(sock);
            cur->second->esp->Change(sock, false);
            std::cout << "已关闭" << std::endl;
        }
    }
}
bool setblock(int sock)
{
    int fl = fcntl(sock, F_GETFL);
    if (fl < 0)
        return false;
    fcntl(sock, F_SETFL, fl | O_NONBLOCK);
    return true;
}
void Func(int sock, std::string &request, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
    // 反序列化
    std::cout << request << std::endl;
    // 处理业务
    // 构建响应
    std::string respon = request;
    request.clear();
    // 发送给客户端
    auto cur = ptr.find(sock);
    assert(cur != ptr.end());
    cur->second->_outbuffer = respon;
    cur->second->esp->Change(sock, true);
}
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
bool setblock(int sock);
#define QUEUE_NUM 20 // 监听队列长度
class Sock
{
public:
    Sock()
        : _listen_queue(QUEUE_NUM)
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
        if (sock < 0)
        {
            std::cout << "socket fail:" << strerror(errno) << std::endl;
            exit(1);
        }
        // 设置端口复用
        int opt = 1;
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        // 设置非阻塞读取
        _sock = sock;
        setblock(_sock);
    }
    void Bind(uint16_t port = 8080, const std::string &ip = "0.0.0.0")
    {
        sockaddr_in sever; // 创建sockaddr_in结构体
        memset(&sever, 0, sizeof sever);
        sever.sin_addr.s_addr = inet_addr(ip.c_str());
        sever.sin_port = htons(port);
        sever.sin_family = AF_INET;
        socklen_t len = sizeof sever;
        int ret = bind(_sock, (sockaddr *)(&sever), len); // 绑定
        if (ret < 0)
        {
            std::cout << "bind fail:" << strerror(errno) << std::endl;
            exit(2);
        }
    }
    int Listen()
    {
        int lret = listen(_sock, _listen_queue); // 监听
        if (lret < 0)
        {
            std::cout << "listen fail:" << strerror(errno) << std::endl;
            exit(3);
        }
        return _sock;
    }
    void Connect(int sock, uint16_t port, const std::string &ip)
    {
        sockaddr_in sever;
        memset(&sever, 0, sizeof sever);
        sever.sin_addr.s_addr = inet_addr(ip.c_str());
        sever.sin_port = htons(port);
        sever.sin_family = AF_INET;
        int ret = connect(sock, (sockaddr *)(&sever), sizeof sever);
        if (ret < 0)
        {
            std::cout << "connect fail:" << strerror(errno) << std::endl;
            exit(4);
        }
    }
    int Accept()
    {
        sockaddr_in client;
        memset(&client, 0, sizeof client);
        socklen_t len = sizeof client;
        int sock = accept(_sock, (sockaddr *)(&client), &len);
        if (sock > 0)
        {
            return sock;
        }
        return -1;
    }
    ~Sock()
    {
        if (_sock > 0)
            close(_sock);
    }
    int _sock; // 套接字
private:
    const int _listen_queue; // 监听队列长度
};
#include "eopllsever.hpp"
int main()
{
    EpollSock s;
    s.start();
    return 0;
}

总体代码如上,其实我最后懒的写线程池和自己定制协议了,但是也不影响。我们先来说了上面的问题之后,就来说说原因吧!(没有会所其他地方,个人感觉是没有必要的,如果有兄弟也在写epoll,可以私信交流)。

先说最后一个原因吧,我把智能指针定义成为static的原因是,如果不定义静态的,那么就会出现局部变量出了作用域就会销毁,所以,要是打算用智能指针的话,千万别定义成局部变量。其实在这里我也想了很久,如果定义成静态的话,势必要浪费资源,而服务器又是无限循环的,但是如果不用的话,那么我们一不注意就会造成内存泄漏,后果就不用收了吧,我对比了一下,相对于内存泄漏,这个还是比较内存泄漏好。

其次就是第二个问题的原因:为什么我要说定义不要再头文件中呢?这里说起来很惭愧,C++用久了之后,忘了很多C的语法了。但是直到今天我才弄明白一个很久以前的知识点。那就是定义最好不要在头文件中。再用C++写的时候,大部分解耦都很实现成类,这个不用说,毕竟是面向对象的编程嘛,但是我今天的写的时候,他给我报了个很疑惑的错误,从来没见过,如下图:

这个错误一出来我就很疑惑,因为代码被我改正确了,所以错误不一样,但是大体就是这样的 ,还有就是显示的是没有这个类型名,我看了看我自己的头文件,都包含了,但是还是显示错误,然后我就上网查了一下,网上说的大概是头文件重复包含了,但是我的每个头文件都有#pragma once防止重复包含的,所以我就一直在看,以为编译过不了,连日志也不能打,只能干看,最后才发现是有几个函数的定义我放在了头文件,导致两个文件互相包含,这样的话,连声明函数都不知道怎么声明,后来才写成来两个.cpp的文件。

最后,不知道大家有没有和我一样犯过这些错误的,如果有,希望下次咱们都不会犯了,如果感觉对你有用话,就点一下赞吧!!!(改了一天的bug,希望大家能够支持)

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

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

相关文章

电机的转矩控制和转速控制的区别和应用(电机控制),电动汽车电机控制更多用转矩控制模式还是转速控制模式?

转矩控制模式牺牲时间获取稳定性&#xff1b;转速模式牺牲稳定性获取时间&#xff1b; 1&#xff0c;VCU根据驾驶员给定的油门踏板开度确定的转矩需求&#xff0c;再将需求转矩指令发送给MCU&#xff0c;转矩控制。控制转矩可以把加速度的控制权掌握在驾驶者手里&#xff0c;更…

makefile,make,CMake项目编译之helloworld

文章目录 makefile&#xff0c;make&#xff0c;CMake项目编译1.相关概念1.1makefile是什么1.2make和makefile1.3cmake1.4Cmake与CMakeLists 的关系 2.makefile文件编写helloworld makefile&#xff0c;make&#xff0c;CMake项目编译 1.相关概念 1.1makefile是什么 工程中的…

Windows下同一台服务器部署多个tomcat服务

工具 apache-tomcat-8.5.97 安装tomcat步骤 下载apache-tomcat-8.5.97&#xff0c;下载下来的文件为apache-tomcat-8.5.97-windows-x64.zip解压该压缩包到指定目录下&#xff0c;比如E:\works修改解压文件夹名字为&#xff1a;tomcat-8080在E:\works目录下创建该文件夹的两个…

大模型学习之书生·浦语大模型5——基于LMDeploy大模型量化部署实践

目录 大模型部署背景 LMDeploy部署 量化 TurboMind API server 动手实践环节

计算机导论09-数据组织与管理

文章目录 数据管理基础数据管理技术的发展数据组织与管理的发展手工数据处理阶段文件方式的数据处理阶段数据库数据处理阶段 数据库技术的发展 数据模型数据模型的要素概念模型逻辑模型 数据库系统数据库系统基础数据库系统构成 数据库系统的结构数据库系统的体系结构数据库系统…

maxwell同步全量历史数据

CentOS安装maxwell 在上篇的基础上&#xff0c;我们实现了实时同步mysql数据到kafka。maxwell不仅可以同步实时数据&#xff0c;也可以同步全量历史数据。在这里模拟一下历史数据的场景&#xff0c;创建表结构如下&#xff0c;并写入测试数据。 CREATE TABLE user_det…

Kafka集群与可靠性

Kafka集群与可靠性 1.Kafka集群搭建实战 使用两台Linux服务器&#xff1a;一台192.168.182.137 一台192.168.182.138 安装kafka首先&#xff0c;我们需要配置java环境变量&#xff08;这里就略过了&#xff09; mkdir /opt/kafka #上传压缩包kafka_2.13-3.3.1.tgz并解压 ta…

某银行主机安全运营体系建设实践

随着商业银行业务的发展&#xff0c;主机规模持续增长&#xff0c;给安全团队运营工作带来极大挑战&#xff0c;传统的运营手段已经无法适应业务规模的快速发展&#xff0c;主要体现在主机资产数量多、类型复杂&#xff0c;安全团队难以对全量资产进行及时有效的梳理、管理&…

【QML COOK】- 010-动态创建组件

上节介绍了Component的概念&#xff0c;本节介绍一下如何使用javascript动态创建对象。 1. 创建工程&#xff0c;新建一个MyComponent.qml的qml import QtQuickRectangle {color: "red" }它很简单就是一个红色框 2. 编辑main.qml import QtQuickWindow {id: root…

mac pro “RESP.app”意外退出 redis desktop manager

文章目录 redis desktop manager下载地址提示程序含有恶意代码“RESP.app”意外退出解决办法&#xff1a;下载python3.10.并安装重新打开RESP如果还是不行&#xff0c;那么需要替换错误路径&#xff08;我的没用&#xff09;外传 最近在研究redis的消息&#xff0c;看到了strea…

Ubuntu系统默认的dash shell改成bash shell

在Ubuntu系统中&#xff0c;如果默认的/bin/sh链接指向了dash&#xff0c;而你希望将其更改为指向bash&#xff0c;可以通过以下步骤操作&#xff1a; sudo rm /bin/sh sudo ln -s /bin/bash /bin/sh 但是&#xff0c;这种做法并不推荐&#xff0c;因为某些系统服务和脚本依赖…

在线艺术字生成器-DedeCMS源码-支持字体转换与自定义-适用于网站设计

创造专属艺术字-字体定制-系统源码-支持自定义字体/在线艺术字体转换器 您现在可以随心所欲地在线生成、转换和设计艺术字体。我们的系统源码以html为主&#xff0c; 虽然基于DedeCMS内核&#xff0c;但赋予您无限的可能性。后台管理功能只是辅助&#xff0c;您可以自由探索和…

外观模式(结构型)

目录 一、前言 二、外观模式 三、总结 一、前言 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它为系统中的一组复杂子系统提供一个简单的接口&#xff0c;从而隐藏了这些子系统的复杂性&#xff0c;并且使得代码更加易于使用和理解。 外…

推荐系统模型(一) DFN 详解 Deep Feedback Network for Recommendation

背景 在大多数的推荐系统中&#xff0c;往往注重于隐式正反馈(例如&#xff1a;点击)&#xff0c;而忽略掉用户的其他行为(例如大多数CTR模型只考虑用户的喜欢&#xff0c;而忽略了不喜欢)。腾讯在Deep Feedback Network for Recommendation 一文中&#xff0c;提出了一个新颖…

VitePress-01-从零开始的项目创建(npm版)

说明 本文介绍一下 VitePress的项目创建的步骤。 主要用到的命令工具是 npm。 本文的操作步骤是从无到有的创建一个完整的基本的【VitePress】项目。 环境准备 根据官方文档的介绍&#xff0c;截止本文发稿时&#xff0c;需要使用node.js 18 的版本。 可以使用node -v 的命令查…

Electron+React项目打包踩坑记录

首先&#xff0c;如何打包 写下本文的时间是 2024/01/16&#xff0c;搜索了网络上 ElectronReact 的打包方式&#xff0c;中间行不通&#xff0c;本文采用的方式是记录本文时 Electron 快速入门(https://www.electronjs.org/zh/docs/latest/tutorial/quick-start)记录的打包方式…

电子学会C/C++编程等级考试2023年05月(八级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:道路 N个以 1 … N 标号的城市通过单向的道路相连:。每条道路包含两个参数:道路的长度和需要为该路付的通行费(以金币的数目来表示) Bob and Alice 过去住在城市 1.在注意到Alice在他们过去喜欢玩的纸牌游戏中作弊后,Bob和她分手…

vtk9.3 配置 visual studio 2019 运行环境 和运行实例详解

&#xff08;1&#xff09;包含文件配置&#xff1a; 项目--属性--VC目录&#xff0c;在包含目录中把include文件夹的地址加进去&#xff0c;一直要到下一级 vtk-9.3目录下&#xff0c; 小知识&#xff1a; 在Visual Studio 2019中运行项目时&#xff0c;如果项目中使用了第三…

SpringBoot项目中简单使用虚拟机Redis

目录 步骤大致如下&#xff1a; 一.在pom文件中加入redis依赖 二.在虚拟机上打开我们下载好的Redis。开启服务器端并获取虚拟机ip地址 三.在项目配置。 四&#xff1a;使用redis 测试 redis是一个以键值对存储的NoSQL。被数百万开发人员用作缓存、矢量数据库、文档数据库、…

SpringBoot-项目复制

Spring Boot是一个用于简化Java应用程序开发的框架&#xff0c;它提供了自动配置和约定优于配置的原则。项目复制是指通过复制现有的Spring Boot项目来创建一个新的项目&#xff0c;以便快速搭建基于相似功能或结构的应用程序。项目复制可以减少类似项目的开发时间、提高生产力…