0

I am currently working on this project (php) where I have to connect my php backend to a remote server using sockets. I am using this code to create the connection and it works fine:

$key=base64_encode(openssl_random_pseudo_bytes(16));
$head = "GET / HTTP/1.1"."\r\n".
      "Upgrade: WebSocket"."\r\n".
      "Pragma: no-cache"."\r\n".
      "Accept-Encoding: gzip, deflate"."\r\n".
      "Connection: Upgrade"."\r\n".
      "Origin: http://$host"."\r\n".
      "Host: $host"."\r\n".
      "Sec-WebSocket-Version: 13"."\r\n".
      "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits"."\r\n".
      "Sec-WebSocket-Protocol: b_xmlproc"."\r\n".
      "Cache-Control: no-cache"."\r\n".
      "Sec-WebSocket-Key: $key"."\r\n".
      "Content-Length: 0\r\n"."\r\n";
$sock = pfsockopen($host, $port, $errno, $errstr, 2);
fwrite($sock, $head ) or die('error:'.$errno.':'.$errstr);

I trigger this code through an HTTP call and I am trying to avoid creating multiple sockets. For the moment, every time I am triggering this endpoint, a new socket is created. However, I would like to use pre-existing socket if one has been created already instead of a new one. Here is what I tried so far, without success.

  • I tried using global variables $GLOBALS = array('globalsocket' => null); but the variable is re-created every time a new request is triggered.
  • I created a file where I tried to serialize the socket and unserialize it, but it did not work; the unserialize socket was of no use
  • I tried class variable, but it was recreated each time of course
  • I read the documentation but almost all the methods need the instance of the socket in the first place, which is what I am trying to retrieve...
  • I also had a look at apc but it's not enabled on the server (and I cannot change that)

If anyone has a clue on how to do that, I would much appreciate ! Thanks.

PS: I must note that I am used to nodejs and not php, which may be the reason why I am having a hard time figuring it out

PPS: I don't have any database where I can store it

Here is how I would do what I want i nodejs


var net = require("net");
const http = require("http");

let client = new net.Socket();
let connected = false;
const requestListener = function (req, res) {
    console.log("status", client.listening);
    if (!connected) {
        client.connect("port", "ip", function () {
            console.log("Connected");
            connected = true;
            
        });
    }

    res.writeHead(200);
    res.end("Hello, World!");
};
const server = http.createServer(requestListener);

client.on("data", function (data) {
    console.log("Received: " + data);
    client.destroy(); // kill client after server's response
});

client.on("close", function () {
    connected = false;
    console.log("Connection closed");
});

client.on("end", function(){
    console.log("Connection end");
    connected = false;
    client.destroy()
})

client.on('error', function(){
    console.log("Connection end");
    connected = false;
    client.destroy()
})

server.listen("xxx");

Bastien L.
  • 119
  • 1
  • 2
  • 9
  • Although it doesn't mention this anywhere, I think you can just call `pfsockopen()` again, with the same parameters, to reuse the persistent connection. There are some caveats. See: https://stackoverflow.com/questions/8099248/php-can-someone-explain-the-pfsockopen-function-for-me-persistent-socket – KIKO Software Mar 13 '21 at 16:16
  • Thank you for your answer but I am not sure it works. I tried with a custom socket server (written in nodejs) where I log each new connection `wss.on( "connection", (connection = async (ws) => { console.log("new connection");})` and I had two logs for two http calls :/ – Bastien L. Mar 13 '21 at 16:34
  • That's a test in a completely other language, I don't think that will tell you anything about PHP. Here's another link, about the same thing: https://stackoverflow.com/questions/14268018/concurrent-use-of-a-persistent-php-socket All questions and answers indicate that reusing it simply means opening the same connection. – KIKO Software Mar 13 '21 at 18:45
  • Mmh sorry; what I meant was that I did set up a "fake" backend in nodejs to test whether it received two different connections when my other php backend used `pfsockopen` to connect. Since I received twice the message "new connection" when a connection was triggered, it means that the php back must have created a new connection the second time and not the same as the previous one (in that case the listener on connection should not have been triggered the second time) – Bastien L. Mar 13 '21 at 20:37
  • I'd like to flip the question a bit and possibly attack this from a different perspective. If this were Node.js, how would you do such a thing? Or more generically, how would you have a client start something on the server, detach itself and resume the existing "thing" on the next connection? Also, semi-related, if serialization had worked, would that mean that your connecting server was "sleeping" but the receiving server was sitting with an open connection, waiting for a response, potentially for hours/days/weeks? Or is it acceptable that the other server might still close the connection? – Chris Haas Mar 15 '21 at 18:10
  • In NodeJs, I did actually manage to achieve what I wanted. I created a server and a socket listener using `let client = new net.Socket(); const http = require("http"); const server = http.createServer(requestListener); ` where my requestlistener looks at the variable client and check if it's connected or not. If it is, just use it as it is; if not, just create it with `client.connect`; I will edit my post so you can see the code. Regarding the second part, actually the server is sending many data, like every 5sec or so, so I don't have to wait for weeks – Bastien L. Mar 15 '21 at 18:42
  • 1
    Thanks @BastienL. I think the big difference between the Node and PHP worlds is that Node is both the server and the framework, whereas for PHP the server (specifically what clients talk to) is detached (Apache/Nginx) from the PHP framework, and when PHP is run in a web context the server is really expecting PHP "be done" eventually. This isn't specific to sockets either. So instead, you'll probably need to make a server-side PHP application that is run continuously and then another PHP application that talks to it. You could also try to spawn a background task but I wouldn't trust it for long – Chris Haas Mar 15 '21 at 18:57

2 Answers2

1

PHP is not a long-running process like Node. Given only the portion of code you provided, the PHP process would start up, open socket, write, and quit. This process would of course be repeated whenever you make a request to that PHP script.

To make a long running PHP script that acts as a server, you'd 1) need to run it from CLI (this is the only way you'll bypass max execution time, which is by default 30 seconds), and 2) you'd need a while loop or similar to keep it open indefinitely (or until some condition is met).

Perhaps you'd be more comfortable with a PHP library for this purpose.

Some things to consider:

http://socketo.me/ - PHP websocket server

https://github.com/ratchetphp/Pawl - PHP websocket client

caffeinatedbits
  • 471
  • 5
  • 8
0

Background

PHP is works just like HTTP, basically stateless will initiate execution every-time it gets new request. We sometime might need to run background jobs and process for which we need to utillize crontab or similar scheduler.

Note: PHP process is blocking IO so you can though use cli on server and keep it running to entertain all your request but that will be reinventing the wheel.

Working approach

I'd suggest use: http://socketo.me/docs/hello-world

From example on above link

<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Chat implements MessageComponentInterface {
    public function onOpen(ConnectionInterface $conn) {
    }

    public function onMessage(ConnectionInterface $from, $msg) {
    }

    public function onClose(ConnectionInterface $conn) {
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
    }
}

You can keep using these 4 function to achieve everything. However you might need your custom logic to prevent your session or auth data.

this also use a background process which you need to execute on server.

php bin/chat-server.php

and you can use any custom script to check and keep it running if interrupted, just like you use pm2 or supervisor for node.

Try and update us if this works for you.

Dheeraj Thedijje
  • 1,053
  • 12
  • 19