上一节 下一节
第二章 第七节 如何发送数据包
参与编写者: MagicLu550
建议学习时间: 40分钟
学习要点: 了解数据包和主要的发送形式
一. 概述
Nukkit实现客户端与服务端交互,是通过发送和接收数据包实现的.数据包在nukkit
的工作过程是占有很重的分量,包括玩家的移动等,都是由一个个数据包接连不断的实现这一
功能.实现收发数据包的机制是RakNet,通过UDP实现的这些功能.RakNet实现的基础是
Netty框架,如下文可以看到
cn/nukkit/raknet/server/UDPServerSocket.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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
1 2 3 4 5 6 7 8 9 10 11
| @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的数据包类的继承结构是
1 2 3
| BinaryStream |------- DataPacket |-------- 我们主要操作的数据包
|
pid() 一般为数据包的NETWORK_ID,在Player,Server,RakNetInterface,DataPacket类中被调用过
DataPacket的主要方法是decode()和encode(),数据包的传输过程中,通过这两个方法实现解码和
编码,使得数据包在服务端与客户端间相互识别.
decode() 是解码方法,一般是客户端发来的数据包,解码到对象的具体属性,之后在服务端中使用这些数据,
即 客户端 -> 服务端
如代码中这样
CameraPacket.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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
1 2 3 4 5 6
| @Override public void encode() { this.reset(); this.putEntityUniqueId(this.cameraUniqueId); this.putEntityUniqueId(this.playerUniqueId); }
|
从这里,我们就可以引入接下来的发包环节,事实上,它很简单
- 事件
发送数据包和接收数据包的时候会触发几种事件,我们可以通过这几种事件进行
抓包
我们比较常用的是这几种
- BatchPacketsEvent: 批处理数据包事件
- DataPacketReceiveEvent: 数据包接收事件
- DataPacketSendEvent: 数据包发送事件
这里我们主要介绍Receive和Send
DataPacketSendEvent主要触发在服务端向客户端发送数据包的时候
Player.java
1 2 3 4 5 6 7 8 9 10 11 12
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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做一个样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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){ MovePlayerPacket packet = new MovePlayerPacket(); packet.x = 0; packet.y = 100; packet.z = 1000; e.getPlayer().dataPacket(packet); } }
|
四. 常用数据包的解释
上一节 下一节