linux笔记归纳25:多路转接epoll

多路转接epoll

目录

多路转接epoll

一、epoll接口

1.1.epoll_create函数

1.2.epoll_ctrl函数

1.3.epoll_wait函数

二、epoll原理

2.1.示意图

2.2.内核结构

2.3.回调机制

2.4.epoll的特点

三、Epoll服务器实现

3.1.EpollServer.hpp

3.2.Main.cc

3.3.Common.hpp

四、LT与ET模式

4.1.LT水平触发模式

4.2.ET边缘触发模式

五、Reactor反应堆模式

5.1.模式框图

5.2.Epoller.hpp

5.3.Reactor.hpp

5.4.Connection.hpp

5.5.Listener.hpp

5.6.Channel.hpp

5.7.Protocol.hpp

5.8.NetCal.hpp

5.9.Main.cc

5.10.TcpClient.cc

5.11.Socket.hpp

5.12.Log.hpp

5.13.InetAddr.hpp

5.14.Common.hpp

5.15.Makefile

六、OTOL设计模式

6.1.核心概念

6.2.多进程实现方案

6.3.多线程实现方案


定位:基于等待多个文件描述符就绪事件的通知机制,为了处理大批量句柄而作了改进的poll

一、epoll接口

1.1.epoll_create函数

作用:创建一个epoll模型

参数:任意大于0的数(已经被忽略)

返回值:文件描述符

1.2.epoll_ctrl函数

作用:控制epoll的文件描述符(用户 → 内核)

参数1:epoll_create的返回值

参数2:设置操作类型

参数3:需要监听的文件描述符

参数4:设置监听事件,附带用户数据

events对应以下几个宏集合

  • EPOLLIN : 表示对应的文件描述符可以读
  • EPOLLOUT : 表示对应的文件描述符可以写
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读
  • EPOLLERR : 表示对应的文件描述符发⽣错误
  • EPOLLHUP : 表示对应的文件描述符被挂断
  • EPOLLET : 将EPOLL设为边缘触发模式
  • EPOLLONESHOT:只监听⼀次事件,当监听完这次事件之后如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL红黑树里

返回值:

  • 成功:0
  • 失败:-1

1.3.epoll_wait函数

作用:控制epoll的文件描述符(内核 → 用户)

参数1:epoll_create的返回值

参数2:数组

参数3:数组长度

参数4:设置超时时间,单位为毫秒

二、epoll原理

2.1.示意图

红黑树本质:用户告诉内核,要关心哪个文件描述符,哪一个事件

就绪队列本质:内核告诉用户,哪一个文件描述符的哪些事件就绪

网络协议栈中的回调机制:一旦底层特定的文件描述符有数据就绪,就会自动进行回调

激活红黑树中的节点到就绪队列中,该机制被提前注册到文件描述符中

内核会严格按照零下标开始,依次拷贝保存就绪事件和文件描述符

应用层在处理就绪事件的时候,处理的都是就绪的,不用进行检测

2.2.内核结构

通过private_data指针找到eventpoll结构

2.3.回调机制

  • sys_epoll_ctl
cpp 复制代码
/*
 * The following function implements the controller interface for
 * the eventpoll file that enables the insertion/removal/change of
 * file descriptors inside the interest set.  It represents
 * the kernel part of the user space epoll_ctl(2).
 */
asmlinkage long
sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
{
	int error;
	struct file *file, *tfile;
	struct eventpoll *ep;
	struct epitem *epi;
	struct epoll_event epds;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n",
		     current, epfd, op, fd, event));

	error = -EFAULT;
	if (ep_op_hash_event(op) &&
	    copy_from_user(&epds, event, sizeof(struct epoll_event)))
		goto eexit_1;

	/* Get the "struct file *" for the eventpoll file */
	error = -EBADF;
	file = fget(epfd);
	if (!file)
		goto eexit_1;

	/* Get the "struct file *" for the target file */
	tfile = fget(fd);
	if (!tfile)
		goto eexit_2;

	/* The target file descriptor must support poll */
	error = -EPERM;
	if (!tfile->f_op || !tfile->f_op->poll)
		goto eexit_3;

	/*
	 * We have to check that the file structure underneath the file descriptor
	 * the user passed to us _is_ an eventpoll file. And also we do not permit
	 * adding an epoll file descriptor inside itself.
	 */
	error = -EINVAL;
	if (file == tfile || !is_file_epoll(file))
		goto eexit_3;

	/*
	 * At this point it is safe to assume that the "private_data" contains
	 * our own data structure.
	 */
	ep = file->private_data;

	down_write(&ep->sem);

	/* Try to lookup the file inside our hash table */
	epi = ep_find(ep, tfile, fd);

	error = -EINVAL;
	switch (op) {
	case EPOLL_CTL_ADD:
		if (!epi) {
			epds.events |= POLLERR | POLLHUP;

			error = ep_insert(ep, &epds, tfile, fd);
		} else
			error = -EEXIST;
		break;
	case EPOLL_CTL_DEL:
		if (epi)
			error = ep_remove(ep, epi);
		else
			error = -ENOENT;
		break;
	case EPOLL_CTL_MOD:
		if (epi) {
			epds.events |= POLLERR | POLLHUP;
			error = ep_modify(ep, epi, &epds);
		} else
			error = -ENOENT;
		break;
	}

	/*
	 * The function ep_find() increments the usage count of the structure
	 * so, if this is not NULL, we need to release it.
	 */
	if (epi)
		ep_release_epitem(epi);

	up_write(&ep->sem);

eexit_3:
	fput(tfile);
eexit_2:
	fput(file);
eexit_1:
	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d\n",
		     current, epfd, op, fd, event, error));

	return error;
}
  • ep_insert
