Быстрый старт с WebSocket на основе phpDaemon

в 22:54, , рубрики: WebSocket, Веб-разработка, метки:

На хабре уже есть статья по этой теме. Но фреймворк с тех пор сильно обновился и, к сожалению, по старой статье разобраться скорее всего будет проблематично. Кроме того, в изучении чего-то нового всегда самое сложное — это начало. Поэтому по свежей памяти постараюсь описать процесс старта хотя бы в общих чертах.

Установка

В принципе, установка достаточно полно описана на официальном сайте.
Я предполагаю, что у вас уже установлен PHP с версией не младше 5.3. Поэтому описываю только остальное (на примере FreeBSD):

  1. Установить из портов необходимые библиотеки для PHP:

    Это можно сделать по отдельности:

    • php5-pcntl
      # cd /usr/ports/devel/php5-pcntl
      # make install clean
      
    • php5-shmop
      # cd /usr/ports/devel/php5-shmop
      # make install clean
      
    • php5-sockets
      # cd /usr/ports/net/php5-sockets
      # make install clean
      

    либо с помощью порта php5-extensions:

    # cd /usr/ports/lang/php5-extensions
    # make config
    

    Отмечаем галочки напротив PCNTL, SHMOP и SOCKETS и устанавливаем:

    # make install clean
    

    При втором подходе в конце можно получить ошибку, что порт php5-extensions уже установлен. Тем не менее, сами библиотеки будут установлены нормально.

  2. Устанавливаем pecl-libevent::
    # cd /usr/ports/devel/pecl-libevent
    # make install clean
    
  3. Если еще не установлен git, ставим (опции можно не менять):
    # cd /usr/ports/devel/git
    # make install clean
    
  4. Устанавливаем phpDaemon как сказано на сайте:
    # cd /usr/local
    # git clone git://github.com/kakserpom/phpdaemon.git
    # chmod +x phpdaemon/bin/phpd
    # ln -s /usr/local/phpdaemon/bin/phpd /usr/bin/phpd
    

В принципе, сама установка на этом завершена. Теперь стоит попробовать запустить первое приложение. Для этого возьмем пример из комплекта — ExampleWebSocket. Для этого необходимо сделать несколько шагов:

  1. Создаем папку applications в папке phpDaemon. Т.е., полный путь должен быть /usr/local/phpdaemon/applications. Здесь будут храниться наши приложения.
  2. Копируем туда файл ExampleWebSocket.php из папки app-examples.
  3. Прописываем конфигурацию нашего WebSocket-сервера в файле /usr/local/phpdaemon/conf/phpd.conf:
    user www;
    group www;
    
    max-workers 8;
    min-workers 1;
    start-workers 1;
    max-idle 0;
    
    WebSocketServer {
     enable 1;
     listen "tcp://0.0.0.0";
     listen-port 8047;
     privileged;
    }
    
    ExampleWebSocket {}
    

Теперь пробуем запустить наш сервер:

# phpd start

Все должно пройти нормально и в логах (/var/log/phpdaemon.log) мы должны увидеть примерно следущее:

Pool:WebSocketServer up.
W#73942 ExampleWebSocket up.
Spawning 7 worker(s).
W#73948 ExampleWebSocket up.
W#73943 ExampleWebSocket up.
W#73944 ExampleWebSocket up.
W#73945 ExampleWebSocket up.
W#73946 ExampleWebSocket up.
W#73947 ExampleWebSocket up.
W#73949 ExampleWebSocket up.

Пишем первое приложение

Хотелось бы предложить в дополнение к идущему в комплекте еще один пример WebSocket-приложения. Думаю, лишний пример никогда не будет вреден разбирающемуся в новом человеку.
Сразу следует оговориться, что в данный момент времени официальная документация сильно устарела. Разработчик обещает исправить это в будущем. Тем не менее, всегда можно открыть интересующий класс фреймворка, найти нужный метод и разобраться как он работает. Чаще всего это не отнимает много времени: код интуитивно понятен.

