webman使用workerman/stomp适用于websocket stomp 协议吗?

supdger

截图
不知道硬件文档说的websocket stomp协议是不是可以用workerman/stomp.
总之,在
https://www.workerman.net/doc/workerman/faq/as-wss-client.html
试了不行.
但是根据文档:https://www.workerman.net/doc/workerman/components/workerman-stomp.html
用workerman/stomp,并参考Client类.
好像ws/wss客户端文档中
$con->transport = 'ssl';
$ws_connection->headers = ['token' => 'value'];
这些用法都无效.
看Client类的onConnectionConnect方法比较符合硬件文档说的连接时在header中传入授权信息.
是不是我需要重写onConnectionConnect方法传入授权?

                        $ws_url = 'stomp://koneonline.kone.cn/websocket/v1/customerapi';
                        $topic = '/user/queue/customerapi/elevatorOnlineStatus';
                        $ws_connection = new Client($ws_url);
                        $ws_connection->transport = 'ssl';
                        $ws_connection->headers = ['authorizationToken' => $access_token];
                        $ws_connection->onConnect = function(Client $client) use ($topic){
                            $client->subscribe($topic, function(Client $client, $data) {
                                var_export($data);
                            });
                        };
                        $ws_connection->onMessage = function($connection, $data){
                            echo "recv: $data\n";
                        };
                        $ws_connection->onError = function ($e) {
                            echo $e;
                        };
                        $ws_connection->connect();

另外Client类:

protected $_options = [
        'vhost'            => '/',
        'login'            => 'guest',
        'passcode'         => 'guest',
        'bindto'           => '',
        'ssl'              => [],
        'connect_timeout'  => 30,
        'reconnect_period' => 2,
        'debug'            => false,
        'heart_beat'       => [0, 0],
    ];

    /**
     * Client constructor.
     * @param $address
     * @param array $options
     */
    public function __construct($address, $options = [])
    {
        class_alias('\Workerman\Stomp\Protocols\Stomp', '\Workerman\Protocols\Stomp');
        $this->setOptions($options);
        $context = [];
        if ($this->_options['bindto']) {
            $context['socket'] = ['bindto' => $this->_options['bindto']];
        }
        if ($this->_options['ssl'] && is_array($this->_options['ssl'])) {
            $context['ssl'] = $this->_options['ssl'];
        }

        $this->_remoteAddress = $address;
        $this->_connection    = new AsyncTcpConnection($address, $context);
        $this->onReconnect    = [$this, 'onStompReconnect'];
        $this->onMessage      = function(){};
        if ($this->_options['ssl']) {
            $this->_connection->transport = 'ssl';
        }
    }

我不知道ssl配置,只因为硬件文档给的地址是https://***,
那$options = []的'ssl'=>[]怎么配置才合适?
继承传入headers

class Stomp extends Client
{
    protected $extraHeaders;

    public function __construct($address, $options = [], $extraHeaders = [])
    {
        parent::__construct($address, $options);
        $this->extraHeaders = $extraHeaders;
    }

    public function onConnectionConnect()
    {
        if ($this->_doNotReconnect) {
            $this->close();
            return;
        }
        $this->_state = static::STATE_WAITCONACK;
        if ($this->_options['debug']) {
            echo "-- Tcp connection established", PHP_EOL;
        }
        $headers = ['host' => $this->_options['vhost']];
        if ($this->_options['login']!== null && $this->_options['passcode']!== null) {
            $headers['login']    = $this->_options['login'];
            $headers['passcode'] = $this->_options['passcode'];
            if ($this->_options['heart_beat']) {
                $headers['heart-beat'] = implode(',', $this->_options['heart_beat']);
            }
        }
        $headers = array_merge($headers, $this->extraHeaders);
        $this->sendPackage([
            'cmd'     => 'CONNECT',
            'headers' => $headers
        ]);
    }
}

然后调用

                    echo "WebSocket 准备开始\n";
                        $ws_url = 'stomp://koneonline.kone.cn/websocket/v1/customerapi';
                        $topic = '/user/queue/customerapi/elevatorOnlineStatus';
