使用 PHP 客户端连接到 websocket

Posted

技术标签:

【中文标题】使用 PHP 客户端连接到 websocket【英文标题】:Connecting to websocket with PHP client 【发布时间】:2014-04-17 17:38:54 【问题描述】:

我正在尝试将基于 php 的客户端连接到 websocket 服务器。

这是我一直在使用的代码,它已在不同的论坛上广泛发布。但由于某种原因,我无法让它工作。

任何帮助将不胜感激。

$host = 'host';  //where is the websocket server
$port = 443; //ssl
$local = "http://www.example.com/";  //url where this script run
$data = '"id": 2,"command": "server_info"';  //data to be send

$head =        "GET / HTTP/1.1"."\r\n".
               "Upgrade: WebSocket"."\r\n".
               "Connection: Upgrade"."\r\n".
               "Origin: $local"."\r\n".
               "Host: $host"."\r\n".
               "Content-Length: ".strlen($data)."\r\n"."\r\n";
////WebSocket handshake
$sock = fsockopen($host, $port, $errno, $errstr, 2);
fwrite($sock, $head ) or die('error:'.$errno.':'.$errstr);
$headers = fread($sock, 2000);
fwrite($sock, "\x00$data\xff" ) or die('error:'.$errno.':'.$errstr);
$wsdata = fread($sock, 2000);  //receives the data included in the websocket package "\x00DATA\xff"
$retdata = trim($wsdata,"\x00\xff"); //extracts data
////WebSocket handshake
fclose($sock);

echo $retdata;

【问题讨论】:

请显示,您如何尝试连接 【参考方案1】:

我可能更喜欢使用现有的 websocket 客户端库(可能是 https://github.com/gabrielbull/php-websocket-client 或 https://github.com/Devristo/phpws/tree/master/src/Devristo/Phpws/Client ?)而不是自己动手,但我至少可以通过以下方式连接:

$head = "GET / HTTP/1.1"."\r\n".
    "Host: $host"."\r\n".
    "Upgrade: websocket"."\r\n".
    "Connection: Upgrade"."\r\n".
    "Sec-WebSocket-Key: asdasdaas76da7sd6asd6as7d"."\r\n".
    "Sec-WebSocket-Version: 13"."\r\n".
    "Content-Length: ".strlen($data)."\r\n"."\r\n";

我的服务器正在使用 TLS/SSL,所以我还需要:

$sock = fsockopen('tls://'.$host, $port, $errno, $errstr, 2);

完整的协议规范是:https://tools.ietf.org/rfc/rfc6455.txt

【讨论】:

这个回复让我开始朝着正确的方向前进。通过对其他一些论坛的更多搜索和对标头代码的调整,我发现了以下解决方案。 ripple.com/forum/…我可以确认它有效! 那ripple.com 的帖子也对我有用!站点的链接结构必须已更改。新链接在这里:forum.ripple.com/…【参考方案2】:

UPDATE 2019:许多服务器要求密钥比原始示例更唯一,导致无法建立升级连接。密钥生成现在已相应更改。

您的标题必须包含:

Sec-WebSocket-Key: (some value) 
Sec-WebSocket-Version: 13

连接成功。 建立连接后,还需要使用 hybi10 帧编码。 请参阅:https://tools.ietf.org/rfc/rfc6455.txt - 虽然有点干。

我做了这个工作示例:

<?php
$sp=websocket_open('127.0.0.1/ws_request.php?param=php_test',$errstr);
websocket_write($sp,"Websocket request message");
echo websocket_read($sp,true);

$sp=websocket_open('127.0.0.1:8080/ws_request.php?param=php_test',$errstr);
websocket_write($sp,"Websocket request message");
echo websocket_read($sp,true);

function websocket_open($url)
  $key=base64_encode(openssl_random_pseudo_bytes(16));
  $query=parse_url($url);
  $header="GET / HTTP/1.1\r\n"
    ."pragma: no-cache\r\n"
    ."cache-control: no-cache\r\n"
    ."Upgrade: WebSocket\r\n"
    ."Connection: Upgrade\r\n"
    ."Sec-WebSocket-Key: $key\r\n"
    ."Sec-WebSocket-Version: 13\r\n"
    ."\r\n";
  $sp=fsockopen($query['host'],$query['port'], $errno, $errstr,1); 
  if(!$sp) die("Unable to connect to server ".$url);
  // Ask for connection upgrade to websocket
  fwrite($sp,$header);
  stream_set_timeout($sp,5);
  $reaponse_header=fread($sp, 1024);
  if(!strpos($reaponse_header," 101 ") 
      || !strpos($reaponse_header,'Sec-WebSocket-Accept: '))
    die("Server did not accept to upgrade connection to websocket"
        .$reaponse_header);
      
  return $sp;


function websocket_write($sp, $data,$final=true)
  // Assamble header: FINal 0x80 | Opcode 0x02
  $header=chr(($final?0x80:0) | 0x02); // 0x02 binary

  // Mask 0x80 | payload length (0-125) 
  if(strlen($data)<126) $header.=chr(0x80 | strlen($data));  
  elseif (strlen($data)<0xFFFF) $header.=chr(0x80 | 126) . pack("n",strlen($data));
  elseif(PHP_INT_SIZE>4) // 64 bit 
    $header.=chr(0x80 | 127) . pack("Q",strlen($data));
  else  // 32 bit (pack Q dosen't work)
    $header.=chr(0x80 | 127) . pack("N",0) . pack("N",strlen($data));

  // Add mask
  $mask=pack("N",rand(1,0x7FFFFFFF));       
  $header.=$mask;

  // Mask application data. 
  for($i = 0; $i < strlen($data); $i++)
    $data[$i]=chr(ord($data[$i]) ^ ord($mask[$i % 4]));

  return fwrite($sp,$header.$data);    


function websocket_read($sp,$wait_for_end=true,&$err='')
  $out_buffer="";
  do
    // Read header
    $header=fread($sp,2);
    if(!$header) die("Reading header from websocket failed");
    $opcode = ord($header[0]) & 0x0F;
    $final = ord($header[0]) & 0x80;
    $masked = ord($header[1]) & 0x80;
    $payload_len = ord($header[1]) & 0x7F;

    // Get payload length extensions
    $ext_len = 0;
    if($payload_len >= 0x7E)
      $ext_len = 2;
      if($payload_len == 0x7F) $ext_len = 8;
      $ext=fread($sp,$ext_len);
      if(!$ext) die("Reading header extension from websocket failed");

      // Set extented paylod length
      $payload_len= 0;
      for($i=0;$i<$ext_len;$i++) 
        $payload_len += ord($header[$i]) << ($ext_len-$i-1)*8;
    

    // Get Mask key
    if($masked)
      $mask=fread($sp,4);
      if(!$mask) die("Reading header mask from websocket failed");
    

    // Get payload
    $frame_data='';
    do
      $frame= fread($sp,$payload_len);
      if(!$frame) die("Reading from websocket failed.");
      $payload_len -= strlen($frame);
      $frame_data.=$frame;
    while($payload_len>0);    

    // if opcode ping, reuse headers to send a pong and continue to read
    if($opcode==9)
      // Assamble header: FINal 0x80 | Opcode 0x02
      $header[0]=chr(($final?0x80:0) | 0x0A); // 0x0A Pong
      fwrite($sp,$header.$ext.$mask.$frame_data);

    // Recieve and unmask data
    elseif($opcode<3)
      $data="";
      if($masked)
        for ($i = 0; $i < $data_len; $i++) 
          $data.= $frame_data[$i] ^ $mask[$i % 4];
      else    
        $data.= $frame_data;
      $out_buffer.=$data;
    

    // wait for Final 
  while($wait_for_end && !$final);

  return $out_buffer;

您可以在这里获得完整版:https://github.com/paragi/PHP-websocket-client

【讨论】:

"事件服务器不接受升级到 websocket 的连接。"怎么办? 这取决于您的设置和服务器的响应。你可以打印出 $reaponse_header 看看我是否给出了线索​​。您的网络服务器是否支持您使用的端口上的 websocket? 我遇到了一个无法连接的问题,但不知道为什么。我正在使用来自 github 的最新代码。 服务器不接受升级连接到 websocket.HTTP/1.1 400 Bad Request X-Powered-By: Ratchet/0.3.6 256,在我的情况下尝试连接到 localhost websocket 服务器棘轮跨度> github上的代码在生产环境中运行。请不要使用 cmets 对您的应用程序进行故障排除。欢迎您给我发邮件。如果代码有问题。请确保有一个可测试的设置:)【参考方案3】:

