Sie sind auf Seite 1von 14

Writing Socket Servers in PHP

August 27, 2003 Tutorials Intended Audience Prerequisites Overview What is a socket server? Types of Sockets PHP Socket functions Creating a Socket in PHP The making of a real server Practical Uses Security One at a time please Possible Additions and Improvements About the Author

Intended Audience
This tutorial is intended for the PHP programmer interested in exploring the use of PHP Sockets functions to create an Internet Socket Server.

Prerequisites
This tutorial uses the following:

PHP Sockets library. The extension is enabled at compile time using the -enable-sockets

configure option. CLI (Command Line Interface) version of PHP: enables the running of the socket server from the command line.

As of PHP 4.3.0 the CLI executable is compiled and installed by default (you can explicitly install the CLI version by specifying --enable-cli at compile time). Linux OS

Although this tutorial uses Linux, the sockets library operates equally well in Windows and *nix environments.

In Windows, PHP Sockets can be activated by un-commenting extension=php_sockets.dll in php.ini

Overview What is a socket server?


A socket server is a service assigned to a particular port that listens to incoming requests and responds to them.

Email servers (POP3, SMTP) and web servers are good examples of socket servers. An HTTP (web) server listens on port 80 to incoming requests and serves back HTML and other files (images, documents etc).

Socket Servers normally run continuously as a service or a daemon.

Types of Sockets

When information is sent across the Internet, it is usually split into packets. This allows the sending of large files in many smaller pieces of information to be later assembled on the other end.

There are two different protocols for splitting the information into packets, depending on the information type being sent and delivery requirements.

TCP (Transmission Control Protocol) the transmitted packets are numbered and are assembled on the other end, they are assembled in order to form the entire message. TCP usually runs over IP (Internet Protocol) hence the term TCP/IP.

TCP ensures that no data is lost (if a packet is lost it will be re-transmitted), and is therefore well suited for sending images, files or other information that must be received whole and integral (like your email).

UDP (User Datagram Protocol) this is a connectionless protocol. Like TCP it can run over the IP protocol. The difference is that UDP provides few error recovery services and so there is no guarantee that a particular packet will be received on the other side, or in what order the packets will be received.

UDP is particularly suitable for streaming data such as music streams (if we lose some information, there is no point in trying to get it again as we may have already played that part).

In this tutorial, TCP sockets will be used to ensure that all the data is received intact. The examples used in this tutorial can be easily converted to use UDP packets.

PHP Socket functions


PHP is well equipped to handle sockets at the lower level. As of PHP3, PHP had introduced socket handling in the form of

fsockopen()

