WebSocket是通过一个Socket来实现双工异步通信的。直接使用WebSocket或者SockJS协议显得特别繁琐。使用它的子协议STOMP,它是一个更高级别的协议,STOMP协议使用一个基于帧格式来定义消息,与HTTP的Request和Response类似。
环境依赖
Spring Boot对使用WebSocket提供了支持,配置源码在org.springframework.boot.autoconfigure.websocket包下。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
顺便说一句,springboot的高级组件会自动引用基础的组件,像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter,所以不要重复引入。
配置Websocket
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter (){
return new ServerEndpointExporter();
}
}
处理WebSocket
@ServerEndpoint("/websocket")
@Component
public class WebSocketService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private static int COUNT = 0;
private static CopyOnWriteArraySet<WebSocketService> websocket = new CopyOnWriteArraySet<WebSocketService>();
private Session session;
/*
建立连接的时候调用
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
websocket.add(this);
addOnlineCount();
log.info("当前在线人数:" + COUNT);
try {
sendMessage("连接成功");
} catch (IOException e) {
e.printStackTrace();
}
}
@OnClose
public void onclose() {
websocket.remove(this);
subOnlineCount();
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/*
接收到客户端的信息
*/
@OnMessage
public void shoudaoMessage(String message, Session session) {
log.info("server 收到的信息是:" + message);
//群发消息
for (WebSocketService item : websocket) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message) throws IOException {
for (WebSocketService item : websocket) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return COUNT;
}
public static synchronized void addOnlineCount() {
WebSocketService.COUNT++;
}
public static synchronized void subOnlineCount() {
WebSocketService.COUNT--;
}
}
新建HTML文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<input id="text" type="text" /><button onclick="send()">Send</button>
<button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script>
var websocket = null;
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:8080/websocket");
// 建立成功后的回调函数
websocket.onopen = function (ev) {
setMessageInnerHTML("open");
}
websocket.onclose=function(){
setMessageInnerHTML("close");
}
websocket.onmessage=function (ev) {
setMessageInnerHTML(ev.data);
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
}else {
alert("浏览器不支持");
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
服务器推送
@RestController
public class WebSocketSendMessageController {
@Autowired
private WebSocketService socketService;
@GetMapping("/send")
public String sendMessage(){
try {
socketService.sendInfo("大家好");
} catch (IOException e) {
e.printStackTrace();
return "fail";
}
return "succcess";
}
}
测试
用浏览器打开刚刚新建的html文件
可以在多个窗口打开,看看控制台日志信息
发送消息
服务器推送消息
可以看到客户端接受到了消息