標籤:
學習mina目的還是搭建通訊架構,學完mina我們瞭解了如何?用戶端和服務端,也就是一個正常channel我們是知道怎麼建立的
但是問題是,我們應用環境通訊分為兩種
1.前後端通訊
其實這個比較好實現,提供一個mina server端,供前端語言通過socket建串連就行,這個通訊就算是ok了,編解碼等通訊解析的細節這裡不講了
以前的遊戲服務端架構業務多用短串連,聊天用長串連,聊天的部分其實就是上面表述的情況
現在是長串連的天下,聊天依舊是長串連,業務也做成長串連,實現了真正意義上的長串連遊戲架構,這其實就表述了一種當下典型架構,
就是後端提供兩個開放的通訊連接埠【即兩個mina server】,供前端的socket串連,一個負責聊天,登入,註冊,另一個負責其他業務,這樣就實現了協議通訊的負載平衡
2.後端的業務服通訊【這是本文的重點】
那麼後端的業務就不需要負載平衡嗎?比如job,非同步更新db,活動副本等
當然也是需要的,怎麼做那,先拿1中的做個解釋
mainserevr[聊天,登入,註冊]---nodeserver[其他業務]
這兩個mina sever端已經建立起來了,但是兩個server之間還不能通訊,我們有兩個選擇,要麼在mainserevr上起個mina client去連nodeserver,要麼在nodeserver
上起個mina client去連mainserevr,思路肯定是這樣的,一旦這個通道建立了,其實互為server和client的,會有一個iosession被通道持有,只要有這個iosession,
就可以主動write,當然對於通道的另一端可以response,也可以通過取得iosession來主動寫
實現方式,我們在nodeserevr上提供一個mainserverClient這樣一個spring的bean去串連mainserver,這樣在nodeserver上就可以向mainserevr發訊息了
3.帶著這個思路設計一下
我把遊戲中的業務分為
public static final String SERVER_TYPE_NODE_STR = "nodeserver";// game nodepublic static final String SERVER_TYPE_MAIN_STR = "mainserver";// 主serverpublic static final String SERVER_TYPE_JOB_STR = "jobserver";// job serverpublic static final String SERVER_TYPE_ASYNCDB_STR = "asyncdbserver";// 非同步DBpublic static final String SERVER_TYPE_ACTIVE_STR = "activityserver";// 活動public static final String SERVER_TYPE_OTHER_STR = "other";// 其他public static final String SERVER_TYPE_GM_STR = "GM";//管理端
每次啟動一種server時,首先啟動一次mina serevr,然後啟動多個mina client去串連其他的mina server,
比如啟動nodeserevr 服務端,然後啟動多個client分別串連mainserevr,jobserevr等的服務端,這樣我就可以
在nodeserver上給其他業務serevr發請求了,具體啟動哪些client看需要
搞一個啟動server類型的方法
public static ClassPathXmlApplicationContext start(String serverTypeStr) {try {
//關閉串連池的鉤子線程ProxoolFacade.disableShutdownHook();
//spring 的核心設定檔String xmlFile = "applicationContext.xml";....log.info("啟動 {} server................", serverTypeName);// 設定到系統內容變數System.setProperty(NodeSessionMgr.SERVER_TYPE_KEY, serverType + "");System.setProperty(NodeSessionMgr.SERVER_TYPE_NAME_KEY,serverTypeName);// final ClassPathXmlApplicationContext parent = new// ClassPathXmlApplicationContext(// xmlFile);String fileName = null;
//這是把spring的住設定檔拆分了一部分內容出來,目前是只載入本server需要的beanif (serverType == NodeSessionMgr.SERVER_TYPE_NODE) {fileName = "wolf/app_nodeserver.xml";} else {fileName = "wolf/app_server.xml";}//手動啟動springfinal ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { xmlFile, fileName });if (context != null) {ServiceLocator.getInstance().setApplicationContext(context);}// 啟動socket serverfinal WolfServer server = (WolfServer) ServiceLocator.getSpringBean("wolf_server");server.setServerType(serverType);
//這個調用就是我們熟悉的啟動mina server端server.start();//這個動用做兩件事,選區需要的serevr類型建立mina client串連startClient(server);
//鉤子線程用來監聽應用停止,為了做停止時的後續處理Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {public void run() {_shutdown();}}, "shutdownHookThread"));
//為了支援web,springMVC,內建一個web serverif (NodeSessionMgr.SERVER_TYPE_MAIN_STR.equalsIgnoreCase(serverTypeStr)) {JettyServer jettyServer = (JettyServer) ServiceLocator.getSpringBean("jettyServer");jettyServer.start();}log.info("start {} end................", serverTypeName);return context;} catch (Exception e) {e.printStackTrace();shutdown();} finally {}return null;}
在看下startClient(server);
private static void startClient(WolfServer server) {// asyncdbServer只會被串連,不會主動串連其他server
// 這部分目的是過濾那些不需要主動連比人的serevr,比武我這裡的非同步db,和活動服if (server.getServerType() == NodeSessionMgr.SERVER_TYPE_ASYNCDB|| server.getServerType() == NodeSessionMgr.SERVER_TYPE_ACTIVE) {return;}// 發送game Server ip port到mainserverMap<String, Object> params = new HashMap<String, Object>();params.put("nodeServerIp", server.getIp());params.put("nodeServerPort", server.getPort());params.put("serverType", server.getServerType());//我需要mainserevr的client,就弄個bean在本服final IWolfClientService mainServerClient = (IWolfClientService) ServiceLocator.getSpringBean("mainServerClient");//這個位置其實就是mina的client連server端mainServerClient.init();Object localAddress = mainServerClient.registerNode(params);
//同上,需要jobserevr的clientfinal IWolfClientService jobServerClient = (IWolfClientService) ServiceLocator.getSpringBean("jobServerClient");if (jobServerClient != null) {jobServerClient.init();Map<String, Object> params1 = new HashMap<String, Object>();params1.putAll(params);jobServerClient.registerNode(params1);}// }.....}
再看下WolfClientService.init()
public void init() {if (start)return;if (wolfClient == null) {log.error("wolf client is null");return;} //mina 的client 串連 mina serverwolfClient.start();if (wolfClient.isConnected())start = true;}
再看下wolfclient.start()
/** * 串連一個伺服器,並指定處理接收到的訊息的處理方法 * */public void start() {// this.context.put("resultMgr", this.resultMgr);logger.info(com.youxigu.dynasty2.i18n.MarkupMessages.getString("WolfClient_9"), processorNum);logger.info(com.youxigu.dynasty2.i18n.MarkupMessages.getString("WolfClient_0"), corePoolSize);logger.info(com.youxigu.dynasty2.i18n.MarkupMessages.getString("WolfClient_4"), maxPoolSize);if (this.serverIp == null || this.serverIp.equals("")) {logger.error(clientName + "沒有配置serverIp,不啟動.........");return;}String threadPrefix = clientName + "[" + this.serverIp + ":"+ this.serverPort + "]";// exector = Executors.newCachedThreadPool(new// NamingThreadFactory(threadPrefix));processor = new SimpleIoProcessorPool<NioSession>(NioProcessor.class,processorNum);// connector = new NioSocketConnector((Executor) exector, processor);connector = new NioSocketConnector(processor);// connector.getSessionConfig().setReuseAddress(true);DefaultIoFilterChainBuilder chain = connector.getFilterChain();if (useLogFilter == 2) {chain.addLast("logging", new LoggingFilter());}// codec filter要放在ExecutorFilter前,因為讀寫同一個socket connection的socket// buf不能並發(事實上主要是讀,寫操作mina已經封裝成一個write Queue)chain.addLast("codec", new ProtocolCodecFilter(codecFactory)); // 設定編碼過濾器// 添加心跳過濾器,用戶端只接受服務端的心跳請求,不發送心跳請求// connector.getSessionConfig().setReaderIdleTime(readIdleTimeOut);// 這裡的KeepAliveFilter必須在codec之後,因為KeepAliveMessageFactoryImpl返回的是Object,如果KeepAliveMessageFactoryImpl返回的是IOBuffer,則可以在codec之前// KeepAliveFilter到底在ExecutorFilter之前好還是之後好,我也不確定KeepAliveFilter filter = new KeepAliveFilter(new KeepAliveMessageFactoryImpl(keepAliveRequestInterval <= 0),IdleStatus.READER_IDLE, new RequestTimeoutCloseHandler(),keepAliveRequestInterval <= 0 ? 600 : keepAliveRequestInterval,30);chain.addLast("ping", filter);// 添加執行線程池executor = new UnorderedThreadPoolExecutor(corePoolSize, maxPoolSize,keepAliveTime, TimeUnit.SECONDS, new NamingThreadFactory(threadPrefix));// 這裡是預先啟動corePoolSize個處理線程executor.prestartAllCoreThreads();chain.addLast("exec", new ExecutorFilter(executor,IoEventType.EXCEPTION_CAUGHT, IoEventType.MESSAGE_RECEIVED,IoEventType.SESSION_CLOSED, IoEventType.SESSION_IDLE,IoEventType.SESSION_OPENED));if (useWriteThreadPool) {executorWrite = new UnorderedThreadPoolExecutor(corePoolSize,maxPoolSize, keepAliveTime, TimeUnit.SECONDS,new NamingThreadFactory(threadPrefix + "write"));executorWrite.prestartAllCoreThreads();chain.addLast("execWrite", new ExecutorFilter(executorWrite,IoEventType.WRITE, IoEventType.MESSAGE_SENT));}// ,logger.isDebugEnabled() ? new// LoggingIoEventQueueHandler("execWrite") : nulls// 配置handler的 logger,在codec之後,列印的是decode前或者encode後的訊息的log// 可以配置在ExecutorFilter之後:是為了在背景工作執行緒中列印log,不是在NioProcessor中列印if (useLogFilter == 1) {chain.addLast("logging", new LoggingFilter());}connector.setHandler(handler);connector.getSessionConfig().setReuseAddress(true);connector.getSessionConfig().setTcpNoDelay(tcpNoDelay);logger.info(com.youxigu.dynasty2.i18n.MarkupMessages.getString("WolfClient_1")+ serverIp + ":" + serverPort);ConnectFuture cf = null;long start = System.currentTimeMillis();while (true) { //這地很關鍵,是個無線迴圈,每10秒串連一次,直到可以和服務端建立串連,否則一支迴圈下去cf = connector.connect(serverAddress);// 建立串連cf.awaitUninterruptibly(10000L);if (!cf.isConnected()) {if ((System.currentTimeMillis() - start) > timeout) {throw new RuntimeException(com.youxigu.dynasty2.i18n.MarkupMessages.getString("WolfClient_5")+ serverIp + ":" + serverPort);}if (cf.getException() != null) {logger.error(com.youxigu.dynasty2.i18n.MarkupMessages.getString("WolfClient_6"), serverIp + ":"+ serverPort, cf.getException().getMessage());}try {Thread.sleep(10000);} catch (Exception e) {}continue;}
//這就是終極目標了,我們的目的就是在serevr的用戶端的bean裡,可以拿到這個iosessionthis.setSession(cf.getSession());logger.info(com.youxigu.dynasty2.i18n.MarkupMessages.getString("WolfClient_10")+ serverIp + ":" + serverPort);shutDown = false;if (handler instanceof WolfMessageChain) {WolfMessageChain wmc = WolfMessageChain.class.cast(handler);wmc.init(context);}break;}}
這樣後端的業務通訊網就可以輕鬆的建立起來,之後想怎麼通訊就看你的了
【MINA】用mina做業務服之間的通訊,實現業務負載平衡思路