You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现不频繁访问数据库的即时通讯系统?

当然可以实现,下面给你几种无需频繁轮询数据库、由发送方触发接收方更新的方案,都能通过PHP、JavaScript配合实现:

方案1:长轮询(Comet)

核心逻辑是客户端发起请求后,服务器会hold住连接,直到有新消息产生或超时才返回响应;客户端收到响应后立刻再次发起请求,以此实现“有消息才更新”的效果,避免无用的数据库查询。

服务器端(longPoll.php)

<?php
session_start();
set_time_limit(0); // 取消脚本超时限制
header('Content-Type: application/json');
header('Cache-Control: no-cache');

// 循环等待新消息
while (true) {
    // 这里替换成你的业务逻辑:检查当前用户是否有未接收的新消息
    // 建议用Redis等缓存存新消息,而非直接查数据库
    $newMessages = getUnreadMessages($_SESSION['user_id']);
    
    if (!empty($newMessages)) {
        echo json_encode($newMessages);
        ob_flush();
        flush();
        break; // 返回消息后终止循环
    }
    sleep(2); // 每2秒检查一次,降低资源消耗
}
?>

客户端JavaScript

function startLongPoll() {
    $.ajax({
        url: 'longPoll.php',
        type: 'GET',
        success: function(messages) {
            // 更新页面消息列表
            messages.forEach(msg => {
                $('#messages').append(`<div>${msg.content}</div>`);
            });
            // 立刻发起下一次长轮询
            startLongPoll();
        },
        error: function() {
            // 出错后延迟3秒重试,避免频繁请求
            setTimeout(startLongPoll, 3000);
        },
        timeout: 30000 // 设置30秒超时,防止连接长时间挂起
    });
}

// 启动长轮询
startLongPoll();
方案2:Server-Sent Events(SSE)

SSE是HTML5原生的服务器向客户端单向推送标准,自带重连机制,代码比长轮询更简洁,适合即时通讯的消息接收场景。

服务器端(sse.php)

<?php
session_start();
set_time_limit(0);
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');

while (true) {
    $newMessages = getUnreadMessages($_SESSION['user_id']);
    
    if (!empty($newMessages)) {
        // 遵循SSE格式:data: 内容\n\n
        echo "data: " . json_encode($newMessages) . "\n\n";
        ob_flush();
        flush();
    }
    sleep(2);
}
?>

客户端JavaScript

function initSSE() {
    if (!window.EventSource) {
        // 不支持SSE的浏览器,降级到长轮询
        startLongPoll();
        return;
    }

    const eventSource = new EventSource('sse.php');
    eventSource.onmessage = function(event) {
        const messages = JSON.parse(event.data);
        // 更新消息列表
        messages.forEach(msg => {
            $('#messages').append(`<div>${msg.content}</div>`);
        });
    };

    eventSource.onerror = function() {
        eventSource.close();
        // 出错后自动重连
        setTimeout(initSSE, 3000);
    };
}

// 初始化SSE连接
initSSE();
方案3:WebSocket(双向实时通信)

如果需要更高效的双向通信(比如发送消息后实时确认状态),WebSocket是最优选择。它建立一次连接后,服务器和客户端可随时互相发送消息,完全摆脱轮询模式。PHP可以通过Ratchet库实现WebSocket服务。

1. 安装依赖

composer require cboden/ratchet

2. WebSocket服务端(chat-server.php)

<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

require __DIR__ . '/vendor/autoload.php';

class ChatServer implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
        // 可以在这里将连接与用户ID绑定(比如通过URL参数传递)
        $conn->user_id = $_GET['user_id'];
        $this->clients->attach($conn);
        echo "用户{$conn->user_id}已连接\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        $data = json_decode($msg, true);
        // 找到接收方的连接并推送消息
        foreach ($this->clients as $client) {
            if ($client->user_id == $data['to_user_id']) {
                $client->send(json_encode($data['message']));
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
        echo "用户{$conn->user_id}已断开连接\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "错误:{$e->getMessage()}\n";
        $conn->close();
    }
}

// 启动WebSocket服务,监听8080端口
$server = IoServer::factory(
    new HttpServer(new WsServer(new ChatServer())),
    8080
);

$server->run();

3. 客户端JavaScript

function initWebSocket(userId) {
    const ws = new WebSocket(`ws://你的域名:8080?user_id=${userId}`);
    
    ws.onopen = function() {
        console.log('WebSocket连接建立成功');
    };

    ws.onmessage = function(event) {
        const message = JSON.parse(event.data);
        // 更新消息列表
        $('#messages').append(`<div>${message.content}</div>`);
    };

    ws.onerror = function(error) {
        console.error('WebSocket错误:', error);
    };

    ws.onclose = function() {
        console.log('连接断开,正在重连...');
        setTimeout(() => initWebSocket(userId), 3000);
    };

    // 发送消息的函数
    window.sendMessage = function(toUserId, content) {
        ws.send(JSON.stringify({
            to_user_id: toUserId,
            message: {content: content}
        }));
    };
}

// 初始化WebSocket连接(传入当前用户ID)
initWebSocket(123);
额外优化建议
  • 不管用哪种方案,都建议用Redis等缓存存储新消息,发送方发消息时写入缓存,服务器端从缓存读取,大幅减少数据库查询压力。
  • 长轮询和SSE可直接通过Apache处理,但高并发场景下需调整Apache的连接数配置,或用Nginx反向代理优化。
  • WebSocket需要单独启动服务,不能通过Apache直接托管,部署时需确保端口可访问。

内容的提问来源于stack exchange,提问作者Quentin Régnier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 00:40:42