凌峰创科服务平台

Ajax如何实现Comet服务器推送?

什么是 AJAX Comet?

AJAX Comet(通常简称为 Comet)是一种 Web 开发技术模型,它允许服务器主动向客户端推送数据,而不是传统的客户端(浏览器)发起请求、服务器响应的模式。

Ajax如何实现Comet服务器推送?-图1
(图片来源网络,侵删)

你可以把它想象成:

  • 传统 AJAX (轮询 Polling):你不停地打电话给快递公司问“我的快递到了吗?”,即使快递没到,你也得隔一段时间打一次,非常浪费资源。
  • Comet (长连接 Long-Polling):你给快递公司打一个电话,然后一直拿着电话听筒,直到快递员告诉你“快递到了,请下楼取”,你才挂断电话,在这期间,电话线是一直为你保留的,你只需要打一次电话就能得到实时信息。

Comet 的核心思想就是保持一个持久化的 HTTP 连接,服务器可以在这个连接上“挂起”很长时间,直到有新的数据需要发送时,才将数据推送给客户端。


为什么需要 Comet?—— 解决“实时性”痛点

在 Comet 出现之前,实现 Web 页面的“实时更新”主要有两种方式,但都有明显的缺点:

  1. 传统 AJAX 轮询

    Ajax如何实现Comet服务器推送?-图2
    (图片来源网络,侵删)
    • 原理:浏览器每隔几秒就向服务器发送一个 AJAX 请求,询问是否有新数据。
    • 缺点
      • 高延迟:数据更新后,最快也要等到下一个轮询周期才能被客户端感知。
      • 服务器压力大:即使没有新数据,服务器也要频繁处理大量无意义的请求,造成资源浪费。
      • 网络开销大:大量的 HTTP 请求头在网络中传输,增加了带宽消耗。
  2. HTML <iframe> 模拟推送

    • 原理:页面中嵌入一个隐藏的 <iframe>,其 src 指向一个服务器端脚本,服务器保持这个连接不断开,通过 iframedocument 对象不断写入 <script> 标签来执行 JavaScript,从而更新页面。
    • 缺点
      • 用户体验差:浏览器状态栏(或地址栏)会一直显示“正在加载...”,给用户带来困扰。
      • 浏览器兼容性问题:不同浏览器处理方式不一,实现复杂。
      • 安全性问题:容易受到跨站脚本攻击。

Comet 技术就是为了克服上述缺点而生的,它提供了更接近实时、更高效的通信方式。


Comet 的两种主要实现方式

Comet 主要通过以下两种 HTTP 请求/响应模式实现:

长轮询

这是目前最常用、最健壮的 Comet 实现方式。

Ajax如何实现Comet服务器推送?-图3
(图片来源网络,侵删)

工作流程:

  1. 客户端发起请求:浏览器通过 AJAX 向服务器发送一个请求。
  2. 服务器保持连接:服务器收到请求后,不立即返回响应,它会检查是否有新数据。
    • 如果有新数据:立即将数据作为响应返回给客户端,然后客户端处理数据,并立即发起下一个长轮询请求。
    • 如果没有新数据:服务器会将这个请求“挂起”(hold),保持 HTTP 连接打开,等待一段时间(30 秒)或直到有新数据产生。
  3. 客户端接收并处理:客户端接收到服务器推送的数据后,通过 JavaScript 更新页面 DOM。
  4. 循环往复:客户端处理完响应后,会立即发送一个新的长轮询请求,如此反复,形成一个循环。

优点:

  • 低延迟:一旦有数据,服务器可以立即推送,延迟极低。
  • 资源利用率相对较高:相比于短轮询,减少了不必要的请求次数。

缺点:

  • 连接管理复杂:需要管理大量并发的长连接,对服务器的并发处理能力要求高。
  • 请求/响应开销:每次数据推送后,都需要重新建立一个新的 HTTP 请求,包含完整的 HTTP 头,有一定开销。
  • 超时处理:需要妥善处理连接超时、网络中断等情况,否则用户体验会很差。

流式传输

这种方式在理论上是“真正的”推送,因为它不关闭连接。

工作流程:

  1. 客户端发起请求:浏览器通过 AJAX 向服务器发送一个请求,并设置 responseType = 'stream' (在部分浏览器中支持) 或使用 <iframe>/<script> 标签来接收流数据。
  2. 服务器持续写入数据:服务器收到请求后,保持连接打开,并在有新数据时,直接向这个 HTTP 响应流中写入数据,通常会在每个数据块后加上特定的分隔符(如换行符 \n)。
  3. 客户端持续读取:客户端通过 JavaScript 不断从响应流中读取数据块,并处理。

优点:

  • 真正的流式:数据可以源源不断地被推送到客户端,没有请求/响应的反复开销。
  • 延迟极低

缺点:

  • 实现复杂:在浏览器端通过 AJAX 直接读取流数据比较困难,兼容性差,传统上需要依赖 <iframe><script>
  • 浏览器限制:很多浏览器会限制单个连接的最大请求数或响应时间。
  • 服务器实现复杂:服务器需要能高效地处理流式写入,并且不能过早地关闭连接。

代码示例:长轮询

这是一个简化的 Node.js (使用 Express 框架) 和 JavaScript 的长轮询示例。

服务器端 (Node.js + Express)

