全双工通信协议:WebSocket

全双工通信协议:WebSockets

  • 前言
  • 何时使用WebSockets
  • WebSocket API
    • TextWebSocketHandler
    • WebSocketConfigurer
    • WebSocket握手
    • 配置服务器
    • 允许的来源
    • 心跳包
    • Java WebSocket API
    • 案例一:前端发送消息并接收后端响应
    • 案例二:模拟后端向前端推送消息
    • 案例三:发送指定用户消息
  • SockJS
    • Spring SockJS和前端SockJS区别
    • 启用SockJS
    • IE 8 and 9
    • 心跳
    • SockJS and CORS
    • SockJsClient
    • WebSocketMessageBrokerConfigurer
    • 使用SockJS
  • 关联文章

前言

WebSocket协议,RFC 6455提供了一种标准化的方法,通过单个TCP连接在客户端和服务器之间建立全双工双向通信通道。它是一种不同于HTTPTCP协议,但设计为在HTTP上工作,使用端口80443,并允许重用现有的防火墙规则。

WebSocket交互以使用HTTP请求开始Upgrade头进行升级,或者在这种情况下,切换到WebSocket协议。以下示例显示了这种交互:

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket # Upgrade标头
Connection: Upgrade # 	使用Upgrade链接
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080

支持WebSocket的服务器返回类似于以下内容的输出,而不是通常的200状态代码:

HTTP/1.1 101 Switching Protocols #协议开关
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp

成功握手后,HTTP升级请求下的TCP套接字对客户端和服务器保持开放,以继续发送和接收消息。

请注意,如果WebSocket服务器运行在web服务器(例如nginx)的后面,您可能需要对其进行配置,以便将WebSocket升级请求传递到WebSocket服务器。

尽管WebSocket被设计为HTTP兼容的,并且以HTTP请求开始,但是理解这两种协议导致非常不同的架构和应用程序编程模型是很重要的。

HTTPREST中,应用程序被构建为许多URL。为了与应用程序进行交互,客户端以请求-响应的方式访问这些URL。服务器根据HTTP URL、方法和头将请求路由到适当的处理程序。

相比之下,在WebSockets中,初始连接通常只有一个URL。随后,所有应用程序消息都在同一个TCP连接上流动。这指向了一个完全不同的异步、事件驱动的消息传递架构。

WebSocket也是一个低级传输协议,与HTTP不同,它没有为消息内容指定任何语义。这意味着除非客户端和服务器在消息语义上达成一致,否则无法路由或处理消息。

WebSocket客户端和服务器可以通过Sec-WebSocket-Protocol HTTP握手请求上的标头。在这种情况下,他们需要拿出自己的公约。

Springboot为例,pom依赖如下:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.4.0</version></dependency>

何时使用WebSockets

WebSockets可以使网页具有动态性和交互性。然而,在许多情况下,AJAXHTTP流或长轮询的组合可以提供简单有效的解决方案。

例如,新闻、邮件和社交feed需要动态更新,但是每隔几分钟更新一次也完全没问题。另一方面,协作、游戏和金融应用需要更接近实时。

延迟本身并不是决定性因素。如果消息量相对较低(例如,监控网络故障),HTTP流或轮询可以提供有效的解决方案。低延迟、高频率和高容量的结合是使用WebSocket的最佳案例。

WebSocket API

Spring框架提供了一个WebSocket API,您可以使用它来编写处理WebSocket消息的客户端和服务器端应用程序。

TextWebSocketHandler

TextWebSocketHandlerSpring 框架提供的一个用于处理 WebSocket 文本消息的抽象类。它是 WebSocketHandler 的子类,可以通过实现它来处理 WebSocket 中的文本消息。

public class MyHandler extends TextWebSocketHandler {@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 处理接收到的文本消息String payload = message.getPayload();System.out.println("接收到消息:" + payload);// 发送响应消息session.sendMessage(new TextMessage("Hello, client!"));}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// WebSocket 连接建立时执行的操作System.out.println("WebSocket 连接已建立");}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// WebSocket 连接关闭时执行的操作System.out.println("WebSocket 连接已关闭");}
}

WebSocketConfigurer

WebSocketConfigurerSpring 框架提供的一个接口,用于配置 WebSocket 相关的参数和处理器。比如:可以将前面的WebSocket处理程序映射到特定的URL,如下例所示:

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myHandler(),"/websocket");//注册了一个名为 myHandler() 的处理器,并将它映射到路径 /websocket 上。}@Beanpublic WebSocketHandler myHandler() {return new MyHandler();}
}

在上面的示例中,通过 @Configuration 注解将该类标记为配置类,并通过 @EnableWebSocket 注解启用 WebSocket 功能。我们注册了一个名为 myHandler() 的处理器,并将它映射到路径 /websocket 上。

WebSocket握手

定制初始HTTP WebSocket握手请求的最简单方法是通过HandshakeInterceptor,它公开了握手“之前”和“之后”的方法。

public interface HandshakeInterceptor {/*** request:当前的 HTTP 请求对象,可以获取请求的头信息等。* response:当前的 HTTP 响应对象,可以设置响应的头信息等。* wsHandler:用于处理 WebSocket 连接和消息的处理器。* attributes:用于保存一些自定义的属性,可以在后续的处理中使用。*/boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception;void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Exception exception);
}

