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 
#include 
#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.CloseOff),
//              new Ice.Optional(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.CloseOff),
             new  Ice.Optional(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】

你可能感兴趣的:(中间件/服务,Zeroc,Ice,RPC中间件)