移動跨平臺開發{二}

websocket跨平臺移動消息推送

       消息推送對於 APP 至關重要,一個及時有效的消息推送能夠幫助客戶獲取有價值的信息,所以消息推送是移動應用的一項重要功能。目前主流的移動操作系統(Android、iOS)的 webview 都已經支持 websocket, 所以對於移動應用,webscoket 也將成爲消息推送選擇。

移動應用消息推送

   應用一般使用 Cordova 之類的中間件,以 WebView 作爲用戶界面層,以 Javascript 作爲基本邏輯,以及和中間件通訊,再由中間件訪問底層 API 的方式,進行應用開發。開發時可能不採用或者大部分不採用原生語言,但是卻有所有原生應用的特性。而開發消息推送功能時,既可以使用 Native App 的系統自帶推送如 GCM 和 APNS,又可以使用基於 html5 的 websocket 推送。

對於混合應用的 websocket 消息推送,其基本原理如下:

wKiom1glQ3SQSAWrAAArNZH-Pw0275.png

圖 2. 原生 app 消息推送工作流程

wKiom1glQ_2CQ4DjAACOFQ147eI331.png

       後者主要應用於原生 app, 而前者由於開發週期短,跨平臺性好,維護成本低,用於混合應用的消息推送。基於 websocket 的消息推送,實現消息推送的服務端,這樣掌握了推送服務的主動權,對於安全性極高的企業,websocket 推送無疑是最好的選擇,因爲如果使用 GCM 或者 APNS 推送,不得不將信息發送到 GCM server 或者 APNS server, 再由 GCM 或者 APNS 服務端轉發到客戶端,信息安全性不得而知。一旦推送服務器出現異常,消息推送將變得非常被動。 但是 GCM 和 APNS 也是使用長連接進行消息推送,而且一個手機上的所有 app 共用一個長連接,對於手機性能將會有極大的幫助。兩種推送各有利弊,讀者可自己選擇。本文將針對 websocket 的消息推送進行介紹


Websocket 接口簡介

       WebSocket 的實現分爲客戶端和服務端兩部分,客戶端(通常爲瀏覽器)發出 WebSocket 連接請求,服務端響應,實現類似 TCP 握手的動作,從而在瀏覽器客戶端和 WebSocket 服務端之間形成一條 HTTP 長連接快速通道。兩者之間後續進行直接的數據互相傳送,不再需要發起連接和響應。同時兩者都可以關閉這個長連接。是利用了混合移動應用的 webview 可以支持 websocket 的這個特性來實現服務器端對客戶端的一個消息推送。Websocket 針對客戶端而言,性能,資源使用以及及時性要比傳統的輪詢更好。

Websocket 客戶端 API

對於 websocket 客戶端,目前主流的移動操作系統的 webview 層都已經支持 websocket 服務。以下列舉了常見的移動系統支持情況:

表 1. 主流移動系統的 webview 對 websocket 的支持情況

移動系統版本
IOSIOS 5+
AndroidAndroid 4.4
Windows phoneWindows phone 8+

常見瀏覽器和移動系統的 webview 都已經實現了 w3 規範的 websocket 接口,具體接口參考清單 1

1. Websocket 客戶端接口

[Constructor(in DOMString url, in optional DOMString protocol)]
interface WebSocket {
  readonly attribute DOMString URL;
       // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSED = 2;
  readonly attribute unsigned short readyState;
  readonly attribute unsigned long bufferedAmount;
  //networking
  attribute Function onopen;
  attribute Function onmessage;
  attribute Function onclose;
  boolean send(in DOMString data);
  void close();
};
WebSocket implements EventTarget;

       URL 屬性代表 WebSocket 服務器的網絡地址,協議通常”ws”或者”wss“,send 方法就是發送數據到服務器端,close 方法就是關閉連接。除了這些方法,還有一些很重要的事件:onopen,onmessage,onerror 以及 onclose。詳細解釋請參考表 2

