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

PHP Ratchet WebSocket请求无响应及连接问题求助

Ratchet WebSocket服务连接问题排查与修复

我依照官方文档搭建了基础PHP WebSocket服务,但测试时遇到一系列问题:

  • 调用测试文件时请求一直无法完成,Postman卡在“发送响应”状态
  • 执行netstat -anp | grep LISTEN确认端口处于监听状态,但请求始终等待onOpen回复
  • Postman的WebSocket功能连接时返回Unexpected server response: 200错误
  • 终端用telnet能正常通信,但浏览器连接时触发Content Security Policy限制

测试调用代码

<?php 
// API for privacy setting of user for blocking/Message Limit
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
//file_put_contents('zee.log',print_r($_POST,true));

// Function to send the SDP and user IDs to the server
function sendSDP($sdp, $userIdA, $userIdB)
{
    // Create a new cURL resource
    $ch = curl_init();

    // Set the cURL options
    curl_setopt($ch, CURLOPT_URL, "callSocketChat_2008.php");  // callSocketChat_2008.php file
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
        'sdp' => $sdp,
        'userIdA' => $userIdA,
        'userIdB' => $userIdB
    )));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    // Execute the cURL request
    $response = curl_exec($ch);

    // Check for errors
    if ($response === false) {
        echo "cURL Error: " . curl_error($ch);
    } else {
        // Request successful, display the response
        echo $response;
    }

    // Close the cURL resource
    curl_close($ch);
}

// Test the sendSDP function
$sdp = "SDP valuexx YY"; // Replace with your actual SDP value
$userIdA = "4"; // Replace with your actual User ID A value
$userIdB = "8"; // Replace with your actual User ID B value

sendSDP($sdp, $userIdA, $userIdB);
?>

Ratchet服务代码

<?php
// Enable error reporting and display errors for debugging purposes
error_reporting(E_ALL);
ini_set('display_errors', 1);

require_once("/home/bitnami/vendor/autoload.php");
use Ratchet\MessageComponentInterface;;
use Ratchet\ConnectionInterface;

class WebSocketHandler implements MessageComponentInterface {
    protected $clients;

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

    public function onOpen(ConnectionInterface $conn) {
        // Handle new WebSocket connection
        // You can add the connection to $this->clients for future reference
        //$request = $conn->httpRequest;
        //$userId = $_POST['userId']; // Assuming the user ID is received in the POST request

        // Save the user ID in the resourceId property of the connection
        $conn->resourceId = $userIdA;

        // Add the connection to the clients list
        $this->clients->attach($conn);
        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        // Handle incoming WebSocket message
        // You can process the message and send a response back to the client
    }

    public function onClose(ConnectionInterface $conn) {
        // Handle WebSocket connection close
        // You can remove the connection from $this->clients if necessary
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        // Handle WebSocket error
        // You can log or handle the error as needed
    }
}

// Get the SDP and user IDs from the request
$sdp = $_POST['sdp'];
$userIdA = $_POST['userIdA'];
$userIdB = $_POST['userIdB'];

// Fetch user details from the elaxer database using the user IDs
$userDetailsA = fetchUserDetails($userIdA);
$userDetailsB = fetchUserDetails($userIdB);

// Send push notification to wake up User B's device
//sendPushNotification($userDetailsB['fcmToken']);

// Wait for User B's device to establish a WebSocket connection
$waitForConnectionTimeout = 30; // Timeout in seconds
$startTime = time();
while (time() - $startTime < $waitForConnectionTimeout) {
    // Check if User B's device has connected to the WebSocket
    if (userDeviceConnected($userIdB)) {
        // User B's device connected, retrieve SDP from the elaxer database
        $sdpB = fetchSDP($userIdB);

        // Save User A's SDP in the elaxer database
        saveSDP($userIdA, $sdp);

        // Return User B's SDP to User A
        echo $sdpB;
        exit;
    }

    // Wait for a short duration before checking again
    sleep(1);
}

// Instantiate WebSocketHandler

//$server = \Ratchet\Server\IoServer::factory(
  //  new \Ratchet\Http\HttpServer(
    //    new \Ratchet\WebSocket\WsServer($webSocketHandler)
    //),
    //8080 // Change the port number if needed
//);

// Set up the WebSocket server using Ratchet
$server = Ratchet\Server\IoServer::factory(new WebSocketHandler(),20081); // Change the port number if needed
$server->run();

// Timeout reached, handle the case when User B's device didn't connect
handleTimeout();