使用纯 php 连接到 WSS stream:

以公众binance wss api为例。

<?php
$sock = stream_socket_client("tls://stream.binance.com:9443",$error,$errnum,30,STREAM_CLIENT_CONNECT,stream_context_create(null));
if (!$sock) 
    echo "[$errnum] $error" . PHP_EOL;
 else 
  echo "Connected - Do NOT get rekt!" . PHP_EOL;
  fwrite($sock, "GET /stream?streams=btcusdt@kline_1m HTTP/1.1\r\nHost: stream.binance.com:9443\r\nAccept: */*\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: ".rand(0,999)."\r\n\r\n");
  while (!feof($sock)) 
    var_dump(explode(",",fgets($sock, 512)));
  

更多详情:WebSockets - send json data via php

【讨论】:

【参考方案4】:

400 错误是因为您在标头中缺少 HostOrigin

$key=base64_encode(openssl_random_pseudo_bytes(16));
$query=parse_url($url);
$local = "http://".$query['host'];
if (isset($_SERVER['REMOTE_ADDR'])) $local = "http://".$_SERVER['REMOTE_ADDR'];  
$header="GET / HTTP/1.1\r\n"
    ."Host: ".$query['host']."\r\n"
    ."Origin: ".$local."\r\n"
    ."Pragma: no-cache\r\n"
    ."Cache-Control: no-cache\r\n"
    ."Upgrade: websocket\r\n"
    ."Connection: Upgrade\r\n"
    ."Sec-WebSocket-Key: $key\r\n"
    ."Sec-WebSocket-Version: 13\r\n"
    ."\r\n";

【讨论】:

【参考方案5】:

https://github.com/ratchetphp/Pawl

我从各种 *** 线程中尝试了大约 10 种不同的解决方案,但都没有奏效,在花了大约 6 个小时尝试不同的解决方案后,这对我有用。 设想: 棘轮作为服务器 我能够通过 reactjs 连接 我无法通过php连接, 预期结果:从 php-client 向所有连接的 react 客户端发送消息。我提供的 git repo 是我一直在寻找的一个好机会。

【讨论】:

这为我指明了正确的方向!谢谢。

以上是关于使用 PHP 客户端连接到 websocket的主要内容,如果未能解决你的问题,请参考以下文章

PHP Websocket客户端[关闭]

.NET 客户端应用程序连接到 WebSocket 服务器的最佳方式是啥?

使用 Echo 客户端示例从 Qt UI 连接到 WebSocket 服务器

JavaScript 客户端无法连接到 Spring 4 WebSocket

将 docker 容器中的 websocket 客户端连接到主机中的 websocket 服务器

Kubernetes Python客户端:使用Autobahn websocket连接到pod /服务/使用bearer token连接