网络连接 · Network

本章假设读者理解 TCP 协议的工作方式并有一定使用经验。
关于 Minecraft 网络协议的详细说明可阅读 https://wiki.vg/Protocol

众所周知,Minecraft Java 版使用的是基于 TCP 的网络传输协议,而 TCP 具有点对点、传输可靠、保证顺序、不保留消息边界等特点。 除了消息边界以外,这些特点同样适用于 Minecraft 协议,请在编写代码时时刻谨记。

Minecraft 在 TCP 的基础上构造了基于长度的分包、基于 zlib 算法的压缩以及基于 RSA 和 AES/CFB8 的加密等协议。

如需收发网络包,请导入 Go-MC 提供的网络库:

import (
	"github.com/Tnze/go-mc/net"
	pk "github.com/Tnze/go-mc/net/packet"
)

特殊情况下,如需同时使用标准库的 net ,请为 Go-MC 的网络库设置别名:

import (
    "net"
    mcnet "github.com/Tnze/go-mc/net"
    pk "github.com/Tnze/go-mc/net/packet"
)

本章提到的 net 包如无特殊说明均指的是 go-mc/net 包,而不是 Go 标准库的 net 包。

创建连接 · Create Connection

使用 go-mc/net 包创建连接的方式与使用 Go 标准库创建 TCP 连接的方式非常类似。

客户端 · Client

以下代码连接运行于 localhost:25565 的服务器:

conn, err := net.DialMC("localhost:25565")
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

服务端 · Server

以下代码在所有本机 IP 地址的 25565 端口监听连接:

listener, err := net.ListenMC("0.0.0.0:25565")
if err != nil {
    log.Fatal(err)
}

for {
    conn, err := listener.Accept()
    if err != nil {
        log.Fatal(err)
    }
    go handle(&conn)
}

发送数据包 · Sending Packets

要发送数据包,你需要有一个网络连接 net.Conn(conn) 与一个数据包 pk.Packet(p)。 通过简单地调用 conn.WritePacket(p) 即可将数据包发送。

关于如何获得 pk.Packet,请参考下一节数据打包 · Packing

err := conn.WritePacket(p)
if err != nil {
	log.Print(err)
	return
}

接收数据包 · Receiving Packets

接收数据包与发送数据包类似,你同样需要有一个 net.Conn(conn) 与一个 pk.Packet(p)。 不同的是这次的操作是从连接读取数据并写入数据包。

var p pk.Packet
err := conn.ReadPacket(&p)
if err != nil {
	log.Print(err)
	return
}

之所以设计为需要传入 &p 而不是直接返回一个新的 p,是为了方便复用 pk.Packet 对象。

当循环接收数据包时,每个连接只使用一个 pk.Packet,可以复用内部缓冲区,起到减少内存分配,减轻 GC 压力的作用。

var p pk.Packet
for {
	err := conn.ReadPacket(&p)
	if err != nil {
		log.Print(err)
		break
	}
	handle(p) // 需要注意 p 只在下次 ReadPacket 调用前有效
}