您可以使用这样的拦截器来阻止握手或使WebSocketSession的任何属性可用。下面的例子使用内置拦截器将HTTP会话属性传递给WebSocket会话:

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myHandler(),"/websocket").addInterceptors(new HttpSessionHandshakeInterceptor());//添加拦截器}@Beanpublic WebSocketHandler myHandler() {return new MyHandler();}
}

当然,你也可以自定义实现拦截器,例如验证用户身份、设置 WebSocketSession 的属性等。

public class MyHandshakeInterceptor  implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {// 在握手前进行一些预处理操作System.out.println("Before handshake");// 验证用户身份等其他逻辑String username = serverHttpRequest.getHeaders().getFirst("username");if (username == null || username.isEmpty()) {System.out.println("Unauthorized access");return false; // 不允许握手继续进行}// 设置自定义属性,可以在后续处理中使用map.put("username", username);return true; // 允许握手继续进行}@Overridepublic void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {// 在握手后进行一些后续处理操作System.out.println("After handshake");if (e != null) {System.out.println("Handshake failed: " + e.getMessage());}}
}

配置服务器

您可以配置底层WebSocket服务器,例如输入消息缓冲区大小、空闲超时等。

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Beanpublic ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();container.setMaxBinaryMessageBufferSize(8192); // 设置二进制消息缓冲区大小container.setMaxTextMessageBufferSize(8192); // 设置文本消息缓冲区大小container.setAsyncSendTimeout(5000l); // 设置异步发送超时时间(毫秒)// 其他配置项...return container;}
}

允许的来源

Spring Framework 4.1.5开始,WebSocketSockJS的默认行为是只接受同源请求。也可以允许所有或指定的源列表。该检查主要是为浏览器客户端设计的。

您可以配置WebSocketSockJS允许的源,如下例所示:

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myHandler(),"/websocket").setAllowedOrigins("*");}
}

setAllowedOrigins("*")表示允许来自任何源的跨域请求连接到 WebSocket 服务器,当然你也可以设置指定域名或服务器。

心跳包

心跳机制是一种保持长连接有效性的机制,它通过定期发送小型的探测消息来检测连接是否仍然活动。这样可以避免代理服务器、网络设备或其他中间层错误地关闭空闲连接。
前端代码如下:

<!DOCTYPE html>
<html>
<head><title>WebSocket Example</title>
</head>
<body>
<h1>这是一个前台页面</h1>
<input type="text" id="inputMessage"/>
<button onclick="sendMessage()">Send</button>
<div id="messages"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"></script>
<script>var sock = null;var heartbeatInterval = 5000; // 心跳间隔时间,单位为毫秒var heartbeatTimer = null;function connect() {sock = new WebSocket('ws://localhost:8088/ws');sock.onopen = function () {console.log('Connected');startHeartbeat(); // 连接成功后开始发送心跳};sock.onmessage = function (message) {// 判断是否为心跳消息if (message.data === 'heartbeat') {console.log('收到心跳消息');} else {console.log('收到其他消息:' + message);}};sock.onclose = function () {console.log('Disconnected');stopHeartbeat(); // 连接关闭时停止发送心跳};}function startHeartbeat() {heartbeatTimer = setInterval(function () {sock.send('heartbeat'); // 发送心跳消息}, heartbeatInterval);}function stopHeartbeat() {clearInterval(heartbeatTimer);}function showMessage(message) {// 处理收到的消息console.log(message);}function sendMessage() {var inputMessage = document.getElementById('inputMessage').value;sock.send(JSON.stringify({'content': inputMessage}));}connect();
</script>
</body>
</html>

上述代码,连接成功后,通过调用startHeartbeat()方法轮询每5秒,发送心跳包heartbeat字符串;后端收到心跳消息响应字符串,通过sock.onmessage监听消息,若连接关闭后停止发送心跳。

后端示例代码如下:

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myWebSocketHandler(),"/ws");}@Beanpublic MyWebSocketHandler myWebSocketHandler() {return new MyWebSocketHandler();}
}
public class MyWebSocketHandler extends TextWebSocketHandler {private static final String HEARTBEAT_MESSAGE = "heartbeat";@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String payload = message.getPayload();System.out.println("接收到消息:" + payload);if (HEARTBEAT_MESSAGE.equals(payload)) {// 响应心跳消息session.sendMessage(new TextMessage("heartbeat"));return;}// 处理接收到的文本消息}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// WebSocket 连接建立时执行的操作System.out.println("WebSocket 连接已建立");}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// WebSocket 连接关闭时执行的操作System.out.println("WebSocket 连接已关闭");}}

上述代码,在handleTextMessage()方法中,接收前端发送的消息,判断为心跳包,并返回响应字符串heartbeat,表示已经收到。若有其他消息另行处理。

效果展示:
在这里插入图片描述

Java WebSocket API

javax.websocket包是Java WebSocket API的一部分,提供了一组接口和注解,用于开发WebSocket客户端和服务器。

其中一些主要的类和接口包括:

