Skip to content

Latest commit

 

History

History
224 lines (170 loc) · 6.72 KB

2-7_如何发送数据包.md

File metadata and controls

224 lines (170 loc) · 6.72 KB

上一节 下一节

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

参与编写者: MagicLu550

建议学习时间: 40分钟

学习要点: 了解数据包和主要的发送形式

一. 概述

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的发包机制

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

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

  1. 事件

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

  1. 发包

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

四. 常用数据包的解释

上一节 下一节