1. 程式人生 > >springboot2.0 + websocket + android客戶端實戰

springboot2.0 + websocket + android客戶端實戰

簡介

WebSocket是HTML5中的協議,支援持久連線,可以有效解決客戶端和服務端之間資料資料同步時需要輪詢的問題。

效果圖

這裡寫圖片描述

服務端

  • 建立web工程(此處省略)

  • 引入websocket maven依賴(springboot2.0以上才支援)

    找到工程的pom.xml資料夾,新增以下依賴。

<!--websocket springboot2.0以上才支援-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId
>
spring-boot-starter-websocket</artifactId> </dependency>
  • 配置Websocket
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 配置websocket並開啟
*/
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } }
  • 編寫WebSocket訊息處理類
package com.mhwang.miniprogram;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.stereotype.Component;

import
java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/websocket/{vmcNo}") // 客戶端URI訪問的路徑 @Component public class WebSocketServer { /** 儲存所有連線的webSocket實體 * CopyOnWriteArrayList使用了一種叫寫時複製的方法, * 當有新元素新增到CopyOnWriteArrayList時, * 先從原有的陣列中拷貝一份出來,然後在新的陣列做寫操作, * 寫完之後,再將原來的陣列引用指向到新陣列。 * 具備執行緒安全,並且適用於高併發場景 */ private static CopyOnWriteArrayList<WebSocketServer> sWebSocketServers = new CopyOnWriteArrayList<>(); private Session mSession; // 與客戶端連線的會話,用於傳送資料 private long mVmcNo; // 客戶端的標識(這裡以機器編號) private Log mLog = LogFactory.getLog(WebSocketServer.class); @OnOpen public void onOpen(Session session, @PathParam("vmcNo") long vmcNo){ mSession = session; sWebSocketServers.add(this); // 將回話儲存 mLog.info("-->onOpen new connect vmcNo is "+vmcNo); mVmcNo = vmcNo; } @OnClose public void onClose(){ sWebSocketServers.remove(this); mLog.info("-->onClose a connect"); } @OnMessage public void onMessage(String message, Session session){ mLog.info("-->onMessage "+message); // 這裡選擇的是讓其他客戶端都知道訊息,類似於轉發的聊天室,可根據使用場景使用 for (WebSocketServer socketServer : sWebSocketServers){ socketServer.sendMessage("i have rcv you message"); } } /** 對外發送訊息 * @param message */ public boolean sendMessage(String message){ try { mSession.getBasicRemote().sendText(message); } catch (IOException e) { mLog.info(e.toString()); return false; } return true; } /** 對某個機器傳送訊息 * @param message * @param vmcNo 機器編號 * @return true,返回傳送的訊息,false,返回failed字串 */ public static String sendMessage(String message, long vmcNo){ boolean success = false; for (WebSocketServer server : sWebSocketServers){ if (server.mVmcNo == vmcNo){ success = server.sendMessage(message); break; } } return success ? message : "failed"; } }
  • 新增外部訪問介面(如不需要,可不寫,這裡主要是想通過該介面給機器傳送命令)
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WebSocketController {

@RequestMapping(value = "/operation/{vmc}/{cmd}")
public String remote(@PathVariable("vmc") long vmc, @PathVariable("cmd") String cmd){
System.out.print("remote");
RemoteOperation operation = new RemoteOperation();
operation.setVmc_no(vmc);
operation.setOperation(cmd);
String message = new Gson().toJson(operation);
System.out.println("message in json is :"+message);
return WebSocketServer.sendMessage(message,vmc);
}

@RequestMapping(value = "/test")
public String test(){
System.out.print("test");
return "hello world";
}
}

這裡需要注意的是,springboot+websocket可能存在這樣的一個bug,使用IntelliJ IDE的package打包成jar包的功能時,本地執行時一切正常,但是打包卻報javax.websocket.server.ServerContainer not available錯誤,這是因為test不通過,導致打包失敗。
解決方法是使用maven的命令列打包。開啟系統cmd命令,進入工程根目錄下,輸入mvn package -DskipTests
這裡寫圖片描述

Android端

Android端的流程跟伺服器端的差不多。

  • 建立Android工程(廢話)

  • 加入gradle依賴

    在工程build.gradle(Module:app)檔案中加入以下依賴:

compile "org.java-websocket:Java-WebSocket:1.3.8"
  • 編寫WebSocket訊息連線及處理類
package com.mhwang.adbcommunicatetest;

import android.content.Context;
import android.content.Intent;
import android.util.Log;


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

import java.net.URI;
import java.net.URISyntaxException;

public class WebClient extends WebSocketClient{
    public static final String ACTION_RECEIVE_MESSAGE = "com.jinuo.mhwang.servermanager";
    public static final String KEY_RECEIVED_DATA = "data";
    private static WebClient mWebClient;
    private Context mContext;
    /**
     *  路徑為ws+伺服器地址+伺服器端設定的子路徑+引數(這裡對應伺服器端機器編號為引數)
     *  如果伺服器端為https的,則字首的ws則變為wss
     */
    private static final String mAddress = "ws://伺服器地址:埠/mhwang7758/websocket/";
    private void showLog(String msg){
        Log.d("WebClient---->", msg);
    }
    private WebClient(URI serverUri, Context context){
        super(serverUri, new Draft_6455());
        mContext = context;
        showLog("WebClient");
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        showLog("open->"+handshakedata.toString());
    }

    @Override
    public void onMessage(String message) {
        showLog("onMessage->"+message);
        sendMessageBroadcast(message);
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        showLog("onClose->"+reason);
    }

    @Override
    public void onError(Exception ex) {
        showLog("onError->"+ex.toString());
    }

    /** 初始化
     * @param vmc_no
     */
    public static void initWebSocket(final Context context, final long vmc_no){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mWebClient = new WebClient(new URI(mAddress+vmc_no), context);
                    try {
                        mWebClient.connectBlocking();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /** 傳送訊息廣播
     * @param message
     */
    private void sendMessageBroadcast(String message){
        if (!message.isEmpty()){
            Intent intent = new Intent();
            intent.setAction(ACTION_RECEIVE_MESSAGE);
            intent.putExtra(KEY_RECEIVED_DATA,message);
            showLog("傳送收到的訊息");
            mContext.sendBroadcast(intent);
        }
    }

}

在應用初始化時呼叫:

WebClient.initWebSocket(this,10086);

記得新增網路許可權。