WebSocket是HTML5中的协议,支持持久连接,可以有效解决客户端和服务端之间数据数据同步时需要轮询的问题。
创建web工程(此处省略)
引入websocket maven依赖(springboot2.0以上才支持)
找到工程的pom.xml文件夹,添加以下依赖。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
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();
}
}
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 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工程(废话)
加入gradle依赖
在工程build.gradle(Module:app)文件中加入以下依赖:
compile "org.java-websocket:Java-WebSocket:1.3.8"
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);
记得添加网络权限。