Team LiB
Previous Section Next Section

Socket Programming

Sockets are an incredibly useful, but also largely misunderstood, technology for communication between two processes in a network. These processes can exist on the same machine, talking to each other through a local socket for interprocess communications, or on different machines via the Internet. Although the concept of socket programming itself is beyond the scope of this book, in this section I'll introduce you to the basic fundamentals needed to use PHP's socket extension to write your own socket servers and clients.

NOTE

To use sockets in PHP, you must compile PHP with the --enable-sockets ./configure option or load the sockets extension dynamically.


When trying examples found in this section of the book, be aware that they are designed to be run from a shell environment using a command-line version of PHP. Although they will run in a Web browser, doing so is not recommended. In the case of scripts that create socket servers, their use can be demonstrated using any program capable of establishing a socket connection, such as telnet (recommended).

Socket Basics

Although there are a number of types of sockets, all sockets function on the same basic principalgetting data from program A to program B. These programs can be on the same machine using interprocess communication (IPC) or on remote machines (such as a Web server and a browser). Sockets can be reliable, doing everything possible to ensure that data gets from point A to B (TCP) or unreliable, where data is sent without regard for whether it was received (UDP). Sockets are also described as "blocking" or "nonblocking." Blocking sockets force your application to wait for data to become available, whereas nonblocking sockets do not. Although all sockets are bidirectional, as you will see in this chapter, there is a difference between server and client sockets as well.

In this book, we'll be examining Internet-based TCP sockets because they are the most common in use today. However, the concepts and code outlined in this section apply to most socket operations.

Creating a New Socket

Regardless of the type of socket being created (client or server), all sockets are initialized using the same facilitiesspecifically, the socket_create() function. The syntax for this function is as follows:

socket_create($domain, $type, $protocol);

$domain represents the type of socket being created and must be one of the constants in Table 21.3. The second parameter, $type, is the type of communication that will be performed on this socket and must be a constant from Table 21.4. The final parameter, $protocol, is the protocol being used on this socket. This parameter can be any valid protocol number (see the getprotobyname() function later in this chapter) or the constants SOL_UDP or SOL_TCP for TCP/UDP connections. On execution, this function either returns a resource representing the created socket or a Boolean false on error.

Table 21.3. Domain Constants for Socket Connections

AF_INET

Internet (IPv4) protocols

AF_INET6

Internet (IPv6) protocols

AF_UNIX

Local interprocess communication


Table 21.4. Socket Type Constants

SOCK_STREAM

A sequenced and reliable bidirectional connection-based stream. Most common in use.

SOCK_DGRAM

An unreliable, connectionless socket that transmits data of a fixed length. Very good for streaming of data where reliability is not a concern.

SOCK_SEQPACKET

Similar to stream sockets, except data is transmitted and received in fixed-length packets.

SOCK_RAW

A raw socket connection, useful for performing ICMP (Internet Control Message Protocol) operations such as traces, pings, and so on.

SOCK_RDM

A reliable but unsequenced socket similar to that of SOCK_DGRAM.


The socket_create() function is the first function call in any socket communication that initializes the socket resource to be used in subsequent socket operations. Recall that earlier in the section, I mentioned that sockets can be used both locally for IPC or remotely in a client/server fashion. The scope of a particular socket's use is called its domain. In PHP, the following domains are available by specifying one of the constants in Table 21.3 for the $domain parameter of the socket_create() function:

After the domain has been established, the type of connection to be created using this socket must be determined. These types are shown in Table 21.4:

As you can see, there are many options when you are selecting the type of socket that will be created. In general, most socket communications occur over either SOCK_STREAM or SOCK_DGRAM sockets. Although the usefulness of SOCK_STREAM is obvious (most of the Internet runs on this type of socket via TCP), it may not be overly obvious why SOCK_DGRAM (used with the UDP protocol) would be useful. After all, why would you ever want to use an "unreliable" method of transmitting data? The answer comes when examining a constant streaming of data, which is being processed in real-time, from a server to a client. Because a lost packet is worthless to this sort of application (because it contained time-sensitive data which is no longer relevant), there is no need to resend the data.

Now that the domain and type of socket has been explained, the final step in the creation of the socket is the actual protocol that will be used to communicate over the socket. Every protocol is designed to operate under a particular socket type, which must be known previously. For the purposes of this chapter, we'll be using Internet IPv4 sockets using the SOCK_STREAM type and SOL_TCP (TCP) connections.

After a socket resource has been created, it can be destroyed using the socket_close() function with the following syntax:

socket_close($socket);

$socket is the socket to destroy.

Dealing with Socket Errors

