Trinitycore的网络通信(boost::asio)

整体类图

image

一、写数据

整体流程:

具体代码见下方《代码解析》

  1. 起服时通过全局静态对象 WorldSocketMgr,在成员方法 WorldSocketMgr::StartNetwork 内,根据配置创建多个(默认为2个)网络线程对象 NetWorkThread 。

    同时创建 AsyncAcceptor 对象,其内部封装了 boost::asio::tcp::acceptor ,用于接收新 socket 。

    调用 NetWorkThread::Start() , 创建 std::thread, 执行绑定的 NetWorkThread::Run()

  2. 线程执行 NetWorkThread::Run() , 执行 boost::asio::io_context::run() , 开启事件循环。

    同时不断循环定时器,每毫秒调用 NetWorkThread::Update() , 注意对于同一个 NetWorkThread 对象,可能是不同的 std::thread去 执行 Update() 。

    在Update()中依次检查NetWorkThread管理的所有 socket ,执行 socket.Update() 。同时检查 socket 状态,如果返回 false, 代表已关闭进行删除逻辑。

  3. 注意这里的 _bufferQueue 是 MPSC 即 多生产者单消费者的无锁队列,队列中的元素是各个业务发送的协议数据包,由程序员根据协议字段压包。感觉业务这里可以用 Protobuf 。

    这里的生产者是各个发包业务,消费者就是 std::thread 。在 Update() 中合并多条协议数据包塞入新创建的 MessageBuffer 中,如果塞不下新创建另一个 MessageBuffer 塞入。

    这里使用 ARC4 算法加密头部,body 没有加密。然后将协议数据包 MessageBuffer,写入 WorldSocket._writeQueue

  4. 调用 _socket.write_some 真正将 _writeQueue 中的协议数据包发送。由于 _socket 设置了 non_blocking,这里不会阻塞。剩余没发送完的,异步 AsyncProcessQueue 发送。 直到 _writeQueue 全清空

代码解析:

  1. 起服时通过全局静态对象 WorldSocketMgr,在成员方法 WorldSocketMgr::StartNetwork 内,根据配置创建多个(默认为2个)网络线程对象 NetWorkThread 。

    同时创建 AsyncAcceptor 对象,其内部封装了 boost::asio::tcp::acceptor ,用于接收新 socket 。

    调用 NetWorkThread::Start() , 创建 std::thread, 执行绑定的 NetWorkThread::Run()

   virtual bool StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount)
    {
        ASSERT(threadCount > 0);

        AsyncAcceptor* acceptor = nullptr;
        try
        {
            acceptor = new AsyncAcceptor(ioContext, bindIp, port);
        }
        catch (boost::system::system_error const& err)
        {
            TC_LOG_ERROR("network", "Exception caught in SocketMgr.StartNetwork ({}:{}): {}", bindIp, port, err.what());
            return false;
        }

        if (!acceptor->Bind())
        {
            TC_LOG_ERROR("network", "StartNetwork failed to bind socket acceptor");
            delete acceptor;
            return false;
        }

        _acceptor = acceptor;       //初始化 AsyncAcceptor
        _threadCount = threadCount;
        _threads = CreateThreads(); //这里根据配置参数,创建对应数量的线程对象NetWorkThread

        ASSERT(_threads);

        for (int32 i = 0; i < _threadCount; ++i)
            _threads[i].Start();   // 这里启动所有网络线程

        _acceptor->SetSocketFactory([this]() { return GetSocketForAccept(); });

        return true;
    }
  1. 线程执行 NetWorkThread::Run() , 执行 boost::asio::io_context::run() , 开启事件循环。

    同时不断循环定时器,每毫秒调用 NetWorkThread::Update() , 注意对于同一个 NetWorkThread 对象,可能是不同的 std::thread去 执行 Update() 。

    在Update()中依次检查NetWorkThread管理的所有 socket ,执行 socket.Update() 。同时检查 socket 状态,如果返回 false, 代表已关闭进行删除逻辑。

    void Run()
    {
        TC_LOG_DEBUG("misc", "Network Thread Starting");

        _updateTimer.expires_from_now(boost::posix_time::milliseconds(1));
        _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); });
        _ioContext.run();       //执行事件循环

        TC_LOG_DEBUG("misc", "Network Thread exits");
        _newSockets.clear();
        _sockets.clear();
    }

    void Update()
    {
        if (_stopped)
            return;

        _updateTimer.expires_from_now(boost::posix_time::milliseconds(1));
        _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); });

        AddNewSockets();

        _sockets.erase(std::remove_if(_sockets.begin(), _sockets.end(), [this](std::shared_ptr<SocketType> sock)
        {
            if (!sock->Update())        //检查socket关闭
                if (sock->IsOpen())
                    sock->CloseSocket();

                this->SocketRemoved(sock);

                --this->_connections;
                return true;
            }

            return false;
        }), _sockets.end());
  1. 注意这里的 _bufferQueue 是 MPSC 即 多生产者单消费者的无锁队列,队列中的元素是各个业务发送的协议数据包,由程序员根据协议字段压包。感觉业务这里可以用 Protobuf 。

    这里的生产者是各个发包业务,消费者就是 std::thread 。在 Update() 中合并多条协议数据包塞入新创建的 MessageBuffer 中,如果塞不下新创建另一个 MessageBuffer 塞入。

    这里使用 ARC4 算法加密头部,body 没有加密。然后将协议数据包 MessageBuffer,写入 WorldSocket._writeQueue

