第一次把 Agent2D 的源码编译出可执行文件、然后看着 11 个自带编号的小球员在同一台机器上连进 rcssserver 时,我才真正理解 RoboCup 仿真2D 的“球队”不过是一堆遵守同一套通信协议的进程。很多新手卡在这个环节:教程只说到“下载源码”“编译”,却没解释为什么要先编 librcsc、为什么 configure 老提示缺库、为什么明明编译成功却连不上服务器。这篇笔记是我自己在踩过一轮坑之后整理的完整流程,覆盖从源码到上场的全链路:编译原理层面的理解、Linux 下常用构建工具的使用、上场必须知道的 server-client 关系,以及一份可以直接照抄的自动开赛脚本。适合已经装好 Ubuntu、了解 rcssserver 基本概念、但还没把任何一支球队代码跑起来的同学。看完你应该能做到三件事:独立编译 agent2d 系列代码,让 11 个 agent 连进服务器,并在 monitor 里看到一场真实的仿真比赛。
1. 编译与上场到底在解决什么问题
1.1 为什么球队代码不是“双击就能跑”
很多第一次接触 RoboCup 仿真2D 的人会下意识把它理解成一个游戏:下载个客户端,点开就能踢球。但实际不是这样。参赛队伍本质上是一个个自主决策的程序,程序用 C++ 等语言写成,经过编译后生成 Linux 可执行文件;比赛服务器 rcssserver 只认这些可执行文件通过标准 socket 发来的指令,它不会直接解读你的源码。
所以“编译”是横在源码和比赛之间的一道必要工序。它把几百个 .cpp、.h 文件变成机器可执行的二进制,这个二进制才是你球队真正的“本体”。编译不好,后面所有工作都无从谈起。我见过不少人卡在“编译通过但上场失败”的中间地带,代码确实生成了可执行文件,但因为依赖库没装对、链接阶段出错,程序一跑就崩,或者压根无法和服务器建立连接。因此第一篇笔记往往讲环境准备,第二篇必须把编译和上场这条主线打通。
1.2 “上场”本质上是网络连接
“上场”这个词听起来像体育比赛里的换人,但在仿真2D里,它其实是一次标准的网络通信过程。比赛服务器启动后会监听一个 TCP 端口,默认是 6000。你的球队代码运行后,每个球员都是一个独立的客户端进程,它们需要主动连接到这个端口,然后用约定的协议完成注册:告诉服务器自己属于哪个队、是几号球员。
也就是说,球队代码编译完只是拿到了“入场券”,真正上场还需要做三件事:
- 启动 rcssserver 比赛服务器,让它进入等待状态。
- 启动 11 个 agent 进程,设置好队伍名、服务器地址、端口和球员编号。
- 打开监控器(monitor),确认 11 个球员成功注册并进入场地。
理解这一点非常关键。因为很多“上场失败”的报错,比如Connection refused、Sorry, the server is full、球员连上又立刻退出,本质上都不是代码逻辑问题,而是网络连接、端口占用、球员编号冲突这些基础环节没做好。先把通信链路搞明白,再回去调战术,你才不会在错误的层面浪费时间。
1.3 从源码到一场比赛的完整链路
把整个流程画成一条线会清晰很多。我建议新手先对着这条链路把每个环节跑通一次,后面再逐步替换成自己的代码:
- 下载并解压 agent2d 源码(通常连带 librcsc 依赖库一起)。
- 编译 librcsc 静态库,并安装到系统目录。
- 编译 agent2d 主程序,生成可执行文件。
- 启动 rcssserver 服务器,指定端口号。
- 用脚本启动 11 个 agent 进程,并传入队伍名、地址、端口、编号参数。
- 打开 soccerwindow2 或 rcssmonitor,连接同一服务器观战。
- 比赛结束后查看生成的日志文件,用 logplayer 回放分析。
这篇文章会按这条链路一步步展开。源码可以在官方或教学社区下载,下面所有路径都以常见的 agent2d-3.1.1 加 librcsc-4.1.0 组合为例,不同版本目录结构可能略有差异,但核心思路一致。
2. 编译之前:环境、源码结构与编译原理
2.1 先把依赖装齐,别让环境拖后腿
在开始编译之前,我强烈建议先把系统依赖一次性装好。很多编译报错根本不是代码问题,而是缺了某个头文件或库。以 Ubuntu 20.04 / 22.04 为例,我用的是这一套:
sudo apt update sudo apt install build-essential autoconf automake libtool pkg-config sudo apt install libboost-all-dev libssl-dev libsdl2-dev qtbase5-dev sudo apt install rcssserver rcssmonitor简单说下每个包的作用。build-essential是必装的,它包含 gcc、g++、make 等基础编译工具,没有它你连 configure 这一步都跑不完。autoconf、automake、libtool是用来生成和运行 configure 脚本的,agent2d 和 librcsc 都依赖这套构建系统。pkg-config用来在 configure 阶段查找已安装的库,后面经常用到。libboost-all-dev是 agent2d 的硬依赖,代码里大量使用了 Boost 的智能指针、线程、随机数等组件。rcssserver和rcssmonitor分别是比赛服务器和官方监视器;如果你不想装监控器,也可以只装 rcssserver,然后用 agent2d 自带的 soccerwindow2。
这些包装完后,可以用g++ --version和make --version验证一下编译器环境。如果系统里同时存在多个 gcc 版本,最好确认一下默认版本,后面编译遇到奇怪报错时,版本因素要纳入排查范围。
2.2 看懂源码目录:agent2d 和 librcsc 是什么关系
下载并解压 agent2d 后,你会看到类似这样的目录结构:
agent2d-3.1.1/ ├── bin/ ├── src/ ├── config/ ├── librcsc-4.1.0/ ├── configure ├── Makefile.in └── ...其中src目录存放的是 agent2d 主程序源码,也就是球员决策逻辑。librcsc-4.1.0是另一个独立的源码库,全称是 RoboCup Soccer Simulation 基础库,封装了世界模型解析、底层动作接口、几何计算等通用功能。agent2d 的代码依赖 librcsc,就像一辆整车依赖发动机和底盘,你没法只编译车身就跑起来。
所以编译顺序必须是:先编译 librcsc,再编译 agent2d。librcsc 编译后会生成静态库文件(比如librcsc.a),agent2d 在链接阶段需要用到它。这里有个新手容易踩的坑:如果你先编译 agent2d,configure 会明确报错说找不到 librcsc,这是正常的,说明依赖顺序不对,不是你的系统坏了。
另外还要注意bin目录,编译成功后生成的可执行文件 agent2d、soccerwindow2 都会放在这里。很多教程默认你知道这个位置,但第一次找的人经常盯着src翻半天还找不到可执行文件在哪。
2.3 编译原理小课:configure、make、链接器到底在干嘛
市面上讲编译原理的书动辄几百页,但对我们编译球队代码来说,只需要建立三层心理模型。
第一层是 configure。它是发行版作者写好的一堆 shell 脚本,作用是在你的机器上“体检”:检查 g++ 版本、检查 Boost 库位置、检查 librcsc 是否已经安装,然后根据检测结果生成对应的 Makefile。所以每次换环境、换机器后,最稳妥的做法是重新跑一次 configure,而不是拿着旧 Makefile 硬编译。
第二层是 make。make 读取 Makefile 里的规则,把一个个.cpp源文件编译成.o目标文件。这个过程很像“一道菜分步骤做”:先把菜洗好、切好(编译),最后再下锅炒(链接)。源文件很多时,make -j4可以同时开 4 个任务,明显缩短等待时间。但第一次编译时,我建议先用单线程跑一遍,这样报错信息不会混在一起,容易定位问题。
第三层是链接。目标文件被链接器拼成一个可执行文件,同时把用到的外部库的二进制代码绑定进来。如果你看到undefined reference to 'xxx',说明某个函数声明了但没找到定义,大概率是链接时缺了某个库,或者库的链接顺序不对。常见做法是在 Makefile 的LIBS变量里加上对应的-l参数。这块知识看起来偏理论,但实际排查编译错误时特别管用。
把这三层搞明白,再遇到编译报错就不会像看天书了:configure 报错看环境,编译阶段报错看语法和头文件,链接阶段报错看库和符号。
3. 亲手把球队代码编译出来
3.1 先编译基础依赖库 librcsc
打开终端,进入 librcsc 源码目录,依次执行:
cd /path/to/agent2d-3.1.1/librcsc-4.1.0 ./configure make这一步会生成 librcsc 的静态库文件。如果一切顺利,你会在源码目录下看到src或对应目录里多了一个.a文件,比如librcsc.a。这个文件就是后面 agent2d 链接时需要的“原材料库”。
这里有个容易忽略的细节:agent2d 的 configure 脚本默认会通过pkg-config去系统路径寻找 librcsc,所以如果你只是编译了 librcsc,但没有安装到系统目录,下一步 configure agent2d 时它依然会提示找不到库。最简单的做法是接着输入:
sudo make install这条命令会把 librcsc 的头文件和库文件复制到/usr/local/include、/usr/local/lib等系统目录下,同时更新 pkg-config 的元数据文件。装完后最好执行一次:
sudo ldconfigldconfig用来刷新动态链接器的缓存,如果后面链接阶段报错说找不到共享库,八成是你忘了这一步。需要说明的是,这一步不是标准文档里一定会写的,而是我实际编译时踩过坑后发现的:很多教程只说“编译 librcsc”,没提 install,结果在编译 agent2d 时会被 configure 卡住半天,报错信息五花八门,特别劝退新手。
3.2 编译主程序 agent2d
基础库解决后,回到 agent2d 顶层目录,执行:
cd /path/to/agent2d-3.1.1 ./configure make如果没有报错,编译完成后检查bin目录:
ls -l bin/你会看到至少以下几个文件:agent2d、soccerwindow2,有的版本还会附带start.sh或test.sh。到这一步,你已经把球队代码从源码变成了可执行程序,这是从“零”到“能跑”最关键的一步。
如果 configure 阶段报错说找不到 librcsc,而你已经安装了 librcsc,但安装到了非默认前缀目录,就需要手动指定搜索路径。常见的做法是设置环境变量:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH然后重新跑 configure。这类环境变量问题的核心原因是系统默认找不到库的位置,把路径明确指给它即可。我要特别强调一个细节:不要看到 configure 报错就开始百度“如何卸载重装”,先想想这个库是不是已经装了、只是路径没对上。百分之九十的 configure 失败都是这个问题。
3.3 验证编译成果
编译成功的标准不是“没有报错”,而是可执行文件能正常响应。先跑一下帮助命令:
./bin/agent2d --help正常情况下会输出一长串启动参数,其中你会看到-h指定服务器主机、-p指定端口、-t指定队伍名、-n指定球员编号。这些参数稍后上场时会用到。同理,./bin/soccerwindow2 --help也可以用来验证监控器程序是否可用。
我在第一次编译时犯过一个低级错误:以为 make 结束看到提示符就完事了,结果直接运行./agent2d却发现根本没这个文件,原来可执行文件在bin目录下。这种目录习惯问题很常见,多花一分钟看一下生成物在哪里,比瞎猜命令要靠谱得多。
3.4 编译加速与编译期异常排查
编译 agent2d 这类项目,第一次全量编译大概要几分钟,和你在搜索引擎里看到的“keil5 编译很慢”等现象是同一个道理:编译器要逐个处理每个源文件,文件越多,耗时越长。如果你有信心环境没问题,可以直接用:
make -j4-j后面的数字是并行编译的任务数,一般取 CPU 核心数或核心数加一。我自己的机器是 8 核,用-j8能把编译时间压缩到一分钟左右。但注意,如果项目本身就依赖严格的编译顺序,并行反而会出错,遇到诡异问题就退回单线程看看报错。
如果你希望生成调试版本,方便之后用 gdb 跟踪,可以在 configure 时加上:
./configure --enable-debug或者在 make 时覆盖编译参数:
make CXXFLAGS="-O0 -g -std=c++11"这里的-O0禁止优化、-g生成调试信息、-std=c++11指定语言标准。后面调试球员异常崩溃时,调试版本比发布版本有用很多。需要提醒的是,修改了编译参数后,最好先make clean清掉旧的目标文件,再重新 make,否则可能出现新旧代码混用导致的诡异行为。
4. 上场:11 个球员怎么连上服务器
4.1 先把比赛服务器跑起来
编译工作结束后,进入上场环节。第一步是启动 rcssserver。打开一个终端窗口,输入:
rcssserver -p 6000 &-p参数指定服务器监听的端口,默认就是 6000,这里显式写出来是为了让你知道有这个参数。加&让它后台运行,这样你还能在同一终端里继续输命令,对于新手特别方便。
启动后,最好确认一下端口确实在监听。我习惯用:
netstat -tlnp | grep 6000或者用ss -tlnp | grep 6000。如果看不到监听记录,说明服务器没起来,或者端口被其他程序占用了。可以用ps aux | grep rcssserver确认进程状态。
服务器启动后不会立刻开踢,它处于“等待客户端连接”的状态。接下来要让你的球队连进去。这一步我用了一个很形象的比喻:服务器就是足球场的大门,agent 就是排队进场的球员,每个球员报到时要说清自己是哪个队、几号。
4.2 手动一条条启动 agent:先跑明白再谈自动化
先用手动方式启动第一个球员,理解每个参数的含义:
./bin/agent2d -t myteam -h 127.0.0.1 -p 6000 -n 1解释一下这些参数:
-t myteam:队伍名。同一支球队的 11 个球员必须使用相同的队伍名,服务器根据队伍名区分对阵双方。-h 127.0.0.1:服务器地址。本地比赛直接填本机回环地址即可,如果服务器在另一台机器上,要填那台机器的 IP。-p 6000:服务器端口,必须和 rcssserver 启动时一致。-n 1:球员编号,范围一般是 1 到 11。服务器会根据编号分配初始站位,比如 1 号通常是守门员位置。
手动启动 1 个球员后,打开 monitor 看看他是否在场上。接着用同样的命令依次启动 2 号、3 号……直到 11 号。但你会立刻发现手动方式太繁琐了,而且每个终端被进程占着很不方便。所以对上场流程熟悉后,就要切换到脚本方式。
补充一个常见错误:如果你启动两个同编号球员,后一个通常会失败或把前一个挤掉,因为服务器不允许相同队伍、相同编号的两个球员同时注册。新手用脚本循环时很容易因为循环变量问题反复启动同一个编号,排查时第一个怀疑对象就是它。
4.3 自动开赛脚本:一分钟让11个球员全部上场
这是我个人非常推荐的做法。写一个简单的 shell 脚本,一次性启动 11 个 agent,同时把每个进程的输出日志分别保存下来,方便之后排查问题。脚本内容如下:
#!/bin/bash TEAM="myteam" SERVER_HOST="127.0.0.1" PORT=6000 AGENT_BIN="./bin/agent2d" LOG_DIR="/tmp/myteam_logs" mkdir -p "$LOG_DIR" for i in $(seq 1 11); do "$AGENT_BIN" -t "$TEAM" -h "$SERVER_HOST" -p "$PORT" -n "$i" \ > "$LOG_DIR/agent_${i}.log" 2>&1 & sleep 0.2 done echo "11 agents launched. PID of this script: $$"脚本逻辑很简单:从 1 循环到 11,每个编号启动一个 agent 进程,标准输出和错误输出都重定向到独立的日志文件。sleep 0.2是为了避免 11 个进程同时请求连接给服务器造成压力,实际测试下来这个间隔比较合适。
停止比赛时,可以用:
pkill -f agent2d或者精确一点,先ps aux | grep agent2d找到进程号再kill。我建议用精确方式,避免误杀其他无关进程。
agent2d 源码里通常也自带了类似的启动脚本,比如bin/start.sh,不同版本内容会有些差异。自己手写一遍脚本的好处是你能真正理解服务器、客户端、参数之间的对应关系,后面换代码、换队伍名、调整端口时都能自己改,而不是依赖别人的脚本。
4.4 用 monitor 看实时比赛,再用日志复盘
球员全部连接成功后,就可以打开监控器看比赛了。如果你的 agent2d 编译成功,可以直接运行自带的:
./bin/soccerwindow2它启动后会通过图形界面让你选择连接哪个服务器、端口,也可以直接传参指定:
./bin/soccerwindow2 -h 127.0.0.1 -p 6000如果你安装的是官方 rcssmonitor,用法也是类似的。连接成功后,你会看到场地上的蓝色小圆点,那是你的球员;如果对手也已经连接,会看到另一个颜色的圆点。此时服务器正常发球、计时,一场仿真比赛就真正跑起来了。
比赛结束后,rcssserver 一般会在运行目录下生成日志文件,比如.rcg和.rcl,分别记录比赛过程和通信命令。用 agent2d 自带的 rcsslogplayer 可以回放这些日志,方便你复盘某个回合的决策。刚开始不要求看得很细,但至少要知道日志文件存在,后面调队形、调策略时会频繁用到。
5. 常见问题与排错技巧
5.1 编译环节问题速查表
这一节把我见过的编译错误整理成速查表,遇到类似报错可以直接按表格排查。很多报错看着不一样,根因其实是同一个。
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
configure: error: C++ compiler cannot create executables | g++ 没装全或版本异常 | 重新安装 build-essential,检查 g++ 版本 |
fatal error: boost/xxx.hpp: No such file or directory | 缺少 Boost 头文件 | 安装 libboost-all-dev |
configure: error: could not find librcsc | librcsc 未编译安装或 pkg-config 路径不对 | 先编译 librcsc 并 sudo make install,再设置 PKG_CONFIG_PATH |
undefined reference to 'xxx' | 链接阶段找不到某个函数定义 | 检查 Makefile 的 LIBS,确认对应库已安装且链接顺序正确 |
make: g++: No such file or directory | 编译器未安装,或 PATH 环境变量异常 | 安装 build-essential,检查 g++ 是否可用 |
std::xxx has not been declared | 编译器默认标准太老 | 在 CXXFLAGS 中加 -std=c++11 或更高版本 |
其中“undefined reference”是我见过最劝退新手的错误。它发生在编译后期,前面的源文件都顺利通过了,只有链接时符号对不上。解决方案通常是检查 Makefile 里链接库的名字和顺序。GNU 链接器对静态库顺序很敏感,被依赖的库要放在依赖方的后面。agent2d 这种情况,-lrcsc一般要放在源文件之后。
5.2 上场连接问题速查表
编译通过了,不代表能上场。连接阶段的问题更细碎,单独列一张表:
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
Connection refused | 服务器没启动,或端口填写错误 | 用 netstat 确认 rcssserver 监听端口,核对 -p 参数 |
| 球员启动后立刻退出,无输出 | 参数缺失或无效 | 在终端前台运行单个 agent,观察直接输出 |
| 同编号球员被踢下线 | 相同 team 下重复注册同一编号 | 检查脚本循环变量,确保 1-11 只启动一次 |
| monitor 里看不到球员 | monitor 连接的服务器端口不对,或球员没注册成功 | 确认 monitor、agent、server 三者都指向同一个 ip:port |
| 比赛没开始,一直等待开球 | 双方球员数量不足,或服务器等待 kick_off 指令 | 确认对阵双方总球员数足够,检查日志是否出现 kick_off |
看到Connection refused时,第一反应不要猜测是否有代理或防火墙(也不要在这类问题上纠缠),先确认最基础的链路:服务器进程在不在、端口是否监听、地址是否写对。我试过在服务器还没启动时就运行 agent,结果一片红字,当时还以为是代码坏了,后来冷静下来才想起 server 根本没开。
5.3 实用调试三板斧:日志、gdb、抓包思路
上场遇到问题,定位手段通常有三层。
第一层是日志。我在脚本里已经把每个 agent 的输出重定向到独立日志文件。启动后先打开这些日志看,很多问题会直接暴露,比如连接失败原因、某个参数不合法、甚至球员在哪个决策阶段发生了异常。这一点非常省时间。
第二层是 gdb。如果程序启动后直接段错误(Segmentation fault),可以用调试版本重新编译,然后运行:
gdb ./bin/agent2d run -t myteam -h 127.0.0.1 -p 6000 -n 1segfault 发生时,gdb 会停在崩溃现场,输入bt查看调用栈,就能快速定位到哪一行代码出了问题。第一次看到bt输出的一堆函数调用可能有点懵,但找最靠上的自己项目里的函数,基本就是崩溃发生的源头。
第三层是网络层面的观察。如果确认程序没崩、但服务器就是没收到注册请求,可以看 agent 的日志,也可以用 tcpdump 或 wireshark 抓包,观察 6000 端口上的数据包交互。不过 99% 的新手问题不需要走到抓包这一步,大多是参数、端口、编号这些基础配置问题。
写在最后
按照这条链路完整跑通一次之后,你就算是真正踏进了 RoboCup 仿真2D 的大门。我自己在刚开始时也经历过编译失败、连不上服务器、打开 monitor 一片空白的阶段,后来发现这些问题很少来自高深的逻辑,反而都败在“先编哪个库”“参数怎么填”“端口对没对上”这类基础细节上。个人经验是,把编译和上场这套流程固定成自己的标准操作,每次换新代码、换新机器都能十几分钟搞定,后面才有时间去研究队形、策略和行为决策。编译这块还有一个小习惯值得养成:每次修改了关键代码后,尽量先make clean再重新编译,不要为了省时间跳过,很多“代码明明改了但行为没变”的诡异问题,根源就是增量编译留下了旧产物。现在先把这 11 个球员跑起来,下一步就可以开开心心地调战术了。