Like all technologies, Sockets are susceptible to errors such as network failure. When you are working with sockets, each function provides a means (generally returning a Boolean false) to indicate that something has gone wrong. When such a situation occurs, you can retrieve the cause of the error by using two functions, the first of which is socket_last_error():

socket_last_error($socket);

$socket is the socket to retrieve the error from. As its name implies, this function is used to return the last error that occurred on the specified socket. This error is in integer form. To translate it into a human-understandable string, the socket API also provides the socket_strerror() function:

socket_strerror($error_code);

$error_code is the value returned from the socket_last_error() function. This function will return a string representing the error returned from socket_last_error().

Creating Client Sockets

Creating a socket suitable to connect to another socket on the Internet is done by using the socket_connect() function.

socket_connect($socket, $address [, $port]);

$socket is the socket to use for the connection, $address is the IP address of the server to connect to, and the optional parameter $port is the port on the server to connect to. Although the $port parameter is optional in the prototype of the function, when making connections on sockets in the AF_INET or AF_INET6 domains it is required. When executed, this function connects to the specified server using the provided socket and returns a Boolean indicating whether the request was successful.

After a connection has been made to another socket listening as a server, data can be transmitted and received through that socket using the socket_read() and socket_write() functions. Because we are a client, the first step after making a connection often is to send some sort of data; therefore, we will look at the socket_write() function first:

socket_write($socket, $buffer [, $length]);

$socket is the socket to write the data specified by the $buffer parameter to. The third optional parameter, $length, can also be specified if desired (otherwise, the entire buffer will be written). When executed, this function sends the provided buffer through the connected socket specified and returns the number of bytes written, or a Boolean false on error.

To read data from a socket, you can use the socket_read() function with the following syntax:

socket_read($socket, $length [, $type]);

$socket is the socket to read a maximum total of $length bytes from. Optionally, the $type parameter can also be specified as described in Table 21.5, which specifies the way data will be read from the socket.

Table 21.5. Type Constants for socket_read()

PHP_BINARY_READ

Treat the data as binary (default).

PHP_NORMAL_READ

Read data until the entire length of data has been read or until a newline/linefeed character is encountered (the \r or \n characters).


As our first sockets example, Listing 21.7 combines what we have just learned in this section to retrieve the index page of a website. This is done by sending a simple HTTP 1.0 GET request and then reading the results into a variable.

Listing 21.7. Retrieving a Website Using Sockets
<?php

    $address = "127.0.0.1";
    $port = 80;

    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

    socket_connect($socket, $address, $port);

    socket_write($socket, "GET /index.php HTTP/1.0\n\n");

    $result = "";

    while($read = socket_read($socket, 1024)) {

        $result .= $read;

    }

    echo "Result received: '$result'\n";

    socket_close($socket);


?>

With simple client socket communications explained, let's now take a look at the other side of the coin by introducing a simple socket-based server.

Creating Server Sockets

When you create server sockets, they are almost always bidirectional services; generally, you can rely on concepts learned for client socket communications. Creating a server socket is a three-step process. The first step is to bind the socket to a particular address and port using the socket_bind() function:

socket_bind($socket, $address [, $port]);

$socket is the socket to bind to the address specified by $address. If the socket exists within the AF_INET or AF_INET6 domains, the optional parameter $port must be specified. When executed, this function attempts to bind the created socket to the address and port specified and returns a Boolean value indicating whether the binding was successful.

NOTE

When binding to an address, be aware that your socket will not be able to accept connections on anything other than the specified address and port you specify! This means that binding to the local host IP (127.0.0.1) will make your socket able to accept only local connections.


After being bound to a address, the socket must be instructed to listen for traffic attempting to communicate with it. This is done using the socket_listen() function:

socket_listen($socket [, $backlog]);

$socket is the bound socket that should begin listening. The optional parameter $backlog is used to create a queue by specifying the maximum number of incoming connections that will be queued. If this parameter is not specified, the connecting client socket will have its connection refused if the socket is currently unavailable. When executed, this function returns a Boolean indicating whether the socket was successfully configured to listen for socket connections.

The third and final step in creating a socket server is to actually instruct the socket itself to accept any incoming connections it receives. This is done using the socket_accept() function:

socket_accept($socket);

$socket is the bound, listening socket to accept connections on. When executed, this function will not return until a connection is waiting to be accepted on the socket, at which point it will return a new socket resource used for communications on the socket. If the socket specified in the $socket parameter has been set to nonblocking, the socket_accept() function will always return false immediately.

NOTE

The socket resource returned by the socket_accept() function cannot be reused, because it applies only to the specific connection made. The socket passed to it in the $socket parameter, however, may be reused.


Listing 21.8 creates a simple socket server that accepts a single connection, accepts a maximum of 1,024 bytes of input, and displays that input to the user.