Итак:

  1. Создаем в папке applications новый файл с именем MyWebSocket.php со следующим кодом:
    <?php
    class MyWebSocket extends AppInstance {
    
    	public $enableRPC=true; // Без этой строчки не будут работать широковещательные вызовы
    	public $sessions=array(); // Здесь будем хранить указатели на сессии подключившихся клиентов
    
    	// С этого метода начинается работа нашего приложения
    	public function onReady() {
    		$appInstance = $this;
    		
    		// Метод timerTask() будет вызываться каждые 5 секунд
    		$this->timerTask($appInstance);
    		
    		// Наше приложение будет доступно по адресу ws://site.com:8047/myws
    		WebSocketServer::getInstance()->addRoute('myws', function ($client) use ($appInstance) {
    			$session=new MyWebSocketRoute($client, $appInstance); // Создаем сессию
    			$session->id=uniqid(); // Назначаем ей уникальный ID
    			$this->sessions[$session->id]=$session; //Сохраняем в массив
    			return $session;
    		});
    
    	}
    
    	function timerTask($appInstance) {
    		// Отправляем каждому клиенту свое сообщение
    		foreach($this->sessions as $id=>$session) {
    			$session->client->sendFrame('This is private message to client with ID '.$id, 'STRING');
    		}
    		
    		// После отправляем всем клиентам сообщение от каждого воркера (широковещательный вызов)
    		$appInstance->broadcastCall('sendBcMessage', array(Daemon::$process->pid));
    		
    		// Перезапускаем наш метод спустя 5 секунд
    		Timer::add(function($event) use ($appInstance) {
    			$this->timerTask($appInstance);
    			$event->finish();
    		}, 5e6); // Время задается в микросекундах
    	}
    	
    	// Функция для широковещательного вызова (при вызове срабатывает во всех воркерах)
    	public function sendBcMessage($pid) {
    		foreach($this->sessions as $id=>$session) {
    			$session->client->sendFrame('This is broadcast message from worker #'.$pid, 'STRING');
    		}
    	}
    
    }
    
    class MyWebSocketRoute extends WebSocketRoute {
    
    	public $client;
    	public $appInstance;
    	public $id; // Здесь храним ID сессии
    
    	public function __construct($client,$appInstance) {
    		$this->client=$client;
    		$this->appInstance=$appInstance;
    	}
    	
    	// Этот метод срабатывает сообщении от клиента
    	public function onFrame($data, $type) {
    		// Отправляем ему ответ
    		$this->client->sendFrame('Server receive from client #'.$this->id.' message "'.$data.'"', 'STRING');
    	}
    	
    	// Этот метод срабатывает при закрытии соединения клиентом
    	public function onFinish() {
    		// Удаляем сессию из массива
    		unset($this->appInstance->sessions[$this->id]);
    	}
    
    }
    

  2. Правим наш конфигурационный файл до следующего вида:
    user www;
    group www;
    
    max-workers        8;
    min-workers        1;
    start-workers        1;
    max-idle        0;
    
    Pool:WebSocketServer {
        enable 1;
        listen "tcp://0.0.0.0";
        listen-port 8047;
        privileged;
    }
    
    MyWebSocket {}
    

  3. Создаем на компьютере HTML-файл со следующим содержимым:
    <script type="text/javascript">
    function add(text) {
    	document.forms[0].b.value=text+"n"+document.forms[0].b.value;
    }
    
    if("WebSocket" in window) {
    	var timer;
    	var ws=new WebSocket("ws://site.com:8047/myws");
    	ws.onopen=function() {
    		add('Connection opened');
    		timer=window.setInterval(function() {
    			var date = new Date();
    			var message='ping at '+date.getSeconds();
    			ws.send(message);
    			add('Client sent message "'+message+'"');
    		}, 30000);
        };
    
        ws.onmessage=function(evt) {
    		add('Message from server: "'+evt.data+'"');
        };
        
        ws.onclose=function() {
    		add('Connection closed');
            window.clearTimeout(timer);
        };
    } else {
        alert("Your browser doesn't support WebSocket");
    }
    </script>
    <form>
    <textarea name="b" style="width:100%;height:100%"/></textarea>
    </form>
    

  4. Перезапускаем демона:
    # phpd restart
    

  5. Открываем наш HTML-файл в браузере (я проверял только в Opera 12.13 и Google Chrome 24.0.1312.57).

После этого JS-клиент начинает взаимодействовать с сервером и в браузере будут выводиться все их взаимодействия.

Примечание

У сервера имеется таймаут равный двум минутам (120 секунд). Соответственно, клиент должен периодически посылать серверу сообщения «пустышки», чтобы сервер не счел его неактивным и не отключил. И не забудьте заменить site.com на адрес вашего хоста.

Автор: bartwell

Источник

Поделиться

* - обязательные к заполнению поля