开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 24 天,点击查看活动详情
一、简介(什么是WebSocket)
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端。因此,在WebSocket中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。
二、数据实时推送的实现方式和应用场景
1.轮询:
客户端通过代码定时向服务器发送AJAX请求,服务器接收请求并返回响应信息。
优点:代码相对简单,适用于小型应用。
缺点:在服务器数据没有更新时,会造成请求重复数据,请求无用,浪费带宽和服务器资源。
2.长连接:
在页面中嵌入一个隐藏的iframe,将这个隐藏的iframe的属性设置为一个长连接的请求或者xrh请求,服务器通过这种方式往客户端输入数据。
优点:数据实时刷新,请求不会浪费,管理较简洁。
缺点:长时间维护保持一个长连接会增加服务器开销。
3.webSocket:
websocket是HTML5开始提供的一种客户端与服务器之间进行通讯的网络技术,通过这种方式可以实现客户端和服务器的长连接,双向实时通讯。
优点:减少资源消耗;实时推送不用等待客户端的请求;减少通信量;
缺点:少部分浏览器不支持,不同浏览器支持的程度和方式都不同。
应用场景:聊天室、智慧大屏、消息提醒、股票k线图监控等。
三、实现
导入websocket依赖
<!-- SpringBoot Websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
websocket配置类
@Configuration
@EnableWebSocket
public class WebSocketServerConfigure implements WebSocketConfigurer {
@Resource
private StringWebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/connect").withSockJS();
}
}
websocket连接处理器
@Slf4j
@Component
public class StringWebSocketHandler extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
WebSocketUtils.add(session);
log.info("与客户端建立连接");
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
session.close(CloseStatus.SERVER_ERROR);
log.error("连接异常", exception);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);
WebSocketUtils.remove(session);
log.error("与客户端断开连接");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 获取客户端发送过来的消息
String receiveMsg = message.getPayload();
log.info(receiveMsg);
}
}
websocket工具类
public class WebSocketUtils {
/**
* 定义存储Session的容器
*/
private final static CopyOnWriteArraySet<WebSocketSession> SESSION_SETS = new CopyOnWriteArraySet<>();
/**
* 添加会话
*/
public static void add(WebSocketSession socketSession) {
SESSION_SETS.add(socketSession);
}
/**
* 移除会话
*/
public static void remove(WebSocketSession socketSession) {
SESSION_SETS.remove(socketSession);
}
/**
* (群)发送消息
*/
public static void sendMessage(Object msg) {
TextMessage textMessage = new TextMessage(JacksonUtils.writeValueAsString(msg));
try {
for (WebSocketSession socketSession : SESSION_SETS) {
socketSession.sendMessage(textMessage);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
定时任务(为了给前端实时推送数据,使用了定时任务。)
@Component
@DisallowConcurrentExecution
@CronExp(id = 1, cron = "0/3 * * * * ?")
public class ItemJob implements Job {
@Autowired
private ItemService itemService;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
WebSocketUtils.sendMessage(itemService.toData());
}
}
实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
private String name;
private Integer num;
}
发送消息服务类
@Service
public class ItemService {
private static final ArrayList<Item> items = new ArrayList<>();
static {
items.add(new Item("Mon", 0));
items.add(new Item("Tue", 0));
items.add(new Item("Wed", 0));
items.add(new Item("Thu", 0));
items.add(new Item("Fri", 0));
items.add(new Item("Sat", 0));
items.add(new Item("Sun", 0));
items.add(new Item("sun", 0));
}
public Map<String, ?> toData() {
items.forEach(e -> e.setNum(e.getNum() + (int) (Math.random() * 5 + 1)));
HashMap<String, Object> map = new HashMap<>();
map.put("yAxisData", EntityUtils.toList(items, Item::getNum));
return map;
}
}
前端实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket客户端</title>
<script src="https://cdn.bootcss.com/sockjs-client/0.3.4/sockjs.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.1/echarts.min.js"></script>
</head>
<body>
<div id="main" style="width:600px;height:400px"></div>
<div id="radar" style="width:600px;height:400px;"></div>
<script type="text/javascript">
let ws = null;
var option;
let targetUri = "/connect";
window.onload = function () {
option1 = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
itemStyle: {
normal: {
label: {
show: true, //开启显示
position: 'top', //在上方显示
textStyle: { //数值样式
color: 'black',
fontSize: 16
}
}
}
}
}
]
};
ws = new SockJS(targetUri);
ws.onmessage = function (event) {
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var parse = JSON.parse(event.data);
console.log(parse)
option1.series[0].data = parse.yAxisData;
console.log(parse.yAxisData)
myChart.setOption(option1, true);
};
}
</script>
</body>
</html>