维护任何一个长链接都须要心跳机制,客户端发送一个心跳给服务器,服务器给客户端一个心跳应答,
这样双方都知道他们之间的链接是没有断开。【客户端先发送给服务端】html
若是超过一个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的心跳,
那么对客户端来讲则断开与服务器的链接从新创建一个链接,对服务器来讲只要断开这个链接便可。java
当一台智能手机连上移动网络时,其实并无真正链接上Internet,运营商分配给手机的IP实际上是运营商的内网IP,手机终端要链接上Internet还必须经过运营商的网关进行IP地址的转换,这个网关简称为NAT(NetWork Address Translation),简单来讲就是手机终端链接Internet 其实就是移动内网IP,端口,外网IP之间相互映射。至关于在手机终端在移动无线网络这堵墙上打个洞与外面的Internet相连。android
GGSN(GateWay GPRS Support Note 网关GPRS支持节点)模块就实现了NAT功能,因为大部分的移动无线网络运营商为了减小网关NAT映射表的负荷,若是一个链路有一段时间没有通讯时就会删除其对应表,形成链路中断,正是这种刻意缩短空闲链接的释放超时,本来是想节省信道资源的做用,没想到让互联网的应用不得以远高于正常频率发送心跳来维护推送的长链接。ios
手机应用发送心跳的频率很短,既形成了信道资源的浪费,也形成了手机电量的快速消耗。git
没有长链接,服务端就没法主动向客户端推送.编程
iOS长链接是由系统来维护的,也就是说苹果的iOS系统在系统级别维护了一个客户端和苹果服务器的长连接,iOS上的全部应用上的推送都是先将消息推送到苹果的服务器而后将苹果服务器经过这个系统级别的长连接推送到手机终端上,这样的的几个好处为:
* (1).在手机终端始终只要维护一个长链接便可,并且因为这个长连接是系统级别的不会出现被杀死而没法推送的状况。
* (2).省电,不会出现每一个应用都各自维护一个本身的长链接。
* (3).安全,只有在苹果注册的开发者才可以进行推送,等等。
不会被杀死,省电,安全.segmentfault
因为Google的推送框架C2DM在中国境内不能使用,android的长链接是由每一个应用各自维护一个长链接,若是都24小时在线,这种电量和流量的消耗是可想而知的。安全
1)轮询(Pull)方式:应用程序应当阶段性的与服务器进行链接并查询是否有新的消息到达,你必须本身实现与服务器之间的通讯,例如消息排队等。并且你还要考虑轮询的频率,若是太慢可能致使某些消息的延迟,若是太快,则会大量消耗网络带宽和电池。服务器
2)SMS(Push)方式:在Android平台上,你能够经过拦截SMS消息而且解析消息内容来了解服务器的意图,并获取其显示内容进行处理。这是一个不错的想法,我就见过采用这个方案的应用程序。这个方案的好处是,能够实现彻底的实时操做。可是问题是这个方案的成本相对比较高,咱们须要向移动公司缴纳相应的费用。咱们目前很难找到免费的短消息发送网关来实现这种方案。微信
3)持久链接(Push)方式:这个方案能够解决由轮询带来的性能问题,可是仍是会消耗手机的电池。iOS平台的推送服务之因此工做的很好,是由于每一台手机仅仅保持一个与服务器之间的链接,事实上C2DM也是这么工做的。Android操做系统容许在低内存状况下杀死系统服务,因此咱们的推送通知服务颇有可能就被操做系统Kill掉了。咱们很难在手机上实现一个可靠的服务,目前也没法与iOS平台的推送功能相比。
Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。
XMPP(Extensible Messageing and Presence Protocol:可扩展消息与存在协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。
基本网络结构:
XMPP中定义了三个角色,客户端,服务器,网关。通讯可以在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,链接管理和信息的路由功能。网关承担着与异构即时通讯系统的互联互通,异构系统能够包括SMS(短信),MSN,ICQ等。基本的网络形式是单客户端经过TCP/IP链接到单服务器,而后在之上传输XML。
XMPP经过TCP传输的是与即时通信相关的指令。XMPP的核心部分就是一个在网络上分片段发送XML的流协议。这个流协议是XMPP的即时通信指令的传递基础,也是一个很是重要的能够被进一步利用的网络基础协议。因此能够说,XMPP用TCP传的是XML流。
C: <stream:stream> C: <presence/> C: <iq type="get"> <query xmlns="jabber:iq:roster"/> </iq> S: <iq type="result"> <query xmlns="jabber:iq:roster"> <item jid="suke@skh.whu.edu.cn"xs/> <item jid="gmz@skh.whu.edu.cn"/> <item jid="beta@skh.whu.edu.cn"/> </query> </iq> C: <message from="suke@skh.whu.edu.cn" to="beta@skh.whu.edu.cn"> <body>Off with his head!</body> </message> S: <message from="lj@skh.whu.edu.cn" to="cyl@skh.whu.edu.cn "> <body>You are all pardoned.</body> </message> C: <presence type="unavailable"/> C: </stream:stream>
MQTT 协议主要解决的是机器与机器之间数据通讯,其适用于以下但不限于这几点:
MQTT 最引觉得豪的就是最小的2 byte 头部传输开销.咱们看下其余流行的协议的message format的设计:
XMPP 消息体xml:
|--------------------| | <stream> | |--------------------| | <presence> | | <show/> | | </presence> | |--------------------| | <message to='foo'> | | <body/> | | </message> | |--------------------| | <iq to='bar'> | | <query/> | | </iq> | |--------------------| | ... | |--------------------| | </stream> | |--------------------|
HTTP
HTTP-message = Request | Response ; HTTP/1.1 messages
还有不少协议,就不同细说了,就举两个我比较了解的.就目前通用的协议来看不多有比MQTT 还要低的传输开销了.
*第一个byte 用于说明消息体的信息(Message Type 4bit|DUP flag |QoS level 2bit|RETAIN).
第二个byte 用于传输咱们须要传输的数据(Remaining Length, 8bit).*
使用XMPP协议(Openfire + Spark + Smack)
简介:基于XML协议的通信协议,前身是Jabber,目前已由IETF国际标准化组织完成了标准化工做。
优势:协议成熟、强大、可扩展性强、目前主要应用于许多聊天系统中,且已有开源的Java版的开发实例androidpn。
缺点:协议较复杂、冗余(基于XML)、费流量、费电,部署硬件成本高。
使用MQTT协议
简介:轻量级的、基于代理的“发布/订阅”模式的消息传输协议。
优势:协议简洁、小巧、可扩展性强、省流量、省电,目前已经应用到企业领域,且已有C++版的服务端组件rsmb。
缺点:不够成熟、实现较复杂、服务端组件rsmb不开源,部署硬件成本较高。
MQTT相比XMPP 有几个优点:
二进制,很是精简,适合作大量节点弱网络差的场景,很是适合如今移动互联网的基础设施;MQTT是自然的订阅发布系统,有权限的人均可以往里头发消息;开源的协议和实现;扩展方便且轻量级。
XMPP不适合移动网络有几个缘由:
协议虽然完整扩展性虽然好,它耗费网络流量很大,交互次数太多,跑起来比MQTT慢不少;另外有高达70%的流量是耗费在XMPP自己的标签和编解码上面。
MQTT是一个由 IBM 开发的传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的设备提供可靠的网络服务。相比于XMPP等传统协议,MQTT 是专门针对移动互联网开发的轻量级传输协议,这种传输协议链接稳定、心跳数据包小,因此具有耗电量低、耗流量低的优点。推送服务的最佳协议!(纯属粘贴,未经验证...)
基于TCP的socket编程 有三种(2011年),
基于TCP的socket编程是采用的流式套接字。
服务器端编程的步骤:
1:加载套接字库,建立套接字(WSAStartup()/socket());
2:绑定套接字到一个IP地址和一个端口上(bind());
3:将套接字设置为监听模式等待链接请求(listen());
4:请求到来后,接受链接请求,返回一个新的对应于这次链接的套接字(accept());
5:用返回的套接字和客户端进行通讯(send()/recv());
6:返回,等待另外一链接请求;
7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
客户端编程的步骤:
1:加载套接字库,建立套接字(WSAStartup()/socket());
2:向服务器发出链接请求(connect());
3:和服务器端进行通讯(send()/recv());
4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
心跳是逻辑应用层的东西,须要本身实现,当socket空闲时,发送心跳包,报文件格式自定义.
心跳检测须要如下步骤:
1 客户端每隔一个时间间隔发生一个探测包给服务器
2 客户端发包时启动一个超时定时器
3 服务器端接收到检测包,应该回应一个包
4 若是客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5 若是客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
此处存一个别人家的demo
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.net.Socket; import java.net.UnknownHostException; import java.util.Arrays; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; public class BackService extends Service { private static final String TAG = "BackService"; private static final long HEART_BEAT_RATE = 3 * 1000; public static final String HOST = "192.168.1.101";// "192.168.1.21";// public static final int PORT = 9800; public static final String MESSAGE_ACTION="org.feng.message_ACTION"; public static final String HEART_BEAT_ACTION="org.feng.heart_beat_ACTION"; private ReadThread mReadThread; private LocalBroadcastManager mLocalBroadcastManager; private WeakReference<Socket> mSocket; // For heart Beat private Handler mHandler = new Handler(); private Runnable heartBeatRunnable = new Runnable() { @Override public void run() { if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) { boolean isSuccess = sendMsg("");//就发送一个\r\n过去 若是发送失败,就从新初始化一个socket if (!isSuccess) { mHandler.removeCallbacks(heartBeatRunnable); mReadThread.release(); releaseLastSocket(mSocket); new InitSocketThread().start(); } } mHandler.postDelayed(this, HEART_BEAT_RATE); } }; private long sendTime = 0L; private IBackService.Stub iBackService = new IBackService.Stub() { @Override public boolean sendMessage(String message) throws RemoteException { return sendMsg(message); } }; @Override public IBinder onBind(Intent arg0) { return iBackService; } @Override public void onCreate() { super.onCreate(); new InitSocketThread().start(); mLocalBroadcastManager=LocalBroadcastManager.getInstance(this); } public boolean sendMsg(String msg) { if (null == mSocket || null == mSocket.get()) { return false; } Socket soc = mSocket.get(); try { if (!soc.isClosed() && !soc.isOutputShutdown()) { OutputStream os = soc.getOutputStream(); String message = msg + "\r\n"; os.write(message.getBytes()); os.flush(); sendTime = System.currentTimeMillis();//每次发送成数据,就改一下最后成功发送的时间,节省心跳间隔时间 } else { return false; } } catch (IOException e) { e.printStackTrace(); return false; } return true; } private void initSocket() {//初始化Socket try { Socket so = new Socket(HOST, PORT); mSocket = new WeakReference<Socket>(so); mReadThread = new ReadThread(so); mReadThread.start(); mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//初始化成功后,就准备发送心跳包 } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void releaseLastSocket(WeakReference<Socket> mSocket) { try { if (null != mSocket) { Socket sk = mSocket.get(); if (!sk.isClosed()) { sk.close(); } sk = null; mSocket = null; } } catch (IOException e) { e.printStackTrace(); } } class InitSocketThread extends Thread { @Override public void run() { super.run(); initSocket(); } } // Thread to read content from Socket class ReadThread extends Thread { private WeakReference<Socket> mWeakSocket; private boolean isStart = true; public ReadThread(Socket socket) { mWeakSocket = new WeakReference<Socket>(socket); } public void release() { isStart = false; releaseLastSocket(mWeakSocket); } @Override public void run() { super.run(); Socket socket = mWeakSocket.get(); if (null != socket) { try { InputStream is = socket.getInputStream(); byte[] buffer = new byte[1024 * 4]; int length = 0; while (!socket.isClosed() && !socket.isInputShutdown() && isStart && ((length = is.read(buffer)) != -1)) { if (length > 0) { String message = new String(Arrays.copyOf(buffer, length)).trim(); Log.e(TAG, message); //收到服务器过来的消息,就经过Broadcast发送出去 if(message.equals("ok")){//处理心跳回复 Intent intent=new Intent(HEART_BEAT_ACTION); mLocalBroadcastManager.sendBroadcast(intent); }else{ //其余消息回复 Intent intent=new Intent(MESSAGE_ACTION); intent.putExtra("message", message); mLocalBroadcastManager.sendBroadcast(intent); } } } } catch (IOException e) { e.printStackTrace(); } } } } }
在Activity中发送以及接收数据:
import java.lang.ref.WeakReference; import org.feng.sockettest.server.BackService; import org.feng.sockettest.server.IBackService; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v4.content.LocalBroadcastManager; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private IBackService iBackService; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { iBackService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { iBackService = IBackService.Stub.asInterface(service); } }; private TextView mResultText; private EditText mEditText; private Intent mServiceIntent; class MessageBackReciver extends BroadcastReceiver { private WeakReference<TextView> textView; public MessageBackReciver(TextView tv) { textView = new WeakReference<TextView>(tv); } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); TextView tv = textView.get(); if (action.equals(BackService.HEART_BEAT_ACTION)) { if (null != tv) { tv.setText("Get a heart heat"); } } else { String message = intent.getStringExtra("message"); tv.setText(message); } }; } private MessageBackReciver mReciver; private IntentFilter mIntentFilter; private LocalBroadcastManager mLocalBroadcastManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLocalBroadcastManager = LocalBroadcastManager.getInstance(this); mResultText = (TextView) findViewById(R.id.resule_text); mEditText = (EditText) findViewById(R.id.content_edit); mReciver = new MessageBackReciver(mResultText); mServiceIntent = new Intent(this, BackService.class); mIntentFilter = new IntentFilter(); mIntentFilter.addAction(BackService.HEART_BEAT_ACTION); mIntentFilter.addAction(BackService.MESSAGE_ACTION); } @Override protected void onStart() { super.onStart(); mLocalBroadcastManager.registerReceiver(mReciver, mIntentFilter); bindService(mServiceIntent, conn, BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); unbindService(conn); mLocalBroadcastManager.unregisterReceiver(mReciver); } public void onClick(View view) { switch (view.getId()) { case R.id.send: String content = mEditText.getText().toString(); try { boolean isSend = iBackService.sendMessage(content);//Send Content by socket Toast.makeText(this, isSend ? "success" : "fail", Toast.LENGTH_SHORT).show(); mEditText.setText(""); } catch (RemoteException e) { e.printStackTrace(); } break; default: break; } } }
完整项目带服务器段代码的见: http://git.oschina.net/fengcunhan/SocketTest.git
摘录自连接:
android长链接心跳机制, 2015.10.30
Android实现推送方式解决方案, 2012.3.4
Android 网络(五) 推送, 2016.9.14
XMPP协议实现原理介绍, 2012.3.4
xmpp协议详解一:xmpp基本概念, 2015.7.30
MQTT 折腾笔记—-协议简读, 2013.4.25
MQTT协议(一):理论篇,2016.3.8
基于TCP的socket编程网络掉线重连,2011
在Android上面如何使用带有心跳检测的Socket, 2013
再补充几篇文章:
Android推送技术研究, 2016-3-6
[包含代码实现]Android产品研发(十二)–>App长链接实现, 2016-6-17
[包含socket实例]基于Java Socket的自定义协议,实现Android与服务器的长链接(一),2016-12-2
Android进程保活详解:一篇文章解决你的全部疑问
移动端IM实践:实现Android版微信的智能心跳机制
移动端IM实践:WhatsApp、Line、微信的心跳策略分析