64 epoll服务器 (ET模式)

基于LT模式修改,并加入前面的应用层计算器,实现稍完整的服务器功能
1.修改tcp_socket.hpp,新增非阻塞读和非阻塞写接口
2.对于accept返回的new_sock加上EPOLLET这样的选项

注意:此代码暂时未考虑listen_sock ET的情况,如果将listen_sock设为ET,则需要非阻塞轮询的方式accept,否则会导致同一时刻大量的客户端同时连接的情况,只能accept一次的问题

目录

  1. 整体结构
  2. 流程
  3. 运行示例
  4. 改进
  5. Reactor的理论

1. 整体结构

在这里插入图片描述
TpcServer服务器类,维护_listen套接字,用来获取连接和监听读写事件,map用套接字做键值,rev数组作为epoll_wait的输出参数
在这里插入图片描述

每一个连接都是一个session结构,包含读写缓冲区,ip和端口方便调试,读写错误的回调处理函数,指针回指服务器
在这里插入图片描述
nocopy类用来给某些类提供无法拷贝的功能
socket类,提供套接字的创建,监听等功能
协议类和计算类为前面章节的内容,用来对收到的数据处理返回结果
Epoll类提供epoll多路转接功能
comm类单独拎出来的设置非阻塞功能,因为很多地方都要用到
在这里插入图片描述

2. 流程

服务端

TpcServer.hpp
继承enable_shared_from_this类可以解决智能指针不能用this构造的问题,使用智能指针对象需要用shared_from_this()功能来获取
在这里插入图片描述
定义两个函数模板,构造时传入报文处理回调
在这里插入图片描述

在这里插入图片描述
Init函数初始化套接字,设置非阻塞,AddConnection函数添加listen套接字到关注事件中,绑定事件分配函数Accepter
在这里插入图片描述

AddConnection函数参数传入要设置的套接字,事件,三个函数,ip和端口用来调试

在这里插入图片描述

Connection
构造时传入sock初始化成员变量
在这里插入图片描述
作为连接管理类,需要管理每个连接的发送和接收缓冲区,所以提供存入缓冲区数据的和返回缓冲区内容的功能,再提供初始化自己成员函数的功能
在这里插入图片描述在这里插入图片描述在这里插入图片描述
TpcServer.hpp
继续说明AddConnection函数,这个函数的作用为每个连接初始化session,添加关注事件和管理,后面每个新链接都要用这个函数

构造一个Connection的临时对象
设置成员TpcServer和回调函数,ip和port
添加对象到map结构里,添加listen的事件,listen关注读

Accpeter连接管理器函数,参数是事件就绪的会话
在这里插入图片描述

不一定只有一个连接到来,所以需要循环读取。用accept获取就绪连接,设置非阻塞后,调用AddConnection函数加入会话管理,作为连接会话三个回调函数分别是读写错误

当错误码是EWOULDBLOCK的时候,说明已经获取完,退出循环,EINTR表示系统调用被信号中断,所以继续读取,其他情况退出

Recver数据读取函数,用来提供读取数据添加到Connection缓冲区的功能
在这里插入图片描述在这里插入图片描述

首先判断了连接的生命周期,如果消亡就退出。通过lock获取一个shared指针对象。
因为是ET模式,所以一次性需要读完所有数据,用recv函数,返回值n大于0表示读取到数据,添加到接收缓冲区中,等于0对方客户度退出,调用错误处理函数,小于0和上面一样判断是否读完,不是就走错误处理

最后将读取到的数据交给处理函数,所有报文情况都由它处理

Sender函数,获取连接的发送缓冲区发送,一次性将数据都发送,返回值大于0发送成功,将发送了的内容删除,判断如果发送缓冲区为空就退出。0表示没发送任何内容也退出,其他情况判断是否走错误处理
在这里插入图片描述

epoll/select/poll,因为写事件经常都是就绪的,发送缓冲区基本会有空间,如果设置了写关心,每次都会就绪,经常返回浪费cpu资源。所以对于读,需要设置常关心,写,按需求设置
当发送完后,检查缓冲区不为空,没发送完就对写事件开启关心,发送完将事件关闭

EnableEvent函数,设置套接字的读和写,根据传入的参数,判断有没有读和写,通过三木运算符,有就加入event,最后修改套接字的事件
在这里插入图片描述
Excepter函数,错误处理函数,遇到错误就是关闭这个链接。如果连接在读和写时发生错误,用这个函数。取消这个套接字的所有关心,关闭文件,map中移除
在这里插入图片描述

IsSafeConnection函数,检查链接是否合法,遍历map,是否存在
在这里插入图片描述

主逻辑
Loop函数,服务器的运行循环,传入超时时间,不断调用事件分配函数和打印连接函数
在这里插入图片描述

PrintConnection函数,打印出map中所有的fd,用来调试
在这里插入图片描述

Dispatcher函数,timeout等待时间是上一个函数传入。不断wait监听revs数组添加了的套接字,n会返回就绪的个数,取到套接字和事件,将异常转为读写统一处理。如果是读事件就绪,并且连接合法,就调用读取函数,写事件调用写函数

在这里插入图片描述

TpcServer.cc
全局的计算类对象,DefaultOnMessage函数是默认的报文处理函数,对报文的完整性判断,计算返回结果并发送
调用计算类的函数,判断返回的字符串是否为空,为空说明报文不完整或有错误。如果处理完成,将结果加入到发送缓冲区,用tcpserver对象发送

在这里插入图片描述
main函数创建svr对象,传入报文处理函数,启动服务器
在这里插入图片描述

客户端