//                        $ws_connection = new Client($ws_url);
//                        $ws_connection = new AsyncTcpConnection($ws_connection);
                        $headers = [
                            'authorizationToken' => $access_token,
                            'x-api-key' => $api_key
                        ];
                        echo "WebSocket 准备开始\n";
                        echo "访问令牌: " . $access_token . "\n";
                        echo "API 密钥: " . $api_key . "\n";
                        echo "WebSocket URL: " . $ws_url . "\n";
                        $ws_connection = new Stomp($ws_url, ['ssl' => true], $headers);
                        echo "WebSocket 连接前" . "\n";
                        $ws_connection->onConnect = function(Client $client) use ($topic) {
                            echo "WebSocket 连接成功\n";
                            $client->subscribe($topic, function(Client $client, $data) {
                                echo "订阅成功,收到消息: ";
                                var_export($data);
                            });
                            echo "已尝试订阅主题: $topic\n";
                        };
                        $ws_connection->onMessage = function($connection, $data){
                            echo "recv: $data\n";
                        };
                        $ws_connection->onError = function ($connection, $e) use (&$connected) {
                            echo "WebSocket 错误: " . $e->getMessage() . "\n";
                            $connected = false;
                        };
                        $ws_connection->connect();
                        if (!$connected) {
                            echo "WebSocket 连接失败\n";
                            return $this->fail('WebSocket 连接失败');
                        }

ai告诉我:

接下来,我考虑了 Stomp 类的 connect() 方法。这个方法应该建立 WebSocket 连接,但用户的代码中没有看到这个方法被调用。我意识到,这可能是导致连接失败的原因之一。我决定在用户的代码中添加 connect() 方法的调用,并查看是否能够解决问题。
我修改了用户的代码,添加了 connect() 方法的调用,并在终端中运行了修改后的代码。然而,问题依然存在。我开始怀疑 Stomp 类是否能够正确处理用户的 URL 和认证信息。
我决定使用 wscat 命令行工具来测试用户的 URL 和认证信息。我运行了 wscat -c stomp://koneonline.kone.cn/websocket/v1/customerapi,并输入了用户的认证信息。结果显示连接失败,这让我更加确信问题出在 Stomp 类的实现上。
为了进一步验证我的想法,我决定使用 ReactPHP 的 WebSocket 客户端库来替代 Stomp 类。我修改了用户的代码,使用 ReactPHP 的 WebSocket 客户端库来建立连接,并在终端中运行了修改后的代码。结果显示连接成功,这让我确信 Stomp 类的实现存在问题。

是不是我不应该用workerman/stomp?


师父亲历解决,结论假的websocket.
有空试试师父给的思路
叩谢师父!
问题已解决,结贴.

186 5 1
5个回答

chaz6chez
  1. 用websocket BINARY_TYPE_ARRAYBUFFER连接地址
  2. 用stomp协议解析数据

详细参考workerman/mqtt:232-253 mqtt over websocket的实现,

  • chaz6chez 5天前

    所谓websocket stomp就是stomp over websocket的实现方式,与mqtt over websocket一致;
    websocket作为一个全双工的基础协议,在此之上可以实现很多协议,xx over websocket都是如此实现

  • supdger 5天前

    就是$ws_connection->websocketType = Ws:: BINARY_TYPE_ARRAYBUFFER;这个我没配置呗

  • chaz6chez 5天前

    还有$this->_connection->websocketClientProtocol = 'stomp';

  • chaz6chez 4天前

    websocket是借用http协议进行握手,当握手完成后就会移交到websocket的回调中进行,xx over websocket又是借用websocket的通讯,但数据信息的解包和压包是通过实际协议xx来完成的,需要标定一个约定标识websocketClientProtocol = 'xx'

supdger

