搭建局域網WebSocket手機和Web遙控器

這裏以遙控Android平臺的機頂盒或應用爲例,實現基於WebSocket的遙控通信。這裏以遙控機頂盒爲例。WebSocket支持雙向通信。WebSocket是一種在單個TCP連接上進行全雙工通信的協議。允許服務端主動向客戶端推送數據,瀏覽器/客戶端和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。很多推送功能就是基於WebSocket進行實現的通信推送。

WebSocket優點:

  • 較少的控制開銷;
  • 更強的實時性;
  • 保持連接狀態;
  • 更好的二進制支持;
  • 可以支持擴展;
  • 更好的壓縮效果等。

更詳細的WebSocket介紹和用法:

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

http://www.websocket.org/

 

目前有兩種方式實現通信遙控:

  1. 一個是基於局域網的,即服務器和客戶端在一個網絡下,可以在機頂盒應用裏內置WebSocket服務端Server,手機和網頁作爲客戶端發送指令。
  2. 另一個是搭建一個公網中轉服務器Server,手機/網頁、機頂盒都作爲客戶端,這樣客戶端都需要知道服務器端地址去主動連接。這樣就不會侷限於同一個網絡通信,手機/網頁發送的消息通過服務器端中轉再傳統給機頂盒客戶端。當然,這樣也有弊端,就是增大了通信運營成本、安全性、也可能會有延遲、服務器一旦出問題,所有客戶端都無法正常工作等。

先看第一種,這裏採用Java搭建WebSocket服務器,內置到機頂盒內。

這裏用到了開源庫:Java-WebSocket https://github.com/TooTallNate/Java-WebSocket

package com.test.ws.utils;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;

public class SimpleServer extends WebSocketServer {

    public SimpleServer(InetSocketAddress address) {
        super(address);
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        conn.send("Welcome to the server!"); //This method sends a message to the new client
        broadcast( "new connection: " + handshake.getResourceDescriptor() ); //This method sends a message to all clients connected
        System.out.println("new connection to " + conn.getRemoteSocketAddress());
    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        System.out.println("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason);
    }

    @Override
    public void onMessage(WebSocket conn, String message) {
        System.out.println("received message from "	+ conn.getRemoteSocketAddress() + ": " + message);
    }

    @Override
    public void onMessage( WebSocket conn, ByteBuffer message ) {
        System.out.println("received ByteBuffer from "	+ conn.getRemoteSocketAddress());
    }

    @Override
    public void onError(WebSocket conn, Exception ex) {
        System.err.println("an error occured on connection " + conn.getRemoteSocketAddress()  + ":" + ex);
    }

    @Override
    public void onStart() {
        System.out.println("server started successfully");
    }


    public static void main(String[] args) {
        String host = "localhost";
        int port = 8887;

        WebSocketServer server = new SimpleServer(new InetSocketAddress(host, port));
        server.run();
    }
}

客戶端分別給出Web和Java兩個版本。

Web版本:

<!DOCTYPE HTML>
<html>

<head>
    <meta charset="utf-8" />
    <title>ws</title>
    <style type="text/css">

    </style>
    <script type="text/javascript">

        var onOpen = function () {
            console.log("Socket opened.");
            socket.send("Hi, Server!");
        },
            onClose = function () {
                console.log("Socket closed.");
            },
            onMessage = function (data) {
                console.log("We get signal:");
                console.log(data);
            },
            onError = function () {
                console.log("We got an error.");
            },

            socket = new WebSocket("ws://127.0.0.1:8887/");
        socket.onopen = onOpen;
        socket.onclose = onClose;
        socket.onerror = onError;
        socket.onmessage = onMessage;

		function sendMessage(){
			socket.send("Hi, Server,I send Message!");
		}
		function left(){
			socket.send("left");
		}
		function right(){
			socket.send("right");
		}
		function up(){
			socket.send("up");
		}
		function down(){
			socket.send("down");
		}
		function enter(){
			socket.send("enter");
		}
		function back(){
			socket.send("back");
		}
    </script>
</head>

<body>
    <div id="connected">
    <button onclick="sendMessage()">連接</button>
    </div>
    <div id="left"><button onclick="left()">Left</button></div>
    <div id="right"><button onclick="right()">Right</button></div>
    <div id="up"><button onclick="up()">Up</button></div>
    <div id="down"><button onclick="down()">Down</button></div>
    <div id="enter"><button onclick="enter()">Enter</button></div>
    <div id="back"><button onclick="back()">Back</button></div>
</body>

</html>

Java版本:

package com.test.ws.utils;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;

public class EmptyClient extends WebSocketClient {

