|
47 | 47 | import static cn.jsmod2.core.utils.Utils.*; |
48 | 48 |
|
49 | 49 | /** |
50 | | - * jsmod2 cn.jsmod2.server class |
| 50 | + * JSMod2是一款基于协议(JSmod2协议)开发的一款使得java插件可以开发SCPSL:SCP基金会秘密实验室 |
| 51 | + * 插件的一个框架,这个框架主要由两部分组成 |
| 52 | + * JPLS和JSmod2组成,JPLS的全称叫做Java Plugin Loading System,JSmod2称为Java Server Mod |
| 53 | + * Java Server Mod提供了开发插件的接口和工具类,JPLS则是驱动插件生效的核心,同时C#服务端必须装配 |
| 54 | + * ProxyHandler(协议代理插件)才能使得java插件真正生效于指定的客户端 |
| 55 | + * JSMod2同时提供了 |
| 56 | + * JSMod2Manager:基于python开发的管理控制平台 |
| 57 | + * JSMod2DevelopmentKit:JSMod2开发工具包,结合在JSmod2中 |
| 58 | + * JSMod2 Repo:JSMod2的maven控制平台 |
| 59 | + * http://repo.noyark.net/nexus |
| 60 | + * 同时JSMod2的源代码在github开放,并作为主要的版本控制平台 |
| 61 | + * https://www.github.com/jsmod2-java-c |
| 62 | + * JSMod2的官方网站: |
| 63 | + * http://jsmod2.cn |
| 64 | + * 以上就是JSMod2的执行体系,即JSmod2的结构 |
| 65 | + * | 这一部分都是JSmod2的组件 | |
| 66 | + * JSmod2->插件->JPLS->ProxyHandler->ServerMod->MultiAdmin/LocalAdmin->Game |
51 | 67 | * |
52 | | - * @author magiclu550 #(code)jsmod2 |
| 68 | + * 首先上一届课讲解了开发一款JavaServerMod插件的流程,JSMod2已经提供了方便的插件加载框架 |
| 69 | + * 并且提供了web网站开发的支持 |
| 70 | + * <code> |
53 | 71 | * |
| 72 | + * @Controller |
| 73 | + * public class Test{ |
| 74 | + * |
| 75 | + * @RequestMapping("/jsmod2") |
| 76 | + * public String hello(){ |
| 77 | + * return "index.html"; |
| 78 | + * } |
| 79 | + * } |
| 80 | + * </code> |
| 81 | + * 您完全可以在代码中使用这种形式进行开发网站,访问网站使用ip:jsmod2的默认web服务器端口/jsmod2即可 |
| 82 | + * |
| 83 | + * 1.JSmod2的心脏: Server类和DefaultServer类 |
| 84 | + * 这两个类是JSMod2的核心部分,一切能够让java和ServerMod交互,其实都是依靠了这个类/对象为基础,Server类 |
| 85 | + * 和ServerMod的Server是不一样的 |
| 86 | + * |
| 87 | + * - Server为基类,实现了IServer接口,IServer继承了Start和Closeable,Reloadable接口 |
| 88 | + * - Start主要包含了start方法(startWatch方法其实和start的作用几乎差不多),Closeable包含close方法,Reloadable包含reload方法 |
| 89 | + * - 分析Server的作用 |
| 90 | + * - Server的第一个作用,就是存储了这个服务端运行时的一切基本信息 |
| 91 | + * - 语言属性 lang |
| 92 | + * - jar包的所在目录 serverfolder |
| 93 | + * - 启动和成功的时间 startTime和startSuccessTime 启动时间就是(startSuccessTime-startTime)ms |
| 94 | + * - 指令的基本信息 |
| 95 | + * - 一系列最常用的常量(在项目中使用频率最高) |
| 96 | + * - 记录管理员信息的对象OpsFile |
| 97 | + * - 使用的是udp还是tcp |
| 98 | + * - 是否和游戏已经对接 |
| 99 | + * - Server的第二个作用,提供了这个服务端运行的基本对象 |
| 100 | + * - LineReader 命令行对象 |
| 101 | + * - RuntimeServer 正在运行的服务器对象 |
| 102 | + * - Logger 打印日志信息的对象 |
| 103 | + * - plugins 服务器的插件对象 |
| 104 | + * - PluginManager 服务器的插件管理对象 |
| 105 | + * - RegisterTemplate 注册机对象:就是管理一些字典信息的对象,用于运行过程根据情况获取,从而使得数据严谨整齐分区安放,比较易于管理 |
| 106 | + * - 第三个作用,运行监听线程:start和startWatch |
| 107 | + * - 根据情况启动ListenerThread(TCP或者UDP) |
| 108 | + * - ListenerThread UDP |
| 109 | + * - ListenerThreadTCP TCP |
| 110 | + * - ListenerThread的作用是监听来自ProxyHandler的信息 |
| 111 | + * - ProxyHandler由c#编写,后期将ProxyHandler会讲解 |
| 112 | + * - ProxyHandler会在事件发生的时候发送数据包(EventPacket),JSmod2的 |
| 113 | + * 监听线程会根据数据包id从Register中的registerEvents()中找到对应的class对象 |
| 114 | + * 例如 -> 0x01 -> 找到AdminQueryEvent.class 之后调用newInstance()产生事件对象 |
| 115 | + * 之后通过callEvent(传入一个Event对象)调用之前注册的监听器方法 |
| 116 | + * - ListenerThread启动后,会将来自ProxyHandler的base64字符串解析成jsmod2协议的字符串(实际上是一个json字符串) |
| 117 | + * 之后将json字符串拆分注入 |
| 118 | + * |
| 119 | + * 完整的事件jsmod2协议长这个样子:0x01-{}|||playerName:UUID序列|||admin-playerName:UUID序列 |
| 120 | + * 这里会把AdminQueryEvent中的playerName字段注入UUID序列(这个序列是为了从Server Mod中找到它所对应的对象) |
| 121 | + * 然后从AdminQueryEvent中找到admin字段,是Player类型,然后Player类型中也有playerName字段,因此就把admin中的 |
| 122 | + * playerName字段注入进去这个UUID序列(而且playerName和admin-playerName对应的uuid序列都是不同的)这样每个复杂对象 |
| 123 | + * 都有一个uuid对应,在proxyHandler中,uuid将会和一个对象绑定在一起 |
| 124 | + * |
| 125 | + * 此时ProxyHandler中的apiMapping就是这样的(在事件触发时就会把这两个信息放了进去,这里省略其他复杂对象) |
| 126 | + * { |
| 127 | + * "UUID1":"AdminQueryEvent对象1", |
| 128 | + * "UUID2":"admin对象1" |
| 129 | + * ... |
| 130 | + * } |
| 131 | + * |
| 132 | + * 当在jsmod2中,使用这个admin |
| 133 | + * <code> |
| 134 | + * public void onAdmin(IAdminQueryEvent e){ |
| 135 | + * e.getAdmin().getName(); |
| 136 | + * } |
| 137 | + * </code> |
| 138 | + * 此时getName发出了这个包 |
| 139 | + * { |
| 140 | + * "id":"xxx"//具体id暂时不用知道 |
| 141 | + * "field":"Name" |
| 142 | + * "playerName":"UUID2" |
| 143 | + * } |
| 144 | + * 之后传输到ProxyHandler |
| 145 | + * ProxyHandler会先读取playerName找到UUID2 |
| 146 | + * 然后在通过UUID2,从apiMapping中找到admin对象1 |
| 147 | + * 之后admin对象1.Name获取返回值 |
| 148 | + * 然后通过JsonSetting对象把数据封装起来 |
| 149 | + * 之后返回 |
| 150 | + * 然后Socket此时进行write读取,然后通过BinaryStream的decode方法解码 |
| 151 | + * 得到Name的返回值 |
| 152 | + * 之后获取Name成功 |
| 153 | + * 这是事件触发和调用方法获取属性的运行流程,也是jsmod2的基本流程 |
| 154 | + * 游戏触发事件Event->之后把Event和它的复杂对象(如Player,TeamRole等等)放到apiMapping表里,并生成一个 |
| 155 | + * UUID Key对应着他们,然后发出协议,把uuid注入到jsmod2的对象进去,之后对象在执行方法时候,会发出数据包,里面包含 |
| 156 | + * 了该对象的uuid,ProxyHandler接受到后,通过这个uuid找到游戏里的这个对象,从而实现对应的操作 |
| 157 | + * |
| 158 | + * 另外这些操作是jsmod2真正的核心部分,也是实现了jsmod2核心功能的地方 |
| 159 | + * |
| 160 | + * 实现这些解码的核心组件是BinaryStream,也就是协议的翻译器 |
| 161 | + * |
| 162 | + * 2.JSMod2的大脑: BinaryStream |
| 163 | + * - BinaryStream在JavaServerMod中是思考的角色,对一切传来的协议进行解析,再对一切发出的协议组合 |
| 164 | + * - Server运行过程中,PacketManager是协议管理器,协议管理器只是充当了控制者和决策者角色,就是我该根据什么情况去调用什么地方 |
| 165 | + * - 如调用事件和调用指令 |
| 166 | + * - 根据id去判别 |
| 167 | + * - Manager接口提供了已经实现的通过ID和数据包信息调用事件和指令的接口 |
| 168 | + * - PacketManager事实上代码很简单 |
| 169 | + * - 然后再就是callEventByPacket, |
| 170 | + * - 就是定义一个EventStream(BinaryStream的子类) |
| 171 | + * - 从events找到class对象 |
| 172 | + * - 之后直接通过callEvent执行(callEvent如何执行的会在Plugin中讲解) |
| 173 | + * - 之后事件调用结束 |
| 174 | + * - 如果不存在这个id |
| 175 | + * - 那么就是关心命令调用 |
| 176 | + * - 命令调用两种方式,c#控制台调用和玩家调用 |
| 177 | + * - 这个是ProxyHandler发的id信息来判断的 |
| 178 | + * - 最终从数据包中获取这个VO对象(Value Object),其实就是把数据包信息拆解,组成的对象 |
| 179 | + * - 最终获得到VO对象,就根据情况来执行,如果是控制台命令,那么就直接runConsoleCommand |
| 180 | + * - 如果不是,就获得到Player对象,然后传进去 |
| 181 | + * - 最后socket会返回一个信息0xFF&1,对对方说明已经结束了指令和事件,游戏可以继续进行下去(在事件没有结束前,ProxyHandler是将 |
| 182 | + * 事件给阻塞掉的) |
| 183 | + * - Server的sendDataPacket 是对象调用时,如Player对象,就会使用这个方法(发出数据包),核心实现还是由BinaryStream实现 |
| 184 | + * - DataPacket是BinaryStream的子类 |
| 185 | + * - DataPacket分为 GetPacket和SetPacket(ControlPacket)和DoPacket以及SimplePacket(DoMethodPacket DoStream...) |
| 186 | + * - DataPacket的介绍在Jsmod2-protocol[参见(1)]里说明了 |
| 187 | + * - JSmod2前期采用一个Packet对应一个Handler,但是作者突发奇想,写出了SimpleHandler,即通用Handler,通过"反射"机制实现的Handler |
| 188 | + * 可以动态的设置和获取数值,这个解析器可以10行代码顶n行 |
| 189 | + * - 对应SimpleHandler就是SimplePacket(Packet的定义在cn.jsmod2.network.protocol下) |
| 190 | + * 这些大概就是BinaryStream的作用 |
| 191 | + * |
| 192 | + * 3.附带的组件 |
| 193 | + * - MultiAdminCommand 基于ProxyHandler的CommandHandler实现的,可以直接调用smod2上的命令 |
| 194 | + * multi HELP 查看smod2命令 multi 命令 参数1 参数2 调用smod2的命令 |
| 195 | + * - Config Framework 基于yaml json properties的Config框架,可以通过ConfigQueryer来定义对象(这样使用了对象池,节省内存开销) |
| 196 | + * |
| 197 | + * - Oaml Config 这是为jsmod2定制的配置文件格式,位于oaml包下,可以从https://github.com/noyark-system/noyark_oaml_java来获得使用 |
| 198 | + * 方法 |
| 199 | + * |
| 200 | + * - Plugin Loading Framework 是为JSmod2定制的基于URLClassLoader的插件加载框架,可以实现自动定义和自动注册的插件框架 |
| 201 | + * - 这个框架分为PluginClassLoader和PluginManager |
| 202 | + * - PluginClassLoader首先读取jar包,通过URLClassloader读取所有类对象,然后进行查找,如果找到配置文件则从plugin.yml读取信息,加载主类 |
| 203 | + * 如果没有配置文件,则找@Main注解,然后找到Main注解后开始加载Plugin对象,加载onLoad onEnable,在服务器停止前调用onDisable(强制停止则不会) |
| 204 | + * - 找到@Main后,会再尝试查找EnableRegister注解,如果没找到,则不进行自动注册,如果找到,则扫描全部实现Listener接口和继承Command对象的类,并 |
| 205 | + * 注册进去 |
| 206 | + * - 注册Command只是将Plugin注入进去,然后放在commands映射表(PluginClassLoader中),如果是Listener,则将类进行拆分,整理出带@EventManager |
| 207 | + * 的方法,之后下一步根据优先级整理成一个个列表,然后放在一个映射表中(首先根据方法的参数类型划分成一个个列表,再根据优先级对列表排序),之后发生事件时, |
| 208 | + * 通过callEvent调用,callEvent则是先得到事件类型,然后把有和这个事件类型相同参数类型(或者子类的类型)的方法找到,然后根据优先级依次执行这些方法 |
| 209 | + * <code> |
| 210 | + * public void onPlayerJoin(IPlayerJoinEvent e){ |
| 211 | + * |
| 212 | + * } |
| 213 | + * //发送callEvent |
| 214 | + * Event e = PlayerJoinEvent.class.newInstance();//动态生成的事件对象 |
| 215 | + * callEvent(e);//则会找到onPlayerJoin这个方法,之后执行 |
| 216 | + * 因此您可以通过这个特性来自定义事件 |
| 217 | + * </code> |
| 218 | + * - Emerald脚本语言 |
| 219 | + * - 基于java写的脚本语言,但是这个语言目前有点挫,但是可以结合命令行使用 |
| 220 | + * |
| 221 | + * |
| 222 | + * |
| 223 | + * 参见(1) |
| 224 | + * Jsmod2协议分为5种请求方式:Get,Set,IDSend,CommandRegister,CommandSender |
| 225 | + * 一种响应: Future响应 |
| 226 | + * Jsmod2端会发送Get和Set和CommandRegister |
| 227 | + * Get请求发送后,会有一个具体的返回值,Get请求基本不会修改对方端的具体参数,它的目的仅仅为了返回值 |
| 228 | + * Set请求发送后,不会有返回值,Set请求一定会修改对方端的具体参数,它的目的仅仅为了修改值 |
| 229 | + * CommandRegister请求,用于注册命令,没有返回值 |
| 230 | + * C#端会发送 |
| 231 | + * IDSend是发送Event请求,当发送Event对象时,将全部对象添加到请求链,并附带一个id账号 |
| 232 | + * CommandSender请求,用于执行jsmod2注册的命令 |
| 233 | + * |
| 234 | + * Future响应是一个二进制的响应串,Jsmod2的Response对象已经封装了它,进行了响应编译 |
| 235 | + * |
| 236 | + * Get和Set请求已经封装在了数据包中,直接使用send即可发包 |
| 237 | + * |
| 238 | + * 一个Get请求 |
| 239 | + * 111-{ |
| 240 | + * "id":"111", |
| 241 | + * "type":"item", |
| 242 | + * "field":"xxx", |
| 243 | + * "player":"ADE4-FL09-ADGB-Y9E6“ |
| 244 | + * } |
| 245 | + * 一个Set请求 |
| 246 | + * 111-{ |
| 247 | + * "id":"111", |
| 248 | + * "type":"item", |
| 249 | + * "do":"remove", |
| 250 | + * "player":"ADE4-FL09-ADGB-Y9E6“ |
| 251 | + * } |
| 252 | + * 111-{ |
| 253 | + * "id":"111", |
| 254 | + * "type":"item", |
| 255 | + * "kinematic":"true", |
| 256 | + * "player":"ADE4-FL09-ADGB-Y9E6“ |
| 257 | + * } |
| 258 | + * IDSend请求 |
| 259 | + * { |
| 260 | + * #省略,对象信息 |
| 261 | + * }|||"admin-playerName":"ADE4-FL09-ADGB-Y9E6" |
| 262 | + * |
| 263 | + * |
| 264 | + * Future响应 |
| 265 | + * { |
| 266 | + * #省略,对象信息 |
| 267 | + * }|||"admin-item":"ADE4-FL09-ADGB-Y9E6"@!{ |
| 268 | + * #省略,对象信息 |
| 269 | + * }|||"admin-item":"ADE4-FL09-ADGB-Y9E7" |
| 270 | + * |
| 271 | + * @author magiclu550 |
54 | 272 | */ |
55 | 273 |
|
56 | 274 | public abstract class Server implements IServer { |
|
0 commit comments