WebSocket

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服务创建上,就不再去查看这部分的源码了

// url : 服务端建立连接请求地址,该地址为必填参数,
// protocol : 可选填,指定可接受的子协议(一般不传这个参数)
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!");
               // 打开一个 web socket
               var ws = new WebSocket("ws://localhost:9998/echo");
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("发送数据");
                  alert("数据发送中...");
               };
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert("数据已接收...");
               };
               ws.onclose = function()
               { 
                  // 关闭 websocket
                   ws.close();
                  alert("连接已关闭..."); 
               };
            }
            else
            {
               // 浏览器不支持 WebSocket
               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;

/**
 * WebSocketService配置类
 *
 * @author liukai
 * @date 2021/10/5 23:04
 */
@Configuration
public class WebSocketServiceConfig {
    /**
     * 当我们使用SpringBoot内置的tomcat容器的时候就必须手动讲ServerEndpointExporter注入
     * 当我们没有使用Springboot内置的容器的时候不能讲ServerEndpointExporter注入到IOC容器中
     * 我们就需要经@Bean注解去掉
     * 这里我们使用的是SpringBoot内置的容器,所以这里就需要注入
     */
    @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;

/**
 * WebSocket服务 restful风格的路径中必须指定sid因为sid是客户端的唯一标识
 * 这里日志只需要抛出即可实际会有统一日志处理
 * @author liukai
 * @date 2021/10/5 23:08
 */
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketService {
    /**
     * 统计在线人数
     */
    private static int onLineNum = 0;
    /**
     * 某一个客户端的链接会话,需要使用此session与客户端发送数据消息
     */
    private Session session;

    /**
     * 存储对应的websocket对象,key:sid value: WebSocketService
     */
    private static ConcurrentHashMap<String, WebSocketService> websocketMap = new ConcurrentHashMap<>();
    /**
     * 用户的sid
     */
    private String sid;

    /**
     * 链接成功后调用
     *
     * @param session 会话Session
     * @param sid     客户端sid
     * @return void
     * @author liukai
     */
    @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");
    }

    /**
     * 关闭连接时会调用
     *
     * @param
     * @return void
     * @author liukai
     */
    @OnClose
    public void onClose() {
        if (websocketMap.get(this.sid) != null) {
            websocketMap.remove(this.sid);
            onLineNumReduce();
        }
    }

    /**
     * 收到客户端的消息后会调用此方法 这里为了演示可简单讲消息放松出去,实际可根据业务需求而定
     *
     * @param message
     * @param session
     * @return void
     * @author liukai
     */
    @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;
                }
            }
        }
    }

    /**
     * 发生错误时回调该方法
     *
     * @param session
     * @param error
     * @return void
     * @author liukai
     */
    @OnError
    public void onError(Session session, Throwable error) {
        //直接答应错误信息
        error.printStackTrace();
    }

    /**
     * 实现WebSocket服务主动向客户端发送消息
     *
     * @param message
     * @return void
     * @author liukai
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * 在线数自增
     *
     * @param
     * @return void
     * @author liukai
     */
    public static void onLineNumAdd() {
        onLineNum++;
    }

    /**
     * 在线数自减
     *
     * @param
     * @return void
     * @author liukai
     */
    public static void onLineNumReduce() {
        onLineNum--;
    }

    /**
     * 获取在线人数
     *
     * @param
     * @return int
     * @author liukai
     */
    public static int getOnlineNum() {
        return onLineNum;
    }

}

8 小结

这次主要是一个问题解决,具体参考的是网上的一些实现方式.具体Spring如何实现的websocket服务需要阅读源码才能清楚,因为时间原因,这里只是简单记录了实现方式和对webSocket作了大致了解.后期有时间在研究关于WebSocket服务端的中的原理等问题.
这里只需要知道此协议的原理和用途即可.

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liu.kai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值