客户端链接服务器,生成5个随机报文发送接收结果打印
是前面章节的网络计算器
网络计算器

3. 运行示例

在这里插入图片描述

4. 全

TcpServer.hpp

#pragma ocne
#include <iostream>
#include <memory>
#include <functional>
#include <unordered_map>
#include "Comm.hpp"
#include "log.hpp"
#include "Epoll.hpp"
#include "Socket.hpp"

class Connection;
class TpcServer;
using func_t = std::function<void(std::weak_ptr<Connection>)>; // 用户缓冲区处理函数模板
using except_func_t = std::function<void(std::weak_ptr<Connection>)>;
static const uint16_t port = 8000;
static const int g_buff_size = 128;
// 设置et
uint32_t EVENT_IN = (EPOLLIN | EPOLLET);
uint32_t EVENt_OUT = (EPOLLOUT | EPOLLET);

class Connection
{
   
   
public:
    Connection(int sock)
    {
   
   
        _sock = sock;
    }

    ~Connection()
    {
   
   

    }

    void AppendInbuff(const std::string& message)
    {
   
   
        _inbuff += message;
    }

    void AppendOutbuff(const std::string& message)
    {
   
   
        _outbuff += message;
    }

    int Fd()
    {
   
   
        return _sock;
    }

    std::string& Inbuffer()  // for debug
    {
   
   
        return _inbuff;
    }

    std::string& Outbuffer()  // for debug
    {
   
   
        return _outbuff;
    }

    void SetHandler(func_t recv_cb, func_t send_cb, except_func_t except_cb)
    {
   
   
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }

    void SetWeakPtr(std::weak_ptr<TpcServer> tcp_setver_ptr)
    {
   
   
        _tcp_server_ptr = tcp_setver_ptr;
    }

private:
    int _sock;
    std::string _inbuff;  // string不能二进制, 需要vector
    std::string _outbuff;
public:
    func_t _recv_cb;
    func_t _send_cb;
    except_func_t _except_cb;

    std::weak_ptr<TpcServer> _tcp_server_ptr;  // 回指向服务器
    std::string _ip;
    uint16_t _port;
};

class TpcServer :public std::enable_shared_from_this<TpcServer>, public nocopy
{
   
   
    static const int num = 64;

public:
    TpcServer(func_t OnMessage)
        : _listensocket_ptr(new Sock())
        , _epoll_ptr(new Epoll())
        , _OnMessage(OnMessage)
        , _quit(true)
    {
   
   
    }

    void AddConnection(int sock, uint32_t event, func_t recv_cb, func_t send_cb, \
                        except_func_t except_cb, const std::string& ip = "0.0.0.0", 
                        uint16_t port = 0)
    {
   
   
        // 1. 给sock创建connection对象, 将lstensock添加到connection中
        // 同时,listeinsock和connection放入_connections
        std::shared_ptr<Connection> new_con(new Connection(sock));
        new_con->SetWeakPtr(shared_from_this());  // 返回当前对象的shared_ptr
        new_con->SetHandler(recv_cb, send_cb, except_cb);
        new_con->_ip = ip;
        new_con->_port = port;
        // 2. 添加到map
        _connections.insert(std::make_pair(sock, new_con));
        // 3. 添加对应事件
        _epoll_ptr->EpollUpdate(EPOLL_CTL_ADD, sock, event);

    }

    void Init()
    {
   
   
        _listensocket_ptr->Socket();
        int opt = 1;
        setsockopt(_listensocket_ptr->Fd(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, 
                   &opt, sizeof(opt));
        // et模式需要非阻塞
        SetNonBlock(_listensocket_ptr->Fd());
        lg.logmessage(info, "listensock create success:%d", _listensocket_ptr->Fd());

        _listensocket_ptr->Bind(port);
        _listensocket_ptr->Listen();
        // 关联connection
        AddConnection(_listensocket_ptr->Fd(), EVENT_IN,
                      std::bind(&TpcServer::Accepter, this, std::placeholders::_1), 
                      nullptr, nullptr);
    }

    void Accepter(std::weak_ptr<Connection> con)
    {
   
   
        // 获取强引用对象, 检查是否销毁
        auto connection = con.lock();
        // 获取新链接
        while (true)
        {
   
   
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // ::调用原生函数
            int sock = ::accept(connection->Fd(), (struct sockaddr *)&peer, &len);
            if (sock > 0)
            {
   
   
                char ipbuf[128];
                inet_ntop(AF_INET, &peer.sin_addr.s_addr, ipbuf, sizeof(ipbuf));
                uint16_t port = ntohs(peer.sin_port);
                lg.logmessage(info, "get a new clinet[%s:%d]:%d", ipbuf, port, sock);
                // 设置非阻塞
                SetNonBlock(sock);
                // 添加连接事件
                AddConnection(sock, EVENT_IN,\
                              std::bind(&TpcServer::Recver, this, std::placeholders::_1),\
                              std::bind(&TpcServer::Sender, this, std::placeholders::_1),\
                              std::bind(&TpcServer::Excepter, this, std::placeholders::_1),\
                              ipbuf, port);
            }
            else 
            {
   
   
                if (errno == EWOULDBLOCK)
                {
   
   
                    break;
                }
                else if (errno == EINTR)  // 信号中断
                {
   
   
                    continue;
                }
                else
                {
   
   
                    break;
                }
            }
        }
    }

    void Recver(std::weak_ptr<Connection> con)
    {
   
   
        if (con.expired())
            return;
        auto connec = con.lock();
        int sock = connec->Fd();

        while (true)
        {
   
   
            char buff[g_buff_size];
            memset(buff, 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值