WebSocket--1.协议解析
目录
一.概念
二.建立流程
三.四大事件
五.js中建立ws链接
六.springboot中进行ws连接
1.首先,添加WebSocket的依赖到你的Spring Boot项目中。
2.接下来,创建一个WebSocket处理器
3.最后,创建一个配置类,注册该WebSocket处理器:
七.使用@ServerEndpoint注解实现
八.群聊私聊案例
九.文字和图片消息的处理
一.概念
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 连接发生错误时触发。可以通过此事件处理连接错误,例如连接失败或消息发送失败等。
五.js中建立ws链接
在JavaScript中建立WebSocket连接可以使用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();
六.springboot中进行ws连接
1.首先,添加WebSocket的依赖到你的Spring Boot项目中。
在pom.xml
文件中,加入以下依赖:
<dependencies>
<!-- ... 其他依赖 ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
2.接下来,创建一个WebSocket处理器
,用于处理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连接关闭时,会打印一条消息。
3.最后,创建一个配置类,注册该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注解实现
以下是使用@ServerEndpoint
注解实现WebSocket的四大事件的示例代码:
@ServerEndpoint("/websocket")
public class MyWebSocketEndpoint {
private static Set<Session> 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<Session> 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();
}
}