仿muduo库实现并发服务器(2)

Connection模块

目的

对连接进行全方位的管理、对通信连接的所有操作都通过这个模块提供的功能或者接口完成。

管理

  1. 对套接字的管理,能够进行套接字的操作:读写问题
  2. 连接事件的管理:设置并监控可读、可写、错误、挂断、任意
  3. 缓冲区的管理,便于socket数据的接收和发送以及上层事务的处理
  4. 协议上下文的管理,记录请求数据的处理过程
  5. 回调函数
    (1)连接建立成功的回调函数:一个连接建立成功后,该如何处理,由用户决定
    (2)业务处理回调函数:一个连接接收到数据之后(可读事件触发后把数据读取出来,放到用户级的接收缓冲区后)该如何处理,由用户决定
    (3)关闭连接的回调函数:一个连接关闭之前该如何处理,由用户决定
    (4)任意事件的回调函数:产生任意事件,需不需要某些处理,可以由用户决定
    注意:这四个回调函数是连接在不同的阶段要干的事情,不是某种事件触发所要干的事情

功能

  1. 连接获取准备:连接获取之后,所处的状态下要进行各种设置(启动读监控,调用回调函数)
  2. 发送数据:给用户提供的发送数据接口,注意不是真正的发送数据接口,只是把数据放到用户的发送缓冲区,然后启动写事件监控
  3. 启动非活跃连接的超时销毁功能:根据需要决定一个连接是长连接还是短连接,如果微信、手机的消息推送等即时通讯就需要长连接,没有必要启动销毁功能,省去了频繁的三次握手和四次挥手开销。
  4. 取消非活跃连接的超时销毁功能
  5. 协议切换:一个连接接收数据后如何进行业务处理,取决于上下文,以及数据的业务处理回调函数
  6. 关闭连接:给用户提供的关闭数据接口,这里应该先看看接收缓冲区是否有数据待处理、发送缓冲区是否有数据待发送,再实际释放连接。

内存访问问题

由于Connection模块是对连接的管理模块,对于连接的所有操作都是通过这个模块完成。如果对连接进行释放操作后,loop线程去执行任务队列里的任务,此时连接已经被释放就会导致内存访问错误,最终程序崩溃

解决方法:使用智能指针shared_ptr对 Connection对象进行管理,这样可以保证任意一个地方对Connection 对象操作的时候,保存了一份shared_ptr ,此时其他地方进行释放操作,只是让shared_ptr--,不会导致Connection 对象的实际释放

代码设计

管理模块

cpp 复制代码
//DISCONNECTED-- 连接关闭状态, CONNECTING-- 连接建立成功,待处理状态,
// CONNECTED-- 连接建立完成,各种设置已完成,可以通信,  DISCONNECTING-- 待关闭,检查是否还有数据未处理未发送的状态
typedef enum
{
    DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING
}ConnStatus;

class Connection;
using PtrConnection = std::shared_ptr<Connection>;
class Connection : public std::enable_shared_from_this<Connection>
{
private:
    uint64_t _conn_id; // 连接的ID,便于连接的管理和查找
    //uint64_t _timer_id; //定时器ID,必须唯一,这里把_conn_id也当做定时器ID,简化操作
    int _sock_fd; //连接对应的文件描述符
    Socket _socket; //套接字的管理
    Channel _channel; //连接事件的管理
    Buffer _in_buffer;// 接收缓冲区:存放从socket读取的数据
    Buffer _out_buffer;// 发送缓冲区:存放要发送给对端的数据
    Any _context; //请求的处理--上下文的管理
    EventLoop *_loop; //所有的功能都在loop线程执行
    ConnStatus _status;
    bool _enable_inactive_release;//连接是否启动非活跃销毁的判断标志,默认false--长连接
    //下面四个回调函数由组件使用者实现和使用,后面会让服务器设置(因为服务器模块的回调处理是由组件使用者设置的)
    using ConnectedCallback = std::function<void(const PtrConnection &)>;
    using MessageCallback = std::function<void(const PtrConnection &, Buffer*)>;
    using ClosedCallback = std::function<void(const PtrConnection &)>;
    using AnyEventCallback = std::function<void(const PtrConnection &)>;
    ConnectedCallback _connected_callback;
    MessageCallback _message_callback;
    ClosedCallback _closed_callback;
    AnyEventCallback _anyevent_callback;
    //组件内的连接关闭回调--组件内设置的
    //因为服务器组件内会把所有的连接管理起来,一旦某个连接要关闭,就应该从管理的地方移除掉自己的信息
    //移除服务器管理的连接信息
    ClosedCallback _server_closed_callback;
private:
    //描述符可读事件触发后调用的函数,recv socket数据放到接收缓冲区
    void HandleRead()
    {
        //1、接收socket的数据放到缓冲区
        char buf[65536];
        ssize_t ret = _socket.NonBlockRecv(buf, 65535);
        if(ret < 0)
        {
            //不能直接关闭
            return ShutdownInLoop();
        }
        _in_buffer.WriteAndMove(buf, ret);
        //2、调用_messagecallback进行业务处理
        if(_in_buffer.ReadAbleSize() > 0)
        {
            //shared_from_this:从当前对象自身获取自身的share_ptr管理对象
            _message_callback(shared_from_this(), &_in_buffer);
        }
    }
    //描述符可写事件触发后调用的函数,将发送缓冲区的数据进行send
    void HandleWrite()
    {
        ssize_t ret = _socket.NonBlockSend(_out_buffer.ReadPosition(), _out_buffer.ReadAbleSize());
        if(ret < 0)
        {
            //写通道关闭了,看看有没有数据待处理然后直接关闭连接、进行释放操作
            if(_in_buffer.ReadAbleSize() > 0)
            {
                _message_callback(shared_from_this(), &_in_buffer);
            }
            return ReleaseInLoop();
        }
        _out_buffer.MoveReadOffset(ret);
        if(_out_buffer.ReadAbleSize() == 0)
        {
            _channel.DisableWrite();//没有数据待发送,关闭写事件监控
            //待关闭状态,有数据,前面已经发送;没有数据则直接释放
            if(_status == DISCONNECTING)
            {
                //直接用ReleaseInLoop,当前函数调到HandleEvent就会出错:Connection对象已经被释放了
                return _loop->QueueInLoop(std::bind(&Connection::ReleaseInLoop, shared_from_this()));
            }
        }
    }
    //描述符任意事件触发后调用的函数:1、刷新连接活跃度,延迟定时销毁任务 2、调用组件使用者的任意事件回调
    void HandleEvent()
    {
        if(_enable_inactive_release == true) _loop->TimerRefresh(_conn_id);
        if(_anyevent_callback) _anyevent_callback(shared_from_this());
    }
    //描述符挂断事件触发
    void HandleClose()
    {
        //一旦挂断,套接字就什么都不做,只检查是否有数据待处理,之后关闭连接
        if(_in_buffer.ReadAbleSize() > 0)
        {
            _message_callback(shared_from_this(), &_in_buffer);
        }
        ReleaseInLoop();
    }
    //描述符错误事件触发
    void HandleError()
    {
        HandleClose();
    }

public:
    Connection(uint64_t conn_id, int sock_fd, EventLoop *loop):_conn_id(conn_id), _sock_fd(sock_fd), _socket(sock_fd),
        _channel(sock_fd, loop), _loop(loop), _status(CONNECTING), _enable_inactive_release(false)
    {
        _channel.SetReadCallback(std::bind(&Connection::HandleRead, this));
        _channel.SetWriteCallback(std::bind(&Connection::HandleWrite, this));
        _channel.SetEventCallback(std::bind(&Connection::HandleEvent, this));
        _channel.SetCloseCallback(std::bind(&Connection::HandleClose, this));
        _channel.SetErrorCallback(std::bind(&Connection::HandleError, this));
    }  
    ~Connection()
    {
        DBG_LOG("Release Connection: %p", this);
    }
};