  • javax.websocket.Session:代表WebSocket会话,提供发送和接收消息的方法。
  • javax.websocket.EndpointWebSocket端点,可继承该类来创建自定义的WebSocket服务器端点。
  • javax.websocket.ClientEndpoint:用于创建WebSocket客户端端点。
  • javax.websocket.server.ServerEndpoint:用于创建WebSocket服务器端点。
  • javax.websocket.OnMessagejavax.websocket.OnOpenjavax.websocket.OnClose等注解:用于标注在WebSocket端点中的方法,以处理消息、开启连接和关闭连接等事件。

示例代码:

@ClientEndpoint
public class MyEndpoint {@OnOpenpublic void onOpen(Session session) {System.out.println("WebSocket opened: " + session.getId());}@OnMessagepublic void onMessage(String message, Session session) {System.out.println("Received message: " + message + " from " + session.getId());try {session.getBasicRemote().sendText("Hello, " + message + "!");} catch (IOException ex) {ex.printStackTrace();}}@OnClosepublic void onClose(Session session) {System.out.println("WebSocket closed: " + session.getId());}public static void main(String[] args) throws Exception {String serverUri = "ws://localhost:8088/ws";ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();WebSocketContainer container = ContainerProvider.getWebSocketContainer();MyEndpoint client = new MyEndpoint();Session session = container.connectToServer(client, URI.create(serverUri));session.close(); // 关闭会话}}

@ClientEndpoint@ServerEndpoint("/websocket")是Java WebSocket API中的注解,它们分别表示WebSocket客户端端点和WebSocket服务器端点。

案例一:前端发送消息并接收后端响应

示例代码如下:

@Controller
public class MyController {@GetMapping(value = "/websocket")public ModelAndView websocket(HttpServletResponse response) throws Exception {ModelAndView modelAndView = new ModelAndView("websocket");return modelAndView;}
}
@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myWebSocketHandler(),"/ws").addInterceptors(new MyHandshakeInterceptor());}@Beanpublic WebSocketHandler myWebSocketHandler() {return new MyWebSocketHandler();}@Beanpublic ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();container.setMaxBinaryMessageBufferSize(8192); // 设置二进制消息缓冲区大小container.setMaxTextMessageBufferSize(8192); // 设置文本消息缓冲区大小container.setAsyncSendTimeout(5000l); // 设置异步发送超时时间(毫秒)// 其他配置项...return container;}
}
public class MyHandshakeInterceptor  implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {// 在握手前进行一些预处理操作System.out.println("Before handshake");// 验证用户身份等其他逻辑return true; // 允许握手继续进行}@Overridepublic void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {// 在握手后进行一些后续处理操作System.out.println("After handshake");}
}
public class MyWebSocketHandler extends TextWebSocketHandler {private static final String HEARTBEAT_MESSAGE = "heartbeat";@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String payload = message.getPayload();System.out.println("接收到消息:" + payload);if (HEARTBEAT_MESSAGE.equals(payload)) {// 响应心跳消息session.sendMessage(new TextMessage("heartbeat"));return;}// 发送响应消息session.sendMessage(new TextMessage("Hello, client!"));}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// WebSocket 连接建立时执行的操作System.out.println("WebSocket 连接已建立");}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// WebSocket 连接关闭时执行的操作System.out.println("WebSocket 连接已关闭");}
}

前端htmlsocket.onmessage 获取消息,代码如下:

<!DOCTYPE html>
<html>
<head><title>WebSocket Example</title>
</head>
<body>
<input type="text" id="messageInput" placeholder="Type a message...">
<button onclick="sendMessage()">Send</button>
<div id="messages"></div><script>var socket = null;var heartbeatInterval = 5000; // 心跳间隔时间,单位为毫秒var heartbeatTimer = null;function connect() {socket = new WebSocket('ws://localhost:8088/ws');socket.onopen = function () {console.log('WebSocket连接已建立');startHeartbeat(); // 连接成功后开始发送心跳};socket.onmessage = function (message) {// 判断是否为心跳消息if (message.data === 'heartbeat') {console.log('收到心跳消息');} else {console.log('收到其他消息:' + message);var message = message.data;document.getElementById("messages").innerHTML += "<p>" + message + "</p>";}};socket.onclose = function () {console.log('WebSocket连接已关闭');stopHeartbeat(); // 连接关闭时停止发送心跳};}function startHeartbeat() {heartbeatTimer = setInterval(function () {socket.send('heartbeat'); // 发送心跳消息}, heartbeatInterval);}function stopHeartbeat() {clearInterval(heartbeatTimer);}function sendMessage() {var message = document.getElementById("messageInput").value;socket.send(message);}connect();
</script>
</body>
</html>

效果展示:
在这里插入图片描述

案例二:模拟后端向前端推送消息

创建List<WebSocketSession>,将每个连接的对象存入集合,调用pushMessageToAllClients()方法,遍历所有WebSocketSession对象,发送消息,示例代码如下:

public class MyWebSocketHandler extends TextWebSocketHandler {private List<WebSocketSession> sessions = new ArrayList<>();private static final String HEARTBEAT_MESSAGE = "heartbeat";@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String payload = message.getPayload();System.out.println("接收到消息:" + payload);if (HEARTBEAT_MESSAGE.equals(payload)) {// 响应心跳消息session.sendMessage(new TextMessage("heartbeat"));return;}// 发送响应消息session.sendMessage(new TextMessage("Hello, client!"));}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// WebSocket 连接建立时执行的操作System.out.println("WebSocket 连接已建立");sessions.add(session);}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// WebSocket 连接关闭时执行的操作System.out.println("WebSocket 连接已关闭");sessions.remove(session);}public void pushMessageToAllClients(String message) {for (WebSocketSession session : sessions) {System.out.println(session);try {session.sendMessage(new TextMessage(message));} catch (IOException e) {e.printStackTrace();}}}
}
@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myWebSocketHandler(),"/ws").addInterceptors(new MyHandshakeInterceptor());}@Beanpublic MyWebSocketHandler myWebSocketHandler() {return new MyWebSocketHandler();}
}
public class MyHandshakeInterceptor  implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {// 在握手前进行一些预处理操作System.out.println("Before handshake");// 验证用户身份等其他逻辑return true; // 允许握手继续进行}@Overridepublic void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {// 在握手后进行一些后续处理操作System.out.println("After handshake");}
}

