02-网络编程简介
12.1 网络编程简介
“程序和诗差不多,你先得把它写出来。”
——E. W. Dijkstra
使用机器构建可以通过互联网相互通信的媒介是一项复杂的任务。这需要不同的设备通过互联网进行通信、运行不同的操作系统、不同版本的应用程序,并且它们需要一组约定的规则来相互交换信息。这些通信规则被称为网络协议,设备彼此之间发送的消息被称为网络数据包。
为了分离各方面的关注度,例如可靠性、可发现性及封装性,这些协议被分成若干层,其中较高层协议堆叠在较低层协议之上。每个网络数据包由来自这些层的信息组成。当前的操作系统已经附带了网络协议堆栈的实现。在此实现中,每层都为其上方的层提供支持。
在最底层,我们有物理层和数据链路层协议,用于指定数据包如何在互联网节点之间通过电缆进行传输,以及它们如何进出计算机网卡。在此之上,我们有IP层,它采用称为IP地址的唯一ID来识别互联网上的节点。在IP层之上,我们有传输层,它采用一种为互联网上的两个进程之间提供点对点传输的协议。此层存在传输控制协议(Transmission Control Protocol,TCP)和用户数据报协议(User Datagram Protocol,UDP)等协议。在传输层之上,我们有应用层,它采用例如HTTP和文件传输协议(File Transfer Protocol,FTP)的协议,这两者用于构建大量的应用程序。它允许更高级别的通信,例如在移动设备上运行的聊天应用程序。整个协议栈协同工作,以促进在计算机上运行的应用程序之间的复杂交互,从而在互联网上传播。
随着越来越多的设备通过互联网连接和共享信息,分布式应用程序架构开始激增,并诞生了两种模型:分散模型,通常也称对等模型;以及集中模型,它也被称为客户端-服务端模型。其中后者更常见。本章的重点将放在构建网络应用程序的客户端-服务端模型上,特别是传输层。
在主流操作系统中,网络堆栈的传输层为开发者提供了一系列API,其名为套接字(socket)。它包括一组接口,用于在两个进程之间建立通信连接。套接字允许你在本地或远程两个进程之间传递数据,而无须开发者了解底层的网络协议。
Socket API源于伯克利软件发行版(Berkley Software Distribution,BSD)的Linux,这是第一个在1983年提供带有Socket API网络堆栈实现的操作系统。它作为当前主流操作系统中网络堆栈的参考实现。在类UNIX操作系统中,套接字遵循相同的理念,即一切都是文件,并公开文件描述符API。这意味着可以像文件一样从套接字中读取和写入数据。
注意
套接字是文件描述符(整数),它指向内核管理的进程描述符表。描述符表包含文件描述符到文件条目结构的映射,该文件条目结构包含发送到套接字的数据的实际缓冲区。
Socket API主要用于TCP/IP层。在这一层,我们创建的套接字按不同级别进行分类。
- 协议 :根据协议,我们可以有TCP套接字或UDP套接字。TCP是一种包含状态的流协议,提供能够以可靠的方式传递消息的能力,而UDP是一种无状态且不可靠的协议。
- 通信类型 :根据我们是与本地计算机还是远程计算机上的进程通信,可以使用互联网套接字或者UNIX域套接字。互联网套接字用于在远程计算机上的进程之间交换信息。它由 IP 地址和端口构成的元组表示。想要远程通信的两个进程必须使用IP套接字。UNIX域套接字用于本地计算机上运行的进程之间的通信。这里,它采用文件系统路径,而不是一对IP地址端口。例如,数据库会采用UNIX域套接字来公开连接端点。
- I/O模型 :根据如何读取和写入套接字数据,我们可以创建两种套接字:阻塞套接字和非阻塞套接字。
现在我们已经了解了不少与套接字有关的信息,接下来让我们探讨一下客户端—服务端模型。在这种网络模型中,设置两台计算机互相通信一般会遵循如下流程:服务器创建套接字并在指定协议(可以是TCP或UDP)之前将其绑定到一对IP地址端口上;然后开始侦听来自客户端的连接。另外,客户端创建一个连接套接字并连接到给定的IP地址和端口。在UNIX中,进程可以使用套接字系统创建套接字。此调用返回一个文件描述符,程序可以使用该文件描述符对客户端或服务器进行读写调用。
Rust在标准库中为我们提供了net模块。这包含传输层的上述网络单元。对于TCP通信,我们有TcpStream和TcpListener类型。对于UDP通信,我们有UdpSocket类型。net模块还提供了用于正确表示v4和v6版本IP地址的数据类型。
构建可靠的网络应用程序需要考虑若干因素。如果你允许在消息交换时丢失少量数据包,则可以使用 UDP 套接字,但如果无法忍受丢包或希望按顺序传递消息,则必须使用TCP套接字。UDP传输速度很快但出现得较晚,能够满足需要最小延迟传输数据包,但允许丢失少量数据包的情况。例如视频聊天应用程序使用UDP,聊天过程中某些数据帧从视频流中丢失,其影响也不大。UDP适用于能够容忍无交付保证的情况。本章将重点讨论TCP套接字,因为它是大多数需要可靠交付的网络应用程序最常采用的协议。
另一个需要考虑的因素是,应用程序为客户提供的服务的质量和效率。从技术角度来看,这又转变为套接字I/O模型的选择问题。
注意
I/O是Input/Output的首字母缩写,是一个表意宽泛的短语,在这种情况下,它仅表示在套接字上读取和写入字节。
在阻塞和非阻塞套接字之间进行选择会改变其体系结构、编写代码的方式,以及如何扩展到客户端。阻塞套接字为用户提供的是同步I/O模型,而非阻塞套接字为用户提供的是异步I/O模型。在实现Socket API的平台上,例如UNIX,默认情况下会在阻塞模式下创建套接字。这要求主流的网络堆栈上默认的I/O模型是同步模型。接下来,让我们探讨一下这两种模型。