WebSocket
前言
问题:
在工作中遇到这样一个问题,前端页面要实时刷新杭州某一条街道的积水告警信息,该信息的变化会随着积水深度的变化而变化,积水深度的变化由降雨量决定,而降雨量是街道办事处调用我们的接口把数据传递给我们的,街道办事处什么时候传递给我们,我们的告警信息就需要进行刷新。问题就在于他们是不定时调用我们的接口。那么页面要刷新数据,就需要在他调用之后才进行刷新。而且是每次调用都要进行刷新。那么什么时候把数据发回给前端比较合适呢?
解决:
1 前端页面轮询,就是每隔一段时间请求一次后台获取最新数据。
? 优点:能获取到数据;
? 缺点:有时候后台数据可能没有变化,轮询会占用宽带资源,影响效率,做了“无用功”;
2 使用websocet 协议解决,websocket是HTML提供的一种基于单个TCP连接的全双工通信协议。(后面详细介绍)
? 优点:前端能知道什么时候获取数据,减少了“无用功”;
? 缺点: 少部分浏览器不支持该协议,而且不同浏览器的支持程度也不同。
1 什么是WebSocket协议
概念: WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议
这个概念其实前言里面已经简单的说了,但是具体我们还不太了解,这里我们只需要知道了这样几个专业名词就能很明确的清楚它的概念了。
HTML5 :
这个就不用多说了,它其实就是构建web内容的一种语言标准和方式。很多一知半解的前端开发者会模糊HTML和前端组件(Element-UI等)的概念,其实这些前端的组件很多也是基于HTML5来实现的,归根到底还是HTML。
TCP:
是一种面向连接安全可靠的传输控制层协议,想要深入了解请参考我的这篇博客 HTTP协议长连接和短连接
全双工:
是通讯传输的一种术语,它允许数据在两个方向上进行传输,并且这两个方向上可以同时进行数据传输,A–>B 和 B–>A 这两者可以同时进行数据传输,可以看出它是四线制的传输方式。通俗的讲就是两个人同时进行对话。
注意:
概念中单个TCP连接我们可以知道,websocket协议的建立只需要建立一次TCP连接,这与HTTP协议不同,HTTP需要进行TCP的“三次握手”。
2 前端如何创建WebSocket
准确的说应该是前端如何向服务端发出创建WebSocket连接的请求,
该连接的创建是由JavaScript来创建,在JS代码中我们只需要按照如下方式就能向服务端完成创建webSocket连接的请求:
具体JS如何在内部完成的连接建立,自行查看JS源码大致也是根据TCP协议发送URL数据连接跟后台完成响应之后建立连接通道,我这里只把重点放在后台的websocket服务创建上,就不再去查看这部分的源码了
var Socket = new WebSocket(url, [protocol] );
3 WebSocket的属性
WebSocket建立连接,发送数据,断开连接的过程中我们可以通过一些属性来查看WebSocket目前处于哪个状态:
3.1 Socket.readyState
只读属性 readyState 表示连接状态,可以是以下值:
0 - 表示连接尚未建立。
1 - 表示连接已建立,可以进行通信。
2 - 表示连接正在进行关闭。
3 - 表示连接已经关闭或者连接不能打开。
3.2 Socket.bufferedAmount
只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数
4 WebSocket的事件
5 WebSocket的方法
6 前端建立WebSocket连接demo代码
demo来自菜鸟教程
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
alert("您的浏览器支持 WebSocket!");
var ws = new WebSocket("ws://localhost:9998/echo");
ws.onopen = function()
{
ws.send("发送数据");
alert("数据发送中...");
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
alert("数据已接收...");
};
ws.onclose = function()
{
ws.close();
alert("连接已关闭...");
};
}
else
{
alert("您的浏览器不支持 WebSocket!");
}
}
</script>
</head>
<body>
<div id="sse">
<a href="javascript:WebSocketTest()">运行 WebSocket</a>
</div>
</body>
</html>
7 Java后台WebSocket服务的创建
在操作之前我们需要引入starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
package show.mrkay.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketServiceConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
package show.mrkay.websocket;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketService {
private static int onLineNum = 0;
private Session session;
private static ConcurrentHashMap<String, WebSocketService> websocketMap = new ConcurrentHashMap<>();
private String sid;
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) throws IOException {
this.session = session;
websocketMap.put(sid, this);
this.sid = sid;
onLineNumAdd();
sendMessage("connect successful");
}
@OnClose
public void onClose() {
if (websocketMap.get(this.sid) != null) {
websocketMap.remove(this.sid);
onLineNumReduce();
}
}
@OnMessage
public void onMessage(String message, Session session) {
if (StrUtil.isNotEmpty(message)) {
for (WebSocketService service : websocketMap.values()) {
try {
service.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
public static void onLineNumAdd() {
onLineNum++;
}
public static void onLineNumReduce() {
onLineNum--;
}
public static int getOnlineNum() {
return onLineNum;
}
}
8 小结
这次主要是一个问题解决,具体参考的是网上的一些实现方式.具体Spring如何实现的websocket服务需要阅读源码才能清楚,因为时间原因,这里只是简单记录了实现方式和对webSocket作了大致了解.后期有时间在研究关于WebSocket服务端的中的原理等问题. 这里只需要知道此协议的原理和用途即可.
|