Trinitycore的网络通信(boost::asio)
整体类图

一、写数据
整体流程:
具体代码见下方《代码解析》
-
起服时通过全局静态对象 WorldSocketMgr,在成员方法 WorldSocketMgr::StartNetwork 内,根据配置创建多个(默认为2个)网络线程对象 NetWorkThread 。
同时创建 AsyncAcceptor 对象,其内部封装了 boost::asio::tcp::acceptor ,用于接收新 socket 。
调用 NetWorkThread::Start() , 创建 std::thread, 执行绑定的 NetWorkThread::Run()
-
线程执行 NetWorkThread::Run() , 执行 boost::asio::io_context::run() , 开启事件循环。
同时不断循环定时器,每毫秒调用 NetWorkThread::Update() , 注意对于同一个 NetWorkThread 对象,可能是不同的 std::thread去 执行 Update() 。
在Update()中依次检查NetWorkThread管理的所有 socket ,执行 socket.Update() 。同时检查 socket 状态,如果返回 false, 代表已关闭进行删除逻辑。
-
注意这里的 _bufferQueue 是 MPSC 即 多生产者单消费者的无锁队列,队列中的元素是各个业务发送的协议数据包,由程序员根据协议字段压包。感觉业务这里可以用 Protobuf 。
这里的生产者是各个发包业务,消费者就是 std::thread 。在 Update() 中合并多条协议数据包塞入新创建的 MessageBuffer 中,如果塞不下新创建另一个 MessageBuffer 塞入。
这里使用 ARC4 算法加密头部,body 没有加密。然后将协议数据包 MessageBuffer,写入 WorldSocket._writeQueue
-
调用 _socket.write_some 真正将 _writeQueue 中的协议数据包发送。由于 _socket 设置了 non_blocking,这里不会阻塞。剩余没发送完的,异步 AsyncProcessQueue 发送。 直到 _writeQueue 全清空
代码解析:
-
起服时通过全局静态对象 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;
}
-
线程执行 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());
-
注意这里的 _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;
}
- 调用 _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();
}
二、读数据
整体流程:
-
起服时执行 AsyncAcceptor::AsyncAcceptWithCallback , 调用 boost::asio::tcp::acceptor::async_accept() 异步接收新 socket, 注意这里选择管理 socket 最少的那个 thread 来管理新 socket。
接收新 socket 后,重新将 async_accept 加入事件监听。
-
async_accept 里执行回调函数,最终执行到 Socket::AsyncRead() , 这里 _socket.async_read_some 异步读取数据到 _readBuffer
-
异步读到数据后,先填充 _headerBuffer 头,再填充协议包实体 _packetBuffer。由于 socket 是流式传输,有可能存在这次没接收完整。那么就 break 退出,等待下次异步读取执行到 ReadHandler。
当数据包读完整后,调用 WorldSocket::ReadDataHandler
-
在 WorldSocket::ReadDataHandler 中,检查数据包合法性。 然后加锁,将数据包写入带锁队列 WorldSocket::_recvQueue
-
在游戏主线程中,每毫秒执行全局Update(), 然后遍历所有玩家对象,即 WorldSession 对象,执行 WorldSession::Update()
其中会从 WorldSession::_recvQueue 取出协议包,然后根据 opcode,找到回调函数执行。
代码解析:
-
起服时执行 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>();
});
}
- 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));
}
-
异步读到数据后,先填充 _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();
}
- 在 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 压包
-
在游戏主线程中,每毫秒执行全局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); // 执行对应协议函数
...
}

浙公网安备 33010602011771号