Zeroc Ice TCP長連接 實現推送功能

業務場景

        公司目前推送方案踩過很多坑,用過極光的(我們使用電信定向卡,遇到較多問題,定向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, 12new 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】
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章