PHP request to PHP Websocket

2019-05-18 09:23发布

I'm searching for help with my websocket problem. I have built a simple HTML5 websocket to connect between my AngularJS-Site (websocket connects through simple JS) and my PHP-Server. The connection works as well, and also sending and recieving data works as well. The reason, why I need a websocket is: I have a diferent REST-Service (PHP) at the same server, which also communicates with the AngularJS-Site. So the REST-Service changes data at a Database. Now, when I kick an action from the AngularJS-Site (create a new user for example), the REST-Service creates a job in a joblist, and the job will be performed from any other service (not relevant), and after some seconds, the service will give a sign to the REST-Service, if the job is done. The job will be set to done (in the database).

Now, at this point as the job is set to done, i need to send a request from the REST-Service to the PHP Websocket, ant the Websocket should send a message to the angularJS-Site. I know, i could polling through Angular-JS, but that would create too big traffic, beacuse many users will use that system at the same time.

Sorry for my bad explanation (and also for my bad english - i'm german ;)).

My simple question: Is there any possibility to send a request from the PHP REST-Service to the websocket, so the websocket will notify my angular: The job is done.

Is that simple request possible, or have I to create a PHP Client which refreshes over and over the database to check, if the job is done and send then to the angular through the websocket? Any other ideas?

Thanks for your help!

Edit: Maybe as said some code would be good :). There's only some standart-HTML5 websocket, but maybe it would help:

JS (in angulars config-method):

//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");};     

Thats the PHP-Websocket (found on internet):

$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));
}    

I don't think, the REST-Service is relevant.

Edit 2 I think, the only solution for me (after you said, a simple request is not possible) is to create a seperate class (php) which communicates with the websocket. It will open a connection to send, that the job is done, and close it after all. That should work for me, not that graceful, but should work. Thanks for help!

4条回答
干净又极端
2楼-- · 2019-05-18 09:40

It's not possible to signal a particular php process as such, you have to take another system for help:

  • unix socket - angular WS opens a socket, and sends its path to the job WS, waits on data available from the socket. Job WS writes into the socket once it is done.

  • inotify - angular WS waits for file being created in some path

  • message queue system (eg RabbitMQ) - angular WS subscribes to an event, which is triggered by job WS

查看更多
成全新的幸福
3楼-- · 2019-05-18 09:51

Sam,

Take a look at Thruway, WampPost and Angular-WAMP. These projects use a protocol called WAMP, which allows different components to talk to each other over Websockets.

Your setup will look something like this:

[WampPost Client]<---->[Thruway WAMP Router]<---->[Angular Client]

I'll give you some quick code samples, so you can see how it all works together, but you'll need to go to each individual project to see how to configure each component.

Thruway Router:

<?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();

Angular:

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);      
});

Publishing a message with Request/Response. This can be done from PHP, Curl or anything that can make an HTTP request.

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

Now everyone that has subscribed to the topic "com.myapp.hello", will receive the message "Hello, world".

This is just a very basic example. You can do a lot more with WAMP, including RPCs and websocket authentication.

Also, I'm one of the developers of Thruway, so if you have any issues or general questions, let me know.

查看更多
倾城 Initia
4楼-- · 2019-05-18 09:55

It is possible to send data between your REST service and your WebSockets server.

The four main ways are:

  1. Have your REST service connect to your WebSockets server.

  2. Have your WebSockets server poll your REST service.

  3. Have shared storage that both your REST service and your WebSockets server can access concurrently.

  4. Signals.

The easiest to implement would be to have your WebSockets server periodically poll your REST service for updates.

  • Advantages: Vast majority of the work is done for you: Simply use your favorite PHP libraries for making web requests, such as cURL and file_get_contents(), among others.

  • Disadvantages: Not real-time. If a few seconds of latency was good enough, then why not just use AJAX or even a full HTTP request instead of WebSockets? Also, the REST service does not use persistent scripts; the cost to set up the request from your WebSockets server would be the same as the cost to set up your request from any other client.

