Pull to refresh

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

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

Установка


В принципе, установка описана на официальном сайте.
Я предполагаю, что у вас уже установлен 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-eio:
    # cd /usr/ports/devel/pecl-eio
    # make install clean
    


  3. Устанавливаем pecl-event:
    # cd /usr/ports/devel/pecl-event
    # make install clean
    


  4. Если еще не установлен git, ставим (опции можно не менять):
    # cd /usr/ports/devel/git
    # make install clean
    


  5. Устанавливаем 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. Для этого необходимо прописать конфигурацию нашего WebSocket-сервера в файле /usr/local/phpdaemon/conf/phpd.conf:
user www;
group www;

max-workers 8;
min-workers 1;
start-workers 1;
max-idle 0;

Pool:Servers\WebSocket {
    enable 1;
    listen "tcp://0.0.0.0";
    listen-port 8047;
    privileged;
}

ExampleWebSocket {}



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


Все должно пройти нормально и в логах (/var/log/phpdaemon.log) мы должны увидеть примерно следущее:
M#7964 \PHPDaemon\Core\Pool:Servers\WebSocket up.
M#7964 \PHPDaemon\Core\Pool:\PHPDaemon\Servers\WebSocket\Pool up.
W#7966 \PHPDaemon\Examples\ExampleWebSocket up.
Spawning 1 worker(s)
W#7967 \PHPDaemon\Examples\ExampleWebSocket up.


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


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

Итак:
  1. Создаем в папке PHPDaemon/Applications новый файл с именем MyWebSocket.php со следующим кодом:
    <?php
    namespace PHPDaemon\Applications;
    
    class MyWebSocket extends \PHPDaemon\Core\AppInstance {
    
    	public $enableRPC=true; // Без этой строчки не будут работать широковещательные вызовы
    	public $sessions=array(); // Здесь будем хранить указатели на сессии подключившихся клиентов
    
    	// С этого метода начинается работа нашего приложения
    	public function onReady() {
    		$appInstance = $this;
    		
    		// Метод timerTask() будет вызываться каждые 5 секунд
    		$this->timerTask($appInstance);
    		
    		// Наше приложение будет доступно по адресу ws://site.com:8047/myws
    		\PHPDaemon\Servers\WebSocket\Pool::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(\PHPDaemon\Core\Daemon::$process->getPid()));
    		
    		// Перезапускаем наш метод спустя 5 секунд
    		\PHPDaemon\Core\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 \PHPDaemon\WebSocket\Route {
    
    	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:Servers\WebSocket {
        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 на адрес вашего хоста.

P.S. Статья обновлена 19.02.2014 и адаптирована под текущую версию фреймворка (1.0-beta3).
Tags:
Hubs:
+26
Comments 34
Comments Comments 34

Articles