精华内容
下载资源
问答
  • Websocket实现即时通讯

    2017-08-22 17:52:00
    关于我和WebSocket的缘:我从大二在计算机网络课上听老师讲过之后,第一次使用就到了毕业之后的第一份工作。直到最近换了工作,到了一家是含有IM社交聊天功能的app的时候,我觉得我现在可以谈谈我对WebSocket/Socket...

    前言

    关于我和WebSocket的缘:我从大二在计算机网络课上听老师讲过之后,第一次使用就到了毕业之后的第一份工作。直到最近换了工作,到了一家是含有IM社交聊天功能的app的时候,我觉得我现在可以谈谈我对WebSocket/Socket的一些看法了。要想做IM聊天app,就不得不理解WebSocket和Socket的原理了,听我一一道来。

    目录

    1.WebSocket使用场景

    2.WebSocket诞生由来

    3.谈谈WebSocket协议原理

    4.WebSocket 和 Socket的区别与联系

    5.iOS平台有哪些WebSocket和Socket的开源框架

    6.iOS平台如何实现WebSocket协议

    一.WebSocket的使用场景

    1.社交聊天

    最著名的就是微信,QQ,这一类社交聊天的app。这一类聊天app的特点是低延迟,高即时。即时是这里面要求最高的,如果有一个紧急的事情,通过IM软件通知你,假设网络环境良好的情况下,这条message还无法立即送达到你的客户端上,紧急的事情都结束了,你才收到消息,那么这个软件肯定是失败的。

    2.弹幕

    说到这里,大家一定里面想到了A站和B站了。确实,他们的弹幕一直是一种特色。而且弹幕对于一个视频来说,很可能弹幕才是精华。发弹幕需要实时显示,也需要和聊天一样,需要即时。

    3.多玩家游戏

    4.协同编辑

    现在很多开源项目都是分散在世界各地的开发者一起协同开发,此时就会用到版本控制系统,比如Git,SVN去合并冲突。但是如果有一份文档,支持多人实时在线协同编辑,那么此时就会用到比如WebSocket了,它可以保证各个编辑者都在编辑同一个文档,此时不需要用到Git,SVN这些版本控制,因为在协同编辑界面就会实时看到对方编辑了什么,谁在修改哪些段落和文字。

    5.股票基金实时报价

    金融界瞬息万变——几乎是每毫秒都在变化。如果采用的网络架构无法满足实时性,那么就会给客户带来巨大的损失。几毫秒钱股票开始大跌,几秒以后才刷新数据,一秒钟的时间内,很可能用户就已经损失巨大财产了。

    6.体育实况更新

    全世界的球迷,体育爱好者特别多,当然大家在关心自己喜欢的体育活动的时候,比赛实时的赛况是他们最最关心的事情。这类新闻中最好的体验就是利用Websocket达到实时的更新!

    7.视频会议/聊天

    视频会议并不能代替和真人相见,但是他能让分布在全球天涯海角的人聚在电脑前一起开会。既能节省大家聚在一起路上花费的时间,讨论聚会地点的纠结,还能随时随地,只要有网络就可以开会。

    8.基于位置的应用

    越来越多的开发者借用移动设备的GPS功能来实现他们基于位置的网络应用。如果你一直记录用户的位置(比如运行应用来记录运动轨迹),你可以收集到更加细致化的数据。

    9.在线教育

    在线教育近几年也发展迅速。优点很多,免去了场地的限制,能让名师的资源合理的分配给全国各地想要学习知识的同学手上,Websocket是个不错的选择,可以视频聊天、即时聊天以及其与别人合作一起在网上讨论问题...

    10.智能家居

    这也是我一毕业加入的一个伟大的物联网智能家居的公司。考虑到家里的智能设备的状态必须需要实时的展现在手机app客户端上,毫无疑问选择了Websocket。

    11.总结

    从上面我列举的这些场景来看,一个共同点就是,高实时性!

    二.WebSocket诞生由来

    1.最开始的轮询Polling阶段

    1194012-ce4df238336909a5.jpg

    这种方式下,是不适合获取实时信息的,客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。客户端会轮询,有没有新消息。这种方式连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。

    2.改进版的长轮询Long polling阶段

    1194012-6ca608d5a37095e6.jpg

    长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,有没有新消息,如果没有新消息,就一直等待。当有新消息的时候,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。但是这种方式还是有一种弊端:例如假设服务器端的数据更新速度很快,服务器在传送一个数据包给客户端后必须等待客户端的下一个Get请求到来,才能传递第二个更新的数据包给客户端,那么这样的话,客户端显示实时数据最快的时间为2×RTT(往返时间),而且如果在网络拥塞的情况下,这个时间用户是不能接受的,比如在股市的的报价上。另外,由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。

    3.WebSocket诞生

    现在急需的需求是能支持客户端和服务器端的双向通信,而且协议的头部又没有HTTP的Header那么大,于是,Websocket就诞生了!

    1194012-b88b2623a2e4a8ea.png

    上图就是Websocket和Polling的区别,从图中可以看到Polling里面客户端发送了好多Request,而下图,只有一个Upgrade,非常简洁高效。至于消耗方面的比较就要看下图了

    1194012-f1f91e25b9635701.png

    上图中,我们先看蓝色的柱状图,是Polling轮询消耗的流量,

    Use case A: 1,000 clients polling every second: Network throughput is (871 x 1,000) = 871,000 bytes = 6,968,000 bits per second (6.6 Mbps)

    Use case B: 10,000 clients polling every second: Network throughput is (871 x 10,000) = 8,710,000 bytes = 69,680,000 bits per second (66 Mbps)

    Use case C: 100,000 clients polling every 1 second: Network throughput is (871 x 100,000) = 87,100,000 bytes = 696,800,000 bits per second (665 Mbps)

    而Websocket的Frame是 just two bytes of overhead instead of 871,仅仅用2个字节就代替了轮询的871字节!

    Use case A: 1,000 clients receive 1 message per second: Network throughput is (2 x 1,000) = 2,000 bytes = 16,000 bits per second (0.015 Mbps)

    Use case B: 10,000 clients receive 1 message per second: Network throughput is (2 x 10,000) = 20,000 bytes = 160,000 bits per second (0.153 Mbps)

    Use case C: 100,000 clients receive 1 message per second: Network throughput is (2 x 100,000) = 200,000 bytes = 1,600,000 bits per second (1.526 Mbps)

    相同的每秒客户端轮询的次数,当次数高达10W/s的高频率次数的时候,Polling轮询需要消耗665Mbps,而Websocket仅仅只花费了1.526Mbps,将近435倍!!

    三.谈谈WebSocket协议原理

    Websocket是应用层第七层上的一个应用层协议,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。

    Websocket的数据传输是frame形式传输的,比如会将一条消息分为几个frame,按照先后顺序传输出去。这样做会有几个好处:

    1)大数据的传输可以分片传输,不用考虑到数据大小导致的长度标志位不足够的情况。

    2)和http的chunk一样,可以边生成数据边传递消息,即提高传输效率。

    QQ截图20160526161900.png

    四.WebSocket 和 Socket的区别与联系

    首先,Socket 其实并不是一个协议。它工作在 OSI 模型会话层(第5层),是为了方便大家直接使用更底层协议(一般是 TCP 或 UDP )而存在的一个抽象层。Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。

    1194012-d35653654be833ae.jpg

    Socket通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket,一个Socket由一个IP地址和一个端口号唯一确定。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。

    Socket在通讯过程中,服务端监听某个端口是否有连接请求,客户端向服务端发送连接请求,服务端收到连接请求向客户端发出接收消息,这样一个连接就建立起来了。客户端和服务端也都可以相互发送消息与对方进行通讯,直到双方连接断开。

    所以基于WebSocket和基于Socket都可以开发出IM社交聊天类的app

    五.iOS平台有哪些WebSocket和Socket的开源框架

    Socket开源框架有:CocoaAsyncSocketsocketio/socket.io-client-swift

    WebSocket开源框架有:facebook/SocketRockettidwall/SwiftWebSocket

    六.iOS平台如何实现WebSocket协议

    Talk is cheap。Show me the code ——Linus Torvalds

    我们今天来看看facebook/SocketRocket的实现方法

    首先这是SRWebSocket定义的一些成员变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @property (nonatomic, weak) id  delegate;
    /**
      A dispatch queue for scheduling the delegate calls. The queue doesn't need be a serial queue.
      If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls.
      */
    @property (nonatomic, strong) dispatch_queue_t delegateDispatchQueue;
    /**
      An operation queue for scheduling the delegate calls.
      If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls.
      */
    @property (nonatomic, strong) NSOperationQueue *delegateOperationQueue;
    @property (nonatomic, readonly) SRReadyState readyState;
    @property (nonatomic, readonly, retain) NSURL *url;
    @property (nonatomic, readonly) CFHTTPMessageRef receivedHTTPHeaders;
    // Optional array of cookies (NSHTTPCookie objects) to apply to the connections
    @property (nonatomic, copy) NSArray *requestCookies;
    // This returns the negotiated protocol.
    // It will be nil until after the handshake completes.
    @property (nonatomic, readonly, copy) NSString *protocol;

    下面这些是SRWebSocket的一些方法

    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
    // Protocols should be an array of strings that turn into Sec-WebSocket-Protocol.
    - (instancetype)initWithURLRequest:(NSURLRequest *)request;
    - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols;
    - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
    // Some helper constructors.
    - (instancetype)initWithURL:(NSURL *)url;
    - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols;
    - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
    // By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes.
    - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
    - (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
    // SRWebSockets are intended for one-time-use only.  Open should be called once and only once.
    - (void)open;
    - (void)close;
    - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason;
    ///--------------------------------------
    #pragma mark Send
    ///--------------------------------------
    //下面是4个发送的方法
    /**
      Send a UTF-8 string or binary data to the server.
      @param message UTF-8 String or Data to send.
      @deprecated Please use `sendString:` or `sendData` instead.
      */
    - (void)send:(id)message __attribute__((deprecated( "Please use `sendString:` or `sendData` instead." )));
    - (void)sendString:(NSString *)string;
    - (void)sendData:(NSData *)data;
    - (void)sendPing:(NSData *)data;
    @end

    对应5种状态的代理方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ///--------------------------------------
    #pragma mark - SRWebSocketDelegate
    ///--------------------------------------
    @protocol SRWebSocketDelegate - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
    @optional
    - (void)webSocketDidOpen:(SRWebSocket *)webSocket;
    - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
    - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
    - (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
    // Return YES to convert messages sent as Text to an NSString. Return NO to skip NSData -> NSString conversion for Text messages. Defaults to YES.
    - (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket;
    @end

    didReceiveMessage方法是必须实现的,用来接收消息的。

    下面4个did方法分别对应着Open,Fail,Close,ReceivePong不同状态的代理方法

    方法就上面这些了,我们实际来看看代码怎么写

    先是初始化Websocket连接,注意此处ws://或者wss://连接有且最多只能有一个,这个是Websocket协议规定的

    1
    2
    3
    4
    self.ws = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString 
    stringWithFormat:@ "%@://%@:%zd/ws" , serverProto, serverIP, serverPort]]]];
         self.ws.delegate = delegate;
         [self.ws open];

    发送消息

    1
    [self.ws send:message];

    接收消息以及其他3个代理方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //这个就是接受消息的代理方法了,这里接受服务器返回的数据,方法里面就应该写处理数据,存储数据的方法了。
    - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
    {
         NSDictionary *data = [NetworkUtils decodeData:message];
         if  (!data)
             return ;
    }
    //这里是Websocket刚刚Open之后的代理方法。就想微信刚刚连接中,会显示连接中,当连接上了,就不显示连接中了,取消显示连接的方法就应该写在这里面
    - (void)webSocketDidOpen:(SRWebSocket *)webSocket
    {
         // Open = silent ping
         [self.ws receivedPing];
    }
    //这是关闭Websocket的代理方法
    - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
    {
         [self failedConnection:NSLS(Disconnected)];
    }
    //这里是连接Websocket失败的方法,这里面一般都会写重连的方法
    - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error
    {
         [self failedConnection:NSLS(Disconnected)];
    }

    最后

    以上就是我想分享的一些关于Websocket的心得,文中如果有错误的地方,欢迎大家指点!一般没有微信QQ那么大用户量的app,用Websocket应该都可以完成IM社交聊天的任务。当用户达到亿级别,应该还有很多需要优化,优化性能各种的吧。

    最后,微信和QQ的实现方法也许并不是只用Websocket和Socket这么简单,也许是他们自己开发的一套能支持这么大用户,大数据的,各方面也都优化都最优的方法。如果有开发和微信和QQ的大神看到这篇文章,可以留言说说看你们用什么方式实现的,也可以和我们一起分享,我们一起学习!我先谢谢大神们的指点了!

     

    1,Android 客户端使用需要配置网络权限; 

    2,需要写一个自己的client类来继承WebsocketClient;实现websocket的状态回调和新消息的解析动作;

    3,需要监控自己的client的链接状态,维持长链接;

    4,发送和接收

     

    下面贴出部分相关代码;

    网络权限的不用说了吧!

    client类:

    常用状态回调方法有4个;

    自己可以在对应的函数里面做响应的处理,比如当链接发生错误时要重新去打开该链接,收到消息时即时的保存聊天记录和发送系统通知来提醒用户查看新消息等等;

     

    [html]  view plain  copy
     
    1. <pre name="code" class="html"><span style="font-size:18px;">public class TestClient extends WebSocketClient {  
    2.   
    3.     public TestClient(URI serverURI) {  
    4.         super(serverURI);  
    5.     }  
    6.     /***  
    7.      * 链接关闭  
    8.      */  
    9.     @Override  
    10.     public void onClose(int arg0, String arg1, boolean arg2) {  
    11.   
    12.     }  
    13.     /***  
    14.      * 链接发生错误  
    15.      */  
    16.     @Override  
    17.     public void onError(Exception arg0) {  
    18.   
    19.     }  
    20.     /**  
    21.      * 新消息  
    22.      */  
    23.     @Override  
    24.     public void onMessage(String arg0) {  
    25.   
    26.     }  
    27.     /***  
    28.      * 链接打开  
    29.      */  
    30.     @Override  
    31.     public void onOpen(ServerHandshake arg0) {  
    32.         // TODO Auto-generated method stub  
    33.   
    34.     }  
    35. }  
    36. </span>  



     

     
     

    下面是我聊天部分代码,有离线消息/PC同步/多对多的聊天;

     

    仅供参考;

     

    [java]  view plain  copy
     
    1. /*** 
    2.  * <h2>WebSocket Android 客户端</h2> 
    3.  * <ol> 
    4.  * <li>Socket链接打开回调 {@link WebSocket#onOpen(ServerHandshake)},此处有 
    5.  * {@link SocketConstant#ON_OPEN} 广播发出; 
    6.  * <li>Socket链接出现异常错误时回调 {@link WebSocket#onError(Exception)},此处有 
    7.  * {@link SocketConstant#ON_ERROR}广播发出; 
    8.  * <li>Socket链接关闭回调 {@link WebSocket #onClose(int, String, boolean)},此处有 
    9.  * {@link SocketConstant#ON_CLOSES}广播发出; 
    10.  * <li>Socket链接接收消息回调 {@link WebSocket#onMessage(String)} 
    11.  * ,此处做收消息的逻辑的处理;包括发送消息服务器返回的发送结果,PC端同步接收的新消息,及外来的新消息; 
    12.  * <li>检测是否有消息遗漏 {@link WebSocket#checkMsgWebId(String, String)},参数为联系人和webId; 
    13.  * <li>取得正在桌面运行的activity的名称 {@link WebSocket#getRunningActivityName()} 
    14.  * <li>接收到的消息的处理 {@link WebSocket#messageHandle(MessageEntity, String)} 
    15.  * ,参数为消息实体和消息类型 (buyer/server) 
    16.  * <li>发送新消息的系统通知 {@link WebSocket#sendNotification(String)},参数为联系人; 
    17.  * <li>保存离线消息 {@link WebSocket#saveOffLineMsg(HashMap)},参数为接收到的离线消息集合; 
    18.  * <li>保存从服务端获取的联系人的webId {@link WebSocket#saveContactsWebID(HashMap)} 
    19.  * ,参数为以联系人为key以最大webId为值得map集合; 
    20.  * </ol> 
    21.  *  
    22.  * @author li'mingqi <a> 2014-3-19</a> 
    23.  *  
    24.  */  
    25. public class WebSocket extends WebSocketClient {  
    26.     // 登陆返回的back_type字段  
    27.     public static final String LOGIN_RETURN_TYPE = "login";  
    28.     // 发送信息的back_type字段  
    29.     public static final String SEND_RETURN_TYPE = "send_result";  
    30.     // 接收信息的back_type字段  
    31.     public static final String RECEIVER_RETURN_TYPE = "msg";  
    32.     // 接收客服的信息的back_type字段  
    33.     public static final String GET_SERVER_RETURN_TYPE = "server_info";  
    34.     // 接收服务端返回对应联系人的最大顺序ID  
    35.     public static final String CONTACTS_MAX_WEBID_TYPE = "max_id_return";  
    36.     // 接收用户的离线消息  
    37.     public static final String USER_OFFLINE_MSG_TYPE = "offline";  
    38.     // 上下文对象  
    39.     private Context mContext;  
    40.     // socket返回json解析类对象  
    41.     private WebSocketParser mParser;  
    42.     // 系统通知管理  
    43.     public NotificationManager mNotificationManager;  
    44.     // 系统通知  
    45.     private Notification mNoti;  
    46.     // 意图  
    47.     private PendingIntent mIntent;  
    48.     // 该系统通知的 id  
    49.     public static final int NOTIFICATION_ID = 100;  
    50.   
    51.     @SuppressWarnings("deprecation")  
    52.     public SGWebSocket(Context context, URI serverUri, Draft draft) {  
    53.         super(serverUri, draft);  
    54.         this.mContext = context;  
    55.                 //新消息的解析类  
    56.                 this.mParser = WebSocketParser.getInstance();  
    57.                 //收到新消息发送的通知  
    58.                 this.mNotificationManager = (NotificationManager) this.mContext  
    59.                 .getSystemService(Context.NOTIFICATION_SERVICE);  
    60.         this.mNoti = new Notification(R.drawable.system_info, "您有新消息!",  
    61.                 System.currentTimeMillis());  
    62.     }  
    63.   
    64.     /*** 
    65.      * send broadcast <SGSocketConstant>ON_CLOSES filter if this socket closed 
    66.      * socket 发生关闭时发送的广播,若想提示,可以接受并处理 
    67.      *  
    68.      */  
    69.     @Override  
    70.     public void onClose(int arg0, String arg1, boolean arg2) {  
    71.         // 更改保存的链接状态  
    72.         UserInfoUtil.saveSocket(mContext, false);  
    73.         mNotificationManager.cancelAll();  
    74.         Intent intent = new Intent(SocketConstant.ON_CLOSES);  
    75.         intent.putExtra(SocketConstant.ON_CLOSES, arg1.toString());  
    76.         mContext.sendBroadcast(intent);  
    77.     }  
    78.   
    79.     /*** 
    80.      * send broadcast <SGSocketConstant>ON_ERROR filter if this socket has error 
    81.      * socket 发生错误发送的广播,若想提示,可以接受并处理 
    82.      *  
    83.      */  
    84.     @Override  
    85.     public void onError(Exception arg0) {  
    86.         Intent intent = new Intent(SGSocketConstant.ON_ERROR);  
    87.         intent.putExtra(SGSocketConstant.ON_ERROR, arg0.toString());  
    88.         mContext.sendBroadcast(intent);  
    89.         this.close();  
    90.     }  
    91.   
    92.     // 买家  
    93.     public static final String MSG_BUYER_TYPE = "1";  
    94.     // 客服  
    95.     public static final String MSG_SERVCER_TYPE = "2";  
    96.   
    97.     // 游客  
    98.     // public static final String MSG_RANDOM_TYPE = "3";  
    99.   
    100.     /*** 
    101.      * receiver message from server 1,登陆返回 type 
    102.      * <WebSocket>LOGIN_RETURN_TYPE; 2,发送返回 type 
    103.      * <WebSocket>SEND_RETURN_TYPE; 3,接收信息返回 type 
    104.      * <WebSocket>RECEIVER_RETURN_TYPE; 
    105.      *  
    106.      * @throws InterruptedException 
    107.      */  
    108.     @Override  
    109.     public void onMessage(String content) {  
    110.         // parser  
    111.         try {  
    112.             JSONObject object = new JSONObject(content);  
    113.             Log.i("json", "卖家--" + object.toString());  
    114.             String back_type = object.getString("back_type");  
    115.             String activity = getRunningActivityName();  
    116.             if (SEND_RETURN_TYPE.equals(back_type)) {// 发送具体消息时返回发送结果  
    117.                 // json解析  
    118.                 MessageEntity entity = mParser.sendMessageParser(mContext,  
    119.                         content);  
    120.                 if ("true".equals(entity.getSend_state())) {// 发送成功  
    121.                     // 判断是否是PC端发送的消息,若是PC端发送的消息,则在Android端做同步存储处理  
    122.                     // 1,首先判断数据库中是否包含该条信息  
    123.                     boolean has = MessageDB.getInstance(mContext)  
    124.                             .findMessageByMsgId(entity.get_id());  
    125.                     if (has) {  
    126.                         // Android端发送  
    127.                         MessageDB.getInstance(mContext).update(entity.get_id(),  
    128.                                 true, entity.getReceiverTime(),  
    129.                                 entity.getWebId());// 更新发送状态为已发送  
    130.                     } else {  
    131.                         // PC端发送,将该消息同步到Android端数据库  
    132.                         entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);  
    133.                         MessageDB.getInstance(mContext).insert(entity,  
    134.                                 SocketConstant.MSG_TYPE_BUYER);// 卖家发送给买家的  
    135.                         // 通知聊天主页面,更新聊天列表  
    136.                         pcSynAndroid(activity);  
    137.                     }  
    138.                     // 检测是否有消息遗漏  
    139.                     checkMsgWebId(entity.getContacts(), entity.getWebId());  
    140.                     Log.i("miss", "发送返回或者PC同步--" + entity.getContacts() + "--"  
    141.                             + entity.getWebId());  
    142.                 } else if ("false".equals(entity.getSend_state())) {// 发送失败  
    143.                     MessageDB.getInstance(mContext).update(entity.get_id(),  
    144.                             false, entity.getReceiverTime(), entity.getWebId());  
    145.                     Toast.makeText(mContext, entity.getErrorText(),  
    146.                             Toast.LENGTH_SHORT).show();  
    147.                 }  
    148.                 // 登陆返回 记录session  
    149.             } else if (LOGIN_RETURN_TYPE.equals(back_type)) {  
    150.                 KApplication.session = object.getString("session_id");  
    151.                 String str = object.getString("login_status");  
    152.                 if ("true".equals(str)) {  
    153.                     UserInfoUtil.saveSocket(mContext, true);  
    154.                     // 生成json请求字符串  
    155.                     String maxIdstring = SocketJsonUtil  
    156.                             .getContactsCurrentWebId(UserInfoUtil  
    157.                                     .getUser(mContext)[0], "2", MessageDB  
    158.                                     .getInstance(mContext)  
    159.                                     .findAllContactsAndType());  
    160.                     // 登陆成功,向服务器索取联系人的最大webId  
    161.                     send(maxIdstring);  
    162.                     Log.i("send", maxIdstring);  
    163.                 } else if ("false".equals(str)) {  
    164.                     UserInfoUtil.saveSocket(mContext, false);  
    165.                 }  
    166.   
    167.             } else if (RECEIVER_RETURN_TYPE.equals(back_type)) {// 接收到的具体聊天的信息  
    168.                 // json解析  
    169.                 MessageEntity entity = mParser.receiverMessagePrser(mContext,  
    170.                         content);  
    171.                 // 判断数据库中是否有该条消息,有则不处理,无则处理消息;  
    172.                 if (!MessageDB.getInstance(mContext).findMessageByMsgId(  
    173.                         entity.get_id())) {  
    174.                     // 消息处理  
    175.                     if (MSG_BUYER_TYPE.equals(entity.getSenderType())) {  
    176.                         // 买家  
    177.                         messageHandle(entity, SocketConstant.MSG_TYPE_BUYER);  
    178.                     } else if (MSG_SERVCER_TYPE.equals(entity.getSenderType())) {  
    179.                         // 卖家,客服  
    180.                         messageHandle(entity, SocketConstant.MSG_TYPE_SERVER);  
    181.                     }  
    182.                     Log.i("miss", "没有该条消息");  
    183.                     // 检测是否有消息遗漏  
    184.                     checkMsgWebId(entity.getContacts(), entity.getWebId());  
    185.                 }  
    186.             } else if (GET_SERVER_RETURN_TYPE.equals(back_type)) {// 获取闪聊客服返回的数据  
    187.                 // 客服  
    188.                 ServerEntity entity = mParser.serverInfoParser(content);// 客服对象  
    189.                 Intent intent = new Intent(SocketConstant.GET_SERVER_INFO);  
    190.                 intent.putExtra("server_info", entity);  
    191.                 mContext.sendBroadcast(intent);  
    192.             } else if (CONTACTS_MAX_WEBID_TYPE.equals(back_type)) {  
    193.                 // 返回的联系人最大的消息id  
    194.                 HashMap<String, String> map = mParser.contactsMaxWebId(content);  
    195.                 // 将联系人和其最大webId存入临时集合  
    196.                 saveContactsWebID(map);  
    197.                 // 开始请求服务器,释放离线消息给客户端;  
    198.                 send(SocketJsonUtil.getOffLine(  
    199.                         UserInfoUtil.getUser(mContext)[0], "2"));  
    200.                 Log.i("send",  
    201.                         SocketJsonUtil.getOffLine(  
    202.                                 UserInfoUtil.getUser(mContext)[0], "2"));  
    203.             } else if (USER_OFFLINE_MSG_TYPE.equals(back_type)) {  
    204.                 // 用户的离线消息  
    205.                 HashMap<String, ArrayList<MessageEntity>> map = mParser  
    206.                         .offLineMsg(mContext, content);  
    207.                 // 将离线消息入库  
    208.                 saveOffLineMsg(map);  
    209.             }  
    210.         } catch (JSONException e) {  
    211.             this.close();  
    212.         }  
    213.     }  
    214.   
    215.     /*** 
    216.      * send broadcast <SocketConstant>ON_OPEN filter if this socket opened 
    217.      * socket 打开时发送的广播,若想提示,可以接受并处理 
    218.      *  
    219.      */  
    220.     @Override  
    221.     public void onOpen(ServerHandshake arg0) {  
    222.         Intent intent = new Intent(SGSocketConstant.ON_OPEN);  
    223.         mContext.sendBroadcast(intent);  
    224.     }  
    225.   
    226.     /*** 
    227.      * 检测正在运行tasktop的activity 
    228.      * @return current running activity name 
    229.      *  
    230.      */  
    231.     private String getRunningActivityName() {  
    232.         ActivityManager activityManager = (ActivityManager) mContext  
    233.                 .getSystemService(Context.ACTIVITY_SERVICE);  
    234.         String runningActivity = activityManager.getRunningTasks(1).get(0).topActivity  
    235.                 .getClassName();  
    236.         return runningActivity;  
    237.     }  
    238.   
    239.     /*** 
    240.      * send notification for this contacts 
    241.      * 发送通知 
    242.      * @param contacts 
    243.      *  
    244.      */  
    245.     @SuppressWarnings("deprecation")  
    246.     private void sendNotification(String contacts) {  
    247.         Intent intent = new Intent(mContext, MainActivity.class);  
    248.         mIntent = PendingIntent.getActivity(mContext, 100, intent, 0);  
    249.         mNoti.flags = Notification.FLAG_AUTO_CANCEL;  
    250.         mNoti.defaults = Notification.DEFAULT_VIBRATE;  
    251.         mNoti.setLatestEventInfo(mContext, "标题", "您有新消息!", mIntent);  
    252.         mNoti.contentView = new RemoteViews(mContext.getApplicationContext()  
    253.                 .getPackageName(), R.layout.notification_item);  
    254.         mNoti.contentView.setTextViewText(R.id.noti_message, "收到来自" + contacts  
    255.                 + "的新消息");  
    256.         mNotificationManager.notify(NOTIFICATION_ID, mNoti);  
    257.     }  
    258.   
    259.     /*** 
    260.      * 具体聊天收到的外来消息处理 
    261.      *  
    262.      * @param entity 
    263.      *            消息实体 
    264.      * @param messageType 
    265.      *            消息类型(买家/客服) 
    266.      */  
    267.     private void messageHandle(MessageEntity entity, String messageType) {  
    268.         String activity = getRunningActivityName();  
    269.         // 处于聊天的页面  
    270.         if ("com.ui.activity.ManageChartActivity".equals(activity)) {  
    271.             // 处于正在聊天对象的页面,将数据写入数据库,并发送广播更新页面数据  
    272.             if (KApplication.crurentContacts.equals(entity.getContacts())) {  
    273.                 /** 
    274.                  * 接收到的消息,消息实体entity的send_state字段状态设置为 
    275.                  * MSG_SEND_SUCCESS_STATE(即201) 
    276.                  **/  
    277.                 entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);// 收到的信息,设置信息的状态  
    278.                 entity.setRead(SocketConstant.READ_STATE);  
    279.                 MessageDB.getInstance(mContext).insert(entity, messageType);// 将数据写入数据库,  
    280.                 Intent intent = new Intent(SocketConstant.NEW_MESSAGE);  
    281.                 intent.putExtra("newmsg", entity);  
    282.                 mContext.sendBroadcast(intent);  
    283.                 // 没有处于闪聊对象的页面,将数据写入数据库,发送系统通知  
    284.             } else {  
    285.                 entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);// 收到的信息,设置信息的状态  
    286.                 entity.setRead(SocketConstant.DEFAULT_READ_STATE);  
    287.                 MessageDB.getInstance(mContext).insert(entity, messageType);  
    288.                 if (KApplication.sp.getBoolean(RefreshUtils.noteFlag, false)) {  
    289.                     sendNotification(entity.getContacts());  
    290.                 }  
    291.                 Intent intent = new Intent(  
    292.                         SocketConstant.RECEIVER_NEW_MESSAGE);  
    293.                 mContext.sendBroadcast(intent);  
    294.             }  
    295.             // 将数据写入数据库,发送系统通知  
    296.         } else {  
    297.             entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);// 收到的信息,设置信息的状态  
    298.             entity.setRead(SocketConstant.DEFAULT_READ_STATE);  
    299.             MessageDB.getInstance(mContext).insert(entity, messageType);  
    300.             Intent intent = new Intent();  
    301.             if ("com.ui.activity.ManageConversationActivity"  
    302.                     .equals(activity)  
    303.                     || "com.ui.activity.MainActivity"  
    304.                             .equals(activity)) {  
    305.                 intent.setAction(SocketConstant.RECEIVER_NEW_MESSAGE);// 聊天页面  
    306.             } else {  
    307.                 intent.setAction(SocketConstant.RECEIVER_NEW_MESSAGE_OTHER);// 其他页面  
    308.             }  
    309.             mContext.sendBroadcast(intent);  
    310.             if (KApplication.sp.getBoolean(RefreshUtils.noteFlag, false)) {  
    311.                 sendNotification(entity.getContacts());  
    312.             }  
    313.         }  
    314.           
    315.     }  
    316.   
    317.     /*** 
    318.      * 电脑与手机同步信息 
    319.      *  
    320.      * @param currentActivity 
    321.      */  
    322.     public void pcSynAndroid(String currentActivity) {  
    323.         if ("com.iflashseller.ui.activity.ManageChartActivity"  
    324.                 .equals(currentActivity)) {  
    325.             // 正好与该联系人对话的页面  
    326.             Intent intent = new Intent(SocketConstant.CHART_ACTIVITY);  
    327.             mContext.sendBroadcast(intent);  
    328.         } else {  
    329.             // 其他页面  
    330.             Intent intent = new Intent(SocketConstant.GROUPS_ACTIVITY);  
    331.             mContext.sendBroadcast(intent);  
    332.         }  
    333.     }  
    334.   
    335.     /*** 
    336.      * 检测是否有消息遗漏 
    337.      *  
    338.      * @param contacts 
    339.      *            联系人 
    340.      * @param webId 
    341.      *            服务端给出的消息Id 
    342.      */  
    343.     public void checkMsgWebId(String contacts, int webId) {  
    344.         // 集合中含有该联系人  
    345.         if (KApplication.webIds.containsKey(contacts)) {  
    346.             Log.i("miss", "保存的--" + KApplication.webIds.get(contacts));  
    347.             // 临时集合中保存的webId  
    348.             int c = KApplication.webIds.get(contacts);  
    349.             /*** 
    350.              * 如果新收到的消息的webId大于临时集合中保存的改联系人的webId,且他们之间的差值大于1, 
    351.              * 则请求服务器推送疑似丢失的webId对应的消息 
    352.              */  
    353.             if (webId > c && (webId - 1) != c) {  
    354.                 // id不连续  
    355.                 for (int i = c + 1; i < webId; i++) {  
    356.                     // 向服务器发送请求,获取遗漏的消息  
    357.                     String miss = SocketJsonUtil.getMissMsg(  
    358.                             UserInfoUtil.getUser(mContext)[0], "2", contacts,  
    359.                             "1", i + "");  
    360.                     this.send(miss);  
    361.                     Log.i("miss", miss);  
    362.                 }  
    363.                 /*** 
    364.                  * 如果他们之间的差值正好为1,则修改临时集合的改联系人的webId, 
    365.                  */  
    366.             } else if (webId > c && (webId - 1) == c) {  
    367.                 KApplication.webIds.put(contacts, webId);  
    368.                 Log.i("miss", "修改的--" + contacts + "--" + webId);  
    369.             }  
    370.             /**** 
    371.              * 临时集合中没有改联系人的信息,则将该联系人的webId存入临时集合. 
    372.              */  
    373.         } else {  
    374.             KApplication.webIds.put(contacts, webId);  
    375.             Log.i("miss", "新增--" + contacts + "--" + webId);  
    376.         }  
    377.     }  
    378.   
    379.     /*** 
    380.      * 将从服务端获取的联系人的webId存入临时集合 
    381.      *  
    382.      * @param map 
    383.      */  
    384.     public void saveContactsWebID(HashMap<String, String> map) {  
    385.         Iterator<Entry<String, String>> iter = map.entrySet().iterator();  
    386.         while (iter.hasNext()) {  
    387.             Entry<String, String> es = iter.next();  
    388.             String contacts = es.getKey();  
    389.             String maxWebID = es.getValue();  
    390.             KApplication.webIds.put(contacts, Integer.parseInt(maxWebID));  
    391.         }  
    392.     }  
    393.   
    394.     /*** 
    395.      * 将离线消息入库 
    396.      *  
    397.      * @param map 
    398.      */  
    399.     public void saveOffLineMsg(HashMap<String, ArrayList<MessageEntity>> map) {  
    400.         Iterator<Entry<String, ArrayList<MessageEntity>>> iter = map.entrySet()  
    401.                 .iterator();  
    402.         while (iter.hasNext()) {  
    403.             ArrayList<MessageEntity> msgs = iter.next().getValue();  
    404.             for (int i = 0; i < msgs.size(); i++) {  
    405.                 threadSleep(100);  
    406.                 MessageDB.getInstance(mContext).insert(msgs.get(i),  
    407.                         SocketConstant.MSG_TYPE_BUYER);  
    408.                 Log.i("write", "离线数据入库---" + msgs.get(i).toString());  
    409.             }  
    410.             /*** 
    411.              * 如果服务端一次释放的离线消息大于等于10条,则继续请求释放离线消息. 
    412.              */  
    413.             if (msgs.size() >= 10) {  
    414.                 send(SocketJsonUtil.getOffLine(  
    415.                         UserInfoUtil.getUser(mContext)[0], "2"));  
    416.                 Log.i("send",  
    417.                         SocketJsonUtil.getOffLine(  
    418.                                 UserInfoUtil.getUser(mContext)[0], "2"));  
    419.             }  
    420.         }  
    421.         // 一轮消息入库结束,发送通知,更新UI;  
    422.         mContext.sendBroadcast(new Intent(  
    423.                 SocketConstant.OFFLINE_MSG_RECEIVER_SUCCESS));  
    424.     }  
    425.   
    426.     private void threadSleep(long time) {  
    427.         try {  
    428.             Thread.currentThread();  
    429.             Thread.sleep(time);  
    430.         } catch (InterruptedException e) {  
    431.             e.printStackTrace();  
    432.         }  
    433.     }  
    434. }  

    至于数据库的一部分代码就不贴出了,无非是增删改查。

     

     

     

    下面贴出部分监控链接状态的代码,以保证能即时的收到消息;

     

    [java]  view plain  copy
     
    1. public class LApplication extends Application {  
    2.   
    3.     public static String TAG = LApplication.class.getSimpleName();  
    4.     /** 接收消息广播 **/  
    5.     private LPullReceiver mPullReceiver;  
    6.     /** 是否是正在登录 **/  
    7.     public static boolean isLoging = false;  
    8.     /** socket管理类 **/  
    9.     private LPushManager mWebSocket;  
    10.       
    11.       
    12.   
    13.     @Override  
    14.     public void onCreate() {  
    15.         super.onCreate();  
    16.         /*** 
    17.          * 注册接收消息的广播 
    18.          */  
    19.         mPullReceiver = new LPullReceiver();  
    20.         // 广播过滤  
    21.         IntentFilter filter = new IntentFilter();  
    22.         // 时钟信息发生变化  
    23.         filter.addAction(Intent.ACTION_TIME_TICK);  
    24.         // 开机广播  
    25.         filter.addAction(Intent.ACTION_BOOT_COMPLETED);  
    26.         // 网络状态发生变化  
    27.         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);  
    28.         // 屏幕打开  
    29.         filter.addAction(Intent.ACTION_SCREEN_ON);  
    30.         // 注册广播  
    31.         registerReceiver(mPullReceiver, filter);  
    32.         // 实例化socket管理类  
    33.         mWebSocket = new LPushManager(getApplicationContext());  
    34.         // 应用重启一次,默认socket为关闭状态  
    35.         LPushUser.saveSocket(getApplicationContext(), false);  
    36.         // 默认链接没有被拒绝  
    37.         LPushUser.saveConnect(getApplicationContext(), false);  
    38.         // 1,获取当前时间  
    39.         long currentTime = System.currentTimeMillis();  
    40.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    41.         Date date = new Date(currentTime);  
    42.         String time = sdf.format(date);  
    43.         // 修改标记的时间,保证5分钟内链接一次  
    44.         LPushUser.saveOpenTime(getApplicationContext(), time);  
    45.     }  
    46.   
    47.     /** 
    48.      * 广播接口类 
    49.      * <ol> 
    50.      * <li>接收时钟发生变化的广播 
    51.      * <li>接收网络发生变化的广播 
    52.      * <li>接收开机发送广播 
    53.      * <li>接收用户登录后发送的广播 
    54.      * </ol> 
    55.      *  
    56.      * @author li'mingqi 
    57.      *  
    58.      */  
    59.     class LPullReceiver extends BroadcastReceiver {  
    60.         @SuppressLint("SimpleDateFormat")  
    61.         @Override  
    62.         public void onReceive(Context context, Intent intent) {  
    63.             String action = intent.getAction();  
    64.             if (Intent.ACTION_TIME_TICK.equals(action)  
    65.                     || ConnectivityManager.CONNECTIVITY_ACTION.equals(action)  
    66.                     || Intent.ACTION_BOOT_COMPLETED.equals(action)  
    67.                     || Intent.ACTION_SCREEN_ON.equals(action)) {  
    68.   
    69.                 if (LPushUser.GETLOG(getApplicationContext()))  
    70.                     Log.i("lpush",  
    71.                             "socket的链接状态----"  
    72.                                     + LPushUser  
    73.                                             .getSocket(getApplicationContext())  
    74.                                     + "是否正在链接--" + isLoging + "----" + action);  
    75.                 // 当时钟或者网络发生变化或者socket关闭或者发生异常时或者开机 时,判断socket连接是否出现异常  
    76.                 if (!LPushUser.getSocket(getApplicationContext()) && !isLoging  
    77.                         && LPushUser.getSocketEnable(getApplicationContext())  
    78.                         && !"".equals(IP) && !"".equals(PORT)  
    79.                         && !LPushUser.getConnectState(getApplicationContext())) {  
    80.                     // 掉线,执行登录动作  
    81.                       
    82.                       
    83.                     if (LNetworkUtil.netIsEnable(getApplicationContext())) {  
    84.                         mWebSocket.secondMethod(IP, PORT);  
    85.                         // 开始登录了,标记打开时间  
    86.                         // 1,获取当前时间  
    87.                         long currentTime = System.currentTimeMillis();  
    88.                         SimpleDateFormat sdf = new SimpleDateFormat(  
    89.                                 "yyyy-MM-dd HH:mm:ss");  
    90.                         Date date = new Date(currentTime);  
    91.                         String time = sdf.format(date);  
    92.                         // 修改标记的时间,保证5分钟嗅探链接一次  
    93.                         LPushUser.saveOpenTime(getApplicationContext(), time);  
    94.                     }  
    95.                 } else {  
    96.                     // APP端已经处于链接正常状态 -----5分钟嗅探链接一次  
    97.                     // 1,获取当前时间  
    98.                     long currentTime = System.currentTimeMillis();  
    99.                     SimpleDateFormat sdf = new SimpleDateFormat(  
    100.                             "yyyy-MM-dd HH:mm:ss");  
    101.                     Date date = new Date(currentTime);  
    102.                     String time = sdf.format(date);  
    103.                     // 2,比对链接打开时间  
    104.                     long minTime = LStringManager.dateDifference(  
    105.                             LPushUser.getOpenTime(getApplicationContext()),  
    106.                             time);  
    107.                     if (LPushUser.GETLOG(getApplicationContext())) {  
    108.                         Log.i("lpush",  
    109.                                 "链接时长----现在时间:"  
    110.                                         + time  
    111.                                         + ";保存时间:"  
    112.                                         + LPushUser  
    113.                                                 .getOpenTime(getApplicationContext())  
    114.                                         + ";时差" + minTime + "分钟");  
    115.                     }  
    116.                     if (minTime >= 5) {  
    117.                         // 大于等于5分钟,则重新链接  
    118.                           
    119.                         // 5分钟之后重新链接  
    120.                         // 修改被拒绝状态  
    121.                         if (LPushUser.getConnectState(getApplicationContext())) {  
    122.                             LPushUser.saveConnect(getApplicationContext(),  
    123.                                     false);  
    124.                         }  
    125.                         if (LNetworkUtil.netIsEnable(getApplicationContext())  
    126.                                 && LPushUser  
    127.                                         .getSocketEnable(getApplicationContext())) {  
    128.                             mWebSocket.secondMethod(IP, PORT);  
    129.                             // 修改标记的时间,保证5分钟嗅探链接一次  
    130.                             LPushUser.saveOpenTime(getApplicationContext(),  
    131.                                     time);  
    132.                         }  
    133.                     }  
    134.                 }  
    135.             }  
    136.         }  
    137.     }  
    138.   
    139.     /*** 
    140.      * 设置推送功能的使用与否,默认使用推送功能,若是关闭推送功能请设置false; 
    141.      *  
    142.      * @param enable 
    143.      *            是否使用 
    144.      *  
    145.      *            li'mingqi  
    146.      */  
    147.     protected void setLPushEnable(boolean enable) {  
    148.         LPushUser.saveSocketEnable(getApplicationContext(), enable);  
    149.     }  
    150.   
    151.       
    152.     /*** 
    153.      *  
    154.      *  
    155.      * @param ip 
    156.      *            ip信息 
    157.      * @param port 
    158.      *            端口信息 
    159.      *  
    160.      *            li'mingqi  
    161.      */  
    162.     protected void setSocketIPInfo(String ip, String port) {  
    163.         this.IP = ip;  
    164.         this.PORT = port;  
    165.     }  
    166.   
    167.       
    168.   
    169.     /** 
    170.      * 设置用户的Uid 
    171.      *  
    172.      * @param uid 
    173.      *            li'mingqi  
    174.      */  
    175.     public void setUserInfo(int uid, String code) {  
    176.         /*** 
    177.          * 数据验证 
    178.          */  
    179.         // if (0 == uid || null == code || "".equals(code)) {  
    180.         // Log.e(TAG, "您输入的用户ID或者CODE值为空");  
    181.         // new NullPointerException("您输入的用户ID或者CODE值为空").printStackTrace();  
    182.         // return;  
    183.         // }  
    184.   
    185.         // 保存用户ID  
    186.         LPushUser.saveUserID(getApplicationContext(), uid);  
    187.         // 保存用户CODE  
    188.         LPushUser.saveUserCode(getApplicationContext(), code);  
    189.         // 重启链接  
    190.         mWebSocket.close();  
    191.     }  
    192.   
    193.     /*** 
    194.      * 设置是否查看日志 
    195.      *  
    196.      * @param flag 
    197.      *            是否查看日志 
    198.      * @version 1.2 li'mingqi 
    199.      */  
    200.     public void openLogInfo(boolean flag) {  
    201.         LPushUser.SAVELOG(getApplicationContext(), flag);  
    202.     }  
    203.   
    204.     /*** 
    205.      * socket链接重置,服务器一直处于拒绝链接状态,客户端链接一次遭拒后标记了遭拒的状态,重置之后可进行再次开启链接; 
    206.      *  
    207.      * @version 1.3 li'mingqi  
    208.      */  
    209.     private void reset() {  
    210.         LPushUser.saveConnect(getApplicationContext(), false);  
    211.     }  
    212. }  

    UI界面显示部分就不贴出了,后面贴出Application类的目的就是监控各种手机系统的广播来嗅探自己的websocket链接的状态;用来维持它以保证能即时的收到消息;

    展开全文
  • Android WebSocket实现即时通讯功能

    千次阅读 2019-07-26 16:50:04
    我选择了Java-WebSocket这个开源框架,GitHub地址:https://github.com/TooTallNate/Java-WebSocket,目前已经有五千以上star,并且还在更新维护中,所以本文将介绍如何利用此开源库实现一个稳定的即时通讯功能。...

    Java-WebSocket框架

    对于使用websocket协议,Android端已经有些成熟的框架了,在经过对比之后,我选择了Java-WebSocket这个开源框架,GitHub地址:https://github.com/TooTallNate/Java-WebSocket,目前已经有五千以上star,并且还在更新维护中,所以本文将介绍如何利用此开源库实现一个稳定的即时通讯功能。

    文章重点

    1、与websocket建立长连接
    2、与websocket进行即时通讯
    3、Service和Activity之间通讯和UI更新
    4、弹出消息通知(包括锁屏通知)
    5、心跳检测和重连(保证websocket连接稳定性)
    6、服务(Service)保活

    一、引入Java-WebSocket

    1、build.gradle中加入

    implementation "org.java-websocket:Java-WebSocket:1.4.0"
    

    2、加入网络请求权限

    <uses-permission android:name="android.permission.INTERNET" />
    

    3、新建客户端类
    新建一个客户端类并继承WebSocketClient,需要实现它的四个抽象方法和构造函数,如下:

    public class JWebSocketClient extends WebSocketClient {
        public JWebSocketClient(URI serverUri) {
            super(serverUri, new Draft_6455());
        }
    
        @Override
        public void onOpen(ServerHandshake handshakedata) {
            Log.e("JWebSocketClient", "onOpen()");
        }
    
        @Override
        public void onMessage(String message) {
            Log.e("JWebSocketClient", "onMessage()");
        }
    
        @Override
        public void onClose(int code, String reason, boolean remote) {
            Log.e("JWebSocketClient", "onClose()");
        }
    
        @Override
        public void onError(Exception ex) {
            Log.e("JWebSocketClient", "onError()");
        }
    }

    其中onOpen()方法在websocket连接开启时调用,onMessage()方法在接收到消息时调用,onClose()方法在连接断开时调用,onError()方法在连接出错时调用。构造方法中的new Draft_6455()代表使用的协议版本,这里可以不写或者写成这样即可。

    4、建立websocket连接
    建立连接只需要初始化此客户端再调用连接方法,需要注意的是WebSocketClient对象是不能重复使用的,所以不能重复初始化,其他地方只能调用当前这个Client。

    URI uri = URI.create("ws://*******");
    JWebSocketClient client = new JWebSocketClient(uri) {
        @Override
        public void onMessage(String message) {
            //message就是接收到的消息
            Log.e("JWebSClientService", message);
        }
    };
    

    为了方便对接收到的消息进行处理,可以在这重写onMessage()方法。初始化客户端时需要传入websocket地址(测试地址:ws://echo.websocket.org),websocket协议地址大致是这样的

    ws:// ip地址 : 端口号
    

    连接时可以使用connect()方法或connectBlocking()方法,建议使用connectBlocking()方法,connectBlocking多出一个等待操作,会先连接再发送。

    try {
        client.connectBlocking();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    

    运行之后可以看到客户端的onOpen()方法得到了执行,表示已经和websocket建立了连接

     

    onOpen.png

    5、发送消息
    发送消息只需要调用send()方法,如下

    if (client != null && client.isOpen()) {
        client.send("你好");
    }
    

    6、关闭socket连接
    关闭连接调用close()方法,最后为了避免重复实例化WebSocketClient对象,关闭时一定要将对象置空。

    /**
     * 断开连接
     */
    private void closeConnect() {
        try {
            if (null != client) {
                client.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            client = null;
        }
    }
    

     

    二、后台运行

    一般来说即时通讯功能都希望像QQ微信这些App一样能在后台保持运行,当然App保活这个问题本身就是个伪命题,我们只能尽可能保活,所以首先就是建一个Service,将websocket的逻辑放入服务中运行并尽可能保活,让websocket保持连接。

    1、新建Service
    新建一个Service,在启动Service时实例化WebSocketClient对象并建立连接,将上面的代码搬到服务里即可。

    2、Service和Activity之间通讯
    由于消息是在Service中接收,从Activity中发送,需要获取到Service中的WebSocketClient对象,所以需要进行服务和活动之间的通讯,这就需要用到Service中的onBind()方法了。

    首先新建一个Binder类,让它继承自Binder,并在内部提供相应方法,然后在onBind()方法中返回这个类的实例。

    public class JWebSocketClientService extends Service {
        private URI uri;
        public JWebSocketClient client;
        private JWebSocketClientBinder mBinder = new JWebSocketClientBinder();
    
        //用于Activity和service通讯
        class JWebSocketClientBinder extends Binder {
            public JWebSocketClientService getService() {
                return JWebSocketClientService.this;
            }
        }
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    }
    

    接下来就需要对应的Activity绑定Service,并获取Service的东西,代码如下

    public class MainActivity extends AppCompatActivity {
        private JWebSocketClient client;
        private JWebSocketClientService.JWebSocketClientBinder binder;
        private JWebSocketClientService jWebSClientService;
    
        private ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                //服务与活动成功绑定
                Log.e("MainActivity", "服务与活动成功绑定");
                binder = (JWebSocketClientService.JWebSocketClientBinder) iBinder;
                jWebSClientService = binder.getService();
                client = jWebSClientService.client;
            }
    
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                //服务与活动断开
                Log.e("MainActivity", "服务与活动成功断开");
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            bindService();
        }
    
        /**
         * 绑定服务
         */
        private void bindService() {
            Intent bindIntent = new Intent(MainActivity.this, JWebSocketClientService.class);
            bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
        }
    }
    

    这里首先创建了一个ServiceConnection匿名类,在里面重写onServiceConnected()和onServiceDisconnected()方法,这两个方法会在活动与服务成功绑定以及连接断开时调用。在onServiceConnected()首先得到JWebSocketClientBinder的实例,有了这个实例便可调用服务的任何public方法,这里调用getService()方法得到Service实例,得到了Service实例也就得到了WebSocketClient对象,也就可以在活动中发送消息了。

     

    三、从Service中更新Activity的UI

    当Service中接收到消息时需要更新Activity中的界面,方法有很多,这里我们利用广播来实现,在对应Activity中定义广播接收者,Service中收到消息发出广播即可。

    public class MainActivity extends AppCompatActivity {
        ...
        private class ChatMessageReceiver extends BroadcastReceiver{
    
            @Override
            public void onReceive(Context context, Intent intent) {
                 String message=intent.getStringExtra("message");
            }
        }
    
       
        /**
         * 动态注册广播
         */
        private void doRegisterReceiver() {
            chatMessageReceiver = new ChatMessageReceiver();
            IntentFilter filter = new IntentFilter("com.xch.servicecallback.content");
            registerReceiver(chatMessageReceiver, filter);
        }
        ...
    }
    
    

    上面的代码很简单,首先创建一个内部类并继承自BroadcastReceiver,也就是代码中的广播接收器ChatMessageReceiver,然后动态注册这个广播接收器。当Service中接收到消息时发出广播,就能在ChatMessageReceiver里接收广播了。
    发送广播:

    client = new JWebSocketClient(uri) {
          @Override
          public void onMessage(String message) {
              Intent intent = new Intent();
              intent.setAction("com.xch.servicecallback.content");
              intent.putExtra("message", message);
              sendBroadcast(intent);
          }
    };
    

    获取广播传过来的消息后即可更新UI,具体布局就不细说,比较简单,看下我的源码就知道了,demo地址我会放到文章末尾。

     

    四、消息通知

    消息通知直接使用Notification,只是当锁屏时需要先点亮屏幕,代码如下

      /**
       * 检查锁屏状态,如果锁屏先点亮屏幕
       *
       * @param content
       */
      private void checkLockAndShowNotification(String content) {
          //管理锁屏的一个服务
          KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
          if (km.inKeyguardRestrictedInputMode()) {//锁屏
              //获取电源管理器对象
              PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
              if (!pm.isScreenOn()) {
                  @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP |
                            PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");
                  wl.acquire();  //点亮屏幕
                  wl.release();  //任务结束后释放
              }
              sendNotification(content);
          } else {
              sendNotification(content);
          }
      }
    
      /**
       * 发送通知
       *
       * @param content
       */
      private void sendNotification(String content) {
          Intent intent = new Intent();
          intent.setClass(this, MainActivity.class);
          PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
          NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
          Notification notification = new NotificationCompat.Builder(this)
                  .setAutoCancel(true)
                  // 设置该通知优先级
                  .setPriority(Notification.PRIORITY_MAX)
                  .setSmallIcon(R.mipmap.ic_launcher)
                  .setContentTitle("昵称")
                  .setContentText(content)
                  .setVisibility(VISIBILITY_PUBLIC)
                  .setWhen(System.currentTimeMillis())
                  // 向通知添加声音、闪灯和振动效果
                  .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_ALL | Notification.DEFAULT_SOUND)
                  .setContentIntent(pendingIntent)
                  .build();
          notifyManager.notify(1, notification);//id要保证唯一
      }
    
    

    如果未收到通知可能是设置里通知没开,进入设置打开即可,如果锁屏时无法弹出通知,可能是未开启锁屏通知权限,也需进入设置开启。为了保险起见我们可以判断通知是否开启,未开启引导用户开启,代码如下:

      /**
       * 检测是否开启通知
       *
       * @param context
       */
      private void checkNotification(final Context context) {
          if (!isNotificationEnabled(context)) {
              new AlertDialog.Builder(context).setTitle("温馨提示")
                      .setMessage("你还未开启系统通知,将影响消息的接收,要去开启吗?")
                      .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                          @Override
                          public void onClick(DialogInterface dialog, int which) {
                              setNotification(context);
                          }
                      }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
    
                  }
              }).show();
          }
      }
      /**
       * 如果没有开启通知,跳转至设置界面
       *
       * @param context
       */
      private void setNotification(Context context) {
          Intent localIntent = new Intent();
          //直接跳转到应用通知设置的代码:
          if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
              localIntent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
              localIntent.putExtra("app_package", context.getPackageName());
              localIntent.putExtra("app_uid", context.getApplicationInfo().uid);
          } else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
                localIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                localIntent.addCategory(Intent.CATEGORY_DEFAULT);
                localIntent.setData(Uri.parse("package:" + context.getPackageName()));
          } else {
              //4.4以下没有从app跳转到应用通知设置页面的Action,可考虑跳转到应用详情页面
              localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
              if (Build.VERSION.SDK_INT >= 9) {
                    localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
                    localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
              } else if (Build.VERSION.SDK_INT <= 8) {
                    localIntent.setAction(Intent.ACTION_VIEW);
                    localIntent.setClassName("com.android.settings", "com.android.setting.InstalledAppDetails");
                    localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
              }
          }
          context.startActivity(localIntent);
      }
    
      /**
       * 获取通知权限,检测是否开启了系统通知
       *
       * @param context
       */
      @TargetApi(Build.VERSION_CODES.KITKAT)
      private boolean isNotificationEnabled(Context context) {
          String CHECK_OP_NO_THROW = "checkOpNoThrow";
          String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
    
          AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
          ApplicationInfo appInfo = context.getApplicationInfo();
          String pkg = context.getApplicationContext().getPackageName();
          int uid = appInfo.uid;
    
          Class appOpsClass = null;
          try {
              appOpsClass = Class.forName(AppOpsManager.class.getName());
              Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
                        String.class);
              Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
    
              int value = (Integer) opPostNotificationValue.get(Integer.class);
              return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
    
          } catch (Exception e) {
              e.printStackTrace();
          }
          return false;
      }
    

    最后加入相关的权限

        <!-- 解锁屏幕需要的权限 -->
        <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
        <!-- 申请电源锁需要的权限 -->
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <!--震动权限-->
        <uses-permission android:name="android.permission.VIBRATE" />
    

     

    五、心跳检测和重连

    由于很多不确定因素会导致websocket连接断开,例如网络断开,所以需要保证websocket的连接稳定性,这就需要加入心跳检测和重连。
    心跳检测其实就是个定时器,每个一段时间检测一次,如果连接断开则重连,Java-WebSocket框架在目前最新版本中有两个重连的方法,分别是reconnect()和reconnectBlocking(),这里同样使用后者。

      private static final long HEART_BEAT_RATE = 10 * 1000;//每隔10秒进行一次对长连接的心跳检测
      private Handler mHandler = new Handler();
      private Runnable heartBeatRunnable = new Runnable() {
          @Override
          public void run() {
              if (client != null) {
                  if (client.isClosed()) {
                      reconnectWs();
                  }
              } else {
                  //如果client已为空,重新初始化websocket
                  initSocketClient();
              }
              //定时对长连接进行心跳检测
              mHandler.postDelayed(this, HEART_BEAT_RATE);
          }
      };
    
      /**
       * 开启重连
       */
      private void reconnectWs() {
          mHandler.removeCallbacks(heartBeatRunnable);
          new Thread() {
              @Override
              public void run() {
                  try {
                      //重连
                      client.reconnectBlocking();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }.start();
      }
    

    然后在服务启动时开启心跳检测

    mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测
    

    我们打印一下日志,如图所示

     

    heartbeat.png

     

    六、服务(Service)保活

    如果某些业务场景需要App保活,例如利用这个websocket来做推送,那就需要我们的App后台服务不被kill掉,当然如果和手机厂商没有合作,要保证服务一直不被杀死,这可能是所有Android开发者比较头疼的一个事,这里我们只能尽可能的来保证Service的存活。

    1、提高服务优先级(前台服务)
    前台服务的优先级比较高,它会在状态栏显示类似于通知的效果,可以尽量避免在内存不足时被系统回收,前台服务比较简单就不细说了。有时候我们希望可以使用前台服务但是又不希望在状态栏有显示,那就可以利用灰色保活的办法,如下

      private final static int GRAY_SERVICE_ID = 1001;
      //灰色保活手段
      public static class GrayInnerService extends Service {
          @Override
          public int onStartCommand(Intent intent, int flags, int startId) {
              startForeground(GRAY_SERVICE_ID, new Notification());
              stopForeground(true);
              stopSelf();
              return super.onStartCommand(intent, flags, startId);
          }
          @Override
          public IBinder onBind(Intent intent) {
              return null;
          }
      }
    
    
       //设置service为前台服务,提高优先级
       if (Build.VERSION.SDK_INT < 18) {
           //Android4.3以下 ,隐藏Notification上的图标
           startForeground(GRAY_SERVICE_ID, new Notification());
       } else if(Build.VERSION.SDK_INT>18 && Build.VERSION.SDK_INT<25){
           //Android4.3 - Android7.0,隐藏Notification上的图标
           Intent innerIntent = new Intent(this, GrayInnerService.class);
           startService(innerIntent);
           startForeground(GRAY_SERVICE_ID, new Notification());
       }else{
           //暂无解决方法
           startForeground(GRAY_SERVICE_ID, new Notification());
       }
    

    AndroidManifest.xml中注册这个服务

       <service android:name=".im.JWebSocketClientService$GrayInnerService"
           android:enabled="true"
           android:exported="false"
           android:process=":gray"/>
    

    这里其实就是开启前台服务并隐藏了notification,也就是再启动一个service并共用一个通知栏,然后stop这个service使得通知栏消失。但是7.0以上版本会在状态栏显示“正在运行”的通知,目前暂时没有什么好的解决办法。

    2、修改Service的onStartCommand 方法返回值

      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
          ...
          return START_STICKY;
      }
    

    onStartCommand()返回一个整型值,用来描述系统在杀掉服务后是否要继续启动服务,START_STICKY表示如果Service进程被kill掉,系统会尝试重新创建Service。

    3、锁屏唤醒

      PowerManager.WakeLock wakeLock;//锁屏唤醒
      private void acquireWakeLock()
      {
          if (null == wakeLock)
          {
              PowerManager pm = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
              wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE, "PostLocationService");
              if (null != wakeLock)
              {
                  wakeLock.acquire();
              }
          }
      }
    

    获取电源锁,保持该服务在屏幕熄灭时仍然获取CPU时,让其保持运行。

    4、其他保活方式
    服务保活还有许多其他方式,比如进程互拉、一像素保活、申请自启权限、引导用户设置白名单等,其实Android 7.0版本以后,目前没有什么真正意义上的保活,但是做些处理,总比不做处理强。这篇文章重点是即时通讯,对于服务保活有需要的可以自行查阅更多资料,这里就不细说了。

    最后附上这篇文章源码地址,GitHub:https://github.com/yangxch/WebSocketClient

    原创不易,转载请注明出处:https://www.jianshu.com/p/7b919910c892

    展开全文
  • Android WebSocket实现即时通讯/推送

    千次阅读 2017-12-09 22:37:27
    使用java-websocket实现即时通讯/推送模块; 支持即时发消息和收消息,消息类型有开发者自定义;; 该模块需要websocket.jar的支持;开源项目地址--https://github.com/TooTallNate/Java-WebSo

    From:http://blog.csdn.net/li352558693/article/details/42639683

    使用java-websocket实现即时通讯/推送模块;

    支持即时发消息和收消息,消息类型有开发者自定义;;

    该模块需要websocket.jar的支持;开源项目地址--https://github.com/TooTallNate/Java-WebSocket

    该开源项目支持客户端client和服务端server的配置使用,并提供示例test;


     

    1,Android 客户端使用需要配置网络权限; 

    2,需要写一个自己的client类来继承WebsocketClient;实现websocket的状态回调和新消息的解析动作;

    3,需要监控自己的client的链接状态,维持长链接;

    4,发送和接收


    下面贴出部分相关代码;

    网络权限的不用说了吧!

    client类:

    常用状态回调方法有4个

    自己可以在对应的函数里面做响应的处理,比如当链接发生错误时要重新去打开该链接,收到消息时即时的保存聊天记录和发送系统通知来提醒用户查看新消息等等;

    [html]  view plain  copy
    1. public class TestClient extends WebSocketClient {  
    2.   
    3.     public TestClient(URI serverURI) {  
    4.         super(serverURI);  
    5.     }  
    6.     /***  
    7.      * 链接关闭  
    8.      */  
    9.     @Override  
    10.     public void onClose(int arg0, String arg1, boolean arg2) {  
    11.   
    12.     }  
    13.     /***  
    14.      * 链接发生错误  
    15.      */  
    16.     @Override  
    17.     public void onError(Exception arg0) {  
    18.   
    19.     }  
    20.     /**  
    21.      * 新消息  
    22.      */  
    23.     @Override  
    24.     public void onMessage(String arg0) {  
    25.   
    26.     }  
    27.     /***  
    28.      * 链接打开  
    29.      */  
    30.     @Override  
    31.     public void onOpen(ServerHandshake arg0) {  
    32.         // TODO Auto-generated method stub  
    33.   
    34.     }  
    35. }  

     
     下面是我聊天部分代码,有离线消息/PC同步/多对多的聊天;
      
     

    仅供参考; copy

    1. /*** 
    2.  * <h2>WebSocket Android 客户端</h2> 
    3.  * <ol> 
    4.  * <li>Socket链接打开回调 {@link WebSocket#onOpen(ServerHandshake)},此处有 
    5.  * {@link SocketConstant#ON_OPEN} 广播发出; 
    6.  * <li>Socket链接出现异常错误时回调 {@link WebSocket#onError(Exception)},此处有 
    7.  * {@link SocketConstant#ON_ERROR}广播发出; 
    8.  * <li>Socket链接关闭回调 {@link WebSocket #onClose(int, String, boolean)},此处有 
    9.  * {@link SocketConstant#ON_CLOSES}广播发出; 
    10.  * <li>Socket链接接收消息回调 {@link WebSocket#onMessage(String)} 
    11.  * ,此处做收消息的逻辑的处理;包括发送消息服务器返回的发送结果,PC端同步接收的新消息,及外来的新消息; 
    12.  * <li>检测是否有消息遗漏 {@link WebSocket#checkMsgWebId(String, String)},参数为联系人和webId; 
    13.  * <li>取得正在桌面运行的activity的名称 {@link WebSocket#getRunningActivityName()} 
    14.  * <li>接收到的消息的处理 {@link WebSocket#messageHandle(MessageEntity, String)} 
    15.  * ,参数为消息实体和消息类型 (buyer/server) 
    16.  * <li>发送新消息的系统通知 {@link WebSocket#sendNotification(String)},参数为联系人; 
    17.  * <li>保存离线消息 {@link WebSocket#saveOffLineMsg(HashMap)},参数为接收到的离线消息集合; 
    18.  * <li>保存从服务端获取的联系人的webId {@link WebSocket#saveContactsWebID(HashMap)} 
    19.  * ,参数为以联系人为key以最大webId为值得map集合; 
    20.  * </ol> 
    21.  *  
    22.  * @author li'mingqi <a> 2014-3-19</a> 
    23.  *  
    24.  */  
    25. public class WebSocket extends WebSocketClient {  
    26.     // 登陆返回的back_type字段  
    27.     public static final String LOGIN_RETURN_TYPE = "login";  
    28.     // 发送信息的back_type字段  
    29.     public static final String SEND_RETURN_TYPE = "send_result";  
    30.     // 接收信息的back_type字段  
    31.     public static final String RECEIVER_RETURN_TYPE = "msg";  
    32.     // 接收客服的信息的back_type字段  
    33.     public static final String GET_SERVER_RETURN_TYPE = "server_info";  
    34.     // 接收服务端返回对应联系人的最大顺序ID  
    35.     public static final String CONTACTS_MAX_WEBID_TYPE = "max_id_return";  
    36.     // 接收用户的离线消息  
    37.     public static final String USER_OFFLINE_MSG_TYPE = "offline";  
    38.     // 上下文对象  
    39.     private Context mContext;  
    40.     // socket返回json解析类对象  
    41.     private WebSocketParser mParser;  
    42.     // 系统通知管理  
    43.     public NotificationManager mNotificationManager;  
    44.     // 系统通知  
    45.     private Notification mNoti;  
    46.     // 意图  
    47.     private PendingIntent mIntent;  
    48.     // 该系统通知的 id  
    49.     public static final int NOTIFICATION_ID = 100;  
    50.   
    51.     @SuppressWarnings("deprecation")  
    52.     public SGWebSocket(Context context, URI serverUri, Draft draft) {  
    53.         super(serverUri, draft);  
    54.         this.mContext = context;  
    55.                 //新消息的解析类  
    56.                 this.mParser = WebSocketParser.getInstance();  
    57.                 //收到新消息发送的通知  
    58.                 this.mNotificationManager = (NotificationManager) this.mContext  
    59.                 .getSystemService(Context.NOTIFICATION_SERVICE);  
    60.         this.mNoti = new Notification(R.drawable.system_info, "您有新消息!",  
    61.                 System.currentTimeMillis());  
    62.     }  
    63.   
    64.     /*** 
    65.      * send broadcast <SGSocketConstant>ON_CLOSES filter if this socket closed 
    66.      * socket 发生关闭时发送的广播,若想提示,可以接受并处理 
    67.      *  
    68.      */  
    69.     @Override  
    70.     public void onClose(int arg0, String arg1, boolean arg2) {  
    71.         // 更改保存的链接状态  
    72.         UserInfoUtil.saveSocket(mContext, false);  
    73.         mNotificationManager.cancelAll();  
    74.         Intent intent = new Intent(SocketConstant.ON_CLOSES);  
    75.         intent.putExtra(SocketConstant.ON_CLOSES, arg1.toString());  
    76.         mContext.sendBroadcast(intent);  
    77.     }  
    78.   
    79.     /*** 
    80.      * send broadcast <SGSocketConstant>ON_ERROR filter if this socket has error 
    81.      * socket 发生错误发送的广播,若想提示,可以接受并处理 
    82.      *  
    83.      */  
    84.     @Override  
    85.     public void onError(Exception arg0) {  
    86.         Intent intent = new Intent(SGSocketConstant.ON_ERROR);  
    87.         intent.putExtra(SGSocketConstant.ON_ERROR, arg0.toString());  
    88.         mContext.sendBroadcast(intent);  
    89.         this.close();  
    90.     }  
    91.   
    92.     // 买家  
    93.     public static final String MSG_BUYER_TYPE = "1";  
    94.     // 客服  
    95.     public static final String MSG_SERVCER_TYPE = "2";  
    96.   
    97.     // 游客  
    98.     // public static final String MSG_RANDOM_TYPE = "3";  
    99.   
    100.     /*** 
    101.      * receiver message from server 1,登陆返回 type 
    102.      * <WebSocket>LOGIN_RETURN_TYPE; 2,发送返回 type 
    103.      * <WebSocket>SEND_RETURN_TYPE; 3,接收信息返回 type 
    104.      * <WebSocket>RECEIVER_RETURN_TYPE; 
    105.      *  
    106.      * @throws InterruptedException 
    107.      */  
    108.     @Override  
    109.     public void onMessage(String content) {  
    110.         // parser  
    111.         try {  
    112.             JSONObject object = new JSONObject(content);  
    113.             Log.i("json""卖家--" + object.toString());  
    114.             String back_type = object.getString("back_type");  
    115.             String activity = getRunningActivityName();  
    116.             if (SEND_RETURN_TYPE.equals(back_type)) {// 发送具体消息时返回发送结果  
    117.                 // json解析  
    118.                 MessageEntity entity = mParser.sendMessageParser(mContext,  
    119.                         content);  
    120.                 if ("true".equals(entity.getSend_state())) {// 发送成功  
    121.                     // 判断是否是PC端发送的消息,若是PC端发送的消息,则在Android端做同步存储处理  
    122.                     // 1,首先判断数据库中是否包含该条信息  
    123.                     boolean has = MessageDB.getInstance(mContext)  
    124.                             .findMessageByMsgId(entity.get_id());  
    125.                     if (has) {  
    126.                         // Android端发送  
    127.                         MessageDB.getInstance(mContext).update(entity.get_id(),  
    128.                                 true, entity.getReceiverTime(),  
    129.                                 entity.getWebId());// 更新发送状态为已发送  
    130.                     } else {  
    131.                         // PC端发送,将该消息同步到Android端数据库  
    132.                         entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);  
    133.                         MessageDB.getInstance(mContext).insert(entity,  
    134.                                 SocketConstant.MSG_TYPE_BUYER);// 卖家发送给买家的  
    135.                         // 通知聊天主页面,更新聊天列表  
    136.                         pcSynAndroid(activity);  
    137.                     }  
    138.                     // 检测是否有消息遗漏  
    139.                     checkMsgWebId(entity.getContacts(), entity.getWebId());  
    140.                     Log.i("miss""发送返回或者PC同步--" + entity.getContacts() + "--"  
    141.                             + entity.getWebId());  
    142.                 } else if ("false".equals(entity.getSend_state())) {// 发送失败  
    143.                     MessageDB.getInstance(mContext).update(entity.get_id(),  
    144.                             false, entity.getReceiverTime(), entity.getWebId());  
    145.                     Toast.makeText(mContext, entity.getErrorText(),  
    146.                             Toast.LENGTH_SHORT).show();  
    147.                 }  
    148.                 // 登陆返回 记录session  
    149.             } else if (LOGIN_RETURN_TYPE.equals(back_type)) {  
    150.                 KApplication.session = object.getString("session_id");  
    151.                 String str = object.getString("login_status");  
    152.                 if ("true".equals(str)) {  
    153.                     UserInfoUtil.saveSocket(mContext, true);  
    154.                     // 生成json请求字符串  
    155.                     String maxIdstring = SocketJsonUtil  
    156.                             .getContactsCurrentWebId(UserInfoUtil  
    157.                                     .getUser(mContext)[0], "2", MessageDB  
    158.                                     .getInstance(mContext)  
    159.                                     .findAllContactsAndType());  
    160.                     // 登陆成功,向服务器索取联系人的最大webId  
    161.                     send(maxIdstring);  
    162.                     Log.i("send", maxIdstring);  
    163.                 } else if ("false".equals(str)) {  
    164.                     UserInfoUtil.saveSocket(mContext, false);  
    165.                 }  
    166.   
    167.             } else if (RECEIVER_RETURN_TYPE.equals(back_type)) {// 接收到的具体聊天的信息  
    168.                 // json解析  
    169.                 MessageEntity entity = mParser.receiverMessagePrser(mContext,  
    170.                         content);  
    171.                 // 判断数据库中是否有该条消息,有则不处理,无则处理消息;  
    172.                 if (!MessageDB.getInstance(mContext).findMessageByMsgId(  
    173.                         entity.get_id())) {  
    174.                     // 消息处理  
    175.                     if (MSG_BUYER_TYPE.equals(entity.getSenderType())) {  
    176.                         // 买家  
    177.                         messageHandle(entity, SocketConstant.MSG_TYPE_BUYER);  
    178.                     } else if (MSG_SERVCER_TYPE.equals(entity.getSenderType())) {  
    179.                         // 卖家,客服  
    180.                         messageHandle(entity, SocketConstant.MSG_TYPE_SERVER);  
    181.                     }  
    182.                     Log.i("miss""没有该条消息");  
    183.                     // 检测是否有消息遗漏  
    184.                     checkMsgWebId(entity.getContacts(), entity.getWebId());  
    185.                 }  
    186.             } else if (GET_SERVER_RETURN_TYPE.equals(back_type)) {// 获取闪聊客服返回的数据  
    187.                 // 客服  
    188.                 ServerEntity entity = mParser.serverInfoParser(content);// 客服对象  
    189.                 Intent intent = new Intent(SocketConstant.GET_SERVER_INFO);  
    190.                 intent.putExtra("server_info", entity);  
    191.                 mContext.sendBroadcast(intent);  
    192.             } else if (CONTACTS_MAX_WEBID_TYPE.equals(back_type)) {  
    193.                 // 返回的联系人最大的消息id  
    194.                 HashMap<String, String> map = mParser.contactsMaxWebId(content);  
    195.                 // 将联系人和其最大webId存入临时集合  
    196.                 saveContactsWebID(map);  
    197.                 // 开始请求服务器,释放离线消息给客户端;  
    198.                 send(SocketJsonUtil.getOffLine(  
    199.                         UserInfoUtil.getUser(mContext)[0], "2"));  
    200.                 Log.i("send",  
    201.                         SocketJsonUtil.getOffLine(  
    202.                                 UserInfoUtil.getUser(mContext)[0], "2"));  
    203.             } else if (USER_OFFLINE_MSG_TYPE.equals(back_type)) {  
    204.                 // 用户的离线消息  
    205.                 HashMap<String, ArrayList<MessageEntity>> map = mParser  
    206.                         .offLineMsg(mContext, content);  
    207.                 // 将离线消息入库  
    208.                 saveOffLineMsg(map);  
    209.             }  
    210.         } catch (JSONException e) {  
    211.             this.close();  
    212.         }  
    213.     }  
    214.   
    215.     /*** 
    216.      * send broadcast <SocketConstant>ON_OPEN filter if this socket opened 
    217.      * socket 打开时发送的广播,若想提示,可以接受并处理 
    218.      *  
    219.      */  
    220.     @Override  
    221.     public void onOpen(ServerHandshake arg0) {  
    222.         Intent intent = new Intent(SGSocketConstant.ON_OPEN);  
    223.         mContext.sendBroadcast(intent);  
    224.     }  
    225.   
    226.     /*** 
    227.      * 检测正在运行tasktop的activity 
    228.      * @return current running activity name 
    229.      *  
    230.      */  
    231.     private String getRunningActivityName() {  
    232.         ActivityManager activityManager = (ActivityManager) mContext  
    233.                 .getSystemService(Context.ACTIVITY_SERVICE);  
    234.         String runningActivity = activityManager.getRunningTasks(1).get(0).topActivity  
    235.                 .getClassName();  
    236.         return runningActivity;  
    237.     }  
    238.   
    239.     /*** 
    240.      * send notification for this contacts 
    241.      * 发送通知 
    242.      * @param contacts 
    243.      *  
    244.      */  
    245.     @SuppressWarnings("deprecation")  
    246.     private void sendNotification(String contacts) {  
    247.         Intent intent = new Intent(mContext, MainActivity.class);  
    248.         mIntent = PendingIntent.getActivity(mContext, 100, intent, 0);  
    249.         mNoti.flags = Notification.FLAG_AUTO_CANCEL;  
    250.         mNoti.defaults = Notification.DEFAULT_VIBRATE;  
    251.         mNoti.setLatestEventInfo(mContext, "标题""您有新消息!", mIntent);  
    252.         mNoti.contentView = new RemoteViews(mContext.getApplicationContext()  
    253.                 .getPackageName(), R.layout.notification_item);  
    254.         mNoti.contentView.setTextViewText(R.id.noti_message, "收到来自" + contacts  
    255.                 + "的新消息");  
    256.         mNotificationManager.notify(NOTIFICATION_ID, mNoti);  
    257.     }  
    258.   
    259.     /*** 
    260.      * 具体聊天收到的外来消息处理 
    261.      *  
    262.      * @param entity 
    263.      *            消息实体 
    264.      * @param messageType 
    265.      *            消息类型(买家/客服) 
    266.      */  
    267.     private void messageHandle(MessageEntity entity, String messageType) {  
    268.         String activity = getRunningActivityName();  
    269.         // 处于聊天的页面  
    270.         if ("com.ui.activity.ManageChartActivity".equals(activity)) {  
    271.             // 处于正在聊天对象的页面,将数据写入数据库,并发送广播更新页面数据  
    272.             if (KApplication.crurentContacts.equals(entity.getContacts())) {  
    273.                 /** 
    274.                  * 接收到的消息,消息实体entity的send_state字段状态设置为 
    275.                  * MSG_SEND_SUCCESS_STATE(即201) 
    276.                  **/  
    277.                 entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);// 收到的信息,设置信息的状态  
    278.                 entity.setRead(SocketConstant.READ_STATE);  
    279.                 MessageDB.getInstance(mContext).insert(entity, messageType);// 将数据写入数据库,  
    280.                 Intent intent = new Intent(SocketConstant.NEW_MESSAGE);  
    281.                 intent.putExtra("newmsg", entity);  
    282.                 mContext.sendBroadcast(intent);  
    283.                 // 没有处于闪聊对象的页面,将数据写入数据库,发送系统通知  
    284.             } else {  
    285.                 entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);// 收到的信息,设置信息的状态  
    286.                 entity.setRead(SocketConstant.DEFAULT_READ_STATE);  
    287.                 MessageDB.getInstance(mContext).insert(entity, messageType);  
    288.                 if (KApplication.sp.getBoolean(RefreshUtils.noteFlag, false)) {  
    289.                     sendNotification(entity.getContacts());  
    290.                 }  
    291.                 Intent intent = new Intent(  
    292.                         SocketConstant.RECEIVER_NEW_MESSAGE);  
    293.                 mContext.sendBroadcast(intent);  
    294.             }  
    295.             // 将数据写入数据库,发送系统通知  
    296.         } else {  
    297.             entity.setSend_state(SocketConstant.MSG_SEND_SUCCESS_STATE);// 收到的信息,设置信息的状态  
    298.             entity.setRead(SocketConstant.DEFAULT_READ_STATE);  
    299.             MessageDB.getInstance(mContext).insert(entity, messageType);  
    300.             Intent intent = new Intent();  
    301.             if ("com.ui.activity.ManageConversationActivity"  
    302.                     .equals(activity)  
    303.                     || "com.ui.activity.MainActivity"  
    304.                             .equals(activity)) {  
    305.                 intent.setAction(SocketConstant.RECEIVER_NEW_MESSAGE);// 聊天页面  
    306.             } else {  
    307.                 intent.setAction(SocketConstant.RECEIVER_NEW_MESSAGE_OTHER);// 其他页面  
    308.             }  
    309.             mContext.sendBroadcast(intent);  
    310.             if (KApplication.sp.getBoolean(RefreshUtils.noteFlag, false)) {  
    311.                 sendNotification(entity.getContacts());  
    312.             }  
    313.         }  
    314.           
    315.     }  
    316.   
    317.     /*** 
    318.      * 电脑与手机同步信息 
    319.      *  
    320.      * @param currentActivity 
    321.      */  
    322.     public void pcSynAndroid(String currentActivity) {  
    323.         if ("com.iflashseller.ui.activity.ManageChartActivity"  
    324.                 .equals(currentActivity)) {  
    325.             // 正好与该联系人对话的页面  
    326.             Intent intent = new Intent(SocketConstant.CHART_ACTIVITY);  
    327.             mContext.sendBroadcast(intent);  
    328.         } else {  
    329.             // 其他页面  
    330.             Intent intent = new Intent(SocketConstant.GROUPS_ACTIVITY);  
    331.             mContext.sendBroadcast(intent);  
    332.         }  
    333.     }  
    334.   
    335.     /*** 
    336.      * 检测是否有消息遗漏 
    337.      *  
    338.      * @param contacts 
    339.      *            联系人 
    340.      * @param webId 
    341.      *            服务端给出的消息Id 
    342.      */  
    343.     public void checkMsgWebId(String contacts, int webId) {  
    344.         // 集合中含有该联系人  
    345.         if (KApplication.webIds.containsKey(contacts)) {  
    346.             Log.i("miss""保存的--" + KApplication.webIds.get(contacts));  
    347.             // 临时集合中保存的webId  
    348.             int c = KApplication.webIds.get(contacts);  
    349.             /*** 
    350.              * 如果新收到的消息的webId大于临时集合中保存的改联系人的webId,且他们之间的差值大于1, 
    351.              * 则请求服务器推送疑似丢失的webId对应的消息 
    352.              */  
    353.             if (webId > c && (webId - 1) != c) {  
    354.                 // id不连续  
    355.                 for (int i = c + 1; i < webId; i++) {  
    356.                     // 向服务器发送请求,获取遗漏的消息  
    357.                     String miss = SocketJsonUtil.getMissMsg(  
    358.                             UserInfoUtil.getUser(mContext)[0], "2", contacts,  
    359.                             "1", i + "");  
    360.                     this.send(miss);  
    361.                     Log.i("miss", miss);  
    362.                 }  
    363.                 /*** 
    364.                  * 如果他们之间的差值正好为1,则修改临时集合的改联系人的webId, 
    365.                  */  
    366.             } else if (webId > c && (webId - 1) == c) {  
    367.                 KApplication.webIds.put(contacts, webId);  
    368.                 Log.i("miss""修改的--" + contacts + "--" + webId);  
    369.             }  
    370.             /**** 
    371.              * 临时集合中没有改联系人的信息,则将该联系人的webId存入临时集合. 
    372.              */  
    373.         } else {  
    374.             KApplication.webIds.put(contacts, webId);  
    375.             Log.i("miss""新增--" + contacts + "--" + webId);  
    376.         }  
    377.     }  
    378.   
    379.     /*** 
    380.      * 将从服务端获取的联系人的webId存入临时集合 
    381.      *  
    382.      * @param map 
    383.      */  
    384.     public void saveContactsWebID(HashMap<String, String> map) {  
    385.         Iterator<Entry<String, String>> iter = map.entrySet().iterator();  
    386.         while (iter.hasNext()) {  
    387.             Entry<String, String> es = iter.next();  
    388.             String contacts = es.getKey();  
    389.             String maxWebID = es.getValue();  
    390.             KApplication.webIds.put(contacts, Integer.parseInt(maxWebID));  
    391.         }  
    392.     }  
    393.   
    394.     /*** 
    395.      * 将离线消息入库 
    396.      *  
    397.      * @param map 
    398.      */  
    399.     public void saveOffLineMsg(HashMap<String, ArrayList<MessageEntity>> map) {  
    400.         Iterator<Entry<String, ArrayList<MessageEntity>>> iter = map.entrySet()  
    401.                 .iterator();  
    402.         while (iter.hasNext()) {  
    403.             ArrayList<MessageEntity> msgs = iter.next().getValue();  
    404.             for (int i = 0; i < msgs.size(); i++) {  
    405.                 threadSleep(100);  
    406.                 MessageDB.getInstance(mContext).insert(msgs.get(i),  
    407.                         SocketConstant.MSG_TYPE_BUYER);  
    408.                 Log.i("write""离线数据入库---" + msgs.get(i).toString());  
    409.             }  
    410.             /*** 
    411.              * 如果服务端一次释放的离线消息大于等于10条,则继续请求释放离线消息. 
    412.              */  
    413.             if (msgs.size() >= 10) {  
    414.                 send(SocketJsonUtil.getOffLine(  
    415.                         UserInfoUtil.getUser(mContext)[0], "2"));  
    416.                 Log.i("send",  
    417.                         SocketJsonUtil.getOffLine(  
    418.                                 UserInfoUtil.getUser(mContext)[0], "2"));  
    419.             }  
    420.         }  
    421.         // 一轮消息入库结束,发送通知,更新UI;  
    422.         mContext.sendBroadcast(new Intent(  
    423.                 SocketConstant.OFFLINE_MSG_RECEIVER_SUCCESS));  
    424.     }  
    425.   
    426.     private void threadSleep(long time) {  
    427.         try {  
    428.             Thread.currentThread();  
    429.             Thread.sleep(time);  
    430.         } catch (InterruptedException e) {  
    431.             e.printStackTrace();  
    432.         }  
    433.     }  
    434. }  
    至于数据库的一部分代码就不贴出了,无非是增删改查。


    下面贴出部分监控链接状态的代码,以保证能即时的收到消息;

    [java]  view plain  copy
    1. public class LApplication extends Application {  
    2.   
    3.     public static String TAG = LApplication.class.getSimpleName();  
    4.     /** 接收消息广播 **/  
    5.     private LPullReceiver mPullReceiver;  
    6.     /** 是否是正在登录 **/  
    7.     public static boolean isLoging = false;  
    8.     /** socket管理类 **/  
    9.     private LPushManager mWebSocket;  
    10.       
    11.     @Override  
    12.     public void onCreate() {  
    13.         super.onCreate();  
    14.         /*** 
    15.          * 注册接收消息的广播 
    16.          */  
    17.         mPullReceiver = new LPullReceiver();  
    18.         // 广播过滤  
    19.         IntentFilter filter = new IntentFilter();  
    20.         // 时钟信息发生变化  
    21.         filter.addAction(Intent.ACTION_TIME_TICK);  
    22.         // 开机广播  
    23.         filter.addAction(Intent.ACTION_BOOT_COMPLETED);  
    24.         // 网络状态发生变化  
    25.         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);  
    26.         // 屏幕打开  
    27.         filter.addAction(Intent.ACTION_SCREEN_ON);  
    28.         // 注册广播  
    29.         registerReceiver(mPullReceiver, filter);  
    30.         // 实例化socket管理类  
    31.         mWebSocket = new LPushManager(getApplicationContext());  
    32.         // 应用重启一次,默认socket为关闭状态  
    33.         LPushUser.saveSocket(getApplicationContext(), false);  
    34.         // 默认链接没有被拒绝  
    35.         LPushUser.saveConnect(getApplicationContext(), false);  
    36.         // 1,获取当前时间  
    37.         long currentTime = System.currentTimeMillis();  
    38.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    39.         Date date = new Date(currentTime);  
    40.         String time = sdf.format(date);  
    41.         // 修改标记的时间,保证5分钟内链接一次  
    42.         LPushUser.saveOpenTime(getApplicationContext(), time);  
    43.     }  
    44.   
    45.     /** 
    46.      * 广播接口类 
    47.      * <ol> 
    48.      * <li>接收时钟发生变化的广播 
    49.      * <li>接收网络发生变化的广播 
    50.      * <li>接收开机发送广播 
    51.      * <li>接收用户登录后发送的广播 
    52.      * </ol> 
    53.      *  
    54.      * @author li'mingqi 
    55.      *  
    56.      */  
    57.     class LPullReceiver extends BroadcastReceiver {  
    58.         @SuppressLint("SimpleDateFormat")  
    59.         @Override  
    60.         public void onReceive(Context context, Intent intent) {  
    61.             String action = intent.getAction();  
    62.             if (Intent.ACTION_TIME_TICK.equals(action)  
    63.                     || ConnectivityManager.CONNECTIVITY_ACTION.equals(action)  
    64.                     || Intent.ACTION_BOOT_COMPLETED.equals(action)  
    65.                     || Intent.ACTION_SCREEN_ON.equals(action)) {  
    66.   
    67.                 if (LPushUser.GETLOG(getApplicationContext()))  
    68.                     Log.i("lpush",  
    69.                             "socket的链接状态----"  
    70.                                     + LPushUser  
    71.                                             .getSocket(getApplicationContext())  
    72.                                     + "是否正在链接--" + isLoging + "----" + action);  
    73.                 // 当时钟或者网络发生变化或者socket关闭或者发生异常时或者开机 时,判断socket连接是否出现异常  
    74.                 if (!LPushUser.getSocket(getApplicationContext()) && !isLoging  
    75.                         && LPushUser.getSocketEnable(getApplicationContext())  
    76.                         && !"".equals(IP) && !"".equals(PORT)  
    77.                         && !LPushUser.getConnectState(getApplicationContext())) {  
    78.                     // 掉线,执行登录动作  
    79.                       
    80.                       
    81.                     if (LNetworkUtil.netIsEnable(getApplicationContext())) {  
    82.                         mWebSocket.secondMethod(IP, PORT);  
    83.                         // 开始登录了,标记打开时间  
    84.                         // 1,获取当前时间  
    85.                         long currentTime = System.currentTimeMillis();  
    86.                         SimpleDateFormat sdf = new SimpleDateFormat(  
    87.                                 "yyyy-MM-dd HH:mm:ss");  
    88.                         Date date = new Date(currentTime);  
    89.                         String time = sdf.format(date);  
    90.                         // 修改标记的时间,保证5分钟嗅探链接一次  
    91.                         LPushUser.saveOpenTime(getApplicationContext(), time);  
    92.                     }  
    93.                 } else {  
    94.                     // APP端已经处于链接正常状态 -----5分钟嗅探链接一次  
    95.                     // 1,获取当前时间  
    96.                     long currentTime = System.currentTimeMillis();  
    97.                     SimpleDateFormat sdf = new SimpleDateFormat(  
    98.                             "yyyy-MM-dd HH:mm:ss");  
    99.                     Date date = new Date(currentTime);  
    100.                     String time = sdf.format(date);  
    101.                     // 2,比对链接打开时间  
    102.                     long minTime = LStringManager.dateDifference(  
    103.                             LPushUser.getOpenTime(getApplicationContext()),  
    104.                             time);  
    105.                     if (LPushUser.GETLOG(getApplicationContext())) {  
    106.                         Log.i("lpush",  
    107.                                 "链接时长----现在时间:"  
    108.                                         + time  
    109.                                         + ";保存时间:"  
    110.                                         + LPushUser  
    111.                                                 .getOpenTime(getApplicationContext())  
    112.                                         + ";时差" + minTime + "分钟");  
    113.                     }  
    114.                     if (minTime >= 5) {  
    115.                         // 大于等于5分钟,则重新链接  
    116.                           
    117.                         // 5分钟之后重新链接  
    118.                         // 修改被拒绝状态  
    119.                         if (LPushUser.getConnectState(getApplicationContext())) {  
    120.                             LPushUser.saveConnect(getApplicationContext(),  
    121.                                     false);  
    122.                         }  
    123.                         if (LNetworkUtil.netIsEnable(getApplicationContext())  
    124.                                 && LPushUser  
    125.                                         .getSocketEnable(getApplicationContext())) {  
    126.                             mWebSocket.secondMethod(IP, PORT);  
    127.                             // 修改标记的时间,保证5分钟嗅探链接一次  
    128.                             LPushUser.saveOpenTime(getApplicationContext(),  
    129.                                     time);  
    130.                         }  
    131.                     }  
    132.                 }  
    133.             }  
    134.         }  
    135.     }  
    136.   
    137.     /*** 
    138.      * 设置推送功能的使用与否,默认使用推送功能,若是关闭推送功能请设置false; 
    139.      *  
    140.      * @param enable 
    141.      *            是否使用 
    142.      *  
    143.      *            li'mingqi  
    144.      */  
    145.     protected void setLPushEnable(boolean enable) {  
    146.         LPushUser.saveSocketEnable(getApplicationContext(), enable);  
    147.     }  
    148.   
    149.     /*** 
    150.      *  
    151.      *  
    152.      * @param ip 
    153.      *            ip信息 
    154.      * @param port 
    155.      *            端口信息 
    156.      *  
    157.      *            li'mingqi  
    158.      */  
    159.     protected void setSocketIPInfo(String ip, String port) {  
    160.         this.IP = ip;  
    161.         this.PORT = port;  
    162.     }  
    163.    
    164.   
    165.     /** 
    166.      * 设置用户的Uid 
    167.      *  
    168.      * @param uid 
    169.      *            li'mingqi  
    170.      */  
    171.     public void setUserInfo(int uid, String code) {  
    172.         /*** 
    173.          * 数据验证 
    174.          */  
    175.         // if (0 == uid || null == code || "".equals(code)) {  
    176.         // Log.e(TAG, "您输入的用户ID或者CODE值为空");  
    177.         // new NullPointerException("您输入的用户ID或者CODE值为空").printStackTrace();  
    178.         // return;  
    179.         // }  
    180.   
    181.         // 保存用户ID  
    182.         LPushUser.saveUserID(getApplicationContext(), uid);  
    183.         // 保存用户CODE  
    184.         LPushUser.saveUserCode(getApplicationContext(), code);  
    185.         // 重启链接  
    186.         mWebSocket.close();  
    187.     }  
    188.   
    189.     /*** 
    190.      * 设置是否查看日志 
    191.      *  
    192.      * @param flag 
    193.      *            是否查看日志 
    194.      * @version 1.2 li'mingqi 
    195.      */  
    196.     public void openLogInfo(boolean flag) {  
    197.         LPushUser.SAVELOG(getApplicationContext(), flag);  
    198.     }  
    199.   
    200.     /*** 
    201.      * socket链接重置,服务器一直处于拒绝链接状态,客户端链接一次遭拒后标记了遭拒的状态,重置之后可进行再次开启链接; 
    202.      *  
    203.      * @version 1.3 li'mingqi  
    204.      */  
    205.     private void reset() {  
    206.         LPushUser.saveConnect(getApplicationContext(), false);  
    207.     }  
    208. }  
    UI界面显示部分就不贴出了,后面贴出Application类的目的就是监控各种手机系统的广播来嗅探自己的websocket链接的状态;用来维持它以保证能即时的收到消息;




    展开全文
  • 用Python和Websocket实现实时通讯

    千次阅读 2017-02-13 11:45:44
    说到websocket大家一定不会陌生,WebSocket是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-...有了websocket, 大家就可以摒弃以往用轮询来实现实时通讯的方式了。 有了websocket后,应运而生的相...

    说到websocket大家一定不会陌生,WebSocketHTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成,当浏览器和服务器握手成功后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。有了websocket, 大家就可以摒弃以往用轮询来实现实时通讯的方式了。
    有了websocket后,应运而生的相关产品也不在少数,选择也成了最大的问题,在这里你可能会说“干嘛用别人的,我可以自己用原始的开发一个啊”对此我只想说你真NB,我也很想知道你是如何解决以下问题的:
    1.        你是打算花1个月,2个月还是1年还实现一个websocket通信的?如果你说你打算花一个月,那说明两点,你的技术要上天了(这么刁的东西你可以那么快弄出来还不存在性能问题,除了我现有的膝盖,请把我下辈子的膝盖也拿走),贵公司土豪(贵公司对技术的炙热追求已经达到了行业的巅峰了,看来你们的项目不是很紧嘛,预算挺多嘛,告诉我贵公司的名字我也来钻研技术,哈哈哈)
    2.        Websocket只支持ie10+, chrome, firefox, safari, opera. 不说多了,当前IE9及低版本的用户群体还有很多,你是怎么觉得这些低版本浏览器的实时通讯的?
    上面两个问题无非就是涉及到两方面问题:开发成本和浏览器兼容性问题。站在管理者角度来看,都希望尽可能多的压缩开发成本,所有选择第三方推送服务是众多企业的一个理性选择。
    选择产品时需要注意以下几点:
    1.        代码结构是否清晰易懂
    2.        消息到达率怎么样,数据到达情况是否可视
    3.        浏览器兼容性如何
    4.        产品的稳定性和安全性
    好,重点来了,我不去评判其他产品的优缺点,只给出我对我最终选择的产品-GoEasy推送作一个单方面的评价。GoEasy推送满足我上面列的所有指标。
    1.        代码结构是否清晰易懂
    GoEasy的代码分为订阅和推送两部分:
    订阅时,只需要三句代码:
    a.        引入goeasy.js (文件非常小)
    <script type="text/javascript" src="http://cdn.goeasy.io/goeasy.js"></script>
    b.        创建goeasy实例

    var goEasy = new GoEasy({appkey: 'appkey'});

    c.        订阅channel.

                         goEasy. subscribe({

                     channel: 'channel1',

                     onMessage: function(message){

                                  alert('Meessage received:'+message.content);//接收到推送的消息

                               }

                        });

    推送时,用goeasy提供的restful api来实现,api只需要三个参数即可:

    URL: http://goeasy.io/goeasy/publish

    Method: Post

    Parameters: appkey, channel, content

    2.        消息到达率怎么样,数据到达情况是否可视
    我们项目的用户并发量目前最高在300人的样子,每天会推送30条消息的样子,每条消息的到达情况都可以在goeasy后台页面进行查看。至于到达率,我们项目的对到达率的要求是98%就目前来看GoEasy应该是100%的到达率。
    3.        浏览器兼容性如何
    除了常用的浏览器chrome, firefox, safari, opera外还支持IE 6IE11的版本,低版本IE浏览器GoEasy采用的是polling的方式。GoEasy在兼容性这方面做的很不错。
    4.        产品的稳定性和安全性
    稳定性的判定:项目已经持续运行了4个月了,没有出现过消息推不出或接收不到的情况。
    安全性的判定:GoEasy在安全控制方面主要是通过appkey来控制,创建好app后系统会生成两个key,一个既可以用来接收又可以用来推送,另一个只可以用来接收。所以用户可以选择性的暴露你的key.
    这样轻松用Python实现客户端与服务器端的实时通信了。
     Pythonwebsocket实时消息推送
    展开全文
  • 有时间验证一下。 C# 如何实现网络通信,方法有很多就不一一介绍了。...Fleck是一个用C#实现WebSocket服务器。 从Nugget项目分支,Fleck不需要继承、容器或其他引用。 需要注意的是:Fleck不依赖H...
  • springboot+websocket实现聊天即时通讯 1.在很多业务场景中,对实时数据要求比较高,我们就不能采用轮训拉取的方式来获取数据了。就可以采用websocket的长链接的形式,实时有服务端或者客户端推送数据,已达到数据的...
  • springBoot结合WebSocket实现通讯小Demo 图片: 一、服务端 1.1 新建一个工程 取名为:spring-boot-websocket 1.2 引入依赖 在pom.xml文件中添加如下依赖: org.springframework.boot spring-boot-starter-...
  • 使用WebSocket实现Android端即时通讯聊天功能

    万次阅读 多人点赞 2018-05-02 09:31:12
    本篇文章主要介绍自己使用WebSocket实现Android端即时通讯聊天功能的过程,最终我们使用WebSocket实现了两个客户端之间的即时通讯聊天功能和直播中的聊天室功能,当然整个WebSocket还是比较复杂的,特别是长链接的...
  • 今天没有延续上一篇讲的内容,穿插一段小插曲,WebSocket 实时数据通讯同步的问题,今天我们并不是很纯粹地讲 WebSocket 相关知识,我们通过 WebGL 3D 拓扑图来呈现一个有趣的 Demo。接下来我们就看看这个实时数据...
  • 我们上一篇《基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)》主要讲解了如何搭建一个实时数据通讯服务器,客户端与服务端是如何通讯的,相信通过上一篇的讲解,再配合上数据库的数据储存,我们就可以...
  • 我们上一篇《基于 WebSocket 实现 WebGL 3D拓扑图实时数据通讯同步(一)》主要讲解了如何搭建一个实时数据通讯服务器,客户端与服务端是如何通讯的,相信通过上一篇的讲解,再配合上数据库的数据储存,我们就可以...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,345
精华内容 2,138
关键字:

websocket实现同步通讯