1. 程式人生 > >Android推送的核心原理:長連線的簡單實現

Android推送的核心原理:長連線的簡單實現

實際需求

移動端需要實時的獲取伺服器的資料

解決方案

  1. 輪詢方式:應用程式開啟定時的輪詢,不停的向伺服器請求資料。
  2. SMS push:傳送二進位制簡訊到移動終端,來達到通知終端的目的。客戶端攔截這類簡訊,然後採取相應的操作
  3. 持久連線方式:應用程式與伺服器建立一個長連線,使伺服器可以隨時的與手機端通訊

方案分析

  • 方法一:輪詢頻率過高,則太過消耗效能,輪詢頻率低,則資料顯示不及時,不可取
  • 方式二: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