如何实现不频繁访问数据库的即时通讯系统?
当然可以实现,下面给你几种无需频繁轮询数据库、由发送方触发接收方更新的方案,都能通过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
相关产品推荐
相关产品推荐

