Page 1 of 2

Maintaing socket connections across page requests

Posted: Thu Aug 21, 2008 5:03 pm
by cjordan
Hi folks,

I've got a client who wants me to write a browser based program that will connect to a socket server given an ip address and port number (that the user would supply). Once the socket is connected, the user will type in a command into a text box and send it to the php server which would then relay the command via the socket to the waiting server on the already open connection. I would then need to read the servers response and relay that message to the user on-screen.

So, in my preliminary dabbling yesterday, I figured out how to open and close a connection to the socket server. I could even write to the socket and read the servers response. The problem is that each of these little tests were self contained. I had one php script that contained the connection code, the code to write a hard-coded command to the socket and then the code to read from the socket followed by the code to close the socket connection.

I started running into problems when I tried to make things more interactive. What I seemed to find was that I could not persist the socket handle across page requests. I started out by writing a little screen with two text boxes, a button and a div which would contain the responses from the socket. I wrote an AJAX function in javascript (jQuery 1.2.6) that called a php script sending the ip and port. That script created a socket to that server on that port and sent the automatic response back to the calling javascript function.

This worked great.

What I soon realized though, was that as soon as my opensocket.php script was finished running, was that I had no way of referring to the socket handle on subsequent requests. I tried storing the socket handle in $_SESSION['socket'], but that failed miserably, and from what I can find, it fails by design. For some reason (security, maybe?) php resource objects cannot be stored in the session. Drat! :(

It would seem like it wouldn't be a problem to open a connection to the socket server on each call, send the command, wait for the response from the server and then close the connection, right? But in this case, it's actually not okay. There are certain commands which when sent to the server return ids that are good for that connection only. Once the socket is closed, that id goes away.

One solution (which my client doesn't really like much) is instead of sending one command at a time, to send a bunch of commands. One reason he doesn't like this is that command 'a' might return an id or code which is needed by command 'b' and there would be know way to know that command a head of time.

So it would seem that I need a way to keep a socket open across several page requests.

Is that possible using PHP? Is there a strategy for doing this that I'm just not getting? I've done a bunch of reading about sockets on the internet, but everyone seems to be talking about turning a php server into a socket server which listens for other programs to connect to it. What *I* want is to connect to a different socket server that is already listening for my connection and waiting for me to issue commands over the open socket connection.

I realize this is kind of a long winded post, but I hope I've made some sense here, and that someone can help me to understand how I might be able to accomplish this task, if it's possible at all. It was a toss up as to which forum I should post it in, so if someone thinks that this discussion would be better put in the PHP Theory and Design forum, rather than this one, then let me know and I can cross post it over there.

Thanks,
Chris

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 7:54 am
by ghurtado
I believe that in order to keep any type of resource open (whether it be a socket, a database connection or a pointer to an open file) the program needs to run uninterrupted. Since PHP stops running at the end of the page and starts all over in the next page refresh, there is probably no practical way you can keep a socket open across page requests.

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 9:21 am
by cjordan
Ghurtado,

Thanks for reading my long winded post. I was afraid that I'd get an answer like the one you gave. Dang! :(

Why is it that resources can't be stored in some persistent scope (like the session scope)? Is this some sort of security thing?

Could I perhaps write a php program that ran in a loop listening for connections from my web app, and upon receiving instructions from my web app would open a socket to the user supplied ip and port? Since the program which opened the connection is running in a loop it never finishes and thus the connection to the socket server stays open. Meanwhile my little looping program is listening for instructions from my web app.

Does that sound feasible, or just convoluted?

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 9:33 am
by ghurtado
No, it is not a security issue, its not even a PHP specific issue. A resource (socket, db connection) isn't quite like a file, in that it is only relevant at a given point in time.

The concept of a socket connection is only relevant in the context of two programs running: a client and a server. If either program stops, the other will close the socket, therefore there is no way to "recover" that socket at a later time, the way you solve this is by creating a new socket connection the next time you run the client.

In the case of trying to save socket connections to the session, you can't because a session is serialized to a file on disk between requests, since a socket is an in-memory process that disappears when the program using it disappears, you cannot serialize this resource to disk, since it would be irrelevant when you unserialize it and the original program is gone.

In your scenario, the solution comes from having two PHP programs: one will act as a server and be always running and a second one as a client and only run when the page refreshes. The PHP client can call the PHP server (no need for sockets so far) which in turn uses sockets to call the "other" server.

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 9:51 am
by cjordan
First, thanks for all your help. It's really helped me to be able to bounce this off someone else.
ghurtado wrote:In the case of trying to save socket connections to the session, you can't because a session is serialized to a file on disk between requests, since a socket is an in-memory process that disappears when the program using it disappears, you cannot serialize this resource to disk, since it would be irrelevant when you unserialize it and the original program is gone.
Gotcha. Okay.
ghurtado wrote:In your scenario, the solution comes from having two PHP programs: one will act as a server and be always running and a second one as a client and only run when the page refreshes. The PHP client can call the PHP server (no need for sockets so far) which in turn uses sockets to call the "other" server.
You're right, there's really no need for sockets between the client and my constantly running server program which would then use sockets to call the "other" server. So, this is probably my best option for writing this isn't it?

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 9:57 am
by ghurtado
I Can't really guarantee that it is the best way, but it's certainly how I would implement it :)

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 10:31 am
by cjordan
Thanks again, Garcia. I'm trying to write my first command-line php app right now. I think it's probably the way to go. :)

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 10:53 am
by ghurtado
Best of luck!

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 11:01 am
by cjordan
Okay, wait.

