What is a socket_select() (or *_select() for that matter) you ask? In general a select() function takes a list of resources (files, sockets, etc) and waits for at least one of them to be available for reading (or writing), and when that happens it lets you know which resources from the lists you've passed are ready for action. This is extremely helpful when dealing with sockets, because they - unlike local files for example - aren't as ready to be read or written to because of net lag and such. Why am I telling you this? Because this function is crucial for writing a responsive real time chat in PHP.
Because of the fact that my first serious encounter with select() was in Communication Applications course - we coded in C - I naively believed that the socket_select() function in PHP will be glad to take file descriptors and sockets mixed just like the C select() function does. I was very wrong.
PHP actually has different (!!) selects for files and for sockets. A question arose in my head: WTF!? Doesn't that kinda beat the point of select if I can not actually monitor two different resource types?? Well, after some foruming and reading I've found out that you can spawn sockets in a file stream form, using the stream_socket_server() and the stream_socket_client() functions. Great! Now that we know some techy details, we can look at some code:
Server side setup
$PORT = 20222; //chat port
$ADDRESS = "localhost"; //adress
$ssock; //server socket
$csock; //chat socket
$uin; //user input file descriptor
$ssock = stream_socket_server("tcp://$ADDRESS:$PORT"); //creating the server sock
echo "Waiting for client...\n";
$csock = stream_socket_accept($ssock); //waiting for the client to connect
//$csock will be used as the chat socket
echo "Connection established\n";
$uin = fopen("php://stdin", "r"); //opening a standart input file stream
What did we do?
In the first two lines we define the address and the port for the socket. Then we use the stream_socket_server() function to create a TCP server socket in stream form, so it can be used later by stream_socket_accept(). The $ssock stream is used only for waiting for an incoming call from a client, it's not actually used for the communication. The $csock is the actual chat socket we will use, it will be open as soon as a client initiates communication with the server. As soon as that happens we give the user a notification that we're connected and put the standart-input stream in to $uin so that we can take user input directly from command line.
Now for the code meat. The communication/select loop!
Server communication
$conOpen = true; //we run the read loop until other side closes connection
while($conOpen) { //the read loop
$r = array($csock, $uin); //file streams to select from
$w = NULL; //no streams to write to
$e = NULL; //no special stuff handling
$t = NULL; //no timeout for waiting
if(0 < stream_select($r, $w, $e, $t)) { //if select didn't throw an error
foreach($r as $i => $fd) { //checking every socket in list to see who's ready
if($fd == $uin) { //the stdin is ready for reading
$text = fgets($uin);
fwrite($csock, $text);
}
else { //the socket is ready for reading
$text = fgets($csock);
if($text == "") { //a 0 length string is read -> connection closed
echo "Connection closed by peer\n";
$conOpen = false;
fclose($csock);
break;
}
echo "[Client says] " .$text;
}
}
}
}
What are we doing? The while loop will run umm.. while connection with the other side is open (if we got this far in the code it is already open).
Now what are the $r, $w, $e and $t variables? Understanding this part is 80% of understanding select().
- The $r is an array of streams (sockets, files, etc) we want to read from. As soon as one of them becomes available for reading, select() returns.
- The $w is the same as the $r only it will wait for streams to become available for writing instead of reading.
- The $e is for high-priority in-bound communication (not interesting atm)
- The $t variable tells the stream_select() function for how long should it wait for a change of status of any of the streams passed to it in the various arrays. We don't want a timeout (ie we don't mind waiting forever), that's why we set it to NULL. Btw, setting $t to 0 will only check once and return immediately.
- Save your streams in other place, so you don't lose them after passing them to select().
- Re-initialize your $r, $w, and $e arrays every time you call select().
Another thing to note: when connection on the other side closes, the socket appears to be readable, but, we can only read 0 bytes (empty string). This is how we know the connection was closed.
Now let's take a look at the client setup
$PORT = 20222; //chat port
$ADDRESS = "localhost"; //adress
$sock = stream_socket_client("tcp://$ADDRESS:$PORT"); //creating the client socket
echo "connection established\n";
$uin = fopen("php://stdin", "r"); //opening a standart input file stream
Looks pretty much like the server setup, except for the fact that we don't need two sockets. We use the same socket for connection and communication. The communication loop of the client is almost identical to that of the server so I'm not going to cover it. You can get both sources at the bottom of the post.
Running the chat
Open two terminals, one for server and one for client.
First run server.php (I'm on Linux):
$> php server.php
The server now waits for an incoming connection
then go to your other terminal and run client.php:
$> php client.php
Both programs should notify you that connection has been established, now type something in any of the terminals to test communication :)
Server:
Client:
You can find the sources here. Questions, suggestions, criticism and love letters are welcome!
good example
ReplyDeleteThanks
Hello friend, very good work I used the example in php and it worked just fine
ReplyDeletemore and can be so every time the client writes something already appears on the server, and used in the same web socket, congratulations on your work:)
www.obsidiann.com
kakaroto
nice .urgently need one so i didnt bother to code. will create a better one and give credit to you
ReplyDeleteCan this example be extended for one to one user chat?
ReplyDelete@Shashi I'm not sure how the code given above is different from what you refer to as "one to one user chat", but I'm pretty sure it can be extended to most kind of chats. Please supply more details, so I can help more.
ReplyDeleteit is not working ... if i write anything on either side , it does not appears immediately on the other side . it appears when you write something on the other side.
ReplyDeleteHey Siddharth, I assume you're running this on Windows? I tested it on Linux and it works as expected, testing on Windows though gave the results you've described. I assume this is because Windows treat file descriptors and sockets differently than Linux.
DeleteAnyway, if you fix it, please post back :)
Excited words written in this blog helped me to improve my aptitudes and helped me to know how I can help myself all alone. I am truly happy to come at this stage. webchat
ReplyDeleteGreat post
ReplyDelete