后台请求接口,调用sendMessage()方法,代码如下:

@Controller
public class MyController {@Autowiredprivate MyWebSocketHandler myWebSocketHandler;@GetMapping(value = "/back")public ModelAndView back(HttpServletResponse response) throws Exception {ModelAndView modelAndView = new ModelAndView("back");return modelAndView;}@GetMapping(value = "/websocket")public ModelAndView websocket(HttpServletResponse response) throws Exception {ModelAndView modelAndView = new ModelAndView("websocket");return modelAndView;}@PostMapping(value = "/sendMessage")@ResponseBodypublic void sendMessage(String message) {try {myWebSocketHandler.pushMessageToAllClients(message);}catch (Exception e){e.printStackTrace();}}
}

前台htmlsocket.onmessage 获取消息,代码如下:

<!DOCTYPE html>
<html>
<head><title>WebSocket Example</title>
</head>
<body>
<h1>这是一个前台页面</h1>
<div id="messages"></div><script>var socket = null;var heartbeatInterval = 5000; // 心跳间隔时间,单位为毫秒var heartbeatTimer = null;function connect() {socket = new WebSocket('ws://localhost:8088/ws');socket.onopen = function () {console.log('WebSocket连接已建立');startHeartbeat(); // 连接成功后开始发送心跳};socket.onmessage = function (message) {// 判断是否为心跳消息if (message.data === 'heartbeat') {console.log('收到心跳消息');} else {console.log('收到其他消息:' + message);var message = message.data;document.getElementById("messages").innerHTML += "<p>" + message + "</p>";}};socket.onclose = function () {console.log('WebSocket连接已关闭');stopHeartbeat(); // 连接关闭时停止发送心跳};}function startHeartbeat() {heartbeatTimer = setInterval(function () {socket.send('heartbeat'); // 发送心跳消息}, heartbeatInterval);}function stopHeartbeat() {clearInterval(heartbeatTimer);}connect();
</script>
</body>
</html>

后台html,发送请求,代码如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>这是一个后台页面</h1>
<input type="text" id="textInput" placeholder="Enter text...">
<button onclick="submitText()">Submit</button><script>function submitText() {var text = document.getElementById("textInput").value;console.log(text)// 创建一个新的XMLHttpRequest对象var xhr = new XMLHttpRequest();// 指定请求的方法、URL和是否异步处理xhr.open("POST", "/sendMessage");xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");// 注册回调函数来处理响应xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {// 处理成功响应console.log("Text submitted successfully!");}};// 发送请求xhr.send("message="+text);}
</script>
</body>
</html>

效果展示:

在这里插入图片描述

案例三:发送指定用户消息

模拟多个用户,选择聊天对象。创建Map对象(key=sessionId,value=WebSocketSession),用户发送消息时通过sessionId获取WebSocketSession对象,然后调用sendMessage()方法,完成发送。示例代码如下:

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myWebSocketHandler(),"/ws").addInterceptors(new MyHandshakeInterceptor());}@Beanpublic MyWebSocketHandler myWebSocketHandler() {return new MyWebSocketHandler();}
}
public class MyWebSocketHandler extends TextWebSocketHandler {private Map<String,WebSocketSession> sessions = new HashMap<>();private static final String HEARTBEAT_MESSAGE = "heartbeat";public List<String> getAllUserId() {return sessions.keySet().stream().collect(Collectors.toList());}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 处理接收到的文本消息String payload = message.getPayload();System.out.println("接收到消息:" + payload);if (HEARTBEAT_MESSAGE.equals(payload)) {// 响应心跳消息session.sendMessage(new TextMessage("heartbeat"));return;}//转换json对象JSONObject parse = JSONObject.parse(payload);String userId = parse.getString("userId");String content = parse.getString("message");// 获取发送的对象WebSocketSession webSocketSession = sessions.get(userId);try {//发送消息webSocketSession.sendMessage(new TextMessage(content));} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// WebSocket 连接建立时执行的操作System.out.println("WebSocket 连接已建立");sessions.put(session.getId(),session);}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// WebSocket 连接关闭时执行的操作System.out.println("WebSocket 连接已关闭");sessions.remove(session.getId());}
}

后台请求接口,getAllUserId()方法获取用户列表,sendMessage()方法发送消息,示例代码如下:

@Controller
public class MyController {@Autowiredprivate MyWebSocketHandler myWebSocketHandler;@GetMapping(value = "/getAllUserId")@ResponseBodypublic ResponseEntity<List> getAllUserId() {List<String> allUserId = myWebSocketHandler.getAllUserId();return ResponseEntity.ok(allUserId);}@GetMapping(value = "/chat")@ResponseBodypublic ModelAndView chat(HttpServletResponse response) throws Exception {ModelAndView modelAndView = new ModelAndView("chat");return modelAndView;}
}

前端html,分为四个部分,一部分发送心跳包,一部分提交消息,一部分通过轮询查询用户列表,一部分通过socket.onmessage 获取消息,代码如下:

<!DOCTYPE html>
<html>
<head><title>WebSocket Example</title>
</head>
<body>
<h1>聊天页面</h1>
<input type="text" id="userId" placeholder="用户id">
<input type="text" id="message" placeholder="消息内容">
<button onclick="sendMessage()">Send</button>
<p>用户列表:
<div id="userIds"></div>
</p>
<p>接受消息:
<div id="content"></div>
</p>
<script>var socket = null;var heartbeatInterval = 5000; // 心跳间隔时间,单位为毫秒var heartbeatTimer = null;function connect() {socket = new WebSocket('ws://localhost:8088/ws');socket.onopen = function () {console.log('WebSocket连接已建立');startHeartbeat(); // 连接成功后开始发送心跳};socket.onmessage = function (message) {// 判断是否为心跳消息if (message.data === 'heartbeat') {console.log('收到心跳消息');} else {console.log('收到其他消息:' + message);var message = message.data;document.getElementById("content").innerHTML += "<p>" + message + "</p>";}};socket.onclose = function () {console.log('WebSocket连接已关闭');stopHeartbeat(); // 连接关闭时停止发送心跳};}function startHeartbeat() {heartbeatTimer = setInterval(function () {socket.send('heartbeat'); // 发送心跳消息}, heartbeatInterval);}function stopHeartbeat() {clearInterval(heartbeatTimer);}//发送消息function sendMessage() {var userId = document.getElementById("userId").value;var message = document.getElementById("message").value;//转换json字符串socket.send(JSON.stringify({"userId":userId,"message":message}));}//查询用户列表function pollForNewMessages() {setInterval(function() {// 发送 Ajax 请求var xhr = new XMLHttpRequest();xhr.open("GET", "getAllUserId", true);xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {// 处理服务器返回的新消息var response = JSON.parse(xhr.responseText);document.getElementById("userIds").innerHTML = "";for (var i = 0; i < response.length; i++) {var message = response[i];document.getElementById("userIds").innerHTML += "<p>" + message + "</p>";}}};xhr.send();}, 5000); // 每隔5秒发送一次请求}//连接connect();// 启动轮询pollForNewMessages();
</script>
</body>
</html>

效果展示:

在这里插入图片描述

SockJS

SockJS是一个浏览器JavaScript库,它提供了一个类似WebSocket的对象。SockJS的目标是用于实现 WebSocket 的兼容性解决方案。

SockJS是为浏览器设计的。它使用各种技术来支持各种浏览器版本。传输分为三大类:WebSocketHTTP流和HTTP长轮询。

SockJS客户端首先发送GET /info从服务器获取基本信息。之后,它必须决定使用哪种传输方式。如果可能,使用WebSocket。如果没有,在大多数浏览器中,至少有一个HTTP流选项。如果不是,则使用HTTP(长)轮询。

Spring SockJS和前端SockJS区别

Spring SockJS 和前端 SockJS 是两个不同的概念。

