《MMO 服务器开发》1.2 本书范围与站内分工

本站已有 170 篇游戏服务器实战文、110 篇月度归档、17 篇 Skynet 与 3 篇 Pitaya,外加一本只写了前 3 章的《游戏服务端编程实践》。本节诚实盘点这些内容的体量,用一张四重区隔表说清本书和它们的边界,并把边界编码成可运行的路由表。

1.2 本书范围与站内分工

上一节画了四层图,这一节要回答一个更不好回答的问题:这本书和站内已经存在的那一大批游戏服务器内容,到底谁写什么?

之所以要专门花一节讲这件事,是因为本站的游戏服务器内容已经不算少:一个 170 篇的实战专题、十个多月的月度归档、两个框架专题,外加一本写到一半的书稿。如果本书不把自己的边界说清楚,读者会在「架构方案」和「从零实现」之间反复横跳,作者也会不自觉地把别人已经写透的内容再抄一遍。这一节的立场很朴素:别人讲过的「是什么、为什么、怎么选」,我们全部外链出去;本书只补别人没补的那一块——「从零写出来,并且跑出数字」。

这不是自夸。恰恰相反,下面这份盘点会诚实地告诉你:站内既有内容在很多方面比本书更完整。本书的价值不在于「更多」,而在于「另一种」——它提供的不是更多的架构综述,而是一份能编译、能跑、能压出数字的代码。

站内已经有什么:一次体量盘点

先老老实实把站内既有的游戏服务器内容盘一遍。下表按目录统计(用 os.listdir 数落盘的 .md,不含各目录自身的 _index.md):

内容位置体量形态
游戏服务器实战专题/posts/game/server/practice/170 篇纯架构设计文,零可运行代码
月度归档 2021.02–2021.12/posts/game/server/ 下 11 个月度目录110 篇按月的架构设计文,同属「方案型」
Skynet 框架专题/posts/game/server/skynet/17 篇Lua 框架用法与最佳实践
Pitaya 框架专题/posts/game/server/pitaya/3 篇框架介绍与框架对比
《游戏服务端编程实践》/posts/books/游戏服务端编程实践/21 章大纲,实际只落盘第 1–3 章书稿,正文偏概念与综述

这张表有两个数字值得单独点出来。

第一,实战专题的 170 篇里,一篇可运行的代码都没有。我逐篇扫过 content/posts/game/server/practice/ 下全部 170 个文件,go / java / python / c / rust / lua 等任何语言的代码围栏出现次数是 0。它们全是「架构方案与取舍」型文章——告诉你网关该怎么切、结算该怎么防重复发奖、合服该怎么迁移——但不会给你一份能 go test 通过的实现。这不是缺陷,是定位:它们的读者是来「看方案、做评审」的。

第二,《游戏服务端编程实践》的 21 章大纲里,正文只落盘了第 1–3 章。它的目录篇列了完整的 21 章(从「游戏服务器的定义与职责」一直到「未来方向与生态」),但真正写出来的只有入门篇的三章,且以概念与综述为主。换句话说,站内确实有一本「游戏服务端」的书稿,但它离「完整、可运行」还有很长一段路。

把这两条合起来看,结论很清楚:站内在「架构方案」这一维度上已经非常厚,但在「从零实现 + 可复现数字」这一维度上几乎是空的。本书要补的正是后者。

四重区隔:本书和它们的四条不同

盘完存量,再说本书和它们的差异。差异不止一条,而是四重——形态、技术栈、验收、游戏。四重都不同,才说明本书不是既有内容的又一次重复:

维度站内既有内容本书《网络与并发内核》
形态架构方案与取舍:「应该怎么设计」从零实现:「自己写出来、跑通、量出来」
技术栈多语言综述(Java / Go / Rust / Lua / C++ 混着讲)Go 主线(框架名 aegis)+ Java 对照
验收无:没有端到端断言,没有压测数字端到端断言 + 本机真跑的压测数字
游戏SLG 异步弱实时(收菜、建筑、异步战斗)MMO 同屏强实时(移动、AOI、广播)

逐条解释这四重:

  • 形态。既有文章回答「应该怎么设计」,本书回答「怎么写出来」。同样讲网关,既有文章给你一张部署拓扑图和一个取舍清单;本书给你一份能编译的网关代码和一条 go test 命令。这两件事不冲突,但也不能互相替代。
  • 技术栈。既有内容是多语言综述——今天看 Java 的线程池,明天看 Rust 的 async,后天看 Lua 的 Skynet。本书收窄到一条主线:Go(框架名 aegis),并在关键处用 Java 做双栈对照。收窄是为了「能跑通」——一门语言写到底,才有连贯的可运行产物。
  • 验收。这是最硬的一条区隔。既有文章不需要产出任何数字;本书每一节都要么有 go test 断言,要么有本机实测的压测数字(且明确标注「同机相对量级,绝对数值不可移植」)。没有数字的实现,在本书里不算完成。
  • 游戏。既有内容默认的语境是 SLG——异步、弱实时、状态变更慢,一次收菜晚 200 毫秒没人会死。本书默认的语境是 MMO——同屏几百人、移动每帧都在变、广播必须在一帧内发出去。同样的「状态同步」,在 SLG 和 MMO 里是两套完全不同的难度,本书只讲后者。

