Настройка WebSocket на сайте для быстрого обмена данными

Author Автор: Роман Чернышов    Опубликовано: 30 ноября 2024

Доброго времени друзья! Вкратце решил описать как организовать обмен данными клиент-сервер, на базе протокола WebSocket, в рамках сайта, например для мгновенного обмена сообщениями в чате. Принцип работы следующий — запускаем скрипт на PHP, работающий в фоновом режиме(это сервер WS), вешаем его на порт(например на 8090), далее настраиваем проксирование Apache или nGinx, чтобы все запросы из вне, по протоколу WS(с HTTP заголовком Upgrate: websocket) переправлялись на localhost:8090. Затем подключаемся к серверу из JavaScript, слушаем и обрабатываем данные. Отправляем сообщения в сокет из PHP. Собственно всё. Далее подробно.

Скрипт на PHP

WebSocket

Пример скрипта на PHP, который выступает в роли сервера WebSocket, сам скрипт я разбирать не буду, там достаточно все просто(работает на основе PHP расширения Socket), но если что-то не понятно, будет повод разобраться.

<?php
$sServer = new socketServer();
 
$sServer->host	= 'localhost';
$sServer->patch	= 'server.php';
$sServer->port	= 8090;
 
$sServer->init();
 
 
class socketServer{
 
	public $host;
 
	public $patch;
 
	public $port;
 
	private $socket;
 
	private $clients;
 
	function __construct(){
 
		$this->socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
 
	} 
 
	public function init(){
 
		socket_set_option( $this->socket, SOL_SOCKET, SO_REUSEADDR, 1 );
		socket_bind( $this->socket, 0, $this->port );
		socket_listen( $this->socket );
 
		$this->clients = [ $this->socket ];
 
		while( true ){
 
			$null = [];
			$nSockets = $this->clients;
			socket_select( $nSockets, $null, $null, 0, 10 );
 
			if( in_array( $this->socket, $nSockets ) ){
 
				$nSocket = socket_accept( $this->socket );
				$headers = socket_read( $nSocket, 1024 );
				$this->clients[] = $nSocket;
 
				$this->headers( $headers, $nSocket );
 
				unset( $nSockets[ array_search( $this->socket, $nSockets ) ] );
			}
 
			foreach( $nSockets as $nsData ){
				while( @ socket_recv( $nsData, $sData, 1024, 0 ) >= 1 ){
					$socketMessage = $this->decode( $sData );
 
					if( $args = json_decode( $socketMessage, true ) )
						$this->send( $args, $this->clients );
 
					break 2;
				}
 
				$sData = @ socket_read( $nsData, 1024, PHP_NORMAL_READ );
				if( $sData === false ){
					unset( $this->clients[ array_search( $nsData, $this->clients ) ] );
				}
			}
 
		}
	}
 
	private function headers( $headersStr, $socket ){
		$headers = [];
		$headersStr = preg_split("/\r\n/", $headersStr);
 
		foreach( $headersStr as $hStr ){
			$hStr = rtrim( $hStr );
 
			if( preg_match('/\A(\S+): (.*)\z/', $hStr, $match  ) )
				$headers[ $match[1] ] = $match[2];
		}
 
		$key = base64_encode( pack('H*', sha1( $headers['Sec-WebSocket-Key'] .'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' ) ) );
 
		$header = "HTTP/1.1 101 Switching Protocols \r\n" .
					"Upgrade: websocket\r\n" .
					"Connection: Upgrade\r\n" .
					"WebSocket-Origin: $this->host" .
					"WebSocket-Location: ws://$this->host:$this->port$this->patch\r\n" .
					"Sec-WebSocket-Accept: $key\r\n\r\n";
 
		socket_write( $socket, $header, strlen( $header ) );
	}
 
	private function send( $args ){
 
		$args = array_merge( $args, [ 'time' => date('H:i') ] );
 
		$msg = $this->encode( json_encode( $args ) );
 
		$length = strlen( $msg );
 
		foreach( $this->clients as $client )
			@socket_write( $client, $msg, $length );
 
		return true;
	}
 