So, how do I go about getting my looping CLI program to listen for commands from my web app? Here's the kind of thing I'm thinking of:

Code: Select all

<?
    while(true){
        $cmd = $_POST['command'];
        switch($cmd){
            "stop": // break out of the loop
                break 2; // break out of the switch and the while
            "opensocket":
                // code to open socket
                break;
            "closesocket":
                // code to close socket
               break;
            "readfromsocket":
               // code to read from open socket
               break;
            "writetosocket":
               // code to write to socket
               break;
            default:
                // we haven't received a command from the user so keep waiting...
                break;
        }
    }
?>
What I don't know is if the $_POST array will be available to the command line script. If not how else would I get information from my web form as input into my command line app? What would the call look like? I'm starting to doubt that the code above will work. hmm...

I also have a question about the reverse: assuming that my $_POST method of getting user input from the web into my command line app works, how would I get information from my command line app back out to my web app?

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 11:13 am
by ghurtado
cjordan wrote:Okay, wait.
I'm not going anywhere :)

You got the right idea with the while loop. Problem with $_POST is twofold though:

- You only get it in web apps (no CLI)
- You can only send it once

So $_POST is what the PHP client will receive for its inputs, what you have posted is the server.

Now that I think about this some more, I think I was wrong to tell you that you could handle PHP client to PHP server communication without sockets, I think you will need to use sockets at that level too.

You may have already seen this, but it is a good tutorial with very good code samples of what you need to do on the PHP socket server end (which will also act like a client to that second server, the remote one):

http://devzone.zend.com/node/view/id/1086

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 11:29 am
by cjordan
okay, I just had a thought... of course the $_POST idea probably won't work, but what if I did something like this:

Code: Select all

 
// create a handle to read from the local host on port 80
$handle = fsockopen('127.0.0.1', 80);
while($cmd = fgets($handle)){
    switch($cmd){
        // switch code in here to handle all possible values for $cmd
        case "closesocket":
            // code to close the open socket connection to my "other" server
            ...
            // close the open handle that was reading from port 80
            fclose($handle);
            // break out of the switch and the loop
            break 2;
        // more case statements to candle all other allowed values for $cmd
        ...
        default:
            // ignore everything else...
            break;
    }
}
Does this seem more like it would work? I realize that my switch syntax in my previous post was incorrect -- missing the "case" in front of my case statements). Oops!

Ha! I just saw your last post, and I think you're right. I'll have to use sockets to go from the client to the server as well as from the server (CLI app) to my "other" server. What are your thoughts on my amended solution?

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 11:52 am
by ghurtado
That looks like the right way to do it, just make sure you keep the architecture clear in your head while you do this, and you should be in good shape.

Web client <-> PHP Client <-> PHP server (the piece you posted) <-> Other server

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 12:17 pm
by cjordan
Thanks. I'll give this a go and see what happens. :)

Re: Maintaing socket connections across page requests

Posted: Fri Aug 22, 2008 3:47 pm
by cjordan
Okay, I don't know if this deserves a new topic or what, but I've given this a go using the architecture that Garcia and I have talked about, namely:
Web client <-> PHP Client <-> PHP server (acting as a socket server) <-> Other server

Here's the code that I'm using. It *almost* works, but not quite.

Code: Select all

<?
error_reporting(E_ALL);
 
/* Allow the script to hang around waiting for connections. */
set_time_limit(0);
 
/* Turn on implicit output flushing so we see what we're getting
 * as it comes in. */
ob_implicit_flush();
 
$address = '192.168.1.40';
$port = 10000;
 
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
}
 
