不知道硬件文档说的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.
有空试试师父给的思路
叩谢师父!
问题已解决,结贴.
详细参考workerman/mqtt:232-253 mqtt over websocket的实现,
所谓websocket stomp就是stomp over websocket的实现方式,与mqtt over websocket一致;
websocket作为一个全双工的基础协议,在此之上可以实现很多协议,xx over websocket都是如此实现
就是$ws_connection->websocketType = Ws:: BINARY_TYPE_ARRAYBUFFER;这个我没配置呗
还有$this->_connection->websocketClientProtocol = 'stomp';
websocket是借用http协议进行握手,当握手完成后就会移交到websocket的回调中进行,xx over websocket又是借用websocket的通讯,但数据信息的解包和压包是通过实际协议xx来完成的,需要标定一个约定标识websocketClientProtocol = 'xx'
转来转去又转回来用AsyncTcpConnection
但是好像不存在websocketClientProtocol这个方法,在类中没找到.那是不是$ws_connection->websocketClientProtocol = 'stomp';没有意义.
但是不管有没有这句.
控制台输出一直都是握手失败.
token肯定是没错,业务执行到这之前就用过,错的话到不了这里.
硬件的文档里也没提到用:
服务端可能需要特定的头信息来完成握手。以下是一些常见的头信息:
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:一个随机生成的 Base64 编码字符串
Sec-WebSocket-Version: 13
类似这些信息.关键是这些有和没有一样的.
可以肯定三点是没问题的:
检查认证信息:要保证 $access_token 和 $api_key 是正确的。
检查协议支持:确认服务器是否支持 stomp 协议。
检查 URL:确保 $ws_url 是有效的 WebSocket 地址。
你去看看ws这个文件,所以不存在你说的"但是好像不存在websocketClientProtocol这个方法,在类中没找到.那是不是$ws_connection->websocketClientProtocol = 'stomp';没有意义",那个属性是有意义的
这样写好像也无法解决Sec-WebSocket-Accept not found. 的问题
这个你需要去分析问题所在了,http握手通过以后会upgrade到websocket,当握手失败的时候和websocket还有其他都没关系,当握手成功获得了upgrade websocket之后就开始websocket->stomp的流程了,具体问题具体分析
控制台打印$ws_connection能看到Sec-WebSocket-Protocol: stomp
为什么服务端还Sec-WebSocket-Accept not found.
你在Protocols/Ws的sendHandshake中找找答案,你的对端服务端需要验证信息,把token放到头信息里
刚才贴错打印信息了,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
"""
具体得看对方提供给你的文档了,自己多调试吧
明天我试试,谢谢师父
打印了在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试试
当websocket客户端发起一个upgrade:websocket的请求时,对端服务器回执Sec-WebSocket-Accept时标识允许使用websocket来进行连接,当没有这个标识时代表握手交互阶段还停留在http协议中,返回的buffer会体现其拒绝的理由,具体需要看对端服务器的协议情况;
这种情况你可以尝试其他的客户端连接,或者是通过http协议连接对端服务器试试情况,或者是通过其文档进行处理。
谢谢师父花费时间.
换成
呢?
应该和端口号无关,js和java都连了。如果用ReactPHP 可能就没问题了。只是我用这webman想直接用workerman自带的处理不想装第三方。
试试呢?
兄台,几个数字,早就试过.谢谢兄台,师父已经帮我解决问题了.
师父亲历解决,结论假的websocket.
有空试试师父给的思路
叩谢师父!
问题已解决,结贴.