【Linux网络】epoll实现的echo服务器{nocopy类/智能指针/echo服务器}

文章目录

1.代码基础

1.1某类唯一存在

这段代码定义了一个名为 nocopy 的类,它旨在防止该类的实例被复制或赋值。这是通过在类中显式删除拷贝构造函数(nocopy(const nocopy &))和赋值操作符(const nocopy &operator=(const nocopy &))来实现的。

拷贝构造函数(Copy Constructor)

拷贝构造函数是一种特殊的构造函数,用于创建一个新对象作为另一个同类型对象的副本。在 nocopy 类中,拷贝构造函数被声明为 = delete,这意味着如果尝试使用拷贝构造函数来创建 nocopy 类的实例的副本,编译器将报错。这确保了 nocopy 类的实例不能通过拷贝构造函数进行复制。

赋值操作符(Assignment Operator)

赋值操作符用于将一个对象的状态(即其成员变量的值)复制到另一个同类型的对象。同样地,在 nocopy 类中,赋值操作符也被声明为 = delete,这意味着如果尝试将一个 nocopy 类的实例赋值给另一个同类型的实例,编译器将报错。这确保了 nocopy 类的实例不能通过赋值操作符进行赋值。

使用场景

资源管理:当类的实例管理着重要的资源(如文件句柄、网络连接、动态分配的内存等)时,可能需要防止这些资源被无意中复制或赋值,以避免资源泄露或不可预测的行为。

唯一性保证:在需要确保某个类的实例在程序中唯一存在的场景中,禁用拷贝和赋值操作可以强制实现这一点。

cpp 复制代码
示例
cpp
#include <iostream>  
  
class nocopy  
{  
public:  
    nocopy() { std::cout << "nocopy constructed\n"; }  
    nocopy(const nocopy &) = delete;  
    const nocopy &operator=(const nocopy &) = delete;  
};  
  
int main() {  
    nocopy obj1; // 正确:调用构造函数  
    // nocopy obj2 = obj1; // 错误:拷贝构造函数被删除  
    // obj1 = obj2; // 错误:赋值操作符被删除  
    return 0;  
}

在这个示例中,尝试使用拷贝构造函数或赋值操作符会导致编译错误,因为它们都已被删除。

当一个类(如你提供的nocopy类)被设计成不允许被拷贝(通过拷贝构造函数)或赋值(通过赋值运算符),这通常是为了确保该类的实例在程序中的唯一性或特定的生命周期管理策略。当其他类继承这个nocopy类时,它们也继承了这些不允许拷贝和赋值的特性,这带来了几个重要的意义和目的:

  1. 防止浅拷贝问题:对于包含动态分配资源(如指针指向的内存、文件句柄等)的类,如果允许拷贝,可能会导致多个对象实例共享相同的资源,从而引发数据竞争、资源泄露等问题。通过禁止拷贝,可以确保每个对象都管理自己独立的资源,从而避免这些问题。
  2. 实现单例模式:单例模式是一种确保类只有一个实例,并提供一个全局访问点的设计模式。通过继承nocopy类,可以很容易地实现一个单例类,因为它自然就不允许通过拷贝来创建额外的实例。
  3. 确保对象唯一性:在某些情况下,可能希望确保某个类的所有实例在逻辑上都是唯一的,或者至少在某些方面(如标识符、配置等)是唯一的。通过禁止拷贝和赋值,可以强制要求使用其他机制(如工厂方法、依赖注入等)来创建和管理这些对象的实例,从而有助于维护这种唯一性。
  4. 强化不可变性和线程安全:对于设计为不可变(immutable)的类,禁止拷贝和赋值可以进一步强调其不可变性,因为任何尝试修改实例(通过赋值)或创建其副本(通过拷贝)的行为都将被编译器阻止。此外,不可变对象通常更容易实现线程安全,因为它们不需要在并发访问时进行同步。
  5. 简化资源管理和生命周期:通过控制对象的拷贝和赋值行为,可以更精确地控制资源的分配和释放时机,以及对象的生命周期。这对于管理复杂资源(如网络连接、数据库连接等)的类尤为重要。
  6. 表达设计意图:通过明确禁止拷贝和赋值,类的设计者可以向使用该类的其他开发者传达关于对象如何被预期使用和管理的明确信息。这有助于减少误解和错误使用,提高代码的可维护性和可读性。
    总之,通过让其他类继承nocopy类并继承其不允许拷贝和赋值的特性,可以在多个方面提高代码的健壮性、安全性和可维护性。

