【Linux C++】实现Reactor服务器:封装Channel类

日期:2025.2.16(晚)2025.2.17(凌晨)

学习内容:

  • 封装Channel类

个人总结:

注:本篇内容比较枯燥,主要是各个类的关系,不想看的可以直接看代码。

背景:

好的,目前我们已经有了Socket,InetAddress,Epoll三个类,目前为止思路还不是特别的凌乱,但是加上了Channel类之后,我的脑子已经乱掉了。这四个类的关系我很混乱,如果让我自己敲我不一定能够敲出来,所以特地整理了一下思绪。

注意:以下内容是各个类之间的关系的推理。

首先是Socket和InetAddress类:InetAddress类就是一个单纯的数据包装的类,没有别的特别的,一个更好使用的地址类。而Socket类是一个对fd描述符的高级包装,我们可以更好的操纵fd(在网络编程的层面上),简化了bind,accept操作,而其中accept函数可以让我们得到客户端已连接描述符的地址。

接着是我们现在多了一个Channel类,Channel类起到的是一个桥梁作用,Epoll类负责任务的分发,就像是一个boss,每一次Channel将客户的fd要做的事情告诉Epoll,然后Epoll将任务分发给Channel去做。(关于为什么要用Channel类,是为了方便更多可以执行的操作,之前的简单的版本没有Channel类,我们只能去执行一些简单的例如数据发送的操作)。

Channel类的HandlEvent函数就是处理事件的函数,根据revent变量(在下面有讲revent和event)来判断执行什么函数。(由于我们写的简单版本,目前只有读事件的处理,也就是EPOLLIN),当是EPOLLIN,就会执行read_callback函数,这是一个function<void()>类型,接口统一为了方便。

然后再说一点细节:

之前我们使用epoll_event,但是现在使用的是Channel类,所以两者之间还是有转化的。这里详情我们直接看代码:

void Epoll::UpdateChannel(Channel* ch) {
    epoll_event one;
    one.data.ptr = ch;
    one.events = ch->GetEvent();
    if (ch->IsInEpoll()) {
        //...
    }
    else {
        if (epoll_ctl(m_epollfd, EPOLL_CTL_ADD, ch->GetFD(), &one) < 0) {
            perror("epoll_ctl");
            exit(-1);
        }
        ch->SetInEpoll();
    }
}

可以看到这里我们存了指针,将Channel类转化到了epoll_event类型,然后再用epoll_ctl函数存入epoll实例中。

那么相对应的,我们在使用epoll_wait函数后,得到的也是epoll_event类型的,也是类似的方式:

std::vector<Channel*> Epoll::NewEvents(int timeout) {
    m_evs.resize(MAXSIZE);
    int newthings = epoll_wait(m_epollfd, m_evs.data(), MAXSIZE, timeout);
	//...
    std::vector<Channel*> ch_vec;
    for (int i = 0; i < newthings; i++) {
        Channel* one = (Channel*)m_evs[i].data.ptr;
        one->SetRevent(m_evs[i].events);
        ch_vec.push_back(one);
    }
    return std::move(ch_vec);
}

再用指针即可,记得设置revent。

这里还有一个浅层的注意事项:关于m_evs[i].events的类型,我们存进去的时候是我们期望的关注的事件event:EPOLLIN,但是在使用了wait函数之后,会修改成了实际发生的事件revent:EPOLLIN(这里大多数都是不会变的,但是确实也有变化的情况)。

所以我们用Channel类后,就需要专门拉出来设两个变量,一个是event,一个是revent。

大体就讲到这里了,后面的就看代码了。

代码:

server.cpp:

#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include <functional>

#include "InetAddress.h"
#include "Socket.h"
#include "Epoll.h"
#include "Channel.h"

using std::cin;
using std::cout;
using std::endl;

int main(int argc, char* argv[]) {
    if (argc != 3) {
        cout << "./server 192.168.11.132 5005" << endl;
        return 0;
    }

    Socket listen(CreatNonBlocking());
    int listenfd = listen.GetFD();
    listen.KeepAlive(1);
    listen.NoDelay(1);
    listen.ReuseAddr(1);
    listen.ReusePort(1);

    InetAddress serv_addr(argv[1], atoi(argv[2]));
    listen.Bind(serv_addr);
    listen.Listen(128);

    Epoll ep;
    Channel* serv_ch = new Channel(&ep, listen.GetFD());
    serv_ch->EnableReading();
    serv_ch->SetReadCallback(std::bind(&Channel::NewConnection, serv_ch, &listen));
    ep.UpdateChannel(serv_ch);
    while (true) {
        auto new_evs = ep.NewEvents();
        for (auto& ev : new_evs) {
            ev->HandleEvent();
        }
    }
    return 0;
}