if (socket_bind($sock, $address, $port) === false) {
    echo "socket_bind() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}
 
if (socket_listen($sock, 5) === false) {
    echo "socket_listen() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}
 
while (true){
    if (($msgsock = socket_accept($sock)) === false) {
        echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
        break;
    }
    /* Send instructions. */
    $msg = "\n\rWelcome to the PHP Test Server.\n\r" . "To quit, type 'quit'. To shut down the server type 'shutdown'.\n\r";
    socket_write($msgsock, $msg, strlen($msg));
 
    while(true) {
        if (($buf = socket_read($msgsock, 2048, PHP_NORMAL_READ)) === false) {
            echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($msgsock)) . "\n\r";
            break 2;
        }
        if (!$buf = trim($buf)) {
            continue;
        }
        switch($buf){
            case 'quit':
                break 2;
            case 'shutdown':
                socket_close($msgsock);
                break 3;
            case 'openGridSock':
                // code to open the socket
                $ip = '64.208.28.117';
                $port = 1234;
                
                if(($GridSock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false){
                    $errmsg = "Problem creating socket: " . socket_strerror(socket_last_error());
                    socket_write($msgsock, $errmsg, strlen($errmsg));
                }
                if(($client = socket_connect($GridSock, $ip, $port)) === false){
                    $errmsg = "Problem connecting to socket: " . socket_strerror(socket_last_error());
                    socket_write($msgsock, $errmsg, strlen($errmsg));
                }
                else{
                    $connectmsg = "Connected.\n\r";
                    socket_write($msgsock, $connectmsg, strlen($connectmsg));
                    $GridResponse = socket_read($GridSock, 1024);
                    socket_write($msgsock, $GridResponse, strlen($GridResponse));
                }
                break;
            case 'closeGridSock':
                    $closemsg = "Connection closed.";
                    socket_close($GridSock);
                    socket_write($msgsock, $closemsg, strlen($closemsg));
                break;
            default:
                // we did not get a specific daemon command so assume what was typed was a gridborg command and write it to the GridSock socket.
                socket_write($GridSock, $buf, strlen($buf));
 
                // imeadately read the gridborg's response and write that message back out to the client
                $GridResponse = socket_read($GridSock, 1024);
                socket_write($msgsock, $GridResponse, strlen($GridResponse));
                break;              
        }
    }
    socket_close($msgsock);
}
?>
This code works in that I can fire up putty, connect to 192.168.1.40 on port 10000 and see my little socket server waiting for me to type something in. Also, if I type the all important 'opensocket' command, it does indeed create another connection to that socket server on port 1234 and I get a response from that server which I then relay via my original socket connection.

Great. So now I want to do this from the web, not from putty. Here's my simple little web page:

Code: Select all

<?
    if(sizeof($_POST) > 0){
        $ip = '192.168.1.40';
        $port = 10000;
        
        if(($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false){
            echo "Problem creating socket: " . socket_strerror(socket_last_error()) . "\n";
        }
        echo "Attempting to connect to '$ip' on port '$port'...";
        if(($client = socket_connect($sock, $ip, $port)) === false){
            echo "\nProblem connecting to socket: " . socket_strerror(socket_last_error()) . "\n";
        }
        else{
            socket_write($sock, $_POST['strText'], strlen($_POST['strText']));
        }
    }
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title>Com Test Client</title>
    </head>
    <body>
 
        <form name="f" method="post" action="comtestclient.php">
            <input type="text" name="strText" value=""><br />
            <input type="submit" value="submit">
        </form>
    </body>
</html>
You can see that what this does is open a connection to MY little socket server and then tries to send the value of the text box (strText) to the socket server using socket_write. I was hoping that my little socket server would read this in just as it had from putty, but when I hit submit, I get the following error from my little socket server:
PHP Warning: socket_read(): unable to read from socket [0]: An existing connection was forcibly closed by the remote host. in C:\Inetpub\wwwroot\GridborgDaemon\gridborgdaemon.php on line 36
socket_read() failed: reason: An existing connection was forcibly closed by the
remote host.
Any thoughts on what I'm doing wrong?

Thanks,
Chris

Re: Maintaing socket connections across page requests

Posted: Sat Aug 23, 2008 1:11 pm
by dml
It looks like it's something to do with the client side of the connection being shut down when the script ends, don't know exactly what - it may be that the buffers aren't being flushed out, or that the connection is being abruptly reset rather than cleanly closed down. Anyway, you might try getting the server to send back an acknowledgement of each command received and processed - just "OK\n" would suffice - and get the client side to read a line and check that it says "OK". And then close the socket properly in your script rather than letting PHP close it automatically.