springboot2.0 + websocket + android客戶端實戰
阿新 • • 發佈:2018-12-15
簡介
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);
記得新增網路許可權。