and other associated functions (see the Network part of the PHP manual at http://www.php.net/network). As of PHP4, PHPs socket functionality was greatly improved with the introduction of low-level interface to BSD style sockets.

Note: the socket functions in PHP are still marked as experimental and hence are subject to change in future releases of PHP. Testing shows they are fairly stable when used in well-written applications.

Creating a Socket in PHP


Creating a low-level socket in PHP is very similar to using socket functions in C and Unix socket programming. The manual at http://www.php.net/sockets outlines the available functions and also points to the Unix Socket FAQ, which is a great resource on socket programming (although it can take a little while to plough through the whole thing).

Lets start with a simple example: a socket server that listens to a connection on port 9000, accepts a string as input, and returns it with all white spaces removed.

Quick note about port numbers. Port numbers under 1024 require root privilege to be able to open them (or Administrator privileges in Windows NT/2000). They are also mostly reserved for well-established services (such as HTTP, POP3, SMTP, FTP etc). For ease of use and security, most user-programmed services listen on port numbers over 1024 (maximum port number available is 65536 as IPV4 has a maximum of quantity of 16 bits).
#!/usr/local/bin/php q

<?php // Set time limit to indefinite execution

set_time_limit (0); // Set the ip and port we will listen on $address = '192.168.0.100'; $port = 9000; // Create a TCP Stream socket $sock = socket_create(AF_INET, SOCK_STREAM, 0); // Bind the socket to an address/port socket_bind($sock, $address, $port) or die('Could not bind to address'); // Start listening for connections socket_listen($sock); /* Accept incoming requests and handle them as child processes */ $client = socket_accept($sock); // Read the input from the client &#8211; 1024 bytes $input = socket_read($client, 1024); // Strip all white spaces from input $output = ereg_replace("[ \t\n\r]","",$input).chr(0); // Display output back to client socket_write($client, $output); // Close the client (child) socket socket_close($client); // Close the master sockets socket_close($sock); ?>

In order to run the program, make sure that the first line #!/usr/local/bin/php q is the location of the PHP CLI (or CGI) binary. You will need to change the mode so it is executable (chmod 755 socket_server.php where socket_server.php is the name of the file) and run it using

./socket_server.php

from the command

line.

Now lets look at each line in detail:

#!/usr/local/bin/php q

Executes the php CLI binary with the quiet options so it does not print HTTP headers.
$sock = socket_create(AF_INET, SOCK_STREAM, 0)

Creates the master socket. This socket will not serve the clients, rather it will listen to incoming requests and spawn new sockets for these clients. It acts a master socket listener.

From the PHP manual (http://www.php.net/socket_create): AF_INET is the domain type of IPV4 protocol used for TCP and UDP. SOCK_STREAM provides sequenced, reliable, full-duplex, connection-based byte streams. The TCP protocol is based on this connection type.

Note: to get a UDP type socket, just replace SOCK_STREAM with SOCK_DGRAM.

socket_bind($sock, $address, $port) or die('Could not bind to address') Binds

the socket to the given address and port. socket_listen($sock) Listens on the given port to the incoming connections, once a connection is made, it will be used to create the spawned socket. $client = socket_accept($sock) Accepts the connection on the master socket. $input = socket_read($client, 1024) Reads from the accepted socket, 1024 bytes at a time (or until a \r, \n or \0 is received depending on the value of the optional third parameter see note below).

Note: The third type parameter to socket_read() can either be

PHP_BINARY_READ, which uses the system read() and is safe for reading binary

data (default type as of PHP >= 4.1.0) or PHP_NORMAL_READ in which reading stops at \n or \r (default in PHP <= 4.0.6)

$output = ereg_replace("[ \t\n\r]","",$input).chr(0)

Removes all

white space characters using a regular expression.

The one item of interest is chr(0), which represents the null character. This is appended to the end of the string that will be sent to the user. The reason for terminating the output with the null character is that quite a few client socket APIs use this character to notify the client of end of transmission (this is also true of the Macromedia Flash XMLSocket API).
socket_write($client, $output) Writes the output to the client. Finally, socket_close($client) and socket_close($sock)

Closes the client and master socket.

The making of a real server


Now that you know the basic steps required to set up a socket and listen to an incoming request, you are ready to create a full-fledged server.

Note that in the code above, the program is run once, awaits an incoming connection and then exits. That is fine for explaining the steps required to create a socket server, but it is unsuitable for real world situations. Once your program runs and responds to an incoming request, you do not want it to exit (meaning you have to restart it).

We therefore need a mechanism to run the program continuously enter the loop.

We can use a while(true) { /* do something */ } to cause the program to run

continuously until we issue an explicit exit statement.

We will expand on the above example, adding the following functionality:

allow the program to run endlessly create termination facilities provide for the simultaneous handling of multiple clients

#!/usr/local/bin/php q

<?php // Set time limit to indefinite execution set_time_limit (0); // Set the ip and port we will listen on $address = '192.168.0.100'; $port = 9000; $max_clients = 10; // Array that will hold client information $clients = Array(); // Create a TCP Stream socket $sock = socket_create(AF_INET, SOCK_STREAM, 0); // Bind the socket to an address/port socket_bind($sock, $address, $port) or die('Could not bind to address'); // Start listening for connections socket_listen($sock); // Loop continuously while (true) { // Setup clients listen socket for reading

$read[0] = $sock; for ($i = 0; $i < $max_clients; $i++) { if ($client[$i]['sock'] != null)

$read[$i + 1] = $client[$i]['sock'] ; } // Set up a blocking call to socket_select() $ready = socket_select($read,null,null,null); /* if a new connection is being made add it to the client array */ if (in_array($sock, $read)) { for ($i = 0; $i < $max_clients; $i++) { if ($client[$i]['sock'] == null) { $client[$i]['sock'] = socket_accept($sock); break; } elseif ($i == $max_clients - 1) print ("too many clients") } if (--$ready <= 0) continue; } // end if in_array

// If a client is trying to write - handle it now for ($i = 0; $i < $max_clients; $i++) // for each client { if (in_array($client[$i]['sock'] , $read)) { $input = socket_read($client[$i]['sock'] , 1024);

if ($input == null) { // Zero length string meaning disconnected unset($client[$i]); } $n = trim($input); if ($input == 'exit') { // requested disconnect socket_close($client[$i]['sock']); } elseif ($input) { // strip white spaces and write back to user $output = ereg_replace("[ \t\n\r]","",$input).chr(0); socket_write($client[$i]['sock'],$output); } } else { // Close the socket socket_close($client[$i]['sock']); unset($client[$i]); } } } // end while // Close the master sockets socket_close($sock); ?>

The basic functionality is the same as the first example, with an added feature when a user passes the string exit the program will terminate the connection with the user.

This program is very similar to the first, except that inside the loop, we have four basic blocks of code.

1. 2. 3. 4.

Set up the sockets for reading. Listen to new clients and set them up in the $client array. Listen to client writes and record the input. Handle the client input.

A new function in use is socket_select($read,null,null,null); This runs the select() system call on the given array of sockets and waits for them to change status. This will block all the sockets until there is a change in status, which is then handled.

As a last point, you may find a situation where you would like to broadcast information to the other clients connected (say in a many-to-many chat environment). This can be achieved with the following code:

$output = 'This is my broadcast message'.chr(0); for ($j = 0; $j < MAX_CLIENTS; $j++) // for each client { if ($client[$j]['sock']) { socket_write($client[$j]['sock'], $output); } }

Practical Uses
Now that you know the basics of creating a socket server, the only limitation is your imagination. Here are some ideas:

Chat server (using a text based or graphical interface). This can be for fun or real application (e.g. customer support representatives). Real time information streaming (news, stocks etc.)

Streaming multimedia (images, videos and sounds) Authentication server Simple web, POP3, SMTP and FTP servers.

Furthermore, the sockets library can be used to create client programs as well as servers.

Security
Security must be taken into consideration when creating programs that will be accessible online. This is true for normal PHP scripts as well as continuously running programs (such as socket servers).

There are many facets to a comprehensive security policy, from programming, to access control and more.

There are many items to consider when planning your security policy. Here are a few to get you started:

File access you must limit file access. If the server allows access to files (e.g. web server), make sure you allow only files in a certain folder to be served out. It would be a bad mistake to serve /etc/passwd or /etc/shadow. File access can be limited by defining a parent or root folder and only allowing subfolders to be accessed by the socket server. Another good idea is to sanitize the data sent by the user to remove extraneous or dangerous characters (such as ../ used to access files in folders one level back).

Fail Safe in cases where the server fails, you should make arrangements for it to fail safely. That is, if your socket server can no longer function as desired, it should be brought to a state where is can cause no damage exit the program and halt execution. Authentication for sensitive services, it is recommended that you use authentication as part of the communications specifications. Even if you create a custom front-end in flash or Visual Basic,

there is no guarantee that someone will not be able to sniff the network connection and decipher the protocol you are using (and any data transferred).

One great way to do authentication is not to allow any actions to occur until a user has successfully authenticated himself (in the example above, this can be done by setting $client[$i]['authenticated'] = true upon successful authentication). Encryption encryption is a great way to protect sensitive information en-route to the server. Encrypted systems can be especially useful when combined with the above-suggested procedures (especially authentication over encrypted channels). Luckily, PHP has an excellent encryption library that you can use (the Mcrypt library see http://www.php.net/mcrypt for further details).

One at a time please


Usually you will want the socket server to have one instance operating at any one given time. In the above example, you use socket_bind() or die('Could not bind to address'). This means that if the program tries to bind to a port already in use, the program will exit giving an error message.

More refined program control can be achieved by using the command line program pidof (/sbin/pidof). This command lists the process ID (pid) of running programs. If you use pidof, make sure you supply the -x argument to display the process id of shell scripts that are running as well.

Possible Additions and Improvements

Add process control and threads (can be done using PCNTL). Add process control functions (discussed above) to ensure that only a single instance is running at any one time (using /sbin/pidof x program name). Run a periodic script to check that the service is running. This can be done as a cron

job in *nix (type man crontab in the *nix shell for further information) or as a Windows scheduled tasks (under Control Panel). Front-end interface can be done in C++, VB, Flash (using XMLSockets), Java or any software that supports TCP/IP or UDP sockets. If the server will be running unattended, you may wish to create a custom error handling function that will log to a text file or a database instead of printing the error messages to stdout.

About the Author


Ori Staub is a senior systems analyst, developer, and consultant specializing in web-based solutions. He has developed many e-commerce and web solutions to work tightly with existing client business models.

Ori can be contacted directly at os@zucker-staub.com

Das könnte Ihnen auch gefallen