第2章 C++游戏服务器:从虚拟世界的第一步说起
第2章 从虚拟世界的第一步说起
2.1 步履之间:服务器端的微观世界
2.1.1 角色移动的六阶段分解
在大型多人在线游戏中,角色行走这个看似简单的动作,在服务端视角下蕴含着复杂的技术逻辑。当我们按下方向键时,客户端并非简单地在本地更新角色坐标,而是触发了一系列服务端验证与广播流程。
角色移动可分解为六个关键阶段:输入捕获、客户端预测、网络传输、服务端验证、状态更新和邻近广播。每个阶段都涉及不同的技术考量。
首先客户端捕获用户输入后,会立即进行本地预测渲染,提供流畅的视觉反馈。同时,移动请求被打包成网络消息发送至服务端。服务端收到请求后,需要验证移动的合法性:角色当前位置是否允许此次移动?移动速度是否在合理范围内?是否存在碰撞障碍?验证通过后,服务端更新角色的世界状态,并将移动结果广播给邻近的玩家客户端。
// 移动验证的核心数据结构
struct Vector3
{
float x;
float y;
float z;
Vector3(float xVal = 0, float yVal = 0, float zVal = 0)
: x(xVal), y(yVal), z(zVal) {}
float Distance(const Vector3& other) const
{
float dx = x - other.x;
float dy = y - other.y;
float dz = z - other.z;
return sqrt(dx * dx + dy * dy + dz * dz);
}
};
// 角色移动请求
struct MoveRequest
{
uint64_t playerId; // 玩家唯一标识
Vector3 currentPosition; // 当前位置
Vector3 targetPosition; // 目标位置
uint32_t moveSpeed; // 移动速度(单位:厘米/秒)
uint64_t timestamp; // 客户端时间戳
uint32_t sequence; // 请求序列号(用于防重放)
};
// 移动验证结果
enum class MoveValidationResult
{
Success, // 验证成功
InvalidPosition, // 非法位置
SpeedExceeded, // 速度超标
CollisionDetected, // 碰撞检测
ReplayAttack, // 重放攻击
InsufficientStamina // 体力不足
};
2.1.2 服务端视角的完整处理流程
从服务端角度看,角色移动处理流程是一个多层次的管道。这个管道必须高效处理成千上万的并发请求,同时保证游戏状态的正确性和一致性。
处理流程始于网络层,服务端监听特定端口,接收客户端连接。每个连接对应一个玩家会话,负责消息的接收和发送。当移动请求到达时,网络层进行初步的协议解析和反序列化,然后传递给逻辑层。
逻辑层是核心业务处理单元。这里进行移动验证、状态更新和事件触发。验证过程需要查询空间索引(如四叉树或网格),检查地形数据,计算路径可达性。对于大型世界地图,需要分块加载地形数据,避免全图查询带来的性能开销。
更新角色状态后,需要确定哪些其他玩家能够看到这次移动。这涉及玩家视野管理和广播优化。传统做法是定期(如100毫秒)向视野内玩家发送状态快照,但现代游戏更倾向于使用事件驱动的方式,只广播发生变化的属性。
最后是持久化层,负责将重要的状态变更写入数据库。为了避免频繁的数据库操作,通常采用脏标记和批量提交的策略。只有关键数据(如位置、任务进度)会实时保存,其他数据可延迟持久化。
// 游戏世界单例管理器
class WorldManager
{
private:
static WorldManager* instance;
std::unordered_map<uint64_t, Player*> onlinePlayers;
std::shared_mutex playersMutex; // 读写锁保护玩家数据
// 空间分区网格(用于快速查找邻近玩家)
struct GridCell
{
std::vector<uint64_t> playerIds;
std::shared_mutex cellMutex;
};
const int WORLD_WIDTH = 10000; // 世界宽度(单位:厘米)
const int WORLD_HEIGHT = 10000; // 世界高度
const int CELL_SIZE = 1000; // 网格大小
std::vector<std::vector<GridCell>> spatialGrid;
WorldManager()
{
// 初始化空间网格
int gridCols = (WORLD_WIDTH + CELL_SIZE - 1) / CELL_SIZE;
int gridRows = (WORLD_HEIGHT + CELL_SIZE - 1) / CELL_SIZE;
spatialGrid.resize(gridRows, std::vector<GridCell>(gridCols));
}
public:
static WorldManager* GetInstance()
{
static std::once_flag initFlag;
std::call_once(initFlag, []()
{
instance = new WorldManager();
});
return instance;
}
// 处理移动请求
MoveValidationResult ProcessMoveRequest(const MoveRequest& request)
{
// 1. 查找玩家
Player* player = nullptr;
{
std::shared_lock<std::shared_mutex> lock(playersMutex);
auto it = onlinePlayers.find(request.playerId);
if (it == onlinePlayers.end())
{
return MoveValidationResult::InvalidPosition;
}
player = it->second;
}
// 2. 验证时间戳(防重放)
if (request.timestamp <= player->GetLastMoveTime())
{
return MoveValidationResult::ReplayAttack;
}
// 3. 计算移动距离和时间
float distance = request.currentPosition.Distance(request.targetPosition);
float expectedTime = distance * 1000.0f / request.moveSpeed; // 转换为毫秒
// 4. 验证移动速度
const uint32_t MAX_MOVE_SPEED = 1500; // 最大移动速度 15米/秒
if (request.moveSpeed > MAX_MOVE_SPEED)
{
// 记录异常行为,可能的外挂检测
LogManager::LogCheatDetection(request.playerId,
"Speed hack detected: " + std::to_string(request.moveSpeed));
return MoveValidationResult::SpeedExceeded;
}
// 5. 碰撞检测
if (!CheckCollision(request.currentPosition, request.targetPosition))
{
return MoveValidationResult::CollisionDetected;
}
// 6. 更新玩家位置
{
std::unique_lock<std::shared_mutex> lock(player->GetMutex());
player->SetPosition(request.targetPosition);
player->SetLastMoveTime(request.timestamp);
player->SetMoveSequence(request.sequence);
}
// 7. 更新空间索引
UpdateSpatialIndex(request.playerId,
request.currentPosition,
request.targetPosition);
// 8. 广播给邻近玩家
BroadcastMovement(request.playerId, request.targetPosition);
return MoveValidationResult::Success;
}
private:
bool CheckCollision(const Vector3& from, const Vector3& to)
{
// 简化的射线检测
// 实际项目会使用更复杂的碰撞检测系统
const float STEP_SIZE = 100.0f; // 检测步长
Vector3 direction = Vector3(to.x - from.x, to.y - from.y, to.z - from.z);
float length = sqrt(direction.x * direction.x +
direction.y * direction.y +
direction.z * direction.z);
if (length < 0.001f) return true;
direction.x /= length;
direction.y /= length;
direction.z /= length;
// 分段检测碰撞
int steps = static_cast<int>(length / STEP_SIZE);
for (int i = 0; i <= steps; ++i)
{
float t = static_cast<float>(i) / steps;
Vector3 point(
from.x + direction.x * length * t,
from.y + direction.y * length * t,
from.z + direction.z * length * t
);
// 检查点是否在障碍物内
if (IsPointInObstacle(point))
{
return false;
}
}
return true;
}
void UpdateSpatialIndex(uint64_t playerId,
const Vector3& oldPos,
const Vector3& newPos)
{
// 计算新旧位置所在的网格
int oldCellX = static_cast<int>(oldPos.x) / CELL_SIZE;
int oldCellY = static_cast<int>(oldPos.y) / CELL_SIZE;
int newCellX = static_cast<int>(newPos.x) / CELL_SIZE;
int newCellY = static_cast<int>(newPos.y) / CELL_SIZE;
// 如果网格发生变化,更新索引
if (oldCellX != newCellX || oldCellY != newCellY)
{
// 从旧网格移除
if (oldCellX >= 0 && oldCellX < spatialGrid[0].size() &&
oldCellY >= 0 && oldCellY < spatialGrid.size())
{
std::unique_lock<std::shared_mutex> lock(
spatialGrid[oldCellY][oldCellX].cellMutex);
auto& cell = spatialGrid[oldCellY][oldCellX];
auto it = std::find(cell.playerIds.begin(),
cell.playerIds.end(),
playerId);
if (it != cell.playerIds.end())
{
cell.playerIds.erase(it);
}
}
// 添加到新网格
if (newCellX >= 0 && newCellX < spatialGrid[0].size() &&
newCellY >= 0 && newCellY < spatialGrid.size())
{
std::unique_lock<std::shared_mutex> lock(
spatialGrid[newCellY][newCellX].cellMutex);
spatialGrid[newCellY][newCellX].playerIds.push_back(playerId);
}
}
}
void BroadcastMovement(uint64_t moverId, const Vector3& newPosition)
{
// 查找需要广播的玩家
std::vector<uint64_t> nearbyPlayers = GetNearbyPlayers(newPosition, 5000);
// 构建广播消息
MovementBroadcastMsg msg;
msg.moverId = moverId;
msg.newPosition = newPosition;
msg.timestamp = GetCurrentTimeMs();
// 发送给每个邻近玩家(实际项目中会优化为组播)
for (uint64_t playerId : nearbyPlayers)
{
if (playerId != moverId) // 不广播给自己
{
SendToPlayer(playerId, msg);
}
}
}
bool IsPointInObstacle(const Vector3& point)
{
// 实际项目中会查询障碍物数据
// 这里返回简化的实现
return false;
}
std::vector<uint64_t> GetNearbyPlayers(const Vector3& position, float radius)
{
std::vector<uint64_t> result;
// 计算查询范围涉及的网格
int minCellX = static_cast<int>((position.x - radius) / CELL_SIZE);
int maxCellX = static_cast<int>((position.x + radius) / CELL_SIZE);
int minCellY = static_cast<int>((position.y - radius) / CELL_SIZE);
int maxCellY = static_cast<int>((position.y + radius) / CELL_SIZE);
// 约束到网格边界
minCellX = std::max(0, minCellX);
maxCellX = std::min(static_cast<int>(spatialGrid[0].size()) - 1, maxCellX);
minCellY = std::max(0, minCellY);
maxCellY = std::min(static_cast<int>(spatialGrid.size()) - 1, maxCellY);
// 收集所有网格中的玩家
for (int y = minCellY; y <= maxCellY; ++y)
{
for (int x = minCellX; x <= maxCellX; ++x)
{
std::shared_lock<std::shared_mutex> lock(spatialGrid[y][x].cellMutex);
for (uint64_t playerId : spatialGrid[y][x].playerIds)
{
// 二次检查距离(网格边界可能包含稍远的玩家)
Player* player = GetPlayer(playerId);
if (player)
{
float distance = position.Distance(player->GetPosition());
if (distance <= radius)
{
result.push_back(playerId);
}
}
}
}
}
return result;
}
};
2.3 基础网络通信的实现原理
2.3.1 通信模型的核心类比
理解网络编程的最佳方式是通过电话系统类比。想象客户端和服务器的通信就像两个人打电话:首先需要建立连接(拨号),然后通过确定的协议交流(语言),最后结束通话(挂断)。
在网络编程中,有三个基本概念必须掌握:套接字(Socket)、IP地址和端口。套接字是通信的端点,IP地址定位网络中的主机,端口号区分同一主机上的不同服务。这好比电话系统中的电话机、电话号码和分机号。
对于游戏服务器,我们通常使用TCP协议保证数据可靠传输,或UDP协议追求更低延迟。现代游戏常采用混合策略:关键数据(如登录、交易)用TCP,实时数据(如位置、技能)用UDP。
2.3.2 必须掌握的网络编程概念
连接管理是游戏服务器的基石。每个客户端连接都需要独立的状态维护,包括认证状态、协议版本、加密密钥等。连接池技术可以重用连接资源,避免频繁创建销毁的开销。
消息协议设计直接影响服务器性能。常见的协议格式有:定长协议、变长协议(长度前缀)和分隔符协议。游戏服务器多采用二进制协议以减少序列化开销,配合Protocol Buffers或FlatBuffers等高效序列化库。
流量控制防止服务器被洪水攻击。令牌桶算法是常用方案,限制客户端每秒最大请求数。同时需要实现心跳机制检测死连接,及时释放资源。
// 游戏服务器网络层核心类
class GameServerNetwork
{
private:
SOCKET listenSocket; // 监听套接字
std::atomic<bool> isRunning; // 服务器运行状态
std::thread acceptorThread; // 接受连接线程
std::vector<std::thread> workerThreads; // 工作线程池
// 连接管理
class ClientConnection
{
public:
SOCKET socket; // 客户端套接字
uint64_t connectionId; // 连接唯一ID
std::atomic<bool> authenticated; // 是否已认证
uint32_t playerId; // 绑定的玩家ID
std::chrono::steady_clock::time_point lastActivity; // 最后活动时间
// 接收缓冲区
static const int BUFFER_SIZE = 8192;
char recvBuffer[BUFFER_SIZE];
int recvLen; // 已接收数据长度
// 发送队列
std::queue<std::vector<char>> sendQueue;
std::mutex sendMutex;
ClientConnection(SOCKET sock, uint64_t id)
: socket(sock), connectionId(id), authenticated(false),
playerId(0), recvLen(0)
{
lastActivity = std::chrono::steady_clock::now();
}
bool IsTimeout() const
{
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(
now - lastActivity);
return duration.count() > 30; // 30秒超时
}
};
std::unordered_map<uint64_t, std::shared_ptr<ClientConnection>> connections;
std::shared_mutex connectionsMutex;
std::atomic<uint64_t> nextConnectionId;
public:
GameServerNetwork()
: isRunning(false), nextConnectionId(1)
{
// 初始化Winsock(Windows平台)
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
throw std::runtime_error("WSAStartup failed");
}
#endif
}
~GameServerNetwork()
{
Stop();
#ifdef _WIN32
WSACleanup();
#endif
}
bool Start(int port, int workerCount = 4)
{
if (isRunning) return false;
// 创建监听套接字
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET)
{
LogManager::LogError("Create socket failed");
return false;
}
// 设置地址重用
int reuseAddr = 1;
setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<char*>(&reuseAddr), sizeof(reuseAddr));
// 绑定地址
sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(port);
if (bind(listenSocket, reinterpret_cast<sockaddr*>(&serverAddr),
sizeof(serverAddr)) == SOCKET_ERROR)
{
LogManager::LogError("Bind failed on port " + std::to_string(port));
closesocket(listenSocket);
return false;
}
// 开始监听
if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
{
LogManager::LogError("Listen failed");
closesocket(listenSocket);
return false;
}
// 设置为非阻塞模式
#ifdef _WIN32
u_long nonBlocking = 1;
ioctlsocket(listenSocket, FIONBIO, &nonBlocking);
#else
int flags = fcntl(listenSocket, F_GETFL, 0);
fcntl(listenSocket, F_SETFL, flags | O_NONBLOCK);
#endif
isRunning = true;
// 启动接受连接线程
acceptorThread = std::thread(&GameServerNetwork::AcceptorThreadFunc, this);
// 启动工作线程
for (int i = 0; i < workerCount; ++i)
{
workerThreads.emplace_back(&GameServerNetwork::WorkerThreadFunc, this);
}
LogManager::LogInfo("Game server started on port " + std::to_string(port));
return true;
}
void Stop()
{
if (!isRunning) return;
isRunning = false;
// 关闭监听套接字
if (listenSocket != INVALID_SOCKET)
{
closesocket(listenSocket);
listenSocket = INVALID_SOCKET;
}
// 关闭所有客户端连接
{
std::unique_lock<std::shared_mutex> lock(connectionsMutex);
for (auto& pair : connections)
{
closesocket(pair.second->socket);
}
connections.clear();
}
// 等待线程结束
if (acceptorThread.joinable())
{
acceptorThread.join();
}
for (auto& thread : workerThreads)
{
if (thread.joinable())
{
thread.join();
}
}
workerThreads.clear();
LogManager::LogInfo("Game server stopped");
}
private:
void AcceptorThreadFunc()
{
while (isRunning)
{
sockaddr_in clientAddr;
socklen_t addrLen = sizeof(clientAddr);
// 接受新连接
SOCKET clientSocket = accept(listenSocket,
reinterpret_cast<sockaddr*>(&clientAddr), &addrLen);
if (clientSocket == INVALID_SOCKET)
{
// 非阻塞模式下,没有新连接是正常情况
#ifdef _WIN32
if (WSAGetLastError() != WSAEWOULDBLOCK)
#else
if (errno != EWOULDBLOCK && errno != EAGAIN)
#endif
{
LogManager::LogError("Accept failed");
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
// 设置客户端套接字为非阻塞
#ifdef _WIN32
u_long nonBlocking = 1;
ioctlsocket(clientSocket, FIONBIO, &nonBlocking);
#else
int flags = fcntl(clientSocket, F_GETFL, 0);
fcntl(clientSocket, F_SETFL, flags | O_NONBLOCK);
#endif
// 创建连接对象
uint64_t connId = nextConnectionId++;
auto connection = std::make_shared<ClientConnection>(clientSocket, connId);
// 添加到连接表
{
std::unique_lock<std::shared_mutex> lock(connectionsMutex);
connections[connId] = connection;
}
// 记录连接信息
char ipStr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientAddr.sin_addr, ipStr, sizeof(ipStr));
LogManager::LogInfo("New connection " + std::to_string(connId) +
" from " + std::string(ipStr) + ":" +
std::to_string(ntohs(clientAddr.sin_port)));
// 发送欢迎消息
SendWelcomeMessage(connection);
}
}
void WorkerThreadFunc()
{
const int MAX_EVENTS = 64;
#ifdef _WIN32
// Windows使用select模型
while (isRunning)
{
fd_set readSet, writeSet, exceptSet;
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
FD_ZERO(&exceptSet);
SOCKET maxSocket = 0;
// 构建select集合
{
std::shared_lock<std::shared_mutex> lock(connectionsMutex);
for (const auto& pair : connections)
{
SOCKET sock = pair.second->socket;
FD_SET(sock, &readSet);
FD_SET(sock, &exceptSet);
// 检查发送队列是否非空
{
std::lock_guard<std::mutex> lock(pair.second->sendMutex);
if (!pair.second->sendQueue.empty())
{
FD_SET(sock, &writeSet);
}
}
if (sock > maxSocket) maxSocket = sock;
}
}
if (maxSocket == 0)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// 设置超时(100毫秒)
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
int result = select(maxSocket + 1, &readSet, &writeSet, &exceptSet, &timeout);
if (result == SOCKET_ERROR)
{
LogManager::LogError("Select failed");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
if (result > 0)
{
// 处理活跃的连接
std::vector<uint64_t> toRemove;
{
std::shared_lock<std::shared_mutex> lock(connectionsMutex);
for (auto& pair : connections)
{
SOCKET sock = pair.second->socket;
if (FD_ISSET(sock, &exceptSet))
{
// 连接异常
toRemove.push_back(pair.first);
continue;
}
if (FD_ISSET(sock, &readSet))
{
if (!ProcessReceive(pair.second))
{
toRemove.push_back(pair.first);
}
}
if (FD_ISSET(sock, &writeSet))
{
if (!ProcessSend(pair.second))
{
toRemove.push_back(pair.first);
}
}
}
}
// 移除断开连接
if (!toRemove.empty())
{
std::unique_lock<std::shared_mutex> lock(connectionsMutex);
for (uint64_t connId : toRemove)
{
auto it = connections.find(connId);
if (it != connections.end())
{
closesocket(it->second->socket);
connections.erase(it);
LogManager::LogInfo("Connection " +
std::to_string(connId) + " closed");
}
}
}
}
// 清理超时连接
CleanupTimeoutConnections();
}
#else
// Linux使用epoll模型(更高效)
int epollFd = epoll_create1(0);
if (epollFd == -1)
{
LogManager::LogError("Epoll create failed");
return;
}
struct epoll_event event;
struct epoll_event events[MAX_EVENTS];
// 添加现有连接到epoll
{
std::shared_lock<std::shared_mutex> lock(connectionsMutex);
for (const auto& pair : connections)
{
event.events = EPOLLIN | EPOLLOUT | EPOLLET;
event.data.u64 = pair.first;
epoll_ctl(epollFd, EPOLL_CTL_ADD, pair.second->socket, &event);
}
}
while (isRunning)
{
int nfds = epoll_wait(epollFd, events, MAX_EVENTS, 100);
if (nfds == -1)
{
if (errno == EINTR) continue;
LogManager::LogError("Epoll wait failed");
break;
}
for (int i = 0; i < nfds; ++i)
{
uint64_t connId = events[i].data.u64;
std::shared_ptr<ClientConnection> conn;
{
std::shared_lock<std::shared_mutex> lock(connectionsMutex);
auto it = connections.find(connId);
if (it == connections.end()) continue;
conn = it->second;
}
if (events[i].events & EPOLLIN)
{
if (!ProcessReceive(conn))
{
// 连接关闭
epoll_ctl(epollFd, EPOLL_CTL_DEL, conn->socket, NULL);
closesocket(conn->socket);
std::unique_lock<std::shared_mutex> lock(connectionsMutex);
connections.erase(connId);
continue;
}
}
if (events[i].events & EPOLLOUT)
{
if (!ProcessSend(conn))
{
// 连接关闭
epoll_ctl(epollFd, EPOLL_CTL_DEL, conn->socket, NULL);
closesocket(conn->socket);
std::unique_lock<std::shared_mutex> lock(connectionsMutex);
connections.erase(connId);
continue;
}
}
}
// 清理超时连接
CleanupTimeoutConnections();
}
close(epollFd);
#endif
}
bool ProcessReceive(std::shared_ptr<ClientConnection> conn)
{
char buffer[4096];
while (true)
{
int bytesReceived = recv(conn->socket, buffer, sizeof(buffer), 0);
if (bytesReceived > 0)
{
// 更新活动时间
conn->lastActivity = std::chrono::steady_clock::now();
// 检查缓冲区是否有足够空间
if (conn->recvLen + bytesReceived > ClientConnection::BUFFER_SIZE)
{
LogManager::LogError("Receive buffer overflow for connection " +
std::to_string(conn->connectionId));
return false;
}
// 复制到接收缓冲区
memcpy(conn->recvBuffer + conn->recvLen, buffer, bytesReceived);
conn->recvLen += bytesReceived;
// 处理完整消息
ProcessMessages(conn);
// 继续接收(非阻塞模式下可能还有数据)
continue;
}
else if (bytesReceived == 0)
{
// 客户端正常关闭连接
return false;
}
else
{
// 错误或没有更多数据
#ifdef _WIN32
int error = WSAGetLastError();
if (error != WSAEWOULDBLOCK)
#else
int error = errno;
if (error != EWOULDBLOCK && error != EAGAIN)
#endif
{
LogManager::LogError("Receive failed for connection " +
std::to_string(conn->connectionId) +
", error: " + std::to_string(error));
return false;
}
break;
}
}
return true;
}
bool ProcessSend(std::shared_ptr<ClientConnection> conn)
{
std::lock_guard<std::mutex> lock(conn->sendMutex);
while (!conn->sendQueue.empty())
{
auto& data = conn->sendQueue.front();
int bytesSent = send(conn->socket, data.data(), data.size(), 0);
if (bytesSent > 0)
{
// 更新活动时间
conn->lastActivity = std::chrono::steady_clock::now();
if (bytesSent == data.size())
{
// 完整发送,移除队列
conn->sendQueue.pop();
}
else
{
// 部分发送,调整数据
data.erase(data.begin(), data.begin() + bytesSent);
break; // 下次再尝试发送剩余部分
}
}
else if (bytesSent == 0)
{
// 连接已关闭
return false;
}
else
{
// 错误或阻塞
#ifdef _WIN32
int error = WSAGetLastError();
if (error != WSAEWOULDBLOCK)
#else
int error = errno;
if (error != EWOULDBLOCK && error != EAGAIN)
#endif
{
LogManager::LogError("Send failed for connection " +
std::to_string(conn->connectionId));
return false;
}
break;
}
}
return true;
}
void ProcessMessages(std::shared_ptr<ClientConnection> conn)
{
// 协议解析器:消息格式为 [4字节长度][消息体]
while (conn->recvLen >= 4)
{
// 读取消息长度
uint32_t msgLength = *reinterpret_cast<uint32_t*>(conn->recvBuffer);
msgLength = ntohl(msgLength); // 网络字节序转换
// 检查长度有效性
if (msgLength > 1024 * 1024) // 最大1MB
{
LogManager::LogError("Invalid message length: " +
std::to_string(msgLength) +
" from connection " +
std::to_string(conn->connectionId));
return;
}
// 检查是否收到完整消息
if (conn->recvLen < 4 + msgLength)
{
break; // 等待更多数据
}
// 提取消息体
std::vector<char> message(conn->recvBuffer + 4,
conn->recvBuffer + 4 + msgLength);
// 处理消息
HandleMessage(conn, message);
// 移动缓冲区数据
int remaining = conn->recvLen - (4 + msgLength);
if (remaining > 0)
{
memmove(conn->recvBuffer,
conn->recvBuffer + 4 + msgLength,
remaining);
}
conn->recvLen = remaining;
}
}
void HandleMessage(std::shared_ptr<ClientConnection> conn,
const std::vector<char>& message)
{
// 这里应该根据协议反序列化消息
// 实际项目中会使用Protocol Buffers等
LogManager::LogDebug("Received message from connection " +
std::to_string(conn->connectionId) +
", length: " + std::to_string(message.size()));
// 简单回显
SendToConnection(conn->connectionId, message);
}
void SendWelcomeMessage(std::shared_ptr<ClientConnection> conn)
{
std::string welcomeMsg = "Welcome to Game Server!";
std::vector<char> data(welcomeMsg.begin(), welcomeMsg.end());
SendToConnection(conn->connectionId, data);
}
void SendToConnection(uint64_t connId, const std::vector<char>& data)
{
std::shared_ptr<ClientConnection> conn;
{
std::shared_lock<std::shared_mutex> lock(connectionsMutex);
auto it = connections.find(connId);
if (it == connections.end()) return;
conn = it->second;
}
// 添加长度前缀
uint32_t length = htonl(static_cast<uint32_t>(data.size()));
std::vector<char> packet(sizeof(length) + data.size());
memcpy(packet.data(), &length, sizeof(length));
memcpy(packet.data() + sizeof(length), data.data(), data.size());
// 添加到发送队列
{
std::lock_guard<std::mutex> lock(conn->sendMutex);
conn->sendQueue.push(std::move(packet));
}
}
void CleanupTimeoutConnections()
{
std::vector<uint64_t> timeoutConnections;
{
std::shared_lock<std::shared_mutex> lock(connectionsMutex);
for (const auto& pair : connections)
{
if (pair.second->IsTimeout())
{
timeoutConnections.push_back(pair.first);
}
}
}
if (!timeoutConnections.empty())
{
std::unique_lock<std::shared_mutex> lock(connectionsMutex);
for (uint64_t connId : timeoutConnections)
{
auto it = connections.find(connId);
if (it != connections.end())
{
closesocket(it->second->socket);
connections.erase(it);
LogManager::LogInfo("Connection " + std::to_string(connId) +
" timeout and closed");
}
}
}
}
};
// 日志管理器(简化版)
class LogManager
{
public:
enum class LogLevel
{
Debug,
Info,
Warning,
Error
};
static void Log(LogLevel level, const std::string& message)
{
const char* levelStr = "";
switch (level)
{
case LogLevel::Debug: levelStr = "DEBUG"; break;
case LogLevel::Info: levelStr = "INFO"; break;
case LogLevel::Warning: levelStr = "WARNING"; break;
case LogLevel::Error: levelStr = "ERROR"; break;
}
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::tm tm;
#ifdef _WIN32
localtime_s(&tm, &time);
#else
localtime_r(&time, &tm);
#endif
char timeStr[64];
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &tm);
std::cout << "[" << timeStr << "] [" << levelStr << "] "
<< message << std::endl;
}
static void LogDebug(const std::string& message)
{
Log(LogLevel::Debug, message);
}
static void LogInfo(const std::string& message)
{
Log(LogLevel::Info, message);
}
static void LogWarning(const std::string& message)
{
Log(LogLevel::Warning, message);
}
static void LogError(const std::string& message)
{
Log(LogLevel::Error, message);
}
static void LogCheatDetection(uint64_t playerId, const std::string& reason)
{
std::string msg = "Cheat detected for player " + std::to_string(playerId) +
": " + reason;
Log(LogLevel::Warning, msg);
// 实际项目中这里会记录到数据库或发送警报
}
};
// 辅助函数
uint64_t GetCurrentTimeMs()
{
auto now = std::chrono::system_clock::now();
auto duration = now.time_since_epoch();
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
2.3.3 构建基础游戏服务器的实践
构建一个完整的游戏服务器需要多个组件的协同工作。以下是一个简化但完整的服务器架构示例,展示了如何将网络层、逻辑层和数据层组合在一起。
// 游戏服务器主类
class GameServer
{
private:
std::unique_ptr<GameServerNetwork> networkLayer;
std::unique_ptr<WorldManager> worldManager;
std::atomic<bool> isRunning;
std::thread tickThread;
// 配置
struct ServerConfig
{
int listenPort = 8888;
int maxConnections = 10000;
int workerThreads = 4;
int tickRate = 20; // 每秒逻辑帧数
std::string serverName = "GameServer";
};
ServerConfig config;
public:
GameServer()
: isRunning(false)
{
networkLayer = std::make_unique<GameServerNetwork>();
worldManager = WorldManager::GetInstance();
}
bool Initialize(const ServerConfig& cfg)
{
config = cfg;
// 初始化网络层
if (!networkLayer->Start(config.listenPort, config.workerThreads))
{
LogManager::LogError("Failed to start network layer");
return false;
}
// 初始化世界管理器
// 这里可以加载地图数据、NPC配置等
LogManager::LogInfo("Game server initialized successfully");
LogManager::LogInfo("Server name: " + config.serverName);
LogManager::LogInfo("Listen port: " + std::to_string(config.listenPort));
LogManager::LogInfo("Max connections: " + std::to_string(config.maxConnections));
LogManager::LogInfo("Tick rate: " + std::to_string(config.tickRate) + "Hz");
return true;
}
void Start()
{
if (isRunning) return;
isRunning = true;
// 启动逻辑帧线程
tickThread = std::thread(&GameServer::TickLoop, this);
LogManager::LogInfo("Game server started");
}
void Stop()
{
if (!isRunning) return;
isRunning = false;
// 停止网络层
networkLayer->Stop();
// 等待逻辑帧线程结束
if (tickThread.joinable())
{
tickThread.join();
}
LogManager::LogInfo("Game server stopped");
}
~GameServer()
{
Stop();
}
private:
void TickLoop()
{
const int TICK_INTERVAL_MS = 1000 / config.tickRate;
auto lastTickTime = std::chrono::steady_clock::now();
while (isRunning)
{
auto currentTime = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastTickTime);
// 确保稳定的逻辑帧率
if (elapsed.count() >= TICK_INTERVAL_MS)
{
// 执行逻辑更新
Update(TICK_INTERVAL_MS);
// 更新最后执行时间
lastTickTime = currentTime;
// 计算实际消耗时间
auto processTime = std::chrono::steady_clock::now() - currentTime;
auto processMs = std::chrono::duration_cast<std::chrono::milliseconds>(
processTime);
// 如果处理时间超过帧间隔,记录警告
if (processMs.count() > TICK_INTERVAL_MS)
{
LogManager::LogWarning("Tick processing time exceeded interval: " +
std::to_string(processMs.count()) + "ms");
}
}
else
{
// 等待下一帧
std::this_thread::sleep_for(
std::chrono::milliseconds(1));
}
}
}
void Update(int deltaTimeMs)
{
// 更新游戏世界状态
// 1. 处理定时事件
// 2. 更新NPC AI
// 3. 检查任务进度
// 4. 广播定期状态更新
static int tickCount = 0;
tickCount++;
// 每10秒记录一次性能数据
if (tickCount % (10 * config.tickRate) == 0)
{
LogPerformanceMetrics();
}
}
void LogPerformanceMetrics()
{
// 实际项目中会收集各种性能指标
// 如:在线玩家数、消息处理速率、内存使用等
LogManager::LogInfo("Performance metrics logged");
}
};
// 服务器启动示例
int main(int argc, char* argv[])
{
try
{
LogManager::LogInfo("Starting game server...");
GameServer server;
GameServer::ServerConfig config;
// 从配置文件或命令行参数读取配置
if (argc > 1) config.listenPort = std::stoi(argv[1]);
if (argc > 2) config.serverName = argv[2];
if (!server.Initialize(config))
{
LogManager::LogError("Server initialization failed");
return 1;
}
// 设置信号处理器
signal(SIGINT, [](int)
{
LogManager::LogInfo("Received shutdown signal");
// 实际项目中会有更优雅的关闭逻辑
});
server.Start();
// 主线程等待服务器运行
LogManager::LogInfo("Server is running. Press Ctrl+C to stop.");
// 简单保持主线程活跃
while (true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
server.Stop();
return 0;
}
catch (const std::exception& e)
{
LogManager::LogError("Fatal error: " + std::string(e.what()));
return 1;
}
}
2.3.4 实现角色移动的基础框架
在上述网络和服务器框架的基础上,我们现在实现角色移动的完整逻辑。这是游戏服务器最核心的功能之一。
// 玩家类定义
class Player
{
private:
uint64_t playerId;
std::string accountName;
Vector3 position;
uint32_t level;
uint32_t moveSpeed;
uint64_t lastMoveTime;
uint32_t lastMoveSequence;
// 玩家状态
enum class PlayerState
{
Offline,
Online,
Moving,
Fighting,
Trading,
Dead
};
PlayerState currentState;
// 视野范围内的其他玩家
std::set<uint64_t> visiblePlayers;
std::shared_mutex visiblePlayersMutex;
// 脏标记,用于持久化
bool positionDirty;
bool statsDirty;
std::shared_mutex playerMutex;
public:
Player(uint64_t id, const std::string& account)
: playerId(id), accountName(account), position(0, 0, 0), level(1),
moveSpeed(300), lastMoveTime(0), lastMoveSequence(0),
currentState(PlayerState::Online), positionDirty(false), statsDirty(false)
{
}
uint64_t GetId() const { return playerId; }
const std::string& GetAccountName() const { return accountName; }
Vector3 GetPosition() const
{
std::shared_lock<std::shared_mutex> lock(playerMutex);
return position;
}
void SetPosition(const Vector3& newPos)
{
std::unique_lock<std::shared_mutex> lock(playerMutex);
position = newPos;
positionDirty = true;
}
uint32_t GetMoveSpeed() const
{
std::shared_lock<std::shared_mutex> lock(playerMutex);
return moveSpeed;
}
uint64_t GetLastMoveTime() const
{
std::shared_lock<std::shared_mutex> lock(playerMutex);
return lastMoveTime;
}
void SetLastMoveTime(uint64_t time)
{
std::unique_lock<std::shared_mutex> lock(playerMutex);
lastMoveTime = time;
}
uint32_t GetLastMoveSequence() const
{
std::shared_lock<std::shared_mutex> lock(playerMutex);
return lastMoveSequence;
}
void SetMoveSequence(uint32_t seq)
{
std::unique_lock<std::shared_mutex> lock(playerMutex);
lastMoveSequence = seq;
}
PlayerState GetState() const
{
std::shared_lock<std::shared_mutex> lock(playerMutex);
return currentState;
}
void SetState(PlayerState state)
{
std::unique_lock<std::shared_mutex> lock(playerMutex);
currentState = state;
}
void AddVisiblePlayer(uint64_t otherPlayerId)
{
std::unique_lock<std::shared_mutex> lock(visiblePlayersMutex);
visiblePlayers.insert(otherPlayerId);
}
void RemoveVisiblePlayer(uint64_t otherPlayerId)
{
std::unique_lock<std::shared_mutex> lock(visiblePlayersMutex);
visiblePlayers.erase(otherPlayerId);
}
std::vector<uint64_t> GetVisiblePlayers() const
{
std::shared_lock<std::shared_mutex> lock(visiblePlayersMutex);
return std::vector<uint64_t>(visiblePlayers.begin(), visiblePlayers.end());
}
bool IsDirty() const
{
std::shared_lock<std::shared_mutex> lock(playerMutex);
return positionDirty || statsDirty;
}
void ClearDirty()
{
std::unique_lock<std::shared_mutex> lock(playerMutex);
positionDirty = false;
statsDirty = false;
}
std::shared_mutex& GetMutex() { return playerMutex; }
};
// 移动消息处理器
class MovementHandler
{
public:
static void HandleMoveRequest(uint64_t connectionId, const MoveRequest& request)
{
auto worldManager = WorldManager::GetInstance();
MoveValidationResult result = worldManager->ProcessMoveRequest(request);
// 构建响应消息
MoveResponse response;
response.playerId = request.playerId;
response.requestSequence = request.sequence;
response.result = result;
response.newPosition = request.targetPosition;
response.serverTime = GetCurrentTimeMs();
// 发送给请求的客户端
SendMoveResponse(connectionId, response);
// 如果移动成功,记录移动日志
if (result == MoveValidationResult::Success)
{
LogMovement(request);
}
}
private:
static void SendMoveResponse(uint64_t connectionId, const MoveResponse& response)
{
// 序列化响应消息
// 实际项目中会使用Protocol Buffers等序列化库
std::vector<char> serialized = SerializeMoveResponse(response);
// 通过网络层发送
// networkLayer->SendToConnection(connectionId, serialized);
}
static void LogMovement(const MoveRequest& request)
{
// 记录移动日志,用于分析和监控
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::tm tm;
#ifdef _WIN32
localtime_s(&tm, &time);
#else
localtime_r(&time, &tm);
#endif
char timeStr[64];
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &tm);
std::string logEntry = std::string(timeStr) +
" Player " + std::to_string(request.playerId) +
" moved from (" +
std::to_string(request.currentPosition.x) + "," +
std::to_string(request.currentPosition.y) + "," +
std::to_string(request.currentPosition.z) + ") to (" +
std::to_string(request.targetPosition.x) + "," +
std::to_string(request.targetPosition.y) + "," +
std::to_string(request.targetPosition.z) + ")";
// 实际项目中会写入日志文件或数据库
std::cout << logEntry << std::endl;
}
static std::vector<char> SerializeMoveResponse(const MoveResponse& response)
{
// 简化的序列化
std::vector<char> data(sizeof(response));
memcpy(data.data(), &response, sizeof(response));
return data;
}
};
// 玩家管理器
class PlayerManager
{
private:
std::unordered_map<uint64_t, std::shared_ptr<Player>> players;
std::unordered_map<std::string, uint64_t> accountToPlayerId;
std::shared_mutex playersMutex;
// 持久化队列
std::queue<std::shared_ptr<Player>> dirtyPlayers;
std::mutex dirtyQueueMutex;
std::condition_variable dirtyQueueCV;
std::thread persistenceThread;
std::atomic<bool> persistenceRunning;
public:
PlayerManager()
: persistenceRunning(false)
{
}
~PlayerManager()
{
StopPersistence();
}
std::shared_ptr<Player> GetPlayer(uint64_t playerId)
{
std::shared_lock<std::shared_mutex> lock(playersMutex);
auto it = players.find(playerId);
if (it != players.end())
{
return it->second;
}
return nullptr;
}
std::shared_ptr<Player> GetPlayerByAccount(const std::string& account)
{
std::shared_lock<std::shared_mutex> lock(playersMutex);
auto it = accountToPlayerId.find(account);
if (it != accountToPlayerId.end())
{
return GetPlayer(it->second);
}
return nullptr;
}
bool AddPlayer(const std::shared_ptr<Player>& player)
{
std::unique_lock<std::shared_mutex> lock(playersMutex);
uint64_t playerId = player->GetId();
if (players.find(playerId) != players.end())
{
return false; // 玩家已存在
}
players[playerId] = player;
accountToPlayerId[player->GetAccountName()] = playerId;
LogManager::LogInfo("Player added: " + player->GetAccountName() +
" (ID: " + std::to_string(playerId) + ")");
return true;
}
void RemovePlayer(uint64_t playerId)
{
std::unique_lock<std::shared_mutex> lock(playersMutex);
auto it = players.find(playerId);
if (it != players.end())
{
// 保存玩家数据
SavePlayerData(it->second);
// 从索引中移除
accountToPlayerId.erase(it->second->GetAccountName());
players.erase(it);
LogManager::LogInfo("Player removed: ID " + std::to_string(playerId));
}
}
void MarkPlayerDirty(const std::shared_ptr<Player>& player)
{
if (player->IsDirty())
{
std::lock_guard<std::mutex> lock(dirtyQueueMutex);
dirtyPlayers.push(player);
dirtyQueueCV.notify_one();
}
}
void StartPersistence()
{
persistenceRunning = true;
persistenceThread = std::thread(&PlayerManager::PersistenceLoop, this);
LogManager::LogInfo("Player persistence thread started");
}
void StopPersistence()
{
if (persistenceRunning)
{
persistenceRunning = false;
dirtyQueueCV.notify_all();
if (persistenceThread.joinable())
{
persistenceThread.join();
}
LogManager::LogInfo("Player persistence thread stopped");
}
}
private:
void PersistenceLoop()
{
while (persistenceRunning)
{
std::shared_ptr<Player> player;
{
std::unique_lock<std::mutex> lock(dirtyQueueMutex);
if (dirtyPlayers.empty())
{
// 等待新数据或超时
dirtyQueueCV.wait_for(lock, std::chrono::seconds(5));
continue;
}
player = dirtyPlayers.front();
dirtyPlayers.pop();
}
if (player)
{
SavePlayerData(player);
player->ClearDirty();
}
}
// 退出前保存所有剩余脏数据
SaveAllDirtyPlayers();
}
void SavePlayerData(const std::shared_ptr<Player>& player)
{
// 实际项目中会保存到数据库
// 这里简化实现为日志记录
LogManager::LogDebug("Saving player data: " + player->GetAccountName());
// 模拟数据库操作延迟
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
void SaveAllDirtyPlayers()
{
std::unique_lock<std::mutex> lock(dirtyQueueMutex);
while (!dirtyPlayers.empty())
{
auto player = dirtyPlayers.front();
dirtyPlayers.pop();
lock.unlock();
SavePlayerData(player);
lock.lock();
}
}
};
// 主函数示例
int RunGameServer()
{
LogManager::LogInfo("Initializing game server components...");
// 初始化玩家管理器
auto playerManager = std::make_shared<PlayerManager>();
playerManager->StartPersistence();
// 初始化世界管理器
auto worldManager = WorldManager::GetInstance();
// 创建测试玩家
auto testPlayer = std::make_shared<Player>(10001, "test_player");
testPlayer->SetPosition(Vector3(100.0f, 200.0f, 0.0f));
playerManager->AddPlayer(testPlayer);
// 模拟移动请求
MoveRequest moveRequest;
moveRequest.playerId = 10001;
moveRequest.currentPosition = Vector3(100.0f, 200.0f, 0.0f);
moveRequest.targetPosition = Vector3(150.0f, 250.0f, 0.0f);
moveRequest.moveSpeed = 300;
moveRequest.timestamp = GetCurrentTimeMs();
moveRequest.sequence = 1;
// 处理移动请求
MovementHandler::HandleMoveRequest(1, moveRequest);
// 等待一段时间让持久化线程工作
std::this_thread::sleep_for(std::chrono::seconds(2));
// 清理
playerManager->StopPersistence();
LogManager::LogInfo("Game server example completed");
return 0;
}
上述代码展示了一个商业级游戏服务器的基础框架。在实际项目中,还需要考虑更多复杂因素:
- 安全性:增加加密通信、防外挂检测、输入验证等
- 性能优化:使用对象池、内存池、更高效的数据结构
- 可扩展性:支持热更新、配置动态加载
- 监控与调试:详细的性能指标收集和实时监控
- 容错处理:异常恢复、数据备份、故障转移
这个框架为理解游戏服务器开发提供了坚实的基础。在实际开发中,每个模块都需要根据具体游戏类型和规模进行深度定制和优化。