代码分析:

1、HandleRead和HandleWrite

(1)为什么 HandleRead 出错调用 ShutdownInLoop,而 HandleWrite 出错调用 ReleaseInLoop?根本原因在于 TCP 连接的双向(全双工)特性以及错误的性质不同。
HandleRead 中的错误( ret < 0 表示网络错误): 这代表"读通道"断了。 但是,这并不意味着"写通道"也断了。 Muduo 支持 TCP 的"半关闭"状态。即使我无法从你那里读取数据,但我可能还有数据积压在 _out_buffer(输出缓冲区)中没有发给你。 ShutdownInLoop 的逻辑是:先尝试把输出缓冲区的数据发送完毕(利用写事件),发完之后再发送 FIN 包来关闭连接。所以这里不能直接销毁连接对象,必须给它机会把遗言交代完。
HandleWrite 中的错误(ret < 0): 这代表"写通道"彻底坏了。一旦发生这类写错误,意味着连接的物理链路或者逻辑状态已经遭到破坏。此时继续尝试从这个 Socket 读取数据通常没有意义,不管你的 _out_buffer 里是否还有数据,你都再也不可能把它们发出去了。 既然发不出去,保留这个 Connection 对象就没有任何意义了,直接释放资源是最正确的选择。

(2)为什么 HandleWrite 出错时,还要检查输入缓冲区并回调业务?这是为了尽最大努力保证业务层的数据完整性。

场景如下:假设客户端发送了一个请求过来,服务器的 HandleRead 成功接收并放入了输入缓冲区。紧接着,服务器试图回复之前的一个响应,结果在 HandleWrite 里发现连接断了(写错误)。 此时,尽管连接断了导致无法回复,但刚才收到的那个新请求可能在业务逻辑上是有效的。 比如这个请求是"记录一条日志"或者"修改某个状态"。 在销毁连接前,最后调用一次 _message_callback,是给业务层一个最后的机会:虽然连接死了,但我手里还攥着刚才收到的一点数据,你要不要处理一下?这样可以避免逻辑上丢失刚才已经读取成功的请求。 至于处理完直接 ReleaseInLoop,正如第一点所说,因为写通道已坏,连接在物理上已经废了,处理完剩余的读取数据后,唯一的归宿就是销毁。

2、为什么要继承std::enable_shared_from_this<T>?

(1)回调定义如下:using ConnectedCallback = std::function<void(const PtrConnection &)>;

回调函数接收的是 PtrConnection(即 std::shared_ptr<Connection>)。这意味着,当 Connection对象在内部触发这个回调时(比如连接建立成功时),它必须把自己作为一个智能指针传出去。

(2)避免"双重释放(Double Free)"的致命错误: 在类内部,当你直接用 this 指针(裸指针)去构造一个新的 shared_ptr 时,它不仅创建了一个新的智能指针对象,还创建了一个全新的、独立的"引用计数器(控制块)"。

就像这样:// 错误写法

_connectedcallback(std::shared_ptr<Connection>(this));

这会导致严重的后果:外部原本管理这个对象的智能指针(A)和这里临时创建的智能指针(B)相互不知道对方的存在。它们会各自创建一个引用计数(两个控制块count都为1)。当它们各自自动销毁时,都会尝试去delete 这块内存。第二次delete会导致程序崩溃。

正确做法:继承之后,初始化PtrConnection时内部生成一个weak_ptr指向管理的对象,使用 shared_from_this() 传递自己

_connectedcallback(shared_from_this()); // 这样传到函数的智能指针和外部管理该对象的指针共享引用计数(只有一个控制块且为2)

std::shared_ptr<T> shared_from_this() {
return weak_this_.lock(); // <--- 核心代码在

}

注意这里提到的是智能指针,裸指针没有引用计数,没有控制块,没有"自动销毁"机制