1.2C++智能指针

C++标准库提供的一种用于自动管理动态分配内存的指针类型。它们的主要目的是解决传统裸指针(raw pointers)在动态内存管理时容易出现的内存泄漏、重复释放等问题。智能指针通过封装裸指针并提供自动的析构函数来确保动态分配的内存被适时释放,从而简化了内存管理。

C++标准库中主要有以下几种智能指针:

std::unique_ptr:

独享所有权的智能指针。 std::unique_ptr不允许拷贝构造和拷贝赋值,但支持移动构造和移动赋值。 这意味着一个std::unique_ptr对象在任意时刻只能被一个std::unique_ptr拥有。当std::unique_ptr被销毁时(例如,离开作用域),它所指向的对象也会被自动删除。

std::shared_ptr:

共享所有权的智能指针。 多个std::shared_ptr实例可以指向同一个对象,并通过内部的控制块(通常是一个计数器)来跟踪有多少std::shared_ptr指向该对象。当最后一个指向该对象的std::shared_ptr被销毁时,对象才会被删除。std::shared_ptr支持拷贝构造和拷贝赋值,这使得它们非常适合在复杂的数据结构或算法中共享所有权。

std::weak_ptr:

std::weak_ptr是一种不拥有其所指对象的智能指针 。它主要用于解决std::shared_ptr之间的循环引用问题 。std::weak_ptr可以指向一个由std::shared_ptr管理的对象,但它不增加对象的共享计数。因此,当所有的std::shared_ptr都被销毁时,即使还有std::weak_ptr指向该对象,对象也会被销毁。std::weak_ptr提供了一个lock成员函数,用于尝试获取一个指向对象的std::shared_ptr,如果对象还存在,则返回一个有效的std::shared_ptr;否则,返回一个空的std::shared_ptr。

智能指针的使用使得C++中的动态内存管理更加安全和方便,减少了内存泄漏和野指针的风险。然而,开发者仍然需要谨慎使用它们,特别是在复杂的所有权关系和生命周期管理中。
复习好文!

2.epoll实现的echo服务器

日志

cpp 复制代码
#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
private:
    int printMethod;
    std::string path;

public:
    Log()
    {
        printMethod = Screen;
        path = "./";
    }

    void Enable(int method)
    {
        printMethod = method;
    }

    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    /*
     void logmessage(int level, const char *format, ...)
        {
            time_t t = time(nullptr);
            struct tm *ctime = localtime(&t);
            char leftbuffer[SIZE];
            snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                     ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                     ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

            va_list s;
            va_start(s, format);
            char rightbuffer[SIZE];
            vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
            va_end(s);

            // 格式:默认部分+自定义部分
            char logtxt[SIZE * 2];
            snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

            // printf("%s", logtxt);
            printLog(level, logtxt);
        }
    */

   // lg(Warning, "accept error, %s: %d", strerror(errno), errno);
    void operator()(int level, const char *msg_format, ...)
    {
        time_t timestamp = time(nullptr);
        struct tm *ctime = localtime(&timestamp);
        //level 年月日
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        //自定义msg
        va_list arg_list;//存储可变参数列表信息
        va_start(arg_list, msg_format);//初始化 使其指向函数参数列表中format参数之后的第一个可变参数
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), msg_format, arg_list);
        va_end(arg_list);//清理va_list变量

        // 格式:默认部分+自定义部分
        char log_content[SIZE * 2];
        snprintf(log_content, sizeof(log_content), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, log_content);
    }

    void printLog(int level, const std::string &log_content)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << log_content << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, log_content);
            break;
        case Classfile:
            printClassFile(level, log_content);
            break;
        default:
            break;
        }
    }

    void printOneFile(const std::string &log_filename, const std::string &log_content)
    {
        //path = "./"; #define LogFile "log.txt"
        std::string _logFilename = path + log_filename;
        int fd = open(_logFilename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, log_content.c_str(), log_content.size());
        close(fd);
    }

    void printClassFile(int level, const std::string &log_content)
    {
        //#define LogFile "log.txt"
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug"
        printOneFile(filename, log_content);
    }

    ~Log()
    {
    }
};

