維護任何一個長鏈接都須要心跳機制,客戶端發送一個心跳給服務器,服務器給客戶端一個心跳應答,
這樣雙方都知道他們之間的鏈接是沒有斷開。【客戶端先發送給服務端】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、微信的心跳策略分析