cpp 复制代码
static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
{
	int error, revents, pwake = 0;
	unsigned long flags;
	struct epitem *epi;
	struct ep_pqueue epq;

	error = -ENOMEM;
	if (!(epi = kmem_cache_alloc(epi_cache, SLAB_KERNEL)))
		goto eexit_1;

	/* Item initialization follow here ... */
	ep_rb_initnode(&epi->rbn);
	INIT_LIST_HEAD(&epi->rdllink);
	INIT_LIST_HEAD(&epi->fllink);
	INIT_LIST_HEAD(&epi->txlink);
	INIT_LIST_HEAD(&epi->pwqlist);
	epi->ep = ep;
	ep_set_ffd(&epi->ffd, tfile, fd);
	epi->event = *event;
	atomic_set(&epi->usecnt, 1);
	epi->nwait = 0;

	/* Initialize the poll table using the queue callback */
	epq.epi = epi;
	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

	/*
	 * Attach the item to the poll hooks and get current event bits.
	 * We can safely use the file* here because its usage count has
	 * been increased by the caller of this function.
	 */
	revents = tfile->f_op->poll(tfile, &epq.pt);

	/*
	 * We have to check if something went wrong during the poll wait queue
	 * install process. Namely an allocation for a wait queue failed due
	 * high memory pressure.
	 */
	if (epi->nwait < 0)
		goto eexit_2;

	/* Add the current item to the list of active epoll hook for this file */
	spin_lock(&tfile->f_ep_lock);
	list_add_tail(&epi->fllink, &tfile->f_ep_links);
	spin_unlock(&tfile->f_ep_lock);

	/* We have to drop the new item inside our item list to keep track of it */
	write_lock_irqsave(&ep->lock, flags);

	/* Add the current item to the rb-tree */
	ep_rbtree_insert(ep, epi);

	/* If the file is already "ready" we drop it inside the ready list */
	if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
		list_add_tail(&epi->rdllink, &ep->rdllist);

		/* Notify waiting tasks that events are available */
		if (waitqueue_active(&ep->wq))
			__wake_up_locked(&ep->wq, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}

	write_unlock_irqrestore(&ep->lock, flags);

	/* We have to call this outside the lock */
	if (pwake)
		ep_poll_safewake(&psw, &ep->poll_wait);

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)\n", current, ep, tfile, fd));

	return 0;

eexit_2:
	ep_unregister_pollwait(ep, epi);

	/*
	 * We need to do this because an event could have been arrived on some
	 * allocated wait queue.
	 */
	write_lock_irqsave(&ep->lock, flags);
	if (ep_is_linked(&epi->rdllink))
		ep_list_del(&epi->rdllink);
	write_unlock_irqrestore(&ep->lock, flags);

	kmem_cache_free(epi_cache, epi);
eexit_1:
	return error;
}
  • ep_ptable_queue_proc
cpp 复制代码
/*
 * This is the callback that is used to add our wait queue to the
 * target file wakeup lists.
 */
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)
{
	struct epitem *epi = ep_item_from_epqueue(pt);
	struct eppoll_entry *pwq;

	if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, SLAB_KERNEL))) {
		init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
		pwq->whead = whead;
		pwq->base = epi;
		add_wait_queue(whead, &pwq->wait);
		list_add_tail(&pwq->llink, &epi->pwqlist);
		epi->nwait++;
	} else {
		/* We have to signal that an error occurred */
		epi->nwait = -1;
	}
}
  • ep_poll_callback
cpp 复制代码
/*
 * This is the callback that is passed to the wait queue wakeup
 * machanism. It is called by the stored file descriptors when they
 * have events to report.
 */
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	int pwake = 0;
	unsigned long flags;
	struct epitem *epi = ep_item_from_wait(wait);
	struct eventpoll *ep = epi->ep;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: poll_callback(%p) epi=%p ep=%p\n",
		     current, epi->ffd.file, epi, ep));

	write_lock_irqsave(&ep->lock, flags);

	/*
	 * If the event mask does not contain any poll(2) event, we consider the
	 * descriptor to be disabled. This condition is likely the effect of the
	 * EPOLLONESHOT bit that disables the descriptor when an event is received,
	 * until the next EPOLL_CTL_MOD will be issued.
	 */
	if (!(epi->event.events & ~EP_PRIVATE_BITS))
		goto is_disabled;

	/* If this file is already in the ready list we exit soon */
	if (ep_is_linked(&epi->rdllink))
		goto is_linked;

	list_add_tail(&epi->rdllink, &ep->rdllist);

is_linked:
	/*
	 * Wake up ( if active ) both the eventpoll wait list and the ->poll()
	 * wait list.
	 */
	if (waitqueue_active(&ep->wq))
		__wake_up_locked(&ep->wq, TASK_UNINTERRUPTIBLE |
				 TASK_INTERRUPTIBLE);
	if (waitqueue_active(&ep->poll_wait))
		pwake++;

is_disabled:
	write_unlock_irqrestore(&ep->lock, flags);

	/* We have to call this outside the lock */
	if (pwake)
		ep_poll_safewake(&psw, &ep->poll_wait);

	return 1;
}

2.4.epoll的特点

接口使用方便:拆分成三个函数,分离输入输出参数,不需要循环设置要关注的文件描述符

数据拷贝轻量:只需在合适的时候调用EPOLL_CTL_ADD将文件描述符拷贝到内核

事件回调机制:避免遍历,使用回调方式将就绪的文件描述符从红黑树加入到就绪队列

没有数量限制:文件描述符数量没有上限

三、Epoll服务器实现

3.1.EpollServer.hpp

cpp 复制代码
#pragma once

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

using namespace SocketModule;
using namespace LogModule;