The next two methods, connecting to your WebSockets server and using shared storage will each be more difficult technically, but can handle large volumes of data easily.

If you choose to connect your REST server to WebSockets, you will have to implement the client handshake and handle the framing correctly as defined in the WebSockets standard (RFC 6455, The WebSocket Protocol). I don't know of any PHP WebSockets client libraries.

Alternatively, you can drop the WebSockets handshake and write your own client/server connection using raw TCP sockets without implementing the WebSockets protocol if you're writing both the server and client yourself, and you can fully and completely ensure the security, authenticity, and validity of any traffic that may connect to your server. (That's a really big if. When it comes to security, don't assume.)

For implementing a WebSockets client within your REST service:

  • Advantages: Real-time traffic. Arbitrary sized data. Smaller per-request set up costs than the periodic polling method above.

  • Disadvantages: RFC 6455 is a pain to implement. Not the worst by a long shot, but still a pain. Also, your REST service is still utilizing run-once scripts for each request. This means that if implemented incorrectly, you may be spawning several unnecessary connections to your WebSockets server. If sending small bursts of data, there are other far more efficient ways to transfer the data.

Shared storage:

I won't recommend any one option. Your main options are files (flat files, socket files), relational databases (MySQL, PostgreSQL), key-value persistent stores (Cassandra, Redis), and in-memory key-value stores (Memcache).

Each have their trade offs, which I won't go into here. The intricacies and pitfalls of using each are numerous enough that I would be doing you a disservice by not telling you to go off and spend some solid time researching them in depth.

  • Advantages: Numerous.

  • Disadvantages: Numerous. "There are only two hard things in Computer Science: cache invalidation, naming things, and off-by-one errors." -- Phil Karlton (paraphrased with off-by-one added)

Signals:

The best analogy that I have for a signal is to walk up behind someone and shout "hey!"

If your message is simply that something happened, then a signal is perfect. All that you need is the process ID (PID) of the script that you want to send a message to.

Just make sure that the process that you're sending a signal to can handle it, or it might just die.

To use:

  1. Get your PID (posix_getpid()).
  2. Store your PID (/tmp/websocket.pid).
  3. Register signal handlers. (pcntl_signal()).
  4. In your other processes, send a signal (posix_kill()).

Easy enough.

  • Advantages: Easiest to implement. Fastest to run.

  • Disadvantages: "-9". Can not send any content along with the message. (You can say that there were changes, but you can't say what those changes were.) Can only send messages to processes on the same machine.

查看更多
爷的心禁止访问
5楼-- · 2019-05-18 10:00

You could poll (long polling could fit) or push the state of the background job. I guess, you are looking for a "push notification" system.

What you have in place is the following:

  • request from client to server
    • when I kick an action from the AngularJS-Site - create a new user
  • server (REST-API) accepts the new task
    • creates a job in a joblist
    • background worker does his thing / queue-processing kicks in
    • result state of this job is written to database

The next step would be to add

  • push notification is send to the client
    • therefore, the client id needs to be stored, so that the message is sent to the right client. internally, this is a message queue with subscriptions. it's client_id based or channel_id based.
    • you have to consider, some edge cases, like: what, if the client is gone and never comes back. you will need some additional time or re-try conditions to handle this.
    • regarding your code request. library usage is a matter of taste, you could look into

An alternative would be to use a simple MySQL table for notfication. See https://stackoverflow.com/a/11552006/1163786 for a basic table structure.

That's similar to the flashmessage transport mechanism in sessions. You could combine this with "intervalic" ajax polls.

You would fetch the notification data from this table once a user logs into the system (for display). During the time the client is logged in, it might check for new data using an ajax request. let's say: like each 60 secs one ajax get request from client to server to check for new messages from the background status queue. This approach would fit a low number of users, else your server would get hammered with requests.

It depends on how complex your system is (number of events, users, notify channels). Message Queue Systems with id/channel based subscriptions allow more complex scenarios and also bandwidth and traffic control through limits.

查看更多
登录 后发表回答