Java 实现 Websocket 通信
代码:https://github.com/ioufev/websocket-springboot-demo
代码备份下载:示例就是客户端和服务端互相发送简单的字符串。
WebSocket
客户端和服务端,都有6个API(准确说是4个事件2个方法),所以说客户端和服务端是对等的。
🟢 onOpen()
🟢 onClose()
🟢 onError()
🟢 onMessage()
🟢 sendMessage()
🟢 close()
Java 端的 4个事件2个方法
js 端的 4个事件2个方法
代码
WebSocketConfig
类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
WebSocketServer
类
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
private static int onlineCount = 0;
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
private Session session;
private String sid = "";
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
this.sid = sid;
sendMessage("conn_success");
System.out.println("新的窗口" + sid + "已连接!当前在线人数为" + getOnlineCount());
}
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("连接窗口" + sid + "关闭!当前在线人数为" + getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) {
if(message.startsWith("target-")){
int index = message.indexOf(":");
String sid = message.substring(7,index);
sendInfo(message.substring(index + 1), sid);
return;
}
this.session = session;
sendMessage("服务端收到来自窗口" + sid + "发送的消息:" + message);
}
@OnError
public void onError(Session session, Throwable error) {
this.session = session;
error.printStackTrace();
}
private void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
// 群发消息
/**
* 群发自定义消息
*/
public static void sendInfo(String message, @PathParam("sid") String sid) {
System.out.println("推送消息到窗口" + sid + ",推送内容:" + message);
for (WebSocketServer item : webSocketSet) {
//这里可以设定只推送给这个sid的,为null则全部推送
if (sid == null) {
// item.sendMessage(message);
} else if (item.sid.equals(sid)) {
item.sendMessage(message);
}
}
}
public static void sendInfo2(String message) {
System.out.println("推送消息到所有窗口" + ",推送内容:" + message);
for (WebSocketServer item : webSocketSet) {
item.sendMessage(message);
}
}
/**
* 群发自定义消息
*/
public static void sendInfo2One(String message, @PathParam("sid") String sid) {
System.out.println("推送消息到窗口" + sid + ",推送内容:" + message);
for (WebSocketServer item : webSocketSet) {
if (item.sid.equals(sid)) {
item.sendMessage(message);
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
ws-demo.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试WS</title>
</head>
<body>
<div>随机ID:
<span id="ws-id">
</span>
</div>
<button id="connect">开始连接</button>
<br/>
<label for="message">发送内容: </label><input id="message" type="text" placeholder="请输入要发送的消息内容">
<br/>
<label for="target-id">发送给谁: </label><input id="target-id" type="text" placeholder="请输入要发送给谁">
<br/>
<button id="sendButton">发送消息</button>
<br/>
<label for="back">收到消息: </label><textarea id="back" placeholder="提示消息" style="width: 600px;height: 200px"></textarea>
<br>
<button id="disconnect">断开连接</button>
<script>
let sendButton = window.document.getElementById("sendButton");
sendButton.addEventListener("click", () => {
send();
})
document.getElementById("disconnect").addEventListener("click", () => {
closeWebSocket();
})
let websocket;
document.getElementById("connect").addEventListener("click" , () =>{
if(websocket){
closeWebSocket()
}
// 随机整数
let random = Math.floor(Math.random()*10000);
window.document.getElementById("ws-id").innerText = random + '';
//判断当前浏览器是否支持WebSocket,是则创建WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/ws/" + random);
}else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
console.log("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
console.log("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
console.log(event.data);
window.document.getElementById("back").value = event.data;
}
//连接关闭的回调方法
websocket.onclose = function () {
console.log("WebSocket连接关闭");
}
})
websocket = null;
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
let message = document.getElementById('message').value;
let target_id = document.getElementById('target-id').value;
if (target_id != ""){
websocket.send('target-' + target_id + ':' + message);
return;
}
websocket.send(message);
}
//如果websocket连接还没断开就关闭了窗口,后台server端会抛异常。
//所以增加监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接
window.onbeforeunload = function () {
closeWebSocket();
}
</script>
</body>
</html>
服务端推送
DemoController
类
import com.ioufev.websocketspringbootdemo.ws.WebSocketServer;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class DemoController {
//推送数据接口
@PostMapping("/push/{cid}")
public Map pushToWeb(@PathVariable String cid, @RequestBody String message) {
Map<String,Object> result = new HashMap<>();
WebSocketServer.sendInfo(message, cid);
result.put("code", cid);
result.put("msg", message);
return result;
}
@PostMapping("/push")
public Map pushToWeb2(@RequestBody String message) {
Map<String,Object> result = new HashMap<>();
WebSocketServer.sendInfo2(message);
result.put("msg", message);
return result;
}
}
动图演示
过程和问题
关于 WebSocket 协议的一些资料
关于 WebSocketServer 类中的 static 关键字
WebSocketServer 类的作用域是原型(Prototype),而其他 Bean 的作用域是单例(Singleton)。
自动装配其他 Bean 实例时需要一些设置
关于 WebSocket 集群转发
分享我使用的 Redis 发布订阅的实现,还可以使用消息队列MQ,ZooKeeper。
关于 WebSocket 的子协议
把 WebSocket 当作传输层
可以传递应用层协议,比如:MQTT、STOMP、AMQP、OPCUA(看代码有),或者自定义的协议,反正只要知道别人发的消息是什么意思就行。