Log lg;

/*
int sum(int n, ...)
{
    va_list s; // char*
    va_start(s, n);

    int sum = 0;
    while(n)
    {
        sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
        n--;
    }

    va_end(s); //s = NULL
    return sum;
}
*/

套接字

cpp 复制代码
#pragma once

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

enum
{
    SocketErr = 2,
    BindErr,
    ListenErr,
};

const int g_backlog = 10;

class Sock
{
private:
    int _sockfd;

public:
    Sock()
    {
    }
    ~Sock()
    {
    }

public:
    void Socket()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            lg(Fatal, "socker error, %s: %d", strerror(errno), errno);
            exit(SocketErr);
        }
    }

    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
            exit(BindErr);
        }
    }

    void Listen()
    {
        if (listen(_sockfd, g_backlog) < 0)
        {
            lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
            exit(ListenErr);
        }
    }

    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
        if (newfd < 0)
        {
            lg(Warning, "accept error, %s: %d", strerror(errno), errno);
            return -1;
        }

        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));

        *clientip = ipstr;
        *clientport = ntohs(peer.sin_port);

        return newfd;
    }

    bool Connect(const std::string &ip, const uint16_t &port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

        int n = connect(_sockfd, (struct sockaddr *)&peer, sizeof(peer));
        if (n == -1)
        {
            std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
            return false;
        }
        return true;
    }

    void CloseFd()
    {
        close(_sockfd);
    }

    int getSocketFd()
    {
        return _sockfd;
    }
};

CMake

cpp 复制代码
cmake_minimum_required(VERSION 3.29)
set(CMAKE_CXX_STANDARD 11)  
set(CMAKE_CXX_STANDARD_REQUIRED True)

project(EpollServer)

add_executable(epoll_server Main.cc)

epoll封装

cpp 复制代码
#pragma once

#include "nocopy.hpp"
#include "Log.hpp"
#include <cerrno>
#include <cstring>
#include <sys/epoll.h>

class Epoller : public nocopy
{
    static const int size = 128;

private:
    int _epollFd;
    int _timeout{3000};

public:
    Epoller()
    {
        _epollFd = epoll_create(size);
        if (_epollFd == -1)
            lg(Error, "epoll_create error: %s", strerror(errno));
        else
            lg(Info, "epoll_create success: %d", _epollFd);
    }

    int EpllerUpdate(int oper, int sock, uint32_t event)
    {
        // int epoll_ctl(int __epfd, int __op, int __fd, epoll_event *)
        int n = 0;
        if (oper == EPOLL_CTL_DEL)
        {
            n = epoll_ctl(_epollFd, oper, sock, nullptr);
            if (n != 0)
                lg(Error, "epoll_ctl delete error!");
        }
        else // EPOLL_CTL_MOD || EPOLL_CTL_ADD
        {
            /*
            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 variable
            } __EPOLL_PACKED;
            */
            struct epoll_event ev;
            ev.events = event;
            ev.data.fd = sock; // 方便后期得知 是哪一个fd就绪了

            n = epoll_ctl(_epollFd, oper, sock, &ev);
            if (n != 0)
                lg(Error, "epoll_ctl error!");
        }
        return n;
    }
    
    int EpollerWait(struct epoll_event revents[], int num)
    {
        // int epoll_wait(int __epfd, epoll_event *__events, int __maxevents, int __timeout)
        int n = epoll_wait(_epollFd, revents, num, /*_timeout 0*/ -1);
        return n;
    }

    ~Epoller()
    {
        if (_epollFd >= 0)
            close(_epollFd);
    }
};

主函数

cpp 复制代码
#include <iostream>
#include <memory>
#include "EpollServer.hpp"

int main()
{
    std::unique_ptr<EpollServer> epoll_svr(new EpollServer(8888));
    epoll_svr->Init();
    epoll_svr->Start();

    /*//无法引用 函数 "Epoller::Epoller(const Epoller &)" (已隐式声明) -- 它是已删除的函数
    Epoller ep;
    Epoller ep1 = ep;
    */

    return 0;
}