class EpollServer
{
    const static int size = 64;
    const static int defaultfd = -1;

public:
    EpollServer(int port)
        : _listensock(std::make_unique<TcpSocket>()), _isrunning(false), _epfd(defaultfd)
    {
        // 创建监听套接字 fd = 3
        _listensock->BuildTcpSocketMethod(port);
        // 创建epoll模型 fd = 4
        _epfd = epoll_create(256);
        if (_epfd < 0)
        {
            LOG(LogLevel::FATAL) << "epoll_create error";
            exit(EPOLL_CREATE_ERR);
        }
        LOG(LogLevel::INFO) << "epoll_create success: " << _epfd;
        // 将监听套接字设置到内核
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = _listensock->Fd();
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->Fd(), &ev);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "add listensockfd failed";
            exit(EPOLL_CTL_ERR);
        }
    }

    void Start()
    {
        int timeout = 1000; // ms
        _isrunning = true;
        while (_isrunning)
        {
            int n = epoll_wait(_epfd, _revs, size, timeout);
            switch(n)
            {
                case 0:
                    LOG(LogLevel::DEBUG) << "time out...";
                    break;
                case -1:
                    LOG(LogLevel::FATAL) << "epoll error";
                    break;
                default:
                    Dispatcher(n);
                    break;
            }
        }
        _isrunning = false;
    }

    // 事件派发器
    void Dispatcher(int rnum)
    {
        LOG(LogLevel::DEBUG) << "event ready ..."; // LT: 水平触发模式
        for(int i = 0; i < rnum; i++)
        {
            int sockfd = _revs[i].data.fd;
            uint32_t revent = _revs[i].events;
            // 循环处理就绪事件
            if(revent & EPOLLIN)
            {
                // 读事件就绪
                if(sockfd == _listensock->Fd())
                {
                    // 获取新连接
                    Accepter();
                }
                else
                {
                    // 普通套接字可读
                    Recver(sockfd);
                }
            }
            // if(revent & EPOLLOUT)
            // {
            //     // 写事件就绪
            // }
        }
    }

    // IO处理器
    void Recver(int sockfd)
    {
        char buffer[1024];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say@  " << buffer << std::endl;
        }
        else if (n == 0)
        {
            LOG(LogLevel::INFO) << "client quit...";
            // 从epoll中移除关心的文件描述符
            // 先移除, 再关闭, 确保移除合法fd
            int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
            if(ret > 0)
            {
                LOG(LogLevel::INFO) << "remove sockfd success: " << sockfd;
            }
            close(sockfd);
        }
        else
        {
            LOG(LogLevel::ERROR) << "recv error";
            int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
            if(ret > 0)
            {
                LOG(LogLevel::INFO) << "remove sockfd success: " << sockfd;
            }
            close(sockfd);
        }
    }

    // 连接管理器
    void Accepter()
    {
        InetAddr client;
        int sockfd = _listensock->Accept(&client);
        if (sockfd >= 0)
        {
            // 获取新连接
            LOG(LogLevel::INFO) << "get a new link, sockfd: " << sockfd << ", client is " << client.StringAddr();
            // 将新的套接字添加到内核
            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = sockfd;
            int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
            if(n < 0)
            {
                LOG(LogLevel::WARNING) << "add sockfd failed";
            }
            else
            {
                LOG(LogLevel::INFO) << "add sockfd success: " << sockfd;
            }
        }
    }

    void Stop()
    {
        _isrunning = false;
    }

    ~EpollServer()
    {
        _listensock->Close();
        if (_epfd > 0)
        {
            close(_epfd);
        }
    }

private:
    std::unique_ptr<Socket> _listensock;
    bool _isrunning;
    int _epfd;                     // epoll模型
    struct epoll_event _revs[size]; // 就绪事件数组
};

3.2.Main.cc

cpp 复制代码
#include "EpollServer.hpp"

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " port" << std::endl;
        exit(USAGE_ERR);
    }

    Enable_Console_Log_Strategy();
    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<EpollServer> svr = std::make_unique<EpollServer>(port);
    svr->Start();
    
    return 0;
}

3.3.Common.hpp

cpp 复制代码
#pragma once

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

enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR,
    OPEN_ERR,
    EPOLL_CREATE_ERR,
    EPOLL_CTL_ERR
};

// 禁止拷贝
class NoCopy
{
public:
    NoCopy()
    {
    }
    ~NoCopy()
    {
    }
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy &) = delete;
};

#define CONV(addr) ((struct sockaddr *)&addr)

四、LT与ET模式

4.1.LT水平触发模式

水平触发:示波器长时间保持高电平或者低电平

**LT模式(Level Triggered):**水平触发工作模式(默认)

只要底层有报文,就要一直通知上层

上层知道还会通知,所以可以不读完

4.2.ET边缘触发模式

边缘触发:示波器从高电平变化为低电平,或者从低电平变化为高电平

**ET模式(Edge Triggered):**边缘触发工作模式

只有底层的数据发生从无到有,从有到多的变化时,才会通知上层

即使上层只把数据拿走一部分,后来数据没有新增,ET也不会通知

循环读取 + 非阻塞IO

ET通知就绪,用户必须通过循环读取本轮缓冲区的全部数据

recv读完缓冲区数据,用户并不清楚,继续调用recv会被阻塞

所以在ET模式下,必须将文件描述符设置为非阻塞的工作模式

LT VS ET

LT模式可以实现非阻塞轮询形式,但必须对程序员进行约束

ET模式则是通过操作系统来约束,增加IO读写方式的确定性

ET通知效率更高,有效通知数量最多

ET尽快读完所有数据,能给对方更新一个更大的窗口

提高对方滑动窗口大小,提高TCP传输效率,提高网络发送报文的并发度

PSH标志位本质:提醒上层读取数据

五、Reactor反应堆模式

5.1.模式框图