3、在HandleWrite模块把ReleaseInLoop打包放入loop任务队列的重要性:在下面的场景中,

若在主函数调用Send接口后,又立即调用ShutDown接口后,执行ShutDown函数里发现_out_buffer有数据,此时写监控触发事件,进入Channel的HandleEvent()中,执行HandleWrite()后,走到最后一步,如果直接用ReleaseInLoop,当前函数return到HandleEvent就会出错:此时Connection对象已经被释放了,那么channel对象也不存在,根本不可能访问到HandleEvent函数;正确做法就是只能把这个释放任务放到任务队列,等HandleEvent执行后再执行,并且这里要注意需要用shared_from_this()拷贝一份shared_ptr存入任务队列(计时器++),避免这个任务还没有执行Connection已经被销毁(用this指针是不安全的)

功能模块

cpp 复制代码
private:  
  //连接建立就绪后,修改状态,进行启动读监控,调用_connectedcallback
    void EstablishedInLoop()
    {
        assert(_status == CONNECTING);//当前状态必须是上层的半连接状态
        _status = CONNECTED;
        //读事件监控需要放在是否启动非活跃销毁任务之后进行:反之,如果立即触发事件可能进行任务刷新,此时还没有执行到添加任务那块
        _channel.EnableRead();
        if(_connected_callback) _connected_callback(shared_from_this()); 
    }
    //发送数据,将数据放到发送缓冲区,启动写事件监控
    void SendInLoop(Buffer buffer, size_t len)
    {
        if(_status == DISCONNECTED) return;
        _out_buffer.WriteBufferAndMove(buffer, len);
        if(_channel.WriteAble() == false)
        {
            _channel.EnableWrite();
        }
    }

    void ReleaseInLoop()
    {
        //1、修改链接状态,置为DISCONNECTED
        _status = DISCONNECTED;
        //2、移除连接的事件监控
        _channel.Remove();
        //3、关闭描述符
        close(_sock_fd);
        //4、如果当前定时器队列还有定时销毁任务,取消任务
        if(_loop->HasTimerTask(_conn_id))
        {
            CancelInactiveReleaseInLoop();
        }
        //5、调用关闭回调函数,先调用用户的,再调用服务器的回调函数,否则先移除服务器管理的连接信息导致Connection被释放
        if(_closed_callback) _closed_callback(shared_from_this());
        if(_server_closed_callback) _server_closed_callback(shared_from_this());
    }
    //提供给组件使用者的关闭接口:需要先判断有没有数据待处理和发送,再关闭
    void ShutdownInLoop()
    {
        _status = DISCONNECTING;
        if(_in_buffer.ReadAbleSize() > 0)
        {
            if(_message_callback)
            _message_callback(shared_from_this(), &_in_buffer);
        }
        //写入数据的时候出错关闭连接,或者没有待发送数据,直接关闭
        if(_out_buffer.ReadAbleSize() > 0)
        {
            if(_channel.WriteAble() == false)
            {
                _channel.EnableWrite();
            }
        }
        if(_out_buffer.ReadAbleSize() == 0)
        {
            ReleaseInLoop();
        }
    }
    void EnableInactiveReleaseInLoop(int sec)
    {
        //1、将判断标志置为true
        _enable_inactive_release = true;
        //2、如果当前定时任务存在,刷新任务//不存在则新增
        if(_loop->HasTimerTask(_conn_id))
        {
            _loop->TimerRefresh(_conn_id);
        }
        else _loop->TimerAdd(_conn_id, sec, std::bind(&Connection::ReleaseInLoop, this));
    }
    void CancelInactiveReleaseInLoop()
    {
        _enable_inactive_release = false;
        if(_loop->HasTimerTask(_conn_id))
        {
            _loop->TimerCancel(_conn_id);
        }
    }
    //切换协议:重置上下文和阶段性处理函数
    void UpgradeInLoop(const Any& context, 
                const ConnectedCallback &con, 
                const MessageCallback& msg, 
                const ClosedCallback& closed, 
                const AnyEventCallback& event)
    {
        _context = context;
        _connected_callback = con;
        _message_callback = msg;
        _closed_callback = closed;
        _anyevent_callback = event;
    }
