网络编程
因特网连接
因特网客户端和服务器通过在连接上发送和接受字节流来通信。对进程的意义上而言,连接是点对点点,从数据可以同时双向流动的角度来说,连接是全双工的,从收发顺序的角度来说,连接是可靠的。
套接字是连接的一个端点,每个套接字都有套接字地址。套接字地址由地址和16位整数的端口组成。当客户端发起一个请求时,客户端套接字地址中的端口由内核自动分配,被称为临时端口(ephemeral port),而服务器套接字地址中的端口通常是某个知名端口。一个连接由两端的套接字地址唯一确定,这对套接字地址叫做套接字对(socket pair)。
套接字地址结构,从 Unix 程序的角度来看,套接字就是一个有相应描述符的打开文件。因特网的套接字地址存放在类型为 sockaddr_in 的 16 字节结构中。
套接字接口(socket interface)是一组函数,和 Unix I/O 函数结合起来,用以创建网络应用。
socket 函数
客户端和服务器使用 socket 函数来创建一个套接字描述(socket descriptor)。socket 返回的 clinetfd 描述符仅是部分打开的,还不能用于读写。
connect 函数
客户端通过 connect 函数来建立和服务器的连接。connect 函数会阻塞,一直到连接成功建立或是发送错误。 如果成功, sockfd 描述符就可以读写了,并得到客户端的套接字对,唯一的确定了客户端主机上的客户端进程。
open_clientfd 函数
将 socket 和 connect 函数包装成 open_clientfd,客户端用它来建立和服务器的连接。
bind 函数
bind 函数告诉内核将服务器套接字地址和套接字描述符联系起来。
linsten 函数
listen 函数将 sockfd 从一个主动套接字(active socket)转化为一个监听套接字(listening socket), 该套接字可以接受来自客户端的连接请求。 默认情况下,内核会认为 socket 函数创建的描述符对应于主动套接字,它存在于一个连接的客户端。 服务器通过调用 listen 函数告诉内核,描述符用于服务器。
open_listenfd 函数
将 socket、bind、和 listen 函数结合成 open_listenfd 函数,服务器用它来创建一个监听描述符。 这个描述符准备好在知名端口上接受连接请求
accept 函数
accept 函数等待来自客户端的连接请求到达侦听描述符 lintenfd,然后在 addr 中填写客户端的套接字地址, 并返回一个已连接描述符(connected descriptor),这个描述符可被用来利用 Unix I/O 函数与客户端通信。
监听描述符作为客户端连接请求的一个端点,被创建一次,并存在于服务器的整个生命周期。 已连接符是客户端和服务器之间已经建立起来了的连接的一个端点。服务器每次接受连接请求时都会创建一次,它只存在于服务器为一个客户端服务的过程中。
Web 服务器
对于 Web 客户端和服务器而言,内容是一个 MIME 类型相关的字节序列。Web 服务器以两种不同的方式向客户端提供内容:服务静态内容和服务动态内容。
一个 HTTP 请求的组成由一个请求行(request line),后面跟随零个或更多的请求报头(request header),再跟随一个空的文本行来终止报头列表。请求行的形式是<method> <uri> <version>
服务动态内容
在服务器接受一个 GET 请求后,
服务器调用 fork 来创建一个子进程。子进程设置 CGI 环境变量 QUERY_STRING。服务器调用 execve 在子进程的上下文中执行 /cgi-bin/adder
程序。服务器通过环境变量来将部分信息传递给子进程。
一个 CGI 程序将它的动态内容发送到标准输出。在子进程加载并运行 CGI 程序之前,它使用 Unix dup2 函数将标准输出重定向到和客户端相关连的已连接描述符。 因此,任何 CGI 程序写到标准输出的东西都会直接到达客户端。
因为父进程不知道子进程生成的内容的类型和大小,所以子进程就要负责生成 content-type 和 content=length 相应报头,以及终止报头的空行。