const express = require('express');
const app = express();
const port = 3000;
// 模拟一个数据源
let messages = [
    { user: 'System', text: '欢迎来到聊天室!' }
];
let nextId = 1;
// 存储所有等待响应的客户端请求
const waitingClients = [];
// 模拟其他用户发送消息
setInterval(() => {
    if (Math.random() > 0.7) { // 30% 的概率有新消息
        const newMessage = {
            id: nextId++,
            user: 'User' + Math.floor(Math.random() * 100),
            text: '这是一条随机消息 ' + new Date().toLocaleTimeString()
        };
        messages.push(newMessage);
        console.log('新消息:', newMessage.text);
        // 通知所有等待的客户端
        waitingClients.forEach(client => {
            client.res.json({ success: true, data: newMessage });
            client.res.end(); // 发送响应后关闭连接
        });
        // 清空等待列表
        waitingClients.length = 0;
    }
}, 5000);
// Comet 长轮询接口
app.get('/get-updates', (req, res) => {
    console.log('客户端发起长轮询请求...');
    // 检查是否有新消息
    if (messages.length > 0) {
        // 如果有,立即返回
        res.json({ success: true, data: messages[messages.length - 1] });
        res.end();
    } else {
        // 如果没有,将客户端加入等待列表
        waitingClients.push({ req, res });
        // 设置一个超时,防止客户端无限等待
        const timer = setTimeout(() => {
            const index = waitingClients.findIndex(c => c.res === res);
            if (index !== -1) {
                waitingClients.splice(index, 1);
                res.status(200).json({ success: false, message: 'No new data, timeout.' });
                res.end();
            }
        }, 30000); // 30秒超时
    }
});
app.listen(port, () => {
    console.log(`Comet 服务器运行在 http://localhost:${port}`);
});

客户端 (HTML + JavaScript)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">Comet 长轮询示例</title>
    <style>
        body { font-family: sans-serif; }
        #messages { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; margin-bottom: 10px; }
        .message { padding: 5px; border-bottom: 1px solid #eee; }
    </style>
</head>
<body>
    <h1>Comet 实时消息</h1>
    <div id="messages"></div>
    <button id="startBtn">开始监听</button>
    <script>
        const messagesDiv = document.getElementById('messages');
        const startBtn = document.getElementById('startBtn');
        let isPolling = false;
        function addMessageToUI(message) {
            const messageEl = document.createElement('div');
            messageEl.className = 'message';
            messageEl.innerHTML = `<strong>${message.user}:</strong> ${message.text}`;
            messagesDiv.appendChild(messageEl);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }
        function startLongPolling() {
            if (isPolling) return;
            isPolling = true;
            startBtn.textContent = '监听中...';
            startBtn.disabled = true;
            function poll() {
                fetch('/get-updates')
                    .then(response => response.json())
                    .then(data => {
                        if (data.success && data.data) {
                            console.log('收到新消息:', data.data);
                            addMessageToUI(data.data);
                        }
                        // 无论成功与否(即使超时),都立即发起下一次请求
                        poll();
                    })
                    .catch(error => {
                        console.error('轮询出错:', error);
                        // 出错后稍等片刻再重试
                        setTimeout(poll, 2000);
                    });
            }
            // 开始第一次轮询
            poll();
        }
        startBtn.addEventListener('click', startLongPolling);
    </script>
</body>
</html>

Comet 的优缺点总结

优点 缺点
实时性高:显著降低了数据传输的延迟。 服务器压力大:需要维护大量长连接,对服务器的内存、CPU 和文件描述符都是巨大考验。
资源利用率高:相比轮询,减少了不必要的 HTTP 请求。 实现复杂:需要处理连接超时、断线重连、并发管理等复杂逻辑。
用户体验好:页面可以实时更新,无需用户手动刷新。 HTTP 头开销:每次请求都带有完整的 HTTP 头,在数据量小但请求频繁的场景下,开销相对较大。
兼容性好:基于标准 HTTP 协议,所有浏览器都支持。 不支持真正的双向通信:Comet 主要是服务器到客户端的单向推送,客户端发起请求仍然需要一个新的 HTTP 连接。

现代替代方案:WebSocket

随着技术的发展,WebSocket 已经成为实现实时通信的主流和标准方案。

WebSocket 是什么? WebSocket 是在单个 TCP 连接上进行全双工通信的协议,它首先通过一个 HTTP 请求进行“握手”,成功后,客户端和服务器之间就建立了一个持久化的、双向的、低延迟的通道。

为什么 WebSocket 优于 Comet?

特性 Comet (长轮询) WebSocket
连接模型 HTTP 请求/响应循环 持久化的 TCP 连接
通信方式 单向(服务器推) 双向(客户端和服务器都可以随时发送消息)
延迟 低(但有请求/响应周期) 极低(消息发送后几乎立即到达)
协议开销 每次请求都有 HTTP 头 握手后是轻量级的帧协议,开销极小
实现复杂度 客户端和服务器端逻辑都较复杂 客户端 API 简单,服务器端有成熟库支持
浏览器兼容性 极好(所有浏览器) 非常好(IE10+,所有现代浏览器)

虽然 Comet 在历史上解决了实时通信的难题,并且作为一种“兼容性更好”的方案仍有其用武之地(例如在非常老旧的浏览器环境中),但对于绝大多数现代 Web WebSocket 是更优的选择,它提供了更高效、更强大、更简单的实时通信能力。

分享:
扫描分享到社交APP
上一篇
下一篇