docker 搭建 EmuLinker-K 街机联机服务器
📝 笔记摘要:docker 搭建 EmuLinker-K 街机联机服务器
街机模拟器一直缺一个能联机的服务端。EmuLinker-K 是 Kaillera 协议的现代实现——协议本身是 1990 年代末的老东西,但服务端已经用 Kotlin 重写过,仓库里带 Dockerfile,一条 docker compose up -d 就能跑起来。
它和 docker 搭建 EmulatorJS 模拟器 是两回事:那个在浏览器里跑、单人玩;这个是独立服务器、给人联机。文章按「它到底怎么联机的 → 端口怎么开 → 中文怎么配 → 怎么管人」的顺序走一遍。
1. 它转发的不是画面,是按键
这是理解整个技术的前提。画面和声音始终在各自机器上本地渲染,服务器只转发输入包。
服务器不校验也不修复画面
它只做一件事:把一个人的按键转发给其他人
两边跑的是各自本地的模拟器,服务器从头到尾没见过画面
同步的前提是两边完全一致
模拟器版本要一样,ROM 要一样,存档也要一样
跨版本、跨平台、随机种子不同的 ROM 基本同步不了
延迟怎么来的
服务器按每人各自的延迟档位提前把按键发给延迟更高的人
延迟差越极端,这个提前量越大,同步越容易崩
协议老但不新
Kaillera 协议是 1990 年代末的东西,2026 年仍在用
模拟器端的插件都是现成的,所以服务端可以随便重写——协议没变,实现换了
2. Docker 部署
仓库根目录自带 Dockerfile 和 docker-compose.yaml,clone 下来直接起:
git clone https://github.com/hopskipnfall/EmuLinker-K.git
cd EmuLinker-K
docker compose up -d
docker compose logs -f
官方 compose 文件是这么写的:
services:
emulinker-k:
build: .
ports:
- "27888:27888/udp"
restart: unless-stopped
# volumes:
# - ./conf:/home/emulinker/conf
没有官方镜像,要本地 build
Dockerfile 是多阶段构建:先用eclipse-temurin:17-jdk跑 Gradle 打包,
再拷到eclipse-temurin:17-jre上运行
首次 build 要拉依赖,几分钟起步,属正常
不用 compose 也行
docker build -t emulinker-k .
docker run -d --name elk -p 27888:27888/udp --restart unless-stopped emulinker-k
容器里的 Java 是自带的
宿主机不需要装 Java,镜像里已经带了 17
很多老的 Kaillera 服务要求手动装 J2SE 5.0,那套在今天早就不适用了
不用 root 跑
镜像里建了emulinker用户,uid 10001,USER指令已经切过去了
堆内存被限死在 256 MB
启动参数是-Xms64m -Xmx256m -XX:+UseSerialGC
这个服务是按单核低端机调校的,空载占用很小,不用改
3. 端口:只要一个 UDP
防火墙这一步比想象的简单,但和老教程差别很大。
只放行 27888/udp
不需要开 TCP,服务端根本不监听
也不需要像老版本那样放行一整段端口
老版本为什么那么麻烦
旧设计靠端口号区分用户:主端口 27888,
每个在线用户从 27889 起再分一个私有端口
默认server.maxUsers=100,加上管理员预留 10 个,
所以要放行 27888 到 27988 整段,只开 27888 的话第二个用户就进不来
新版为什么简化了
改用协议里的标识字段区分用户,端口不再是容量瓶颈
对应的server.maxUsers默认值从100变成0,
0 是不限人数,不是禁止登录——这个 0 很容易被误读
云服务器还要开两处
云厂商的安全组和系统内部的防火墙都得放行,只开一个照样不通
排错顺序
日志里已经有连接记录但客户端连不上,说明包根本没进到服务器,
问题在网络层不在服务本身:先查安全组,再查系统防火墙,最后才回头看配置
4. 中文配置
这一节对中文服主来说是最实用的,不改的话玩家进去看到的是问号。
# conf/emulinker.cfg
emulinker.charset=GBK
emulinker.language=zh
UTF-8 一定不行
模拟器端的 Kaillera 插件普遍不支持 UTF-8,客户端侧就绕不过去
简体中文用GBK,日文Shift_JIS,韩文x-IBM949
英文西欧Windows-1252,西里尔文Windows-1251
language 不填会怎样
默认是custom,意思是所有提示都走你自己的 language.properties
新版把中文、俄语、西语、葡语、日语、韩语直接编译进二进制,
设成zh就直接有中文提示,不用翻文件
老版本默认字符集是西里尔文
旧版emulinker.cfg里默认写的是Cp1251
没手动改过的中文服务器,玩家看到的提示全是乱码
而且旧版只有一份language.properties,中文得自己一条条翻
怎么验证生效了
拿模拟器插件连一次,看大厅欢迎语是不是正常中文
显示成?或方块 字符集不对
显示成正常英文 语言没设成zh,或者被custom覆盖了
language.properties 现在写什么
主要写自定义欢迎语,不再承担翻译全部提示的活
键名形如KailleraServerImpl.LoginMessage.1
5. 配置的生效方式
access.cfg 是热重载的
文件被监控,手工编辑也会自动生效,不需要重启
封号、解封、禁言全部走这个文件
emulinker.cfg 只在启动时读
端口、字符集、人数上限改了都要重启容器
docker compose restart emulinker-k
把配置挂出来
官方 compose 里那段 volumes 是注释掉的,要自己打开
volumes:
- ./conf:/home/emulinker/conf
这样改配置、查封禁记录都在宿主机做,down也不丢
大厅里直接敲管理命令
/ban/silenceUserID + 分钟数,临时
/permaban/permamuteUserID,永久
/info/clear查状态 / 一律解除
只在服务器大厅聊天框里输入,游戏内不生效
权限不够时命令是静默的
/ban/silence需要 Moderator,
/permaban/permamute需要 Admin
权限不够不报错也不执行,看起来像命令拼错了
原因参数只写进日志,不会告诉被处理的人,防止绕过审核
老配置别直接搬
旧版里的game.timeoutMillis、game.desynchTimeouts已被移除,
部分键的默认值也变了,以新版自带的 conf 为准
6. 常用配置项
controllers.connect.port 默认
27888,改端口这里
controllers.v086.clientTypes.clientType 接受的协议,逗号分隔,默认v086
server.maxUsers 同时在线人数,0为不限
server.maxPing 允许的最大往返延迟毫秒,默认1000,不要超过 1000
server.allowedConnectionTypes 默认1,2,3,4,5,6全开,
1=LAN 到6=Bad,能限档就在这里限
server.allowSinglePlayer 默认TRUE,
设成FALSE就必须两人以上才能开局,防刷房间
server.idleTimeout 默认3600秒不操作踢人,0关闭
server.maxGames 同时能开几个房间,0不限
masterList.touchEmulinker / touchKaillera 默认FALSE,
设成TRUE才会出现在公开服务器列表里
masterList.serverName 列表里显示的名字,默认「Not Set」
metrics.enabled 默认FALSE,打开后输出服务器健康指标
7. 不装 Docker 的装法
# Linux / macOS:一条脚本,同时也是升级命令
curl -fsSL https://raw.githubusercontent.com/hopskipnfall/EmuLinker-K/master/release/setup.sh | bash
# 已有服务要升级,就在 EmuLinker-K 目录里再跑一遍同样的命令
需要 Java 17 或更高
这条命令只下载和配置,不装 Java,缺了会直接失败
脚本是交互式的
能装 beta 版本,也能指定版本升级或降级
Windows 走 zip
去 Releases 页下最新的.zip,解压后跑start-server.bat
脚本名字变了
新版的start-server.sh/stop-server.sh会检查进程状态、显示输出、检测更新
老的server.sh和kill-server.sh已经删掉了
停止服务
./stop-server.sh正常停,会走清理流程
Windows 上直接 Ctrl+C 或关窗口
💡 补充几个容易忽略的:
为什么服务端能随便重写
所有扩展都向后兼容,老客户端行为完全不变,不用升级任何客户端
这是重写能成立的前提,理论上你也能用别的实现替换它
还有个更轻的实现
direlera-rs是 Rust 重写版,实测内存 15 MB 对 ELK 的 247 MB,快一个量级
但仍在实验期,且当前只接受 LAN 连接类型,公网联机要等它放开
TCP 一个都别开
服务端只监听 UDP,开错协议的表现是客户端一直转圈直到超时
带宽要求比想象低
既然不传画面,服务器只跑按键包,一个千兆带宽的 VPS 同时带几十人没问题
同步崩了多半不是服务器的问题
先查两边模拟器版本是否一致、ROM 是否同一个文件
再查延迟档位是不是被拉到6=Bad了
客户端要连哪个地址
插件里填服务器公网 IP 或域名,端口默认 27888
服务器自己那台机器的客户端要用内网地址绕回去,不要用公网 IP
日志在哪
容器日志用docker compose logs -f
新版可选metrics.enabled输出聚合指标,比翻日志快
服务器名称记得改
默认是「Not Set」,出现在列表里很难看,masterList.serverName改掉
🔥 一句话总结:EmuLinker-K 靠 Kaillera 协议做街机联机,服务器只转发按键不碰画面;部署就是
git clone之后docker compose up -d,容器自带 Java 17 不用在宿主机装;防火墙只放行27888/udp一个端口(老版本要放行 27888–27988 整段),TCP 完全不用开;中文服主必须在emulinker.cfg里改emulinker.charset=GBK和emulinker.language=zh,不然玩家看到的是乱码;access.cfg支持热重载,封号禁言靠大厅里的/permaban和/permamute直接写进文件;升级在EmuLinker-K目录里重跑一次setup.sh就行。
