目前,比较流行的Web服务器开源软件如Apache等。Web服务器的作用是接收来自客户端的请求并作出相应的响应,为客户端提供某种服务,如资源请求。
首先来看一个用perl脚本写的非常简单的Web服务程序,客户端和服务器采取socket套接字通信:
#!/usr/bin/perl
use Socket;
use Carp;
use FileHandle;
# (1) use port 8080 by default, unless overridden on command line
$port = (@ARGV ? $ARGV[0] : 8080);
# (2) create local TCP socket and set it to listen for connections
$proto = getprotobyname('tcp');
socket(S, PF_INET, SOCK_STREAM, $proto) || die;
setsockopt(S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die;
bind(S, sockaddr_in($port, INADDR_ANY)) || die;
listen(S, SOMAXCONN) || die;
# (3) print a startup message
printf(" <<<Type-O-Serve Accepting on Port %d>>>\n\n",$port);
while (1)
{
# (4) wait for a connection C
$cport_caddr = accept(C, S);
($cport,$caddr) = sockaddr_in($cport_caddr);
C->autoflush(1);
# (5) print who the connection is from
$cname = gethostbyaddr($caddr,AF_INET);
printf(" <<<Request From '%s'>>>\n",$cname);
# (6) read request msg until blank line, and print on screen
while ($line = <C>)
{
print $line;
if ($line =~ /^\r/) { last; }
}
# (7) prompt for response message, and input response lines,
# sending response lines to client, until solitary "."
printf(" <<<Type Response Followed by '.'>>>\n");
while ($line = <STDIN>)
{
$line =~ s/\r//;
$line =~ s/\n//;
if ($line =~ /^\./) { last; }
print C $line . "\r\n";
}
close(C);
}
该脚本的处理过程如下图所示:
当然,实际的Web服务程序需要符合HTTP协议规范,远非这么简单,可以分析一个更为通用的Web服务器对来自客户端请求的响应过程如下图所示,
大致可以分为7个步骤:
(1)建立连接:如果客户端已经打开了一个与服务器的persistent connection,就可以直接使用该连接,否则需要新建一个连接。
(2)接收客户端的请求:对请求消息进行缓冲、解析处理;
(3)处理请求:对于繁忙的服务器,可能同时需要处理的连接请求成千上万个,下面会介绍服务器采取的一些架构;
(4)访问请求的资源:根据请求消息中的URL映射到服务器上某个指定的资源;
(5)创建响应;包括判断资源的MIME类型,资源长度等,以及响应是否成功或者重定向;
(6)发送响应给客户端,如果是persistent connection,响应发送完成后,仍保留连接打开状态。
(7)日志记录;
如果服务器上已经打开了若干个连接(每个连接来自不同的客户端),则服务器和这些连接连接的通信(I/O)的处理方式可以参考下图:
上面画出了4种服务器处理连接的设计:
(1)单线程I/O结构:服务器只使用一个线程按照串行的方式处理所有连接,只有当前一个连接的操作完成之后才进行下一个连接的处理;
(2)多线程I/O结构:同时创建多个线程(按需或者预先创建好一个线程池),这样支持并发的连接处理;
(3)多路复用结构:一个线程同时处理多个连接,监控所有连接的状态,当某个连接需要的数据准备好了就对它进行处理;
(4)多路复用的多线程结构:综合了多线程和多路复用的混合式结构。