2015-01-15 3 views
1

Я ищу помощь в решении моей проблемы с websocket. Я создал простой веб-узел HTML5 для соединения между моим сайтом AngularJS (websocket подключается через простой JS) и моим PHP-сервером. Соединение также работает, а также отправляет и получает данные. Причина, по которой мне нужен websocket, заключается в следующем: у меня есть другой REST-Service (PHP) на том же сервере, который также взаимодействует с сайтом AngularJS. Таким образом, REST-Service изменяет данные в базе данных. Теперь, когда я запускаю действие с сайта AngularJS (например, создайте нового пользователя), REST-Service создает задание в списке заданий, и задание будет выполняться из любой другой службы (не имеет значения), а после некоторых секунд, служба даст знак REST-Service, если задание выполнено. Задание будет настроено на выполнение (в базе данных).PHP-запрос на PHP Websocket

Теперь, в этот момент, когда задание выполнено, мне нужно отправить запрос от REST-Service на веб-узел PHP, а Websocket должен отправить сообщение на сайт angularJS. Я знаю, я мог бы опросить Angular-JS, но это создало бы слишком большой трафик, и многие пользователи будут использовать эту систему одновременно.

Извините за мое плохое объяснение (а также за мой плохой английский - я немецкий;)).

Мой простой вопрос: Есть ли возможность отправить запрос с PHP REST-Service на веб-сайт, поэтому веб-сайт уведомит мой угловой: работа выполнена.

Возможно, это простой запрос, или мне нужно создать PHP-клиент, который обновляет базу данных и проверяет, выполняется ли задание и отправляется затем в угловое окно через websocket? Любые другие идеи?

Благодарим за помощь!

Редактировать: Возможно, как сказано, какой-то код будет хорошим :). Там только некоторые Standart-HTML5 WebSocket, но, возможно, это помогло бы:

JS (в углам главного конфигурационного-метода):

//configure websocket 
var uri= "ws://x.x.x:9000/websocket/websocket.php";  
ws= new WebSocket(uri); 

ws.onopen = function(ev) { // connection is open 
    console.log("Websocket: Connection established"); 
} 

ws.onmessage = function(ev) { 
    console.log(ev.data); 
}; 

ws.onerror = function(ev){ 
    console.log("Websocket: Connection Error: " + ev.Error);}; 

ws.onclose = function(ev){ 
    console.log("Websocket: Connection closed");};  

То РНР-WebSocket (найдено в интернете):

$host = 'lucadev.lonzagroup.net'; //host 
$port = '9000'; //port 
$null = NULL; //null var 

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); 
socket_bind($socket, 0, $port); 
socket_listen($socket); 
$clients = array($socket); 

//start endless loop, so that our script doesn't stop 
while (true) { 
    //manage multipal connections 
    $changed = $clients; 
    //returns the socket resources in $changed array 
    socket_select($changed, $null, $null, 0, 10); 

    //check for new socket 
    if (in_array($socket, $changed)) { 
     $socket_new = socket_accept($socket); //accpet new socket 
     $clients[] = $socket_new; //add socket to client array 

     $header = socket_read($socket_new, 1024); //read data sent by the socket 
     perform_handshaking($header, $socket_new, $host, $port); //perform websocket handshake 

     socket_getpeername($socket_new, $ip); //get ip address of connected socket 
     $response = mask(json_encode(array('type'=>'system', 'message'=>$ip.' connected'))); //prepare json data 
     send_message($response); //notify all users about new connection 

     //make room for new socket 
     $found_socket = array_search($socket, $changed); 
     unset($changed[$found_socket]); 
    } 

    //loop through all connected sockets 
    foreach ($changed as $changed_socket) { 

     //check for any incomming data 
     while(socket_recv($changed_socket, $buf, 1024, 0) >= 1) 
     { 
      $received_text = unmask($buf); //unmask data 
      $tst_msg = json_decode($received_text); //json decode 
      $user_name = $tst_msg->name; //sender name 
      $user_message = $tst_msg->message; //message text 
      $user_color = $tst_msg->color; //color 

      //prepare data to be sent to client 
      $response_text = mask(json_encode(array('type'=>'usermsg', 'name'=>$user_name, 'message'=>$user_message, 'color'=>$user_color))); 
      send_message($response_text); //send data 
      break 2; //exist this loop 
     } 

     $buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ); 
     if ($buf === false) { // check disconnected client 
      // remove client for $clients array 
      $found_socket = array_search($changed_socket, $clients); 
      socket_getpeername($changed_socket, $ip); 
      unset($clients[$found_socket]); 

      //notify all users about disconnected connection 
      $response = mask(json_encode(array('type'=>'system', 'message'=>$ip.' disconnected'))); 
      send_message($response); 
     } 
    } 
} 
// close the listening socket 
socket_close($sock); 