读事件的关心需要常设,写事件的关心需要按需设置

如果写事件设置为常设,epoll就会一直让写事件就绪

当把发送缓冲区写满后,再向epoll设置写事件的关心

手动开启EPOLLOUT,默认就要触发一次写事件

5.2.Epoller.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/epoll.h>
#include "Log.hpp"
#include "Common.hpp"

using namespace LogModule;

class Epoller
{
public:
    Epoller()
        : _epfd(-1)
    {
        _epfd = epoll_create(128);
        if (_epfd < 0)
        {
            LOG(LogLevel::FATAL) << "epoll_create error";
            exit(EPOLL_CREATE_ERR);
        }
        LOG(LogLevel::INFO) << "create epoll success: " << _epfd;
    }

    void ModEventHelper(int sockfd, uint32_t events, int oper)
    {
        struct epoll_event ev;
        ev.events = events;
        ev.data.fd = sockfd;
        int n = epoll_ctl(_epfd, oper, sockfd, &ev);
        if (n < 0)
        {
            LOG(LogLevel::ERROR) << "epoll_ctl error";
            return;
        }
        LOG(LogLevel::INFO) << "epoll_ctl success: " << sockfd;
    }

    void AddEvent(int sockfd, uint32_t events)
    {
        ModEventHelper(sockfd, events, EPOLL_CTL_ADD);
    }

    void ModEvent(int sockfd, uint32_t events)
    {
        ModEventHelper(sockfd, events, EPOLL_CTL_MOD);
    }

    void DelEvent(int sockfd)
    {
        int n = epoll_ctl(sockfd, EPOLL_CTL_DEL, sockfd, nullptr);
        (void)n;
    } 

    int WaitEvents(struct epoll_event revs[], int maxnum, int timeout)
    {
        int n = epoll_wait(_epfd, revs, maxnum, timeout);
        if (n < 0)
        {
            LOG(LogLevel::WARNING) << "epoll_wait error";
        }
        else if (n == 0)
        {
            LOG(LogLevel::WARNING) << "epoll_wait timeout";
        }
        else
        {
            // TODO
        }
        return n;
    }

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

private:
    int _epfd;
};

5.3.Reactor.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <memory>
#include <unordered_map>
#include "Epoller.hpp"
#include "Connection.hpp"
#include "Log.hpp"

using namespace LogModule;

// 反应堆
class Reactor
{
    static const int revs_num = 128;

private:
    bool IsConnectionExitsHelper(int sockfd)
    {
        auto iter = _connections.find(sockfd);
        if (iter == _connections.end())
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    bool IsConnectionExits(const std::shared_ptr<Connection> &conn)
    {
        return IsConnectionExitsHelper(conn->GetSockFd());
    }

    bool IsConnectionExits(int sockfd)
    {
        return IsConnectionExitsHelper(sockfd);
    }

    bool IsConnectionEmpty()
    {
        return _connections.empty();
    }

    int LoopOnce(int timeout)
    {
        return _epoller_ptr->WaitEvents(_revs, revs_num, timeout);
    }

    // 事件派发器
    void Dispatcher(int n)
    {
        for (int i = 0; i < n; i++)
        {
            int sockfd = _revs[i].data.fd;      // 就绪的文件描述符
            uint32_t revents = _revs[i].events; // 就绪的事件
            // 1. 将所有的异常处理统一转化成IO错误
            // 2. 将所有的IO异常统一转化成一个异常处理函数
            if (revents & EPOLLERR)
            {
                // 处理异常
                revents |= (EPOLLIN | EPOLLOUT);
            }
            if (revents & EPOLLHUP)
            {
                // 处理连接
                revents |= (EPOLLIN | EPOLLOUT);
            }
            if (revents & EPOLLIN)
            {
                // 读事件就绪
                if (IsConnectionExits(sockfd))
                {
                    _connections[sockfd]->Recver();
                }
            }
            if (revents & EPOLLOUT)
            {
                // 写事件就绪
                if (IsConnectionExits(sockfd))
                {
                    _connections[sockfd]->Sender();
                }
            }
        }
    }

public:
    Reactor()
        : _epoller_ptr(std::make_unique<Epoller>()), _isrunning(false)
    {
    }

    void Loop()
    {
        if (IsConnectionEmpty())
        {
            return;
        }
        _isrunning = true;
        int timeout = -1;
        while (_isrunning)
        {
            PrintConnection(); // debug
            int n = LoopOnce(timeout);
            Dispatcher(n);
        }
        _isrunning = false;
    }

    // 将新连接添加到哈希表, 写入到epoll内核
    void AddConnection(std::shared_ptr<Connection> &conn)
    {
        // 0. 不要重复添加连接
        if (IsConnectionExits(conn))
        {
            LOG(LogLevel::WARNING) << "conn is exits: " << conn->GetSockFd();
            return;
        }

        // 1. conn对应的fd与关心事件写入epoll内核
        uint32_t events = conn->GetEvent();
        int sockfd = conn->GetSockFd();
        _epoller_ptr->AddEvent(sockfd, events);

        // 2. 设置conn的回指指针
        conn->SetOwner(this);

        // 3. conn对象添加到哈希表中
        _connections[sockfd] = conn;
    }

    // 设置对读写事件的关心
    void EnableReadWrite(int sockfd, bool enableread, bool enablewrite)
    {
        // 判断文件描述符是否存在
        if (!IsConnectionExits(sockfd))
        {
            LOG(LogLevel::WARNING) << "EnableReadWrite, conn is exists: " << sockfd;
            return;
        }

        // 修改当前文件描述符对应的关心事件
        uint32_t new_event = (EPOLLET | (enableread ? EPOLLIN : 0) | (enablewrite ? EPOLLOUT : 0));
        _connections[sockfd]->SetEvent(new_event);

        // 写透内核, 调整文件描述符对特定事件的关心
        _epoller_ptr->ModEvent(sockfd, new_event);
    }

    // 删除连接
    void DelConnection(int sockfd)
    {
        // epoll移除的时候, sockfd必须合法
        _epoller_ptr->DelEvent(sockfd);

        // 从哈希表中移除自己
        _connections.erase(sockfd);

        // 关闭不需要的文件描述符
        close(sockfd);

        LOG(LogLevel::INFO) << "client quit: " << sockfd;
    }

    void PrintConnection()
    {
        std::cout << "当前Reactor正在管理的fd List: ";
        for(auto &conn : _connections)
        {
            std::cout << conn.second->GetSockFd() << " ";
        }
        std::cout << "\r\n";
    }

    void Stop()
    {
        _isrunning = false;
    }

    ~Reactor()
    {
    }

private:
    std::unique_ptr<Epoller> _epoller_ptr;                             // epoll模型
    std::unordered_map<int, std::shared_ptr<Connection>> _connections; // 管理所有的连接
    bool _isrunning;                                                   // 启动标志位
    struct epoll_event _revs[revs_num];                                // 就绪事件存储数组
};

5.4.Connection.hpp

cpp 复制代码
#pragma once

// 封装文件描述符, 保证给每一个文件描述符一套缓冲区

#include <iostream>
#include <string>
#include <functional>
#include "InetAddr.hpp"

class Reactor;
class Connection;
using handler_t = std::function<std::string(std::string &)>;

// 基类
class Connection
{
public:
    Connection()
        : _events(0), _owner(nullptr)
    {
    }