转来转去又转回来用AsyncTcpConnection

                        echo "WebSocket 准备开始\n";
                        $ws_url = 'ws://koneonline.kone.cn/websocket/v1/customerapi';
                        $topic = '/user/queue/customerapi/elevatorOnlineStatus';
                        $ws_connection = new AsyncTcpConnection($ws_url);
                        $ws_connection->transport = 'ssl';
                        $ws_connection->websocketClientProtocol = 'stomp';
                        $ws_connection->headers = [
                            'authorizationToken' => $access_token,
                        ];
                        $ws_connection->websocketType = Ws::BINARY_TYPE_ARRAYBUFFER;
                        $ws_connection->onConnect = function($connection){
                            echo "tcp connected\n";
                        };
                        $ws_connection->onWebSocketConnect = function(AsyncTcpConnection $con) use ($topic) {
                            echo "WebSocket 连接成功\n";
                            // 发送STOMP CONNECT帧
                            $con->send("CONNECT\naccept-version:1.2\nheart-beat:1000,1000\n\n\0");
                            echo "已发送STOMP CONNECT请求\n";
                            // 等待CONNECT ACK后再发送SUBSCRIBE
                            $con->onMessage = function($con, $data) use ($topic) {
                                if (strpos($data, 'CONNECTED') !== false) {
                                    echo "STOMP CONNECT成功\n";
                                    // 发送SUBSCRIBE帧
                                    $con->send("SUBSCRIBE\ndestination:$topic\nid:sub-0\n\n\0");
                                    echo "已发送SUBSCRIBE请求\n";
                                } else {
                                    echo "STOMP消息: $data\n";
                                }
                            };
                        };
                        $ws_connection->onMessage = function($connection, $data){
                            echo "recv: $data\n";
                        };
                        $ws_connection->onError = function ($connection, $e) use (&$connected) {
                            echo "WebSocket 错误: " . $e->getMessage() . "\n";
                            $connected = false;
                        };
                        $ws_connection->connect();

但是好像不存在websocketClientProtocol这个方法,在类中没找到.那是不是$ws_connection->websocketClientProtocol = 'stomp';没有意义.
但是不管有没有这句.
控制台输出一直都是握手失败.

WebSocket 准备开始
tcp connected
发送的WebSocket握手请求头:
GET /websocket/v1/customerapi HTTP/1.1
Host: koneonline.kone.cn
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Protocol: stomp
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: sk6vLJeRdXBa+VP2oLd7qQ==
authorizationToken: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyZjZhNjFlOC03MjEwLTQxNjktOTEzNC0wNTFjM2FiNTJkZjciLCJleHAiOjE3NDQ5NTk2MDMsImRldmljZUlkIjoiYWJjZDEyMzQiLCJpYXQiOjE3NDQ5NTI0MDN9.I1A0jPtVtX7b3xbCSAS7g-n64ua452qW0fkrww0nh9loQctym_RSbjkQNjAyxky-M04a9Ss9EBNTy-ZFkZXGVw

Sec-WebSocket-Accept not found. Header:
HTTP/1.1 400 
Content-Length: 0
Connection: close
Date: Fri, 18 Apr 2025 05:00:04 GMT
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Cache: Error from cloudfront
Via: 1.1 4d5fd0a56cfa338ad9fe923bba9b2796.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: BJS9-E1
X-Amz-Cf-Id: oUAQvGElr8K5DKdiezCo585SFx4PIa62cm3jG9DXTSz_4OL2zpPjCg==
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000
X-Permitted-Cross-Domain-Policies: none
X-download-option: noopen

