不知道硬件文檔說的websocket stomp協(xié)議是不是可以用workerman/stomp.
總之,在
http://m.wtbis.cn/doc/workerman/faq/as-wss-client.html
試了不行.
但是根據(jù)文檔:http://m.wtbis.cn/doc/workerman/components/workerman-stomp.html
用workerman/stomp,并參考Client類.
好像ws/wss客戶端文檔中
$con->transport = 'ssl';
$ws_connection->headers = ['token' => 'value'];
這些用法都無效.
看Client類的onConnectionConnect方法比較符合硬件文檔說的連接時在header中傳入授權(quán)信息.
是不是我需要重寫onConnectionConnect方法傳入授權(quán)?
$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
]);
}
}
然后調(diào)用
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 連接,但用戶的代碼中沒有看到這個方法被調(diào)用。我意識到,這可能是導致連接失敗的原因之一。我決定在用戶的代碼中添加 connect() 方法的調(diào)用,并查看是否能夠解決問題。
我修改了用戶的代碼,添加了 connect() 方法的調(diào)用,并在終端中運行了修改后的代碼。然而,問題依然存在。我開始懷疑 Stomp 類是否能夠正確處理用戶的 URL 和認證信息。
我決定使用 wscat 命令行工具來測試用戶的 URL 和認證信息。我運行了 wscat -c stomp://koneonline.kone.cn/websocket/v1/customerapi,并輸入了用戶的認證信息。結(jié)果顯示連接失敗,這讓我更加確信問題出在 Stomp 類的實現(xiàn)上。
為了進一步驗證我的想法,我決定使用 ReactPHP 的 WebSocket 客戶端庫來替代 Stomp 類。我修改了用戶的代碼,使用 ReactPHP 的 WebSocket 客戶端庫來建立連接,并在終端中運行了修改后的代碼。結(jié)果顯示連接成功,這讓我確信 Stomp 類的實現(xiàn)存在問題。
是不是我不應該用workerman/stomp?
師父親歷解決,結(jié)論假的websocket.
有空試試師父給的思路
叩謝師父!
問題已解決,結(jié)貼.
詳細參考workerman/mqtt:232-253 mqtt over websocket的實現(xiàn),
所謂websocket stomp就是stomp over websocket的實現(xiàn)方式,與mqtt over websocket一致;
websocket作為一個全雙工的基礎(chǔ)協(xié)議,在此之上可以實現(xiàn)很多協(xié)議,xx over websocket都是如此實現(xiàn)
websocket是借用http協(xié)議進行握手,當握手完成后就會移交到websocket的回調(diào)中進行,xx over websocket又是借用websocket的通訊,但數(shù)據(jù)信息的解包和壓包是通過實際協(xié)議xx來完成的,需要標定一個約定標識websocketClientProtocol = 'xx'
轉(zhuǎn)來轉(zhuǎn)去又轉(zhuǎn)回來用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";
// 發(fā)送STOMP CONNECT幀
$con->send("CONNECT\naccept-version:1.2\nheart-beat:1000,1000\n\n\0");
echo "已發(fā)送STOMP CONNECT請求\n";
// 等待CONNECT ACK后再發(fā)送SUBSCRIBE
$con->onMessage = function($con, $data) use ($topic) {
if (strpos($data, 'CONNECTED') !== false) {
echo "STOMP CONNECT成功\n";
// 發(fā)送SUBSCRIBE幀
$con->send("SUBSCRIBE\ndestination:$topic\nid:sub-0\n\n\0");
echo "已發(fā)送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
發(fā)送的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肯定是沒錯,業(yè)務執(zhí)行到這之前就用過,錯的話到不了這里.
硬件的文檔里也沒提到用:
服務端可能需要特定的頭信息來完成握手。以下是一些常見的頭信息:
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:一個隨機生成的 Base64 編碼字符串
Sec-WebSocket-Version: 13
類似這些信息.關(guān)鍵是這些有和沒有一樣的.
可以肯定三點是沒問題的:
檢查認證信息:要保證 $access_token 和 $api_key 是正確的。
檢查協(xié)議支持:確認服務器是否支持 stomp 協(xié)議。
檢查 URL:確保 $ws_url 是有效的 WebSocket 地址。
你去看看ws這個文件,所以不存在你說的"但是好像不存在websocketClientProtocol這個方法,在類中沒找到.那是不是$ws_connection->websocketClientProtocol = 'stomp';沒有意義",那個屬性是有意義的
$ws_connection->websocketType = Ws::BINARY_TYPE_ARRAYBUFFER;
$ws_connection->websocketClientProtocol = 'stomp';
Ws::sendHandshake($ws_connection);
這樣寫好像也無法解決Sec-WebSocket-Accept not found. 的問題
這個你需要去分析問題所在了,http握手通過以后會upgrade到websocket,當握手失敗的時候和websocket還有其他都沒關(guān)系,當握手成功獲得了upgrade websocket之后就開始websocket->stomp的流程了,具體問題具體分析
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.
剛才貼錯打印信息了,AsyncTcpConnection本身可以 $ws_connection->headers = ['authorizationToken' => $access_token];設置token.這個一直有.
在sendHandshake中打印一下你send的header信息,另外,多試一試這個header大小寫問題,大小寫也有影響,比如AuthorizationToken
補齊了
+"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
"""
具體得看對方提供給你的文檔了,自己多調(diào)試吧
打印了在sendHandshake中send的header信息:
發(fā)送的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是從前面業(yè)務用過來的,激活設備就要用這個token,然后才到websocket這一步.
我看了社區(qū)Sec-WebSocket-Accept not found.這個問題的其他帖子.從配置內(nèi)容上根本看不出什么.甚至有的是本地沒問題,服務器上報錯.我感覺是不是我本地的問題.后面我換ReactPHP試試
當websocket客戶端發(fā)起一個upgrade:websocket的請求時,對端服務器回執(zhí)Sec-WebSocket-Accept時標識允許使用websocket來進行連接,當沒有這個標識時代表握手交互階段還停留在http協(xié)議中,返回的buffer會體現(xiàn)其拒絕的理由,具體需要看對端服務器的協(xié)議情況;
這種情況你可以嘗試其他的客戶端連接,或者是通過http協(xié)議連接對端服務器試試情況,或者是通過其文檔進行處理。
ws://koneonline.kone.cn/websocket/v1/customerapi
換成
ws://koneonline.kone.cn:443/websocket/v1/customerapi
呢?