public:
    //成员的操作
    //获取管理的文件描述符
    int Fd() { return _sock_fd; }
    //获取连接的ID
    int Id() { return _conn_id; }
    bool Connected() { return _status == CONNECTED; }
    //获取上下文
    Any* GetContext() { return &_context; }

    //回调函数的设置
    //连接建立完成时再调用
    void SetContext(const Any& context) { _context = context; }

    void SetConnectedCallback(const ConnectedCallback &cb) { _connected_callback = cb; }
    void SetMessageCallback(const MessageCallback&cb) { _message_callback = cb; }
    void SetClosedCallback(const ClosedCallback& cb) { _closed_callback = cb; }
    void SetAnyEventCallback(const AnyEventCallback& cb) { _anyevent_callback = cb; }
    void SetServerClosedCallback(const ClosedCallback& cb) { _server_closed_callback = cb; }
    //对外提供的接口
    //连接建立就绪后,进行启动读监控,修改状态,调用_connectedcallback
    void Established() 
    { 
        _loop->RunInLoop(std::bind(&Connection::EstablishedInLoop, this));
    }
    //发送数据,将数据放到发送缓冲区,启动写事件监控
    void Send(const char* data, size_t len)
    {
        //data是外界传入,如果SendInLoop在任务队列,可能执行的时候data,buf已经被销毁
        Buffer buf;
        buf.WriteAndMove(data, len);
        _loop->RunInLoop(std::bind(&Connection::SendInLoop, this, buf, len));
    } 
    //提供给组件使用者的关闭接口:需要先判断有没有数据待处理和发送,再关闭
    void Shutdown()
    {
        _loop->RunInLoop(std::bind(&Connection::ShutdownInLoop, this));
    }
    //启动非活跃销毁,定义多长时间无通信就是非活跃,添加定时销毁任务
    void EnableInactiveRelease(int sec)
    {
        _loop->RunInLoop(std::bind(&Connection::EnableInactiveReleaseInLoop, this, sec));
    }
    //取消非活跃销毁
    void CancelInactiveRelease()
    {
        _loop->RunInLoop(std::bind(&Connection::CancelInactiveReleaseInLoop, this));
    }
    //切换协议:重置上下文和阶段性处理函数
    //这个接口必须在eventloop线程中,且要立即执行:有可能新事件触发需要处理时,前面的切换协议还在任务队列没有执行,那么新事件处理就会用原协议
    void Upgrade(const Any& context, 
                const ConnectedCallback &con, 
                const MessageCallback& msg, 
                const ClosedCallback& closed, 
                const AnyEventCallback& event)
    {
        _loop->AssertInLoop();/////
        _loop->RunInLoop(std::bind(&Connection::UpgradeInLoop, this, context, con, msg, closed, event));
    }

1、基本认识:把所有操作都加上 InLoop 后缀并强制扔到同一个 EventLoop 线程中执行,目的就是为了把"多线程并发问题"转化为"单线程串行执行",从而实现无锁编程,保证绝对的线程安全

2、Connectionn内部传的参数是自身时,传裸指针还是shared_from_this的问题

_message_callback(shared_from_this(), &_in_buffer);

_anyevent_callback(shared_from_this());

_connected_callback(shared_from_this());

_closed_callback(shared_from_this());

_server_closed_callback(shared_from_this());

_loop->QueueInLoop(std::bind(&Connection::ReleaseInLoop, shared_from_this()));
_channel.SetReadCallback(std::bind(&Connection::HandleRead, this));
_channel.SetWriteCallback(std::bind(&Connection::HandleWrite, this));
_channel.SetEventCallback(std::bind(&Connection::HandleEvent, this));
_channel.SetCloseCallback(std::bind(&Connection::HandleClose, this));
_channel.SetErrorCallback(std::bind(&Connection::HandleError, this));