  • Spring SockJS

Spring SockJSSpring 框架提供的一个 WebSocket 子协议,用于在服务器端实现 WebSocket 功能。它通过 WebSocketHTTP 长轮询等技术来实现双向通信,支持与各种类型的客户端进行通信,包括 Web 浏览器、移动应用等。在服务器端,你可以使用 Spring SockJS 提供的 API 来创建和管理 WebSocket 连接,并通过编写 WebSocket 处理器来处理客户端发送来的消息。

  • 前端 SockJS

前端 SockJS是一个 JavaScript 库,用于在 Web 浏览器中实现 WebSocket 功能。它提供了一个与 WebSocket API 类似的接口,但使用了一种称为“轮询”的技术来模拟 WebSocket 的行为,以达到在不支持 WebSocket 的浏览器中也能实现实时通信的目的。在前端代码中,你可以通过引入 sockjs 库来创建 WebSocket 连接,并通过监听 onmessage 事件来接收服务器发送来的消息。

总之,Spring SockJS 和前端 SockJS 都是用于实现 WebSocket 功能的技术,但分别运行在服务器端和客户端,提供了不同的 API 和接口来实现双向通信。Spring SockJS 在服务器端提供了更多的功能和灵活性,而前端 sockjs 则提供了跨浏览器的兼容性和易用性。

启用SockJS

您可以通过Java配置来启用SockJS,如下例所示:

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myHandler(),"/sockjs").withSockJS();}
}

如果不调用withSockJS()方法,客户端使用SockJS访问报404,示例代码如下:

<!DOCTYPE html>
<html>
<head><title>WebSocket Example</title>
</head>
<body>
<h1>这是一个前台页面</h1>
<input type="text" id="inputMessage" />
<button onclick="sendMessage()">Send</button>
<div id="messages"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"></script>
<script>var sock = null;var heartbeatInterval = 5000; // 心跳间隔时间,单位为毫秒var heartbeatTimer = null;function connect() {sock = new SockJS('http://localhost:8088/sockjs');sock.onopen = function () {console.log('Connected');};sock.onmessage = function (message) {showMessage(message);};sock.onclose = function () {console.log('Disconnected');};}function showMessage(message) {// 处理收到的消息console.log(message);}function sendMessage() {var inputMessage = document.getElementById('inputMessage').value;sock.send(JSON.stringify({ 'content': inputMessage }));}connect();
</script>
</body>
</html>

如图所示:

在这里插入图片描述

IE 8 and 9

SockJS客户端通过使用微软的XDomainRequestie8ie9中支持Ajax/XHR流。它可以跨域工作,但不支持发送cookiecookie对于Java应用程序通常是必不可少的。但是,由于SockJS客户机可以与许多服务器类型(不仅仅是Java服务器)一起使用,因此它需要知道cookie是否重要。如果是这样,SockJS客户端更倾向于使用Ajax/XHR进行流处理。否则,它依赖于基于框架的技术。

