Примерно так. Одно время копал в сторону веб-сокетов. Есть готовые наработки для дэймона и для чата. На просторах интернета готовый код не нашёл, только библиотеки, поэтому подгонял по RFC сам.
Общий принцип - правильно кодировать и декодировать. Ну и само собой у серверов, куда подключаешься, тоже свои заморочки имеются.
$context = stream_context_create(
[
'ssl' => [
'disable_compression' => true
],
]
);
$fp = stream_socket_client("ssl://example.com:9443", $errstr, $errno, 30, STREAM_CLIENT_CONNECT, $context);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
echo "true\n";
fwrite($fp, "POST /ws/stream HTTP/1.1\r\n" .
"Host: example.com\r\n" .
"Accept: */*\r\n" .
"Cache-Control: no-cache\r\n" .
"Sec-WebSocket-Key: qwerty123\r\n" .
"X-Mbx-Apikey: $akey\r\n" .
"Connection: Upgrade\r\n" .
"Upgrade: websocket\r\n\r\n");
$old = time() + 180000000;
fwrite($fp, $data);
while (!feof($fp)) {
$stat = fstat($fp);
$fp2 = fread($fp, 8192);
$new = time();
$fp2 = decode($fp2);
if ($fp2['opcode'] == 9) {
fwrite($fp, encode($new, 'ping'));
echo "\nPING\n\n";
};
if (isset(json_decode($fp2['message'])->data->p)) $fp3 = json_decode($fp2['message'])->data->p;
if (isset($fp3)) echo $fp3."\n";
print_r($fp2)."\n";
}
}
fclose($fp);
echo('close');
function decode($str, $message_only = false)
{
/* Разбираем заголовок */
if (empty($str)) return "\n\n Пустое значение \n\n";
$header = unpack("n", $str)[1]; /* Рапаковываем первые 16 бит, порядок байт «big endian», т.к. сетевой протокол */
$data = [];
$data['fin'] = (bool) (($header >> (16 - 1)) & 0b1); /* Финальный фрейм. Если сообщение нефрагмантированное, то всегда 1, если фрагмантированно, то у последнего 1 у остальных 0 */
$data['rsv1'] = (bool) (($header >> (16 - 2)) & 0b1); /* Флаги RSV1, RSV2, RSV3 служат для расширений протокола, почти всегда в false */
$data['rsv2'] = (bool) (($header >> (16 - 3)) & 0b1);
$data['rsv3'] = (bool) (($header >> (16 - 4)) & 0b1);
$data['opcode'] = (int) (($header >> (16 - 8)) & 0b1111); /* Тип фрейма */
$data['is_mask'] = (bool) (($header >> (16 - 9)) & 0b1); /* Замаскированы ли фреймы */
$data['length_prev'] = (int) (($header >> (16 - 16)) & 0b1111111); /* Предварительная длина фрейма */
/* Определяем тип фрейма */
switch ($data['opcode']) {
case '1':
$data['type'] = '1'; //text
break;
case '2':
$data['type'] = '2'; //binary
break;
case '8':
$data['type'] = '8'; //close
break;
case '9':
$data['type'] = '9'; //ping
break;
case '10':
$data['type'] = '10'; //pong
break;
default:
break;
}
/* Определяем длину фрейма */
if ($data['length_prev'] < 126)
{
$data['length'] = $data['length_prev'];
}
elseif ($data['length_prev'] === 126)
{
$data['length'] = unpack("x2/n", $str)[1];
}
elseif ($data['length_prev'] > 126)
{
// $data['length'] = unpack("x2/J", $str)[1];
$data['length'] = unpack("x2/x4/N", $str)[1];
}
/* Маска */
if ($data['is_mask'])
{
if ($data['length_prev'] < 126)
{
$mask = substr($str, 2, 4);
}
elseif ($data['length_prev'] === 126)
{
$mask = substr($str, 2 + 2, 4);
}
elseif ($data['length_prev'] > 126)
{
$mask = substr($str, 2 + 8, 4);
}
}
/* Тело запроса */
$message_start = 2;
if ($data['length_prev'] < 126)
{
}
elseif ($data['length_prev'] === 126)
{
$message_start += 2;
}
elseif ($data['length_prev'] > 126)
{
$message_start += 8;
}
if ($data['is_mask'])
{
$message_start += 4;
}
$data['message_start'] = $message_start;
$message = substr($str, $message_start, $data['length']);
/* Размаскируем сообщение */
if ($data['is_mask'])
{
$length = strlen($message);
for ($i = 0; $i < $length; $i++)
{
$message[$i] = $message[$i] ^ $mask[$i % 4];
}
}
$data['message'] = $message;
/* Возвращаем сообщение */
if ($message_only === false)
{
return $data;
}
else
{
return $data['message'];
}
}
/**
* Закодировать строку
*
* @param string $str
* @param boolean $is_mask
* @return string
*/
function encode($str, $ping = false, $is_mask = false)
{
$bin = "";
/* Основные флаги */
$data =
[
"fin" => 0b1,
"rsv1" => 0b0,
"rsv2" => 0b0,
"rsv3" => 0b0,
"opcode" => 0x1,
"is_mask" => $is_mask === true ? 0b1 : 0b0,
"length" => strlen($str)
];
if ($ping = 'ping') $data['opcode'] = 0xA;
/* Предварительная длина */
if ($data['length'] < 126)
{
$data['length_prev'] = $data['length'];
}
elseif ($data['length'] < 65536)
{
$data['length_prev'] = 126;
}
else
{
$data['length_prev'] = 127;
}
/* Заголовок */
$header = $data['fin'];
$header = $header << 1 | $data['rsv1'];
$header = $header << 1 | $data['rsv2'];
$header = $header << 1 | $data['rsv3'];
$header = $header << 4 | $data['opcode'];
$header = $header << 1 | $data['is_mask'];
$header = $header << 7 | $data['length_prev'];
$bin .= pack("n", $header);
/* Расширенная длина тела */
if ($data['length_prev'] === 126)
{
$bin .= pack("n", $data['length']);
}
elseif ($data['length_prev'] > 126)
{
$bin .= pack("x4N", $data['length']);
}
/* Маскировать сообщение */
if ($is_mask)
{
$mask = substr(md5(microtime()), 0, 4);
$bin .= $mask;
$length = strlen($str);
for ($i = 0; $i < $length; $i++)
{
$str[$i] = $str[$i] ^ $mask[$i % 4];
}
}
$bin .= $str;
print_r($bin)."\n";
return $bin;
}