什么是WebSocet?
WebSocket是一种在Web应用程序中实现双向通信的协议,它可以在客户端和服务器之间建立持久的连接,使得客户端可以实时地接收服务器推送的消息或数据。相比传统的HTTP请求,WebSocket可以实现更快、更实时的数据传输
WebSocket适用于什么场景?
- 实时通信:如聊天应用、在线游戏等需要实时通信的应用。
- 实时监控:如实时监控系统、实时数据显示等需要实时更新的应用。
- 实时交互:如在线投票、网络直播等需要实时交互的应用。
为什么要使用WebSocket?
以上三种场景,如果不使用WebSocket,那么久需要通过轮询或长轮询等方式实现实时通信,但这种方式会导致不必要的网络开销和延迟。而且,轮询和长轮询都需要客户端不断地向服务器发送请求,会占用服务器的资源。相比之下,WebSocket可以建立一次连接,保持持久连接,并且只在数据更新时传输数据,因此减少了网络开销和服务器的负载。
那么在这里我们就实现一个聊天的案例:
首先创建一个SpringBoot项目,然后maven引入以下包
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
然后创建ServerEndpointExporterConfig,注入ServerEndpointExporter
package cc.oolo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class ServerEndpointExporterConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
然后创建一个WebSocketConfigurator,用于获取我们连接websocket时候,url上所带的参数
package cc.oolo.config;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {
// WebSocketConfig类
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// 获取userId参数
String userId = request.getParameterMap().get("userId").get(0);
// 将userId放入attributes中
sec.getUserProperties().put("userId", userId);
}
}
然后还需要创建一个存储消息的实体类
package cc.oolo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SocketMessage {
private String from;
private String to;
/**
* 为1代表单发给某个用户,为0代表广播
*/
private Integer type;
private String message;
}
最后就是创建我们的websocket请求处理类了
package cc.oolo.websocket;
import cc.oolo.config.WebSocketConfigurator;
import cc.oolo.entity.SocketMessage;
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
@Component
@ServerEndpoint(value = "/p2p",configurator = WebSocketConfigurator.class)
public class P2PWebSocket {
private static Map<String,Session> sessionMap = new HashMap<>();
/**
* 建立连接
*/
@OnOpen
public void onOpen(Session session) {
String userId = (String) session.getUserProperties().get("userId");
sessionMap.put(userId,session);
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) throws IOException {
String userId = (String) session.getUserProperties().get("userId");
sessionMap.remove(userId);
session.close();
}
/**
* 向另一个会话发送消息
*/
@OnMessage
public void sendMessage(String message) throws IOException {
SocketMessage socketMessage = JSON.parseObject(message, SocketMessage.class);
Session otherSession = sessionMap.get(socketMessage.getTo());
Integer type = socketMessage.getType();
//type为1的时候为单发
if (type == 1){
otherSession.getBasicRemote().sendText(socketMessage.getMessage());
}
//type为0的时候为广播
if (type == 0){
for (String key : sessionMap.keySet()){
sessionMap.get(key).getBasicRemote().sendText(socketMessage.getMessage());
}
}
}
}
创建前端html页面
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>WebSocket P2P</title>
</head>
<body>
用户名:<input id="userId" type="text" />
<button onclick="concatWS()">连接</button>
<br>
发送用户:<input id="toUserId" type="text" />
</br>
<input id="text" type="text" />
<button onclick="send(1)">发给某个用户</button>
<button onclick="send(0)">广播</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
var userId = null;
function concatWS(){
userId = document.getElementById('userId').value
console.log(userId)
//判断当前浏览器是否⽀持WebSocket, 主要此处要更换为⾃⼰的地址
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/p2p?userId="+userId);
} else {
alert('Not support websocket')
}
//连接发⽣错误的回调⽅法
websocket.onerror = function() {
setMessageInnerHTML("服务器通信故障");
};
//连接成功建⽴的回调⽅法
websocket.onopen = function(event) {
setMessageInnerHTML("与服务器已建⽴连接");
}
//接收到消息的回调⽅法
websocket.onmessage = function(event) {
console.log(event.data)
setMessageInnerHTML(event.data);
}
//连接关闭的回调⽅法
websocket.onclose = function() {
setMessageInnerHTML("WebSocket连接已关闭");
}
//监听窗⼝关闭事件,当窗⼝关闭时,主动去关闭websocket连接,防⽌连接还没断开就关闭
//窗⼝,server端会抛异常。
window.onbeforeunload = function() {
websocket.close();
}
//将消息显示在⽹⻚上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send(type) {
var message = document.getElementById('text').value;
var to = document.getElementById("toUserId").value;
websocket.send(JSON.stringify({from: userId,to: to,message: message,type: type}));
}
</script>
</html>
测试: