Android推送的核心原理:長連線的簡單實現
實際需求
移動端需要實時的獲取伺服器的資料
解決方案
- 輪詢方式:應用程式開啟定時的輪詢,不停的向伺服器請求資料。
- SMS push:傳送二進位制簡訊到移動終端,來達到通知終端的目的。客戶端攔截這類簡訊,然後採取相應的操作
- 持久連線方式:應用程式與伺服器建立一個長連線,使伺服器可以隨時的與手機端通訊
方案分析
- 方法一:輪詢頻率過高,則太過消耗效能,輪詢頻率低,則資料顯示不及時,不可取
- 方式二:android簡訊push的關鍵在於攔截簡訊,一旦攔截到了簡訊,可以利用android自帶的特性去完成後續操作,比如可以Push notification,可以Toast,可以發起連線伺服器的請求,客戶端應用不線上時,可以發一個Intent啟動該應用然後再進行處理。單由於是簡訊的方式,就勢必要涉及通訊收費的問題,當需要推送的訊息多的時候,就會產生很大的額外開銷
- 方式三:這種方式可以比較好的解決上面兩種方式的弊端,也是下文中要分析的方式
Socket
持久化連線就是所謂的長連線,現在基本上不會使用原生的Socket來實現長連線,都是使用第三方的推送服務,如極光推送、小米推送等,這裡只是使用Socket來簡要說明長連線的原理
概念
Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個外觀模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。
互動如下:
伺服器端先初始化Socket,然後與埠繫結(bind),對埠進行監聽(listen),呼叫accept阻塞,等待客戶端連線。在這時如果有個客戶端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時客戶端與伺服器端的連線就建立了。客戶端傳送資料請求,伺服器端接收請求並處理請求,然後把迴應資料傳送給客戶端,客戶端讀取資料,最後關閉連線,一次互動結束
長連線和短連線
整個通訊過程,客戶端和服務端只用一個Socket物件,長期保持Socket的連線,這種稱之為“長連線”;每一次請求都新建一個Socket,處理完成之後直接關閉Socket,這種稱之為“短連線”。所以,其實區分長短連線就是:整個客戶和服務端的通訊過程是利用一個Socket還是多個Socket進行的
我們在eclipse中新建兩個Java工程SocketClient、SocketServer來來模擬客戶端和伺服器端
客戶端:SocketClient
功能:既可以主動的向伺服器端傳送資訊,也可以被動的接收伺服器發來的訊息
public class SocketClient {
public static void main(String[] args) {
SocketClient client = new SocketClient();
client.start();
}
private void start() {
BufferedReader inputReader = null;
BufferedReader reader = null;
BufferedWriter writer = null;
Socket socket = null;
try {
// 連線目標
socket = new Socket("192.168.1.101", 9898);
// 讀取socket的內容
reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
// 寫入socket的內容
writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream()));
// 獲取控制檯輸入
inputReader = new BufferedReader(new InputStreamReader(System.in));
// 對伺服器的監聽,收到來自伺服器的內容
startServerReplyListener(reader);
// 控制檯輸入
String inputContent;
while (!(inputContent = inputReader.readLine()).equals("bye")) {
//向伺服器主動傳送訊息
writer.write(inputContent+"\n");
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//注意這裡的關閉socket是為了客戶端出現異常的時候關閉,正常是執行try塊的while()迴圈
reader.close();
writer.close();
inputReader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void startServerReplyListener(final BufferedReader reader) {
new Thread(new Runnable() {
@Override
public void run() {
try {
String response;
while ((response = reader.readLine()) != null) {
System.out.println(response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
伺服器:SocketServer
功能:可主動的接收客戶端的資訊,主動的推送訊息給客戶端
package com.zsk.server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) {
SocketServer socketServer = new SocketServer();
socketServer.startServer();
}
private void startServer(){
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(9898);
System.out.println("服務已開啟...");
while(true){
//當沒有Socket連線時,阻塞
socket = serverSocket.accept();
//管理連線
manageConnection(socket);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void manageConnection(final Socket socket){
new Thread(new Runnable() {
@Override
public void run() {
BufferedReader reader = null;
BufferedWriter writer = null;
try {
System.out.println("client:"+socket.hashCode()+"已連線");
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String receivedMsg;
/**
這一段可以模擬伺服器端主動推送訊息,只不過太過粗糙
new Timer().schedule(new TimerTask() {
@Override
public void run() {
try {
writer.write("server reply: " + "伺服器推送!!!" + "\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}, 3000,3000);
*/
//將接收到的資料,重新返回給客戶端
while((receivedMsg = reader.readLine()) != null) {
System.out.println("client"+socket.hashCode()+receivedMsg);
writer.write("server reply: " + receivedMsg + "\n");
writer.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
reader.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
在伺服器SocketServer中可以看到,每一次的連線都是新開一個執行緒,這就是很大的一個弊端,當有很多的連線的時候,比方說數十萬的連線時,這種高併發的情況對於這種處理方法是不可取的,同時這種方式是阻塞式的,需要開執行緒去進行維護,考慮到這種情況,Java1.4之後新加了nio可以提供多路非阻塞式的高伸縮性網路,但是這種方式使用十分複雜,所以多是採用基於nio的第三方框架如Mina、Nety