默认情况下,Spring SockJS 会从 CDN 地址加载 SockJS 客户端。为了避免在跨域情况下出现问题,你可以将 SockJS 客户端库配置为从与应用程序相同的源加载。例如,如果你的应用程序位于 http://localhost:8080/,那么你可以将 SockJS 客户端库的 URL 配置为 http://localhost:8080/myapp/js/sockjs-client.js

下面的例子展示了如何在Java配置中这样做:

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myWebSocketHandler(), "/sockjs").withSockJS().setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");}
}

调用setClientLibraryUrl()方法设置 SockJS 客户端库的 URL

心跳

SockJS协议要求服务器发送心跳消息,以防止代理断定连接被挂起(在 SockJS 中,通常是服务器负责发送心跳消息来保持连接的活跃状态,而客户端不需要显式地发送心跳。)。Spring SockJS配置有一个名为heartbeatTime可以用来定制频率。默认情况下,心跳在25秒后发送,假设该连接上没有发送其他消息。

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myHandler(),"/sockjs").withSockJS().setHeartbeatTime(5000);}
}

上述代码中,调用setHeartbeatTime()方法设置心跳时间间隔为 5 秒,如果超过了指定的时间间隔而没有收到心跳信号,SockJS 通常会假定连接已断开或丢失(本地测试不会有这种情况)。具体行为可能因使用的库或框架而异,但通常会触发一些事件或采取适当的措施,如重新连接。

效果展示
在这里插入图片描述

当使用STOMP over WebSocketSockJS时,如果STOMP客户端和服务器协商要交换的心跳,则SockJS心跳被禁用。

HTTP流和HTTP长轮询SockJS传输要求连接保持比通常更长的开放时间。一个特定的问题是Servlet API不为已经离开的客户端提供通知。

Spring尽最大努力识别这种表示客户端断开(特定于每个服务器)的网络故障,并通过使用专用日志类别DISCONNECTED_CLIENT_LOG_CATEGORY(在AbstractSockJsSession中定义)记录最小的消息。如果需要查看堆栈跟踪,可以将该日志类别设置为TRACE

SockJS and CORS

如果允许跨域请求,SockJS协议将在XHR流和轮询传输中使用CORS进行跨域支持。因此,除非检测到响应中存在CORS标头,否则将自动添加CORS标头。因此,如果应用程序已经配置为提供CORS支持(例如,通过Servlet过滤器),SpringSockJsService将跳过这一部分。

SockJS期望以下头和值:

  • Access-Control-Allow-Origin:从Origin请求头的值初始化,比如:http://example.com
  • Access-Control-Allow-Credentials:始终设置为true
  • Access-Control-Request-Headers:根据等价请求头的值初始化。
  • Access-Control-Allow-Methods:传输支持的HTTP方法(参见TransportType enum)。
  • Access-Control-Max-Age:设置为31536000(1年)。

如果CORS配置允许,考虑排除带有SockJS端点前缀的URL,从而让SpringSockJsService处理它。

SockJsClient

Spring提供了一个SockJS Java客户端,可以在不使用浏览器的情况下连接到远程SockJS端点。当需要在公共网络上的两个服务器之间进行双向通信时(也就是说,在网络代理可以排除使用WebSocket协议的情况下),这尤其有用。SockJS Java客户端对于测试目的也非常有用(例如,模拟大量并发用户)。

以下示例显示了如何创建SockJS客户端并连接到SockJS端点:

//底层 WebSocket 客户端
WebSocketClient webSocketClient = new StandardWebSocketClient();
List<Transport> transports = new ArrayList<>(1);
transports.add(new WebSocketTransport(webSocketClient));
//实例
SockJsClient sockJsClient = new SockJsClient(transports);
// STOMP 协议的 WebSocket 客户端
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);String url = "http://localhost:8080/portfolio";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
//建立与服务器的连接,并发送一条消息。
StompSession stompSession = stompClient.connect(url, sessionHandler).get();
stompSession.send("/message", "Hello, server!");

SockJS对消息使用JSON格式的数组。默认情况下,使用Jackson 2,并且需要在类路径中。或者,您可以配置的自定义实现SockJsMessageCodec并在上配置它SockJsClient.

以下示例显示了您也应该考虑自定义的服务器端SockJS相关属性:

@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/sockjs").withSockJS()//设置 SockJS 流模式下的缓存大小限制,默认是 128 KB。.setStreamBytesLimit(512 * 1024) //设置 HTTP 传输模式下消息缓存的大小限制,默认是 100 条消息。.setHttpMessageCacheSize(1000) //设置 SockJS 断开连接的延迟时间,默认是 5 秒。.setDisconnectDelay(30 * 1000); }
}

WebSocketMessageBrokerConfigurer

WebSocketMessageBrokerConfigurer也是Spring框架的接口,用于配置基于消息代理的 WebSocket。通过实现该接口,你可以配置消息代理、消息端点、消息转发等功能,以实现复杂的 WebSocket 消息传递和处理逻辑,同样也支持Websocket代码中的配置信息。

重载registerStompEndpoints()方法,建立 WebSocket 连接,等设置。示例如下:

  • 注册端点

示例代码如下:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfigurer implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/sockjs");//注册端点}
}
  • 设置允许来源

示例代码如下:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfigurer implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/sockjs")//注册端点.setAllowedOrigins("*");//设置来源}
}
  • 启用SockJS