bool WorldSocket::Update()
{
    EncryptablePacket* queued;
    if (_bufferQueue.Dequeue(queued))
    {
        // Allocate buffer only when it's needed but not on every Update() call.
        MessageBuffer buffer(_sendBufferSize);
        do
        {
            // 合并多个queued包 到 buffer包里,用加密的header作为头部分割。头部有size信息
            ServerPktHeader header(queued->size() + 2, queued->GetOpcode());
            if (queued->NeedsEncryption())
                _authCrypt.EncryptSend(header.header, header.getHeaderLength()); //只加密head

            if (buffer.GetRemainingSpace() < queued->size() + header.getHeaderLength())
            {
                QueuePacket(std::move(buffer));
                buffer.Resize(_sendBufferSize);
            }

            if (buffer.GetRemainingSpace() >= queued->size() + header.getHeaderLength())
            {
                buffer.Write(header.header, header.getHeaderLength());
                if (!queued->empty())
                    buffer.Write(queued->contents(), queued->size());
            }
            else    // single packet larger than buffer size
            {
                // 之前的 MessageBuffer 包塞不下,新创建个 MessageBuffer 塞入包
                MessageBuffer packetBuffer(queued->size() + header.getHeaderLength());
                packetBuffer.Write(header.header, header.getHeaderLength());
                if (!queued->empty())
                    packetBuffer.Write(queued->contents(), queued->size());

                QueuePacket(std::move(packetBuffer));
            }

            delete queued;
        } while (_bufferQueue.Dequeue(queued));

        if (buffer.GetActiveSize() > 0)
            QueuePacket(std::move(buffer));   // 将协议数据包 MessageBuffer,写入 WorldSocket._writeQueue
    }

    if (!BaseSocket::Update())// 调用 HandleQueue,真正发送 QueuePacket
        return false;

    _queryProcessor.ProcessReadyCallbacks();

    return true;
}
  1. 调用 _socket.write_some 真正将 _writeQueue 中的协议数据包发送。由于 _socket 设置了 non_blocking,这里不会阻塞。剩余没发送完的,异步 AsyncProcessQueue 发送。 直到 _writeQueue 全清空
    bool HandleQueue()
    {
        if (_writeQueue.empty())
            return false;

        MessageBuffer& queuedMessage = _writeQueue.front();

        std::size_t bytesToSend = queuedMessage.GetActiveSize();

        // 这里由于设置了 non_blocking, write_some 不会阻塞
        boost::system::error_code error;
        std::size_t bytesSent = _socket.write_some(boost::asio::buffer(queuedMessage.GetReadPointer(), bytesToSend), error);

        if (error)
        {
            if (error == boost::asio::error::would_block || error == boost::asio::error::try_again)
                return AsyncProcessQueue();

            _writeQueue.pop();
            if (_closing && _writeQueue.empty())
                CloseSocket();
            return false;
        }
        else if (bytesSent == 0)
        {
            _writeQueue.pop();
            if (_closing && _writeQueue.empty())
                CloseSocket();
            return false;
        }
        else if (bytesSent < bytesToSend) // now n > 0
        {
            queuedMessage.ReadCompleted(bytesSent);
            return AsyncProcessQueue(); // 剩余部分继续异步发送
        }

        //单个协议包发送完毕
        _writeQueue.pop();
        if (_closing && _writeQueue.empty())
            CloseSocket();
        return !_writeQueue.empty();
    }