    virtual void Recver() = 0;
    virtual void Sender() = 0;
    virtual void Excepter() = 0;
    virtual int GetSockFd() = 0;

    void RegisterHandler(handler_t handler)
    {
        _handler = handler;
    }

    void SetEvent(const uint32_t &events)
    {
        _events = events;
    }

    uint32_t GetEvent()
    {
        return _events;
    }

    void SetOwner(Reactor *owner)
    {
        _owner = owner;
    }

    Reactor *GetOwner()
    {
        return _owner;
    }

    ~Connection()
    {
    }

private:
    uint32_t _events; // 关心事件
    Reactor *_owner;  // 回指指针
public:
    handler_t _handler; // 回调方法
};

5.5.Listener.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <memory>
#include "Epoller.hpp"
#include "Socket.hpp"
#include "Common.hpp"
#include "Connection.hpp"
#include "Channel.hpp"

using namespace SocketModule;

// 专门用来获取新连接

class Listener : public Connection
{
public:
    Listener(int port = defaultport)
        : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildTcpSocketMethod(_port);
        SetEvent(EPOLLIN | EPOLLET);
        SetNonBlock(_listensock->Fd());
    }

    void Recver() override
    {
        // LOG(LogLevel::DEBUG) << "进入Listener模块的Recver函数";
        // ET模式下, 循环获取全部连接, 监听套接字设置为非阻塞
        InetAddr client;
        while (true)
        {
            int sockfd = _listensock->Accept(&client);
            if (sockfd == ACCEPT_ERR)
            {
                break;
            }
            else if (sockfd == ACCEPT_CONTINUE)
            {
                continue;
            }
            else if (sockfd == ACCEPT_DONE)
            {
                break;
            }
            else
            {
                // 连接获取成功
                // 普通的文件描述符
                std::shared_ptr<Connection> conn = std::make_shared<Channel>(sockfd, client);
                conn->SetEvent(EPOLLIN | EPOLLET);
                if (_handler != nullptr)
                {
                    conn->RegisterHandler(_handler);
                }
                GetOwner()->AddConnection(conn);
            }
        }
    }

    int GetSockFd() override
    {
        return _listensock->Fd();
    }

    void Sender() override
    {
    }

    void Excepter() override
    {
    }

    ~Listener()
    {
    }

private:
    int _port;
    std::unique_ptr<Socket> _listensock;
};

5.6.Channel.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <functional>
#include "Common.hpp"
#include "Connection.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;
#define SIZE 1024

class Channel;

// 普通套接字封装

class Channel : public Connection
{
public:
    Channel(int sockfd, const InetAddr &client)
        : _sockfd(sockfd), _client_addr(client)
    {
        SetNonBlock(_sockfd);
    }

    void Recver() override
    {
        char buffer[SIZE];
        while (true) // 循环读取, 保证本轮事件数据读完(本层只解决IO问题)
        {
            // 粘包问题与序列反序列化问题(引入自定义协议, 由应用层解决)
            buffer[0] = 0; // 清空缓冲区
            ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                _inbuffer += buffer; // 接收缓冲区中, 入队列的过程
            }
            else if (n == 0)
            {
                Excepter(); // 异常处理
                return;
            }
            else
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 底层数据读完
                    break;
                }
                else if (errno == EINTR)
                {
                    // 被信号中断
                    continue;
                }
                else
                {
                    // 读取出错
                    Excepter(); // 异常处理
                    return;
                }
            }
        }
        LOG(LogLevel::DEBUG) << "Channel Inbuffer: " << _inbuffer;
        if (!_inbuffer.empty())
        {
            _outbuffer += _handler(_inbuffer);
        }
        if (!_outbuffer.empty())
        {
            Sender();
            // GetOwner()->EnableReadWrite(_sockfd, true, true);
        }
    }

    void Sender() override
    {
        while (true)
        {
            ssize_t n = send(_sockfd, _outbuffer.c_str(), _outbuffer.size(), 0);
            if(n > 0)
            {
                _outbuffer.erase(0, n);
                if(_outbuffer.empty())
                {
                    break;
                }
            }
            else if(n == 0)
            {
                break;
            }
            else
            {
                if(errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 缓冲区写满
                    break;
                }
                if(errno == EINTR)
                {
                    // 被信号中断
                    continue;
                }
                else
                {
                    // 发送出错
                    Excepter(); // 异常处理
                    return;
                }
            }
        }

        if(!_outbuffer.empty())
        {
            // 1. 发送缓冲区写满(发送条件不具备)
            // 开启对写事件的关心
            GetOwner()->EnableReadWrite(_sockfd, true, true);
        }
        else
        {
            // 2. 数据发送完毕
            // 关闭对写事件的关心
            GetOwner()->EnableReadWrite(_sockfd, true, false);
        }
    }

    int GetSockFd() override
    {
        return _sockfd;
    }

    void Excepter() override
    {
        // 处理所有的异常
        // 移除文件描述符, 删除connection
        GetOwner()->DelConnection(_sockfd);
    }

    std::string &Inbuffer()
    {
        return _inbuffer;
    }

    void AppendOutBuffer(const std::string &out)
    {
        _outbuffer += out;
    }

    ~Channel()
    {
    }