示例代码如下:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfigurer implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/sockjs")//注册端点.withSockJS();//启用SockJS}
}
  • 设置Spring SockJS资源地址

示例代码如下:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfigurer implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/sockjs")//注册端点.withSockJS() .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js")//Spring SockJS资源地址;}
}
  • 设置心跳时长

示例代码如下:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfigurer implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/sockjs")//注册端点.withSockJS() .setHeartbeatTime(5000)//设置心跳时长;}
}

重载configureMessageBroker()方法配置消息代理,在这里你可以设置消息代理的前缀、订阅地址等。示例如下:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfigurer implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {// 配置消息代理registry.enableSimpleBroker("/topic"); //指定了消息代理的目标前缀,即服务器会将以“/topic”和开头的消息发送到所有订阅了对应前缀的客户端。registry.setApplicationDestinationPrefixes("/app");//指定了消息发送的前缀,即发送的消息会以“/app”开头}
}

这一章内容主要以WebSocketConfigurer 配置为主,后续更多的用法在下一章节内容讲解。

使用SockJS

前面介绍了WebSocket的案例,我们以案例一为例,将代码替换为SockJS,分两步走:

  • 第一步

服务端启动SockJS,调用withSockJS()方法。

  • 第二步

引入SockJS库,然后把 new WebSocket(url); 替换成 new SockJS(url);

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"></script>

完整示例代码如下:

@Controller
public class MyController {@GetMapping(value = "/websocket")@ResponseBodypublic ModelAndView websocket(HttpServletResponse response) throws Exception {ModelAndView modelAndView = new ModelAndView("websocket");return modelAndView;}
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(myWebSocketHandler(), "/sockjs").withSockJS()//启用SockJS.setHeartbeatTime(5000);}@Beanpublic MyWebSocketHandler myWebSocketHandler() {return new MyWebSocketHandler();}
}
public class MyWebSocketHandler extends TextWebSocketHandler {@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String payload = message.getPayload();System.out.println("接收到消息:" + payload);// 发送响应消息session.sendMessage(new TextMessage("Hello, client!"));}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// WebSocket 连接建立时执行的操作System.out.println("WebSocket 连接已建立");}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// WebSocket 连接关闭时执行的操作System.out.println("WebSocket 连接已关闭");}
}

前端代码如下:

<!DOCTYPE html>
<html>
<head><title>WebSocket Example</title>
</head>
<body>
<input type="text" id="messageInput" placeholder="Type a message...">
<button onclick="sendMessage()">Send</button>
<div id="messages"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"></script>
<script>var socket = null;function connect() {socket = new SockJS('http://localhost:8088/sockjs');socket.onopen = function () {console.log('WebSocket连接已建立');};socket.onmessage = function (message) {// 判断是否为心跳消息console.log('收到其他消息:' + message);var message = message.data;document.getElementById("messages").innerHTML += "<p>" + message + "</p>";};socket.onclose = function () {console.log('WebSocket连接已关闭');};}function sendMessage() {var message = document.getElementById("messageInput").value;socket.send(message);}connect();
</script>
</body>
</html>

效果展示:

在这里插入图片描述

握手成功后,服务器每5秒负责发送心跳消息来保持连接的活跃状态,当遇到不支持WebSocket的情况时,SockJS会尝试使用其他的方案来连接。

关联文章

全双工通信协议:WebSockets+STOMP

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/643261.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell 虚拟机基线配置脚本示例

这是一个配置虚拟机基线的示例&#xff0c;包含关闭防火墙、禁用SElinux、设置时区、安装基础软件等。 这只是一个简单的模板&#xff0c;基线配置方面有很多&#xff0c;后续可以按照这个模板去逐步添加 代码示例 [rootbogon ~]# cat bastic.sh #!/bin/bashRED\E[1;31m GRE…

【C++】C++入门(一)

个人主页 &#xff1a; zxctsclrjjjcph 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. C关键字3. 命名空间3.1 命名空间定义3.2 命名空间的使用 4. C输入&输出 1. 前言 C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0…

【产品交互】超全面B端设计规范总结

不知不觉已经深耕在B端这个领域3年有余&#xff0c;很多人接触过B端后会觉得乏味&#xff0c;因为B端的设计在视觉上并没有C端那么有冲击力&#xff0c;更多的是结合业务逻辑&#xff0c;设计出符合业务需求的交互&#xff0c;以及界面排版的合理性&#xff0c;达到产品的可用性…

助力工业焊缝质量检测,YOLOv7【tiny/l/x】不同系列参数模型开发构建工业焊接场景下工件表面焊接缺陷检测识别分析系统

焊接是一个不陌生但是对于开发来说相对小众的场景&#xff0c;在工件表面焊接场景下常常有对工件表面缺陷智能自动化检测识别的需求&#xff0c;工业AI结合落地是一个比较有潜力的场景&#xff0c;在我们前面的博文开发实践中也有一些相关的实践&#xff0c;感兴趣的话可以自行…

菜鸡后端的前端学习记录

前言 记录一下看视频学习前端的的一些笔记&#xff0c;以前对Html、Js、CSS有一定的基础&#xff08;都认得&#xff0c;没用过&#xff09;&#xff0c;现在不想从头再来了&#xff0c;学学Vue框架&#xff0c;不定时更新&#xff0c;指不定什么时候就鸽了。。。。 Vue2 01…

SQL Server多数据表之间的数据查询和分组查询

文章目录 一、多数据表之间的数据查询1.1内连接查询&#xff08;Inner join&#xff09;1.2 左外连接 (LEFT JOIN):1.3右外连接 (RIGHT JOIN):1.4. 全外连接 (FULL OUTER JOIN):1.5 交叉连接 (CROSS JOIN):1.6 自连接 (SELF JOIN):1.7 子查询: 二、分组查询2.1 分组查询2.2 查询…

静态分析C语言生成函数调用关系的利器——cflow(二)

大纲 环境准备选择项目分析代码简单分析高级分析坑&#xff1a;不能显示main函数所有调用函数的调用栈坑2&#xff1a;重定义错误坑3&#xff1a;缺失编译时产生的文件坑4&#xff1a;缺失工程的头文件包含路径指定坑5&#xff1a;操作系统的坑只存在于windows操作系统上的文件…

rabbitmq基础-java-1、快速入门

1、AMQP AMQP&#xff0c;即Advanced Message Queuing Protocol&#xff08;高级消息队列协议&#xff09;&#xff0c;一个提供统一消息服务的应用层标准高级消息队列协议&#xff0c;是应用层协议的一个开放标准&#xff0c;为面向消息的中间件设计&#xff0c;基于此协议的客…

Parallels Desktop 19 mac 虚拟机软件 兼容M1 M2

Parallels Desktop 19 for Mac 是一款适用于 macOS 的虚拟机软件。无需重启即可在 Mac 上运行 Windows、Linux 等系统&#xff0c;具有速度快、操作简单且功能强大的优点。包括 30 余种实用工具&#xff0c;可简化 Mac 和 Windows 上的日常任务。 软件下载&#xff1a;Parallel…

Linux目录结构:深入理解与命令创建指南

目录 摘要&#xff1a; 一.linux目录介绍 1.目录结果设置标准 2.目录结构介绍 二.linux命令 1.常见命令 # 与 $ 提示的区别 ifconfig查看ip地址 su 命令格式 cd 目录查看 查看文件内容 创建目录及文件 复制和移动 tar find chmod 2. vim一般使用 摘要&#xff1a; 前…

基于中文垃圾短信数据集的经典文本分类算法实现

垃圾短信的泛滥给人们的日常生活带来了严重干扰&#xff0c;其中诈骗短信更是威胁到人们的信息与财产安全。因此&#xff0c;研究如何构建一种自动拦截过滤垃圾短信的机制有较强的实际应用价值。本文基于中文垃圾短信数据集&#xff0c;分别对比了朴素贝叶斯、逻辑回归、随机森…

CentOS使用

1.使用SSH连接操作虚拟机中的CentOS 1.1 配置静态IP 想要使用ssh连接就需要获取虚拟机的IP&#xff0c;但若DHCP&#xff0c;则每次连接都要确定虚拟机的IP是否变化&#xff0c;故直接分配一个静态IP vmware中&#xff0c;编辑–虚拟网络编辑器&#xff0c;记住下方的子网掩…

windows和linux下SHA1,MD5,SHA256校验办法

今天更新android studio到Android Studio Hedgehog | 2023.1.1时&#xff0c;发现提示本机安装的git版本太老&#xff0c;于是从git官网下载最新的git。 git下载地址&#xff1a; https://git-scm.com/ 从官网点击下载最新windows版本会跳转到github仓库来下载发布的git&…

【趣味CSS3.0】粘性定位属性Position:sticky是不是真的没用了?

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起学习和进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&a…

sublime text 开启vim模式

sublime text 开启vim模式 打开配置文件 mac下点击菜单栏 Sublime Text -> Settings... -> Settings 修改配置文件并保存 添加配置 // 开启vim模式 "ignored_packages": [// "Vintage", ], // 以命令模式打开文件 "vintage_start_in_comman…

视频监控平台EasyCVR增加fMP4流媒体视频格式及其应用场景介绍

近期我们在视频监控管理平台EasyCVR系统中新增了HTTP-FMP4播放协议&#xff0c;今天我们就来聊聊该协议的特点和应用。 fMP4&#xff08;Fragmented MPEG-4&#xff09;是基于MPEG-4 Part 12的流媒体格式&#xff0c;是流媒体的一项重要技术&#xff0c;因为它能通过互联网传送…

【GitHub项目推荐--12 年历史的 PDF 工具开源了】【转载】

最近在整理 PDF 的时候&#xff0c;有一些需求普通的 PDF 编辑器没办法满足&#xff0c;比如 PDF 批量合并、编辑等。 于是&#xff0c;我就去 GitHub 上看一看有没有现成的轮子&#xff0c;发现了这个 PDF 神器「PDF 补丁丁」&#xff0c;让人惊讶的是这个 PDF 神器有 12 年的…

RabbitMQ进阶篇【理解➕应用】

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于RabbitMQ的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 一.什么是交换机 1.概念释义 2.例…

【数据分析】matplotlib、numpy、pandas速通

教程链接&#xff1a;【python教程】数据分析——numpy、pandas、matplotlib 资料&#xff1a;https://github.com/TheisTrue/DataAnalysis 1 matplotlib 官网链接&#xff1a;可查询各种图的使用及代码 对比常用统计图 1.1 折线图 &#xff08;1&#xff09;引入 from …

51单片机LCD1602调试工具

参考视频&#xff1a;江协科技51单片机 LCD1602头文件代码 #ifndef __LCD1602_H__ #define __LCD1602_H__//用户调用函数&#xff1a; void LCD_Init(); void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char); void LCD_ShowString(unsigned char Line,un…