Minecraft(我的世界)中文论坛

 找回密码
 注册(register)

!header_login!

只需一步,立刻登录

查看: 10508|回复: 17

[其它开发教程] [Tutor][PCD]Forge与Bukkit|Sponge之间的通信|数据传输——Messenger类

[复制链接]
海螺螺 当前离线
积分
19465
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2015-2-24
查看详细资料
发表于 2017-8-28 20:26:00 | 显示全部楼层 |阅读模式

您尚未登录,立即登录享受更好的浏览体验!

您需要 登录 才可以下载或查看,没有帐号?注册(register)

x
本帖最后由 754503921 于 2019-6-14 17:31 编辑

Forge | LiteLoader 与 Bukkit / Sponge 之间的通信
—— PluginMessengeChannel 与 FMLNetworkEvent
在实际开发中,如果你有一些天才的设想,比如借助 Forge 让你的插件服务器变得更加有特色,但是苦于无法进行数据传输的话,那么现在你就可以学习如何让 Bukkit / Sponge 和 Forge 之间传输信息了。
此教程适用于 1.7.10-1.12 (已测试) 1.13 可以看这里,1.7.10就把包名改成 cpw 那个就行,Bukkit的插件全版本通用。

概述
Bukkit / Sponge 与 Forge 通信的原理为:
服务端发送 PluginMessage 到 Forge 客户端,客户端使用 FMLNetworkEvent.ClientCustomPacketEvent 接受处理信息。
Forge 使用 FMLEventChannel 将 FMLNetworkPacket 发送至 Bukkit,Bukkit 服务器使用 PluginMessageListener 接受处理消息,Sponge 使用注册的频道添加的监听器 RawDataListener 处理消息。
教程使用的包名为 com.ilummc.msgtutor,主类为 MessageMain。

Bukkit 接收消息部分

不知道你在查阅 Bukkit 的 Javadocs 时有没有注意到这样一个包 org.bukkit.plugin.messaging,这就是用于通信的包。
首先你需要一个实现了 PluginMessageListener 的类,本教程我们将其命名为 MessageListener:
package com.ilummc.msgtutor;

import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;

public class MessageListener implements PluginMessageListener {

        @Override
        public void onPluginMessageReceived(String channel, Player player, byte[] data) {

        }
}
自动补全的方法 onPluginMessageReceived 为接收到消息时调用的方法,channel 为通道名称,data 为具体的数据内容。
(2018/7/25 补充) 自 1.13 后,bukkit 对 channel 的名称做出了限制,需要使用 namespace:name 的格式,比如 fmltutor:fmltutor
接着,你需要注册消息输入和输出的通道,通过 Messenger 类的 registerIncomingPluginChannelregisterOutgoingPluginChannel 方法完成,代码如下:
package com.ilummc.msgtutor;

import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class MessageMain extends JavaPlugin {

        @Override
        public void onEnable() {
                // 注册消息接受通道
                Bukkit.getMessenger().registerIncomingPluginChannel(this, "msgtutor", new MessageListener());
                // 注册消息发送通道
                Bukkit.getMessenger().registerOutgoingPluginChannel(this, "msgtutor");
        }
}
registerIncomingPluginChannel 注册了接受消息的通道,和使用的 PluginMessageListener 实例,registerOutgoingPluginChannel 则注册了发送消息使用的通道。
到此你完成了Bukkit 接收消息的部分。

Forge 客户端接收消息部分

我们需要先注册一个通道,使用 NetworkRegistry 类的 newEventDrivenChannel 方法:
package com.ilummc.msgtutor;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.FMLEventChannel;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;

@SuppressWarnings("all")
@Mod(modid="msgtutor", version="tutor", name="MessageTutor")
public class MessageMain {
        static FMLEventChannel channel;
        