private:
    int _sockfd;            // 该连接的文件描述符
    std::string _inbuffer;  // 输入缓冲区
    std::string _outbuffer; // 输出缓冲区
    InetAddr _client_addr;  // 客户端信息
};

5.7.Protocol.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>
#include <functional>
#include "Socket.hpp"
#include "Log.hpp"

// 自定义网络版本的计算器

using namespace SocketModule;
using namespace LogModule;

const std::string ProtSep = " ";

// client → server
class Request
{
public:
    Request()
    {
    }

    Request(int x, int y, char oper)
        : _x(x), _y(y), _oper(oper)
    {
    }

    // 序列化
    std::string Serialize()
    {
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;

        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }

    // 反序列化
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
        }
        return ok;
    }

    ~Request()
    {
    }

    int X()
    {
        return _x;
    }

    int Y()
    {
        return _y;
    }

    char Oper()
    {
        return _oper;
    }

private:
    int _x;
    int _y;
    char _oper;
};

// server → client
class Response
{
public:
    Response()
    {
    }

    Response(int result, int code)
        : _result(result), _code(code)
    {
    }

    // 序列化
    std::string Serialize()
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        Json::FastWriter writer;
        return writer.write(root);
    }

    // 反序列化
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
        }
        return ok;
    }

    ~Response()
    {
    }

    void SetResult(int res)
    {
        _result = res;
    }

    void SetCode(int code)
    {
        _code = code;
    }

    void ShowResult()
    {
        std::cout << "计算结果是: " << _result << "[ " << _code << " ]" << std::endl;
    }

private:
    int _result; // 运算结果
    int _code;   // 0: success 1,2,3,4: 不同异常
};

const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>; // 计算业务回调

class Protocol
{
public:
    Protocol()
    {
    }
    Protocol(func_t func)
        : _func(func)
    {
    }

    // 编码
    std::string Encode(const std::string jsonstr)
    {
        std::string len = std::to_string(jsonstr.size());
        return len + sep + jsonstr + sep;
    }

    // 解码
    bool Decode(std::string &buffer, std::string *package)
    {
        ssize_t pos = buffer.find(sep);
        if (pos == std::string::npos)
        {
            return false;
        }
        // 获取报文长度
        std::string package_len_str = buffer.substr(0, pos);
        int package_len_int = std::stoi(package_len_str);
        // 计算一个完整报文长度
        int target_len = package_len_str.size() + sep.size() + package_len_int + sep.size();
        // 保证buffer里至少有一个完整报文
        if (buffer.size() < target_len)
        {
            return false;
        }
        // 提取一个完整报文
        *package = buffer.substr(pos + sep.size(), package_len_int);
        // 将报文移除缓冲区
        buffer.erase(0, target_len);
        return true;
    }

    std::string Execute(std::string &package)
    {
        Request req;
        bool ok = req.Deserialize(package);
        if (!ok)
        {
            return std::string();
        }
        // 完成计算功能
        Response resp = _func(req);
        // 序列化
        std::string json_str = resp.Serialize();
        // 添加自定义长度
        std::string send_str = Encode(json_str);
        // 返回运算结果
        return send_str;
    }

    bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
    {
        while (true)
        {
            int n = client->Recv(&resp_buff);
            if (n > 0)
            {
                // 解析报文
                std::string json_package;
                while (Decode(resp_buff, &json_package))
                {
                    // 反序列化
                    resp->Deserialize(json_package);
                }
                return true;
            }
            else if (n == 0)
            {
                std::cout << "server quit" << std::endl;
                return false;
            }
            else
            {
                std::cout << "recv error" << std::endl;
                return false;
            }
        }
    }

    std::string BuildRequestString(int x, int y, char oper)
    {
        // 构建一个完整的请求
        Request req(x, y, oper);
        // 序列化
        std::string json_req = req.Serialize();
        // 添加自定义长度
        return Encode(json_req);
    }

    ~Protocol()
    {
    }

private:
    func_t _func; // 计算业务回调
};

5.8.NetCal.hpp

cpp 复制代码
#pragma once

#include "Protocol.hpp"
#include <iostream>

class Cal
{
public:
    Response Execute(Request &req)
    {
        Response resp(0, 0);
        switch (req.Oper())
        {
        case '+':
            resp.SetResult(req.X() + req.Y());
            break;
        case '-':
            resp.SetResult(req.X() - req.Y());
            break;
        case '*':
            resp.SetResult(req.X() * req.Y());
            break;
        case '/':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(1); // 1: 除0错误
            }
            else
            {
                resp.SetResult(req.X() / req.Y());
            }
        }
        break;
        case '%':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(2); // 2: 模0错误
            }
            else
            {
                resp.SetResult(req.X() % req.Y());
            }
        }
        break;
        default:
            resp.SetCode(3); // 3: 非法操作
            break;
        }

        return resp;
    }
};