这里涉及到是否需要延长Connection对象的生命周期,像_message_callback、QueueInLoop等函数是属于外部函数,可以理解成函数的最后执行是在Connectionn类外,这里用shared_from_this()是为防止连接提前关闭导致Connection对象被释放,其他外部函数无法访问Connection对象,所以要生成一个临时的 std::shared_ptr 来增加引用计数,强行延长对象生命周期,防止任务执行时访问野指针,保证对象不会被真的释放;

而_channel本身是在Connectionn里定义的,和指针_loop不同(_loop也是外界传入初始化Connectionn对象的),这意味着当Connection对象要被delete时,成员变量_channel要先被释放,_channel都被释放了,就不可能会去执行HandleRead、HandleWrite等函数,那么这个this参数也应该无效,没必要传shared_from_this();并且是在构造函数里面进行监控事件的回调处理设置,构造函数是使用不了shared_from_this(),因为此时还没有初始化完成,使用不了PtrConnection。

3、回调函数的理解:

举Connection模块和组件使用者模块

class Connection{

void HandleRead(){

//1、接收socket的数据放到缓冲区

char buf[65536];

ssize_t ret = _socket.NonBlockRecv(buf, 65535);

if(ret < 0)

{

//不能直接关闭

return ShutdownInLoop();

}

_in_buffer.WriteAndMove(buf, ret);

//2、调用_messagecallback进行业务处理

if(_in_buffer.ReadAbleSize() > 0)

{

//shared_from_this:从当前对象自身获取自身的share_ptr管理对象

_message_callback(shared_from_this(), &_in_buffer);

}

}

};

例如这个功能需要用到组件使用者的阶段处理函数 _message_callback,这里的实现流程:组件使用者模块通过 ptr_con->SetMessageCallback(std::bind(OnMessage, std::placeholders::_1, std::placeholders::_2));//参数由connection内部提供
将这个OnMessage设置给Connection的_message_callback这个回调(Call back)函数 ,当Connection里面执行到_message_callback 时,这个函数又出去,即回头去调用OnMessage 这里才是真正的函数实现。
而这个参数的设置是因为OnMessage 需要用到Connection的功能和Buffer对象才需要在Connection定义回调函数变量时这样设置: using MessageCallback = std::function<void(const PtrConnection &, Buffer*)>。

Acceptor模块

目的

对监听套接字进行管理

设计步骤

  1. 创建一个监听套接字
  2. 启动读事件监控
  3. 事件触发后,获取新连接
  4. 调用新连接获取成功后的回调函数:这里注意,这个回调函数的实现是要对新连接的描述符进行处理,其实就是为新连接创建Connection进行管理;此功能的实现不应该在Acceptor模块,而是由服务器模块提供(后面实现)

代码实现

cpp 复制代码
//监听套接字的管理
class Acceptor
{
private:
    Socket _socket; //监听套接字
    EventLoop *_loop; //对监听套接字进行事件监控
    Channel _channel; //对监听套接字进行事件管理
    using AcceptCallback = std::function<void(int)>;
    AcceptCallback _accept_callback;
private:
    int CreateServer(int port)
    {
        bool ret = _socket.CreateServer(port);
        assert(ret == true);
        return _socket.Fd();
    }
    void HandleRead()
    {
        int newfd = _socket.Accept();
        if(newfd < 0) return;
        //成功获取新连接,调用对新链接处理的回调函数
        _accept_callback(newfd);
    }
public:
    Acceptor(EventLoop *loop, int port):_socket(CreateServer(port)), _channel(_socket.Fd(), loop)
    {
        _channel.SetReadCallback(std::bind(&Acceptor::HandleRead, this));
    }
    //不能在构造函数里开启读监控,否则会出现一开启就有新连接,执行HandleRead时,回调函数还没有设置好
    void Listen()
    {
        _channel.EnableRead();
    }
    void SetAcceptCallback(const AcceptCallback& cb) { _accept_callback = cb; }
};

