行业资讯

  • 首页
  • 新闻中心
  • 行业资讯

手游服务端框架之跨服匹配服,手游服务器租用


2018年08月29日

如今的手游世界,如果没搞个跨服赛事,都不好意思说它是一个手游了。

说到跨服,就不得不说下匹配服了。比如一个跨服天梯赛事,需要满足不同服的玩家能够同屏PK。为了能够把实力接近的玩家作为对手,我们需要一个独立的匹配服来收集数据,然后进行房间分配。匹配服,也是跨服赛设计的基础。

典型的匹配服通信层我们可以采用http,也可以采用socket。本文将采用http作为游戏服与匹配服的通信层。选择http方式,我们可以搭个tomcat服务,非常方便。当然,如果不使用tomcat的话,我们也可以使用mina或者netty本身的http服务。

设计思路也非常简单,有点像游戏服的业务处理器。我们需要做到,对于不同的请求,我们都绑定一个方法与之对应。而对于数据的编解码,由于匹配服的通信数据一般都比较短,我们直接用json进行序列化即可。

下面,开始我们的编码。

★如有手游服务器租用可咨询宇众临风,QQ:2850293179   Tel:15999932452   订购网址:www.yuzhongidc.com

搭建mina的http服务

在前面游戏后台设计中,我们已经看到如何使用mina搭建http服务了。


 
  1. /**

  2. * 匹配服http服务

  3. * @author kingston

  4. */

  5. public class MatchServer {

  6.  
  7. private Logger logger = LoggerFactory.getLogger(MatchServer.class);

  8.  
  9. private IoAcceptor acceptor;

  10.  
  11. //http端口

  12. int port = 8899;

  13.  
  14. public void start() throws Exception {

  15. acceptor = new NioSocketAcceptor();

  16. acceptor.getFilterChain().addLast("codec", new HttpServerCodec());

  17. acceptor.setHandler(new HttpServerHandle());

  18.  
  19. acceptor.bind(new InetSocketAddress(port));

  20.  
  21. logger.error("---------> http server start at port:{}", port);

  22. }

  23.  
  24. public void shutdown() {

  25. if (acceptor != null) {

  26. acceptor.unbind();

  27. acceptor.dispose();

  28. }

  29. logger.error("---------> http server stop at port:{}", port);

  30. }

  31. }

  32.  
  33. class HttpServerHandle extends IoHandlerAdapter {

  34.  
  35. private static Logger logger = LoggerFactory.getLogger(MatchServer.class);

  36.  
  37. @Override

  38. public void exceptionCaught(IoSession session, Throwable cause)

  39. throws Exception {

  40. }

  41.  
  42. @Override

  43. public void messageReceived(IoSession session, Object urlParams)

  44. throws Exception {

  45. if (urlParams instanceof HttpRequest) {

  46. // 请求,解码器将请求转换成HttpRequest对象

  47. HttpRequest request = (HttpRequest) urlParams;

  48. Message msg = parseHttpRequest(request);

  49. UrlDispatcher.getInstance().dispatch(session, msg);

  50. }

  51. }

  52.  
  53. @SuppressWarnings("unchecked")

  54. private Message parseHttpRequest(HttpRequest httpReq) {

  55. String service = httpReq.getParameter("service");

  56. if (StringUtils.isEmpty(service)) {

  57. return null;

  58. }

  59.  
  60. Class<?> clazz = UrlDispatcher.getInstance().getMessageClazzBy(service);

  61. String paramJson = httpReq.getParameter("param");

  62. if (StringUtils.isNotEmpty(paramJson)) {

  63. try{

  64. return (Message)new Gson().fromJson(URLDecoder.decode(paramJson), clazz);

  65. }catch(Exception e) {

  66. e.printStackTrace();

  67. }

  68. }

  69. return null;

  70. }

  71.  
  72. }

消息通信

在游戏服,我们发出一条http请求。匹配服为了将请求分发到对应的处理器,我们需要为每一条消息作一个标记。最简单的,可以使用请求消息的类名。所以,我们必须把业务签名和参数都融合到url里面去。也就是说,一个有效的url可能是这样:

