目录
1.首先,添加WebSocket的依赖到你的Spring Boot项目中。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。它通过在客户端和服务器之间建立持久的连接,实现了服务器端可以主动推送消息给客户端的功能,而不需要客户端发起请求。
WebSocket协议的主要特点包括:
全双工通信:客户端和服务器可以同时发送和接收消息,无需等待对方的回应。
基于事件驱动:当有新的消息到达时,服务器可以主动推送消息给客户端,而不需要客户端发起请求。
较低的带宽消耗:WebSocket协议使用较少的 HTTP 头信息,因此带宽消耗较小。
较低的延迟:WebSocket协议采用长连接的方式,减少了连接建立的时间和数据传输的延迟。
跨域通信:WebSocket协议支持跨域通信,可以在不同的域名下进行通信。
适用于实时应用:由于WebSocket协议的特性,它非常适用于需要实时更新的应用程序,如在线聊天、实时数据更新等。
WebSocket协议的使用需要具备以下条件:
1.客户端和服务器都需要支持WebSocket协议。
2.客户端和服务器之间需要建立一个WebSocket连接。
3.客户端和服务器需要通过WebSocket协议进行通信,发送和接收消息。
目前,WebSocket协议已被广泛应用于Web应用程序、移动应用程序和即时通讯等领域
建立WebSocket连接的流程如下:
创建WebSocket对象:在客户端代码中创建一个WebSocket对象,用于与服务器建立连接。
发起握手请求:客户端WebSocket对象发送一个HTTP升级请求,请求将协议从HTTP更改为WebSocket。
服务器回应握手请求:服务器收到握手请求后,返回一个HTTP升级响应,确认请求已成功。
建立WebSocket连接:一旦握手成功,建立WebSocket连接。此时,双方可以通过WebSocket对象发送和接收消息。
数据传输:客户端和服务器之间可以通过WebSocket对象进行双向数据传输。
关闭连接:当WebSocket连接不再需要时,可以通过调用WebSocket对象的close()方法来关闭连接。
WebSocket协议建立报文格式如下:
- GET /chat HTTP/1.1
- Host: server.example.com
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
- Sec-WebSocket-Version: 13
- HTTP/1.1 101 Switching Protocols
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
其中,Sec-WebSocket-Key字段是客户端随机生成的16字节的字符串,服务器收到后会加上一个固定的字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",然后通过SHA-1算法计算出Sec-WebSocket-Accept字段的值。这个值用于验证客户端发送的请求是否合法。
WebSocket传输的数据格式是经过封装的二进制或文本数据。
在建立WebSocket连接之后,客户端和服务器都可以互相发送消息。发送的消息可以是文本类型或二进制类型。
对于文本类型的消息,客户端和服务器发送的数据格式如下:
- Opcode: TEXT (0x1)
- Payload length: 7 (7字节长度)
- Payload data: "Hello!"
这个消息报文包含了一个Opcode字段,指示消息类型为文本类型;一个Payload length字段,指示消息的长度为7字节;一个Payload data字段,保存了具体的消息内容。
对于二进制类型的消息,格式类似,只是Opcode字段值改为BINARY (0x2)。
除了普通的消息类型外,WebSocket还支持PING和PONG类型的消息,用于心跳检测。PING消息的格式如下:
- Opcode: PING (0x9)
- Payload length: 4 (4字节长度)
- Payload data: "ping"
服务器接收到PING消息后会回复一个PONG消息,PONG消息的格式如下:
- Opcode: PONG (0xA)
- Payload length: 4 (4字节长度)
- Payload data: "pong"
WebSocket有四个主要的事件:
连接建立事件(onopen):在客户端与服务器成功建立连接时触发。可以用来发送初始数据或进行认证。
消息接收事件(onmessage):当服务器向客户端发送消息时触发。可以通过此事件获取服务器发送的数据。
连接关闭事件(onclose):在客户端与服务器的连接关闭时触发。可以进行一些清理工作,比如释放资源或重新连接。
错误事件(onerror):当 WebSocket 连接发生错误时触发。可以通过此事件处理连接错误,例如连接失败或消息发送失败等。
在JavaScript中建立WebSocket连接可以使用WebSocket
对象。以下是建立WebSocket连接的步骤:
new WebSocket()
构造函数创建一个WebSocket对象。需要传入WebSocket的URL作为参数。例如:const socket = new WebSocket('wss://example.com/socket');
事件处理:WebSocket对象有几个事件处理函数,用于处理不同的WebSocket状态和消息。
onopen
事件:当WebSocket连接成功建立时触发。onmessage
事件:当接收到服务器发送的消息时触发。onclose
事件:当WebSocket连接关闭时触发。onerror
事件:当发生WebSocket错误时触发。你可以使用下面的语法进行事件处理:
- socket.onopen = function() {
- // 连接成功
- };
-
- socket.onmessage = function(event) {
- // 接收到消息
- const message = event.data;
- };
-
- socket.onclose = function(event) {
- // 连接关闭
- const code = event.code;
- const reason = event.reason;
- };
-
- socket.onerror = function(error) {
- // 处理错误
- };
发送和接收消息:可以使用WebSocket对象的send()
方法发送消息,使用onmessage
事件处理函数接收消息。例如:
- socket.send('Hello, server!');
-
- socket.onmessage = function(event) {
- const message = event.data;
- console.log('Received message: ' + message);
- };
关闭连接:可以使用WebSocket对象的close()
方法关闭连接。例如:
socket.close();
在pom.xml
文件中,加入以下依赖:
- <dependencies>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-websocketartifactId>
- dependency>
- dependencies>
,用于处理WebSocket相关的事件:
- import org.springframework.stereotype.Component;
- import org.springframework.web.socket.CloseStatus;
- import org.springframework.web.socket.TextMessage;
- import org.springframework.web.socket.WebSocketHandler;
- import org.springframework.web.socket.WebSocketMessage;
- import org.springframework.web.socket.WebSocketSession;
-
- @Component
- public class MyWebSocketHandler implements WebSocketHandler {
-
- @Override
- public void afterConnectionEstablished(WebSocketSession session) throws Exception {
- System.out.println("WebSocket连接已建立");
- session.sendMessage(new TextMessage("你已成功连接到WebSocket服务器"));
- }
-
- @Override
- public void handleMessage(WebSocketSession session, WebSocketMessage> message) throws Exception {
- System.out.println("收到消息:" + message.getPayload());
- session.sendMessage(new TextMessage("服务端已收到您的消息:" + message.getPayload()));
- }
-
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
- System.out.println("WebSocket连接已关闭");
- }
-
- @Override
- public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
- System.out.println("WebSocket传输错误");
- }
-
- @Override
- public boolean supportsPartialMessages() {
- return false;
- }
- }
在这个处理器中,我们实现了WebSocketHandler
接口,并重写了其中的方法。在afterConnectionEstablished
方法中,当Websocket连接成功建立时,会打印一条消息,并发送一条欢迎消息给客户端。在handleMessage
方法中,当收到客户端发送的消息时,会打印消息内容,并返回一条回复消息给客户端。在afterConnectionClosed
方法中,当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 {
-
- @Override
- public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
- registry.addHandler(new MyWebSocketHandler(), "/websocket")
- .setAllowedOrigins("*");
- }
- }
在这个配置类中,我们使用@EnableWebSocket
注解启用WebSocket支持,并通过registerWebSocketHandlers
方法注册了我们之前创建的WebSocket处理器MyWebSocketHandler
,指定了WebSocket的访问路径为/websocket
,并设置允许的跨域访问。
以下是使用@ServerEndpoint
注解实现WebSocket的四大事件的示例代码:
- @ServerEndpoint("/websocket")
- public class MyWebSocketEndpoint {
-
- private static Set
sessionSet = new HashSet<>(); -
- @OnOpen
- public void onOpen(Session session) {
- System.out.println("WebSocket连接已建立");
- sessionSet.add(session);
- try {
- session.getBasicRemote().sendText("你已成功连接到WebSocket服务器");
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- @OnMessage
- public void onMessage(String message, Session session) {
- System.out.println("收到消息:" + message);
- try {
- session.getBasicRemote().sendText("服务端已收到您的消息:" + message);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- @OnClose
- public void onClose(Session session) {
- System.out.println("WebSocket连接已关闭");
- sessionSet.remove(session);
- }
-
- @OnError
- public void onError(Throwable error) {
- System.err.println("WebSocket错误:" + error.getMessage());
- }
- }
在这个示例中,通过@ServerEndpoint("/websocket")
注解声明了一个WebSocket端点,并使用@OnOpen
、@OnMessage
和@OnClose
注解分别实现了连接建立、接收消息和连接关闭事件。
需要注意的是,Session
对象用于表示客户端与服务器之间的WebSocket连接,通过它可以向客户端发送消息。在示例中,我们使用一个HashSet
来保存所有连接的Session
对象。
下面是一个简单的示例,展示了如何在WebSocket中实现群聊和私聊功能:
- import javax.websocket.*;
- import javax.websocket.server.ServerEndpoint;
- import java.io.IOException;
- import java.util.HashSet;
- import java.util.Set;
-
- @ServerEndpoint("/websocket")
- public class ChatWebSocketEndpoint {
-
- private static Set
sessionSet = new HashSet<>(); -
- @OnOpen
- public void onOpen(Session session) {
- System.out.println("WebSocket连接已建立");
- sessionSet.add(session);
- try {
- session.getBasicRemote().sendText("你已成功连接到WebSocket服务器");
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- @OnMessage
- public void onMessage(String message, Session session) {
- String[] messageParts = message.split(":", 2);
- String sender = session.getId();
-
- if (messageParts.length > 1) { // 私聊
- String receiver = messageParts[0].trim();
- String privateMessage = messageParts[1].trim();
- sendPrivateMessage(sender, receiver, privateMessage);
- } else { // 群聊
- sendGroupMessage(sender, message);
- }
- }
-
- private void sendGroupMessage(String sender, String message) {
- for (Session session : sessionSet) {
- try {
- session.getBasicRemote().sendText("[" + sender + "]: " + message);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
-
- private void sendPrivateMessage(String sender, String receiver, String message) {
- for (Session session : sessionSet) {
- if (session.getId().equals(receiver)) {
- try {
- session.getBasicRemote().sendText("[私聊]" + sender + ": " + message);
- } catch (IOException e) {
- e.printStackTrace();
- }
- return;
- }
- }
-
- try {
- Session senderSession = getSessionById(sender);
- senderSession.getBasicRemote().sendText("私聊对象不存在");
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private Session getSessionById(String sessionId) {
- for (Session session : sessionSet) {
- if (session.getId().equals(sessionId)) {
- return session;
- }
- }
- return null;
- }
-
- @OnClose
- public void onClose(Session session) {
- System.out.println("WebSocket连接已关闭");
- sessionSet.remove(session);
- }
-
- @OnError
- public void onError(Throwable error, Session session) {
- System.err.println("WebSocket错误:" + error.getMessage());
- sessionSet.remove(session);
- }
- }
这个示例中定义了一个WebSocket端点ChatWebSocketEndpoint
,使用@ServerEndpoint("
/websocket
")
注解来指定WebSocket访问的路径。在onOpen
方法中,当有新的客户端连接时,将其Session
对象添加到一个静态的sessionSet
中,并向客户端发送连接成功的消息。在onMessage
方法中,根据收到的消息内容判断是群聊还是私聊,并调用相应的方法来发送消息。sendGroupMessage
方法用于发送群聊消息,遍历所有的连接Session
对象,并发送消息到每个会话。sendPrivateMessage
方法用于发送私聊消息,找到私聊的接收者的Session
对象,发送消息给接收者。如果找不到接收者,向发送者发送一条错误消息。
当我们发送的信息包含图片时,可以通过格式进行判断,假如是base64编码的图片,就像下面这样先进行判断,然后在发送给对应的客户端
- @OnMessage
- public void onMessage(String message, Session session) {
- if (message.startsWith("data:image")) { // 判断是否为图片数据
- // 处理图片数据
- byte[] imageData = getImageDataFromMessage(message);
- sendImage(session, imageData);
- } else {
- // 处理字符串数据
- sendText(session, message);
- }
- }
-
- private byte[] getImageDataFromMessage(String message) {
- // 提取图片数据,具体实现略
- // 这里假设直接从Base64编码中提取图片数据
- String base64Data = message.substring(message.indexOf(",") + 1);
- return javax.xml.bind.DatatypeConverter.parseBase64Binary(base64Data);
- }
-
- private void sendImage(Session sender, byte[] imageData) {
- for (Session session : sessionSet) {
- if (session != sender) {
- try {
- session.getBasicRemote().sendBinary(ByteBuffer.wrap(imageData));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- private void sendText(Session session, String message) {
- try {
- session.getBasicRemote().sendText(message);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }