youyichannel

志于道,据于德,依于仁,游于艺!

0%

WebSocket实战

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);
// 建立websocket连接
const websocket = new WebSocket("ws://localhost:8080/ws/" + clientId);

// ----- WebSocket API -----

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;

/**
* @author codejuzi
*/
@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;

/**
* @author codejuzi
*/
@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;

/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@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;

/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
webSocketServer.sendToAllClient("From Server:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
}
}