5.9.Main.cc

cpp 复制代码
#include <iostream>
#include <string>
#include "Reactor.hpp"
#include "Listener.hpp"
#include "Channel.hpp"
#include "Log.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include "NetCal.hpp"

static void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

// ./server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    LogModule::ConsoleLogStrategy();
    uint16_t port = std::stoi(argv[1]);

    // 构建业务对象
    std::shared_ptr<Cal> cal = std::make_shared<Cal>();

    // 构建Protocol对象
    std::shared_ptr<Protocol> protocol = std::make_shared<Protocol>(
        [&cal](Request &req) -> Response
        {
            return cal->Execute(req);
        });

    // 构建Listener对象
    std::shared_ptr<Connection> conn = std::make_shared<Listener>(port);
    conn->RegisterHandler(
        [&protocol](std::string &inbuffer) -> std::string
        {
            std::string response_str;
            while (true)
            {
                std::string package;
                if (!protocol->Decode(inbuffer, &package))
                {
                    break;
                }
                response_str += protocol->Execute(package);
            }
            return response_str;
        });

    // 构建Reactor对象
    std::unique_ptr<Reactor> R = std::make_unique<Reactor>();

    R->AddConnection(conn);
    R->Loop();

    return 0;
}

5.10.TcpClient.cc

cpp 复制代码
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
#include <memory>

using namespace SocketModule;

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}

// 获取数据
void GetDataFromStdin(int *x, int *y, char *oper)
{
    std::cout << "Please Enter x: ";
    std::cin >> *x;
    std::cout << "Please Enter y: ";
    std::cin >> *y;
    std::cout << "Please Enter oper: ";
    std::cin >> *oper;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();
    client->BuildTcpClientSocketMethod();

    if (client->Connect(server_ip, server_port) != 0)
    {
        // 连接失败
        std::cerr << "connect error" << std::endl;
        exit(CONNECT_ERR);
    }

    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
    std::string resp_buffer;

    // 服务器连接成功
    while (true)
    {
        // 获取数据
        int x, y;
        char oper;
        GetDataFromStdin(&x, &y, &oper);
        // 构建请求
        std::string req_str = protocol->BuildRequestString(x, y, oper);
        // 发送请求
        client->Send(req_str);
        // 获取应答
        Response resp;
        bool res = protocol->GetResponse(client, resp_buffer, &resp);
        if (res == false)
        {
            break;
        }
        // 显示结果
        resp.ShowResult();
    }
    client->Close();
    return 0;
}

5.11.Socket.hpp

cpp 复制代码
#pragma once

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

namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog = 16;
    // 设计模式: 模板方法模式
    class Socket
    {
    public:
        virtual ~Socket() {}                                                  // 析构函数
        virtual void SocketOrDie() = 0;                                       // 创建套接字
        virtual void BindOrDie(uint16_t port) = 0;                            // 绑定IP和端口号
        virtual void ListenOrDie(int backlog) = 0;                            // 监听
        virtual int Accept(InetAddr *client) = 0;                             // 获取
        virtual void Close() = 0;                                             // 关闭
        virtual int Recv(std::string *out) = 0;                               // 读取
        virtual int Send(const std::string &message) = 0;                     // 写入
        virtual int Connect(const std::string &server_ip, uint16_t port) = 0; // 连接
        virtual int Fd() = 0;

    public:
        void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
    };

    const static int defaultfd = -1;
    class TcpSocket : public Socket
    {
    public:
        TcpSocket()
            : _sockfd(defaultfd)
        {
        }
        TcpSocket(int fd)
            : _sockfd(fd)
        {
        }

        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "socket error";
                exit(SOCKET_ERR);
            }
            int opt = 1;
            setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
            LOG(LogLevel::INFO) << "socket success: " << _sockfd;
        }

        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if (n < 0)
            {
                LOG(LogLevel::FATAL) << "bind error";
                exit(BIND_ERR);
            }
            LOG(LogLevel::INFO) << "bind success";
        }

        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                LOG(LogLevel::FATAL) << "listen error";
                exit(LISTEN_ERR);
            }
            int opt = 1;
            setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 设置地址复用
            LOG(LogLevel::INFO) << "listen success";
        }

#define ACCEPT_ERR -3
#define ACCEPT_CONTINUE -2
#define ACCEPT_DONE -1
        int Accept(InetAddr *client) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = ::accept(_sockfd, CONV(peer), &len);
            if (fd < 0)
            {
                // LOG(LogLevel::WARNING) << "accept warning...";
                // return -1;
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    return ACCEPT_DONE; // 底层没有新连接
                }
                else if (errno == EINTR)
                {
                    return ACCEPT_CONTINUE; // 被信号中断, 继续读取
                }
                else
                {
                    LOG(LogLevel::WARNING) << "accept error";
                    return ACCEPT_ERR; // 读取出错
                }
            }
            return fd;
        }

        void Close() override
        {
            if (_sockfd >= 0)
            {
                ::close(_sockfd);
            }
        }

        int Recv(std::string *out) override
        {
            // 流式读取
            char buffer[4096];
            ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                *out += buffer;
            }
            return n;
        }

        int Send(const std::string &message) override
        {
            return ::send(_sockfd, message.c_str(), message.size(), 0);
        }

        int Connect(const std::string &server_ip, uint16_t port) override
        {
            InetAddr server(server_ip, port);
            return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
        }

        int Fd() override
        {
            return _sockfd;
        }

        ~TcpSocket()
        {
        }

    private:
        int _sockfd;
    };
};

5.12.Log.hpp

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include <filesystem> // C++17中 文件操作的相关封装
#include <fstream>
#include "Mutex.hpp"
#include <memory>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <cstdio>

