websocket 简绍
websocket 是一种在单个 tcp 连接上进行全双工通信的协议。websocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
在 websocket api 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
websocket 的优势包括
- 全双工通信:允许服务器主动向客户端发送信息,同时也能够提供客户端到服务器的低延迟通信。
- 减少带宽消耗:一旦建立连接之后,通信开销很小,因为不需要http头部。
创建更丰富的交互体验:实时更新数据的能力,使得应用能够更加流畅地响应用户的动作
可以通过 javascript 的 websocket 对象来创建一个 websocket 连接。
var ws = new websocket('ws://localhost:8080');
javascript 设置
处理事件
websocket 对象提供了几个事件处理器来处理连接的状态变化:
- open:连接成功建立时触发。
- message:接收到消息时触发。
- error:发生错误时触发。
- close:连接关闭时触发。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>websocket example</title>
<script>
document.addeventlistener('domcontentloaded', function () {
const socket = new websocket('ws://localhost:8080/websocket');
socket.onopen = function (event) {
console.log('websocket connection established.');
};
socket.onmessage = function (event) {
console.log('received from server:', event.data);
};
socket.onclose = function (event) {
console.log('websocket connection closed.');
};
socket.onerror = function (error) {
console.error('websocket error:', error);
};
function sendmessage() {
let message = document.getelementbyid('message').value;
socket.send(message);
console.log('sent message:', message);
}
});
</script>
</head>
<body>
<input type="text" id="message" placeholder="type your message here">
<button onclick="sendmessage()">send message</button>
</body>
</html>
确保你的服务器端已正确配置,并且能够接收来自客户端的连接请求。
使用https和wss(加密的websocket)可以提高安全性,特别是在生产环境中。
如果你的服务器部署在不同的域上,请确保服务器配置允许跨域访问。
java 服务端设置
导jar包
通过 maven 导入 spring boot 中 集成的 websocket jar 包
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-websocket</artifactid>
</dependency>
创建websocket端点
@enablewebsocket
@enablewebsocket 是 spring 框架中的一个注解,用于启用 websocket 支持。当一个类被标记为此注解时,spring 会自动配置必要的组件来处理 websocket 请求。这个注解通常应用在一个配置类上,该类负责设置 websocket 的处理逻辑。
registerwebsockethandlers
- registerwebsockethandlers 是 spring websocket 框架中的一个重要方法,它用来注册 websocket 处理器(websockethandler)。这个方法是 websocketconfigurer 接口中定义的一个抽象方法,需要在你的配置类中实现。通过这个方法,你可以指定哪些路径映射到特定的 websocket 处理器,并可以配置其他相关选项。
- addhandler 方法:接受两个参数,一个是 websockethandler 的实例,另一个是 websocket 终结点的 url 映射。
- setallowedorigins 方法:指定哪些源(域名)可以访问你的 websocket 终结点。如果设置为 “*”,则表示任何源都可以访问。
- withsockjs 方法:这是一个可选的配置项,用于启用 sockjs,它是一种兼容性更好的 websocket 实现,能够在不支持原生 websocket 的浏览器上使用其他传输方式(如长轮询)。
接下来,定义一个websocket处理器类,并配置一个端点来接受websocket连接:
import org.springframework.context.annotation.configuration;
import org.springframework.web.socket.config.annotation.enablewebsocket;
import org.springframework.web.socket.config.annotation.websocketconfigurer;
import org.springframework.web.socket.config.annotation.websockethandlerregistry;
@configuration
@enablewebsocket
public class websocketconfig implements websocketconfigurer {
private final websockethandler websockethandler;
public websocketconfig(websockethandler websockethandler) {
this.websockethandler = websockethandler;
}
@override
public void registerwebsockethandlers(websockethandlerregistry registry) {
registry.addhandler(websockethandler, "/websocket").setallowedorigins("*");
}
}
这里我们定义了一个websocket配置类,并注册了websocket处理器。/websocket 是websocket的端点路径。
实现websocket处理器
afterconnectionestablished
void afterconnectionestablished(websocketsession session) throws exception;
- afterconnectionestablished 是 websockethandler 接口中定义的一个方法,它会在 websocket 连接成功建立之后被调用。这个方法为你提供了一个机会来执行一些初始化操作,例如记录连接信息、将连接加入到一个管理连接的集合中,或者发送初始消息给客户端。
- websocketsession session —— 表示新建立的 websocket 会话。
- exception —— 如果处理过程中出现任何异常,可以抛出
afterconnectionclosed
void afterconnectionclosed(websocketsession session, closestatus status) throws exception;
- afterconnectionclosed 是 websockethandler 接口中定义的一个方法,它会在 websocket 连接关闭时被调用。这个方法允许你在连接关闭后执行一些清理操作,例如从会话管理器中移除会话、记录关闭事件或者发送通知给其他客户端。
- websocketsession session:表示已关闭的 websocket 会话。
- closestatus status:表示关闭状态的原因。closestatus 是一个枚举类型,提供了标准的关闭码和关闭原因,例如 normal_closure(正常关闭)、going_away(离开)、protocol_error(协议错误)等。
- 抛出:exception —— 如果处理过程中出现任何异常,可以抛出。
handletextmessage
void handletextmessage(websocketsession session, textmessage message) throws exception;
- handletextmessage 是 websockethandler 接口中定义的一个方法,用于处理从客户端接收到的文本消息。当客户端发送文本消息时,这个方法会被调用。对于二进制消息,则会调用 handlebinarymessage 方法。
- websocketsession session:表示当前的 websocket 会话。
- textmessage message:包含从客户端接收到的文本消息。
- 抛出:exception —— 如果处理过程中出现任何异常,可以抛出
- 现在我们需要实现一个websocket处理器类来处理连接事件:
import org.springframework.web.socket.closestatus;
import org.springframework.web.socket.textmessage;
import org.springframework.web.socket.websocketsession;
import org.springframework.web.socket.handler.textwebsockethandler;
import java.util.concurrent.concurrenthashmap;
import org.springframework.web.socket.closestatus;
import org.springframework.web.socket.textmessage;
import org.springframework.web.socket.websocketsession;
import org.springframework.web.socket.handler.textwebsockethandler;
import java.util.concurrent.concurrenthashmap;
public class websockethandler extends textwebsockethandler {
private static final concurrenthashmap<string, websocketsession> sessions = new concurrenthashmap<>();
@override
public void afterconnectionestablished(websocketsession session) throws exception {
super.afterconnectionestablished(session);
string id = session.getid();
sessions.put(id, session);
system.out.println("websocket connection established with session id: " + id);
}
@override
public void afterconnectionclosed(websocketsession session, closestatus status) throws exception {
super.afterconnectionclosed(session, status);
string id = session.getid();
sessions.remove(id);
system.out.println("websocket connection closed with session id: " + id);
}
public void sendmessagetosession(string sessionid, string message) {
websocketsession session = sessions.get(sessionid);
if (session != null && session.isopen()) {
try {
session.sendmessage(new textmessage(message));
} catch (exception e) {
e.printstacktrace();
}
}
}
public void broadcastmessage(string message) {
for (websocketsession session : sessions.values()) {
if (session.isopen()) {
try {
session.sendmessage(new textmessage(message));
} catch (exception e) {
e.printstacktrace();
}
}
}
}
}
现在我们可以使用websockethandler类中的sendmessagetosession或broadcastmessage方法来发送数据给前端。
发送给特定会话:如果想要向特定的客户端发送消息,可以使用sendmessagetosession方法。你需要知道客户端的会话id。
websockethandler.sendmessagetosession(sessionid, "hello, client!");
广播消息:如果你想向所有已连接的客户端广播消息,可以使用broadcastmessage方法
websockethandler.broadcastmessage("hello, everyone!")
注销websocket连接
要在java后端注销websocket连接,可以通过调用 websockethandler 类中的 removesession 方法来实现。例如,你可以从http请求或其他服务中调用此方法:
@getmapping("/logout/{sessionid}")
public responseentity<string> logout(@pathvariable string sessionid) {
websockethandler handler = new websockethandler(); // 或者通过依赖注入获取
handler.removesession(sessionid);
return responseentity.ok("session " + sessionid + " has been closed.");
}
自己定义一个 固定id 方便使用
前端 javascript 代码
- 创建websocket连接:在dom加载完成后,创建一个websocket连接。
- 发送握手消息:在连接建立后,发送一个包含固定id的握手消息。
- 处理接收到的消息:设置消息接收处理函数,用于接收后端发送的数据。
后端 java 代码
- 配置websocket处理器:配置websocket处理器,注册websocket路径。
- 处理握手消息:接收前端发送的握手消息,并根据消息内容保存客户端id与websocket会话的映射。
- 发送消息:提供方法根据客户端id发送消息。
前端 javascript
<!doctype html>
<html l ang="en">
<head>
<meta charset="utf-8">
<title>websocket client example</title>
</head>
<body>
<input type="text" id="messageinput" placeholder="enter your message here">
<button id="sendbutton">send message</button>
<div id="log"></div>
<script>
document.addeventlistener('domcontentloaded', function () {
const socket = new websocket('ws://example.com/websocket');
socket.onopen = function (event) {
console.log('websocket connection established.');
// 固定的客户端id
const clientid = 'client-123';
// 发送握手消息
socket.send(json.stringify({
type: 'handshake',
clientid: clientid,
message: 'hello, server!'
}));
};
socket.onmessage = function (event) {
console.log('received from server:', event.data);
document.getelementbyid('log').textcontent += 'received from server: ' + event.data + '\n';
};
socket.onclose = function (event) {
console.log('websocket connection closed.');
document.getelementbyid('log').textcontent += 'websocket connection closed.\n';
};
socket.onerror = function (error) {
console.error('websocket error:', error);
document.getelementbyid('log').textcontent += 'websocket error: ' + error + '\n';
};
document.getelementbyid('sendbutton').addeventlistener('click', function () {
let message = document.getelementbyid('messageinput').value;
if (socket.readystate === websocket.open) {
socket.send(message);
document.getelementbyid('log').textcontent += 'sent message: ' + message + '\n';
} else {
alert('websocket connection is not open.');
}
});
});
</script>
</body>
</html>
java 服务器端配置
import com.fasterxml.jackson.databind.objectmapper;
import com.ruoyi.common.utils.socket.handshakemessage;
import org.springframework.stereotype.component;
import org.springframework.web.socket.closestatus;
import org.springframework.web.socket.textmessage;
import org.springframework.web.socket.websocketsession;
import org.springframework.web.socket.handler.textwebsockethandler;
import java.util.map;
import java.util.concurrent.concurrenthashmap;
@component
public class websockethandler extends textwebsockethandler {
private static final map<string, websocketsession> clientsessions = new concurrenthashmap<>();
private static final objectmapper objectmapper = new objectmapper();
@override
public void afterconnectionestablished(websocketsession session) throws exception {
super.afterconnectionestablished(session);
string sessionid = session.getid();
system.out.println("websocket connection established with session id: " + sessionid);
}
@override
protected void handletextmessage(websocketsession session, textmessage message) throws exception {
string payload = message.getpayload();
handshakemessage handshakemessage = objectmapper.readvalue(payload, handshakemessage.class);
if ("handshake".equals(handshakemessage.gettype())) {
string clientid = handshakemessage.getclientid();
string sessionid = session.getid();
// 存储clientid与sessionid的映射关系
clientsessions.put(clientid, session);
// 可以选择回复客户端确认握手成功的消息
session.sendmessage(new textmessage("handshake successful"));
}
}
@override
public void afterconnectionclosed(websocketsession session, closestatus status) throws exception {
super.afterconnectionclosed(session, status);
string sessionid = session.getid();
system.out.println("websocket connection closed with session id: " + sessionid);
// 移除会话
clientsessions.values().removeif(s -> s.getid().equals(sessionid));
}
public void sendmessagetoclient(string clientid, string message) {
websocketsession session = clientsessions.get(clientid);
if (session != null && session.isopen()) {
try {
session.sendmessage(new textmessage(message));
} catch (exception e) {
e.printstacktrace();
}
}
}
// 定义握手消息模型
private static class handshakemessage {
private string type;
private string clientid;
private string message;
// getters and setters
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论