用户:
Nomen查看:0 回复:3 评论:0 创建时间:2023-12-28T09:20:08
这里是没有开场白的Nomen,如果你用不习惯RemoteWrapper这样的形式的通信,可以尝试一下SocketWrapper
同时在这次与RemoteWrapper的更新中增加了虚拟端口这一概念,防止多个工具之间通信冲突,有了端口之后,我们就可以同时使用多个工具啦
传送门前往SocketWrapper1.1.0:
shequ.codemao.cn/community/680102
目前这个工具还较为简陋,模拟了部分WebSocket的特性与方法,这是一个简单的例子来入门
// 注:SocketWrapper系列默认通信虚拟端口为6100
// 一个简单的通信示例
// SERVER index.js
let ssw = new SocketServerWrapper(); // 初始化
ssw.on("connect", (client /* 当连接成功后,会实例化一个Socket */) => {
console.log(`[server] 连接成功,实例id: ${client.id}`); // 当实例连接成功时打印消息
client.listen("message", (d) => console.log(`[server] 收到来自客户端发送的消息: ${d}`));
client.listen("disconnect", (d) => { console.log(`[server] 客户端断开了连接,理由是: ${d.reason}`) });
client.send("你好客户端!");
client.ping(() => console.log(`[server] pong!`)); // 尝试ping一下,当收到pong之后会执行回调
});
// CLIENT clientIndex.js
let scw = new SocketClientWrapper(); // 初始化
scw.onmessage = (d) => console.log(`[client] 收到来自服务端的消息: ${d}`);
scw.onerror = console.error; // 用于输出报错信息
scw.onopen = () => {
scw.send("hello!"); // 发送消息
scw.ping(() => { // 当收到pong之后用理由"1145"关闭会话
console.log(`[client] pong!`);
scw.close(1145);
})
};
scw.onclose = (code)=>{
console.log(`[client] 服务器断开了连接,理由是: ${code}`);
}
Socket.server.js
class SocketError extends Error {
constructor(message, socket) {
super(message);
this.name = "SocketError";
this.socket = socket;
}
}
/**
* @typedef SocketEventToken
* @property {Function} cancel 调用后取消事件
*/
class Socket {
static messageType = {
DATA: "data",
PING: "ping",
PONG: "pong",
CONNECT: "connect",
CLOSE: "close"
}
static states = {
CONNECTING: 0,
OPEN: 1,
CLOSED: 2
}
/**
* 请注意,一般来讲你不应该手动实例化这个类,该类用于与客户端进行通信,需要进行一些配置,SocketServerWrapper会自动操作
* @constructor
* @param {GameEntity} entity 监听的玩家实例
* @param {number} id 会话id
* @param {number} port 端口号
*/
constructor(entity, id, port) {
this.target = entity;
this.state = Socket.states.CONNECTING;
this.listener = [];
this.id = id;
this.port = port;
this.events = {
"message": [],
"error": [],
"connect": [],
"disconnect": [],
"ping": [],
"_pong": []
};
this._start();
}
/**
*
* @param {"message"|"error"|"connect"|"disconnect"|"ping"} type 监听类型
* @param {Function} f 当事件触发时运行,message事件会收到数据,其他事件没有任何参数传入
* @returns {Object} 取消令牌
* @returns {SocketEventToken} 事件取消
*/
listen(type, f) {
if (Object.keys(this.events).includes(type)) {
this.events[type].push(f);
return {
cancel: () => this.events[type] = this.events[type].filter(v => v !== f)
}
}
}
removeListener(type) {
if (this.events[type] && !type.startsWith("_")) this.events[type] = [];
}
/**
* 发送一条消息
* @param {JSONValue} message 发送的数据
* @param {Object} [c] 额外配置
*/
send(message, c) {
try {
if (this.state === Socket.states.OPEN) remoteChannel.sendClientEvent(this.target, this.wrap({
data: message,
...c
}));
else throw new SocketError("套接字实例非连接状态", this);
} catch (err) {
this._emit("error", err);
}
}
/**
* 断开连接
* @param {number} code 根据协议,你需要提供一个连接断开的理由,默认为1005,即CLOSE_NO_STATUS
*/
disconnect(code = 1005) {
this.send({reason: code}, {type: "disconnect"})
this._emit(this._parse(args).type, this._parse(args).data?.reason);
this._stop();
}
wrap({ id = this.id, data, type = Socket.messageType.DATA, time = Date.now() }) {
return {
id,
data,
type,
time
}
}
/**
* 尝试发送一条ping消息,如果客户端处于连接状态并且符合连接协议,则会返回pong消息,可以通过观察是否超时来判定
* @example ping((time)=>console.log(`客户端依旧存活,当前时间${new Date(time).toString()}`));
* @param {Function} f 当收到pong之后触发
* @returns {Promise<void>} 当收到pong之后承诺被解决
*/
async ping(f) {
if (this.state !== Socket.states.OPEN) throw new Error("套接字实例非连接状态");
return new Promise(resolve => {
remoteChannel.sendClientEvent(this.target, this.wrap({
type: Socket.messageType.PING,
}));
let r = this.listen("_pong", () => {
f(Date.now());
r.cancel();
resolve(Date.now());
})
})
}
_emit(type, ...args) {
if (this.events[type]) {
this.events[type].forEach(v => v(...args));
}
}
_parse(d) {
if (typeof d === "object" && d !== null) return d;
else return { data: d };
}
_start() {
if (this.state !== Socket.states.CONNECTING) return;
this.state = Socket.states.OPEN;
this._emit("connect");
this.listener.push(remoteChannel.onServerEvent(({ args }) => {
if (this._parse(args).port === this.port && this._parse(args).id === this.id) {
if (this._parse(args).type === Socket.messageType.PING) {
this.send(undefined, {type: Socket.messageType.PONG});
this._emit("ping");
} else if (this._parse(args).type === Socket.messageType.CLOSE) {
this._emit(this._parse(args).type, this._parse(args).data?.reason);
this._stop();
} else if (this._parse(args).type === Socket.messageType.DATA) {
this._emit("message", this._parse(args).data);
}else {
this._emit(this._parse(args).type, this._parse(args).data);
}
}
}), world.onPlayerLeave(({ entity }) => {
if (entity === this.target) {
this._emit("disconnect");
this._stop();
}
}));
}
_stop() {
if (this.state === Socket.states.OPEN) {
this.state = Socket.states.CLOSED;
this.listener.forEach(v => v.cancel());
Object.keys(this.events).forEach(k => this.removeListener(k));
}
}
}
class SocketServerWrapper {
static get defaultConfig() {
return {
port: 6100,
}
}
/**
* @param {Object} [config] 配置
* @param {number} [config.port=6100] 端口号,默认为6100
*/
constructor(config = {}) {
this.events = {};
this.clients = [];
this.listeners = [];
this._assign(config);
this._start();
}
/**
* @callback SocketServerEvent
* @param {Socket} client Socket连接实例
* @param {JSONValue} [data] 本次消息的数据
*/
/**
*
* @param {"connect"|"message"|"disconnect"} type 监听类型
* @param {SocketServerEvent} f 回调函数,参数为client,data
* @param {number} [id] 会话id
* @returns {SocketEventToken} 事件取消
*/
on(type, f, id) {
this._registerEvent(type);
if (typeof f === "function") {
this.events[type].push({ handler: f, id });
return {
cancel: () => this.events[type] = this.events[type].filter(v => v.handler !== f)
}
}
}
_start() {
this.listeners.push(remoteChannel.onServerEvent(({ entity, args }) => {
let a = this._toData(args);
if (a.port !== this.port) return;
if (a.type === "connect") {
let client = new Socket(entity, Date.now(), this.port);
client.listen("disconnect", () => this._emit("disconnect", a.id, client))
client.send({ id: client.id }, { type: Socket.messageType.CONNECT });
this._emit("connect", a.id, client);
} else if (a.type === "message" && this.clients.some(c => c.id === a.id && c.target === entity)) {
this._emit("message", a.id, this.clients.filter(c => c.id === a.id && c.target === entity)[0], a.data);
}
}))
}
_toData(data) {
if (typeof data === "object" && data !== null) return data
else return { data };
}
_assign(c) {
let o = {}, dc = this.constructor.defaultConfig;
Object.keys(dc).forEach(v => {
if (c[v]) o[v] = c[v];
else o[v] = dc[v];
});
Object.assign(this, o);
}
_emit(type, id, ...args) {
if (Array.isArray(this.events[type])) {
this.events[type].filter(v => !id || v.id === id).forEach(f => {
f.handler(...args);
})
}
}
_registerEvent(type) {
if (!this.events[type]) this.events[type] = [];
}
}
Socket.client.js
function SocketClientWrapper({ port } = {}){
if (!new.target) throw "SocketClientWrapper must be called with new";
this.port = port || 6100;
this.state = 0;
this.listeners = [];
this.id = null;
Object.assign(this, {
CONNECTING: 0,
OPEN: 1,
CLOSED: 2,
onopen: ()=>{},
onmessage: ()=>{},
onerror: ()=>{},
onclose: ()=>{},
onpong: ()=>{}
});
let wrap = (message, type) => {
return {
port: this.port,
type,
id: this.id,
data: message,
}
}
function exec(f, ...args){
if(typeof f==="function") return f(...args);
else return null;
}
let error = (e)=>{
exec(this.onerror, e);
return new Error(e);
}
this.ping = function (f) {
this.send(undefined, "ping");
this.onpong = f;
}
this.send = function (message, type = "data") {
if (this.state !== this.OPEN) {
throw error("套接字实例非连接状态");
}
if (!this.id) {
throw error("无法正确获取会话id,请尝试重新实例化!");
}
remoteChannel.sendServerEvent(wrap(message, type));
}
this.close = function (code = 1005) {
this.send({reason: code}, "disconnect");
this.state = this.CLOSED;
exec(this.onclose, code);
this.listeners.forEach(v => remoteChannel.events.remove("client", v));
}
let _l = (v) => {
let value = (typeof v === "object" && v !== null) ? v : { data: v };
if (this.state === this.CONNECTING) {
if (value.type === "connect") {
this.state = this.OPEN;
this.id = value.data.id;
exec(this.onopen, this);
}
} else if (this.state === this.OPEN) {
if (value.type === "ping") {
this.send(undefined, "_pong");
} else if (value.type === "data") {
exec(this.onmessage, value.data);
} else if (value.type === "disconnect") {
this.state = this.CLOSED;
this.listeners.forEach(v => remoteChannel.events.remove("client", v));
exec(this.onclose, value.data.reason);
} else if (value.type === "pong"){
exec(this.onpong);
}
}
}
this.listeners.push(_l);
remoteChannel.events.on("client", _l);
remoteChannel.sendServerEvent({type:"connect", port: this.port});
}
---
好消息,七莓大佬编写的UiWrapper收录进了Box3-Tools仓库中,该库能够便捷的管理GUI
github.com/helloyork/Box3-Tools/tree/main/src/GUI/UiWrapper/1.1.0
神岛吉吉喵imgsrc="https://static.codemao.cn/emoji/codemao/%E7%BC%96%E7%A8%8B%E7%8C%AB_%E7%82%B9%E8%B5%9E.gif"alt="emotion_编程猫_点赞"
点赞0
评论