一.UDP协议简介
UDP(User Datagram Protocol)是一种无连接的传输层协议,属于OSI模型中的传输层。其核心特点如下:
- 1.无连接: 无需三次握手建立连接,直接发送数据报(datagram),适合快速传输但对可靠性要求不高的场景。
- 2.不可靠传输: 不保证数据顺序、不确认接收、不重传丢失包,因此可能出现丢包、乱序或重复包。
- 3.低开销: 协议头仅8字节(源/目的端口、长度、校验和),远小于TCP的20字节,传输效率高。
- 4.支持广播/多播: 可一对多发送数据,适合实时音视频流、在线游戏状态同步等场景。
- 5.应用场景: 实时音视频(如WebRTC)、在线游戏、DNS查询、传感器数据传输等。
二.UDP套接字封装
网络地址封装实现:
SocketAddress.h
#pragma once
class SocketAddress
{
public:
SocketAddress(uint32_t inAddress, uint16_t inPort)
{
//使用IPV4协议
GetAsSockAddrIn()->sin_family = AF_INET;
//主机字节序转换为网络字节序
GetAsSockAddrIn()->sin_addr.S_un.S_addr = htonl(inAddress);//ip地址
GetAsSockAddrIn()->sin_port = htons(inPort);//端口
}
SocketAddress(const sockaddr& inSockAddr)
{
//复制inSockAddr到成员mSockAddr
memcpy(&mSockAddr, &inSockAddr, sizeof(sockaddr));
}
SocketAddress()
{
//使用IPV4协议
GetAsSockAddrIn()->sin_family = AF_INET;
//IP地址设置为INADDR_ANY(表示接受所有可用的网络接口)
GetIP4Ref() = INADDR_ANY;
//设置端口号
GetAsSockAddrIn()->sin_port = 0;
}
//比较地址族 端口号 IP地址
bool operator==(const SocketAddress& inOther) const noexcept
{
if (this == &inOther) return true;
return (mSockAddr.sa_family == AF_INET &&
GetAsSockAddrIn()->sin_port == inOther.GetAsSockAddrIn()->sin_port) &&
(GetIP4Ref() == inOther.GetIP4Ref());
}
//计算SocketAddress对象哈希值
size_t GetHash() const noexcept
{
auto hash_combine = [](size_t seed, auto v) {
return seed ^ (std::hash<decltype(v)>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
};
size_t hash = std::hash<uint32_t>()(GetIP4Ref());
hash = hash_combine(hash, ntohs(GetAsSockAddrIn()->sin_port));
hash = hash_combine(hash, mSockAddr.sa_family);
return hash;
}
uint32_t GetSize() const { return sizeof(sockaddr); }
string ToString() const;
private:
friend class UDPSocket;
friend class TCPSocket;
//存储socket地址信息
sockaddr mSockAddr;
//获取IPV4地址引用
#if _WIN32
uint32_t& GetIP4Ref() { return *reinterpret_cast<uint32_t*>(&GetAsSockAddrIn()->sin_addr.S_un.S_addr); }
const uint32_t& GetIP4Ref() const { return *reinterpret_cast<const uint32_t*>(&GetAsSockAddrIn()->sin_addr.S_un.S_addr); }
#else
uint32_t& GetIP4Ref() { return GetAsSockAddrIn()->sin_addr.s_addr; }
const uint32_t& GetIP4Ref() const { return GetAsSockAddrIn()->sin_addr.s_addr; }
#endif
//获取指向sockaddr_in结构体的指针 方便访问和操作sockaddr结构体中的IPv4地址和端口信息
sockaddr_in* GetAsSockAddrIn()
{
return reinterpret_cast<sockaddr_in*>(&mSockAddr);
}
const sockaddr_in* GetAsSockAddrIn() const
{
return reinterpret_cast<const sockaddr_in*>(&mSockAddr);
}
};
typedef shared_ptr<SocketAddress> SocketAddressPtr;
/*
为自定义类型 SocketAddress 提供哈希计算功能,
使其可以作为无序容器(如 std::unordered_map、std::unordered_set)的键
*/
namespace std {
template<>
struct hash<SocketAddress> {
size_t operator()(const SocketAddress& sa) const noexcept {
return sa.GetHash();
}
};
}
SocketAddress.cpp
#include"MalouPCH.h"
string SocketAddress::ToString() const
{
#if _WIN32
const sockaddr_in* s = GetAsSockAddrIn();
wchar_t destinationBuffer[128];
if (s->sin_family == AF_INET)
{
snprintf((char*)destinationBuffer, sizeof(destinationBuffer),
"%d.%d.%d.%d",
s->sin_addr.s_addr & 0xFF,
(s->sin_addr.s_addr >> 8) & 0xFF,
(s->sin_addr.s_addr >> 16) & 0xFF,
(s->sin_addr.s_addr >> 24) & 0xFF);
}
else
{
InetNtop(s->sin_family, const_cast<in_addr*>(&s->sin_addr), destinationBuffer, sizeof(destinationBuffer));
}
return StringUtil::Sprintf("%s:%d",
destinationBuffer,
ntohs(s->sin_port));
#else
//not implement on mac for now...
return string("not implemented on mac for now");
#endif
}
UDPSocket.h
#pragma once
#include"SocketAddress.h"
class UDPSocket
{
public:
~UDPSocket();
/*
将套接字绑定到指定地址和端口
@param InToAddress IP地址和端口信息
@return 成功返回 NO_ERROR,失败返回错误码
*/
int Bind(const SocketAddress& InToAddress);
/*
向指定地址发送UDP数据包
@param InToSend 要发送的数据缓冲区
@param InLen 数据长度
@param 目标地址信息
@return 成功: 实际发送的字节数;失败: 返回负的错误码
*/
int SendTo(const void* InToSend, int InLen, const SocketAddress& InToAddress);
/*
接收UDP数据包并获取发送方地址
@param InToReceive 接收数据缓冲区
@param InLen 缓冲区长度
@param OutFromAddress 输出参数,存储发送方地址
@return
成功:
返回接收的字节数
失败:
1.非阻塞模式下无数据: 返回0;
2.连接重置: 返回 -WSAECONNRESET;
3.其他错误: 返回负的错误码。
*/
int ReceiveFrom(void* InToReceive, int InLen, SocketAddress& OutFromAddress);
/*
设置套接字为阻塞或非阻塞模式
@param InShouldBeNonBlocking
@return 成功: NO_ERROR;失败: 错误码
*/
int SetNonBlockingMode(bool InShouldBeNonBlocking);
private:
friend class SocketUtil;
UDPSocket(SOCKET InSocket) :mSocket(InSocket) {}
SOCKET mSocket;
};
typedef shared_ptr<UDPSocket> UDPSocketPtr;
UDPSocket.cpp
#include"MalouPCH.h"
UDPSocket::~UDPSocket()
{
//跨平台处理 关闭socket
#if _WIN32
closesocket(mSocket);
#else
close(mSocket);
#endif
}
int UDPSocket::Bind(const SocketAddress& InToAddress)
{
int err = bind(mSocket, &InToAddress.mSockAddr, InToAddress.GetSize());
if (0 != err)
{
SocketUtil::ReportError("UDPSokcet::Bind");
return SocketUtil::GetLastError();
}
return NO_ERROR;
}
int UDPSocket::SendTo(const void* InToSend, int InLen, const SocketAddress& InToAddress)
{
int byteSentCount = sendto(mSocket, static_cast<const char*>(InToSend), InLen, 0, &InToAddress.mSockAddr, InToAddress.GetSize());
if (byteSentCount <= 0)
{
SocketUtil::ReportError("UDPSocket::SendTo");
return -SocketUtil::GetLastError();
}
return byteSentCount;
}
int UDPSocket::ReceiveFrom(void* InToReceive, int InLen, SocketAddress& OutFromAddress)
{
socklen_t fromLength = OutFromAddress.GetSize();
int readByteCount = recvfrom(mSocket, static_cast<char*>(InToReceive), InLen, 0, &OutFromAddress.mSockAddr, &fromLength);
if (readByteCount > 0)
{
return readByteCount;
}
else
{
int err = SocketUtil::GetLastError();
if (WSAEWOULDBLOCK == err)
{
return 0;
}
else if (WSAECONNRESET == err)
{
LOG("Connection reset from %s", OutFromAddress.ToString().c_str());
return -WSAECONNRESET;
}
else
{
SocketUtil::ReportError("UDPSocket::ReceiveFrom");
return -err;
}
}
}
int UDPSocket::SetNonBlockingMode(bool InShouldBeNonBlocking)
{
#if _WIN32
u_long arg = InShouldBeNonBlocking ? 1 : 0;
int result = ioctlsocket(mSocket, FIONBIO, &arg);
#else
int flags = fcntl(mSocket, F_GETFL, 0);
flags = InShouldBeNonBlocking ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);
int result = fcntl(mSocket, F_SETFL, flags);
#endif
if (SOCKET_ERROR == result)
{
SocketUtil::ReportError("UDPSocket::SetNonBlockingMode");
return SocketUtil::GetLastError();
}
else
{
return NO_ERROR;
}
}
三.基于bit流的网络数据读写类
MemoryBitStream.h
#pragma once
/*
* 浮点数转换定点数
* @param InNumber 输入的浮点数
* @param InMin 数值范围的最小值
* @param InPrecision 量化精度(步长)
* @return uint32_t
*/
inline uint32_t ConvertToFixed(float InNumber, float InMin, float InPrecision)
{
return static_cast<int>((InNumber - InMin) / InPrecision);
}
/*
* 定点数转换浮点数
* @param InNumber 输入的整数
* @param InMin 数值范围的最小值
* @param InPrecision 量化精度(步长)
* @return float
*/
inline float ConvertFromFixed(uint32_t InNumber, float InMin, float InPrecision)
{
return InNumber * InPrecision + InMin;
}
/*输出bit流*/
class OutputMemoryBitStream
{
public:
OutputMemoryBitStream() :
mBitHead(0),
mBuffer(nullptr)
{
ReallocBuffer(1500 * 8);
}
/*释放缓冲区内存*/
~OutputMemoryBitStream() { delete mBuffer; }
/*
* 写入指定bit位数据
* @param InData 待写入数据 单字节
* @param InBitCount 写入长度
*/
void WriteBits(uint8_t InData, uint32_t InBitCount);
/*
* 写入指定bit位数据
* @param InData 待写入数据
* @param InBitCount 写入长度
*/
void WriteBits(const void* InData, uint32_t InBitCount);
/*获取缓冲区指针*/
const char* GetBufferPtr() const { return mBuffer; }
/*获取缓冲区bit位置偏移*/
uint32_t GetBitLength() const { return mBitHead; }
/*获取缓冲区字节位置偏移*/
uint32_t GetByteLength() const { return (mBitHead + 7) >> 3; }
/*
* 按字节长度写入数据
* @param InData 待写入数据
* @param InByteCount 字节长度
*/
void WriteBytes(const void* InData, uint32_t InByteCount)
{
WriteBits(InData, InByteCount << 3);
}
template<typename T>
void Write(T InData, uint32_t InBitCount = sizeof(T) * 8)
{
static_assert(std::is_arithmetic<T>::value ||
std::is_enum<T>::value,
"Generic Write only supports primitive data types");
WriteBits(&InData, InBitCount);
}
/*写入bool类型数据*/
void Write(bool InData) { WriteBits(&InData, 1); }
/*写入Vector3类型数据*/
void Write(const Vector3& InVector);
/*写入Quaternion类型数据*/
void Write(const Quaternion& InQuat);
/*写入string类型数据*/
void Write(const string& InString)
{
uint32_t elementCount = static_cast<uint32_t>(InString.size());
Write(elementCount);
for (const auto& element : InString)
{
Write(element);
}
}
private:
/*分配缓冲区内存*/
void ReallocBuffer(uint32_t InNewBitLength);
private:
/*缓冲区指针*/
char* mBuffer;
/*缓冲区位偏移*/
uint32_t mBitHead;
/*缓冲区容量大小*/
uint32_t mBitCapacity;
};
/*输入bit流*/
class InputMemoryBitStream
{
public:
InputMemoryBitStream(char* InBuffer, uint32_t InBitCount) :
mBuffer(InBuffer),
mBitCapacity(InBitCount),
mBitHead(0),
mIsBufferOwner(false) {
}
InputMemoryBitStream(const InputMemoryBitStream& InOther) :
mBitCapacity(InOther.mBitCapacity),
mBitHead(InOther.mBitHead),
mIsBufferOwner(true)
{
int byteCount = (mBitCapacity + 7) / 8;
mBuffer = static_cast<char*>(new char[byteCount]);
memcpy(mBuffer, InOther.mBuffer, byteCount);
}
~InputMemoryBitStream()
{
if (mIsBufferOwner) delete mBuffer;
}
const char* GetBufferPtr()const { return mBuffer; }
uint32_t GetRemainingBitCount()const { return mBitCapacity - mBitHead; }
void ReadBits(uint8_t& OutData, uint32_t InBitCount);
void ReadBits(void* OutData, uint32_t InBitCount);
void ReadBytes(void* OutData, uint32_t InByteCount) { ReadBits(OutData, InByteCount << 3); }
template<typename T>
void Read(T& InData, uint32_t InBitCount = sizeof(T) * 8)
{
static_assert(std::is_arithmetic< T >::value ||
std::is_enum< T >::value,
"Generic Read only supports primitive data types");
ReadBits(&InData, InBitCount);
}
void Read(uint32_t& OutData, uint32_t InBitCount = 32) { ReadBits(&OutData, InBitCount); }
void Read(int& OutData, uint32_t InBitCount = 32) { ReadBits(&OutData, InBitCount); }
void Read(float& OutData) { ReadBits(&OutData, 32); }
void Read(uint16_t& OutData, uint32_t InBitCount = 16) { ReadBits(&OutData, InBitCount); }
void Read(int16_t& OutData, uint32_t InBitCount = 16) { ReadBits(&OutData, InBitCount); }
void Read(uint8_t& OutData, uint32_t InBitCount = 8) { ReadBits(&OutData, InBitCount); }
void Read(bool& OutData) { ReadBits(&OutData, 1); }
void Read(string& InString)
{
uint32_t elementCount;
Read(elementCount);
InString.resize(elementCount);
for (auto& element : InString)
{
Read(element);
}
}
void ResetToCapacity(uint32_t InByteCount)
{
mBitCapacity = InByteCount << 3;
mBitHead = 0;
}
void Read(Quaternion& OutQuat);
void Read(Vector3& OutVector);
private:
/*缓冲区指针*/
char* mBuffer;
/*缓冲区位偏移*/
uint32_t mBitHead;
/*缓冲区容量大小*/
uint32_t mBitCapacity;
bool mIsBufferOwner;
};
MemoryBitStream.cpp
#include"MalouPCH.h"
void OutputMemoryBitStream::ReallocBuffer(uint32_t InNewBitLength)
{
if (mBuffer == nullptr)
{
//分配内存 一个字节8位 所以要右移3位计算字节数
mBuffer = new char[InNewBitLength >> 3];
//初始化内存为0
memset(mBuffer, 0, InNewBitLength >> 3);
}
else
{
//分配临时内存
char* tempBuffer = new char[InNewBitLength >> 3];
//初始化内存为0
memset(tempBuffer, 0, InNewBitLength >> 3);
//拷贝旧数据到新的内存区
memcpy(tempBuffer, mBuffer, mBitCapacity >> 3);
//释放旧缓冲区
delete mBuffer;
//更新指针指向新缓冲区
mBuffer = tempBuffer;
}
//更新容量
mBitCapacity = InNewBitLength;
}
void OutputMemoryBitStream::WriteBits(uint8_t InData, uint32_t InBitCount)
{
//计算写入后的新头指针位置
uint32_t nextBitHead = mBitHead + InBitCount;
//大于当前容量时 更新缓冲区大小
if (nextBitHead > mBitCapacity)
{
ReallocBuffer(std::max(mBitCapacity * 2, nextBitHead));
}
//计算字节偏移 = mBitHead / 8
uint32_t byteOffset = mBitHead >> 3;
//计算位偏移 = mBitHead % 8
uint32_t bitOffset = mBitHead & 0x7;
//构造位掩码 保护不需要修改的位
//0xff = 1111 1111 如bitOffset=3左移3位再取反 生成0000 0111
uint8_t currentMask = ~(0xff << bitOffset);
//按位操作保留原值未覆盖部分,写入新数据
//例:bitOffset=3 还剩5位可以写入数据
//mBuffer[byteOffset] = 0000 0111 & currentMask 0000 0111 = 0000 0111不修改原有数据
//InData<<bitOffset计算新数据中当前字节可以被写入的数据,例如InData=1111 1111,
//则InData<<bitOffset=1111 1000;与先前的结果进行|运算 填满当前字节。
mBuffer[byteOffset] = (mBuffer[byteOffset] & currentMask) | (InData << bitOffset);
//处理跨字节写入
//计算当前字节剩余可写入数据的位数
uint32_t bitsFreeThisByte = 8 - bitOffset;
//如果剩余空间不足,将多余位写入下一个字节
//例如:bitOffset=3 时写入8位数据,需要5位在当前字节,3位在下一字节
if (bitsFreeThisByte < InBitCount)
{
//右移取得当前字节存储完后剩余的数据 存入下一字节
mBuffer[byteOffset + 1] = InData >> bitsFreeThisByte;
}
//更新头指针
mBitHead = nextBitHead;
}
void OutputMemoryBitStream::WriteBits(const void* InData, uint32_t InBitCount)
{
const char* srcByte = static_cast<const char*>(InData);
//循环一次写入8个bit到缓冲区
while (InBitCount > 8)
{
WriteBits(*srcByte, 8);
//更新源数组指针
++srcByte;
//更新源数据长度
InBitCount -= 8;
}
//写入剩余不足8个bit的数据
if (InBitCount > 0)
{
WriteBits(*srcByte, InBitCount);
}
}
void OutputMemoryBitStream::Write(const Vector3& InVector)
{
Write(InVector.X);
Write(InVector.Y);
Write(InVector.Z);
}
void OutputMemoryBitStream::Write(const Quaternion& InQuat)
{
float precision = 2.f / 65535.f;
Write(ConvertToFixed(InQuat.X, -1.f, precision), 16);
Write(ConvertToFixed(InQuat.Y, -1.f, precision), 16);
Write(ConvertToFixed(InQuat.Z, -1.f, precision), 16);
Write(InQuat.W < 0);
}
void InputMemoryBitStream::ReadBits(uint8_t& OutData, uint32_t InBitCount)
{
//计算字节偏移 = mBitHead / 8
uint32_t byteOffset = mBitHead >> 3;
//计算单字节位偏移 = mBitHead % 8
uint32_t bitOffset = mBitHead & 0x7;
//从当前字节读取数据,并右移去除低位不需要的比特
OutData = static_cast<uint8_t>(mBuffer[byteOffset]) >> bitOffset;
//处理跨字节读取
uint8_t bitsFreeThisByte = 8 - bitOffset;
if (bitsFreeThisByte < InBitCount)
{
//如果当前字节剩余位数不足,从下一个字节读取高位数据
//通过位或操作(|=)合并两部分数据
OutData |= static_cast<uint8_t>(mBuffer[byteOffset + 1]) << bitsFreeThisByte;
}
//生成掩码保留指定比特数 清除超出InBitCount的高位数据
OutData &= (~(0xff << InBitCount));
//更新读取位置
mBitHead += InBitCount;
}
void InputMemoryBitStream::ReadBits(void* OutData, uint32_t InBitCount)
{
uint8_t* destByte = reinterpret_cast<uint8_t*>(OutData);
while (InBitCount > 8)
{
ReadBits(*destByte, 8);
++destByte;
InBitCount -= 8;
}
if (InBitCount > 0)
{
ReadBits(*destByte, InBitCount);
}
}
void InputMemoryBitStream::Read(Quaternion& OutQuat)
{
float precision = 2.f / 65536.f;
uint32_t f = 0;
Read(f, 16);
OutQuat.X = ConvertFromFixed(f, -1.f, precision);
Read(f, 16);
OutQuat.Y = ConvertFromFixed(f, -1.f, precision);
Read(f, 16);
OutQuat.Z = ConvertFromFixed(f, -1.f, precision);
OutQuat.W = sqrtf(1.f -
(OutQuat.X * OutQuat.X +
OutQuat.Y * OutQuat.Y +
OutQuat.Z * OutQuat.Z));
bool isNegative;
Read(isNegative);
if (isNegative)
{
OutQuat.W *= -1;
}
}
void InputMemoryBitStream::Read(Vector3& OutVector)
{
Read(OutVector.X);
Read(OutVector.Y);
Read(OutVector.Z);
}
四.网络管理器
基类 NetworkManager.h
typedef unordered_map<int, GameObjectPtr> IntToGameObjectMap;
class NetworkManager
{
public:
//静态常量标记数据包的类型
static const uint32_t kHelloCC = 'HELO';
static const uint32_t kWelcomeCC = 'WLCM';
static const uint32_t kStateCC = 'STAT';
static const uint32_t kInputCC = 'INPT';
static const int kMaxPacketsPerFrameCount = 10;
NetworkManager();
virtual ~NetworkManager();
bool Init(uint16_t InPort);
/*处理收到的所有数据包*/
void ProcessIncomingPackets();
/*处理数据包*/
virtual void ProcessPacket(InputMemoryBitStream& InInputStream, const SocketAddress& InFromAddress) = 0;
/*处理连接重置*/
virtual void HandleConnectionReset(const SocketAddress& inFromAddress) { (void)inFromAddress; }
/*发送数据包*/
void SendPacket(const OutputMemoryBitStream& inOutputStream, const SocketAddress& inFromAddress);
const WeightedTimedMovingAverage& GetBytesReceivedPerSecond() const { return mBytesReceivedPerSecond; }
const WeightedTimedMovingAverage& GetBytesSentPerSecond() const { return mBytesSentPerSecond; }
//模拟丢包和延迟
void SetDropPacketChance(float inChance) { mDropPacketChance = inChance; }
float GetDropPacketChance() const { return mDropPacketChance; }
void SetSimulatedLatency(float inLatency) { mSimulatedLatency = inLatency; }
float GetSimulatedLatency() const { return mSimulatedLatency; }
inline GameObjectPtr GetGameObject(int inNetworkId) const;
/*添加网络id和游戏对象映射*/
void AddToNetworkIdToGameObjectMap(GameObjectPtr inGameObject);
/*移除网络id和游戏对象映射*/
void RemoveFromNetworkIdToGameObjectMap(GameObjectPtr inGameObject);
protected:
IntToGameObjectMap mNetworkIdToGameObjectMap;
private:
/*处理收到的包*/
class ReceivedPacket
{
public:
ReceivedPacket(float inReceivedTime, InputMemoryBitStream& inInputMemoryBitStream, const SocketAddress& inAddress);
/*获取发包方地址*/
const SocketAddress& GetFromAddress() const { return mFromAddress; }
/*获取收包的时间戳*/
float GetReceivedTime() const { return mReceivedTime; }
/*获取数据包缓冲区*/
InputMemoryBitStream& GetPacketBuffer() { return mPacketBuffer; }
private:
/*收包时间*/
float mReceivedTime;
/*数据包缓冲区*/
InputMemoryBitStream mPacketBuffer;
/*发包方地址*/
SocketAddress mFromAddress;
};
/*更新每秒发送字节数统计的工具函数 统计网络吞吐量*/
void UpdateBytesSentLastFrame();
/*读取收到的数据包并加入到队列中*/
void ReadIncomingPacketsIntoQueue();
/*处理队列中的数据包*/
void ProcessQueuedPackets();
/*数据包队列*/
queue<ReceivedPacket,list<ReceivedPacket>> mPacketQueue;
UDPSocketPtr mSocket;
WeightedTimedMovingAverage mBytesReceivedPerSecond;
WeightedTimedMovingAverage mBytesSentPerSecond;
int mBytesSentThisFrame;
float mDropPacketChance;
float mSimulatedLatency;
};
inline GameObjectPtr NetworkManager::GetGameObject(int inNetworkId) const
{
auto gameObjectIt = mNetworkIdToGameObjectMap.find(inNetworkId);
if (gameObjectIt != mNetworkIdToGameObjectMap.end())
{
return gameObjectIt->second;
}
else
{
return GameObjectPtr();
}
}
NetworkManager.cpp
#include"MalouPCH.h"
NetworkManager::NetworkManager():
mBytesSentThisFrame(0),
mDropPacketChance(0.f),
mSimulatedLatency(0.f)
{
}
NetworkManager::~NetworkManager()
{
}
bool NetworkManager::Init(uint16_t InPort)
{
mSocket = SocketUtil::CreateUDPSocket(INET);
SocketAddress ownAddress(INADDR_ANY, InPort);
mSocket->Bind(ownAddress);
LOG("Initializing NetworkManager at port %d", InPort);
mBytesReceivedPerSecond = WeightedTimedMovingAverage(1.f);
mBytesSentPerSecond = WeightedTimedMovingAverage(1.f);
if (mSocket == nullptr) return false;
if (mSocket->SetNonBlockingMode(true) != NO_ERROR) return false;
return true;
}
void NetworkManager::ProcessIncomingPackets()
{
ReadIncomingPacketsIntoQueue();
ProcessQueuedPackets();
UpdateBytesSentLastFrame();
}
void NetworkManager::SendPacket(const OutputMemoryBitStream& InOutputStream, const SocketAddress& InFromAddress)
{
int sentByteCount = mSocket->SendTo(InOutputStream.GetBufferPtr(), InOutputStream.GetByteLength(), InFromAddress);
if (sentByteCount > 0)
{
mBytesSentThisFrame += sentByteCount;
}
}
void NetworkManager::ProcessQueuedPackets()
{
//遍历队列
while (!mPacketQueue.empty())
{
ReceivedPacket& nextPacket = mPacketQueue.front();//获取队列前端的引用
//检查当前时间(Timing::Instance.GetTimef())是否已经超过数据包的预定接收时间 模拟真实网络延迟 同时保持时序正确性
if (Timing::Instance.GetTimef() > nextPacket.GetReceivedTime())
{
//处理数据包
ProcessPacket(nextPacket.GetPacketBuffer(), nextPacket.GetFromAddress());//纯虚函数由客户端和服务端分别自定义处理逻辑
mPacketQueue.pop();//从队列中移除
}
else
{
break;
}
}
}
void NetworkManager::UpdateBytesSentLastFrame()
{
if (mBytesSentThisFrame > 0)
{
mBytesSentPerSecond.UpdatePerSecond(static_cast<float>(mBytesSentThisFrame));
mBytesSentThisFrame = 0;
}
}
void NetworkManager::ReadIncomingPacketsIntoQueue()
{
char packetMem[1500];//缓冲区存储收到的数据包,以太网MTU通常为1500个字节
int packetSize = sizeof(packetMem);//设置为缓冲区大小
InputMemoryBitStream inputSteam(packetMem, packetSize * 8);//输入bit流对象 处理接收到的数据
SocketAddress fromAddress;//存储发送方地址信息
int receivePackedCount = 0;//接收计数器
int totalReadByteCount = 0;//总共读取的字节数
//使用while循环,限制每帧最多处理kMaxPacketsPerFrameCount个数据包
while (receivePackedCount < kMaxPacketsPerFrameCount)
{
int readByteCount = mSocket->ReceiveFrom(packetMem, packetSize, fromAddress);//读取数据
if (0 == readByteCount)
{
break;
}
else if (-WSAECONNRESET == readByteCount)//连接重置错误
{
HandleConnectionReset(fromAddress);
}
else if (readByteCount > 0)
{
inputSteam.ResetToCapacity(readByteCount);//重置输入流容量
++receivePackedCount;//增加接收计数器
totalReadByteCount += readByteCount;//增加读取的总字节数
//模拟丢包
if (MalouMath::GetRandomFloat() >= mDropPacketChance)
{
//计算模拟接收时间
float simulatedReceiveTime = Timing::Instance.GetTimef() + mSimulatedLatency;
//加入队列
mPacketQueue.emplace(simulatedReceiveTime, inputSteam, fromAddress);
}
else
{
LOG("Dropped packet!", 0);
}
}
else
{
LOG("UCatch Error!", 0);
}
}
if (totalReadByteCount > 0)
{
//如果有接收到数据,更新每秒接收字节数的统计
mBytesReceivedPerSecond.UpdatePerSecond(static_cast<float>(totalReadByteCount));
}
}
void NetworkManager::AddToNetworkIdToGameObjectMap(GameObjectPtr InGameObject)
{
mNetworkIdToGameObjectMap[InGameObject->GetNetworkId()] = InGameObject;
}
void NetworkManager::RemoveFromNetworkIdToGameObjectMap(GameObjectPtr InGameObject)
{
mNetworkIdToGameObjectMap.erase(InGameObject->GetNetworkId());
}
NetworkManager::ReceivedPacket::ReceivedPacket(float InReceivedTime, InputMemoryBitStream& IoInputMemoryBitStream, const SocketAddress& InAddress):
mReceivedTime(InReceivedTime),
mFromAddress(InAddress),
mPacketBuffer(IoInputMemoryBitStream)
{}
服务器网络管理器 NetworkManagerServer.h
class NetworkManagerServer : public NetworkManager
{
public:
static NetworkManagerServer* Instance;
/*初始化单例并启动服务器*/
static bool StaticInit(uint16_t Port);
/*处理已认证客户端的数据包*/
virtual void ProcessPacket(InputMemoryBitStream& InputStream, const SocketAddress& FromAddress) override;
/*处理客户端连接重置*/
virtual void HandleConnectionReset(const SocketAddress& FromAddress) override;
/*发送所有待发数据包*/
void SendOutgoingPackets();
/*检测无响应客户端*/
void CheckForDisconnects();
/*注册需要同步的游戏对象*/
void RegisterGameObject(GameObjectPtr GameObject);
/*注册并返回智能指针包裹的对象*/
inline GameObjectPtr RegisterAndReturn(GameObject* GameObject);
/*取消游戏对象注册*/
void UnregisterGameObject(GameObject* GameObject);
/*标记对象状态需要同步*/
void SetStateDirty(int NetworkId, uint32_t DirtyState);
void RespawnCats();
/*获取玩家客户端代理*/
ClientProxyPtr GetClientProxy(int PlayerId) const;
private:
NetworkManagerServer();
/*处理新客户端连接包*/
void HandlePacketFromNewClient(InputMemoryBitStream& InputStream, const SocketAddress& FromAddress);
/*处理已认证客户端的数据包*/
void ProcessPacket(ClientProxyPtr ClientProxy, InputMemoryBitStream& InputStream);
/*发送欢迎信息给新客户端*/
void SendWelcomePacket(ClientProxyPtr ClientProxy);
/*向所有客户端发送状态更新*/
void UpdateAllClients();
/*序列化游戏世界状态 通常用于客户端刚连接时的完整状态初始化*/
void AddWorldStateToPacket(OutputMemoryBitStream& OutputStream);
/*序列化记分板状态*/
void AddScoreBoardStateToPacket(OutputMemoryBitStream& OutputStream);
/*向特定客户端发送状态包*/
void SendStatePacketToClient(ClientProxyPtr ClientProxy);
void WriteLastMoveTimestampIfDirty(OutputMemoryBitStream& OutputStream, ClientProxyPtr ClientProxy);
/*处理玩家输入指令*/
void HandleInputPacket(ClientProxyPtr ClientProxy, InputMemoryBitStream& InputStream);
/*清理断开连接的客户端*/
void HandleClientDisconnected(ClientProxyPtr ClientProxy);
/*分配新网络对象ID*/
int GetNewNetworkId();
//玩家ID到客户端代理的映射
unordered_map<int, ClientProxyPtr> mPlayerIdToClientMap;
//网络地址到客户端代理的映射
unordered_map<SocketAddress, ClientProxyPtr> mAddressToClientMap;
//新玩家ID分配计数器
int mNewPlayerId;
//新网络对象ID分配计数器
int mNewNetworkId;
//上次状态包发送时间
float mTimeOfLastSatePacket;
//状态包发送间隔(秒)
float mTimeBetweenStatePackets;
//客户端超时断开阈值
float mClientDisconnectTimeout;
};
inline GameObjectPtr NetworkManagerServer::RegisterAndReturn(GameObject* GameObject)
{
GameObjectPtr toRet(GameObject);
RegisterGameObject(toRet);
return toRet;
}
NetworkManagerServer.cpp
#include"MalouServerPCH.h"
NetworkManagerServer* NetworkManagerServer::Instance;
NetworkManagerServer::NetworkManagerServer() :
mNewPlayerId(1),
mNewNetworkId(1),
mTimeBetweenStatePackets(0.033f),
mClientDisconnectTimeout(3.f)
{
}
bool NetworkManagerServer::StaticInit(uint16_t Port)
{
Instance = new NetworkManagerServer();
return Instance->Init(Port);
}
void NetworkManagerServer::HandleConnectionReset(const SocketAddress& FromAddress)
{
//通过网络地址查找客户端代理并处理连接重置
auto it = mAddressToClientMap.find(FromAddress);
if (it != mAddressToClientMap.end())
{
HandleClientDisconnected(it->second);
}
}
void NetworkManagerServer::ProcessPacket(InputMemoryBitStream& InputStream, const SocketAddress& FromAddress)
{
//通过网络地址查找客户端代理
auto it = mAddressToClientMap.find(FromAddress);
if (it == mAddressToClientMap.end())//没找到 视为新加入的客户端
{
HandlePacketFromNewClient(InputStream, FromAddress);
}
else
{
ProcessPacket((*it).second, InputStream);
}
}
void NetworkManagerServer::ProcessPacket(ClientProxyPtr ClientProxy, InputMemoryBitStream& InputStream)
{
//更新指定代理上次处理数据包的时间
ClientProxy->UpdateLastPacketTime();
//读取数据包类型
uint32_t packetType;
InputStream.Read(packetType);
switch (packetType)
{
case kHelloCC://首次连接
SendWelcomePacket(ClientProxy);
break;
case kInputCC://玩家输入
//获取数据包管理器并读取状态数据
if (ClientProxy->GetDeliveryNotificationManager().ReadAndProcessState(InputStream))
{
HandleInputPacket(ClientProxy, InputStream);
}
break;
default:
LOG("Unknown packet type received from %s", ClientProxy->GetSocketAddress().ToString().c_str());
break;
}
}
void NetworkManagerServer::HandlePacketFromNewClient(InputMemoryBitStream& InputStream, const SocketAddress& FromAddress)
{
//读取数据包类型
uint32_t packetType;
InputStream.Read(packetType);
if (packetType == kHelloCC)//首次连接
{
//读取玩家名字
string name;
InputStream.Read(name);
//创建新的客户端代理并加入映射
ClientProxyPtr newClientProxy = std::make_shared< ClientProxy >(FromAddress, name, mNewPlayerId++);
mAddressToClientMap[FromAddress] = newClientProxy;
mPlayerIdToClientMap[newClientProxy->GetPlayerId()] = newClientProxy;
static_cast<Server*> (Engine::Instance.get())->HandleNewClient(newClientProxy);
//发送欢迎数据包
SendWelcomePacket(newClientProxy);
//同步当前游戏中的对象到新客户端
for (const auto& pair : mNetworkIdToGameObjectMap)
{
newClientProxy->GetReplicationManagerServer().ReplicateCreate(pair.first, pair.second->GetAllStateMask());
}
}
else
{
LOG("Bad incoming packet from unknown client at socket %s", FromAddress.ToString().c_str());
}
}
void NetworkManagerServer::SendWelcomePacket(ClientProxyPtr ClientProxy)
{
OutputMemoryBitStream welcomePacket;
//写入数据包类型常量和分配的玩家id
welcomePacket.Write(kWelcomeCC);
welcomePacket.Write(ClientProxy->GetPlayerId());
LOG("Server Welcoming, new client '%s' as player %d", ClientProxy->GetName().c_str(), ClientProxy->GetPlayerId());
SendPacket(welcomePacket, ClientProxy->GetSocketAddress());
}
void NetworkManagerServer::RespawnCats()
{
for (auto it = mAddressToClientMap.begin(), end = mAddressToClientMap.end(); it != end; ++it)
{
ClientProxyPtr clientProxy = it->second;
clientProxy->RespawnCatIfNecessary();
}
}
void NetworkManagerServer::SendOutgoingPackets()
{
float time = Timing::Instance.GetTimef();
//遍历地址代理映射
for (auto it = mAddressToClientMap.begin(), end = mAddressToClientMap.end(); it != end; ++it)
{
//获取客户端代理对象
ClientProxyPtr clientProxy = it->second;
//处理超时数据包
clientProxy->GetDeliveryNotificationManager().ProcessTimedOutPackets();
//检查客户端移动状态藏标记 如果命中则同步到指定客户端
if (clientProxy->IsLastMoveTimestampDirty())
{
SendStatePacketToClient(clientProxy);
}
}
}
void NetworkManagerServer::UpdateAllClients()
{
for (auto it = mAddressToClientMap.begin(), end = mAddressToClientMap.end(); it != end; ++it)
{
it->second->GetDeliveryNotificationManager().ProcessTimedOutPackets();
SendStatePacketToClient(it->second);
}
}
void NetworkManagerServer::SendStatePacketToClient(ClientProxyPtr ClientProxy)
{
OutputMemoryBitStream statePacket;
//写入包类型数据
statePacket.Write(kStateCC);
//可靠UDP传输处理 返回InFlightPacket用于跟踪数据包状态
InFlightPacket* ifp = ClientProxy->GetDeliveryNotificationManager().WriteState(statePacket);
//写入玩家的移动数据
WriteLastMoveTimestampIfDirty(statePacket, ClientProxy);
//写入计分板数据
AddScoreBoardStateToPacket(statePacket);
//创建同步数据传输对象
ReplicationManagerTransmissionData* rmtd = new ReplicationManagerTransmissionData(&ClientProxy->GetReplicationManagerServer());
//写入所有脏状态的对象数据
ClientProxy->GetReplicationManagerServer().Write(statePacket, rmtd);
//将同步数据附加到可靠传输包
ifp->SetTransmissionData('RPLM', TransmissionDataPtr(rmtd));
SendPacket(statePacket, ClientProxy->GetSocketAddress());
}
void NetworkManagerServer::WriteLastMoveTimestampIfDirty(OutputMemoryBitStream& OutputStream, ClientProxyPtr ClientProxy)
{
//检查客户端最后处理移动时间戳是否有更新
bool isTimestampDirty = ClientProxy->IsLastMoveTimestampDirty();
OutputStream.Write(isTimestampDirty);
if (isTimestampDirty)
{
//写入服务器已处理的最后移动指令时间戳
OutputStream.Write(ClientProxy->GetUnprocessedMoveList().GetLastMoveTimestamp());
//写入后重置脏标记
ClientProxy->SetIsLastMoveTimestampDirty(false);
}
}
void NetworkManagerServer::AddWorldStateToPacket(OutputMemoryBitStream& OutputStream)
{
const auto& gameObjects = World::Instance->GetGameObjects();
OutputStream.Write(gameObjects.size());
for (GameObjectPtr gameObject : gameObjects)
{
OutputStream.Write(gameObject->GetNetworkId());//写入网络id
OutputStream.Write(gameObject->GetClassId());//写入类型id
gameObject->Write(OutputStream, 0xffffffff);//写入完整状态(全量同步)
}
}
void NetworkManagerServer::AddScoreBoardStateToPacket(OutputMemoryBitStream& OutputStream)
{
ScoreBoardManager::Instance->Write(OutputStream);
}
int NetworkManagerServer::GetNewNetworkId()
{
int toRet = mNewNetworkId++;
if (mNewNetworkId < toRet)//越界检查
{
LOG("Network ID Wrap Around!!! You've been playing way too long...", 0);
}
return toRet;
}
void NetworkManagerServer::HandleInputPacket(ClientProxyPtr ClientProxy, InputMemoryBitStream& InputStream)
{
//读取指令数量
uint32_t moveCount = 0;
InputStream.Read(moveCount, 2);//2位可表示0-3个指令,适合高频小包
//指令循环处理
Move move;
for (; moveCount > 0; --moveCount)
{
if (move.Read(InputStream))
{
if (ClientProxy->GetUnprocessedMoveList().AddMoveIfNew(move))//添加新指令到待处理队列
{
ClientProxy->SetIsLastMoveTimestampDirty(true);//设置时间戳变化标志,触发后续确认
}
}
}
}
ClientProxyPtr NetworkManagerServer::GetClientProxy(int PlayerId) const
{
auto it = mPlayerIdToClientMap.find(PlayerId);
if (it != mPlayerIdToClientMap.end())
{
return it->second;
}
return nullptr;
}
void NetworkManagerServer::CheckForDisconnects()
{
//保存断开连接的客户端代理
vector<ClientProxyPtr> clientsToDC;
//计算从客户端搜到数据包的时间间隔
float minAllowedLastPacketFromClientTime = Timing::Instance.GetTimef() - mClientDisconnectTimeout;
//遍历网络地址到客户端代理的映射 检查收包的时间间隔 若小于上值 则将改代理加入clientsToDC
for (const auto& pair : mAddressToClientMap)
{
if (pair.second->GetLastPacketFromClientTime() < minAllowedLastPacketFromClientTime)
{
clientsToDC.push_back(pair.second);
}
}
//遍历所有断开的代理 处理断开逻辑
for (ClientProxyPtr client : clientsToDC)
{
HandleClientDisconnected(client);
}
}
void NetworkManagerServer::HandleClientDisconnected(ClientProxyPtr ClientProxy)
{
//移除相关映射
mPlayerIdToClientMap.erase(ClientProxy->GetPlayerId());
mAddressToClientMap.erase(ClientProxy->GetSocketAddress());
static_cast<Server*> (Engine::Instance.get())->HandleLostClient(ClientProxy);
//所有玩家都断开连接停止服务器
if (mAddressToClientMap.empty())
{
Engine::Instance->SetShouldKeepRunning(false);
}
}
void NetworkManagerServer::RegisterGameObject(GameObjectPtr GameObject)
{
//分配并设置gameobject网络id
int newNetworkId = GetNewNetworkId();
GameObject->SetNetworkId(newNetworkId);
//添加id到gameobject的映射
mNetworkIdToGameObjectMap[newNetworkId] = GameObject;
//遍历网络地址到客户端代理的映射 同步创建命令
for (const auto& pair : mAddressToClientMap)
{
pair.second->GetReplicationManagerServer().ReplicateCreate(newNetworkId, GameObject->GetAllStateMask());
}
}
void NetworkManagerServer::UnregisterGameObject(GameObject* inGameObject)
{
int networkId = inGameObject->GetNetworkId();
mNetworkIdToGameObjectMap.erase(networkId);
for (const auto& pair : mAddressToClientMap)
{
pair.second->GetReplicationManagerServer().ReplicateDestroy(networkId);
}
}
void NetworkManagerServer::SetStateDirty(int NetworkId, uint32_t DirtyState)
{
for (const auto& pair : mAddressToClientMap)
{
pair.second->GetReplicationManagerServer().SetStateDirty(NetworkId, DirtyState);
}
}
客户端网络管理器 NetworkManagerClient.h
class NetworkManagerClient : public NetworkManager
{
enum NetworkClientState
{
NCS_Uninitialized,
NCS_SayingHello,
NCS_Welcomed
};
public:
static NetworkManagerClient* Instance;
static void StaticInit(const SocketAddress& inServerAddress, const string& inName);
void SendOutgoingPackets();
virtual void ProcessPacket(InputMemoryBitStream& inInputStream, const SocketAddress& inFromAddress) override;
const WeightedTimedMovingAverage& GetAvgRoundTripTime() const { return mAvgRoundTripTime; }
float GetRoundTripTime() const { return mAvgRoundTripTime.GetValue(); }
int GetPlayerId() const { return mPlayerId; }
float GetLastMoveProcessedByServerTimestamp() const { return mLastMoveProcessedByServerTimestamp; }
private:
NetworkManagerClient();
void Init(const SocketAddress& inServerAddress, const string& inName);
void UpdateSayingHello();
void SendHelloPacket();
void HandleWelcomePacket(InputMemoryBitStream& inInputStream);
void HandleStatePacket(InputMemoryBitStream& inInputStream);
void ReadLastMoveProcessedOnServerTimestamp(InputMemoryBitStream& inInputStream);
void HandleGameObjectState(InputMemoryBitStream& inInputStream);
void HandleScoreBoardState(InputMemoryBitStream& inInputStream);
void UpdateSendingInputPacket();
void SendInputPacket();
void DestroyGameObjectsInMap(const IntToGameObjectMap& inObjectsToDestroy);
DeliveryNotificationManager mDeliveryNotificationManager;
ReplicationManagerClient mReplicationManagerClient;
SocketAddress mServerAddress;
NetworkClientState mState;
float mTimeOfLastHello;
float mTimeOfLastInputPacket;
string mName;
int mPlayerId;
float mLastMoveProcessedByServerTimestamp;
WeightedTimedMovingAverage mAvgRoundTripTime;
float mLastRoundTripTime;
};
NetworkManagerClient.cpp
#include"MalouClientPCH.h"
NetworkManagerClient* NetworkManagerClient::Instance;
namespace
{
const float kTimeBetweenHellos = 1.f;
const float kTimeBetweenInputPackets = 0.033f;
}
NetworkManagerClient::NetworkManagerClient() :
mState(NCS_Uninitialized),
mDeliveryNotificationManager(true, false),
mLastRoundTripTime(0.f)
{
}
void NetworkManagerClient::StaticInit(const SocketAddress& ServerAddress, const string& Name)
{
Instance = new NetworkManagerClient();
return Instance->Init(ServerAddress, Name);
}
void NetworkManagerClient::Init(const SocketAddress& ServerAddress, const string& Name)
{
NetworkManager::Init(7779);
mServerAddress = ServerAddress;
mState = NCS_SayingHello;
mTimeOfLastHello = 0.f;
mName = Name;
mAvgRoundTripTime = WeightedTimedMovingAverage(1.f);
}
void NetworkManagerClient::ProcessPacket(InputMemoryBitStream& InputStream, const SocketAddress& FromAddress)
{
uint32_t packetType;
InputStream.Read(packetType);
switch (packetType)
{
case kWelcomeCC:
HandleWelcomePacket(InputStream);
break;
case kStateCC:
if (mDeliveryNotificationManager.ReadAndProcessState(InputStream))
{
HandleStatePacket(InputStream);
}
break;
}
}
void NetworkManagerClient::SendOutgoingPackets()
{
switch (mState)
{
case NCS_SayingHello:
UpdateSayingHello();
break;
case NCS_Welcomed:
UpdateSendingInputPacket();
break;
}
}
void NetworkManagerClient::UpdateSayingHello()
{
float time = Timing::Instance.GetTimef();
if (time > mTimeOfLastHello + kTimeBetweenHellos)
{
SendHelloPacket();
mTimeOfLastHello = time;
}
}
void NetworkManagerClient::SendHelloPacket()
{
OutputMemoryBitStream helloPacket;
helloPacket.Write(kHelloCC);
helloPacket.Write(mName);
SendPacket(helloPacket, mServerAddress);
}
void NetworkManagerClient::HandleWelcomePacket(InputMemoryBitStream& InputStream)
{
if (mState == NCS_SayingHello)
{
int playerId;
InputStream.Read(playerId);
mPlayerId = playerId;
mState = NCS_Welcomed;
LOG("'%s' was welcomed on client as player %d", mName.c_str(), mPlayerId);
}
}
void NetworkManagerClient::HandleStatePacket(InputMemoryBitStream& InputStream)
{
if (mState == NCS_Welcomed)
{
ReadLastMoveProcessedOnServerTimestamp(InputStream);
HandleScoreBoardState(InputStream);
mReplicationManagerClient.Read(InputStream);
}
}
void NetworkManagerClient::ReadLastMoveProcessedOnServerTimestamp(InputMemoryBitStream& InputStream)
{
bool isTimestampDirty;
InputStream.Read(isTimestampDirty);
if (isTimestampDirty)
{
InputStream.Read(mLastMoveProcessedByServerTimestamp);
float rtt = Timing::Instance.GetFrameStartTime() - mLastMoveProcessedByServerTimestamp;
mLastRoundTripTime = rtt;
mAvgRoundTripTime.Update(rtt);
InputManager::Instance->GetMoveList().RemovedProcessedMoves(mLastMoveProcessedByServerTimestamp);
}
}
void NetworkManagerClient::HandleGameObjectState(InputMemoryBitStream& InputStream)
{
IntToGameObjectMap objectsToDestroy = mNetworkIdToGameObjectMap;
int stateCount;
InputStream.Read(stateCount);
if (stateCount > 0)
{
for (int stateIndex = 0; stateIndex < stateCount; ++stateIndex)
{
int networkId;
uint32_t fourCC;
InputStream.Read(networkId);
InputStream.Read(fourCC);
GameObjectPtr go;
auto itGO = mNetworkIdToGameObjectMap.find(networkId);
if (itGO == mNetworkIdToGameObjectMap.end())
{
go = GameObjectRegistry::Instance->CreateGameObject(fourCC);
go->SetNetworkId(networkId);
AddToNetworkIdToGameObjectMap(go);
}
else
{
go = itGO->second;
}
go->Read(InputStream);
objectsToDestroy.erase(networkId);
}
}
DestroyGameObjectsInMap(objectsToDestroy);
}
void NetworkManagerClient::HandleScoreBoardState(InputMemoryBitStream& InputStream)
{
ScoreBoardManager::Instance->Read(InputStream);
}
void NetworkManagerClient::DestroyGameObjectsInMap(const IntToGameObjectMap& inObjectsToDestroy)
{
for (auto& pair : inObjectsToDestroy)
{
pair.second->SetDoesWantToDie(true);
mNetworkIdToGameObjectMap.erase(pair.first);
}
}
void NetworkManagerClient::UpdateSendingInputPacket()
{
float time = Timing::Instance.GetTimef();
if (time > mTimeOfLastInputPacket + kTimeBetweenInputPackets)
{
SendInputPacket();
mTimeOfLastInputPacket = time;
}
}
void NetworkManagerClient::SendInputPacket()
{
const MoveList& moveList = InputManager::Instance->GetMoveList();
if (moveList.HasMoves())
{
OutputMemoryBitStream inputPacket;
inputPacket.Write(kInputCC);
mDeliveryNotificationManager.WriteState(inputPacket);
int moveCount = moveList.GetMoveCount();
int firstMoveIndex = moveCount - 3;
if (firstMoveIndex < 3)
{
firstMoveIndex = 0;
}
auto move = moveList.begin() + firstMoveIndex;
inputPacket.Write(moveCount - firstMoveIndex, 2);
for (; firstMoveIndex < moveCount; ++firstMoveIndex, ++move)
{
move->Write(inputPacket);
}
SendPacket(inputPacket, mServerAddress);
}
}
五.可靠UDP实现
使用Ack机制实现可靠的UDP传输,它的基本原理如下:发送方会在每个数据包中写入一个唯一的序列号,接收方在接收到数据包后,会用该序列号构建Ack数据并且写入到要发送的数据包中,原发送方再收到新的数据包后会检查其中的Ack信息,来判断接收方是否正常接收到数据包,如果没有则会触发重传或者一些其他的逻辑。
核心组件 DeliveryNotificationManager.h
/*
* 可靠数据包传输管理器
* 核心功能
1.数据包序列编号:为每个发出的数据包分配唯一序列号
2.ACK确认机制:接收方确认已收到的数据包
3.丢包检测与处理:通过超时机制检测丢包
4.传输统计:记录成功/丢失/发送的数据包数量
*/
class DeliveryNotificationManager
{
public:
DeliveryNotificationManager(bool ShouldSendAcks, bool ShouldProcessAcks);
~DeliveryNotificationManager();
/*核心流程方法 发送数据包时调用*/
inline InFlightPacket* WriteState(OutputMemoryBitStream& OutputStream);
/*核心流程方法 接收数据包时调用*/
inline bool ReadAndProcessState(InputMemoryBitStream& InputStream);
/*检查超时未确认的数据包 触发丢包处理逻辑*/
void ProcessTimedOutPackets();
//统计获取方法
uint32_t GetDroppedPacketCount() const { return mDroppedPacketCount; }
uint32_t GetDeliveredPacketCount() const { return mDeliveredPacketCount; }
uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; }
const deque<InFlightPacket>& GetInFlightPackets() const { return mInFlightPackets; }
private:
//序列号处理
InFlightPacket* WriteSequenceNumber(OutputMemoryBitStream& OutputStream);
/*剥离控制序列号 后续逻辑直接处理ack信息*/
bool ProcessSequenceNumber(InputMemoryBitStream& InputStream);
//ACK处理
void WriteAckData(OutputMemoryBitStream& OutputStream);
void ProcessAcks(InputMemoryBitStream& InputStream);
//状态管理
void AddPendingAck(PacketSequenceNumber SequenceNumber);
void HandlePacketDeliveryFailure(const InFlightPacket& FlightPacket);
void HandlePacketDeliverySuccess(const InFlightPacket& FlightPacket);
//下一个发出的数据包序列号
PacketSequenceNumber mNextOutgoingSequenceNumber;
//预期接收的下一个序列号
PacketSequenceNumber mNextExpectedSequenceNumber;
//已发送但未确认的数据包队列 按发送时间排序的队列
deque<InFlightPacket> mInFlightPackets;
//待发送的ACK确认范围队列
deque<AckRange> mPendingAcks;
/*
非对称可靠性需求:
某些应用可能只需要单向可靠性。例如:
服务器→客户端:需要可靠传输(如状态更新)
客户端→服务器:可以容忍丢包(如输入指令)
混合可靠性模式:
允许某些通道启用可靠性,其他通道保持不可靠传输
*/
//是否应发送ACK确认
bool mShouldSendAcks;
//是否处理收到的ACK
bool mShouldProcessAcks;
//成功交付的数据包计数
uint32_t mDeliveredPacketCount;
//丢失的数据包计数
uint32_t mDroppedPacketCount;
//已发送的数据包计数
uint32_t mDispatchedPacketCount;
};
inline InFlightPacket* DeliveryNotificationManager::WriteState(OutputMemoryBitStream& OutputStream)
{
InFlightPacket* toRet = WriteSequenceNumber(OutputStream);//写入序列号到数据流
//如果需要发送ACK,写入ACK数据
if (mShouldSendAcks)
{
WriteAckData(OutputStream);
}
return toRet;//返回InFlightPacket对象用于跟踪
}
inline bool DeliveryNotificationManager::ReadAndProcessState(InputMemoryBitStream& InputStream)
{
bool toRet = ProcessSequenceNumber(InputStream);//处理收到的序列号(检测丢包/乱序)
//如果需要处理ACK,解析ACK数据
if (mShouldProcessAcks)
{
ProcessAcks(InputStream);
}
return toRet;
}
DeliveryNotificationManager.cpp
#include "MalouPCH.h"
namespace
{
const float kDelayBeforeAckTimeOut = 0.5f;
}
DeliveryNotificationManager::DeliveryNotificationManager(bool ShouldSendAcks, bool ShouldProcessAcks):
mNextOutgoingSequenceNumber(0),
mNextExpectedSequenceNumber(0),
mShouldSendAcks(ShouldSendAcks),
mShouldProcessAcks(ShouldProcessAcks),
mDeliveredPacketCount(0),
mDroppedPacketCount(0),
mDispatchedPacketCount(0)
{}
DeliveryNotificationManager::~DeliveryNotificationManager()
{
//LOG("DNM destructor. Delivery rate %d%%, Drop rate %d%%",
// (100 * mDeliveredPacketCount) / mDispatchedPacketCount,
// (100 * mDroppedPacketCount) / mDispatchedPacketCount);
}
void DeliveryNotificationManager::ProcessTimedOutPackets()
{
//计算超时时间点
float timeoutTime = Timing::Instance.GetTimef() - kDelayBeforeAckTimeOut;
while (!mInFlightPackets.empty())
{
const auto& nextInFlightPacket = mInFlightPackets.front();
//该包已发出超过0.5秒仍未收到ACK → 网络可能已丢失该包
if (nextInFlightPacket.GetTimeDispatched() < timeoutTime)
{
HandlePacketDeliveryFailure(nextInFlightPacket);
mInFlightPackets.pop_front();
}
else//否则终止检查(队列按时间排序)
{
break;
}
}
}
InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber(OutputMemoryBitStream& OutputStream)
{
//分配并写入递增的序列号
PacketSequenceNumber sequenceNumber = mNextOutgoingSequenceNumber++;
OutputStream.Write(sequenceNumber);
//增加发送计数器
++mDispatchedPacketCount;
//如果需要处理ACK,创建InFlightPacket跟踪包
if (mShouldProcessAcks)
{
mInFlightPackets.emplace_back(sequenceNumber);
return &mInFlightPackets.back();
}
else
{
return nullptr;
}
}
void DeliveryNotificationManager::WriteAckData(OutputMemoryBitStream& OutputStream)
{
//检查有待确认的ack
bool hasAcks = (mPendingAcks.size() > 0);
//写入是否有ACK的标志
OutputStream.Write(hasAcks);
//写入最早的ACK范围数据
if (hasAcks)
{
mPendingAcks.front().Write(OutputStream);
mPendingAcks.pop_front();
}
}
bool DeliveryNotificationManager::ProcessSequenceNumber(InputMemoryBitStream& InputStream)
{
//创建并读取序列号
PacketSequenceNumber sequenceNumber;
InputStream.Read(sequenceNumber);
if (sequenceNumber == mNextExpectedSequenceNumber)//预期序列号 正常接收
{
mNextExpectedSequenceNumber = sequenceNumber + 1;//更新预期值
if (mShouldSendAcks)
{
AddPendingAck(sequenceNumber);
}
return true;
}
else if (sequenceNumber < mNextExpectedSequenceNumber)//旧序列号 重复包忽略
{
return false;
}
else if (sequenceNumber > mNextExpectedSequenceNumber)//新序列号 检测到丢包,跳过缺失包
{
mNextExpectedSequenceNumber = sequenceNumber + 1;
if (mShouldSendAcks)
{
AddPendingAck(sequenceNumber);
}
return true;
}
return false;
}
void DeliveryNotificationManager::ProcessAcks(InputMemoryBitStream& InputStream)
{
//创建并读取ACK状态
bool hasAcks;
InputStream.Read(hasAcks);
//存在ACK
if (hasAcks)
{
//创建并读取ACK范围数据
AckRange ackRange;
ackRange.Read(InputStream);
PacketSequenceNumber nextAckdSequenceNumber = ackRange.GetStart();//获取范围起始索引
uint32_t onePastAckdSequenceNumber = nextAckdSequenceNumber + ackRange.GetCount();//获取范围大小
while (nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPackets.empty())
{
const auto& nextInFlightPacket = mInFlightPackets.front();
PacketSequenceNumber nextInFlightPacketSequenceNumber = nextInFlightPacket.GetSequenceNumber();
if (nextInFlightPacketSequenceNumber < nextAckdSequenceNumber)//已确认包之前的包 → 视为丢包
{
auto copyOfInFlightPacket = nextInFlightPacket;
mInFlightPackets.pop_front();
HandlePacketDeliveryFailure(copyOfInFlightPacket);
}
else if (nextInFlightPacketSequenceNumber == nextAckdSequenceNumber)//正好是确认的包 → 成功处理
{
HandlePacketDeliverySuccess(nextInFlightPacket);
mInFlightPackets.pop_front();
++nextAckdSequenceNumber;
}
else if (nextInFlightPacketSequenceNumber > nextAckdSequenceNumber)//确认包之后的包 → 跳过
{
++nextAckdSequenceNumber;
}
}
}
}
void DeliveryNotificationManager::AddPendingAck(PacketSequenceNumber SequenceNumber)
{
//尝试扩展最后一个ACK范围,优化ACK数据量(合并连续序列号)
if (mPendingAcks.size() == 0 || !mPendingAcks.back().ExtendIfShould(SequenceNumber))
{
//如果不能扩展则新建ACK范围
//这里使用了c++11的新特性 原位构造函数,其特点是
//直接在容器内存中构造元素
//不需要预先创建临时对象
//参数会完美转发给 T 的构造函数
mPendingAcks.emplace_back(SequenceNumber);
}
}
void DeliveryNotificationManager::HandlePacketDeliveryFailure(const InFlightPacket& FlightPacket)
{
++mDroppedPacketCount;//增加丢包计数
FlightPacket.HandleDeliveryFailure(this);//回调包对象的失败处理
}
void DeliveryNotificationManager::HandlePacketDeliverySuccess(const InFlightPacket& FlightPacket)
{
++mDeliveredPacketCount;//增加成功计数
FlightPacket.HandleDeliverySuccess(this);//回调包对象的成功处理
}
辅助类 AckRange.h
/*
* 高效批量确认网络数据包辅助类
* 用于优化可靠UDP协议中的ACK确认机制
* 核心功能
1.表示连续的ACK范围:记录一个连续的已接收数据包序列
2.范围扩展:动态合并相邻的序列号
3.序列化/反序列化:支持网络传输
*应用场景示例
包5(创建范围[5])
包6(扩展为[5,6])
包8(创建新范围[8])
包7(扩展为[5,6,7],合并后[5,6,7,8])
最终只需发送一个ACK范围:start=5, count=4
*/
class AckRange
{
public:
AckRange():mStart(0),mCount(0){}
AckRange(PacketSequenceNumber Start):mStart(0),mCount(1){}
/*扩展ACK范围 自动合并相邻包,减少需要发送的ACK数量*/
inline bool ExtendIfShould(PacketSequenceNumber SequenceNumber);
PacketSequenceNumber GetStart()const { return mStart; }
uint32_t GetCount()const { return mCount; }
void Write(OutputMemoryBitStream& OutputStream) const;
void Read(InputMemoryBitStream& InputStream);
private:
//连续ACK范围的起始序列号
PacketSequenceNumber mStart;
//连续ACK的数量
uint32_t mCount;
};
inline bool AckRange::ExtendIfShould(PacketSequenceNumber SequenceNumber)
{
if (SequenceNumber == mStart + mCount)
{
++mCount;
return true;
}
return false;
}
AckRange.cpp
#include"MalouPCH.h"
void AckRange::Write(OutputMemoryBitStream& OutputStream) const
{
OutputStream.Write(mStart);
bool hasCount = mCount > 1;
OutputStream.Write(hasCount);
if (hasCount)
{
uint32_t countMinusOne = mCount - 1;
uint8_t countToAck = countMinusOne > 25 ? 25 : static_cast<uint8_t>(countMinusOne);
OutputStream.Write(countToAck);
}
}
void AckRange::Read(InputMemoryBitStream& InputStream)
{
InputStream.Read(mStart);
bool hasCount;
InputStream.Read(hasCount);
if (hasCount)
{
uint8_t countMinusOne;
InputStream.Read(countMinusOne);
mCount = countMinusOne + 1;
}
else
{
mCount = 1;
}
}
辅助类 InFlightPacket.h
class DeliveryNotificationManager;
using PacketSequenceNumber = uint16_t;
/*
可靠UDP实现的核心组件
用于跟踪已发送但未确认的网络数据包的类
核心功能
1.记录在途数据包:跟踪已发送但未收到ACK确认的数据包
2.传输数据管理:存储与数据包关联的传输数据
3.传输结果处理:提供成功/失败的回调接口
*/
class InFlightPacket
{
public:
InFlightPacket(PacketSequenceNumber SequenceNumber);
/*获取包序列号*/
PacketSequenceNumber GetSequenceNumber() const { return mSequenceNumber; }
/*获取包发送时间*/
float GetTimeDispatched() const { return mTimeDispatched; }
/*存储附加数据*/
void SetTransmissionData(int Key, TransmissionDataPtr TransmissionData)
{
mTransmissionDataMap[Key] = TransmissionData;
}
/*获取附加数据*/
const TransmissionDataPtr GetTransmissionData(int Key) const
{
auto it = mTransmissionDataMap.find(Key);
if (it != mTransmissionDataMap.end())
{
return it->second;
}
return nullptr;
}
/*传输失败回调*/
void HandleDeliveryFailure(DeliveryNotificationManager* DeliveryNotificationManager) const;
/*传输成功回调*/
void HandleDeliverySuccess(DeliveryNotificationManager* DeliveryNotificationManager) const;
private:
//数据包唯一序列号
PacketSequenceNumber mSequenceNumber;
//数据包发送时间戳
float mTimeDispatched;
//存储与包关联的传输数据
unordered_map<int,TransmissionDataPtr> mTransmissionDataMap;
};
InFlightPacket.cpp
#include"MalouPCH.h"
InFlightPacket::InFlightPacket(PacketSequenceNumber SequenceNumber) :
mSequenceNumber(SequenceNumber),
mTimeDispatched(Timing::Instance.GetTimef())
{
}
void InFlightPacket::HandleDeliveryFailure(DeliveryNotificationManager* DeliveryNotificationManager) const
{
for (const auto& pair : mTransmissionDataMap)
{
pair.second->HandleDeliveryFailure(DeliveryNotificationManager);
}
}
void InFlightPacket::HandleDeliverySuccess(DeliveryNotificationManager* DeliveryNotificationManager) const
{
for (const auto& pair : mTransmissionDataMap)
{
pair.second->HandleDeliverySuccess(DeliveryNotificationManager);
}
}
六.同步管理器
同步命令封装 ReplicationCommand.h
#pragma once
/*
* 网络同步操作类型枚举
*/
enum ReplicationAction
{
RA_Create,
RA_Update,
RA_Destroy,
RA_RPC,
RA_MAX
};
class ReplicationManagerTransmissionData;
struct ReplicationCommand
{
public:
ReplicationCommand() {}
ReplicationCommand(uint32_t InitialDirtyState) :
mAction(RA_Create),
mDirtyState(InitialDirtyState) {}
void HandleCreateAckd() { if (mAction == RA_Create) { mAction = RA_Update; } }
void AddDirtyState(uint32_t State) { mDirtyState |= State; }
void SetDestroy() { mAction = RA_Destroy; }
bool HasDirtyState() const { return (mAction == RA_Destroy) || (mDirtyState != 0); }
ReplicationAction GetAction() const { return mAction; }
uint32_t GetDirtyState() const { return mDirtyState; }
inline void ClearDirtyState(uint32_t StateToClear);
void Write(OutputMemoryBitStream& OutputStream, int NetworkId, ReplicationManagerTransmissionData* TransactionData);
void Read(InputMemoryBitStream& InputStream, int NetworkId);
private:
uint32_t mDirtyState;
ReplicationAction mAction;
};
inline void ReplicationCommand::ClearDirtyState(uint32_t StateToClear)
{
mDirtyState &= ~StateToClear;
if (mAction == RA_Destroy)
{
mAction = RA_Update;
}
}
服务器同步管理器 ReplicationManagerServer.h
/*
* 游戏服务器端对象同步管理器
* 管理游戏对象的网络复制(创建/更新/销毁)和状态同步
*/
class ReplicationManagerServer
{
public:
/*同步创建操作*/
void ReplicateCreate(int NetworkId, uint32_t InitialDirtyState);
/*同步销毁操作*/
void ReplicateDestroy(int NetworkId);
/*设置状态脏标记*/
void SetStateDirty(int NetworkId, uint32_t DirtyState);
/*处理创建ACK包*/
void HandleCreateAckd(int NetworkId);
/*停止同步指定对象*/
void RemoveFromReplication(int NetworkId);
/*将待同步操作写入网络包*/
void Write(OutputMemoryBitStream& OutputStream, ReplicationManagerTransmissionData* TransmissinData);
private:
/*序列化创建操作*/
uint32_t WriteCreateAction(OutputMemoryBitStream& OutputStream, int NetworkId, uint32_t DirtyState);
/*序列化状态更新操作*/
uint32_t WriteUpdateAction(OutputMemoryBitStream& OutputStream, int NetworkId, uint32_t DirtyState);
/*序列化销毁操作*/
uint32_t WriteDestroyAction(OutputMemoryBitStream& OutputStream, int NetworkId, uint32_t DirtyState);
unordered_map<int, ReplicationCommand> mNetworkIdToReplicationCommand;
};
ReplicationManagerServer.cpp
#include"MalouServerPCH.h"
void ReplicationManagerServer::ReplicateCreate(int NetworkId, uint32_t InitialDirtyState)
{
mNetworkIdToReplicationCommand[NetworkId] = ReplicationCommand(InitialDirtyState);
}
void ReplicationManagerServer::ReplicateDestroy(int NetworkId)
{
mNetworkIdToReplicationCommand[NetworkId].SetDestroy();
}
void ReplicationManagerServer::RemoveFromReplication(int NetworkId)
{
mNetworkIdToReplicationCommand.erase(NetworkId);
}
void ReplicationManagerServer::SetStateDirty(int NetworkId, uint32_t DirtyState)
{
mNetworkIdToReplicationCommand[NetworkId].AddDirtyState(DirtyState);
}
void ReplicationManagerServer::HandleCreateAckd(int NetworkId)
{
mNetworkIdToReplicationCommand[NetworkId].HandleCreateAckd();
}
void ReplicationManagerServer::Write(OutputMemoryBitStream& OutputStream, ReplicationManagerTransmissionData* TransmissinData)
{
for (auto& pair : mNetworkIdToReplicationCommand)
{
ReplicationCommand& replicationCommand = pair.second;
if (replicationCommand.HasDirtyState())
{
int networkId = pair.first;
OutputStream.Write(networkId);
ReplicationAction action = replicationCommand.GetAction();
OutputStream.Write(action, 2);
uint32_t writtenState = 0;
uint32_t dirtyState = replicationCommand.GetDirtyState();
switch (action)
{
case RA_Create:
writtenState = WriteCreateAction(OutputStream, networkId, dirtyState);
break;
case RA_Update:
writtenState = WriteUpdateAction(OutputStream, networkId, dirtyState);
break;
case RA_Destroy:
writtenState = WriteDestroyAction(OutputStream, networkId, dirtyState);
break;
}
//记录已发送的同步操作
TransmissinData->AddTransmission(networkId, action, writtenState);
replicationCommand.ClearDirtyState(writtenState);
}
}
}
uint32_t ReplicationManagerServer::WriteCreateAction(OutputMemoryBitStream& OutputStream, int NetworkId, uint32_t DirtyState)
{
GameObjectPtr gameObject = NetworkManagerServer::Instance->GetGameObject(NetworkId);
OutputStream.Write(gameObject->GetClassId());
return gameObject->Write(OutputStream, DirtyState);
}
uint32_t ReplicationManagerServer::WriteUpdateAction(OutputMemoryBitStream& OutputStream, int NetworkId, uint32_t DirtyState)
{
GameObjectPtr gameObject = NetworkManagerServer::Instance->GetGameObject(NetworkId);
uint32_t writtenState = gameObject->Write(OutputStream, DirtyState);
return writtenState;
}
uint32_t ReplicationManagerServer::WriteDestroyAction(OutputMemoryBitStream& OutputStream, int NetworkId, uint32_t DirtyState)
{
(void)OutputStream;
(void)NetworkId;
(void)DirtyState;
return DirtyState;
}
客户端同步管理器 ReplicationManagerClient.h
class ReplicationManagerClient
{
public:
void Read(InputMemoryBitStream& InputStream);
private:
void ReadAndDoCreateAction(InputMemoryBitStream& InputStream, int NetworkId);
void ReadAndDoUpdateAction(InputMemoryBitStream& InputStream, int NetworkId);
void ReadAndDoDestroyAction(InputMemoryBitStream& InputStream, int NetworkId);
};
ReplicationManagerClient.cpp
#include"MalouClientPCH.h"
#include <cassert>
void ReplicationManagerClient::Read(InputMemoryBitStream& InputStream)
{
while (InputStream.GetRemainingBitCount() >= 32)
{
int networkId; InputStream.Read(networkId);
uint8_t action; InputStream.Read(action, 2);
switch (action)
{
case RA_Create:
ReadAndDoCreateAction(InputStream, networkId);
break;
case RA_Update:
ReadAndDoUpdateAction(InputStream, networkId);
break;
case RA_Destroy:
ReadAndDoDestroyAction(InputStream, networkId);
break;
}
}
}
void ReplicationManagerClient::ReadAndDoCreateAction(InputMemoryBitStream& InputStream, int inNetworkId)
{
uint32_t fourCCName;
InputStream.Read(fourCCName);
GameObjectPtr gameObject = NetworkManagerClient::Instance->GetGameObject(inNetworkId);
if (!gameObject)
{
gameObject = GameObjectRegistry::Instance->CreateGameObject(fourCCName);
gameObject->SetNetworkId(inNetworkId);
NetworkManagerClient::Instance->AddToNetworkIdToGameObjectMap(gameObject);
assert(gameObject->GetClassId() == fourCCName);
}
gameObject->Read(InputStream);
}
void ReplicationManagerClient::ReadAndDoUpdateAction(InputMemoryBitStream& InputStream, int inNetworkId)
{
GameObjectPtr gameObject = NetworkManagerClient::Instance->GetGameObject(inNetworkId);
gameObject->Read(InputStream);
}
void ReplicationManagerClient::ReadAndDoDestroyAction(InputMemoryBitStream& InputStream, int inNetworkId)
{
GameObjectPtr gameObject = NetworkManagerClient::Instance->GetGameObject(inNetworkId);
if (gameObject)
{
gameObject->SetDoesWantToDie(true);
NetworkManagerClient::Instance->RemoveFromNetworkIdToGameObjectMap(gameObject);
}
}
七.游戏对象网络同步的传输数据处理
可靠数据传输回调机制的抽象基类 TransmissionData.h
class DeliveryNotificationManager;
/*
* 可靠数据传输回调机制的抽象基类
* 用于在网络包传输成功或失败时执行特定操作
* 核心设计目的
1.提供传输结果回调接口:当数据包被确认送达或丢失时触发相应处理
2.支持多态行为:允许派生类实现不同的传输后处理逻辑
3.生命周期管理:通过智能指针自动管理内存
*/
class TransmissionData
{
public:
virtual void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const = 0;
virtual void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const = 0;
};
using TransmissionDataPtr = shared_ptr<TransmissionData>;
数据处理类 ReplicationManagerTransmissionData.h
class ReplicationManagerServer;
/*
* 游戏对象网络同步的传输数据处理类
* 处理游戏世界中对象的创建、状态更新和销毁的可靠传输
*/
class ReplicationManagerTransmissionData : public TransmissionData
{
public:
ReplicationManagerTransmissionData(ReplicationManagerServer* ReplicationManagerServer) :
mReplicationManagerServer(ReplicationManagerServer)
{}
/*单个同步操作类*/
class ReplicationTransmission
{
public:
ReplicationTransmission(int NetworkId, ReplicationAction Action, uint32_t State) :
mNetworkId(NetworkId),
mAction(Action),
mState(State)
{}
int GetNetworkId() const { return mNetworkId; }
ReplicationAction GetAction() const { return mAction; }
uint32_t GetState() const { return mState; }
private:
//对象网络id
int mNetworkId;
//操作类型(创建/更新/销毁)
ReplicationAction mAction;
//对象状态
uint32_t mState;
};
/*添加同步操作*/
void AddTransmission(int NetworkId, ReplicationAction Action, uint32_t State);
/*传输结果处理*/
virtual void HandleDeliveryFailure(DeliveryNotificationManager* DeliveryNotificationManager) const override;
virtual void HandleDeliverySuccess(DeliveryNotificationManager* DeliveryNotificationManager) const override;
private:
/*对象创建失败处理*/
void HandleCreateDeliveryFailure(int NetworkId) const;
/*状态更新失败处理*/
void HandleUpdateStateDeliveryFailure(int NetworkId, uint32_t State, DeliveryNotificationManager* DeliveryNotificationManager) const;
/*对象销毁失败处理*/
void HandleDestroyDeliveryFailure(int NetworkId) const;
/*对象创建成功处理*/
void HandleCreateDeliverySuccess(int NetworkId) const;
/*对象销毁成功处理*/
void HandleDestroyDeliverySuccess(int NetworkId) const;
//服务器同步管理器指针
ReplicationManagerServer* mReplicationManagerServer;
//保存同步操作
vector<ReplicationTransmission> mTransmissions;
};
using ReplicationManagerTransmissionDataPtr = shared_ptr<ReplicationManagerTransmissionData>;
ReplicationManagerTransmissionData.cpp
#include"MalouServerPCH.h"
void ReplicationManagerTransmissionData::AddTransmission(int NetworkId, ReplicationAction Action, uint32_t State)
{
mTransmissions.emplace_back(NetworkId, Action, State);
}
void ReplicationManagerTransmissionData::HandleDeliveryFailure(DeliveryNotificationManager* DeliveryNotificationManager) const
{
for (const ReplicationTransmission& rt : mTransmissions)
{
int networkId = rt.GetNetworkId();
switch (rt.GetAction())
{
case RA_Create:
HandleCreateDeliveryFailure(networkId);
break;
case RA_Update:
HandleUpdateStateDeliveryFailure(networkId, rt.GetState(), DeliveryNotificationManager);
break;
case RA_Destroy:
HandleDestroyDeliveryFailure(networkId);
break;
}
}
}
void ReplicationManagerTransmissionData::HandleDeliverySuccess(DeliveryNotificationManager* DeliveryNotificationManager) const
{
for (const ReplicationTransmission& rt : mTransmissions)
{
switch (rt.GetAction())
{
case RA_Create:
HandleCreateDeliverySuccess(rt.GetNetworkId());
break;
case RA_Destroy:
HandleDestroyDeliverySuccess(rt.GetNetworkId());
break;
}
}
}
void ReplicationManagerTransmissionData::HandleCreateDeliveryFailure(int NetworkId) const
{
GameObjectPtr gameObject = NetworkManagerServer::Instance->GetGameObject(NetworkId);
if (gameObject)
{
mReplicationManagerServer->ReplicateCreate(NetworkId, gameObject->GetAllStateMask());
}
}
void ReplicationManagerTransmissionData::HandleDestroyDeliveryFailure(int NetworkId) const
{
mReplicationManagerServer->ReplicateDestroy(NetworkId);
}
void ReplicationManagerTransmissionData::HandleUpdateStateDeliveryFailure(int NetworkId, uint32_t State, DeliveryNotificationManager* DeliveryNotificationManager) const
{
if (NetworkManagerServer::Instance->GetGameObject(NetworkId))//检查对象的有效性
{
//遍历所有已发送但未确认的数据包,
for (const auto& inFlightPacket : DeliveryNotificationManager->GetInFlightPackets())
{
ReplicationManagerTransmissionDataPtr rmtdp = std::static_pointer_cast<ReplicationManagerTransmissionData>(inFlightPacket.GetTransmissionData('RPLM'));
//状态冲突检测
for (const ReplicationTransmission& otherRT : rmtdp->mTransmissions)
{
/*
* 使用位掩码清除已经被其他在途包包含的状态位
* 例如:
失败的状态:0b00001111 (表示需要同步4个属性)
在途包的状态:0b00000011
结果状态:0b00001100 (只保留未被覆盖的属性)
*/
State &= ~otherRT.GetState();
}
}
//只有当剩余状态不为0时 重新标记需要同步的状态
if (State)
{
mReplicationManagerServer->SetStateDirty(NetworkId, State);
}
}
}
void ReplicationManagerTransmissionData::HandleCreateDeliverySuccess(int NetworkId) const
{
mReplicationManagerServer->HandleCreateAckd(NetworkId);
}
void ReplicationManagerTransmissionData::HandleDestroyDeliverySuccess(int NetworkId) const
{
mReplicationManagerServer->RemoveFromReplication(NetworkId);
}
八.服务器客户端代理
单个客户端连接的代理类,管理客户端相关的所有网络状态和数据
ClientProxy.h
/*
* 单个客户端连接的代理类
* 管理客户端相关的所有网络状态和数据
* 核心职责
1.客户端连接管理:维护客户端网络地址和连接状态
2.输入处理:缓存和处理客户端输入指令
3.可靠传输:管理数据包的可靠传输和确认
4.对象同步:控制客户端可见的游戏对象同步
5.玩家状态:维护玩家专属的游戏状态
*
*/
class ClientProxy
{
public:
ClientProxy(const SocketAddress& SocketAddress, const string& Name, int PlayerId);
/*获取客户端网络地址*/
const SocketAddress& GetSocketAddress() const { return mSocketAddress; }
/*获取玩家ID*/
int GetPlayerId() const { return mPlayerId; }
/*获取玩家昵称*/
const string& GetName() const { return mName; }
/*设置输入状态*/
void SetInputState(const InputState& InputState) { mInputState = InputState; }
/*获取输入状态*/
const InputState& GetInputState() const { return mInputState; }
/*更新最后通信时间*/
void UpdateLastPacketTime();
/*获取最后通信时间*/
float GetLastPacketFromClientTime() const { return mLastPacketFromClientTime; }
/*获取可靠传输管理器*/
DeliveryNotificationManager& GetDeliveryNotificationManager() { return mDeliveryNotificationManager; }
/*获取对象同步管理器*/
ReplicationManagerServer& GetReplicationManagerServer() { return mReplicationManagerServer; }
/*获取未处理的移动指令列表*/
const MoveList& GetUnprocessedMoveList() const { return mUnprocessedMoveList; }
/*获取未处理的移动指令列表*/
MoveList& GetUnprocessedMoveList() { return mUnprocessedMoveList; }
/*设置时间戳脏标记*/
void SetIsLastMoveTimestampDirty(bool IsDirty) { mIsLastMoveTimestampDirty = IsDirty; }
/*获取时间戳脏标记*/
bool IsLastMoveTimestampDirty() const { return mIsLastMoveTimestampDirty; }
void HandleCatDied();
void RespawnCatIfNecessary();
private:
//可靠传输管理器
DeliveryNotificationManager mDeliveryNotificationManager;
//对象同步管理器
ReplicationManagerServer mReplicationManagerServer;
//客户端网络地址
SocketAddress mSocketAddress;
//玩家昵称
string mName;
//玩家唯一ID
int mPlayerId;
//当前输入状态
InputState mInputState;
//最后收到数据包的时间
float mLastPacketFromClientTime;
//复活倒计时
float mTimeToRespawn;
//待处理的移动指令队列
MoveList mUnprocessedMoveList;
//时间戳脏标记
bool mIsLastMoveTimestampDirty;
};
using ClientProxyPtr = shared_ptr<ClientProxy>;
ClientProxy.cpp
#include"MalouServerPCH.h"
namespace
{
const float kRespawnDelay = 3.f;
}
ClientProxy::ClientProxy(const SocketAddress& SocketAddress, const string& Name, int PlayerId) :
mSocketAddress(SocketAddress),
mName(Name),
mPlayerId(PlayerId),
mDeliveryNotificationManager(false, true),
mIsLastMoveTimestampDirty(false),
mTimeToRespawn(0.f)
{
UpdateLastPacketTime();
}
void ClientProxy::UpdateLastPacketTime()
{
mLastPacketFromClientTime = Timing::Instance.GetTimef();
}
void ClientProxy::HandleCatDied()
{
mTimeToRespawn = Timing::Instance.GetFrameStartTime() + kRespawnDelay;
}
void ClientProxy::RespawnCatIfNecessary()
{
if (mTimeToRespawn != 0.f && Timing::Instance.GetFrameStartTime() > mTimeToRespawn)
{
static_cast<Server*> (Engine::Instance.get())->SpawnCatForPlayer(mPlayerId);
mTimeToRespawn = 0.f;
}
}