《ZLToolKit源码学习笔记》(20)网络模块之TcpServer

系列文章目录 

《ZLToolKit源码学习笔记》(1)VS2019源码编译

《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析

《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析

《ZLToolKit源码学习笔记》(4)工具模块之消息广播器

《ZLToolKit源码学习笔记》(5)工具模块之资源池

《ZLToolKit源码学习笔记》(6)线程模块之整体框架概述

《ZLToolKit源码学习笔记》(7)线程模块之线程池组件:任务队列与线程组

《ZLToolKit源码学习笔记》(8)线程模块之线程负载计算器

《ZLToolKit源码学习笔记》(9)线程模块之任务执行器

《ZLToolKit源码学习笔记》(10)线程模块之线程池

《ZLToolKit源码学习笔记》(11)线程模块之工作线程池WorkThreadPool

《ZLToolKit源码学习笔记》(12)事件轮询模块之整体框架概述

《ZLToolKit源码学习笔记》(13)事件轮询模块之管道的简单封装

《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器

《ZLToolKit源码学习笔记》(15)事件轮询模块之事件轮询器EventPoller

《ZLToolKit源码学习笔记》(16)网络模块之整体框架概述

《ZLToolKit源码学习笔记》(17)网络模块之基础接口封装类SockUtil

《ZLToolKit源码学习笔记》(18)网络模块之Buffer缓存

《ZLToolKit源码学习笔记》(19)网络模块之套接字封装

《ZLToolKit源码学习笔记》(20)网络模块之TcpServer(本文)

《ZLToolKit源码学习笔记》(21)网络模块之TcpClient与Session

《ZLToolKit源码学习笔记》(22)网络模块之UdpServer


前言

本节学习ZLToolKit的TCP服务器封装。


目录

系列文章目录

前言

一、概述

1.1、类图

1.2、网络模型

1.3、服务器处理流程

二、功能分析

2.1、setOnCreateSocket

2.2、getPort

2.3、构造函数

2.4、start

三、使用

四、知识点整理(后续补充)

4.1、如何处理客户端的connect

4.2、 如何接收客户端发送过来的数据

4.3、服务器向客户端发送数据的过程


一、概述

1.1、类图

Server类作为基类,将服务器和EventPoller进行了关联。

1.2、网络模型

服务器模型:多线程+epoll (select),一个listen fd + 多个epoll实例

每一个线程都创建了一个epoll实例,并以ET边沿触发模式监听同一个listen fd的读事件,使用EPOLLEXCLUSIVE 标志位防止惊群效应,线程阻塞在epoll_wait上等待客户端连接,默认情况下,当前线程accept的client fd也会添加到当前线程epoll中管理,但TcpServer改变了这一情况,它将client fd添加到了负载较轻的epoll线程中管理。client fd同样使用ET边沿触发模式。

1.3、服务器处理流程

创建TcpServer对象时,会根据CPU核心数创建多个EventPoller线程对象,比如,本人PC是8核心,那么将会创建8个事件轮询器对象,TcpServer对象会与其中一个EventPoller进行关联。

在调用start接口时,会克隆7个TcpServer子对象,分别与其余7个EventPoller对象关联。

这8个TcpServer的listen fd完全一致,并且分别在8个EventPoller线程中进行读事件(accept)监听,即8个线程监听了同一个fd的读事件。

当收到客户端请求时,由操作系统选择其中一个线程来处理,实现了客户端会话处理的负载均衡。(需考虑不同平台下系统是否都能做到负载均衡,以及是否有惊群问题)

每一个client请求,都被封装成一个TcpSession,交由对应的TcpServer对象管理。并在关联的EventPoller线程中,监听了client fd的读、写、异常事件。

服务器与客户端大致的交互流程如下:


二、功能分析

2.1、setOnCreateSocket

void TcpServer::setOnCreateSocket(Socket::onCreateSocket cb) {
    if (cb) {
        _on_create_socket = std::move(cb);
    } else {
        _on_create_socket = [](const EventPoller::Ptr &poller) {
            return Socket::createSocket(poller, false);
        };
    }
    for (auto &pr : _cloned_server) {
        pr.second->setOnCreateSocket(cb);
    }
}

自定义socket对象的构造方式,因为该接口只能在TcpServer对象实例化之后调用,而监听socket在构造函数中就已经创建,所以通过该接口只能控制之后的client socket的创建方式。

2.2、getPort

获取服务器监听端口号, 如果没有指定端口号,将选择监听随机端口,通过该接口可以获取。

2.3、构造函数