function send_message($msg) 
{ 
    global $clients; 
    foreach($clients as $changed_socket) 
    { 
     @socket_write($changed_socket,$msg,strlen($msg)); 
    } 
    return true; 
} 


//Unmask incoming framed message 
function unmask($text) { 
    $length = ord($text[1]) & 127; 
    if($length == 126) { 
     $masks = substr($text, 4, 4); 
     $data = substr($text, 8); 
    } 
    elseif($length == 127) { 
     $masks = substr($text, 10, 4); 
     $data = substr($text, 14); 
    } 
    else { 
     $masks = substr($text, 2, 4); 
     $data = substr($text, 6); 
    } 
    $text = ""; 
    for ($i = 0; $i < strlen($data); ++$i) { 
     $text .= $data[$i]^$masks[$i%4]; 
    } 
    return $text; 
} 

//Encode message for transfer to client. 
function mask($text) 
{ 
    $b1 = 0x80 | (0x1 & 0x0f); 
    $length = strlen($text); 

    if($length <= 125) 
     $header = pack('CC', $b1, $length); 
    elseif($length > 125 && $length < 65536) 
     $header = pack('CCn', $b1, 126, $length); 
    elseif($length >= 65536) 
     $header = pack('CCNN', $b1, 127, $length); 
    return $header.$text; 
} 

//handshake new client. 
function perform_handshaking($receved_header,$client_conn, $host, $port) 
{ 
    $headers = array(); 
    $lines = preg_split("/\r\n/", $receved_header); 
    foreach($lines as $line) 
    { 
     $line = chop($line); 
     if(preg_match('/\A(\S+): (.*)\z/', $line, $matches)) 
     { 
      $headers[$matches[1]] = $matches[2]; 
     } 
    } 
    $secKey = $headers['Sec-WebSocket-Key']; 
    $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); 
    //hand shaking header 
    $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . 
    "Upgrade: websocket\r\n" . 
    "Connection: Upgrade\r\n" . 
    "WebSocket-Origin: $host\r\n" . 
    "WebSocket-Location: wss://$host:$port/websocket/websocket.php\r\n". 
    "Sec-WebSocket-Accept:$secAccept\r\n\r\n"; 
    socket_write($client_conn,$upgrade,strlen($upgrade)); 
}  

Я не думаю, что REST-сервис имеет значение.

Edit 2 Я думаю, единственное решение для меня (после того, как вы сказали, простой запрос не представляется возможным), чтобы создать отдельный класс (PHP), который взаимодействует с WebSocket. Он откроет соединение для отправки, что работа будет выполнена, и закроет ее в конце концов. Это должно работать для меня, а не изящно, но должно работать. Спасибо за помощь!

+0

пример вашего кода был бы хорош ... – empiric

ответ

1

Сэм,

Посмотрите на Thruway, WampPost и Angular-WAMP. Эти проекты используют протокол под названием WAMP, который позволяет различным компонентам разговаривать друг с другом через веб-узлы.

Ваша установка будет выглядеть примерно так:

[Клиент WampPost] < ----> [Thruway WAMP Router] < ----> [Угловая Client]

Я дам вам некоторые быстрые примеры кода, поэтому вы можете увидеть, как все это работает вместе, но вам нужно перейти к каждому отдельному проекту, чтобы узнать, как настроить каждый компонент.

Thruway Маршрутизатор:

<?php 
    require 'vendor/autoload.php'; 
    use Thruway\Peer\Router; 
    use Thruway\Transport\RatchetTransportProvider; 

    $router = new Router(); 

    //Websockets listen on port 9090 
    $transportProvider = new RatchetTransportProvider("127.0.0.1", 9090); 
    $router->addTransportProvider($transportProvider); 

    //WampPost Client listens on port 8181 
    $router->addInternalClient(new \WampPost\WampPost('realm1', null, '127.0.0.1', 8181));   

    $router->start(); 

Угловой:

app.config(function ($wampProvider) { 
    $wampProvider.init({url: 'ws://127.0.0.1:9090/',realm: 'realm1'}); 
}) 
app.run(function($wamp){ 
    $wamp.open(); //This will open the connection when the app starts 
}) 
app.controller("MyCtrl", function($scope, $wamp) { 
    // Subscribe to a topic 
    function onevent(args) { 
     $scope.hello = args[0]; 
    } 
    $wamp.subscribe('com.myapp.hello', onevent);  
}); 

Публикация сообщение с запросом/ответом. Это можно сделать из PHP, Curl или всего, что может сделать HTTP-запрос.

curl -H "Content-Type: application/json" -d '{"topic": "com.myapp.hello", "args": ["Hello, world"]}' http://127.0.0.1:8181/pub 

Теперь все, что подписался на тему «com.myapp.hello», получит сообщение «Привет, мир».

Это всего лишь очень простой пример. Вы можете сделать намного больше с WAMP, включая RPC и проверку подлинности в Интернете.

Кроме того, я являюсь одним из разработчиков Thruway, поэтому, если у вас есть какие-либо вопросы или общие вопросы, дайте мне знать.

0

Это не возможно, чтобы сигнализировать определенный процесс PHP как таковой, вы должны принять другую систему помощи:

  • UniX гнездо - угловая WS открывает сокет и посылает свой путь к WS работы, ждет данных, доступных из сокета. Задание WS записывается в сокет после его завершения.

  • inotify - угловая WS ждет файл создается в некотором пути

  • сообщение системы очередь (например, RabbitMQ) - угловая WS присоединяется к событию, который запускается WS работы

0

Вы можете опросить (может потребоваться длительный опрос) или нажать состояние фонового задания. Я думаю, вы ищете систему «push notification».

То, что вы на месте заключается в следующем:

  • запрос от клиента к серверу
    • когда я пнуть действие из AngularJS-сайта - создать нового пользователя
  • сервер (REST-API) принимает новую задачу
    • создает задание в списке работ
    • фон работник делает его вещь/очереди обработки пинки в
    • результате состояние этой работы записывается в базу данных

Следующим шагом будет добавление

  • толчок уведомление отправляет клиенту
    • поэтому идентификатор клиента необходимо сохранить, чтобы почтовый ящик e отправляется правильному клиенту. внутри, это очередь сообщений с подписками. это основанный на client_id или channel_id.
    • Вы должны учитывать некоторые случаи, например: что, если клиент ушел и никогда не возвращается. вам понадобится дополнительное время или повторить попытку для этого.
    • относительно ваш код запрос. Использование библиотеки является делом вкуса, вы можете посмотреть в

В качестве альтернативы можно использовать простую таблицу MySQL для notfication. См. https://stackoverflow.com/a/11552006/1163786 для базовой структуры таблицы.

Это похоже на механизм транспорта flashmessage в сеансах. Вы можете совместить это с «интервальными» опросами ajax.

Вы должны получать данные из этой таблицы после входа пользователя в систему (для отображения). Во время входа в систему клиент может проверить новые данные с помощью запроса ajax. скажем: каждый каждые 60 секунд один ajax получает запрос от клиента к серверу, чтобы проверять наличие новых сообщений из очереди состояния фона. Такой подход подходит для небольшого числа пользователей, иначе ваш сервер будет забит запросами.

Это зависит от того, насколько сложна ваша система (количество событий, пользователей, уведомление каналов). Системы очереди сообщений с подписями на основе id/channel позволяют более сложные сценарии, а также пропускную способность и управление трафиком через ограничения.

+0

спасибо за ваш ответ. пункт 2 и 3 на вас следующие шаги не подойдут для моей ситуации. если задание выполняется, должно быть отправлено всем пользователям, а не только тем, кто создал задание, поэтому мне не нужен клиент или боюсь, что пользователь не вернется. если пользователи выходят из системы до появления уведомления - не проблема, это не так уж важно, что пользователь получит уведомление, вернувшись. он может посмотреть список работ. Я уже рассмотрел два примера, которые вы опубликовали, но я не нашел решение, на которое я надеялся. – Sam

+0

«если задание должно быть отправлено всем пользователям, а не только одному». Это делает его довольно легким, тогда это всего лишь один основной канал подписки для «обновлений статуса от рабочего фона». Просто зарегистрируйтесь на этом канале при входе в систему, чтобы новая информация была нажата. Вот и все. - Альтернативой будет реализация базового опроса ajax с интервалом. как каждые 60 секунд один ajax получает запрос от клиента к серверу для проверки новых сообщений из очереди состояния фона. Это зависит от того, насколько сложна ваша система. Подписки на основе id/channel позволяют более сложные сценарии. –