服务器

cpp 复制代码
#pragma once

#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Epoller.hpp"
#include "Log.hpp"
#include "nocopy.hpp"

uint32_t EVENT_IN = (EPOLLIN);
uint32_t EVENT_OUT = (EPOLLOUT);

// 此程序的read/write仍忽略了网络读取报文完整性问题 主要考虑epoll逻辑
class EpollServer : public nocopy
{
    static const int num = 64;

private:
    std::shared_ptr<Epoller> _epollerPtr;

    std::shared_ptr<Sock> _listenSocketPtr;
    uint16_t _port;

public:
    EpollServer(uint16_t port)
        : _port(port),
          _listenSocketPtr(new Sock()),
          _epollerPtr(new Epoller())
    {
    }

    void Init()
    {
        _listenSocketPtr->Socket();
        _listenSocketPtr->Bind(_port);
        _listenSocketPtr->Listen();

        lg(Info, "create listen socket success: %d\n", _listenSocketPtr->getSocketFd());
    }

    void Accepter() // 获取了一个新连接
    {
        std::string clientip;
        uint16_t clientport;
        int sock = _listenSocketPtr->Accept(&clientip, &clientport);
        if (sock > 0)
        {
            // 不能直接读取 原因select/poll已讲
            _epollerPtr->EpllerUpdate(EPOLL_CTL_ADD, sock, EVENT_IN);
            lg(Info, "get a new link, client info@ %s:%d", clientip.c_str(), clientport);
        }
    }

    void Recver(int fd)
    {
        char buffer[1024];
        // 1.不一定是完整的报文
        // 2.读到部分报文 下次的Recver()函数中buffer是局部变量 
        // A报文如果发两次过来 也无法拼接成完整报文
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a messge: " << buffer << std::endl;
            std::string echo_str = "server echo $ ";
            echo_str += buffer;
            write(fd, echo_str.c_str(), echo_str.size());
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            _epollerPtr->EpllerUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            _epollerPtr->EpllerUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
    }

    void Dispatcher(struct epoll_event revs[], int num)
    {
        for (int i = 0; i < num; i++)
        {
            uint32_t events = revs[i].events;
            int fd = revs[i].data.fd;
            if (events & EVENT_IN) // 客户联连接读取事件就绪
            {
                if (fd == _listenSocketPtr->getSocketFd())
                    Accepter();
                else // 普通读取事件就绪
                    Recver(fd);
            }
            // else if (events & EVENT_OUT){}
            // else{}
        }
    }

    void Start()
    {
        // 将listensock添加到epoll中 -> listensock和他关心的事件 添加到内核epoll模型中rb_tree
        _epollerPtr->EpllerUpdate(EPOLL_CTL_ADD, _listenSocketPtr->getSocketFd(), EVENT_IN);
        struct epoll_event revs[num];
        for (;;)
        {
            int n = _epollerPtr->EpollerWait(revs, num);
            if (n > 0) // 有事件就绪
            {
                lg(Debug, "event happened, first event fd is : %d", revs[0].data.fd);
                Dispatcher(revs, n);
            }
            else if (n == 0)
                lg(Info, "time out ...");
            else
                lg(Error, "epll wait error");
        }
    }

    ~EpollServer()
    {
        _listenSocketPtr->CloseFd();
    }
};
相关推荐
咖喱鱼蛋11 分钟前
Ubuntu安装Electron环境
linux·ubuntu·electron
ac.char15 分钟前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm
肖永威20 分钟前
CentOS环境上离线安装python3及相关包
linux·运维·机器学习·centos
tian2kong23 分钟前
Centos 7 修改YUM镜像源地址为阿里云镜像地址
linux·阿里云·centos
mengao123425 分钟前
centos 服务器 docker 使用代理
服务器·docker·centos
布鲁格若门27 分钟前
CentOS 7 桌面版安装 cuda 12.4
linux·运维·centos·cuda
网络安全-杰克32 分钟前
网络安全概论
网络·web安全·php
C-cat.34 分钟前
Linux|进程程序替换
linux·服务器·microsoft
dessler35 分钟前
云计算&虚拟化-kvm-扩缩容cpu
linux·运维·云计算
怀澈12236 分钟前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++