二、读数据

整体流程:

  1. 起服时执行 AsyncAcceptor::AsyncAcceptWithCallback , 调用 boost::asio::tcp::acceptor::async_accept() 异步接收新 socket, 注意这里选择管理 socket 最少的那个 thread 来管理新 socket。

    接收新 socket 后,重新将 async_accept 加入事件监听。

  2. async_accept 里执行回调函数,最终执行到 Socket::AsyncRead() , 这里 _socket.async_read_some 异步读取数据到 _readBuffer

  3. 异步读到数据后,先填充 _headerBuffer 头,再填充协议包实体 _packetBuffer。由于 socket 是流式传输,有可能存在这次没接收完整。那么就 break 退出,等待下次异步读取执行到 ReadHandler。

    当数据包读完整后,调用 WorldSocket::ReadDataHandler

  4. 在 WorldSocket::ReadDataHandler 中,检查数据包合法性。 然后加锁,将数据包写入带锁队列 WorldSocket::_recvQueue

  5. 在游戏主线程中,每毫秒执行全局Update(), 然后遍历所有玩家对象,即 WorldSession 对象,执行 WorldSession::Update()

    其中会从 WorldSession::_recvQueue 取出协议包,然后根据 opcode,找到回调函数执行。

代码解析:

  1. 起服时执行 AsyncAcceptor::AsyncAcceptWithCallback , 调用 boost::asio::tcp::acceptor::async_accept() 异步接收新 socket, 注意这里选择管理 socket 最少的那个 thread 来管理新 socket。

    接收新 socket 后,重新将 async_accept 加入事件监听。

    template<AcceptCallback acceptCallback>
    void AsyncAcceptWithCallback()
    {
        tcp::socket* socket;
        uint32 threadIndex;
        std::tie(socket, threadIndex) = _socketFactory();
        _acceptor.async_accept(*socket, [this, socket, threadIndex](boost::system::error_code error)
        {
            if (!error)
            {
                try
                {
                    socket->non_blocking(true);

                    acceptCallback(std::move(*socket), threadIndex);
                }
                catch (boost::system::system_error const& err)
                {
                    TC_LOG_INFO("network", "Failed to initialize client's socket {}", err.what());
                }
            }

            if (!_closed) //循环接收新socket
                this->AsyncAcceptWithCallback<acceptCallback>();
        });
    }
  1. async_accept 里执行回调函数,最终执行到 Socket::AsyncRead() , 这里 _socket.async_read_some 异步读取数据到 _readBuffer
    void AsyncRead()
    {
        if (!IsOpen())
            return;

        _readBuffer.Normalize();
        _readBuffer.EnsureFreeSpace();
        _socket.async_read_some(boost::asio::buffer(_readBuffer.GetWritePointer(), _readBuffer.GetRemainingSpace()),
            std::bind(&Socket<T>::ReadHandlerInternal, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2));
    }
  1. 异步读到数据后,先填充 _headerBuffer 头,再填充协议包实体 _packetBuffer。由于 socket 是流式传输,有可能存在这次没接收完整。那么就 break 退出,等待下次异步读取执行到 ReadHandler。

    当数据包读完整后,调用 WorldSocket::ReadDataHandler

