第七节 如何发送数据包

上一节 下一节

第二章 第七节 如何发送数据包

参与编写者: 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的发包机制

  1. 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);
}

从这里,我们就可以引入接下来的发包环节,事实上,它很简单

  1. 事件

发送数据包和接收数据包的时候会触发几种事件,我们可以通过这几种事件进行
抓包
我们比较常用的是这几种
- 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;
}

  1. 发包

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){
//1. 定义数据包对象
MovePlayerPacket packet = new MovePlayerPacket();
//2. 设置数据包数值,这里我随便写了几个值
packet.x = 0;
packet.y = 100;
packet.z = 1000;
//3.发出
e.getPlayer().dataPacket(packet);
}
}

四. 常用数据包的解释

上一节 下一节