服务器向客户端推送消息的2种技术分析

  HTTP是无状态、单向的协议,用户只能够通过客服端向服务器发送请求并由服务器处理发回一个响应。

一、AJAX长轮询(Long Polling via AJAX)

  Comet 翻译为彗星,彗星长长的尾巴形容服务器和客户端需要建立一个“长”链接。基于这种架构开发的应用中,服务器端可以主动以异步的方式向客户端程序推送数据,而不需要客户端显式的发出请求。
  Comet 架构非常适合事件驱动的 Web 应用,以及对交互性和实时性要求很强的应用,如股票交易行情分析、聊天室和 Web 版在线游戏等。


  AJAX长轮询简单来说,需要服务器与客户端需要保持一条长时间的请求。
  客户端给服务器发送了一个Ajax(异步JavaScript和XML)请求,然后等待服务器响应,只要一有事件发生,服务器端就会在挂起的请求中送回响应。如果第一次的请求返回数据后,第二次请求会立刻发出,这种技术就称为Ajax 长轮询。

  • 优点: 减少轮询次数,低延迟,浏览器兼容性较好。
  • 缺点: 服务器需要保持大量连接。

实操

  下面是一个使用 Vue 2 和 Java 实现的 AJAX 长轮询的示例:

  客户端(Vue 2):

<template>
  <div>
    <p>服务器时间: {{ serverTime }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      serverTime: ''
    };
  },
  mounted() {
    this.longPolling();
  },
  methods: {
    longPolling() {
      const self = this;
      const xhr = new XMLHttpRequest();
      
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
          const response = JSON.parse(xhr.responseText);
          self.serverTime = response.time;
          self.longPolling();
        }
      };
      
      xhr.open('GET', '/api/long-polling', true);
      xhr.send();
    }
  }
};
</script>

  服务端(spring):

import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;

@RestController
public class LongPollingController {
    
    @GetMapping("/api/long-polling")
    public ResponseEntity<?> longPolling() {
        // 模拟一个长时间的操作
        try {
            Thread.sleep(ThreadLocalRandom.current().nextInt(5000, 10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 构造响应数据
        Map<String, Object> response = new HashMap<>();
        response.put("time", new Date());
        
        return ResponseEntity.ok(response);
    }
}

  客户端收到响应后,更新页面上的服务器时间,并再次发起长轮询请求。这样就实现了一个基本的 AJAX 长轮询机制。

  长轮询是一种比较老旧的实时通信方式,现在更常用的是 WebSocket 技术。



二、WebSockets协议

  与HTTP不同,WebSockets是一种通过TCP工作的有状态通信协议。
  通信最初是作为HTTP握手开始的,但如果两个通信方同意,则通过WebSockets通信。
  WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输(全双工)。

  • 优点:更强的实时性,由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少
  • 缺点:即使Websockets服务端已经发现连接断开,仍然没有办法推送数据,只能被动等待客户端重新建立好连接才能推送,在此之前数据将可能会被采取丢弃的措施处理掉。

实操

  以下是一个使用 Spring 框架实现 WebSocket 的简单示例:

  首先,需要创建一个 WebSocket 处理器类来处理 WebSocket 连接和消息的交互:

import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;

@Component
public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("WebSocket连接已建立");
    }

    @Override
    public void handleMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("接收到WebSocket消息:" + message.getPayload());
        session.sendMessage(new TextMessage("服务器已收到消息:" + message.getPayload()));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("WebSocket连接已关闭");
    }
}

  接下来,需要配置 WebSocket 的处理器和端点:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private MyWebSocketHandler myWebSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWebSocketHandler, "/websocket")
                .setAllowedOrigins("*");
    }
}

  在上述配置中,将 MyWebSocketHandler 注册为 WebSocket 处理器,并将其映射到 /websocket 路径上。

  最后,创建一个简单的控制器来返回包含 WebSocket 客户端的页面:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "index.html";
    }
}

  在上述控制器中,当访问根路径 / 时,返回名为 index.html 的页面。

  创建 index.html 文件,并在其中编写 WebSocket 相关的 JavaScript 代码:
  通过监听 onmessage 事件来接收服务器发送的消息,并将其显示在页面上。同时提供一个输入框和按钮,用于发送消息给服务器。

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Demo</title>
</head>
<body>
    <h1>WebSocket Demo</h1>

    <div id="output"></div>

    <script>
        const socket = new WebSocket("ws://localhost:8080/websocket");

        socket.onopen = function(event) {
            console.log("WebSocket连接已建立");
        };

        socket.onmessage = function(event) {
            const outputDiv = document.getElementById("output");
            outputDiv.innerHTML += "<p>收到服务器消息:" + event.data + "</p>";
        };

        socket.onclose = function(event) {
            console.log("WebSocket连接已关闭");
        };

        function sendMessage() {
            const message = document.getElementById("message").value;
            socket.send(message);
        }
    </script>

    <input type="text" id="message" placeholder="请输入消息">
    <button onclick="sendMessage()">发送消息</button>
</body>
</html>



三、总结

  在弱网情况下,TCP长连接会经常性的断开,使用以上两种方法均有弊端,短轮询是弱网情况下更好的选择。还是要根据不同的使用场景选择服务器推送技术。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

倔强的腿毛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值