http://localhost:8899?service=MReqLadderApplyMessage&param={"playerId":0,"score":0,"power":0}

为了能区别游戏服和匹配服的消息类型,我们匹配服的消息,都加一个M(Match)前缀,那么请求协议就MReq,响应协议就是MRes了。

对于游戏服来说,发出的请求属于Message的子类,返回的消息也是Message的子类。底层帮我们实现了消息的编解码。我们可以看下代码实现。


 
  1. public class MatchHttpUtil {

  2.  
  3. public static Message submit(Message request) throws IOException {

  4. String signature = request.getClass().getSimpleName();

  5. String data = new Gson().toJson(request);

  6. String param = HttpUtil.buildUrlParam("service", signature,

  7. "param", data);

  8.  
  9. String url = "http://localhost:8899" + "?" + param;

  10. System.err.println("发送url:" + url);

  11. String resultJson = HttpUtil.get(url);

  12. UrlResponse urlResponse = new Gson().fromJson(resultJson, UrlResponse.class);

  13.  
  14. String respClazz = urlResponse.getAttachemt();

  15. Class<?> msgClazz = MatchMessageFactory.getInstance().getMessageBy(respClazz);

  16. Message msgResponse = (Message)new Gson().fromJson(urlResponse.getMessage(), msgClazz);

  17. return msgResponse;

  18. }

  19.  
  20. }

 

业务处理器

我们依然使用 @Controller注解来标识一个模块处理器,使用@RequestMapper注解来标记业务处理方法。不同的是,在游戏服我们每个消息的元信息都带有一个模块号和子类型号。在匹配服,我们就不这里处理了。因为匹配服的业务比较少。我们直接用消息类的名称作为业务签名即可。

在业务分发器,我们保存每一个方法签名,与对应的方法处理器。


 
  1. public class UrlDispatcher {

  2.  
  3. private Logger logger = LoggerFactory.getLogger(getClass());

  4.  
  5. private volatile static UrlDispatcher instance;

  6.  
  7. /** [message signature, CmdExecutor] */

  8. private static final Map<String, CmdExecutor> service2Handler = new HashMap<>();

  9.  
  10. private static final Map<String, Class<?>> signature2Message = new HashMap<>();

  11. }

匹配服在收到一个http请求,通过参数解析得到对应的业务签名,同时通过json反序列化得到请求消息的参数。将消息分发到对应的业务处理器。代码如下: 


 
  1. public void dispatch(IoSession session, Message message) {

  2. String signature = buildSignature(message.getClass());

  3. CmdExecutor cmdExecutor = service2Handler.get(signature);

  4. if (cmdExecutor == null) {

  5. logger.error("message executor missed, signature={}", signature);

  6. return;

  7. }

  8.  
  9. Object[] params = convertToMethodParams(session, cmdExecutor.getParams(), message);

  10. Object controller = cmdExecutor.getHandler();

  11. try {

  12. //通过反射

  13. cmdExecutor.getMethod().invoke(controller, params);

  14. }catch(Exception e) {

  15. logger.error("", e);

  16. }

  17. }

一个完整的业务处理器,代码如下 (可以看出,跟游戏服是非常类似的):


 
  1. @Controller

  2. public class LadderController {

  3.  
  4. @RequestMapping

  5. public void apply(IoSession session, MReqLadderApplyMessage request) {

  6. HttpMessagePusher.push(session, new MResLadderApplySuccMessage());

  7. }

  8.  
  9. }

示例代码

启动匹配服服务器(MatchStartup.java)

再执行游戏服的单元测试


 
  1. public class TestMatchHttp {

  2.  
  3. @Test

  4. public void httpRquest() throws IOException {

  5. Message response = MatchHttpUtil.submit(new MReqLadderApplyMessage());

  6.  
  7. System.err.println("收到响应<<<<<<<<<" + response);

  8. }

  9. }

 

手游服务端开源框架系列完整的代码请移步github ->> jforgame


客服