TcpServer::TcpServer(const EventPoller::Ptr &poller) : Server(poller) {
    setOnCreateSocket(nullptr);
    _socket = createSocket(_poller);
    _socket->setOnBeforeAccept([this](const EventPoller::Ptr &poller) {
        return onBeforeAcceptConnection(poller);
    });
    _socket->setOnAccept([this](Socket::Ptr &sock, shared_ptr<void> &complete) {
        auto ptr = sock->getPoller().get();
        auto server = getServer(ptr);
        ptr->async([server, sock, complete]() {
            //该tcp客户端派发给对应线程的TcpServer服务器
            server->onAcceptConnection(sock);
        });
    });
}

创建监听socket,并设置accept时,socket构造事件回调(即client socket构造)以及tcp监听接收到连接回调。

2.4、start

主要看下以下流程,该接口内部会创建多个子TcpServer对象,这些子TcpServer对象通过Socket对象克隆的方式在多个poller线程中监听同一个listen fd,这样这个TCP服务器将会通过抢占式accept的方式把客户端均匀的分布到不同的poller线程,通过该方式能实现客户端负载均衡以及提高连接接收速度。

EventPollerPool::Instance().for_each([&](const TaskExecutor::Ptr &executor) {
        EventPoller::Ptr poller = dynamic_pointer_cast<EventPoller>(executor);
        if (poller == _poller || !poller) {
            return;
        }
        auto &serverRef = _cloned_server[poller.get()];
        if (!serverRef) {
            serverRef = onCreatServer(poller);
        }
        if (serverRef) {
            serverRef->cloneFrom(*this);
        }
    });


void TcpServer::cloneFrom(const TcpServer &that) {
    if (!that._socket) {
        throw std::invalid_argument("TcpServer::cloneFrom other with null socket!");
    }
    _on_create_socket = that._on_create_socket;
    _session_alloc = that._session_alloc;
    _socket->cloneFromListenSocket(*(that._socket));
    weak_ptr<TcpServer> weak_self = std::dynamic_pointer_cast<TcpServer>(shared_from_this());
    _timer = std::make_shared<Timer>(2.0f, [weak_self]() -> bool {
        auto strong_self = weak_self.lock();
        if (!strong_self) {
            return false;
        }
        strong_self->onManagerSession();
        return true;
    }, _poller);
    this->mINI::operator=(that);
    _parent = &that;
}

在此处再次对listen fd进行了监听。 

_socket->cloneFromListenSocket(*(that._socket));

三、使用

 TCP服务器的测试程序见ZLToolKit\tests\test_tcpEchoServer.cpp。


四、知识点整理(后续补充)

4.1、如何处理客户端的connect

ET+循环accept

4.2、 如何接收客户端发送过来的数据

4.3、服务器向客户端发送数据的过程

使用缓存,监听了socket的写事件,默认直接写数据,当写满时,等待触发可写事件,然后继续发送缓存中剩余数据;

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Libevent是一个事件驱动的网络编程框架,而event.h是其核心头文件之一。该头文件定义了事件处理相关的结构体、函数和宏等内容。 下面是event.h中常用的一些定义和函数: ### 1.事件回调函数 ```c typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg); ``` 该类型定义了事件回调函数的原型,其中fd是事件所在的文件描述符,events是事件类型,arg是用户传入的参数。 ### 2.事件结构体 ```c struct event { event_callback_fn ev_callback; // 事件回调函数 int ev_fd; // 事件所在的文件描述符 short ev_events; // 事件类型 short ev_res; // 事件结果 struct event_base *ev_base; // 事件所属的event_base void *ev_arg; // 用户传入的参数 }; ``` 该结构体表示一个事件,其中ev_callback是事件回调函数,ev_fd是事件所在的文件描述符,ev_events是事件类型,ev_res是事件结果,ev_base是事件所属的event_base,ev_arg是用户传入的参数。 ### 3.事件类型 ```c #define EV_TIMEOUT 0x01 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 #define EV_PERSIST 0x10 #define EV_ET 0x20 ``` 该宏定义了事件类型,分别为超时事件、读事件、写事件、信号事件、持续事件和边缘触发事件。 ### 4.事件处理函数 ```c struct event_base *event_base_new(void); int event_base_dispatch(struct event_base *base); int event_base_loopexit(struct event_base *base, const struct timeval *tv); void event_base_free(struct event_base *base); ``` 这些函数用于创建event_base、处理事件、退出事件循环和释放event_base等操作。 以上是event.h中的一些常用内容,更多细节可以查看源码和官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦时小

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值