公司目前推送方案踩過很多坑,用過極光的(我們使用電信定向卡,遇到較多問題,定向ip等等,而且極光偶爾不太穩定推送無法到達,使用第三方避免不了這種問題)、用過自建的UDP推送(UDP會有丟包的情況)還稍微好一點,但是都會有問題,目前我們打算使用Ice的長連接,使設備和服務器保持一個tcp的長連接,實現實時推送的功能。
解決的問題
1. 實時推送(雙向)
2. 穿透防火牆(打洞,解決複雜的網絡環境,企業路由等)
3. udp封殺,打洞不成功,比如有線的網絡環境比較複雜,使用tcp替換
新建maven項目,可以參考ice系列的前面的介紹。Zeroc Ice開發環境搭建
其實核心的概念就是一個互爲服務的概念,這樣的話,就可以實現雙向的數據發送。
不管是服務端向客戶端做心跳、還是客戶端向服務端做心跳、或者是定時請求接口都是爲了保持tcp的連接是可用的。
slice定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #include <Ice/BuiltinSequences.ice> #include <Ice/Identity.ice> #include "ICommon.ice" module switcher { /** * 回調客戶端接口定義(服務端調用客戶端的接口) */ interface ISwitchCallback { /** * 發送二進制數組 * @param byteSeq 二進制數組 * @return true/false */ bool send(Ice::ByteSeq byteSeq) throws SwitchException; /** * 發送字符串 * @param msg 字符串 * @return true/false */ bool sendMsg(string msg) throws SwitchException; }; /** * 服務端接口定義(客戶端調用服務端的接口) */ interface ISwitch { /** * 對服務端進行心跳(無異常則表示成功) * @param sn 設備串號 * @param netMode 網絡接入方式 0:沒有 1:3G 2:4G 3:以太網 4:wifi 5:2G * @param netStrength 網絡信號強度 */ bool heartbeat(Ice::Identity id, string sn, int netMode, int netStrength) throws SwitchException; /** * 設備回調 * @param byteSeq 二進制數組 * @return true/false */ bool callBack(string msg) throws SwitchException; }; }; |
服務端接口定義關鍵代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | /** * 心跳(如果用戶自己定時做心跳可以心跳時傳參) */ @Override public boolean heartbeat(Identity id, String sn, int netMode, int netStrength, Current current) { LOGGER.info(switchCallbackPrxCacheMap.size()); LOGGER.info( "tcp heartbeat begin params sn = " + sn + " id.name = " + id.name + ", category = " + id.category + ", netMode = " + netMode + ", netStrength = " + netStrength); Ice.Connection con = current.con; Ice.IPConnectionInfo ipConn = (Ice.IPConnectionInfo) con.getInfo(); if ( null != ipConn) { LOGGER.info( "ipConn remote:" + ipConn.remoteAddress + ":" + ipConn.remotePort); LOGGER.info( "ipConn local:" + ipConn.localAddress + ":" + ipConn.localPort); } LOGGER.info( "heartbeat" ); // 心跳業務處理 // 如果已經存在不更新緩存 if (switchCallbackPrxCacheMap.containsKey(sn)) { SwitchCallbackPrxCache switchCallbackPrxCache = switchCallbackPrxCacheMap.get(sn); if (ipConn.remoteAddress.equals(switchCallbackPrxCache.getIp()) && switchCallbackPrxCache.getPort() == ipConn.remotePort) { LOGGER.info( "already exist cache, return true\n" ); return true ; } else { switchCallbackPrxCacheMap.remove(sn); } } ISwitchCallbackPrx switchCallbackPrx = ISwitchCallbackPrxHelper.checkedCast(con.createProxy(id)); switchCallbackPrxCache = new SwitchCallbackPrxCache(); switchCallbackPrxCache.setiSwitchCallbackPrx(switchCallbackPrx); switchCallbackPrxCache.setIp(ipConn.remoteAddress); switchCallbackPrxCache.setPort(ipConn.remotePort); switchCallbackPrxCacheMap.put(sn, switchCallbackPrxCache); // 如果用戶不是定時心跳,而是使用ice自帶的心跳必須執行以下代碼 holdHeartbeat(current.con); LOGGER.info( "register end, return true. \n" ); return true ; } /** * ice自帶保持心跳 * * @author [email protected] * @param con */ private void holdHeartbeat(Ice.Connection con) { con.setCallback( new Ice.ConnectionCallback() { @Override public void heartbeat(Ice.Connection c) { LOGGER.debug( "service heartbeat..." ); } @Override public void closed(Ice.Connection c) { LOGGER.debug( "service close!" ); } }); // 每10/2 s向對方做心跳 // 服務端向客戶端做心跳 客戶端打印客戶端的con.setCallback(new Ice.ConnectionCallback() // con.setACM(new Ice.IntOptional(10), new Ice.Optional<Ice.ACMClose>(Ice.ACMClose.CloseOff), // new Ice.Optional<Ice.ACMHeartbeat>(Ice.ACMHeartbeat.HeartbeatAlways)); } |
客戶端接口定義關鍵代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Override public boolean send( byte [] byteSeq, Current __current) throws SwitchException { // 客戶端打印會打印以下信息 LOGGER.info( "send() byteSeq = " + new String(byteSeq)); return true ; } @Override public boolean sendMsg(String msg, Current __current) throws SwitchException { // 客戶端打印會打印以下信息 LOGGER.info( "sendMsg() msg = " + msg); return true ; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | public static void main(String[] args) { String confPath = PUSH_CLIENT_CONFIG; if ( null != APP_MAIN) confPath = APP_MAIN + "/src/main/resources/syscfg.properties" ; else { confPath = PUSH_CLIENT_CONFIG; File file = new File(confPath); if (!file.exists()) { confPath = System.getProperty( "user.dir" ) + "/src/test/resources/SwitchClient.conf" ; } } SwitchClient lpClient = new SwitchClient(); int status = lpClient.main( "SwitchClient" , args, confPath); System.exit(status); } public int run(String[] args) { try { communicator = communicator(); switchPushPrx = ISwitchPrxHelper.checkedCast(communicator.stringToProxy(prxStr)); switchPushPrx.ice_ping(); } catch (Ice.LocalException ex) { ex.printStackTrace(); } Ice.ObjectAdapter adapter = communicator.createObjectAdapter( "" ); Ice.Identity id = new Ice.Identity(); id.category = "" ; id.name = "SwitchClient" ; adapter.add( new SwitchCallbackI(), id); adapter.activate(); switchPushPrx.ice_getConnection().setAdapter(adapter); LOGGER.info( "SwitchClient ice is started! " + "getEndpoint = " + switchPushPrx.ice_getConnection().getEndpoint()._toString()); try { // while (true) { LOGGER.info( "SwitchClient is begin heartbeat." ); // 使用異步的方式 switchPushPrx.begin_heartbeat(id, sn, 1 , 2 , new Callback_ISwitch_heartbeat() { @Override public void exception(LocalException __ex) { } @Override public void response( boolean arg) { LOGGER.info( "heartbeat result = " + arg); if (arg) { LOGGER.info( "心跳成功" ); } else { LOGGER.info( "心跳失敗" ); } } @Override public void exception(UserException ex) { } }); LOGGER.info( "SwitchClient is end heartbeat.\n" ); // Thread.sleep(10000); // } // 可以使用以上的while(true) Thread.sleep(HEARTBEAT_TIME);的方式定時請求保持tcp連接的心跳,這個方式是爲了每次心跳都傳參 // 也可以使用以下的方式,使用ice自帶的功能保持tcp的連接心跳,無法每次心跳傳參 holdHeartbeat(switchPushPrx.ice_getConnection()); } catch (Exception e) { e.printStackTrace(); } communicator().waitForShutdown(); return 0 ; } public void destroy() { if ( null != communicator) { communicator.destroy(); } } /** * ice自帶保持心跳 * * @author [email protected] * @param con */ private void holdHeartbeat(Ice.Connection con) { con.setCallback( new Ice.ConnectionCallback() { @Override public void heartbeat(Ice.Connection c) { System.out.println( "sn:" + sn + " client heartbeat...." ); } @Override public void closed(Ice.Connection c) { System.out.println( "sn:" + sn + " " + "closed...." ); } }); // 每30/2 s向對方做心跳 // 客戶端向服務端做心跳 服務端打印服務端的con.setCallback(new Ice.ConnectionCallback() con.setACM( new Ice.IntOptional( 10 ), new Ice.Optional<Ice.ACMClose>(Ice.ACMClose.CloseOff), new Ice.Optional<Ice.ACMHeartbeat>(Ice.ACMHeartbeat.HeartbeatAlways)); } |
服務端日誌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 2017-01-05 14:21:12,019 INFO [com.demo.tcp.ice.impl.SwitchI] - 0 2017-01-05 14:21:12,019 INFO [com.demo.tcp.ice.impl.SwitchI] - tcp heartbeat begin params sn = 0481deb6494848488048578316516694 id .name = SwitchClient, category = , netMode = 1, netStrength = 2 2017-01-05 14:21:12,020 INFO [com.demo.tcp.ice.impl.SwitchI] - ipConn remote:127.0.0.1:15004 2017-01-05 14:21:12,020 INFO [com.demo.tcp.ice.impl.SwitchI] - ipConn local :127.0.0.1:5010 2017-01-05 14:21:12,020 INFO [com.demo.tcp.ice.impl.SwitchI] - heartbeat -- 17-1-5 14:21:12:031 Main: Network: sent 77 of 77 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 -- 17-1-5 14:21:12:036 Main: Network: received 14 of 14 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 -- 17-1-5 14:21:12:036 Main: Network: received 12 of 12 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 2017-01-05 14:21:12,037 INFO [com.demo.tcp.ice.impl.SwitchI] - register end, return true . -- 17-1-5 14:21:12:038 Main: Network: sent 26 of 26 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 -- 17-1-5 14:21:17:022 Main: Network: received 14 of 14 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 2017-01-05 14:21:17,022 DEBUG [com.demo.tcp.ice.impl.SwitchI] - service heartbeat... -- 17-1-5 14:21:22:022 Main: Network: received 14 of 14 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 2017-01-05 14:21:22,022 DEBUG [com.demo.tcp.ice.impl.SwitchI] - service heartbeat... -- 17-1-5 14:21:27:021 Main: Network: received 14 of 14 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 2017-01-05 14:21:27,021 DEBUG [com.demo.tcp.ice.impl.SwitchI] - service heartbeat... 2017-01-05 14:21:29,501 INFO [com.demo.tcp.main.SwitchUtil] - ice tcp send params sn = 0481deb6494848488048578316516694 -- 17-1-5 14:21:29:502 Main: Network: sent 59 of 59 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 -- 17-1-5 14:21:29:502 Main: Network: received 14 of 14 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 -- 17-1-5 14:21:29:503 Main: Network: received 12 of 12 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 2017-01-05 14:21:29,503 INFO [com.demo.tcp.main.SwitchUtil] - ice tcp send end, sendResult = true 2017-01-05 14:21:29,503 INFO [com.demo.tcp.main.SwitchUtil] - sendResult = true 2017-01-05 14:21:29,503 INFO [com.demo.tcp.main.Main] - result = true -- 17-1-5 14:21:32:023 Main: Network: received 14 of 14 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 2017-01-05 14:21:32,023 DEBUG [com.demo.tcp.ice.impl.SwitchI] - service heartbeat... -- 17-1-5 14:21:37:022 Main: Network: received 14 of 14 bytes via tcp local address = 127.0.0.1:5010 remote address = 127.0.0.1:15004 2017-01-05 14:21:37,022 DEBUG [com.demo.tcp.ice.impl.SwitchI] - service heartbeat... |
客戶端日誌
1 2 3 4 5 6 7 8 9 10 | -- 17-1-5 14:21:11:526 SwitchClient: Network: established tcp connection local address = 127.0.0.1:15004 remote address = 127.0.0.1:5010 2017-01-05 14:21:12,017 INFO [com.demo.tcp.main.Main] - SwitchClient ice is started! getEndpoint = tcp -h 127.0.0.1 -p 5010 -t 60000 2017-01-05 14:21:12,017 INFO [com.demo.tcp.main.Main] - SwitchClient is begin heartbeat. 2017-01-05 14:21:12,017 INFO [com.demo.tcp.main.Main] - SwitchClient is end heartbeat. 2017-01-05 14:21:12,038 INFO [com.demo.tcp.main.Main] - heartbeat result = true 2017-01-05 14:21:12,038 INFO [com.demo.tcp.main.Main] - 心跳成功 2017-01-05 14:21:29,502 INFO [com.demo.tcp.ice.impl.SwitchCallbackI] - sendMsg() msg = test msg. |
當使用用戶自定義的方式進行心跳的時候,服務端和客戶端的holdHeartbeat(Ice.Connection con) 方法不掉用即可,客戶端需要記得進行定時的調用心跳接口。
這個方案是否能較好的解決問題,得實際大量使用才知道,目前我們是有需要的設備纔會轉到TCP,不然還是使用原有的UDP方式。
代碼
代碼
資料下載
Ice-3.6.2.pdf 【 Ice 3.6.1版本開始已經有穿透防火牆的TCP連接方式Glacier2】