网络应用由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源,并且通过操作这种资源来为客户端提供服务。
1. 网络模型
物理上而言,网络是一个按照地理远近组成的层次系统。最低层次是LAN(本地局域网),LAN最流行的技术是以太网。
早期,多个主机通过双绞线和集线器相连,和同一个集线器相连的主机被称为以太网段。集线器是物理层设备,作用是将端口中收到的数据帧不加分辨地广播到其余所有端口。
在集线器的基础上,一个网桥可以将两个以太网段互联成较大的局域网,称为桥接以太网(bridged Ethernet)。相比于集线器,网桥属于链路层设备,其内动态维护了端口转发表。
事实上,目前集线器和网桥均已被淘汰。广泛使用的是交换机。
在局域网的基础上,路由器将多个不兼容的局域网互连起来,组成一个internet(互联网络)。
internet指一般概念上的互联网络,开头大写字母的Internet指的是全球IP因特网。
为了解决数据从源主机跨网络发送数据到目标主机的问题,在主机和路由器上运行的协议软件主要解决下面两个问题
- 命名机制: 不同的局域网技术有不同的主机地址分配方式。互联网络协议通过定义一种统一的主机地址格式消除这种差异。
- 传送机制: 互联网络协议通过定义一种把数据分片打包为包头和有效载荷的统一方式消除不同局域网络的差异。
2. 全球IP因特网
IP地址
IP地址就是一个32位的无符号整数,按大端法传送。
1 |
|
IP地址通常使用点分十进制表示,inet_pton
和inet_ntop
可以用于IP地址和点分十进制串之间的转换。
1 |
|
因特网域名
域名是一串用句点分割的单词。域名集合构成了一个层次结构,每个域名编码了它在这个层次中的位置。
因特网定义了域名集合和IP地址集合间的映射。目前这些映射由分布在世界上的DNS(Domain Name System)数据库维护。
因特网连接
客户端和服务器间的连接是点对点、全双工的。连接的一个端点是一个套接字,每个套接字都有相应的套接字地址。
- 套接字地址: (地址:端口)
- 因特网地址
- 16位的整数端口
- 连接: (cliaddr:cliport, servaddr:servport)
3. 套接字接口
套接字地址
因特网的套接字地址存放在sockaddr_in
的16字节结构中。
注意: 可能还有其它互联网络的套接字地址,这些套接字地址在被传送给connect, bind, accept
函数前都需要先强制转换成通用的sockaddr
类型。
1 |
|
socket函数
1 |
|
socket
函数返回的clientfd
描述符只是部分打开的,还不能用于读写。
例如,socket(AF_INET, SOCK_STREAM, 0);
AF_INET
表示正在使用32位IP地址SOCK_STREAM
表示这个套接字是连接的一个端点
注意: socket
的各个参数最好使用getaddrinfo函数自动生成。
connect函数
connect
函数会一直阻塞直到连接成功建立或发生错误。
1 |
|
3.1. 服务器相关套接字函数
bind函数
bind
告知内核将addr
中的套接字地址和套接字描述符中的sockfd
相互关联。请使用getaddrinfo
为它提供参数。
listen函数
默认情况下使用socket()
函数创建的是主动套接字(active socket),它被用于一个连接的客户端。listen()
函数则将主动套接字转换为监听套接字(listening socket)。该套接字被服务器端使用。
accept函数
accept
等待客户端请求到达listenfd,然后在addr
中填写客户端的套接字地址,并返回一个已连接描述符(connected descriptor),可用于和客户端通信。
1 |
|
3.2. 主机和服务的转换
3.2.1. getaddrinfo
和getnameinfo
- 这两个函数用于在(域名,地址,端口)字符串表示和socket地址结构间的转换。
- 这两个函数是可重入的,可以用于线程程序中。
getaddrinfo
函数将主机名、主机地址、服务名和端口号的字符串转化成套接字地址结构。
1 |
|
- host: 域名或者IP地址
- service: 服务名(例如:
http
)或者端口号 - hints: 一个
addrinfo
结构,仅能设置下面的参数,其它字段必须为0ai_family
: 默认返回IPv4和IPv6的套接字地址。设置本字段可以限制只返回IPv4或IPv6。ai_socktype
: 默认对于host关联的每个地址(可能关联多个),最多返回三个addrinfo
结构,ai_socktype
字段分别是。设置本字段可以限制返回的类型。- 连接
- 数据报
- 原始套接字
- ..
ai_flags
:AI_ADDRCONFIG
: 使用连接时建议使用,仅当本地主机配置为IPv4/IPv6时返回IPv4/IPv6地址。AI_CANONNAME
:host
对应的官方域名会放在列表第一个addrinfo
中。AI_NUMBERICSERV
: 强制service
为端口号。AI_PASSIVE
: 默认返回主动套接字。设置这个,同时需要将host
设置为NULL
,则返回监听套接字。得到的addrinfo
结构中的地址字段将会是通配符,表示可以接收所有IP地址的请求。
- result
- 返回一个指向
addrinfo
结构的链表
- 返回一个指向
- 注意:
addrinfo
字段不是透明的,能够直接访问并用于传递给socket, connect, bind
函数。result
中的列表使用完后需要使用freeaddrinfo()
释放
和getaddrinfo
功能相反的是getnameinfo
函数。用于从套接字地址sa
中提取出主机和服务名字符串。
1 |
|
- flags:
NI_NUMBERICHOST
: 强制返回数字的地址字符串。NI_NUMBERICSERV
: 强制返回数字的端口号。
3.2.2. open_clientfd
和open_listenfd
通过使用getaddrinfo
能够提高程序可移植性,而不依赖任何特定版本的IP协议。
1 |
|
1 |
|
4. Web 服务器
Web服务器和客户端通过HTTP协议交互,它是一个基于文本的应用级协议。
Web服务和常规的文件检索服务之间的区别主要在于Web内容可以使用一种叫做HTML(Hypertext Markup Language,超文本标记语言)的语言来编写。浏览器能够根据这种语言显示文本和图形对象。
但是,HTML真正强大之处在于一个页面可以包含指针(超链接)。
4.1. Web内容
Web内容是一种MIME(Multipurpose Internet Mail Extensions,多用途网际邮件扩充协议)类型的字节序列。
MIME类型 | 描述 |
---|---|
text/html | HTML页面 |
text/plain | 无格式文本 |
application/postscript | Postscript文档 |
image/gif | GIF格式编码的二进制图像 |
image/png | PNG格式编码的二进制图像 |
image/jpeg | JPEG格式编码的二进制图像 |
Web服务器能够提供两种类型的内容
- 静态内容: 将一个静态磁盘文件的内容返回给客户端。
- 动态内容: 运行一个可执行文件,并将它的输出返回给客户端。
4.2. HTTP事务
HTTP事务由请求和响应组成。
- HTTP请求
- 请求行(request line):
method URI version
method
包括GET, POST, OPTIONS, HEAD, PUT, DELETE, TRACE
等URI
给出统一资源标识符,包括文件名和可选的参数version
给出HTTP的版本
- 零个和多个请求报头(request header):
header-name: header-data
- 例如,
Host: www.aol.com
- 例如,
- 空的文本行
- 请求行(request line):
- HTTP响应
- 响应行(response line):
version status-code status-message
- 例如,
HTTP/1.0 200 OK
- 例如,
- 零个和多个响应报头(response header):
Date
Content-type
Content-Length
- 终止报头的空文本行
- 响应主体(response body)
- 响应行(response line):
4.3. 服务动态内容
为了服务动态内容,需要解决以下问题
- 客户端如何传参给服务器
- 服务器如何传参给子进程
- 子进程将结果发给哪里
事实上,CGI标准(Common Gateway Interface,通用网关接口) 用于解决这些问题。
- 客户端传参
- GET请求: 通过URI传递
- POST请求: 通过请求主体传递
- 服务器传参给子进程
- 服务器会调用
fork
来创建一个子进程,并使用execve
执行cgi程序。在这之间,服务器可以通过环境变量传参。CGI定义了用于传参的环境变量,例如QUERY_STRING
SERVER_PORT
REQUEST_METHOD
- ..
- 服务器会调用
- 子进程将输出发送到哪里
- 服务器通过
dup2
在执行cgi程序之前,将输出重定向到和客户端相连的已连接描述符
- 服务器通过