Channel.h:

#pragma once

#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include <functional>

class Epoll;
class Socket;

class Channel {
private:
    int m_fd = -1;
    Epoll* m_ep = nullptr;
    bool m_is_in_epoll = false;
    uint32_t m_event = 0;
    uint32_t m_revent = 0;
    std::function<void()> read_callback;
public:
    Channel(Epoll* ep, int fd);
    ~Channel();
    void EnableReading();
    void EnableET();
    void SetInEpoll();
    void SetRevent(const uint32_t& ev);
    void SetReadCallback(std::function<void()> func);
    bool IsInEpoll();
    int GetFD();
    uint32_t GetEvent();
    uint32_t GetRevent();
    void HandleEvent();
    void NewConnection(Socket* listen);
    void OnMessage();
};

Epoll.h:

#pragma once

#include <sys/epoll.h>
#include <unistd.h>
#include <vector>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

class Channel;

class Epoll {
private:
    static const int MAXSIZE = 10;
    int m_epollfd;
    std::vector<epoll_event> m_evs;

public:
    Epoll();
    ~Epoll();
    void AddFD(int fd, uint32_t opt);
    void UpdateChannel(Channel* ch);
    std::vector<Channel*> NewEvents(int timeout = -1);
};

Socket.h:

#pragma once

#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netinet/in.h>

class InetAddress;

int CreatNonBlocking();

class Socket {
private:
    int m_fd;
public:
    Socket(int fd);
    ~Socket();
    void ReuseAddr(bool opt);
    void ReusePort(bool opt);
    void KeepAlive(bool opt);
    void NoDelay(bool opt);
    void Bind(const InetAddress& addr);
    void Listen(int siz = 128);
    int Accept(InetAddress& addr);
    const int GetFD() const;
};

InetAddress.h:

#pragma once

#include <sys/socket.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddress {
private:
    sockaddr_in m_addr;
public:
    InetAddress() = default;
    InetAddress(const std::string& ip, uint16_t port);
    InetAddress(const sockaddr_in& addr);
    ~InetAddress();

    void SetAddress(const sockaddr_in& one);

    const sockaddr* Addr() const;
    const char* IP() const;
    const short Port() const;
};

Channel.cpp:

#include "Channel.h"
#include "Epoll.h"
#include "InetAddress.h"
#include "Socket.h"

Channel::Channel(Epoll* ep, int fd) {
    m_ep = ep;
    m_fd = fd;
}

Channel::~Channel() {

}

void Channel::EnableReading() {
    m_event |= EPOLLIN;
}

void Channel::EnableET() {
    m_event |= EPOLLET;
}

void Channel::SetInEpoll() {
    m_is_in_epoll = true;
}

void Channel::SetRevent(const uint32_t& ev) {
    m_revent = ev;
}

void Channel::SetReadCallback(std::function<void()> func) {
    read_callback = func;
}


bool Channel::IsInEpoll() {
    return m_is_in_epoll;
}

int Channel::GetFD() {
    return m_fd;
}

uint32_t Channel::GetEvent() {
    return m_event;
}

uint32_t Channel::GetRevent() {
    return m_revent;
}

void Channel::HandleEvent() {
    if (this->GetRevent() & EPOLLIN) {
        read_callback();
    }
    else if (this->GetRevent() & EPOLLOUT) {
    }
    else {
        printf("error: (fd: %d) \n", this->GetFD());
        close(this->GetFD());
    }
}

void Channel::NewConnection(Socket* listen) {
    InetAddress client_addr1;
    Socket* client = new Socket(listen->Accept(client_addr1));
    int clientfd = client->GetFD();
    printf("New Client fd:%d  ip:%s  port:%hu \n", clientfd, client_addr1.IP(), client_addr1.Port());

    Channel* client_ch = new Channel(m_ep, clientfd);
    client_ch->EnableET();
    client_ch->EnableReading();
    client_ch->SetReadCallback(std::bind(&Channel::OnMessage, client_ch));
    m_ep->UpdateChannel(client_ch);
}

