WebSocket实现浏览器和服务器的通信
WebSocket简介
WebSocket是基于TCP协议的一种新的网络协议。它实现了浏览器和服务器之间的全双工通信
——
浏览器和服务器只需要完成一次握手,二者之间就可以创建持久性的连接,并进行双向的数据传输。
HTTP VS WebSocket
区别:
- HTTP是短连接;WebSocket是长连接
- HTTP通信是单向的,基于请求响应模型;WebSocket支持双向通信
联系:
- HTTP和WebSocket底层都是基于TCP协议
思考:既然WebSocket支持双向通信,功能看似比HTTP强大,那么为什么不基于WebSocket开发所有的业务功能?
WebSocket的缺点
- 服务器长期维护长连接需要一定的成本
- 各个浏览器支持程度不一
- WebSocket是长连接,受网络限制比较大,需要处理好重连等措施
=>
WebSocket只适合在特定的场景下使用,比如视频弹幕、网络聊天、实况更新等
案例实战
需求:实现浏览器与服务器全双工通信。浏览器既可以向服务器发送消息,服务器也可主动向浏览器推送消息。
客户端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>WebSocket-Demo</title> </head> <body> <input type="text" id="text"/> <button onclick="send()">发送消息</button> <button onclick="closeConn()">关闭连接</button> <div id="msg"></div> </body> <script type="text/javascript" src="js/websocket-demo.js"></script> </html>
|
if (!"WebSocket" in window) { alert("Not Support WebSocket"); }
const clientId = Math.random().toString().substring(2);
const websocket = new WebSocket("ws://localhost:8080/ws/" + clientId);
websocket.onerror = function () { setMessageInnerHTML("error"); };
websocket.onopen = function () { setMessageInnerHTML("connect success"); };
websocket.onmessage = function (event) { setMessageInnerHTML(event.data); };
websocket.onclose = function () { setMessageInnerHTML("close"); };
websocket.onbeforeunload = function () { websocket.close(); };
const setMessageInnerHTML = (innerHtml) => { document.getElementById("msg").innerHTML += innerHtml + "<br>"; };
const send = () => { websocket.send(document.getElementById("text").value); document.getElementById("text").value = ""; };
const closeConn = () => { websocket.close(); };
|
服务端
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.12</version> <relativePath/> </parent>
<groupId>com.juzi</groupId> <artifactId>websocket-demo</artifactId> <version>1.0-SNAPSHOT</version>
<properties> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
</project>
|
application.yml
server: port: 8080 spring: application: name: websocket-demo
|
启动类:
package com.juzi.websocket;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.socket.config.annotation.EnableWebSocket;
@SpringBootApplication @EnableScheduling @EnableWebSocket public class WebSocketApplication { public static void main(String[] args) { SpringApplication.run(WebSocketApplication.class, args); } }
|
Server核心:
package com.juzi.websocket.server;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;
import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.util.HashMap; import java.util.Map;
@Component @ServerEndpoint("/ws/{sid}") public class WebSocketServer {
private static final Map<String, Session> sessionMap = new HashMap<>();
private final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
@OnOpen public void onOpen(Session session, @PathParam("sid") String sid) { logger.info("[onOpen]: sid = {}", sid); sessionMap.put(sid, session); }
@OnMessage public void onMessage(String message, @PathParam("sid") String sid) { logger.info("收到消息[onMessage]: sid = {}, message = {}", sid, message); }
@OnClose public void onClose(@PathParam("sid") String sid) { logger.info("[onClose]: sid = {}", sid); sessionMap.remove(sid); }
public void sendToAllClient(String message) { sessionMap.values().forEach(session -> { try { session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } }); }
}
|
package com.juzi.websocket.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration public class WebSocketConfiguration {
@Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }
}
|
定时任务类:
package com.juzi.websocket.task;
import com.juzi.websocket.server.WebSocketServer; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter;
@Component public class WebSocketTask { @Resource private WebSocketServer webSocketServer;
@Scheduled(cron = "0/5 * * * * ?") public void sendMessageToClient() { webSocketServer.sendToAllClient("From Server:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now())); } }
|