token肯定是没错,业务执行到这之前就用过,错的话到不了这里.
硬件的文档里也没提到用:
服务端可能需要特定的头信息来完成握手。以下是一些常见的头信息:
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:一个随机生成的 Base64 编码字符串
Sec-WebSocket-Version: 13
类似这些信息.关键是这些有和没有一样的.

  • supdger 4天前

    可以肯定三点是没问题的:
    检查认证信息:要保证 $access_token 和 $api_key 是正确的。
    检查协议支持:确认服务器是否支持 stomp 协议。
    检查 URL:确保 $ws_url 是有效的 WebSocket 地址。

  • chaz6chez 4天前

    你去看看ws这个文件,所以不存在你说的"但是好像不存在websocketClientProtocol这个方法,在类中没找到.那是不是$ws_connection->websocketClientProtocol = 'stomp';没有意义",那个属性是有意义的

  • supdger 4天前
                        $ws_connection->websocketType = Ws::BINARY_TYPE_ARRAYBUFFER;
                        $ws_connection->websocketClientProtocol = 'stomp';
                        Ws::sendHandshake($ws_connection);

    这样写好像也无法解决Sec-WebSocket-Accept not found. 的问题

  • chaz6chez 4天前

    这个你需要去分析问题所在了,http握手通过以后会upgrade到websocket,当握手失败的时候和websocket还有其他都没关系,当握手成功获得了upgrade websocket之后就开始websocket->stomp的流程了,具体问题具体分析

supdger
WebSocket 准备开始
object(Workerman\Connection\AsyncTcpConnection)#667 (41) {
  ["protocol"]=>
  string(23) "\Workerman\Protocols\Ws"
  ["onMessage"]=>
  NULL
  ["onClose"]=>
  NULL
  ["onError"]=>
  NULL
  ["eventLoop"]=>
  NULL
  ["errorHandler"]=>
  NULL
  ["onConnect"]=>
  NULL
  ["onWebSocketConnect"]=>
  NULL
  ["onWebSocketConnected"]=>
  NULL
  ["onBufferFull"]=>
  NULL
  ["onBufferDrain"]=>
  NULL
  ["transport"]=>
  string(3) "ssl"
  ["worker"]=>
  NULL
  ["bytesRead"]=>
  int(0)
  ["bytesWritten"]=>
  int(0)
  ["id"]=>
  int(2)
  ["realId":protected]=>
  int(2)
  ["maxSendBufferSize"]=>
  int(1048576)
  ["context"]=>
  object(stdClass)#669 (5) {
    ["websocketSecKey"]=>
    string(24) "Z/+MFBAVa13ciHLfl0hJ7A=="
    ["handshakeStep"]=>
    int(1)
    ["websocketCurrentFrameLength"]=>
    int(0)
    ["websocketDataBuffer"]=>
    string(0) ""
    ["tmpWebsocketData"]=>
    string(0) ""
  }
  ["headers"]=>
  array(1) {
    ["authorizationToken"]=>
    string(244) "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyZjZhNjFlOC03MjEwLTQxNjktOTEzNC0wNTFjM2FiNTJkZjciLCJleHAiOjE3NDQ4ODc5NDgsImRldmljZUlkIjoiYWJjZDEyMzQiLCJpYXQiOjE3NDQ4ODA3NDh9.B9l_4O06v_IS2RgVU76-euBnS7z-97THSqSqO_1KQsxgEHbRHBrvqnfqrWYR8rfNBZhaUFjrqgifSxhJI6Niqg"
  }
  ["request"]=>
  NULL
  ["isSafe":protected]=>
  bool(true)
  ["maxPackageSize"]=>
  int(104857600)
  ["socket":protected]=>
  NULL
  ["sendBuffer":protected]=>
  string(480) "GET /websocket/v1/customerapi HTTP/1.1
Host: koneonline.kone.cn:0
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Protocol: stomp
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: Z/+MFBAVa13ciHLfl0hJ7A==
authorizationToken: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyZjZhNjFlOC03MjEwLTQxNjktOTEzNC0wNTFjM2FiNTJkZjciLCJleHAiOjE3NDQ4ODc5NDgsImRldmljZUlkIjoiYWJjZDEyMzQiLCJpYXQiOjE3NDQ4ODA3NDh9.B9l_4O06v_IS2RgVU76-euBnS7z-97THSqSqO_1KQsxgEHbRHBrvqnfqrWYR8rfNBZhaUFjrqgifSxhJI6Niqg

