Skip to content

网络系统

Spigot↔Forge 双向 Packet 通道,支持自动/自定义序列化、端到端加密和大数据包分片。

前置知识

需要了解 IoC 容器(处理器需要 @Component 注册)。跨服同步/RPC(横向链路)请查看 ServerSync

Quick Start

kotlin
// 1. 定义数据包
@Packet
data class GreetPacket(val name: String, val level: Int) : IPacket

// 2. 注册处理器
@Component
class GreetHandler {
    @PacketHandler(GreetPacket::class)
    fun handle(packet: GreetPacket) {
        println("Hello ${packet.name}, Lv.${packet.level}")
    }
}

API Reference

数据包注解

注解说明
@Packet标记一个类为数据包,默认使用内置自动序列化
@PacketEncoder标记实例方法为自定义编码器
@PacketDecoder标记 companion object 中的静态方法为自定义解码器
@PacketHandler标记方法为数据包处理器

@PacketHandler

参数类型说明
valueKClass<out IPacket>要处理的数据包类型

方法签名:

场景签名说明
客户端处理器fun handle(packet: T)无 player 参数
服务端处理器fun handle(packet: T, player: Any?)player 为发送者

ForyWriter API

方法参数类型说明
writeBoolean(v)Boolean写入布尔值
writeByte(v)Byte写入字节
writeShort(v)Short写入短整数
writeInt(v)Int写入整数
writeLong(v)Long写入长整数
writeFloat(v)Float写入浮点数
writeDouble(v)Double写入双精度浮点
writeString(v)String写入字符串
writeBytes(v)ByteArray写入字节数组
writeIntArray(v)IntArray写入整数数组
writeObject(v)Any自动序列化对象

ForyReader API

方法返回值说明
readBoolean()Boolean读取布尔值
readByte()Byte读取字节
readShort()Short读取短整数
readInt()Int读取整数
readLong()Long读取长整数
readFloat()Float读取浮点数
readDouble()Double读取双精度浮点
readString()String读取字符串
readBytes()ByteArray读取字节数组
readIntArray()IntArray读取整数数组
readObject<T>()T反序列化对象

跨平台 Wrapper 类型

Wrapper 类型Forge 类型Spigot 类型用途
ItemStackWrapperItemStackItemStack物品
LocationWrapperForgeLocationLocation精确位置(含旋转)
BlockPosWrapperBlockPosBlock方块坐标
EntityWrapperEntityEntity实体
ResourceIdWrapperResourceLocationNamespacedKey资源标识符
Vec3WrapperVec3Vector三维向量
PotionEffectWrapperMobEffectInstancePotionEffect药水效果
WorldRefWrapperLevelWorld世界引用

通信安全

机制说明
密钥交换ECDH(椭圆曲线 Diffie-Hellman)握手
数据加密AES-GCM 对称加密
信任存储TofuStore(Trust on First Use)记录首次连接的服务端公钥
防重放滑动窗口机制防止数据包重放攻击

自定义序列化

对于需要自定义序列化的数据包,使用 @PacketEncoder@PacketDecoder

kotlin
@Packet
data class CustomPacket(
    val value: String,
    val items: List<String>
) : IPacket {

    @PacketEncoder
    fun encode(writer: ForyWriter) {
        writer.writeString(value)
        writer.writeInt(items.size)
        items.forEach { writer.writeString(it) }
    }

    companion object {
        @PacketDecoder
        fun decode(reader: ForyReader): CustomPacket {
            val value = reader.readString()
            val size = reader.readInt()
            val items = (0 until size).map { reader.readString() }
            return CustomPacket(value, items)
        }
    }
}

跨平台 Wrapper 用法

BlockPosWrapper

kotlin
val pos = BlockPosWrapper(100, 64, -200)

val above = pos.above()            // y + 1
val below = pos.below()            // y - 1
val north = pos.north()            // z - 1
val south = pos.south()            // z + 1
val dist = pos.manhattanDistance(otherPos)
val chunkX = pos.chunkX
val chunkZ = pos.chunkZ

LocationWrapper

kotlin
val loc = LocationWrapper(
    dimension = "minecraft:overworld",
    x = 100.5, y = 64.0, z = -200.5,
    yaw = 0f, pitch = 0f
)
val newLoc = loc.add(1.0, 0.0, 0.0)
val distance = loc.distance(otherLoc)

ResourceIdWrapper

kotlin
val id = ResourceIdWrapper.of("minecraft:diamond")
val id2 = ResourceIdWrapper.of("mymod", "custom_item")
id.namespace  // "minecraft"
id.path       // "diamond"
id.isVanilla  // true

平台转换扩展

kotlin
// Forge 客户端
import com.behemiron.engine.forge.util.crossplatform.*
val wrapper = minecraftItemStack.toWrapper()
val forgeStack = wrapper.toForge()

// Spigot 服务端
import com.behemiron.engine.spigot.util.crossplatform.*
val wrapper = bukkitItemStack.toWrapper()
val bukkitStack = wrapper.toBukkit()

完整示例

kotlin
// 定义业务数据包
@Packet
data class TeleportPacket(val target: LocationWrapper) : IPacket

@Packet
data class SpawnEntityPacket(
    val entity: EntityWrapper,
    val position: BlockPosWrapper
) : IPacket

// 客户端处理器
@Component
class ClientHandler {
    @PacketHandler(TeleportPacket::class)
    fun onTeleport(packet: TeleportPacket) {
        println("传送到: ${packet.target}")
    }
}

// 服务端处理器(带 player 参数)
@Component
class ServerHandler {
    @PacketHandler(SpawnEntityPacket::class)
    fun onSpawn(packet: SpawnEntityPacket, player: Any?) {
        if (packet.entity.typeId.isBlank()) return
        println("玩家 $player 请求生成实体: ${packet.entity.typeId}")
    }
}

注意事项

  • 数据包保持精简,只传输必要数据,避免传输 Map<String, Any> 等大而模糊的结构。
  • 优先使用 Wrapper 类型(LocationWrapper 等)而非分散的基本类型字段。
  • 服务端处理器中必须验证客户端数据的合法性,不要信任客户端输入。
  • 处理器类必须用 @Component 注册到 IoC 容器中。