void Channel::OnMessage() {
    char buf[1024];
    while (true) {

        memset(buf, 0, sizeof buf);

        ssize_t read_siz = read(this->GetFD(), buf, sizeof buf);

        if (read_siz > 0) {
            printf("recv: (fd: %d) %s\n", this->GetFD(), buf);
            send(this->GetFD(), buf, sizeof buf, 0);
        }
        else if (read_siz == -1 and errno == EINTR) {
            continue;
        }
        else if (read_siz == -1 and (errno == EAGAIN || errno == EWOULDBLOCK)) {
            break;
        }
        else if (read_siz == 0) {
            printf("disconnect: (fd: %d) \n", this->GetFD());
            close(this->GetFD());
            break;
        }
    }
}

Epoll.cpp:

#include "Channel.h"
#include "Epoll.h"

Epoll::Epoll() { m_epollfd = epoll_create(1); }

Epoll::~Epoll() { close(m_epollfd); }

void Epoll::UpdateChannel(Channel* ch) {
    epoll_event one;
    one.data.ptr = ch;
    one.events = ch->GetEvent();
    if (ch->IsInEpoll()) {
        if (epoll_ctl(m_epollfd, EPOLL_CTL_MOD, ch->GetFD(), &one) < 0) {
            perror("epoll_ctl");
            exit(-1);
        }
    }
    else {
        if (epoll_ctl(m_epollfd, EPOLL_CTL_ADD, ch->GetFD(), &one) < 0) {
            perror("epoll_ctl");
            exit(-1);
        }
        ch->SetInEpoll();
    }
}

std::vector<Channel*> Epoll::NewEvents(int timeout) {
    m_evs.resize(MAXSIZE);
    int newthings = epoll_wait(m_epollfd, m_evs.data(), MAXSIZE, timeout);

    if (newthings < 0) {
        perror("epoll_wait");
        exit(-1);
    }
    else if (newthings == 0) {
        return std::vector<Channel*>();
    }

    std::vector<Channel*> ch_vec;
    for (int i = 0; i < newthings; i++) {
        Channel* one = (Channel*)m_evs[i].data.ptr;
        one->SetRevent(m_evs[i].events);
        ch_vec.push_back(one);
    }
    return std::move(ch_vec);
}

Socket.cpp:

#include "Socket.h"
#include "InetAddress.h"

int CreatNonBlocking() {
    return socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
}

Socket::Socket(int fd) :m_fd(fd) {
}

Socket::~Socket() {
    close(m_fd);
}

void Socket::ReuseAddr(bool opt) {
    setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, &opt, static_cast<socklen_t>(sizeof opt));
}

void Socket::ReusePort(bool opt) {
    setsockopt(m_fd, SOL_SOCKET, SO_REUSEPORT, &opt, static_cast<socklen_t>(sizeof opt));
}

void Socket::KeepAlive(bool opt) {
    //套接字开启保活机制,如果长时间没有传输数据会检测是否链接,没有链接就会释放资源
    setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE, &opt, static_cast<socklen_t>(sizeof opt));
}
void Socket::NoDelay(bool opt) {
    //禁用nagle算法,减少数据延迟
    setsockopt(m_fd, SOL_SOCKET, TCP_NODELAY, &opt, static_cast<socklen_t>(sizeof opt));
}

void Socket::Bind(const InetAddress& addr) {
    if (bind(m_fd, addr.Addr(), sizeof(sockaddr_in)) < 0) {
        perror("Bind");
        exit(-1);
    }
}

void Socket::Listen(int siz) {
    if (listen(m_fd, siz) < 0) {
        perror("Listen");
        exit(-1);
    }
}

int Socket::Accept(InetAddress& addr) {
    struct sockaddr_in one;
    socklen_t len = sizeof(sockaddr_in);
    int clientfd = accept4(m_fd, (sockaddr*)&one, &len, SOCK_NONBLOCK);
    if (clientfd == -1) {
        perror("accept4");
        exit(-1);
    }
    addr.SetAddress(one);

    return clientfd;
}

const int Socket::GetFD()const {
    return m_fd;
}

InetAddress.cpp:

#include "InetAddress.h"

InetAddress::InetAddress(const std::string& ip, uint16_t port) {
    m_addr.sin_family = AF_INET;
    m_addr.sin_port = htons(port);
    m_addr.sin_addr.s_addr = inet_addr(ip.c_str());
}

InetAddress::InetAddress(const sockaddr_in& addr) { m_addr = addr; }

InetAddress::~InetAddress() {}

const sockaddr* InetAddress::Addr() const { return (sockaddr*)&m_addr; }

const char* InetAddress::IP() const { return inet_ntoa(m_addr.sin_addr); }
const short InetAddress::Port() const { return htons(m_addr.sin_port); }

void InetAddress::SetAddress(const sockaddr_in& one) { m_addr = one; }
posted @   AdviseDY  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示