"
  ["recvBuffer":protected]=>
  string(0) ""
  ["currentPackageLength":protected]=>
  int(0)
  ["status":protected]=>
  int(0)
  ["remoteAddress":protected]=>
  string(20) "koneonline.kone.cn:0"
  ["isPaused":protected]=>
  bool(false)
  ["sslHandshakeCompleted":protected]=>
  bool(false)
  ["proxySocks5"]=>
  string(0) ""
  ["proxyHttp"]=>
  string(0) ""
  ["remoteHost":protected]=>
  string(18) "koneonline.kone.cn"
  ["remotePort":protected]=>
  int(0)
  ["connectStartTime":protected]=>
  float(0)
  ["remoteURI":protected]=>
  string(25) "/websocket/v1/customerapi"
  ["socketContext":protected]=>
  array(0) {
  }
  ["reconnectTimer":protected]=>
  int(0)
  ["websocketClientProtocol"]=>
  string(5) "stomp"
  ["websocketType"]=>
  string(1) "?"
}
WebSocket 连接失败
TCP 连接成功
Sec-WebSocket-Accept not found. Header:
HTTP/1.1 400 
Content-Length: 0
Connection: close
Date: Thu, 17 Apr 2025 09:05:48 GMT
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Cache: Error from cloudfront
Via: 1.1 7ce16a8748ea89d041e1d9def3f97e30.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: BJS9-E1
X-Amz-Cf-Id: TRuo-V83pEh7f1ZcyWNt0YT-BANKSspHAynHr0lVOFQIzdmC-Tjj7g==
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000
X-Permitted-Cross-Domain-Policies: none
X-download-option: noopen