+0

У меня уже есть система опроса ajax, которая работает также, но я думаю, что это сделает трафик, который слишком велик. Конечно, я мог бы установить интервал до 60 секунд, но тогда пользователю пришлось подождать 60 секунд, чтобы получить уведомление, и это слишком долго. Я думаю, что ваша идея с основным каналом подписки - это то, что я ищу. я попробую. Спасибо! – Sam

0

Возможно отправить данные между службой REST и сервером WebSockets.

четыре основных способа являются:

  1. Попросите службы REST подключиться к серверу WebSockets.

  2. Попросите свой сервер WebSockets опросить вашу службу REST.

  3. Имейте общее хранилище, к которому одновременно могут обращаться как ваша служба REST, так и ваш сервер WebSockets.

  4. Сигналы.

Проще всего реализовать, чтобы ваш сервер WebSockets периодически проводил опрос службы REST для обновлений.

  • Преимущества: Подавляющее большинство работы делается для вас: Просто используйте ваши любимые библиотеки PHP для создания веб-запросов, такие как закручивание и file_get_contents(), среди других.

  • Недостатки: Не в режиме реального времени. Если несколько секунд латентности были достаточно хороши, то почему бы просто не использовать AJAX или даже полный HTTP-запрос вместо WebSockets? Кроме того, служба REST не использует постоянные скрипты; стоимость установки запроса с вашего сервера WebSockets будет такой же, как и стоимость для настройки вашего запроса от любого другого клиента.

Следующих два метода, подключение к серверу WebSockets и использование общего хранилища будет каждый сложнее технически, но может обрабатывать большие объемы данных легко.

Если вы решите подключить свой сервер REST к WebSockets, вам нужно будет реализовать клиентское рукопожатие и правильно обработать фреймворк, как определено в стандарте WebSockets (RFC 6455, The WebSocket Protocol). Я не знаю ни одного PHP WebSockets клиентов библиотек.

В качестве альтернативы вы можете отказаться от рукопожатия WebSockets и написать свое собственное соединение клиент/сервер с использованием несовместимых сокетов TCP без применения протокола WebSockets, если вы сами пишете сервер и клиент, , и вы можете полностью и полностью обеспечить безопасность, аутентичность и достоверность любого трафика, который может подключаться к вашему серверу. (Это действительно большое, если когда речь идет о безопасности, не думайте.).

Для реализации клиента WebSockets в вашей службе REST:

  • Преимущество: Трафик в реальное время. Произвольные данные. Меньшие затраты по запросу устанавливают затраты, чем метод периодического опроса выше.

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

Общее хранилище:

Я не буду рекомендовать какой-то один вариант. Основными вашими параметрами являются файлы (плоские файлы, файлы сокетов), реляционные базы данных (MySQL, PostgreSQL), постоянные хранилища ключевых значений (Cassandra, Redis) и хранилища ключей в памяти (Memcache).

У каждого есть свои компромиссы, в которые я не буду входить.Трудности и ловушки использования каждого из них достаточно многочисленны, и я буду делать вам плохую услугу, не говоря вам о том, чтобы уйти и провести какое-то время, исследуя их подробно.

  • Преимущество: Многочисленное.

  • Недостатки: Многочисленные. «В« Информатике »есть только две тяжелые вещи: недействительность кэша, именование вещей и ошибки« один за другим ». - Фил Карлтон (перефразировать с офф-на-один добавляется)

Сигналы:

Лучшая аналогия, что у меня есть для сигнала идти позади кого-то и кричать: «Эй!»

Если ваше сообщение просто то, что что-то произошло, тогда сигнал совершенен. Все, что вам нужно, это идентификатор процесса (PID) скрипта, к которому вы хотите отправить сообщение.

Просто убедитесь, что процесс, по которому вы отправляете сигнал, может справиться с этим, или он может просто умереть.

Применение:

  1. Получите ваш PID (posix_getpid()).
  2. Сохраните свой ПИД-код (/tmp/websocket.pid).
  3. Зарегистрировать обработчики сигналов. (pcntl_signal()).
  4. В других ваших процессах отправьте сигнал (posix_kill()).

Простой.

  • Преимущества: Самый простой в реализации. Самый быстрый для запуска.

  • Недостатки: "-9". Невозможно отправить какой-либо контент вместе с сообщением. (Можно сказать, что произошли изменения, но вы не можете сказать, каковы были эти изменения.) Можно отправлять сообщения только на процессы на одном компьютере.