7.2. SocketsSockets first appeared in the form of sockets as part of BSD in 1983. An Internet socket is defined as a combination of an IP address, a port, and a protocol. A socket establishes a two-way communication pipeline between applications. Each end of the socket connection is bound to a virtual "port" on the machine, allowing the network layer to figure out which application to send incoming data to. All data sent using TCP or UDPwhich includes most of the user-generated traffic on the Internetuses a socket connection. In PHP we can easily create sockets using the fsockopen( ), and then read and write to it using the standard I/O functions: $sock = fsockopen($ip_address, $port); fwrite($sock, "hello world\n"); $data = fread($sock, 1024); fclose($sock); The above example is trivially simple. We connect to a port on a remote server using TCP. We send a string over the socket ("hello world\n") and then read back up to 1,024 bytes of response. We then close the socket, disconnecting from the server. There are a number of problems with the above code, though, all related to error checking. Our first rule tells us to expect failure at every point in the chain. We're performing a sequence of four actions (open, write, read, and close), and one or more of those could fail. The open operation could fail if the remote server can't be routed to, isn't listening on the accepted port, or the listening service has hung. The write operation could fail if the connection closes during the transfer. The read operation could fail because no data becomes available (or takes too long) or the connection fails. The close operation could fail if the connection fails (although we typically don't care about problems during a close operation). Whenever we perform any socket I/O operations, we need to explicitly check for failure at every step. This should be the case in any kind of development but is especially important in web development. Typically, each request is served by a single thread (or process), so I/O calls are blocked. Time spent waiting for an I/O call is time the user will be waiting at the other end. If a remote server hangs and it takes 10 seconds to timeout, the end user is going to have to wait those 10 seconds for every page containing a remote service request. Any operation can take a long time if network conditions degrade, the remote host hangs or is slow, or the remote host is busy. When connecting, we need to set up connection and I/O timeout values in seconds: function sock_connect($host, $port, $connection_timeout, $io_timeout){ $sock = @fsockopen($host, $port, &$errno, &$errstr, $connection_timeout); if ($sock){ @stream_set_timeout($sock, $io_timeout); } return $sock; } Once we have opened a socket that we can read and write to without hanging indefinitely, we can think about actually reading and writing to it. First we'll try and write some data. We need to make sure that the data actually got written and that we didn't encounter a timeout: function sock_write($sock, $data, $len){ # try and write the data if (@fwrite($sock, $data, $len) === FALSE){ @fclose($sock); return 0; } # check we didn't time out $meta = @stream_get_meta_data($sock); if ($meta[timed_out]){ @fclose($sock); return 0; } return 1; } We already have a fair amount more code than our original simple example. When we want to start reading data, it gets even trickier. This next function allows us to read a specified number of bytes and checks for all of our error conditions: function sock_read($sock, $len, $read_block_size){ # end of file? if (@feof($sock)){ @fclose($sock); return 0; } # try and read some data $data = ''; $meta = @stream_get_meta_data($sock); while ((strlen($data) < $len) && !@feof($sock) && !$meta[timed_out]){ $diff = $len - strlen($data); $rlen = min($diff, $read_block_size); $data .= @fread($sock, $rlen); $meta = @stream_get_meta_data($sock); } # check we didn't time out if ($meta[timed_out]){ @fclose($sock); return 0; } # check the socket didn't close if (@feof($sock)){ @fclose($sock); return 0; } # check we filled our buffer if (!strlen($data) == $len){ @fclose($sock); return 0; } return $data; } With these three functions, we can start to safely perform socket I/O without worrying about the various failure scenarioswhen something goes wrong, our function returns 0, and we can start the process again (we'll talk more about redundancy later in this chapter, in "Remote Services Redundancy"). |