-
Notifications
You must be signed in to change notification settings - Fork 308
Examples
客户端服务端可以采用protobuf作为通信协议,同时,为了避免省去编写.proto文件的麻烦,引入jprotobuf工具库,只需要加入相应注解即可。
jprotobuf本来不需要为协议字段添加相应的getter/setter方法,为了让通信层便于切换,还是推荐加入这些方法。
由于通信层的设计是服务端客户端工作对接最为关键的第一步,需要双方共同决定选择哪种通信协议最为方便。
作为可选方案,也提供了基于java的反射机制自动完成消息的编解码。
使用protobuf编解码,只需引入以下依赖
<dependency>
<groupId>io.github.jforgame</groupId>
<artifactId>jforgame-codec-protobuf</artifactId>
<version>1.0.0</version>
</dependency>
使用反射自动编解码,只需引入以下依赖
<dependency>
<groupId>io.github.jforgame</groupId>
<artifactId>jforgame-codec-struct</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>io.github.jforgame</groupId>
<artifactId>jforgame-socket-netty</artifactId>
<version>1.0.0</version>
</dependency>
采用默认组件的话,只需一行代码启动服务器
TcpSocketServerBuilder.newBuilder().bindingPort(HostAndPort.valueOf(ServerConfig.getInstance().getServerPort()))
.setMessageFactory(GameMessageFactory.getInstance())
.setMessageCodec(new StructMessageCodec())
.setSocketIoDispatcher(new MessageIoDispatcher(ServerScanPaths.MESSAGE_PATH))
.build()
.start();
客户端通信协议需要实现Message接口,请求协议统一以Req开头,推送协议统一以Res开头。Message类需用使用@MessageMeta绑定模块号与cmd。 业务模块号在Modules申明,而cmd在对应的业务模块内部自行定义即可,无需统一管理。
- 基于java的反射技术。每一个模块用一个类,模块的每一种操作绑定对应的方法体。
- 模块消息映射器命名为XXController,并使用@MessageRoute注解。
- 绑定请求消息与对应的Method方法,使用@RequestMapper注解,获取请求参数后,转交给对应的业务管理类。
当你有个db数据需要持久化,比如Player对象,只需要让该类继承BaseEntity, 同时Player类加上@Entity,然后在各个需要持久化的字段加上@Column注解。需要特别注意的是,每个BaseEntity至少需要一个字段使用@Id注解作为主键,用于查找与修改操作。只要添加这些注解,框架会自动帮您在数据库生成新表或者增加新的字段,无须手动操作数据库schema。 当你的Player对象需要入库,你只需要这样操作(无须手动设置db状态!!)
Player p = new Player();
p.setId(123456L);
p.setName();
DbService.getInstance().add2Queue(player);
当你的Player对象有属性变更,你只需要这样操作
player.setLevel(100);
DbService.getInstance().add2Queue(player);
本框架使用了guava cache构建了一套缓存系统,当你需要获取数据库某个对象时,如果对象已在内存,则直接返回该对象;否则,自动从数据库把它捞取上来并加入缓存,你只需要让对象的业务模块集成CacheService即可。例如:
public class PlayerManager extends CacheService<Long, Player>
使用@Listener和@EventHandler注解可以轻松将事件类型与对应的业务方法绑定在一起,例如:
@Listener
public class SkillListener {
@EventHandler(value=EventType.LEVEL_UP)
public void onPlayerLevelup(EventPlayerLevelUp levelUpEvent) {
System.err.println(getClass().getSimpleName()+"捕捉到事件"+levelUpEvent);
}
}
GM命令允许你快速修改内存数据,你只需要编写自己的gm命令相应的表达式模式,通过正则表达式解析参数,交给具体的业务代码处理。 例如 ^reloadConfig\s+([a-zA-Z_]+)" 这样的模型可以用来匹配 "reloadConfig CofingPlayerLevel" 这样的命令,表示重载"CofingPlayerLevel"这张配置表。
跨服请求与响应包,必须继承Traceable,绑定回调id。这里不在协议层设计的原因是,大部分消息无需该字段,减少消息头长度。
// 服务端
import jforgame.socket.share.IdSession;
import jforgame.socket.share.annotation.MessageRoute;
import jforgame.socket.share.annotation.RequestHandler;
@MessageRoute
public class HelloMsgRoute {
@RequestHandler
public Object sayHello(IdSession session, int index, ReqHello request) {
ResHello response = new ResHello();
response.setContent("hello, rpc");
response.setIndex(index);
return response;
}
}
// 客户端,同时支持同步模式、异步模式!!
SocketIoDispatcher msgDispatcher = new SocketIoDispatcherAdapter() {
@Override
public void dispatch(IdSession session, Object message) {
System.err.println("收到消息<-- " + message.getClass().getSimpleName() + "=" + JsonUtil.object2String(message));
}
@Override
public void exceptionCaught(IdSession session, Throwable cause) {
cause.printStackTrace();
}
};
SocketClient socketClient = new TcpSocketClient(msgDispatcher, GameMessageFactory.getInstance(), new StructMessageCodec(), hostPort);
IdSession session = socketClient.openSession();
ResHello response = (ResHello) RpcMessageClient.request(session, new ReqHello());
System.out.println("rpc 消息同步调用");
System.out.println(response);
RpcMessageClient.callBack(session, new ReqHello(), new RequestCallback() {
@Override
public void onSuccess(Object callBack) {
System.err.println("rpc 消息异步调用");
ResHello response = (ResHello) callBack;
System.err.println(response);
}
@Override
public void onError(Throwable error) {
System.out.println("----onError");
error.printStackTrace();
}
});
框架自带http服务器,允许后台管理网站通过http的方式发送管理命令。例如关闭游戏服务的请求如下
http://localhost:8080/?cmd=1
新增一条后台指令,非常方便,例如
@CommandHandler(cmd=HttpCommands.CLOSE_SERVER)
public class CloseServerCommandHandler extends HttpCommandHandler {
@Override
public HttpCommandResponse action(HttpCommandParams httpParams) {
LoggerSystem.HTTP_COMMAND.getLogger().info("收到后台命令,准备停服");
SchedulerManager.INSTANCE.registerUniqueTimeoutTask("http_close_server", () -> {
Runtime.getRuntime().exit(0);
}, 5*1000);
return HttpCommandResponse.valueOfSucc();
}
}
在测试环境,只需执行代码根目录下的onekey.sh脚本即可一键完成停服->更新->起服的操作。
若只需要执行当中任一步骤,则执行根目录下的admin.sh,根据需要传入stop/update/start参数。