// Fetch user details from the elaxer database using the user ID
function fetchUserDetails($userId)
{
    // Implement the logic to fetch user details from the elaxer database
    // Return an array containing the user details
}

// Send push notification to wake up User B's device
function sendPushNotification($fcmToken)
{
    // Implement the logic to send a push notification using FCM to the specified FCM token
}

// Check if User B's device has connected to the WebSocket
function userDeviceConnected($userId)
{
    // Implement the logic to check if User B's device has connected to the WebSocket
    // Return true if the device is connected, false otherwise
}

// Fetch SDP from the elaxer database using the user ID
function fetchSDP($userId)
{
    // Implement the logic to fetch SDP from the elaxer database using the user ID
    // Return the retrieved SDP
}

// Save SDP in the elaxer database using the user ID
function saveSDP($userId, $sdp)
{
    // Implement the logic to save the SDP in the elaxer database using the user ID
}

// Handle the case when User B's device didn't connect within the timeout
function handleTimeout()
{
    // Implement the logic to handle the timeout case
    // For example, you can return an error response or perform a fallback action
}
?>

补充错误信息

Postman WebSocket连接错误

Could not connect to ws://www.example.com/callSocketChat_2008.php
01:58:47
Error: Unexpected server response: 200
Handshake Details
Request URL: ws://www.example.com/callSocketChat_2008.php
Request Method: GET
Status Code: 200 OK
Request Headers
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: ansqUU0N7J4Q6S8PyR6XEg==
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Host: www.example.com/callSocketChat_2008
Response Headers
Date: Tue, 30 May 2023 20:28:15 GMT
Server: Apache
Vary: Accept-Encoding
X-UA-Compatible: IE=Edge
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

浏览器连接错误

Content Security Policy: The page’s settings blocked the loading of a resource at ws://example.in:8080/ (“connect-src”). debugger eval code:1:11


问题排查与修复方案

1. 分离HTTP请求处理与WebSocket服务逻辑

你的服务代码把POST请求循环和WebSocket启动放在同一个文件里,导致服务永远无法启动(循环会先执行并阻塞)。必须将两者拆分:

  • 单独创建WebSocket服务启动文件,仅负责启动Ratchet服务
  • 创建独立的HTTP接口处理SDP提交和用户逻辑

2. 修正WebSocket服务初始化代码

直接用IoServer实例化处理器缺少协议层包装,无法处理标准WebSocket握手,正确写法:

$webSocketHandler = new WebSocketHandler();
$server = \Ratchet\Server\IoServer::factory(
    new \Ratchet\Http\HttpServer(
        new \Ratchet\WebSocket\WsServer($webSocketHandler)
    ),
    20081 // 指定监听端口
);
$server->run();

3. 修复onOpen方法中的未定义变量

onOpen中直接使用$userIdA会报错,应从WebSocket握手请求的URL参数获取用户标识:

public function onOpen(ConnectionInterface $conn) {
    $queryParams = $conn->httpRequest->getUri()->getQuery();
    parse_str($queryParams, $params);
    $userId = $params['userId'] ?? $conn->resourceId; // 优先取URL参数,默认用系统分配的resourceId
    $conn->userId = $userId;
    $this->clients->attach($conn);
    echo "New connection! ({$conn->userId})\n";
}

4. 正确连接WebSocket服务

不要通过Apache解析PHP文件连接WebSocket,正确地址是ws://你的域名:20081,带用户参数的示例:

var conn = new WebSocket('ws://example.com:20081?userId=4');
conn.onopen = function(e) {
    console.log("Connection established!");
};

conn.onmessage = function(e) {
    console.log(e.data);
};

5. 解决Content Security Policy限制

  • 在页面的CSP配置中添加ws://example.in:20081到connect-src规则
  • 生产环境建议使用wss(WebSocket over SSL),需要给Ratchet配置SSL证书:
$server = \Ratchet\Server\IoServer::factory(
    new \Ratchet\Http\HttpServer(
        new \Ratchet\WebSocket\WsServer($webSocketHandler)
    ),
    20081,
    '0.0.0.0',
    [
        'local_cert' => '/path/to/your/certificate.pem',
        'local_pk' => '/path/to/your/private-key.pem',
        'verify_peer' => false
    ]
);

6. 修正测试流程

  • WebSocket服务需要在终端独立启动:php your-websocket-server.php
  • 用浏览器或专门的WebSocket测试工具连接,不要用cURL调用服务文件

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 13:58:07