Zeroc Ice TCP长连接 实现推送功能

来源:互联网 发布:excel数据插件 编辑:程序博客网 时间:2024/06/08 08:30
业务场景

        公司目前推送方案踩过很多坑,用过极光的(我们使用电信定向卡,遇到较多问题,定向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 jerome_s@qq.com
 * @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 jerome_s@qq.com
 * @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方式。

代码
        https://github.com/JeromeSuz/demo_zeroc_ice_tcp

资料下载
       Ice-3.6.2.pdf 【 Ice 3.6.1版本开始已经有穿透防火墙的TCP连接方式Glacier2】
0 0