void WorldSocket::ReadHandler()
{
    if (!IsOpen())
        return;

    MessageBuffer& packet = GetReadBuffer();
    while (packet.GetActiveSize() > 0)
    {
        if (_headerBuffer.GetRemainingSpace() > 0)
        {
            // need to receive the header
            std::size_t readHeaderSize = std::min(packet.GetActiveSize(), _headerBuffer.GetRemainingSpace());
            _headerBuffer.Write(packet.GetReadPointer(), readHeaderSize);
            packet.ReadCompleted(readHeaderSize);

            if (_headerBuffer.GetRemainingSpace() > 0)
            {
                // Couldn't receive the whole header this time.
                // 流式读取,head都没读完数据就没了。中断读取,等待下次把数据补全。
                ASSERT(packet.GetActiveSize() == 0);
                break;
            }

            // We just received nice new header
            if (!ReadHeaderHandler())
            {
                CloseSocket();
                return;
            }
        }

        // We have full read header, now check the data payload
        if (_packetBuffer.GetRemainingSpace() > 0)
        {
            // need more data in the payload
            std::size_t readDataSize = std::min(packet.GetActiveSize(), _packetBuffer.GetRemainingSpace());
            _packetBuffer.Write(packet.GetReadPointer(), readDataSize);
            packet.ReadCompleted(readDataSize);

            if (_packetBuffer.GetRemainingSpace() > 0)
            {
                // Couldn't receive the whole data this time.
                // 流式读取,协议包主体都没读完数据就没了。中断读取,等待下次把数据补全。
                ASSERT(packet.GetActiveSize() == 0);
                break;
            }
        }

        // just received fresh new payload
        // 单个协议包读取完整了,
        ReadDataHandlerResult result = ReadDataHandler();
        _headerBuffer.Reset();
        if (result != ReadDataHandlerResult::Ok)
        {
            if (result != ReadDataHandlerResult::WaitingForQuery)
                CloseSocket();

            return;
        }
    }

    AsyncRead();
}
  1. 在 WorldSocket::ReadDataHandler 中,检查数据包合法性。 然后加锁,将数据包写入带锁队列 WorldSocket::_recvQueue
    sessionGuard.lock();  // 加锁

    LogOpcodeText(opcode, sessionGuard);

    if (!_worldSession)
    {
        TC_LOG_ERROR("network.opcode", "ProcessIncoming: Client not authed opcode = {}", uint32(opcode));
        delete packetToQueue;
        return ReadDataHandlerResult::Error;
    }

    OpcodeHandler const* handler = opcodeTable[opcode];
    if (!handler)
    {
        TC_LOG_ERROR("network.opcode", "No defined handler for opcode {} sent by {}", GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet.GetOpcode())), _worldSession->GetPlayerInfo());
        delete packetToQueue;
        return ReadDataHandlerResult::Error;
    }

    // Our Idle timer will reset on any non PING opcodes on login screen, allowing us to catch people idling.
    _worldSession->ResetTimeOutTime(false);

    // Copy the packet to the heap before enqueuing
    _worldSession->QueuePacket(packetToQueue); // _recvQueue 压包
  1. 在游戏主线程中,每毫秒执行全局Update(), 然后遍历所有玩家对象,即 WorldSession 对象,执行 WorldSession::Update()

    其中会从 WorldSession::_recvQueue 取出协议包,然后根据 opcode,找到回调函数执行。

bool WorldSession::Update(uint32 diff, PacketFilter& updater)
{
    if (IsConnectionIdle() && !HasPermission(rbac::RBAC_PERM_IGNORE_IDLE_CONNECTION))
        m_Socket->CloseSocket();

    WorldPacket* packet = nullptr;
    bool deletePacket = true;
    std::vector<WorldPacket*> requeuePackets;
    uint32 processedPackets = 0;
    time_t currentTime = GameTime::GetGameTime();

    constexpr uint32 MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE = 100;
    while (m_Socket && _recvQueue.next(packet, updater)){
	...
	OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode()); // 找到对应协议处理的函数
	ClientOpcodeHandler const* opHandle = opcodeTable[opcode];
	opHandle->Call(this, *packet);   // 执行对应协议函数
	...
	}
posted @ 2025-02-25 16:54  最美的期待  阅读(139)  评论(0)    收藏  举报
levels of contents