在网络通讯领域,我们最熟悉的通讯协议即 HTTP
,其在 TCP
的基础上进一步扩展使之更为的简单易用。而 WebSocket 作为一种通讯方式在某些应用场景下也是一种不错的选择。
WebSocket
是一种在单个 TCP
连接上进行全双工通信的通信协议,它提供了一个持久的连接(即长链接),允许客户端和服务器之间进行实时、双向的数据传输。WebSocket
的出现解决了传统的 HTTP
协议的一些限制,特别是对于需要频繁更新的实时应用程序(如聊天应用、在线游戏等)而言,WebSocket
提供了更低的延迟和更高的效率。
Websocket
一个经典应用场景即消息的主动推送,即由后端向主动推送讯息,在通知与聊天场景中应用广泛。本文即着重介绍如何在前后端分离项目中实现后端的消息主动推送。
一、后端配置
1. 依赖导入
老规矩,在后端的 Spring 项目工程中导入对应的依赖,内容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. 配置管理
在项目中注入 WebSocket Bean
对象,代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3. 服务启动
新建 WebSocketServer
类用于管理请求的连接与关闭,当接收到新的请求时存入缓存容器 websocketMap
,同时计数器 onlineCount
自增用于记录当前的连接数。
为了区分不同的连接这里通过请求参数的第一个值作为唯一标识符,即请求地址格式为:ws://${host}:${port}/websocket?userId=1
,从而使后续能够精准的推送消息给指定连接用户。
其中 @ServerEndpoint
注解可以理解为 HTTP
接口中 @RequstMapping
与 @RestController
的复合体, onMessage()
方法为首次连接时返回值,用于响应以便知晓是否连接成功。
@Component
@ServerEndpoint(value = "/websocket")
public class WebSocketServer {
private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 当前在线连接数
*/
private static final AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 存放客户端对应的 WebSocket 对象
*/
private static final Map<String, WebSocketServer> websocketMap = new ConcurrentHashMap<>();
/**
* 会话标识
*/
private String flag;
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
// 添加会话至容器
this.session = session;
String queryString = session.getQueryString();
flag = queryString.split("=")[1];
websocketMap.put(flag, this);
// 计数器自增
onlineCount.incrementAndGet();
try {
log.info("Receive new session, current connected number: " + getOnlineCount());
sendMessage("You have successfully connected.");
} catch (IOException e) {
log.error("Websocket IO exception, stack tree: ", e);
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
// 从容器中删除会话
websocketMap.remove(flag);
// 计数器自减
onlineCount.decrementAndGet();
log.info("New session close, current connected number: " + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("Receive from client, message: {}", message);
// 群发消息
for (WebSocketServer item : websocketMap.values()) {
try {
item.sendMessage(message);
} catch (IOException e) {
log.error("Session onMessage, stack tree: ", e);
}
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("Session onError, stack tree: ", error);
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote()
.sendText(message);
}
public int getOnlineCount() {
return onlineCount.get();
}
}
4. 消息推送
当连接建立之后即可向连接主动推送数据消息,这里定义了两个方法 send()
与 batchSend()
用于单点推送与消息群发,其仍定义于上述的 WebSocketServer
类中,限于篇幅故分点介绍。
其中单点推送方法 send()
第一个参数 key
即建立连接时传入的唯一标识符,batchSend()
则会推送消息至所有当前的连接的用户,注意其中输入的内容为字符串,可使用 Jackson
等类库序列化为 Json
字符串后传输。
/**
* 群发自定义消息
*/
public boolean send(String key, String message) {
if (websocketMap.isEmpty()) {
log.info("Currently didn't have valid session.");
return false;
}
boolean success = false;
try {
WebSocketServer server = websocketMap.get(key);
if (Objects.isNull(server)) {
log.info("The session of [{}] not existed.", key);
return false;
}
server.sendMessage(message);
success = true;
} catch (IOException e) {
log.error("Session send() error, stack tree: ", e);
}
return success;
}
public boolean batchSend(String message) {
if (websocketMap.isEmpty()) {
log.info("Currently didn't have valid session.");
return false;
}
boolean success = true;
for (WebSocketServer item : websocketMap.values()) {
try {
item.sendMessage(message);
} catch (IOException e) {
success = false;
log.error("Session batchSend() error, stack tree: ", e);
}
}
return success;
}
二、前端应用
1. 依赖安装
在 Vue
前端项目中同样需要安装 WebSocket
依赖,命令如下:
npm install vue-websocket
2. 应用监听
完成依赖安装后即可在项目中建立依赖,通过 new WebSocket(url)
即可相对较为简单。
连接建立之后通过 addEventListener
回调函数监听不同的连接事件,代码如下不详细介绍。
created() {
const host = 127.0.0.1
const port = 9090
const url = `ws://${host}:${port}/websocket?userId=1`
const socket = new WebSocket(url);
// 添加WebSocket事件监听器
socket.addEventListener('open', (event) => {
console.log('WebSocket 连接已打开', event);
})
socket.addEventListener('message', (event) => {
const data = event.data
console.log('接受到后端信息: ', data)
if (data !== null) {
this.$message.success('接受到后端消息: ' + data)
}
})
socket.addEventListener('close', (event) => {
console.log('WebSocket 连接已关闭', event.data);
})
socket.addEventListener('error', (event) => {
console.error('WebSocket 错误', event);
})
}
}