IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

为什么 skynet 提供的包协议只用 2 个字节表示包长度

云风的 BLOG 2015-01-11 23:15:18 累计浏览 1,826 次
本机暂存

   skynet 提供了一个 lua  库 netpack ,用来把 tcp 流中的数据解析成 长度 + 内容的包。虽然使用 skynet 的同学可以不使用它,而自己另外实现一套解析协议来解析外部 TCP 数据流(比如 skynet 中的 redis driver 解析 redis server 的数据流就是用的换行符分割包),但依然有很多同学询问,能不能自定义包头长度。

   这里的这个库定义的协议中,包长度是用 big-endian 的 2 个字节表示的,也就是说一个包的长度不得超过 64K 。这让很多人很为难。已经几次有同学建议,把长度放宽成 4 个字节,因为他们的应用环境中大部分包不会超过 64K ,但总有少量超过这个限制的。

   历史上,skynet 的 gate 还是用 C 实现的时候(那个版本依然可以使用)的确可以自定义是使用 2 个字节还是 4 个字节表示包长。但经过一番考虑,我还是去掉了这个选择。

   一个好的库,应该简洁,且引导使用者用正确的方法做正确的事情;而不应该提供让用户犯错的机会。在和游戏客户端通讯的时候,如果你只采用一个 TCP 连接,那么允许很长的数据包本身就是错误的。甚至 64K 都太大。

   游戏通常需要比较快的响应速度,如果你允许在单个 TCP 连接中插入一个太大的数据块,比如 100K ,那么在比较弱的网络条件下(例如手机网络),处理这个包可能就需要超过 1 分钟的时间。而这么大的数据块,在业务逻辑上大多不期待立刻能发出或收到的。一个典型的应用场景就是用户在拍卖行中查询所有的上架物品,如果把所有返回数据都放在一个数据包中,很容易就变得很大。而查询大量这个操作,用户本身就对立刻回应没有期待。

   而在单个 TCP 连接上,这样一个大数据块会阻塞住整个信道,后面本来需要快速送到的数据全部被延迟了。

   如果你想在业务层做一个心跳包检测网络是否超时,很容易就把心跳包拦在后面。而通常网络处理层不会提供接口让业务层探知是否正在接受数据(skynet 的 gateserver 就没有提供这样的接口,虽然它很容易提供,只需要修改几行 lua 代码),只能在一个完整的数据包收到后才会交给业务层处理。

   正确的做法是,在长度+内容这个协议上再加一个层次。加一条协议叫大数据块,允许把一个大数据块分几段发送。可以在这条协议中加上数据块 id ,在后面引用这个数据块的包中附上数据块 id 即可。

   为什么要用这种比较绕的方式,而不直接把包头从 2 字节改成 4 字节?当你做这个设计时,就已经表明你重视了上面提到的问题。


   当你把数据包都分割的比较小时,才能实现单个 TCP 连接上承载多个信道的能力。对于网络游戏,并不是所有的数据包都是上下文相关的,你可以看成隐含着有多条线索。比如聊天频道的信息和场景同步的信息就是相互独立的。skynet 为这种场景还提供了额外的 socket api 支持。socket.lwrite 可以把一个字符串(一个数据包)写到低优先级通道。只有等默认通道(高优先级)的包全部发送完后,低优先级通道上的包才至少被发送一个(单个包可以保证原子性)。

   比如,你可以用它来发送聊天信息,就不会因为聊天信息泛滥把其它重要数据包都塞住。同样,你可以用来发送被分割后的大数据块。如果同时你还有很多其它重要的数据需要传输给客户端,那么这些数据块就会被打散穿插在其间。

   当然,你也可以把所有给客户端的数据全部用 lwrite 发送,而仅仅把心跳包放在常规高优先级通道,可以保证心跳频率更稳定。


   另外,采用 4 字节的包长度还有一个安全漏洞,可能被攻击利用。

   一般的分割包的代码,在收到包头时,都会根据长度信息预先分配出相当长的空间,等着后面的数据达到后填入。如果攻击者不断在新建立的连接上发送一个恶意的长度信息,比如 2G ,服务器内存很容易被快速消耗光。

   早期 skynet 的 gate 实现时,采用的是共享一个固定长度的 ringbuffer 的实现,可以避免这种攻击。但新的版本由于不再允许 4 字节长度,就没有做特别处理了。

   如果你的应用环境非常特殊,坚持一个允许更大长度的数据包协议。那么我建议你慎重的实现一个分包模块,而不是简单的把 netpack 库中的 2 改成 4 。

同分类推荐文章

  1. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  2. Go 实验特性详解 (2026-06-21 10:05:27)
  3. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)

查看更多 后端 文章 →

建议继续学习

  1. gen_tcp发送进程被挂起起因分析及对策 (累计阅读 37,821)
  2. TCP 的那些事儿(上) (累计阅读 22,696)
  3. 从输入 URL 到页面加载完成的过程中都发生了什么事情? (累计阅读 15,933)
  4. 自建DNS以防止GFW干扰 (累计阅读 13,125)
  5. 浅谈TCP优化 (累计阅读 11,082)
  6. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 10,845)
  7. 查看 Apache并发请求数及其TCP连接状态 (累计阅读 10,069)
  8. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 8,840)
  9. websocket 连接 C Server的尝试 (累计阅读 7,924)
  10. 计算机网络协议包头赏析-TCP (累计阅读 7,854)