10.2.3 TrinityCore 网络模块封装
完成端口(IOCP)
完成端口不是指物理上的端口,也不是指网络中的端口,而是指操作系统所提供的一种机制。 这种机制是操作系统提供一种高效处理 io 操作结果的通知机制。总之,完成端口是一种实现高效 异步 io 的机制。
原理图
重叠 io
无需等待上一个 IO 操作完成就可以提交下一个 IO 操作请求。也就是这些 IO 操作可以堆叠在一 起。
注意:尽管 IO 操作是按顺序投递的,但是 IO 操作完成通知可以是随机无需的(在多线程等待 IO 完成通知时)。
重要接口
CreateIoCompletionPort
该接口可以仅创建 IO 完成端口,也可以将现有 IO 完成端口与支持重叠 IO 的任何句柄(TCP 套接字,文件,命名管道等)相关联。
// 头文件:ioapiset.h
HANDLE CreateIoCompletionPort([in] HANDLE FileHandle,[in, optional] HANDLE ExistingCompletionPort,[in] ULONG_PTR CompletionKey,[in] DWORD NumberOfConcurrentThreads
);
AcceptEx 函数接受新连接,返回本地和远程地址,并接收客户端应用程序发送的第一个数据块。
BOOL AcceptEx([in] SOCKET sListenSocket,[in] SOCKET sAcceptSocket,[in] PVOID lpOutputBuffer,[in] DWORD dwReceiveDataLength,[in] DWORD dwLocalAddressLength,[in] DWORD dwRemoteAddressLength,[out] LPDWORD lpdwBytesReceived,[in] LPOVERLAPPED lpOverlapped
);
ConnectEx 函数与指定的套接字建立连接,并可以选择在建立连接后发送数据。 仅在面向连 接的套接字上支持 ConnectEx 函数。
BOOL ConnectEx([in] SOCKET s,[in] const sockaddr *name,[in] int namelen,[in, optional] PVOID lpSendBuffer,[in] DWORD dwSendDataLength,[out] LPDWORD lpdwBytesSent,[in] LPOVERLAPPED lpOverlapped
)
DisconnectEx 函数关闭套接字上的连接,并允许重用套接字句柄。
BOOL DisconnectEx(SOCKET s,LPOVERLAPPED lpOverlapped,DWORD dwFlags,DWORD dwReserved
)
WSARecv 函数从连接的套接字或绑定的无连接套接字接收数据。
int WSAAPI WSARecv([in] SOCKET s,[in, out] LPWSABUF lpBuffers,[in] DWORD dwBufferCount,[out] LPDWORD lpNumberOfBytesRecvd,[in, out] LPDWORD lpFlags,[in] LPWSAOVERLAPPED lpOverlapped,[in] LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
WSASend 函数在连接的套接字上发送数据。
int WSAAPI WSASend([in] SOCKET s,[in] LPWSABUF lpBuffers,[in] DWORD dwBufferCount,[out] LPDWORD lpNumberOfBytesSent,[in] DWORD dwFlags,[in] LPWSAOVERLAPPED lpOverlapped,[in] LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
GetQueuedCompletionStatus
尝试从指定的 I/O 完成端口取消对 I/O 完成数据包的排队。 如果没有完成数据包排队,函数 将等待与完成端口关联的挂起 I/O 操作完成。 此函数将线程与指定的完成端口相关联。 一个线程最多可以与一个完成端口相关联。
BOOL GetQueuedCompletionStatus([in] HANDLE CompletionPort,LPDWORD lpNumberOfBytesTransferred,[out] PULONG_PTR lpCompletionKey,[out] LPOVERLAPPED *lpOverlapped,[in] DWORD dwMilliseconds
);
boost.asio
boost.asio 是一个跨平台的 C++ 网络库。支持同步 IO 和异步 IO。
同步 IO
boost::asio::io_context io_context;
boost::asio::ip::tcp::socket socket(io_context);
// 抛异常版本的接口
socket.connect(server_endpoint); // 将会抛出异常// 带错误信息版本的接口
boost::system::error_code ec;
socket.connect(server_endpoint, ec); // 这里不会抛出异常
异步 IO
proactor 模型
Asynchronous Operation Processor reactor 模型中, 相当于 select、poll、epoll。 IOCP 中, 相当于 AcceptEx、WSARecv、WSASend 等。
怎么描述 proactor 网络模型?
当发起者想要处理一个 io,通过异步操作处理器(AOP) 执行一个异步操作,并在异步操作返回 后将事件写入完成事件队列,同时提供完成处理回调函数等待完成通知后调用该回调函数。
前摄器通过异步事件解复用器阻塞等待完成事件队列中的事件发生,并将完成事件返回给发起者调 用回调函数。
基本概念及接口
io_context 类似 reactor 对象,或者 iocp 对象。
ip::tcp::endpoint:端点,某个地址和端口的封装。
ip::tcp::socket:socket 创建时,需要与 io_context 进行绑定。
reactor 和 proactor 的区别
reactor 和 proactor 处理 io 的设计模式,都是将对 io 的处理转化为对事件的处理(共同点)。
不同点:
- reactor 通常采用的是同步 io,proactor 通常采用的是异步 io。注意:proactor 也可由 reactor 来实现。
- reactor 处理方式是你要做什么(处理 io),reactor 告诉你什么时候(就绪事件)可以做, 然后你再做(处理 io)。proactor 处理方式是你要做什么(处理 io),proactor 把事件做完 (处理 io)后告诉你处理后的结果(完成事件)。
- reactor 是就绪事件(可以做的时机);proactor 是完成事件(处理后的结果)。
- reactor 处理事件的方式是:注册事件一次,提供回调函数,循环反复处理【事件触发,调用 回调函数】;proactor 处理事件的方式是:循环反复处理【发起事件,提供回调,事件触 发,调用回调函数】。
怎么通过 reactor 模式实现为 proactor 模式?
- reactor 在注册事件的时候提供的回调函数职责由 io 就绪后的处理逻辑改为 io 处理完成后的 处理逻辑。
- 同时 proactor 在处理事件回调过程中,内部封装好 io 处理流程,处理完毕后再调用回调函 数。
TrinityCore
原理图
AsncAcceptor.h:封装 tcp::acceptor,并实现负载均衡机制,将接收的 socket 转发到 network 线程;
NetworkThread.h :封装读写操作处理流程以及管理 socket,每 1ms 更新一次 socket。
Socket.h :封装单个连接,定制读写操作,考虑到跨平台处理的差异。
SocketMgr.h :管理整个网络模块的抽象,提供连接打开后的操作。
参考链接:0voice · GitHub