namespace LogModule
{
    using namespace MutexModule;
    const std::string gsep = "\r\n";

    // 2. 刷新策略(策略模式: C++多态)

    // 策略基类
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 策略a: 显示器打印
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }

        void SyncLog(const std::string &message) override
        {
            // 加锁
            LockGuard lockguard(_mutex);

            // 打印日志
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    // 缺省参数
    const std::string defaultpath = "/var/log/";
    const std::string defaultfile = "my.log";

    // 策略b: 指定文件写入
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            // 加锁
            LockGuard lockguard(_mutex);
            // 如果当前路径存在
            if (std::filesystem::exists(_path))
            {
                return;
            }
            // 如果当前路径不存在
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }

        void SyncLog(const std::string &message) override
        {
            // 加锁
            LockGuard lockguard(_mutex);
            // "./log" + "/" + "my.log"
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            // 以追加的方式打开文件
            std::ofstream out(filename, std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            // 写入日志
            out << message << gsep;
            // 关闭文件
            out.close();
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件名称

        Mutex _mutex; // 互斥锁
    };

    // 形成完整日志 && 根据策略选择不同刷新方式

    // 1. 形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string LeveltoStr(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOW";
        }
    }

    // 2. 获取时间方法
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d-%02d-%02d",
                 curr_tm.tm_year + 1900, curr_tm.tm_mon + 1, curr_tm.tm_mday,
                 curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);
        return timebuffer;
    }

    // 日志类
    class Logger
    {
    public:
        Logger()
        {
            // 默认使用显示器
            EnableConsoleLogStrategy();
        }

        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 内部类: 表示未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger)
            {
                // 日志左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LeveltoStr(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 日志右半部分
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:
            std::string _curr_time; // 时间
            LogLevel _level;        // 等级
            pid_t _pid;             // 进程PID
            std::string _src_name;  // 文件名
            int _line_number;       // 行号
            std::string _loginfo;   // 一条完整的日志信息
            Logger &_logger;
        };

        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;
    };

    // 全局日志对象
    Logger logger;

// 使用宏简化用户操作, 获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enbale_File_Log_Strategy() logger.EnableFileLogStrategy()
};

#endif

5.13.InetAddr.hpp

cpp 复制代码
#pragma once

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

// 网络地址 <=> 主机地址

class InetAddr
{
public:
    InetAddr()
    {
    }
    InetAddr(struct sockaddr_in &addr)
    {
       SetAddr(addr);
    }
    InetAddr(const std::string &ip, uint16_t port)
        : _ip(ip), _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        // 主机序列 → 网络序列(端口号)
        _addr.sin_port = htons(_port);
        // 点分十进制 → 网络序列(IP地址)
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
    }
    InetAddr(uint16_t port)
        : _port(port), _ip("0")
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = htons(_port);
        _addr.sin_addr.s_addr = INADDR_ANY;
    }
    void SetAddr(struct sockaddr_in &addr)
    {
        _addr = addr;
        // 网络序列 → 主机序列(端口号)
        _port = ntohs(_addr.sin_port);
        // 网络序列 → 点分十进制(IP地址)
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
        _ip = ipbuffer;
    }
    uint16_t Port()
    {
        return _port;
    }
    std::string Ip()
    {
        return _ip;
    }
    const struct sockaddr_in &NetAddr()
    {
        return _addr;
    }
    const struct sockaddr *NetAddrPtr()
    {
        return CONV(_addr);
    }
    socklen_t NetAddrLen()
    {
        return sizeof(_addr);
    }
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }
    std::string StringAddr()
    {
        return _ip + " : " + std::to_string(_port);
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

5.14.Common.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR,
    OPEN_ERR,
    EPOLL_CREATE_ERR,
    EPOLL_CTL_ERR
};

// 禁止拷贝
class NoCopy
{
public:
    NoCopy()
    {
    }
    ~NoCopy()
    {
    }
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy &) = delete;
};

int defaultport = 8080;

void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if(fl < 0)
    {
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

#define CONV(addr) ((struct sockaddr *)&addr)

5.15.Makefile

cpp 复制代码
server:Main.cc
	g++ -o $@ $^ -std=c++17 -l jsoncpp
.PHONY:clean
clean:
	rm -f server

六、OTOL设计模式

6.1.核心概念

One Thread One Loop:描述基于事件驱动编程的架构

一个执行流,一个Reactor

每一个线程运行一个独立的事件循环,从而实现高效的并发处理

6.2.多进程实现方案

6.3.多线程实现方案

方案一:

方案二:

相关推荐
Frank_refuel1 小时前
【C++项目】手撕JRpc框架(单机怎么优化到分布式思想)
开发语言·c++
geats人山人海1 小时前
linux 1.目录结构
linux·运维·服务器
ShineWinsu1 小时前
对于Redis:Hash类型的解析
c++·redis·分布式·缓存·面试·hash·哈希表
wuminyu1 小时前
JEP491中synchronized关键字引发的平台线程钉住解决方法简介
java·linux·c语言·jvm·c++
All for pursuit.1 小时前
【贪心-4】581.最短无序连续子数组
数据结构·c++·算法·leetcode
应用市场2 小时前
嵌入式Linux从裸板到产品(一):全景地图与交叉编译环境,从四个上板翻车现场说
linux·运维·服务器
皓月盈江2 小时前
Linux系统PC与Linux系统服务器通过scp上传与下载文件
linux·运维·服务器·scp·文件上传·文件下载
对讲机数码科普2 小时前
危化厂区防爆专网通信建设实践:从合规框架到验收清单
运维·网络·架构
傲世仙尊2 小时前
HTTP深水区-重定向状态码与GETPOST的传参之路
网络·网络协议·http