参与编写者: MagicLu550
一. 概述
Nukkit实现客户端与服务端交互,是通过发送和接收数据包实现的.数据包在nukkit 的工作过程是占有很重的分量,包括玩家的移动等,都是由一个个数据包接连不断的实现这一 功能.实现收发数据包的机制是RakNet,通过UDP实现的这些功能.RakNet实现的基础是 Netty框架,如下文可以看到
cn/nukkit/raknet/server/UDPServerSocket.java
package cn.nukkit.raknet.server;
import cn.nukkit.utils.ThreadedLogger;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOption;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* author: MagicDroidX
* Nukkit Project
*/
public class UDPServerSocket extends ChannelInboundHandlerAdapter {
RakNetServer.java
@Override
public void run() {
this.setName("RakNet Thread #" + Thread.currentThread().getId());
Runtime.getRuntime().addShutdownHook(new ShutdownHandler());
UDPServerSocket socket = new UDPServerSocket(this.getLogger(), port, this.interfaz);
try {
new SessionManager(this, socket);
} catch (Exception e) {
Server.getInstance().getLogger().logException(e);
}
}
二. Netty框架
Netty框架是使用最广泛的java-nio框架之一,由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具, 用以快速开发高性能、高可靠1性的网络服务器和客户端程序。Netty相当于简化和流线化了网络应用的编程开发过程.
如果你想要更深入研究这个框架可以参见Netty框架的github
三. Nukkit的发包机制
- Nukkit 数据包的结构
Nukkit的数据包类的继承结构是
BinaryStream
|------- DataPacket
|-------- 我们主要操作的数据包
pid() 一般为数据包的NETWORK_ID,在Player,Server,RakNetInterface,DataPacket类中被调用过
DataPacket的主要方法是decode()和encode(),数据包的传输过程中,通过这两个方法实现解码和 编码,使得数据包在服务端与客户端间相互识别.
decode() 是解码方法,一般是客户端发来的数据包,解码到对象的具体属性,之后在服务端中使用这些数据,
即 客户端 -> 服务端
如代码中这样
CameraPacket.java
package cn.nukkit.network.protocol;
import lombok.ToString;
@ToString
public class CameraPacket extends DataPacket {
public long cameraUniqueId;
public long playerUniqueId;
@Override
public byte pid() {
return ProtocolInfo.CAMERA_PACKET;
}
@Override
public void decode() {
this.cameraUniqueId = this.getVarLong();
this.playerUniqueId = this.getVarLong();
}
encode() 是编码方法,会在发包时被调用,将在服务端设置的数据值写入发出到客户端
即 服务端 -> 客户端
如代码这样
CameraPacket.java
@Override
public void encode() {
this.reset();
this.putEntityUniqueId(this.cameraUniqueId);
this.putEntityUniqueId(this.playerUniqueId);
}
从这里,我们就可以引入接下来的发包环节,事实上,它很简单
- 事件
发送数据包和接收数据包的时候会触发几种事件,我们可以通过这几种事件进行 抓包 我们比较常用的是这几种 - BatchPacketsEvent: 批处理数据包事件 - DataPacketReceiveEvent: 数据包接收事件 - DataPacketSendEvent: 数据包发送事件 这里我们主要介绍Receive和Send
DataPacketSendEvent主要触发在服务端向客户端发送数据包的时候
Player.java
public int dataPacket(DataPacket packet, boolean needACK) {
if (!this.connected) {
return -1;
}
try (Timing timing = Timings.getSendDataPacketTiming(packet)) {
//There!!!!!
DataPacketSendEvent ev = new DataPacketSendEvent(this, packet);
this.server.getPluginManager().callEvent(ev);
if (ev.isCancelled()) {
return -1;
}
DataPacketReceiveEvent主要触发在客户端向服务端发送数据包并且服务端接收到的时候.
Player.java
public void handleDataPacket(DataPacket packet) {
if (!connected) {
return;
}
try (Timing timing = Timings.getReceiveDataPacketTiming(packet)) {
//There!!!!!!!!!
DataPacketReceiveEvent ev = new DataPacketReceiveEvent(this, packet);
this.server.getPluginManager().callEvent(ev);
if (ev.isCancelled()) {
return;
}
- 发包
Nukkit提供了友好的数据包机制,我们可以通过需求定义,发送数据包
Nukkit提供了发送数据包的方法,并允许开发者直接发送数据包和监听数据包的收发
一般的,发送数据包的方式都是使用玩家对象的dataPacket实现
player.dataPacket(DataPacket)
,这是一个最常用的方式。
当然,先前的Server类也提到了批量发包的方法(Server类)
- batchPackets(Player[], DataPacket[]) 批量发送数据包
- broadcastPacket(Player[], DataPacket) 向所有玩家广播数据包
这三个方法就是发包所常使用的方法了。
这里我们用dataPacket方法做案例
这里用MovePlayerPacket做一个样例
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
import cn.nukkit.network.protocol.MovePlayerPacket;
public class TestListener implements Listener {
@EventHandler
public void onPlayer(PlayerJoinEvent e){
//1. 定义数据包对象
MovePlayerPacket packet = new MovePlayerPacket();
//2. 设置数据包数值,这里我随便写了几个值
packet.x = 0;
packet.y = 100;
packet.z = 1000;
//3.发出
e.getPlayer().dataPacket(packet);
}
}
四. 常用数据包的解释