	private function encode( $sData ){
		$b = 0x81;
		$header = '';
		$length = strlen( $sData );
 
		if( $length <= 125 )
			$header = pack( 'CC', $b, $length );
		elseif( $length > 125 && $length < 65536 )
			$header = pack( 'CCn', $b, 126, $length );
		elseif( $length > 65536 )
			$header = pack( 'CCNN', $b, 127, $length );
 
		return $header . $sData;
	}
 
	private function decode( $sData ){
 
		$str = '';
		$length = ord( $sData[1] ) & 127;
 
		if( $length == 126 ){
			$mask = substr( $sData, 4, 4 );
			$data = substr( $sData, 8 );
		} elseif( $length == 127 ){
			$mask = substr( $sData, 10, 4 );
			$data = substr( $sData, 14 );
		} else {
			$mask = substr( $sData, 2, 4 );
			$data = substr( $sData, 6 );
		}
 
		for( $i=0; $i < strlen( $data ); $i++ )
			$str .= $data[ $i ] ^ $mask[ $i%4 ];
 
		return $str;
	}
 
	function __destruct(){
		socket_close( $this->socket );
	}
 
}?>

Запускаем демон на PHP (сервер WS)

Скрипт запускается в режиме демона, командой:

php server.php &

Настраиваем проксирование Apache на сервер WS

Чтобы все запросы, пришедшие на наш сайт, такого вида https://example.com/socket/ по протоколу WS/WSS, перенаправлялись на WebSocket сервер, что слушает порт 8090, в настройка Apache, добавляем следующее:

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /socket/ ws://localhost:8090 [P,L]

Если у вас на сервере стоит связка nGing и Apache, то проксирование можно настроить только на nGinx.

Отправляем сообщение в WebSocket из PHP

Отправляем сообщения на WebSocket, из PHP(используя Stream) по протоколу HTTPS:

<php
$args = []; // Массив с отправляемыми данными
$port = 443;
$host = 'example.com';
$headers = "GET /socket/ HTTP/1.1\r\n" .
  "Host: $host\r\n" .
  "Upgrade: websocket\r\n" .
  "Connection: Upgrade\r\n" .
  "Sec-WebSocket-Key: " . base64_encode( random_bytes( 16 ) ) . "\r\n" .
  "Sec-WebSocket-Version: 13\r\n\r\n";
$context = stream_context_create( [ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false ] ] );
$socket = stream_socket_client("ssl://$host:$port", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
if( $socket ){
	fwrite( $socket, $headers );
	$response = fread( $socket, 1024);
	fwrite( $socket, $this->wsEncode( json_encode( $args ) ) );
	fclose( $socket );
}

Подключаем скрипт на JavaScript к WS

Прослушиваем WebSocket на стороне сайта из JavaScript:

var socket = new WebSocket('wss://example.com/socket/'),
app.socket.onopen	= () => {};
app.socket.onclose	= () => {};
app.socket.onerror	= error => {};
app.socket.onmessage = function(event) {
    var data = JSON.parse( event.data ); // Обработка пришедших сообщений
};

Друзья, если вам нужна можешь в настройке WebSocket на вашем сайте, обращайтесь, буду раз сотрудничеству!

Оставить комментарий

Автор блога
Роман Чернышов
Веб-разработчик,
Full Stack
Senior, Architect
PHP, JavaScript, Node.JS, Python, HTML 5, CSS 3, MySQL, Bash, Linux Admin
Заказать работу
предложить оффер

Моя книга
Книга. Веб-разработчик. Легкий вход в профессию
Печатная книга
Веб-разработчик.
Легкий вход в профессию
Купить за 359₽
Популярные записи
Последние вопросы
Список вопросов
Последние комментарии
Меню

Archive

Мои проекты
Insurance CMS Love Crm CMS Совместные покупки Мой PHP Framework Хостинг для моих клиентов Лицензии на мой софт и поддержка