Skip to content

Examples

Hang edited this page Jul 12, 2024 · 12 revisions

客户端通信协议

客户端服务端可以采用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>

netty版socket网络通信(亦可选择mina)

<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在对应的业务模块内部自行定义即可,无需统一管理。

业务处理器(消息路由)

  1. 基于java的反射技术。每一个模块用一个类,模块的每一种操作绑定对应的方法体。
  2. 模块消息映射器命名为XXController,并使用@MessageRoute注解。
  3. 绑定请求消息与对应的Method方法,使用@RequestMapper注解,获取请求参数后,转交给对应的业务管理类。

添加需要持久化的db实体

当你有个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命令允许你快速修改内存数据,你只需要编写自己的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();
	}
}

测试环境部署(Linux环境)

在测试环境,只需执行代码根目录下的onekey.sh脚本即可一键完成停服->更新->起服的操作。
若只需要执行当中任一步骤,则执行根目录下的admin.sh,根据需要传入stop/update/start参数。