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

React Native(Expo)与PHP WebSocket连接失败问题求助

问题:移动端WebSocket连接被拒绝,无法接收数据库变更通知

我是移动开发新手,从没搭建过Socket服务器。现在开发一款移动端应用,想要在数据库表发生变更时向相关用户发送通知,于是搭建了基于Ratchet的PHP WebSocket服务器,计划通过前端推送通知(即使应用处于关闭状态),但目前前端无法连接到服务器,报错如下:

LOG WebSocket error: {"isTrusted": false, "message": "The operation couldn’t be completed. Connection refused"}
LOG WebSocket is closed.

PHP WebSocket服务器代码

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

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use React\EventLoop\Loop;
use Ratchet\App;

class DatabaseWatcher implements MessageComponentInterface
{
    protected $clients;
    protected $lastMaxId;

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

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn, ['ref_restaurant' => null]);
        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        $data = json_decode($msg, true);
        if (isset($data['ref_restaurant'])) {
            $this->clients[$from]['ref_restaurant'] = $data['ref_restaurant'];
            echo "Client {$from->resourceId} set ref_restaurant to {$data['ref_restaurant']}\n";
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }

    public function getMaxId()
    {
        try {
            $pdo = new PDO('mysql:host=193.203.168.77;dbname=u440859155_db_yumeats', 'u440859155_yumeats', 'YumEats2024@');
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

            $stmt = $pdo->query('SELECT MAX(id) AS max_id FROM orders');
            $result = $stmt->fetch(PDO::FETCH_ASSOC);

            return $result['max_id'] ?? 0;
        } catch (PDOException $e) {
            echo "Database error: " . $e->getMessage() . "\n";
            return 0;
        }
    }

    public function checkDatabase()
    {
        try {
            $pdo = new PDO('mysql:host=193.203.168.77;dbname=u440859155_db_yumeats', 'u440859155_yumeats', 'YumEats2024@');
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

            $stmt = $pdo->prepare('SELECT * FROM orders WHERE id > :lastMaxId');
            $stmt->execute(['lastMaxId' => $this->lastMaxId]);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

            if (!empty($rows)) {
                $this->lastMaxId = max(array_column($rows, 'id'));
                echo "New entries found:\n";
                print_r($rows); // Log the new entries
                foreach ($this->clients as $client) {
                    $clientRestaurant = $this->clients[$client]['ref_restaurant'];
                    $filteredRows = array_filter($rows, function ($row) use ($clientRestaurant) {
                        return $row['ref_restaurant'] === $clientRestaurant;
                    });

                    if (!empty($filteredRows)) {
                        $client->send(json_encode($filteredRows));
                    }
                }
            }
        } catch (PDOException $e) {
            echo "Database error: " . $e->getMessage() . "\n";
        }
    }
}

$app = new Ratchet\App('0.0.0.0', 8080);
$loop = Loop::get();

$databaseWatcher = new DatabaseWatcher();
$loop->addPeriodicTimer(1, function () use ($databaseWatcher) {
    $databaseWatcher->checkDatabase();
});

$app->route('/database-watcher', $databaseWatcher, ['*']);
$app->run();

前端(React)连接代码

const ws = useRef(null); 

const initializeWebSocket = (refRestaurant) => {
    ws.current = new WebSocket('ws://localhost:8080/database-watcher');
    
    ws.current.onopen = () => {
        console.log('WebSocket is connected.');
        ws.current.send(JSON.stringify({ ref_restaurant: refRestaurant }));
    };
    
    ws.current.onmessage = (event) => {
        const data = JSON.parse(event.data);
        console.log('Received data:', data);
        setOrdersAndClients(prevOrders => [...prevOrders, ...data]); 

        Alert.alert(
            "Nouvelle commande",
            "Une nouvelle commande a été reçue.",
            [{ text: "OK" }]
        );
    };
    
    ws.current.onclose = () => {
        console.log('WebSocket is closed.');
    };
    
    ws.current.onerror = (error) => {
        console.log('WebSocket error:', error);
    };
};

排查与修复方案

1. 修正连接地址(最核心问题)

前端代码中使用ws://localhost:8080仅适用于本地模拟器/浏览器测试:

  • 真机测试时,必须替换为服务器的公网IP或域名(比如你的数据库IP193.203.168.77),即ws://193.203.168.77:8080/database-watcher
  • Android模拟器可使用10.0.2.2映射主机的localhost,iOS模拟器可直接用localhost(前提是服务器和模拟器在同一局域网)

2. 开放服务器端口

  • 在服务器防火墙(如ufw、iptables)中放行8080端口的TCP入站连接
  • 云服务器需在控制台安全组中添加8080端口的允许规则

3. 确保服务器正常运行

  • 启动服务器后,终端需保持运行状态,若没有报错且能打印日志,说明服务器在正常监听
  • 生产环境建议用supervisor等工具守护进程,避免终端关闭后服务器停止

4. 处理协议与跨域问题

  • 若前端使用HTTPS环境,必须改用wss://加密连接,可通过Nginx反向代理WebSocket请求并配置SSL证书
  • 确保服务器允许跨域连接(Ratchet的route方法已设置['*'],这部分无需修改)

代码优化建议

  • 复用数据库连接:当前每次检查数据库都新建PDO连接,建议在构造函数中初始化一次,减少资源消耗
  • 替换轮询逻辑:每秒轮询数据库效率低,可改用MySQL Binlog监听或触发器+消息队列的方式,更高效捕获数据变更
  • 后台通知补充:WebSocket仅能在应用前台在线时推送,若要实现应用关闭时的通知,需结合FCM(Android)或APNs(iOS)推送服务

内容的提问来源于stack exchange,提问作者dwain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:02:33