CentOS编译石器时代源码:老版本游戏服务端环境搭建实战
1. 前言为什么还在折腾石器时代的源代码编译看到这个标题点进来的朋友我猜大多数是两类人一类是当年在渔村、加加村、玛丽娜丝渔村泡了无数个通宵的老玩家想在自己机器上把当年那个石器时代重新跑起来找回点青春记忆另一类是搞技术研究的学生或开发者想研究一下十几年前经典MMORPG的服务端是怎么组织代码的进程之间怎么通信、数据库结构怎么设计、GM指令怎么实现。不管你属于哪一类石器时代源代码的编译都有个统一的落脚点——环境搭建。这个游戏的服务端代码年代非常久远最早可以追溯到21世纪初代码风格、依赖库、编译方式都和今天的Web项目完全不一样。很多人在网上找到源代码压缩包解压之后第一个感觉是这玩意儿怎么编译缺了一堆头文件、make到一半报错、数据库连不上、gmsv一启动就崩……这些坑我全都踩过。这篇文章写给想在 CentOS 6.5 / CentOS 7以及类似的老版本Linux发行版上顺利编译石器时代源代码的人。我会把完整过程拆开讲清楚包括环境选型、依赖安装、编译步骤、数据库初始化以及我在实际操作中遇到过的经典问题和对应的排查方法。如果环境不太一样比如你用的是更高版本的系统很多思路同样能迁移过去。先说结论这套古老的C/C代码在CentOS 7上编译是最顺利的CentOS 6.5也能过但需要手动解决一些库版本兼容问题。接下来我按实际操作的顺序从零开始讲。2. 动手前的关键认知石器时代服务端到底由什么组成2.1 老代码的“脾气”和现代项目完全不同在开始装环境之前一定要先理解你要编译的东西长什么样。石器时代服务端最早源自日方源代码典型的Japanaese style命名风格后来流到中文社区后经过多个版本分支演化。常见的代码包里一般包含这样几个关键部分gmsv游戏主服务进程承载怪物、地图、掉落、战斗逻辑是整个服务端最核心的部分saac账号认证服务负责登录时的账号密码校验ac账号连接服务承担角色信息查询、进入游戏前的数据准备db数据库中间层负责把游戏逻辑和MySQL解耦sa_server / sa_client部分版本的客户端和服务端引擎share目录各大进程共享的头文件和公共函数。这套代码的典型特征是大量使用全局变量、宏定义、一个源文件几千行、命名缩写严重比如CHARACTER缩写为CHARGetCharacterData这类函数名比比皆是。这些都是那个年代的正常风格阅读和编译的时候不要用现代工程标准去要求它。最关键的一点是这份代码的设计目标是跑在32位系统上编译时的字节对齐、指针长度、内存布局都和64位环境有点水土不服。但并不代表64位系统完全跑不了——只要处理好-m32编译选项和32位兼容库在现在的机器上照样能编出可用的服务端。2.2 “CentOS 6.5 7等通用”的环境选型思路标题里说“CentOS 6.5 7等通用”这个说法是有道理的。老代码依赖的都是基础库zlib、readline、openssl、mysql-devel这些库在6.x和7.x中都有只是版本号不同。真正的差异集中在几个点gcc版本CentOS 6.5自带gcc 4.4.7CentOS 7自带gcc 4.8.5。老代码对gcc版本极其敏感4.4.7太老导致某些C语法不支持4.8.5则比较稳妥。如果系统是CentOS 8或更新的版本gcc默认版本到了8.x甚至11.x反而容易出现“too many arguments”之类的报错处理起来更费劲MySQL客户端库CentOS 6时代用mysql-develCentOS 7时代可以用mysql-devel也可以改用mariadb-develCentOS 7默认把MySQL换成了MariaDB32位兼容库如果要加-m32编译需要安装glibc-devel.i686、libstdc-devel.i686等32位开发包系统服务类型CentOS 6用的是SysVinitservice命令CentOS 7用的是systemdsystemctl命令这会影响你写启动脚本的方式。我个人的建议是优先使用CentOS 7 x86_64虚拟机虚拟机内存给2GB以上不需要图形界面最小化安装就够了。CentOS 6.5作为备选方案适合那些坚持要在老系统上跑老代码的人。如果你是第一次搞千万别一上来就挑战高版本系统否则光处理gcc报错就够你折腾一周。3. 编译环境的搭建基础依赖与系统配置3.1 最小化安装后的第一件事网络与基础工具虚拟机装完CentOS 7之后第一步不是急着装依赖而是先把网络配置好。很多朋友卡在第一步就是因为网卡没启动。CentOS 7默认网卡可能没开机自启需要改配置文件vi /etc/sysconfig/network-scripts/ifcfg-ens33把ONBOOTno改成ONBOOTyes然后执行systemctl restart network或直接reboot。网络通了之后先把基础工具链装上yum install -y wget vim unzip zip lrzsz yum groupinstall -y Development ToolsDevelopment Tools这个组包会把gcc、g、make、autoconf、automake、libtool等一整套编译工具全部装好省去一个个手动安装的麻烦。装完后确认一下版本gcc --version g --version make --version如果gcc版本在4.8.5左右就没问题。这是整个编译环境最关键的基础后面所有步骤都建立在这上面。另外强烈建议把系统时间校准一下老代码编译时可能会用到time()函数生成时间戳如果虚拟机的系统时间和宿主机偏差过大数据库里生成的角色创建时间、封号时间会非常诡异。3.2 依赖库安装zlib、readline、openssl、mysql石器时代服务端编译时对库的依赖其实不算多但每一样都不能少。我自己实测需要以下几个开发包yum install -y zlib zlib-devel yum install -y readline readline-devel yum install -y openssl openssl-devel yum install -y mysql-devel如果系统是CentOS 7mysql-devel可能指向MariaDB的兼容包安装命令可能提示找不到MySQL官方源——没关系mariadb-devel在功能上完全兼容yum install -y mariadb-devel这里解释一下为什么需要这些库zlib游戏资源文件map、graphic等很多是压缩存储的服务端读取时需要zlib解压readline服务端的控制台交互GM命令输入依赖这个库提供命令行编辑功能openssl部分版本的账号认证流程有加密通信需要它的加密函数库mysql-devel / mariadb-develdb进程连接MySQL数据库的客户端库这个是最关键的少了它编译时百分之百报mysql.h: No such file or directory。32位兼容库方面如果你决定按我后面推荐的-m32方式来编译还要额外装yum install -y glibc-devel.i686 libstdc-devel.i686 zlib-devel.i686装包过程中最容易出的问题是yum源失效。CentOS 6.5的官方源早就不维护了yum install会报404。解决办法是改用Vault镜像源改 /etc/yum.repos.d/ 下的repo文件把mirror.centos.org换成vault.centos.org对应路径。CentOS 7的话目前还能正常用但如果机器很老也建议顺手把源换到国内镜像阿里云、清华源都行速度会好很多。4. 核心编译实战从源码到可执行文件4.1 先看源代码目录结构别急着make拿到源代码压缩包后先解压并观察目录结构unzip stoneage_source.zip cd stoneage_source ls -la正常的石器时代服务端源码包通常会有以下目录gmsv/游戏主服务源码saac/账号服务源码ac/连接服务源码db/数据库中间层源码share/公共头文件和函数sql/或database/数据库初始化脚本各目录下常见Makefile或者.dspVisual Studio工程文件Linux下用不到。每个子目录下的Makefile通常会引用../share路径下的公共头文件所以整个项目必须保持目录结构的完整性绝对不能单独把一个子目录拷走编译。这里要特别提醒很多网上下载的源码包是Windows版本的工程一堆.dsp/.dsw文件Linux下没法直接用。遇到这种情况需要自己写Makefile把对应目录下的 .c/.cpp 文件全部编译一遍。工作量不小但也不是不能做——后面我会给出一个可以照抄的基础Makefile模板。4.2 编写通用Makefile并开始编译如果你手里的代码包自带Linux Makefile那很幸运直接进入编译流程即可。大多数情况下Makefile里会有类似这样的目标make clean make但是因为Makefile写了很久、系统库路径有差异经常会报缺头文件或者链接错误。我的经验是直接自己写一份简单明确的Makefile反而比修一份旧Makefile更省时间。下面这份是实际验证可用的gmsv编译模板CC gcc CXX g CFLAGS -m32 -O2 -g -Wall -I../share -I/usr/include/mysql CXXFLAGS $(CFLAGS) LIBS -L/usr/lib/mysql -lmysqlclient -lz -lpthread -lrt -ldl OBJS main.o char_base.o char_data.o npc.o battle.o \ item.o map.o magic.o skill.o guild.o chat.o \ protocol.o db_if.o util.o gmsv: $(OBJS) $(CXX) $(CFLAGS) -o gmsv $(OBJS) $(LIBS) %.o: %.cpp $(CXX) $(CXXFLAGS) -c $ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ clean: rm -f *.o gmsv注意几个关键点-m32表示编译成32位程序这是我强烈建议的方式。老代码的struct内存布局、枚举大小、long类型长度都基于32位模型设计如果不加-m32在64位环境下会出现大量奇怪的逻辑错误比如封包结构错位、数据库读出来的数值不对-I/usr/include/mysql指定MySQL头文件路径不同系统可能路径不同可以用mysql_config --include命令查一下-L/usr/lib/mysql是MySQL客户端库的路径同样可以用mysql_config --libs获取准确值-lrt链接实时库老代码里有些会用clock_gettime这个库在glibc 2.17之前的版本需要显式链接。有朋友会问-m32要求系统有32位库如果只装了64位系统怎么办其实64位CentOS上是完全可行的只要装了前面提到的.i686开发包gcc就能编译出32位程序。我在CentOS 7 x86_64上就是这么编的跑起来完全正常。每个服务进程的Makefile写法都类似只是源文件列表不同。我建议按这个顺序编译saac → db → ac → gmsv因为saac和db相对独立参与的游戏逻辑少可以先验证环境没有问题最后再啃gmsv这块硬骨头。编译过程中如果某个 .cpp 文件报错不要慌常见原因和处理方法我在第5节统一集中讲。这里先给一个心理准备第一次编译报个几十个错是很正常的很多都是头文件路径不对、某个宏没定义之类的机械性问题逐个解决后代码往往就能完整编过。4.3 数据库初始化和基础配置服务端编译完成只是第一步要让服务端真正跑起来还需要准备MySQL数据库。这也是整个环境搭建里最容易被忽略、拒新手于门外的一个环节。先安装并启动数据库CentOS 7默认是MariaDByum install -y mariadb-server mariadb systemctl start mariadb systemctl enable mariadb mysql_secure_installation然后创建游戏数据库并导入源码包中自带的SQL脚本。一般的脚本路径是sql/目录下的db.sql、account.sql、game.sql之类写法因版本而异。经常见到的是一个大而全的脚本导入方式如下mysql -uroot -p create database saac default charset utf8; create database db default charset utf8; create database game default charset utf8; exit; mysql -uroot -p saac sql/account.sql mysql -uroot -p db sql/db.sql mysql -uroot -p game sql/game.sql如果你的脚本文件开头有CREATE DATABASE和USE语句也可以直接全量导入不用手动建库。但不管哪种方式记住一个原则导入的顺序不要乱因为表之间有外键关联比如角色表引用了账号表的ID。正常顺序是account脚本先建账号相关表db脚本建日志/封禁表game脚本建角色、物品、宠物等数据表。数据库建好后还要确认服务端的数据库连接配置。游戏服务端的数据库账号通常在share/db_config.h或者每个进程目录下的config.ini里配置。DB_HOST127.0.0.1 DB_USERroot DB_PASS你的密码 DB_NAMEdb这块代码里默认的数据库账号密码一般是root和空密码或者test/test。你需要改成自己实际环境的值。很多朋友编译成功了但gmsv一启动就提示连不上数据库原因十有八九就是这层配置没改对。5. 常见问题与排查实录5.1 make时报错“mysql.h: No such file or directory”这是最高频的错误没有之一。正常情况下系统在/usr/include/mysql/目录下应该有mysql.h。检查一下ls /usr/include/mysql/mysql.h如果没有说明mysql-devel或mariadb-devel没安装或者装了但路径不对。可以用find /usr -name mysql.h 2/dev/null找一下实际路径然后修改Makefile里的-I参数指向这个路径。还有一个变种问题是mysql.h存在但编译报my_config.h: No such file or directory。这是因为老代码用的是旧版MySQL的头文件结构新版MySQL把很多定义拆分到了不同头文件。解决办法是检查MySQL版本如果是MySQL 8.x/MariaDB 10.x头文件目录下确实找不到my_config.h了建议装一个MySQL 5.6/5.7开发包或者修改代码把#include my_config.h这一行注释掉一般不影响编译通过。5.2 编译中报undefined reference to pthread_mutex_lock或别的符号找不到链接阶段报符号未定义看起来吓人本质上是Makefile里的连接库参数顺序不对或者缺库。老代码大量使用多线程链接时必须把-lpthread放在所有 .o 文件后面。为什么因为GCC的链接顺序是按依赖关系组织的文件A依赖库BB就要写在A后面。写反了就会出现符号找不到的报错——哪怕库确实装了。同理-lmysqlclient也要放在源文件列表之后。这个方法可以根据MySQL安装信息动态生成最稳妥的方式是在Makefile里加一行MYSQL_LIBS : $(shell mysql_config --libs)然后链接时把$(MYSQL_LIBS)放在最后。mysql_config --libs会输出类似-L/usr/lib64/mysql -lmysqlclient的结果自动适配路径比自己手写要可靠。调完这一条至少能解决八成undefined reference问题。5.3 32位编译报错“bits/libc-header-start.h: No such file”这个错误几乎百分百是32位编译环境没装完整。执行yum install -y glibc-devel.i686 libstdc-devel.i686如果还是报错再补libgcc.i686和ncurses-devel.i686。装完后重新编译即可。这里插一句如果你实在不想折腾32位环境也有一个方案——不加-m32在64位模式下编译。但你需要做好心理准备即使编译通过服务端运行一段时间后可能出现一些“玄学”问题比如商会物品价格显示错误、战斗伤害计算不对、宠物数值溢出。这些都是因为老代码在某些地方把指针强转成int存储在32位下正好4字节64位下就截断了。这种bug排查起来极度痛苦。所以我的建议还是老老实实用-m32一劳永逸。5.4 gmsv启动后秒退或提示“can not connect to db”在数据库配置正确的前提下最常见的问题是SELinux拦截。CentOS默认开启了SELinux会阻止服务端进程访问网络端口。排查方式getenforce如果输出Enforcing临时关闭setenforce 0启动gmsv试试如果能正常运行改配置文件持久化关闭。修改/etc/selinux/config把SELINUXenforcing改成SELINUXdisabled重启后生效。这一步对新手特别不友好因为报错信息根本不会告诉你和SELinux有关只会说连不上。我当年排查这个用了整整一个晚上。另外CentOS 7的firewalld防火墙默认只放行22端口记得开放服务端需要的端口段石器时代服务端一般用TCP 9065端口用于客户端连接还有MySQL的3306内网访问。简单起见直接关掉防火墙systemctl stop firewalld systemctl disable firewalld这仅用于本地或内网实验环境如果是在有安全要求的生产/公网环境请按需开放端口而不是直接关闭防火墙。5.5 运行时乱码或中文显示异常石器时代源码在流转过程中注释和部分字符串是简体中文GBK编码而CentOS默认locale是UTF-8。编译时如果字符串常量中包含中文字符可能编译告警但真正常见的问题是服务端日志输出中文乱码游戏内NPC对话乱码。处理方法分两层第一层确保MySQL数据库、表、字段的字符集和你导入的SQL脚本编码一致。我在4.3里要求用utf8建库但如果你的SQL脚本本身是GBK编码保存的导入时先把脚本转成UTF-8或者建库用charset gbk保证两头一致第二层服务端进程读取配置文件时如果代码里用了setlocale(LC_ALL, )那进程启动时的locale环境变量会影响中文处理可以在启动脚本里显式设置export LANGzh_CN.GBK或者直接不设置locale让程序按默认ASCII方式处理中文字符原样透传靠数据库层面保证正确显示。我实际操作中发现第二层在多数版本里不需要动第一层处理对就基本能避免乱码了。5.6 常见问题速查表现象直接原因快速解法mysql.h: No such file缺mysql/mariadb开发包安装对应devel包检查-I路径my_config.h缺失MySQL版本太新注释掉include行或换5.6/5.7开发包undefined reference链接库顺序错误把-lmysqlclient -lpthread移到.o文件后面bits/libc-header-start.h不存在缺32位glibc安装glibc-devel.i686gmsv连不上数据库配置/SELinux/防火墙检查配置setenforce 0关闭firewalld中文乱码字符集不一致统一SQL脚本和数据库字符集编译警告特别多gcc版本差异属正常现象非致命警告可忽略make: g: Command not found只装了gcc没装gyum install -y gcc-c运行时提示缺libmysqlclient.so.X动态库路径不在ldconfig缓存给/etc/ld.so.conf.d/加mysql路径跑ldconfig6. 按顺序跑的完整编译流程参考考虑到文字描述比较长这里给你一条龙的命令清单。假设你已经在CentOS 7 x86_64下完成系统最小化安装登录后按顺序执行# 1. 网络与基础工具 vi /etc/sysconfig/network-scripts/ifcfg-ens33 # 把ONBOOT改成yes reboot # 2. 基础依赖 yum install -y wget vim unzip zip lrzsz yum groupinstall -y Development Tools yum install -y zlib zlib-devel readline readline-devel openssl openssl-devel yum install -y mariadb-devel glibc-devel.i686 libstdc-devel.i686 # 3. 启动数据库并初始化 systemctl start mariadb systemctl enable mariadb # 4. 上传源码并解压假设源码包是source.tar.gz tar zxvf source.tar.gz cd source # 5. 按顺序编译各进程 cd saac make clean make cp saac ../bin/ cd .. cd db make clean make cp db ../bin/ cd .. cd ac make clean make cp ac ../bin/ cd .. cd gmsv make clean make cp gmsv ../bin/ cd .. # 6. 导入数据库脚本按实际情况调整路径 mysql -uroot -p sql/init_all.sql # 7. 关闭SELinux和防火墙 setenforce 0 systemctl stop firewalld systemctl disable firewalld # 8. 按顺序启动服务端进程 cd bin ./saac ./db ./ac ./gmsv 如果每一步都顺利现在打开客户端的登录器配置文件把服务器IP指向你虚拟机的IP端口按服务端实际配置填写就能看到经典的登录界面了。启动顺序也有讲究为什么要先saac再db再ac最后gmsv因为gmsv启动时会去连接db和ac的服务端口如果上游服务没起来它会报连接失败然后退出。虽然很多版本gmsv带自动重连逻辑但保险起见还是按顺序来。停止服务时顺序反过来先停gmsv再停ac再停db最后saac。7. 一些备注和我的实操体会最后分享几个只有实际折腾过才会知道的细节帮你少走弯路。第一虚拟机内存建议给足2GB以上。gmsv编译时非常吃内存尤其是链接阶段多个大目标文件同时存在时内存占用轻松超过1GB内存不足时make会进程被杀掉表现为“Killed”或者直接没有报错就退出。如果宿主机配置允许给4GB会更舒服。编译完运行阶段其实内存占用不高但编译阶段是真的猛。第二建议保留一个干净的虚拟机快照。编译环境搭建的完整成本其实挺高一旦成功跑起来马上做一个快照snapshot后面不管改坏什么配置、删错文件都能秒回滚。快照这个习惯在做数据库导入测试、服务端配置调优时尤其有用。第三不要用最新的MySQL 8.x来做数据库。老代码用的SQL语法和MySQL 8默认的认证方式caching_sha2_password完全不兼容会报认证插件错误。CentOS 7默认的MariaDB 5.5/10.x是经过验证完全能跑的版本不要自己去升级数据库大版本。第四代码里那些带#ifdef _WIN32的宏定义分支在Linux下编译时会被预处理器自动跳过不用管。但如果某些文件本身是Windows风格.cpp文件头引用了windows.h需要手动把include那一行注释掉或加#ifdef _WIN32包起来否则必然编译失败。第五服务端跑起来后验证状态可以用ps aux | grep查看进程是否常驻也可以看各进程生成的log目录下的日志文件。gmsv启动时会输出地图加载、NPC初始化、怪物刷新等大量信息如果日志里能看到“Server Start OK”或者类似字样说明主服务已经正常起来了。数据库层面可以通过mysql -e select * from game.character limit 5验证建号后数据是否落库。石器时代源代码的编译过程说难也难说简单也简单——本质上就是一套老掉牙的C/C代码在Linux上的常规编译流程只要环境匹配、依赖齐全几十个进程文件编译完也就几分钟的事。真正的门槛在于你能不能接受用“老方式”思考问题不要把现代工程的习惯直接套用上去。先把环境当古董伺候再把它当成一个研究样本去理解整个过程会顺畅很多。