一句话总结:「为什么、怎么选」看既有文章,「怎么写、跑出什么数字」看本书。 这条线划得干净,读者和作者都省事。

把这条线落到具体话题,就是下面这张「不写什么、去哪里看」的清单。左列是本书不展开的话题,中列一句话交代为什么,右列给出站内既有内容:

本书不展开的话题一句话结论去哪里看
网关部署拓扑与选型属于方案层,与实现无关/posts/game/server/practice/
结算防重复发奖属于业务方案,非网络内核/posts/game/server/practice/
合服与跨服数据迁移属于运维与数据方案/posts/game/server/practice/
框架与商业引擎怎么选属于项目决策/posts/game/server/pitaya/
Skynet 框架的具体用法属于框架 API,非从零实现/posts/game/server/skynet/
游戏服务端的概念总论属于综述,既有书稿已覆盖/posts/books/游戏服务端编程实践/

有人会问:既然站内已经写了 170 篇,为什么还要自己实现一遍?因为读得懂和写得出是两种能力。看懂一篇「网关该怎么分层」的方案文,和亲手处理长连接的会话表、心跳超时、断线重连票据、消息乱序,中间隔着一整个调试周期。本书要补的正是这一段。

最后交代一下阅读方式。本书是一条依赖链:第 1 章立边界,第 2–4 章把网络 I/O、协议、编解码从零写出来,第 5 章补可靠 UDP,第 6–11 章做接入、会话、路由、定时器、Actor、幂等,第 12–16 章进入同步、预测、时间、权威、安全,第 17 章把前 16 章拼成 aegis 并压出第一组承载数字,第 18 章回顾。建议顺序读——跳读的代价不是看不懂某一章,而是写到第 12 章时发现第 3 章的长度前缀留了坑,得回头重做。想只当手册查也可以,但要跑通代码仍建议先读前几章。

可运行产物:把边界编码成路由表

上面两张表是给人看的。但「不越界」这件事如果只靠自觉,迟早会漂。所以本书把这张边界表编码成一个可查询的路由函数:给它一个话题,它告诉你这个话题归谁——是站内既有文章,还是本书。这样「越界」就变成一条可以被测试断言的失败用例。

下面这段是 L1,go test 可以直接跑。先看 internal/overview/scope.go:

// file: internal/overview/scope.go
// tier: L1
// verify: go test ./internal/overview/
// Package overview also encodes this book's boundary against the game-server
// content already on the site. The tables in section 1.2 are for humans; this
// file is the machine-readable version, so "stay in your lane" becomes an
// assertion a test can fail on instead of a slogan nobody re-reads.
package overview

import "strings"

// Owner says who is responsible for a topic: an existing section of the site,
// or this book (the "from scratch, with numbers" half).
type Owner struct {
	// Kind is either "practice" (existing site content) or "this-book".
	Kind string
	// Path is the absolute site path the reader should go to.
	Path string
}

// Owners for the content this book deliberately does not rewrite.
var (
	// PracticeServer is the 170-article architecture-only practice section.
	PracticeServer = Owner{Kind: "practice", Path: "/posts/game/server/practice/"}
	// PracticeSkynet is the Skynet (Lua framework) usage section.
	PracticeSkynet = Owner{Kind: "practice", Path: "/posts/game/server/skynet/"}
	// PracticePitaya is the Pitaya framework section.
	PracticePitaya = Owner{Kind: "practice", Path: "/posts/game/server/pitaya/"}
	// ExistingBook is the half-finished《游戏服务端编程实践》.
	ExistingBook = Owner{Kind: "practice", Path: "/posts/books/游戏服务端编程实践/"}
	// ThisBook is where the from-scratch implementation lives.
	ThisBook = Owner{Kind: "this-book", Path: "/posts/books/网络与并发内核/"}
)

// Rule maps a topic keyword to its owner. First match wins.
type Rule struct {
	Keyword string
	Owner   Owner
}

// scopeRules is the 1.2 boundary table, in code. Order is priority.
var scopeRules = []Rule{
	{"架构方案", PracticeServer},
	{"选型对比", PracticeServer},
	{"Skynet", PracticeSkynet},
	{"Pitaya", PracticePitaya},
	{"SLG 项目", ExistingBook},
	{"从零实现", ThisBook},
	{"端到端压测", ThisBook},
}