    public EmptyClient(URI serverUri, Draft draft) {
        super(serverUri, draft);
    }

    public EmptyClient(URI serverURI) {
        super(serverURI);
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        send("Hello, it is me. Mario :)");
        System.out.println("new connection opened");
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        System.out.println("closed with exit code " + code + " additional info: " + reason);
    }

    @Override
    public void onMessage(String message) {
        System.out.println("received message: " + message);
    }

    @Override
    public void onMessage(ByteBuffer message) {
        System.out.println("received ByteBuffer");
    }

    @Override
    public void onError(Exception ex) {
        System.err.println("an error occurred:" + ex);
    }

    public static void main(String[] args) throws URISyntaxException {
        WebSocketClient client = new EmptyClient(new URI("ws://localhost:8887"));
        client.connect();
    }
}

在Android機頂盒端使用的時候,可以將Server放置在一個Service裏後臺監聽運行:

package com.test.ws;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;

import java.io.IOException;
import java.net.InetSocketAddress;

public class ConnectService extends Service implements SimpleServer.MessageListener {
    private Thread thread;
    private SimpleServer server;
    private static MessageListener msgListener;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (thread != null) {
            thread = null;
        }
        thread = new Thread() {
            @Override
            public void run() {
                super.run();
                server = new SimpleServer(getBaseContext(), new InetSocketAddress(Constants.HOST, Constants.PORT));
                server.setMessageListener(ConnectService.this);
                server.run();
            }
        };
        thread.start();
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msgListener != null) {
                msgListener.onMessage(msg.obj.toString());
            }
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (server != null) {
            try {
                server.stop();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onMessage(String text) {
        Message message = new Message();
        message.obj = text;
        handler.sendMessage(message);
    }

    public static void setMessageListener(MessageListener messageListener) {
        msgListener = messageListener;
    }

    public interface MessageListener {
        void onMessage(String message);
    }
}

機頂盒服務器端接收模擬按鍵操作:

@Override
    public void onMessage(String message) {
        switch (message) {
            case "left": {
                sendKeyCode(KeyEvent.KEYCODE_DPAD_LEFT);
            }
            break;
            case "right": {
                sendKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT);
            }
            break;
            case "up": {
                sendKeyCode(KeyEvent.KEYCODE_DPAD_UP);
            }
            break;
            case "down": {
                sendKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
            }
            break;
            case "enter": {
                sendKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
            }
            break;
            case "back": {
                sendKeyCode(KeyEvent.KEYCODE_BACK);
            }
            break;
            default:
                Toast.makeText(getBaseContext(), "default:" + message, Toast.LENGTH_SHORT).show();
                break;
        }
    }

// 模擬按鍵發送
private synchronized void sendKeyCode(final int keyCode){
		new Thread () {
			public void run() {
				try {
					Instrumentation inst = new Instrumentation();
					inst.sendKeyDownUpSync(keyCode);
				} catch (Exception e) {
					Log.e("Exception when sendPointerSync", e.toString());
				}
			}
		}.start();
	}

並在項目文件清單里加入權限:

<uses-permission android:name="android.permission.INJECT_EVENTS" />

模擬按鍵核心是Instrumentation這個對象,Instrumentation其實主要用來做測試的,可以模擬所有系統級別按鍵操作。

默認非系統應用只能模擬控制本應用內(進程內)的按鍵實踐。只有擁有系統應用權限的纔可以模擬整個Android系統的事件,包括控制其他的應用操作。

 

如果採用中轉服務器的話,這裏採用NodeJS搭建WebSocket服務器端,這裏只給出一個簡單的Server的搭建示例,後續完善:


var ws = require("nodejs-websocket")

// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function(conn) {
	console.log("New connection")
	conn.on("text", function(str) {
		console.log("Received " + str)
		conn.sendText(str.toUpperCase() + "!!!")
	})
	conn.on("close", function(code, reason) {
		console.log("Connection closed")
	})
}).listen(8001)

 

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