下面先给出对新连接进行处理的HandleNewConnection函数

cpp 复制代码
//tcp_srv.cc

#include "../source/server.hpp"


uint64_t conn_id = 0;
std::unordered_map<uint64_t, PtrConnection> _conns; 
EventLoop loop;

void ConnectionDestroy(const PtrConnection& ptr)
{
    _conns.erase(ptr->Id());
}
void OnConnected(const PtrConnection& ptr)
{
    DBG_LOG("New Connection: %p", ptr.get());
}
void OnMessage(const PtrConnection& ptr, Buffer* buf)
{
    DBG_LOG("%s", buf->ReadPosition());
    buf->MoveReadOffset(buf->ReadAbleSize());
    std::string s = "hello, bugubugu!";
    ptr->Send(s.c_str(), s.size());
    ptr->Shutdown();
}
void HandleNewConnection(int newfd)
{
    conn_id++;
    PtrConnection ptr_con(new Connection(conn_id, newfd, &loop));
    _conns.insert(std::make_pair(conn_id, ptr_con));
    ptr_con->SetConnectedCallback(std::bind(OnConnected, std::placeholders::_1));//参数由connection内部提供
    ptr_con->SetMessageCallback(std::bind(OnMessage, std::placeholders::_1, std::placeholders::_2));
    ptr_con->SetServerClosedCallback(std::bind(ConnectionDestroy, std::placeholders::_1));
    //10s后对非活跃连接进行释放
    //释放任务的添加要放在监控之前,如果先开启监控,立即有事件,在刷新释放任务里找不到这个任务
    ptr_con->EnableInactiveRelease(10);
    ptr_con->Established();
}
int main()
{
    Acceptor acceptor(&loop, 8080);
    acceptor.SetAcceptCallback(std::bind(HandleNewConnection, std::placeholders::_1));
    acceptor.Listen();
    while(1)
    {
        loop.Start();
    }
    return 0;
}

注意:这里对通信套接字的处理其实要用一个从属的loop,而不是和监听套接字绑定的主loop,我们先暂时用一个loop,后面再进行修改

通过main函数不难发现,如果 Acceptor 模块里在构造函数就开启读监控,马上就有新连接,那么Acceptor 还没来得及设置新连接处理回调函数,Acceptor::HandleRead 就马上执行,而新描述符newfd 有效但是没办法进行处理,这是非常危险的。所以要在设置完回调函数之后,再去启动读监控,获取新连接。

相关推荐
Easonmax2 小时前
【鸿蒙pc命令行适配】OpenSSL主流版本介绍以及为何推荐移植OpenSSL 3.5版本
服务器·华为·harmonyos
Sweet_vinegar2 小时前
腾讯轻型服务器外网访问不上?
服务器·docker·腾讯云·arl
研发小能2 小时前
主流国产DevOps平台选型分析:从适配度、部署效率与生态依赖分析嘉为蓝鲸平台
运维·研发效能·devops·devops平台·devops系统
开开心心就好2 小时前
卸载工具清理残留,检测垃圾颜色标识状态
linux·运维·服务器·python·安全·tornado·1024程序员节
REDcker2 小时前
OpenSSL 完整文档
c++·安全·github·c·openssl·后端开发
小舞O_o2 小时前
gitlab文件上传
linux·服务器·git·python·目标检测·机器学习·gitlab
f狐0狸x2 小时前
C++ vector 从入门到上手:核心基本用法全解析
开发语言·c++
王老师青少年编程2 小时前
2023年12月GESP真题及题解(C++七级): 商品交易
c++·题解·真题·gesp·csp·七级·商品交易
HalvmånEver2 小时前
Linux:信号捕捉下(信号四)
linux·运维·数据库