WebSocket通信学习笔记

1 简介

WebSocket是一种全双工通信协议,它允许客户端和服务器之间建立持久化的双向连接,从而在不频繁创建HTTP请求的情况下进行实时数据传输。与传统的HTTP协议相比,WebSocket更适合需要实时数据更新的应用场景,如聊天应用、实时股票行情、在线游戏等。
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
WebSocket可以通过wss://前缀实现加密通信,类似于HTTPS。由于WebSocket协议本身没有对数据的加密和身份验证机制,因此在使用时通常结合TLS/SSL进行加密,并通过认证机制确保通信的安全性。

1.1 工作原理

1) 握手阶段

WebSocket连接从客户端发起一个标准的HTTP请求开始,但这个请求的头部包含一些额外的字段,表示客户端希望升级到WebSocket协议。这是一个GET请求,并且包含以下几个重要的头部字段:

  • Upgrade: websocket:表示客户端希望将这个连接升级为WebSocket。
  • Connection: Upgrade:与Upgrade字段配合使用,表明连接类型的变化。
  • Sec-WebSocket-Key:一个由客户端生成的随机的Base64编码的key,服务器将用它来生成一个响应密钥以确认握手。
  • Sec-WebSocket-Version:表示WebSocket协议的版本,通常为13。

2) 服务器响应

如果服务器支持WebSocket,它将回应一个HTTP 101状态码,表示协议切换成功,并且在响应头部包含以下字段:

  • Upgrade: websocket:确认协议升级。
  • Connection: Upgrade:与Upgrade字段配合使用。
  • Sec-WebSocket-Accept:服务器根据客户端提供的Sec-WebSocket-Key,加上一个固定的字符串,然后通过SHA-1加密并进行Base64编码后生成的值。这个值用于确认握手的有效性。

握手完成后,连接正式切换为WebSocket协议,客户端和服务器可以开始通过这个持久连接进行双向通信。

3) 数据帧传输

一旦连接建立,客户端和服务器之间的数据传输以帧的形式进行。WebSocket的数据帧由一个小的头部和可变长度的数据负载组成,帧可以携带文本数据(UTF-8编码的字符串)或二进制数据。WebSocket支持以下几种帧类型:

  • 文本帧:用于传输文本数据。
  • 二进制帧:用于传输二进制数据。
  • 关闭帧:用于表示连接关闭。
  • Ping帧:客户端或服务器发送Ping帧以测试连接的连通性。
  • Pong帧:用于响应Ping帧。

4) 连接关闭

WebSocket连接可以由客户端或服务器任意一方发起关闭。关闭连接时,必须发送一个关闭帧,其中可以包含关闭码和关闭原因。收到关闭帧后,另一方应尽快发送自己的关闭帧并关闭连接。

1.2 WebSocket 优缺点分析

1)优点

  • 低延迟:由于WebSocket连接是持久的,消除了频繁创建和关闭连接的开销,延迟较低,非常适合需要实时数据更新的应用。
  • 全双工通信:与HTTP的请求-响应模式不同,WebSocket支持全双工通信,客户端和服务器可以同时发送和接收消息。
  • 节省带宽:WebSocket在建立连接后没有HTTP协议的头部负载,可以节省带宽。

2) 缺点

  • 复杂性:WebSocket的实现和调试可能比传统的HTTP协议要复杂,尤其是在处理连接状态、网络问题和安全性方面。
  • 不适用于所有应用:对于一些不需要实时通信的应用,WebSocket可能显得过于复杂和不必要。

1.3 适用场景

  • 实时聊天应用:WebSocket可以实现低延迟的消息传递,非常适合实时聊天。
  • 实时推送:如新闻、体育比分、金融市场数据等。
  • 多人在线游戏:需要低延迟的数据同步和通信。
  • 协作编辑工具:如在线文档编辑、代码协作等,WebSocket可以实现实时的内容同步。

对比 HTTP:

  • HTTP适用于大多数Web应用和API场景,其简单的请求-响应模型、无状态特性和广泛的工具支持,使其成为Web开发的标准选择。
  • WebSocket则适合需要实时、双向数据传输的应用场景,如聊天、在线游戏和金融数据推送。虽然实现上更为复杂,但其在特定场景下的性能和效率优势是HTTP无法比拟的。

详细比较:

  • **通信模式:**HTTP 是请求-响应模式,单项且无状态的;WebSocket 是全双工通信,双向且持续连接。
  • **连接管理:**HTTP通常是短连接,每次请求都重新建立连接;WebSocket是长连接,握手后保持连接,适合频繁数据交换。
  • **使用场景:**HTTP适用于网页加载、API调用等不需要实时更新的场景;WebSocket适用于实时聊天、在线游戏等需要低延迟、实时数据更新的应用。

HTTP和WebSocket底层都是TCP连接。

2 Demo 实例

1) 创建 SpringBoot 项目

2) 引入 Maven 依赖


<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-websocketartifactId>
dependency>

3) 导入前端页面

DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <title>WebSocket Demotitle>
  head>
  <body>
    <input id="text" type="text" />
    <button onclick="send()">发送消息button>
    <button onclick="closeWebSocket()">关闭连接button>
    <div id="message">
    div>
  body>
  <script type="text/javascript">
    var websocket = null;
    var clientId = Math.random().toString(36).substr(2);

    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window){
      //连接WebSocket节点
      websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
    }
    else{
      alert('Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function(){
      setMessageInnerHTML("error");
    };

    //连接成功建立的回调方法
    websocket.onopen = function(){
      setMessageInnerHTML("连接成功");
    }

    //接收到消息的回调方法
    websocket.onmessage = function(event){
      setMessageInnerHTML(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function(){
      setMessageInnerHTML("close");
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function(){
      websocket.close();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML){
      document.getElementById('message').innerHTML += innerHTML + '
'
; } //发送消息 function send(){ var message = document.getElementById('text').value; websocket.send(message); } //关闭连接 function closeWebSocket() { websocket.close(); }
script> html>

4) 编写后端 websocket 服务类

@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
    // 存放会话对象
    private static Map<String, Session> sessionMap = new HashMap<>();


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                // 服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

5)编写 websocket 配置类

这个配置类的主要作用是启用WebSocket支持。当Spring容器启动时,它会自动扫描所有标记了@ServerEndpoint注解的类,并将它们注册为WebSocket端点,允许客户端通过这些端点进行WebSocket通信。 如果你在Spring Boot项目中使用内置的Web容器(如Tomcat、Jetty等),并且使用WebSocket,则需要这个ServerEndpointExporter来将WebSocket端点自动注册到容器中。

@Configuration
public class WebSocketConfiguration {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

6) 定时任务模拟后端发送给用户端消息

@Component
public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过websocket每隔五秒向客户发送信息
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}

3 结果展示

前端结果:
除了自己能主动给后端发送信息以外,前段每隔五秒还能收到后端发来的信息。
WebSocket通信学习笔记_第1张图片
后端结果:
服务端自动接收前段发来的信息,同时会在定时任务中,每隔五秒给前端发送一条信息。
WebSocket通信学习笔记_第2张图片
代码获取:https://gitee.com/strivezhangp/java-study-log.git 切换到相关分支即可拉取。

你可能感兴趣的:(websocket,学习,笔记,spring,boot,java)