// Route returns the owner of a topic. A topic that matches no rule defaults to
// this book: the book's job is exactly "what nobody else wrote from scratch",
// so the fallback must point at us, never away.
func Route(topic string) Owner {
	for _, r := range scopeRules {
		if strings.Contains(topic, r.Keyword) {
			return r.Owner
		}
	}
	return ThisBook
}

// ExternalPaths returns every distinct site path this book defers to. Callers
// use it to assert that each one is a real, absolute section path.
func ExternalPaths() []string {
	seen := map[string]bool{}
	var out []string
	for _, r := range scopeRules {
		if r.Owner.Kind == "practice" && !seen[r.Owner.Path] {
			seen[r.Owner.Path] = true
			out = append(out, r.Owner.Path)
		}
	}
	return out
}

配套测试 internal/overview/scope_test.go 断言两件事:路由表按表走,以及所有外链目标都是 /posts/ 下的合法 section 路径(单段 /<topic>/ 是死链,不能写):

// file: internal/overview/scope_test.go
// tier: L1
// verify: go test ./internal/overview/
package overview

import (
	"strings"
	"testing"
)

func TestRouteToPractice(t *testing.T) {
	cases := map[string]Owner{
		"网关分层的架构方案":   PracticeServer,
		"引擎选型对比":      PracticeServer,
		"Skynet 服务模型": PracticeSkynet,
		"Pitaya 集群":   PracticePitaya,
		"SLG 项目实战":    ExistingBook,
	}
	for topic, want := range cases {
		if got := Route(topic); got != want {
			t.Errorf("Route(%q) = %+v, want %+v", topic, got, want)
		}
	}
}

func TestRouteToThisBook(t *testing.T) {
	for _, topic := range []string{"从零实现 KCP", "端到端压测与承载数字", "一个没人写过的话题"} {
		if got := Route(topic); got != ThisBook {
			t.Errorf("Route(%q) = %+v, want this-book", topic, got)
		}
	}
}

func TestExternalPathsAreAbsoluteSectionPaths(t *testing.T) {
	paths := ExternalPaths()
	if len(paths) == 0 {
		t.Fatal("expected at least one external path")
	}
	for _, p := range paths {
		// Section pages live under /posts/; a bare single-segment path would
		// be a dead link, because articles are flattened to /<slug>/.
		if !strings.HasPrefix(p, "/posts/") {
			t.Errorf("external path %q is not under /posts/", p)
		}
		if !strings.HasSuffix(p, "/") {
			t.Errorf("external path %q must be a section path ending in /", p)
		}
	}
}

func TestRouteFallbackIsNeverExternal(t *testing.T) {
	if got := Route("完全未知的话题 xyz"); got.Kind != "this-book" {
		t.Fatalf("fallback owner = %+v, want this-book", got)
	}
}

本机实跑结果(go test ./internal/overview/ -v,节选):

--- PASS: TestRouteToPractice (0.00s)
--- PASS: TestRouteToThisBook (0.00s)
--- PASS: TestExternalPathsAreAbsoluteSectionPaths (0.00s)
--- PASS: TestRouteFallbackIsNeverExternal (0.00s)
PASS
ok  	aegis/internal/overview	0.324s

Route() 的默认分支值得强调:没命中任何规则的话题,一律判给本书。这是刻意的——本书的职责就是「别人没写、要从零实现的」,所以兜底方向必须是「自己写」,而不是「甩给别人」。测试里的 TestRouteFallbackIsNeverExternal 就是在守这条不变量。

本节小结

这一节做了两件事,都是为了让本书和既有内容各归其位:

  1. 盘点存量:站内有 170 篇实战文(零可运行代码)、110 篇月度归档、17 篇 Skynet、3 篇 Pitaya,以及一本只落盘前 3 章的《游戏服务端编程实践》。「架构方案」这一维已经很厚,「从零实现 + 数字」这一维几乎是空的。
  2. 划清边界:形态、技术栈、验收、游戏四重区隔,本书只补后者那一半,并把这条边界编码成可运行的路由表。

下一节把「本书要补的那一半」具体化成一张零件清单:一个 MMO 服务端,最少需要哪些零件才能跑起来。

阅读导航:上一节:1.1 分层与职责边界:一个 MMO 服务端长什么样 · 下一节:1.3 一个 MMO 服务端最少要有哪些零件 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「mmo」更多文章

  1. 《MMO 服务器开发》工程化与运营 · 目录
  2. 《MMO 服务器开发》9.3 上线检查清单
  3. 《MMO 服务器开发》9.2 性能预算达标