Listing 21.8. Creating a Simple Socket-Based Server
<?php
    /* Disable script time-out */
    set_time_limit(0);
    $address = "127.0.0.1";
    $port = 4545;

    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

    socket_bind($socket, $address, $port);

    socket_listen($socket);
    $connection = socket_accept($socket);

    $result = trim(socket_read($connection, 1024));

    echo "Result received: '$result'\n";

    socket_close($connection);

    socket_shutdown($socket);
    socket_close($socket);


?>

NOTE

To create a server whose sockets listen on a port below 1,000, the executing user must have administrator/superuser rights. Also note that the preceding script will not terminate until a connection has been made, causing what may look like a lock-up.


Working with Multiple Sockets at Once

Listing 21.8 is a socket-based server; however, it is marginally useful because only a single connection can be made to it at a time. To create a more useful socket server, you will need to be able to process multiple sockets at the same time. To do so, we'll need to introduce the socket_select() function whose syntax is as follows:

socket_select(&$read, &$write, &$error, $sec [, $usec]);

$read, $write, and $error, are all pass-by-reference variables (arrays, specifically). These arrays should contain a list of all of the sockets we are interested in monitoring for reading, writing, and error catching, respectively. For instance, placing an active socket into the array passed to the $read parameter would instruct PHP to check to see whether the socket had any data to read. The final two parameters, $sec and the optional $usec, are timeout values that control how long the socket_select() function will wait before returning control to PHP. When executed, the socket_select() function returns an integer representing the total number of changed sockets (from the list provided) and modifies the $read, $write, and $error arrays by removing those entries that did not change from them. The result is that each array will contain a list of those sockets that require attention:

  • Sockets listed in the $read array have data to be read from them, or an incoming connection to them.

  • Sockets listed in the $write array have data to be written to them.

  • Sockets listed in the $error array have encountered an error condition that must be handled.

In the event of an error, socket_select() returns a Boolean false.

To use this function in a practical Socket application, first a socket must be created to represent our server as a whole. This "master" socket will bind to the desired address and port and begin actually listening for connections. This socket is then added to the $read array, and a controlled infinite loop is entered. The socket_select() function is then used to monitor the master socket for a new connection. When a new connection is found, the socket_accept() function is triggered, which results in the creation of a new server socket used for communications with the connecting client. This new communications socket is then monitored through the same socket_select() call (by adding it to the same array as our master socket) and logic is used to provide the actual functionality of our server. Listing 21.9 provides a working example of a simple server that accepts a configurable number of connections:

Listing 21.9. Creating Multisocket Servers in PHP
<?php

    set_time_limit(0);

    $NULL = NULL;

    $address = "127.0.0.1";
    $port = 4545;

    $max_clients = 10;
    $client_sockets = array();

    $master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

    $res = true;

    $res &= @socket_bind($master, $address, $port);
    $res &= @socket_listen($master);

    if(!$res) {

        die("Could not bind and listen on $address:$port\n");

    }

    $abort = false;

    $read = array($master);

    while(!$abort) {

        $num_changed = socket_select($read, $NULL, $NULL, 0, 10);

        /* Did any change? */
        if($num_changed) {

            /* Did the master change (new connection) */

            if(in_array($master, $read)) {

               if(count($client_sockets) < $max_clients) {

                    $client_sockets[] = socket_accept($master);
                    echo "Accepting connection (" . count($client_sockets) .
                         " of $max_clients)\n";

               }

            }

         /* Cycle through each client to see if any of them changed */
            foreach($client_sockets as $key => $client) {

                /* New data on a client socket? Read it and respond */
                if(in_array($client, $read)) {
                    $input = socket_read($client, 1024);

                    if($input === false) {

                        socket_shutdown($client);
                        unset($client_sockets[$key]);

                    } else {

                        $input = trim($input);

                        if(!@socket_write($client, "You said: $input\n")) {
                            socket_close($client);
                            unset($client_sockets[$key]);
                        }

                    }

                    if($input == 'exit') {

                        socket_shutdown($master);
                        $abort = true;
                    }

                }

            }

        }

        $read = $client_sockets;
        $read[] = $master;

    }


?>

NOTE

Listing 21.9 highlights a limitation of PHP's scripting engine that requires a rather confusing-looking workaround in our call to socket_select():

$num_changed = socket_select($read, $NULL, $NULL, 0, 10);

Note the use of a variable named $NULL. In PHP, for functions that accept their parameters by reference (as the socket_select() function does for the first three), NULL is an unaccepted value. However, passing NULL as one or more of the lists is a completely acceptable behavior. Thus, the workaround is to assign the NULL value to a variable:

$NULL = NULL;

and pass that as our value to socket_select().


    Team LiB
    Previous Section Next Section