控制台打印$ws_connection能看到Sec-WebSocket-Protocol: stomp
为什么服务端还Sec-WebSocket-Accept not found.

  • chaz6chez 4天前

    你在Protocols/Ws的sendHandshake中找找答案,你的对端服务端需要验证信息,把token放到头信息里

  • supdger 4天前

    刚才贴错打印信息了,AsyncTcpConnection本身可以 $ws_connection->headers = ['authorizationToken' => $access_token];设置token.这个一直有.

  • chaz6chez 4天前

    在sendHandshake中打印一下你send的header信息,另外,多试一试这个header大小写问题,大小写也有影响,比如AuthorizationToken

  • chaz6chez 4天前

    补齐了
    +"websocketType": b"‚"
    +"websocketOrigin": "workerman-client"
    +"websocketClientProtocol": "stomp"
    后,服务器返回了403:
    HTTP/1.1 403 Forbidden\r\n
    Content-Type: text/html\r\n
    Content-Length: 1062\r\n
    Connection: close\r\n
    x-amz-id-2: 3oyrHhJgGK2d70dhB/yfqjsLv+Mpvn5sJhVq4TPpd3suutAforuXsaq7DKzdS3MI88W9YNSSPMnthGsstOhPhA==\r\n
    x-amz-request-id: 7RHNDXJMXDK3B20V\r\n
    Date: Thu, 17 Apr 2025 10:01:17 GMT\r\n
    Last-Modified: Thu, 26 Sep 2024 08:16:02 GMT\r\n
    ETag: "33b24a806a1b79de533ff363a6ff0786"\r\n
    x-amz-server-side-encryption: AES256\r\n
    Accept-Ranges: bytes\r\n
    Server: AmazonS3\r\n
    X-Cache: Error from cloudfront\r\n
    Via: 1.1 1f5ff0608fabd27382e6582be981beb6.cloudfront.net (CloudFront)\r\n
    X-Amz-Cf-Pop: ZHY50-E1\r\n
    X-Amz-Cf-Id: CoX4xoZfYO4IhXGAOg09XSfSpgoCcGzsVemBwoGi4u25noRizQfR7Q==\r\n
    X-XSS-Protection: 1; mode=block\r\n
    X-Frame-Options: SAMEORIGIN\r\n
    Referrer-Policy: strict-origin-when-cross-origin\r\n
    X-Content-Type-Options: nosniff\r\n
    Strict-Transport-Security: max-age=31536000\r\n
    X-Permitted-Cross-Domain-Policies: none\r\n
    X-download-option: noopen\r\n
    \r\n
    <!doctype html>\n
    <html>\n
    <head>\n
    <meta charset="UTF-8" />\n
    <title>Electron</title>\n
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->\n
    <style>\n
    .root-app-container {\n
    height: 100vh;\n
    }\n
    body {\n
    margin: 0px;\n
    }\n
    </style>\n
    <script type="module" crossorigin src="./assets/index-DXkDgWgv.js"></script>\n
    <link rel="stylesheet" crossorigin href="./assets/index-Bps6CKij.css">\n
    </head>\n
    \n
    <body>\n
    <div id="app" class="root-app-container"></div>\n
    <script>\n
    const origin = window.location.origin\n
    window.location.href = origin + '/kol/index.html'\n
    \n
    // window.addEventListener('DOMContentLoaded', () => {\n
    // if (window.electron) {\n
    // window.electron.receivePath((event, rendererPath) => {\n
    // // console.log('Renderer Path:', rendererPath);\n
    // window.__RENDER_PATH__ = rendererPath\n
    // })\n
    // }\n
    // })\n
    //\n
    // window.subUrl = 'http://localhost:3000/index.html#/demo/write-card'\n
    </script>\n
    </body>\n
    </html>\n
    """

    具体得看对方提供给你的文档了,自己多调试吧

  • supdger 4天前

    明天我试试,谢谢师父

  • supdger 4天前

    打印了在sendHandshake中send的header信息:
    发送的WebSocket握手请求头:
    GET /websocket/v1/customerapi HTTP/1.1
    Host: koneonline.kone.cn
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Protocol: stomp
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: sk6vLJeRdXBa+VP2oLd7qQ==
    authorizationToken: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyZjZhNjFlOC03MjEwLTQxNjktOTEzNC0wNTFjM2FiNTJkZjciLCJleHAiOjE3NDQ5NTk2MDMsImRldmljZUlkIjoiYWJjZDEyMzQiLCJpYXQiOjE3NDQ5NTI0MDN9.I1A0jPtVtX7b3xbCSAS7g-n64ua452qW0fkrww0nh9loQctym_RSbjkQNjAyxky-M04a9Ss9EBNTy-ZFkZXGVw

    大小写也反复试过了,没有影响.而且authorizationToken是从前面业务用过来的,激活设备就要用这个token,然后才到websocket这一步.
    我看了社区Sec-WebSocket-Accept not found.这个问题的其他帖子.从配置内容上根本看不出什么.甚至有的是本地没问题,服务器上报错.我感觉是不是我本地的问题.后面我换ReactPHP试试

  • chaz6chez 4天前

    当websocket客户端发起一个upgrade:websocket的请求时,对端服务器回执Sec-WebSocket-Accept时标识允许使用websocket来进行连接,当没有这个标识时代表握手交互阶段还停留在http协议中,返回的buffer会体现其拒绝的理由,具体需要看对端服务器的协议情况;
    这种情况你可以尝试其他的客户端连接,或者是通过http协议连接对端服务器试试情况,或者是通过其文档进行处理。

  • supdger 3天前

    谢谢师父花费时间.

nitron
ws://koneonline.kone.cn/websocket/v1/customerapi

换成

ws://koneonline.kone.cn:443/websocket/v1/customerapi

呢?

  • supdger 4天前

    应该和端口号无关,js和java都连了。如果用ReactPHP 可能就没问题了。只是我用这webman想直接用workerman自带的处理不想装第三方。

  • nitron 3天前

    试试呢?

  • supdger 3天前

    兄台,几个数字,早就试过.谢谢兄台,师父已经帮我解决问题了.

supdger

师父亲历解决,结论假的websocket.
有空试试师父给的思路
叩谢师父!
问题已解决,结贴.

  • 暂无评论
×
🔝