以前做的一个例子,用PHP模拟Web服务器和客户端。年初的时候网站数据丢失,重新补传一次。
服务端代码server.php:
<?php $host = '127.0.0.1'; $port = 9083; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($socket === FALSE) { display_socket_error(); } if (socket_bind($socket, $host, $port) === FALSE) { display_socket_error(); } if (socket_listen($socket, 3) === FALSE) { display_socket_error(); } while (true) { $csocket = socket_accept($socket); if ($csocket === FALSE) { display_socket_error(); } $request = parse_socket_request($csocket); print_r($request); if (strcasecmp($request['uri'], '/quit') === 0) { socket_close($csocket); break; } handle_request($csocket, $request); socket_close($csocket); } socket_close($socket); function display_socket_error() { $errorcode = socket_last_error(); $errormsg = socket_strerror($errorcode); die("Socket error: [$errorcode] $errormsg"); } function parse_socket_request($socket) { $result = socket_read($socket, 4096, PHP_BINARY_READ); if ($result === FALSE) { display_socket_error(); } $lines = explode("\r\n", $result); list($method, $uri, $protocol) = explode(" ", $lines[0], 3); $headers = array(); for ($i = 1; $i < count($lines); $i++) { if ($lines[$i] === "") break; $parts = explode(":", $lines[$i], 2); $headers[trim($parts[0])] = trim($parts[1], " \""); } $body = ''; for ($i = $i + 1; $i < count($lines); $i++) { $body .= $lines[$i] . "\r\n"; } return array('protocol' => $protocol, 'uri' => $uri, 'method' => $method, 'headers' => $headers, 'body' => $body ); } function send_response_line($socket, $line) { if (socket_write($socket, $line, strlen($line)) === FALSE) { display_socket_error(); } } function handle_request($socket, $request) { $abs_path = realpath(dirname(__FILE__) . '/../' . $request['uri']); if (is_file($abs_path)) { send_response_content($socket, $abs_path, 200, "OK"); return; } if (is_dir($abs_path)) { $index_file = $abs_path . DIRECTORY_SEPARATOR . 'index.html'; if (is_file($index_file)) { send_response_content($socket, $index_file, 200, "OK"); return; } } send_response_404($socket); } function send_response_content($socket, $path, $statusCode, $statusText) { $mimetype = get_content_type($path); $content = file_get_contents($path); send_response_line($socket, "HTTP/1.1 " . $statusCode . " " . $statusText . "\r\n"); send_response_line($socket, "Server: PHPSocketServer\r\n"); send_response_line($socket, "Accept-Ranges: bytes\r\n"); send_response_line($socket, "Content-Type: " . $mimetype . "\r\n"); send_response_line($socket, "Content-Length: " . strlen($content) . "\r\n"); send_response_line($socket, "\r\n"); send_response_line($socket, $content); } function send_response_404($socket) { send_response_content($socket, realpath(dirname(__FILE__) . '/error404.html'), 404, 'Not Found'); } function get_content_type($path) { $mimetypelist["gif"] = "image/gif"; $mimetypelist["jpeg"] = "image/jpeg"; $mimetypelist["jpg"] = "image/jpeg"; $mimetypelist["jpe"] = "image/jpeg"; $mimetypelist["png"] = "image/png"; $mimetypelist["tiff"] = "image/tiff"; $mimetypelist["tif"] = "image/tiff"; $mimetypelist["bmp"] = "image/x-ms-bmp"; $mimetypelist["html"] = "text/html"; $mimetypelist["htm"] = "text/html"; $mimetypelist["txt"] = "text/plain"; $ext = pathinfo($path, PATHINFO_EXTENSION); if (isset($mimetypelist[$ext])) return $mimetypelist[$ext]; else return ''; }
客服端代码client.php:
<?php $host = '127.0.0.1'; $port = 9083; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($socket === FALSE) { display_socket_error(); } if (socket_connect($socket, $host, $port) === FALSE) { display_socket_error(); } echo "Connection successful on IP $host, port $port\r\n\r\n\r\n"; send_request_line("GET /abc.txt HTTP/1.1\r\n"); send_request_line("Host: localhost\r\n"); send_request_line("\r\n"); $response = parse_socket_response(); print_r($response); socket_close($socket); function send_request_line($line) { global $socket; if (socket_write($socket, $line, strlen($line)) === FALSE) { display_socket_error(); } } function display_socket_error() { $errorcode = socket_last_error(); $errormsg = socket_strerror($errorcode); die("Socket error: [$errorcode] $errormsg"); } function parse_socket_response() { global $socket; $result = socket_read($socket, 4096, PHP_BINARY_READ); if ($result === FALSE) { display_socket_error(); } /* 返回的响应信息如下: HTTP/1.1 200 OK Date: Wed, 27 Jul 2011 01:49:49 GMT Server: Apache/2.2.15 (Win32) mod_ssl/2.2.15 OpenSSL/0.9.8n Last-Modified: Tue, 26 Jul 2011 05:57:42 GMT ETag: "200000006a2ae-16-4a8f29c2a18a6" Accept-Ranges: bytes Content-Length: 22 Content-Type: text/plain This is a server file. 每一行都以\r\n作为结尾。 响应信息可以分为三个部分: 1. 第一行是响应行,以空格分割,依次是协议,状态码和状态说明。 2. 响应头,在响应信息中以一个空行分割。名称和值以冒号分开。 3. 内容,在响应信息的最后面。 */ $lines = explode("\r\n", $result); list($protocol, $statusCode, $statusText) = explode(" ", $lines[0], 3); $headers = array(); for ($i = 1; $i < count($lines); $i++) { if ($lines[$i] === "") break; $parts = explode(":", $lines[$i], 2); $headers[trim($parts[0])] = trim($parts[1], " \""); } $body = ''; for ($i = $i + 1; $i < count($lines); $i++) { $body .= $lines[$i] . "\r\n"; } return array('protocol' => $protocol, 'statusCode' => $statusCode, 'statusText' => $statusText, 'headers' => $headers, 'body' => $body ); }