表 2. Websocket 的對象方法屬性

名稱類型描述
WebSocket對象提供到遠程主機的雙向通道
close方法關閉 websocket
send方法使用 websocket 發送數據到服務器
binaryType屬性由 onmessage 接收的二進制數據格式。
bufferedAmount屬性使用 send 的已排隊的數據字節數
extensions屬性報告服務器所選中的擴展名。
onclose屬性當套接字關閉時調用的事件處理程序。
onerror屬性當出現錯誤時調用的事件處理程序。
onmessage屬性通知接收到消息的事件處理程序。
onopen屬性當 websocket 已連接時調用的事件處理程序。
protocol屬性報告服務器所選中的協議。
readyState屬性報告 websocket 連接的狀態。
url屬性報告套接字的當前 URL。

下面一段代碼展示了建立 websocket 實例:

2. Websocket 創建連接實例

var ws= new WebSocket("ws://localhost:8080/PushNotification");
ws.onopen = function (event) {
console.log("connected to server");
};
ws.onmessage = function (event) {
//when a new message coming, we will call Cordova plugin here

    當有新消息到達時,onmessage 會自動觸發,在這個方法裏利用 Cordova plugin 去實現調用 android 或者 ios 的 notification。


Websocket 服務器端 API

對於 websocket 服務器端,目前主流的 web 服務器都已經支持。以下列舉了常見的服務器支持情況:

WEBSOCKETS IN NGINX

    1.3.13版本的Nginx支持的連接升級,作爲WebSockets代理!很多人都在等待着這個“WebSocketsd的Nginx中的支持。以下是最常見的進行在Nginx的WebSocket實現。

Nginx中websoket設置

location /chat/ {
   proxy_pass http://backend;
   proxy_http_version 1.1;
   proxy_set_header Upgrade $http_upgrade;
   proxy_set_header Connection "Upgrade";
}

    這是相當簡單的配置,沒有使用所有新的HTTP版本。通過創建一個連接變量,讓proxy_set_headers成通用的包含文件。

map $http_connection $upgrade_requested {
   default upgrade;
   ''      close;
}
,,

      這使得變量$ upgrade_requested可連接proxy_set_header,如果連接升級不要求迴應,默認值不干擾正常的請求。即如果你總是代理HTTP / 1.1那麼你不需要一個專門處理WebSocket的位置。
      連接升級似乎不太可能被移植到穩定的分支,所以如果你想使用這個,你將不得不使用qi分支。值得慶幸的是,在nginx的發展並不意味着它不運行穩定,它只是意味着API可能會改變,因此它隻影響模塊的作者。不要害怕安裝開發版本來玩這個新的功能。

對websocke的限制

客戶端必須完成 Connection Upgrade
不然會導致錯誤或丟失功能
WebSockets 的Time Out
WebSockets的proxy_read_timeout 缺省值爲:60秒.
Keep-Alive 和 WebSockets
Keep-alive pings 不能使用,TCP端爲空包。繼續切換proxy_read_timeout的值,不要害怕。
WebSockets 支持 SSL
由於WebSockets 以進入正常的proxy模式 SSL 應使用相同的方法.
Proxy Buffers代理緩存
WebSockets包有兩個內存Buffer,決定proxy_buffer_size的大小,。一個upstream data數據,還有一個到磁盤.
Upgrade Header的問題例子
一些帶寬做upgrade header檢測同時請求header到另一個“upgrade”或“Upgrade”, 有時看上去正確但是不工作,請改變header值.

其他還有Weblogic12c+、iis7.0+、tomcat7.0.5+、jetty7.0+


利用 Cordova plugin 調用本地 notification

以上了解了如何在 App 中創建 websocket 連接,當我們收到信息時,這個時候只需要調用 IOS 或者 Android 的本地消息推送即可讓用戶知道有新的信息到達。這個時候我們就要創建一個新的 Cordova plugin 去觸發 notification。

創建一個 Cordova plugin

Cordova plugin 是我們通過 JavaScript 調用系統 API 的中間件,一般情況下通過 JavaScript 不能夠完成而系統 API 可以完成的任務時,我們就要創建一個 plugin。不同的平臺在調用底層 API 時會有不同,但是前段代碼不會改變。 以下我們將創建一個 Cordova plugin 用來調用本地消息推送。下文以 android 平臺爲例,其他平臺思路類似。

1. 在 cordova_plugins.js 中引入新的 plugin

{
"file": "plugins/com.test.notification/www/notification.js",
"id": "com.test.notification.localNotification",
"clobbers": [
"cordova.plugins.localNotification" ]
}

2. 然後在 plugins/com.test.notification/www 目錄下邊創建 notification.js,代碼示例如下:

cordova.define("com.test.notification.localNotification",
 function(require, exports, module) {
var argscheck = require('cordova/argscheck'),
   utils = require('cordova/utils'),
   exec = require('cordova/exec');
var localNotification = function() {
};
localNotification.sendNotify = function(message,success, error) {  
  cordova.exec(success, error, 'localNotification', 'sendNotify', message);
};
module.exports = localNotification;
});

3. 創建一個 java 類來繼承 Cordova 接口,代碼示例如下

package com.test.notification;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

//ellipsis code
// …… .

package com.test.notification;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

//ellipsis code
// …… .


public class localNotification extends CordovaPlugin {
//ellipsis code
// … ..
@Override
public boolean execute(String action, final JSONArray args,
final CallbackContext callbackContext) throws JSONException {
if ("sendNotify".equals(action)) {
     NotificationManager manager = (NotificationManager) this.cordova
         .getActivity().getSystemService(
             Context.NOTIFICATION_SERVICE);

     String title = args.getString(0);
     String text = args.getString(1);
     Notification notification
     = new Notification.Builder(this.cordova.getActivity())
     .setTicker("New notification").setDefaults(1)
     .setSmallIcon(R.drawable.icon)
     .setAutoCancel(true)
     .setContentTitle(title).setContentText(text)
     .setContentIntent(PendingIntent.getActivity(this.cordova.getActivity(),
             0, this.cordova.getActivity().getIntent(), 0)).build();
     manager.notify(1, notification);

     return true;
                 }
        }
}

在 res/xml/config.xml 配置 plugin feature

至此一個完整的 plugin 已經建立完成。然後我們只需要在 JavaScript 端調用這個 plugin 就可以觸發一個 notification,具體調用方式參考清單 4:

4. 客戶端調用系統 notification

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

//ellipsis code
// …… .

package com.test.notification;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
 var ws= new WebSocket("ws://localhost:8080/PushNotification");
ws.onopen = function (event) {
console.log("connected to server");
};
ws.onmessage = function (event) {
var notifys = jQuery.parseJSON(evnt.data);
   var message = ["New Job Notification",notifys[0].message];
   cordova.plugins.localNotification.sendNotify(message);
};

   當執行 cordova.plugins.localNotification.sendNotify 方法時,cordova 會向 webview 發送一個 XMLHttpRequest 請求並且包含的參數中有 sendNotify 關鍵字,這個請求會被我們已經實現的 Cordova plugin 攔截住,並且執行一個 android Notification,這個時候一個消息就成功推送到客戶端。如下圖

圖 3. 通過 websocket server 成功將信息推送到客戶端

wKiom1gnHXjBR1gvAAGz9IEZ2gQ794.png-wh_50

Websocket 消息推送優點 :
1. 開發週期短,維護成本低。
2. 消息不經轉第三方服務器,直接由服務器發送到客戶端,安全性好。基於 GCM 或者 APNS 的消息推送會把消息發送 GCM 服務器或 APNS 服務器,再由他們轉發到客戶端。
3. 自己開發服務端,可擴展性好。
4. 對於客戶端而言,長連接比輪詢的方式性能和及時性更好。

參考資料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章