        @EventHandler
        public void preload(FMLPreInitializationEvent evt) {
                // 注册事件
                MinecraftForge.EVENT_BUS.register(this);
                FMLCommonHandler.instance().bus().register(this);
                // 注册通道
                channel = NetworkRegistry.INSTANCE.newEventDrivenChannel("msgtutor");
                channel.register(this);
        }
}
接着,我们添加一个监听器,监听 FMLNetworkEvent.ClientCustomPacketEvent 事件:
        @SubscribeEvent
        public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent evt) {
                FMLLog.getLogger().info(new String(evt.getPacket().payload().array()));
        }
此事件的 getPacket 方法可以获得一个 FMLProxyPacket 实例数据包,这个实例的方法 payload 可以获得数据包携带的内容 ByteBuf,而 ByteBuf 实例的方法 array 则可以得到 byte[] 类型的数据。
(2018/7/8 补充) 在 forge 的 1.12.2 以后的版本,该事件的 ByteBuf 变成了一个 netty 魔性优化的实例,导致性能的上升以及 array() 方法的失效,你需要手动 new 一个数组然后用 readBytes 来读数据。
到此,你就可以接收来自服务器的消息了。

Sponge 接收消息部分

Sponge.getChannelRegistrar() 方法返回 message channel 的注册器,然后通过其 createRawChannel 方法注册一个新的 channel。
通过 addListener 添加新的监听器。
package com.ilummc.msgtutor;
// 省略导入
@Plugin(id = "msgtutor",
        name = "MessageTutor",
        version = "1.0-SNAPSHOT",
        authors = {"IzzelAliz"})
public class ServerGui {

    private static ChannelBinding.RawDataChannel channel;

    @Listener
    public void onServerStart(GameStartedServerEvent event) {
        // 注册频道
        channel = Sponge.getChannelRegistrar().createRawChannel(this, "msgtutor");
        // 添加监听器
        // PlatformType 指定监听来自哪里的信息,我们监听的是客户端,所以使用 CLIENT

        channel.addListener(Platform.Type.CLIENT, (data, connection, side) -> {
            // 将连接类型转换为 PlayerConnection
            if (connection instanceof PlayerConnection) {
                PlayerConnection conn = (PlayerConnection) connection;
                // 示例给玩家发送消息
                conn.getPlayer().sendMessage(Text.of(new String(data.array())));
            }
        });
    }
}

发送消息
Bukkit 发送消息给客户端的方法为
Bukkit.getPlayer("Izzel_Aliz").sendPluginMessage(Plugin plugin, String channel, byte[] data);
Forge 发送消息给服务器的方法为
byte[] array = ...; // 你要发送的消息的 byte 数组
ByteBuf buf = Unpooled.wrappedBuffer(array);
FMLProxyPacket packet = new FMLProxyPacket(new PacketBuffer(buf), "msgtutor"); // 数据包
channel  // FMLEventChannel 实例
    .sendToServer(packet);
Sponge 发送消息给客户端的方法为
ChannelBinding.RawDataChannel channel = ... ; // 你注册的 channel 实例
Player player = .... ; // 目标玩家
channel.sendTo(player, channelBuf -> channelBuf.writeByteArray("发送的消息").getBytes());
// ChannelBuf 有大量方法,可以写入读取不同种类的数据,使用与 ByteBuf 类似
你可以发送任何东西,只要能将其作为 byte 数组发送。byte 数组的长度限制为 32766 字节。

LiteLoader 接受/发送
请移步 @ustc_zzzz 大佬的帖子的章节 与服务端插件交互 http://www.mcbbs.net/thread-659755-1-1.html
关于线程安全

以上接收时的事件全部是在网络线程被触发,所以对于线程不安全的Minecraft来说,线程安全问题需要额外注意。
由于本人对 Forge 的操作并不是很熟练,所以只能以 Bukkit 作为例子,如果你接收到的信息只是一条字符串,并且你只是想将其发送给玩家(Player#sendMessage),那么你可以随意在网络线程中使用,因为这个方法是线程安全的;但是,如果你需要进行踢出(Player#kickPlayer),那么你必须在主线程(Server Thread)进行这个操作,否则可能得到一个报错、崩溃或者意想不到的结果(尽管 Bukkit 会阻止 Async Kick 的行为并发出警告)。
那么,我们可以用以下的方法将数据转交给主线程处理:
  • 使用 Bukkit 或者 Sponge 的调度器

  • 利用管道IO

ByteBuf 的简单使用

ByteBuf 类被提供于 io.netty 包中,使用可以通过 Maven 导入
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-buffer</artifactId>
    <version>4.1.20.Final</version>
</dependency>
使用 Gradle 导入
compile group: 'io.netty', name: 'netty-buffer', version: '4.1.20.Final'
ByteBuf 可以说是 java.nio.ByteBuffer 类的加强,主要有以下优点:
  • 读写指针分离,不用调用 flip() 方法来切换读写状态
  • 写入时可以自动增加容量
  • 提供了 Unpooled 和 Pooled 两种用于不同的场景

创建一个 ByteBuf 的方法很多,比如
Unpooled.buffer() // 创建一个普通的 ByteBuf
Unpooled.wrappedBuffer(byte[]) // 从已有 byte 数组创建
Pooled.buffer() // 创建一个高并发优化的 ByteBuf
Unpooled 和 Pooled 类重载的方法还有很多,详情可以查阅 Javadocs
另及:Minecraft 还叫不上高并发,Pooled 的高并发优化对于 Minecraft 大概没啥用

写入/读取
ByteBuf buf = ... ;
buf.writeInt(int); // 写入 int 型数据
buf.writeBytes(byte[], int, int); // 写入 byte 数组,后两个参数分别是 offset 和 length
buf.readLong(); // 返回一个 long 型数据
buf.toString(int, int, Charset); // 将 ByteBuf 内部的 byte[] 转换为 String 型数据,前两个参数分别是 offset 和 length
重载的方法还有很多,基本什么数据都能往里写,这里介绍一些实用的示例。

ByteBuf 里的两个读写指针,分别可以通过 readerIndex 和 writerIndex 方法获得
ByteBuf 内部维护了一个 byte 数组,其中 ByteBuf 的 capacity 为数组长度,可以通过 getCapacity 方法获得
0 <= readerIndex <= writerIndex <= capacity
  • 0 到 readerIndex 之间的数组区域称为 discardable bytes,discardReadBytes 方法可以将数组从 readerIndex 之后的部分移动到 0,从而增大可用区。
  • readerIndex 到 writerIndex 之间的数组区域称为 readable bytes,这一部分可以进行读取,每次读取之后,readerIndex 都会相应增加(增加数据长度,如 readLong 就会增加 Long.BYTES)。
  • writerIndex 到 capacity 之间的数组区域称为 writable bytes,这一部分可以写入,每次写入之后,writerIndex 都会相应增加(如 writeShort 就会增加 Short.BYTES)。如果写入的长度大于 capacity - writerIndex,则自动扩容。


后记 & 常用链接 & 补充阅读

Forge的通信 http://www.mcbbs.net/thread-711966-1-1.html
Spigot Javadocs https://hub.spigotmc.org/javadocs/spigot/
Sponge Javadocs https://jd.spongepowered.org/5.1.0/?overview-summary.html
Forge 1.7.10的Javadocs http://jd.ddmcloud.com/forge/1.7.10/
Netty Javadocs http://netty.io/4.1/api/index.html
ByteBuf http://blog.csdn.net/z69183787/article/details/52980426
http://www.2cto.com/kf/201604/500997.html
线程通信 http://www.cnblogs.com/hapjin/p/5492619.html
你可以用你的天才般的设想,让插件服务器玩起来像 Mod 一样,一切都是有可能的
我是一名 Bukkit 插件开发者,碰巧会一丁点的 Forge 开发,甚至刚学了一点 Sponge 开发。
使用 Forge 1.8.9 作为示范。


来自群组: PluginsCDTribe

评分

参与人数 21人气 +34 金粒 +282 贡献 +3 收起 理由
b1250447239 + 2 + 30 太强了
Mistanbul + 20 神乎其技!6的飞起!
南外丶仓鼠 + 2 神乎其技!6的飞起!
和煦樱 + 2 + 40 神乎其技!6的飞起!
MZIMU + 1 + 5 大赞,总算是发现了。。。
诺凡克罗诺 + 2 + 40 神乎其技!6的飞起!
byxiaobai + 2 MCBBS有你更精彩~
CatSeed + 2 神乎其技,不服不行!
1257375501 + 2 MCBBS有你更精彩~
wodewode + 12 MCBBS有你更精彩~
光の旋律 + 1 + 10 神乎其技,不服不行!
GiNYAi + 2 MCBBS有你更精彩~
Ynglife + 1 感谢方法!
Mangocraft + 1 Ssssssssssssssssssss
土球球 + 5 + 60 + 3 神乎其技,不服不行!
清晨w + 1 + 20 神乎其技,不服不行!
V乐乐 + 1 + 15 !!~~~太赞
+ 3 + 25 MCBBS有你更精彩~
Ir.Nep + 2 神乎其技,不服不行!
耗子 + 2 MCBBS有你更精彩~

查看全部评分

GiNYAi 当前离线
积分
9135
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2012-3-31
查看详细资料
发表于 2020-2-4 00:12:54 | 显示全部楼层
Forge的SimpleNetworkWrapper(SimpleImpl) 实际上也是走的PluginMessageChannel

格式是
1位byte的 `id`
其他你写到ByteBuf里的东西

也就是
只要格式正确 Forge的接受和发送部分可以使用 SimpleImpl来处理
或者说Bukkit插件也可以处理ForgeMod用SimpleImpl发送过来的数据

评分

参与人数 1人气 +1 收起 理由
海螺螺 + 1 MCBBS有你更精彩~

查看全部评分

回复

使用道具 举报

wt6654499 当前离线
积分
181
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2015-2-23
查看详细资料
发表于 2017-9-6 10:47:28 | 显示全部楼层
支持技术!!!!
回复

使用道具 举报

653224281 当前离线
积分
488
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2013-7-2
查看详细资料
发表于 2017-9-7 11:14:59 | 显示全部楼层
支持一下 学习一下
回复

使用道具 举报

Spedin 当前离线
积分
2504
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2015-8-28
查看详细资料
发表于 2017-9-7 20:06:33 | 显示全部楼层
哇,这样子的话以后的插件和mod是不是可以联合在一起去实现一些功能了!
回复

使用道具 举报

当前离线
积分
11364
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2015-1-1
查看详细资料
发表于 2017-9-8 12:01:27 | 显示全部楼层
支持下大佬!感谢分享。。。终于有一篇系统地讲述Forge和Bukkit通过Plm进行通讯的教程了!
为什么不早点写出来呢??回忆起我当初请教了N个大佬才勉强整会,当时要是有这个教程就好了

回复

使用道具 举报

海螺螺 当前离线
积分
19465
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2015-2-24
查看详细资料
 楼主| 发表于 2017-9-8 12:48:51 | 显示全部楼层
1582952890 发表于 2017-9-8 12:01
支持下大佬!感谢分享。。。终于有一篇系统地讲述Forge和Bukkit通过Plm进行通讯的教程了!
为什么不早点 ...

早点。。
我也想写,但是感觉没啥人想要呢
其实是之前没想过来编程版写教程,而且论坛有一个验证机器码的插件来着,可以用那个学习的
回复

使用道具 举报

li709854423 当前离线
积分
3778
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2012-8-12
查看详细资料
发表于 2017-10-10 10:37:38 | 显示全部楼层
问题是,在spigot的服务端里。能往独立的MOD里发消息吗?
回复

使用道具 举报

3TUSK 当前离线
积分
7767
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2013-3-8
查看详细资料
发表于 2017-10-10 10:51:41 | 显示全部楼层
li709854423 发表于 2017-10-10 10:37
问题是,在spigot的服务端里。能往独立的MOD里发消息吗?

Spigot 服务器插件 -> Forge 客户端的 Mod 是可以的。
Forge 客户端的 Mod -> Spigot 服务器插件也是可以的。
甚至 Spigot 服务器插件 <-> LiteLoader 客户端 Mod也是可以的。(理论上是这样。我没有试过。)

但是如果你是说 Spigot 服务器插件 -> Forge 服务器的 Mod.. 等等你是怎么把 MinecraftForge 装到 Spigot 服务器端上的
回复

使用道具 举报

li709854423 当前离线
积分
3778
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2012-8-12
查看详细资料
发表于 2017-10-10 11:07:23 | 显示全部楼层
u.s.knowledge 发表于 2017-10-10 10:51
Spigot 服务器插件 -> Forge 客户端的 Mod 是可以的。
Forge 客户端的 Mod -> Spigot 服务器插件也是可以 ...

我的意思就是独立MOD。。独立MOD的意思就是不存在服务端。。。
也就说你应该是解决了我的一个误区。。我一直以为spigot服务端是无法往客户端MOD发消息的。。现在看来大有可为了
回复

使用道具 举报

3TUSK 当前离线
积分
7767
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2013-3-8
查看详细资料
发表于 2017-10-10 11:47:20 | 显示全部楼层
li709854423 发表于 2017-10-10 11:07
我的意思就是独立MOD。。独立MOD的意思就是不存在服务端。。。
也就说你应该是解决了我的一个误区。。我 ...

我从未听说过有这样使用“独立Mod”这个词的。
独立Mod这个词通常指不需要任何前置(不含library mod)的Mod,比如像IC2、暮色这样的就可以用独立Mod这个词来描述,但是IC2高级机器这个Mod需要IC2作为前置,就不能称之为独立Mod了。

我一直以为spigot服务端是无法往客户端MOD发消息的。。


这篇文章讲述的就是 Bukkit 服务器插件与 MinecraftForge 客户端 Mod 之间如何通信。Spigot 仍然是 Bukkit
的超集
。难道 Spigot 摆脱了Bukkit API?
回复

使用道具 举报

li709854423 当前离线
积分
3778
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2012-8-12
查看详细资料
发表于 2017-10-10 18:37:37 | 显示全部楼层
u.s.knowledge 发表于 2017-10-10 11:47
我从未听说过有这样使用“独立Mod”这个词的。
独立Mod这个词通常指不需要任何前置(不含library mod)的 ...

事实上我之前一直用的在KC端进行插件控制MOD发包。当时我以为是因为KC端才允许这样..也没去spigot试过仔细想想在通道一致地址一致的情况下。。肯定是可以互相收包发包的
至于独立MOD。。个人理解or误解的问题就没必要讨论了
回复

使用道具 举报

RINNDA 当前离线
积分
5194
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2015-9-20
查看详细资料
发表于 2018-2-4 20:57:28 | 显示全部楼层
        神乎其技,不服不行! 这个教程很易懂
回复

使用道具 举报

Ynglife 当前离线
积分
190
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2016-1-3
查看详细资料
发表于 2018-7-10 05:13:18 | 显示全部楼层
非常感谢,刚好脱离了1.7.10的bukkit,正愁该怎么翻Sponge的文档就看到了这个。
回复

使用道具 举报

wysljjzzh 当前离线
积分
122
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-6-20
查看详细资料
发表于 2019-6-24 10:42:21 | 显示全部楼层
楼主的帖子很实用,收藏了~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册(register)

本版积分规则

Archiver|小黑屋|Mcbbs.net ( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版

GMT+8, 2021-5-14 06:11 , Processed in 0.077446 second(s), Total 42, Slave 34 queries, Release: Build.2021.04.28 1615, Gzip On, Redis On.

"Minecraft"以及"我的世界"为Mojang Synergies AB的商标 本站与Mojang以及微软公司没有从属关系

© 2010-2020 我的世界中文论坛 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载

快速回复 返回顶部 返回列表