本文的客户端基于我们的GQT开源项目:http://cxlh.iteye.com/blog/2021463
本人拙见,如有不同意见,欢迎拍砖,同时献给特别有对服务端跨语言需求的程序猿们!
客户端(GQT Demo V3(服务端配套版).rar)太大请挪步到QQ群下载(群号:101189702),注明:GQT或Java,C++等,Java服务端源码在附件中下载;
总体设计思路:
- 高可用性:每个业务服务端都是独立的个体,任何一个业务服务器Crash时,都不会影响服务,并且业务服务器可以按需增加,业务服务端使用Java代码实现,主要是为了考虑更好的数据库操作,更好的事务支持,更好的社区支持以及可用更多的开源服务,提高开发效率;
- 自动负载均衡:当某个接口频繁调用,增加的响应服务器自动分流接口调用压力,也就说我们的异步的请求/响应模式下,有一个Broker仲裁程序决定客户端发来的请求交由哪个Java业务服务器来处理,这个Broker程序我们采用C++代码实现;
- 客户端(包括桌面或移动端)网关:根据策略选取远程Socket服务器连接,Socket服务器与服务器之间的消息通讯通过地址寻址远程路由,以便连在不同Socket服务器上的用户之间可以相互通讯,典型的应用就是聊天程序;
- Sub/Pub类消息:客户端订阅服务端的服务,服务端有多个Pub服务器(Java编写),交由Socket服务器将客户端Sub的消息Push给Client,此时Socket服务只作为Proxy用;
好了,其实这里只有2个C++核心组件(Socket服务器,Broker仲裁程序)和N个Java业务服务器(请求处理服务器,Pub服务器),以下介绍用法:
客户端发送请求,异步收取,编写业务代码步骤:
编写业务请求的服务端非常简单,看起来就这么几句:
public class StockServer extends BaseReqServer { private static Logger logger = LogManager.getLogger(StockServer.class.getName()); public StockServer(String ip, String port, ReqCallBack callback) { super(ip, port, callback); } public static void main(String[] args) { logger.info("=============StockServer========"); new Thread(){ @Override public void run() { ReqCallBack callback = new StockCallBack(); String ip = prop.getProperty("gqt-reqserver-ip"); String port = prop.getProperty("gqt-reqserver-port"); final StockServer ss = new StockServer(ip,port,callback); ss.startServer(); } }.start(); } }
处理请求Java代码:
public class StockCallBack extends ReqCallBackAbstract{ final SpringInstance si = SpringInstance.getInstance(); final private StockService stockService = (StockService)si.getService("stockService"); @Override public void solve(JSONObject json,String c, String args) { try{ String[] commandArray = StrUtil.split(c, "."); if(commandArray.length == 2){ String objStr = commandArray[0]; String objFunc = commandArray[1]; //Map<String,Object> argMap = (Map<String,Object>)JSON.parse(args); if(objStr.equals("stock")){ Map<String,Object> argMap = new HashMap<String, Object>(); if(objFunc.equals("listStockByKeyword")){ argMap.put("kw", args); }else if(objFunc.equals("listByDate")){ argMap.put("date", args); } Object ret = invokeMethodRequest(stockService, objFunc, argMap); JSONObject jsonBODY = json.getJSONObject("body"); jsonBODY.put("ret",ret.toString()); } } }catch(Exception ex){ ex.printStackTrace(); } } }
Java代码跟数据库打交道:
编写Java服务端,比如我们编写一个按关键字读取股票列表等接口(依赖注入用的Spring): @Repository @CacheNamespace(implementation = org.mybatis.caches.ehcache.EhcacheCache.class, readWrite = true) public interface StockDao { @Select("select SYMBOL,SHORT_NAME from `master`") public List<Map> listStock(); @Select("select SYMBOL,SHORT_NAME from `master` where SHORT_NAME like '%${k}%' or SPELL_NAME like '%${k}%' or SYMBOL like '%${k}%' limit 0,10") public List<Map> listStockByKeyword(Map<String,String> map); @Select("select SYMBOL,SHORT_NAME from `master` where exch_id=8") public List<Map> listAllSh(Map<String,String> map); @Select("select SYMBOL,SHORT_NAME from `master` where trade_date=#dt#") public List<Map> listStockByDate(Map<String,String> map); }
编写客户端C++或脚本:
gw.req("命令代码"."命令代码对应的参数列表"); //回调函数 gw.s_cb_gw.connect(function(trcode,msg){ log("-----------------------------------------------------------------"); log("<p>gqt server异步方式获取数据,回调信息:trcode=" + trcode + ",msg=" + msg+"</p>"); });
效果如下:
这样做的好处:
- 桌面或移动的客户端再也不用编写繁琐的网络通信相关的程序,只需要发送请求/订阅,处理响应/订阅数据包即可,也不用关心底层的数据分包合包,客户端代理,网络传输的加解密和压缩等;
- 客户端开发人员也几乎不用和服务端程序员沟通,通过JSON解析请求透传给Java,Java响应的数据带上包头和响应给客户端,客户端解析JSON即可;
- 跨语言的服务端虽然会损失一定的性能,但似乎这点ZeroMQ已做的足够好;
Demo程序部署步骤:
- 创建一个MySQL Demo数据库,数据库名:stock,utf-8编码,导入output下的stock.sql;
- 配置output下的config.properties文件,指定机器IP,配置jdbc.properties,确保数据库配置正确;
- 点击runStockServer.bat,运行stock的Java Demo服务器,可以开任意个;
- 配置gqt-server-communicator(cpp)下的config_ims.ini文件,IP地址全部换成机器IP;
- 启动ss_server.exe,ss_zserver.exe;
- 点击客户端GQT程序,配置config_ims.ini中的IP地址,切换到gateway的标签页,enjoy it!