01-联机游戏开发基础与HTTP服务器搭建
1 网络拓扑结构
我们先了解在联机游戏中最常见的网络拓扑结构,星型拓扑。
在这种设计下,服务器程序作为世界的权威,实际运行和掌握着游戏世界的状态数据。众多客户端程序都与这个中心服务器进行连接,从中获取最新的世界状态数据。然后在本地根据客户端预先设计好的逻辑,将这些内容渲染到窗口上。
也就是说在这种模式下,客户端相当于是一个接入到网络的显示器和手柄。进处理画面显示和玩家输入的操作。而整个世界的运转逻辑被拆分到了服务端程序上。
在一般情况下,服务端只需要关注数据逻辑,而不需要关注使用何种素材进行画面渲染的客户端功能。
在一些支持客户端创建服务器的游戏中,也是相似的道理。对于这些游戏,我们可以简单理解为客户端和服务端绑定在一起,游戏房间运行时使用房主的服务端程序作为权威服务器。
2 域名 IP 端口
我们知道,要想在网络世界中发送数据包,那就必然需要知晓其目的地。而IP就是用来描述网络地址的符号。而端口可以看作是在一台设备上的多个网络数据入口。不同的应用程序从不同的端口上收发数据。
域名,也就是常说的“网址”。这些网址其实可以看作是IP的“别名”。网络通信时,会借助DNS服务器从域名得到IP。DNS服务器相当于维护着一张巨大的map映射表。记录着域名到IP的映射关系,这样我们的数据包就可以从本地出发,发送到对应的IP地址。然后从指定的端口进入设备。交付给监听此处端口的程序,完成网络通信。
HTTP,日常生活中所见的网页网址基本都以HTTP开头,它的全称是超文本传输协议,HyperText Transfer Protocol。
这里的协议,通俗的讲就是“规范”或者“约定”。网络数据包的传输是二进制结构。我们要怎么让服务器和客户端明白这个二进制数据包中,每一部分都是什么含义呢?
协议描述的就是这部分内容。HTTP的理论知识细节有很多,我们目前只需要关注两个概念:请求方法和状态码。
首先是请求方法,HTTP协议在通信时,笼统而言可以由两部分组成:请求和响应。请求便是客户端向服务端使用不同的请求方法发送数据的过程。而响应则是服务端在接受并处理后向客户端返回数据的过程。
请求方法有很多,分别有着不同的含义和应用场景。在本项目中我们只需要关注POST请求即可。

而状态码,则是用来描述服务端对客户端的响应结果。在本项目中,我们只需要检测状态码是否为200即可,200状态码意味着HTTP请求成功。

下面我们便可以开始http服务器的搭建了
在本项目中,我们使用了cpp-httplib这个三方库。来帮助我们处理HTTP相关的内容。这时一个单头文件的库,我们只需要引入一个.h头文件即可。
github仓库的连接 https://github.com/yhirose/cpp-httplib
3 简单程序
我们在项目目录下新建三个文件夹分别是client,server和thirdparty。
在thirdparty中放入之前下载的httplib.h
在client和server中分别新建client.cpp和server.cpp
在客户端代码client.cpp中,我们连接到本地端口25565,发送hello路由的消息请求。并打印输出服务器响应的内容
#include "../thirdparty/httplib.h"
int main(int argc, char** argv)
{
httplib::Client client("localhost:25565");
httplib::Result result = client.Post("/hello");
if (!result || result->status != 200)
{
std::cout << "Hello Failed!" << std::endl;
return -1;
}
std::cout << result->body << std::endl;
system("pause");
return 0;
}
在服务端代码server.cpp中,我们创建了服务器对象,设置请求的路由为hello时执行的回调函数。在lambda回调函数中,我们设置了响应的内容为纯文本形式的“Hello From Server!”。最后让服务端监听本地的25565端口。
#include "../thirdparty/httplib.h"
int main(int argc, char** argv)
{
httplib::Server server;
server.Post("/hello", [&](const httplib::Request& req, httplib::Response& res)
{
std::cout << "Hello from Client!" << std::endl;
res.set_content("Hello From Server!", "text/plain");
});
server.listen("localhost", 25565);
return 0;
}
4 配置CMake

首先在workspaceFolder新建一个全局的CMakeLists.txt, 其中配置头文件目录,并且设置server和client两个子目录。这里设置的环境变量都会被子目录的CMakeLists.txt继承
cmake_minimum_required(VERSION 3.10)
project(hakimi_adventure)
set(CMAKE_CXX_STANDARD 17)
add_compile_options(-finput-charset=UTF-8 -fexec-charset=GBK)
# 设置EasyX路径
set(EasyX_INCLUDE_DIR "C:/msys64/mingw64/include")
set(EasyX_LIB_DIR "C:/msys64/mingw64/lib")
set(EasyX_LIB_PATH "${EasyX_LIB_DIR}/libeasyx.a")
# 包含头文件目录
include_directories(${EasyX_INCLUDE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/include)
add_subdirectory(server)
add_subdirectory(client)
在client/CMakeLists.txt中,编写下述代码
# 编译客户端可执行文件
add_executable(client client.cpp)
# 如果客户端也用到网络/图形库,链接相同的库;如果不用 EasyX,可删除对应项
target_link_libraries(client PRIVATE
${EasyX_LIB_PATH} # 若客户端无图形界面,删除此行
ws2_32 # 必须保留(httplib 网络依赖)
winmm # 若客户端无图形界面,删除此行
msimg32 # 若客户端无图形界面,删除此行
pthread # 必须保留(httplib 线程依赖)
)
在server/CMakeLists.txt中,编写下述代码
# 编译服务端可执行文件
add_executable(server server.cpp)
# 链接必要的库(核心修正:添加 ws2_32 解决网络函数缺失)
target_link_libraries(server PRIVATE
${EasyX_LIB_PATH} # EasyX 图形库
ws2_32 # Windows 套接字库(解决 setsockopt 错误)
winmm # 多媒体库(EasyX 依赖)
msimg32 # 图形相关库(EasyX 依赖)
pthread # 线程库(httplib 依赖)
)
为了让运行时,能够同时运行server和client两个程序,我们在.vscode中新建一个launch.json,
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run Server",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/build/server/server.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"console": "externalTerminal",
},
{
"name": "Run Client",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/build/client/client.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"console": "externalTerminal"
}
],
"compounds": [
{
"name": "Run Server & Client",
"configurations": ["Run Server", "Run Client"],
"stopAll": true
}
]
}

之后在vscode的“运行和调试(ctrl shift D)”中可以看到我们定义的启动配置,选择Run Server & Client,点击绿色的三角,就可以开始运行了。
5 测试

在vscode的左下角可以看到我们的启动配置,以及Cmake tools插件的“生成”按钮。点击“生成”可以对整个项目进行编译,点击“Run Server & Client”可以同时运行server和client两个程序
编译运行,可以看到最终结果符合我们的预期

评论
如果你已登录 GitHub,就可以直接在这里评论。