logo资料库

TCP-IP详解卷二:实现(超清晰版).pdf

第1页 / 共872页
第2页 / 共872页
第3页 / 共872页
第4页 / 共872页
第5页 / 共872页
第6页 / 共872页
第7页 / 共872页
第8页 / 共872页
资料共872页,剩余部分请下载后查看
下载 1.1 引言 第1章 概 述 本章介绍伯克利 ( B e r k e l e y )联网程序代码。开始我们先看一段源代码并介绍一些通篇要用 的印刷约定。对各种不同代码版本的简单历史回顾让我们可以看到本书中的源代码处于什么 位置。接下来介绍了两种主要的编程接口,它们在 U n i x与非U n i x系统中用于编写T C P / I P协议。 然后我们介绍一个简单的用户程序,它发送一个 U D P数据报给一个位于另一主机上的日 期/时间服务器,服务器返回一个 U D P数据报,其中包含服务器上日期和时间的 A S C I I码字符 串。这个进程发送的数据报经过所有的协议栈到达设备驱动器,来自服务器的应答从下向上 经过所有协议栈到达这个进程。通过这个例子的这些细节介绍了很多核心数据结构和概念, 这些数据结构和概念在后面的章节中还要详细说明。 本章的最后介绍了在本书中各源代码的组织,并显示了联网代码在整个组织中的位置。 1.2 源代码表示 不考虑主题,列举 15 000 行源代码本身就是一件难事。下面是所有源代码都使用的文本 格式: 1.2.1 将拥塞窗口设置为 1 3 8 7 - 3 8 8 这是文件t c p _ s u b r . c中的函数t c p _ q u e n c h。这些源文件名引用 4 . 4 B S D - L i t e发 布的文件。 4 . 4 B S D在1 . 1 3节中讨论。每个非空白行都有编号。正文所描述的代码的起始和结 束位置的行号记于行开始处,如本段所示。有时在段前有一个简短的描述性题头,对所描述 的代码提供一个概述。 这些源代码同 4 . 4 B S D - L i t e发行版一样,偶尔也包含一些错误,在遇到时我们会提出来并 加以讨论,偶尔还包括一些原作者的编者评论。这些代码已通过了 G N U缩进程序的运行,使 它们从版面上看起来具有一致性。制表符的位置被设置成 4个栏的界线使得这些行在一个页面 中显示得很合适。在定义常量时,有些 # i f d e f语句和它们的对应语句 # e n d i f被删去 (如: G A T E W A Y和M R O U T I N G,因为我们假设系统被作为一个路由器或多播路由器 )。所有r e g i s t e r说
2计计TCP/IP详解 卷 2:实现 下载 明符被删去。有些地方加了一些注释,并且一些注释中的印刷错误被修改了,但代码的其他 部分被保留下来。 这些函数大小不一,从几行 (如前面的 t c p _ q u e n c h)到最大11 0 0行(t c p _ i n p u t)。超过 大约4 0行的函数一般被分成段,一段一段地显示。虽然尽量使代码和相应的描述文字放在同 一页或对开的两页上,但为了节约版面,不可能完全做到。 本书中有很多对其他函数的交叉引用。为了避免给每个引用都添加一个图号和页码,书 封底内页中有一个本书中描述的所有函数和宏的字母交叉引用表和描述的起始页码。因为本 书的源代码来自公开的 4 . 4 B S D _ L i t e版,因此很容易获得它的一个拷贝:附录 B详细说明了各 种方法。当你阅读文章时,有时它会帮助你搜索一个在线拷贝 [例如U n i x程序grep ( 1 )]。 描述一个源代码模块的各章通常以所讨论的源文件的列表开始,接着是全局变量、代码 维护的相关统计以及一个实际系统的一些例子统计,最后是与所描述协议相关的 S N M P变量。 全局变量的定义通常跨越各种源文件和头文件,因此我们将它们集中到的一个表中以便于参 考。这样显示所有的统计,简化了后面当统计更新时对代码的讨论。卷 1的第 2 5章提供了 S N M P的所有细节。我们在本文中关心的是由内核中的 T C P / I P例程维护的、支持在系统上运 行的S N M P代理的信息。 1.2.2 印刷约定 通篇的图中,我们使用一个等宽字体表示变量名和结构成员名 (m _ n e x t),用斜体等宽字 体表示定义常量 ( N U L L)或常量的值 (5 1 2)的名称,用带花括号的粗体等宽字体表示结构名称 (m b u f { })。这里有一个例子: 在表中,我们使用等宽字体表示变量名称和结构成员名称,用斜体等宽字体表示定义的 常量。这里有一个例子: m _ f l a g s 说 明 M _ B C A S T 以链路层广播发送 /接收 通常用这种方式显示所有的 # d e f i n e符号。如果必要,我们显示符号的值 (M _ B C A S T的 值无关紧要 )并且所列符号按字母排序,除非对顺序有特殊要求。 通篇我们会使用像这样的缩进的附加说明来描述历史的观点或实现的细节。 我们用有一个数字在圆括号里的命令名称来表示 U n i x命令,如 g r e p( 1 )。圆括号中的数字 是4 . 4 B S D手册“manual page”中此命令的节号,在那里可以找到其他的信息。 1.3 历史 本书讨论在伯克利的加利福尼亚大学计算机系统研究组的 T C P / I P实现的常用引用。历史 上,它曾以 4.x BSD系统(伯克利软件发行 )和“B S D联网版本”发行。这个源代码是很多其他 实现的起点,不论是 U n i x或非U n i x操作系统。 图1 - 1显示了各种 B S D版本的年表,包括重要的 T C P / I P特征。显示在左边的版本是公开可
下载 第1章 概 述计计3 用源代码版,它包括所有联网代码:协议本身、联网接口的内核例程及很多应用和实用程序 (如Te l n e t和F T P )。 4.2BSD (1983) 第一次被广泛应用的 TCP/IP版本 4.3BSD (1996) 改进了TCP性能 4.3BSD Tahoe (1988) 慢启动,防拥塞,快速 重传 4.3BSD Reno (1990) 快速恢复, T C P首部预 测,S L I P首部压缩,路 由表改变 4.4BSD(1993) 多播,长肥管道的修改 BSD联网软件 版本1.0(1989):Net/1 BSD联网软件 版本2.0(1991):Net/2 4.4BSD-Lite(1994) 在正文中用Net/3表示 图1-1 带有重要T C P / I P特征的各种 B S D版本 虽然本文描述的软件的官方名称为 4 . 4 B S D - L i t e发行软件,但我们简单地称它为 N e t / 3。 虽然源代码由 U. C. Berkeley发行并被称为伯克利软件发行,但 T C P / I P代码确实是各种研 究者的工作的融合,包括伯克利和其他地区的研究人员。 通篇我们会使用术语源于伯克利的实现来谈及各厂商的实现,如 SunOS 4.x、系统V版本 4 ( S V R 4 )和AIX 3.2,它们的 T C P / I P代码最初都是从伯克利源代码发展而来的。这些实现有很 多共同之处,通常包括同样的错误! 在图1 - 1中没有显示的伯克利联网代码的第 1版实际上是1 9 8 2年的4 . 1 c B S D,但是 广泛发布的是1 9 8 3年的版本4 . 2 B S D。 在4 . 1 c B S D之前的 B S D版本使用的一个 T C P / I P实现,是由 Bolt Beranek and N e w m a n ( B B N )的Rob Gurwitz和Jack Haverty开发的。[Salus 1994]的第1 8章提供了另 外一些合并到 4 . 2 B S D中的B B N代码细节。其他对伯克利 T C P / I P代码有影响的实现是 由B a l l i s t i c s研究室的Mike Muuss为P D P - 11开发的T C P / I P实现。 描述联网代码从一个版本到下一个版本的变化的文档有限。 [Karels and McKusick 1986]描述了从 4 . 2 B S D到4 . 3 B S D的变化,并且 [Jacobson 1990d]描述了从 4.3BSD Ta h o e到4.3BSD Reno的变化。 1.4 应用编程接口 在互联网协议中两种常用的应用编程接口 ( A P I )是插口 ( s o c k e t )和T L I (运输层接口 )。前者
4计计TCP/IP详解 卷 2:实现 下载 有时称为伯克利插口 (Berkeley socket),因为它被广泛地发布于 4 . 2 B S D系统中(见图1 - 1 )。但它 已被移植到很多非 BSD Unix系统和很多非 U n i x系统中。后者最初是由 AT & T开发的,由于被 X / O p e n承认,有时叫作 X T I ( X / O p e n传输接口)。X / O p e n是一个计算机厂商的国际组织,它制 定自己的标准。X T I是T L I的一个有效超集。 虽然本文不是一本程序设计书,但既然在 N e t / 3 (和所有 B S D版本)中应用编程用插口来访 问T C P / I P,我们还是说明一下插口。在各种非 U n i x系统中也实现了插口。插口和 T L I的编程细 节在[Stevens 1990]中可以找到。 系统Ⅴ版本 4 ( S V R 4 )也为应用编程提供了一组插口 A P I,在实现上与本文中列举的有所不 同。在S V R 4中的插口基于“流”子系统,在 [Rago 1993]中有所说明。 1.5 程序示例 在本章我们用一个简单的 C程序(图1- 2 )来介绍一些 B S D网络实现的很多特点。 图1-2 程序示例:发送一个数据报给 U D P日期/时间服务器并读取一个应答
下载 1. 创建一个数据报插口 第1章 概 述计计5 1 9 - 2 0 s o c k e t函数创建了一个UDP 插口,并且给进程返回一个保存在变量 s o c k f d中的描 述符。差错处理函数 e r r _ s y s在[Stevens 1992]的附录 B . 2中给出。它接收任意数量的参数, 并用v s p r i n t f对它们格式化,将系统调用产生的 e r r n o值对应的 U n i x错误信息打印出来, 并中断进程。 我们在不同的地方使用术语插口: ( 1 )为4 . 2 B S D开发的程序用来访问网络协议的 A P I通常叫插口 A P I或者就叫插口接口; (2) socket是插口 A P I中的一个函数的名字; ( 3 )我们把调用 s o c k e t创建的端点叫做一个插口,如评注“创建一个数据报插口”。 但是这里还有一些地方也使用术语插口: (4) socket函数的返回值叫一个插口描 述符或者就叫一个插口; ( 5 )在内核中的伯克利联网协议实现叫插口实现,相比较其 他系统如:系统V的流实现。( 6 )一个I P地址和一个端口号的组合叫一个插口, I P地址 和端口号对叫一个插口对。所幸的是引用哪一种术语是很明显的。 2. 将服务器地址放到结构 s o c k a d d r _ i n中 2 1 - 2 4 在一个互联网插口地址结构中存放日期 /时间服务器的 I P地址( 1 4 0 . 2 5 2 . 1 . 3 2 )和端口号 ( 1 3 )。大多数 T C P / I P实现都提供标准的日期 /时间服务器,它的端口号为 13 [Stevens 1994,图 1- 9 ]。我们对服务器主机的选择是随意的—直接选择了提供此服务的本地主机 (图1- 1 7 )。 函数i n e t _ a d d r将一个点分十进制表示的 I P地址的 A S C I I字符串转换成网络字节序的 3 2 b i t二进制整数。 ( I n t e r n e t协议族的网络字节序是高字节在后 )。函数h t o n s把一个主机字节序 的短整数(可能是低字节在后 )转换成网络字节序 (高字节在后 )。在S p a r c这种系统中,整数是高 字节在后的格式, h t o n s 典型地是一个什么也不做的宏。但是在低字节在后的 8 0 3 8 6上的 B S D / 3 8 6系统中, h t o n s可能是一个宏或者是一个函数,来完成一个 16 bit整数中的两个字节 的交换。 3. 发送数据报给服务器 2 5 - 2 7 程序调用 s e n d t o发送一个 1 5 0字节的数据报给服务器。因为是运行时栈中分配的未初 始化数组,1 5 0字节的缓存内容是不确定的。但没有关系,因为服务器根本就不看它收到的报 文的内容。当服务器收到一个报文时,就发送一个应答给客户端。应答中包含服务器以可读 格式表示的当前时间和日期。 我们选择的 1 5 0字节的客户数据报是随意的。我们有意选择一个报文长度在 1 0 0 ~ 2 0 8之间 的值,来说明在本章的后面要提到的 m b u f链表的使用。为了避免拥塞,在以太网中,我们希 望长度要小于1 4 7 2。 4. 读取从服务器返回的数据报 2 8 - 3 2 程序通过调用 r e c v f r o m来读取从服务器发回的数据报。 U n i x服务器典型地发回一 个如下格式的2 6字节字符串 Sat Dec 11 11:28:05 1993\r\n \ r是一个 A S C I I回车符, \ n是A S C I I换行符。我们的程序将回车符替换成一个空字节,然后 调用p r i n t f输出结果。 在本章和下一章我们在分析函数 s o c k e t、s e n d t o和r e c v f r o m的实现时,要进入这个 例子的一些细节部分。
6计计TCP/IP详解 卷 2:实现 1.6 系统调用和库函数 下载 所有的操作系统都提供服务访问点,程序可以通过它们请求内核中的服务。各种 U n i x都 提供精心定义的有限个内核入口点,即系统调用。我们不能改变系统调用,除非我们有内核 的源代码。 U n i x第7版提供大约 5 0个系统调用, 4 . 4 B S D提供大约 1 3 5个,而 S V R 4大约有 1 2 0 个。 在《U n i x程序员手册》第 2节中有系统调用接口的文档。它是以 C语言定义的,在任何给 定的系统中无需考虑系统调用是如何被调用的。 在各种U n i x系统中,每个系统调用在标准 C函数库中都有一个相同名字的函数。一个应用 程序用标准C的调用序列来调用此函数。这个函数再调用相应的内核服务,所使用的技术依赖 于所在系统。例如,函数可能把一个或多个 C参数放到通用寄存器中,并执行几条机器指令产 生一个软件中断进入内核。对于我们来说,可以把系统调用看成 C函数。 在《U n i x程序员手册》的第 3节中为程序员定义了一般用途的函数。虽然这些函数可能调 用一个或多个内核系统调用但没有进入内核的入口点。如函数 p r i n t f可能调用了系统调用 w r i t e 去执行输出,而函数 s t r c p y (复制一个串 )和a t o i (将A S C I I码转换成整数)完全不涉及操作系统。 从实现者的角度来看,一个系统调用和库函数有着根本的区别。但在用户看来区别并不 严重。例如,在 4 . 4 B S D 中我们运行图1 - 2 中的程序。程序调用了三个函数: s o c k e t 、 s e n d t o和r e c v f r o m,每个函数最终调用了一个内核中同样名称的函数。在本书的后面我们 可以看到这三个系统调用的 B S D内核实现。 如果我们在 S V R 4中运行这个程序,在那里,用户库中的插口函数调用“流”子系统,那 么三个函数同内核的相互作用是完全不同的。在 S V R 4中对s o c k e t的调用最终调用内核 o p e n 系统调用,操作文件 / d e v / u d p并将流模块 s o c k m o d放置到结果流。调用 s e n d t o导致一个 p u t m s g系统调用,而调用 r e c v f r o m导致一个 g e t m s g系统调用。这些 SVR 4的细节在本书 中并不重要,我们仅仅想指出的是:实现可能不同但都提供相同的 A P I给应用程序。 最后,从一个版本到下一个版本的实现技术可能会改变。例如,在 N e t / 1中,s e n d和s e n d t o 是分别用内核系统调用实现的。但在 N e t / 3中,s e n d是一个调用系统调用s e n d t o的库函数: send(int s, char *msg, int len, int flags) { return(sendto(s, msg, len, flags, (struct sockaddr *) NULL, 0)); } 用库函数实现 s e n d的好处是仅调用 s e n d t o,减少了系统调用的个数和内核代码的长度。缺 点是由于多调用了一个函数,增加了进程调用 s e n d的开销。 因为本书是说明 T C P / I P的伯克利实现的,大多数进程调用的函数 (s o c k e t、b i n d、 c o n n e c t等)是直接由内核系统调用来实现。 1.7 网络实现概述 N e t / 3通过同时对多种通信协议的支持来提供通用的底层基础服务。的确, 4 . 4 B S D支持四 种不同的通信协议族: 1) TCP/IP(互联网协议族),本书的主题。 2) XNS(Xerox网络系统 ),一个与 T C P / I P相似的协议族;在 8 0年代中期它被广泛应用于连
下载 第1章 概 述计计7 接X e r o x设备(如打印机和文件服务器 ),通常使用的是以太网。虽然 N e t / 3仍然发布它的代码, 但今天已很少使用这个协议了,并且很多使用伯克利 T C P / I P代码的厂商把 X N S代码删去了 (这 样他们就不需要支持它了 )。 3) OSI协议[Rose 1990;Piscitello and Chapin 1993]。这些协议是在 8 0年代作为开放系统 技术的最终目标而设计的,来代替所有其他通信协议。在 9 0年代初它没有什么吸引力,以致 于在真正的网络中很少被使用。它的历史地位有待进一步确定。 4) Unix域协议。从通信协议是用来在不同的系统之间交换信息的意义上来说,它还不算 是一套真正的协议,但它提供了一种进程间通信 ( I P C )的形式。 相对于其他 I P C,例如系统 V消息队列,在同一主机上两个进程间的 I P C使用U n i x域协议 的好处是 U n i x域协议用与其他三种协议同样的 A P I (插口)访问。另一方面,消息队列和大多数 其他形式 I P C的A P I与插口和T L I完全不同。在同一主机上的两进程间的 I P C使用网络A P I,更 容易将一个客户 /服务器应用程序从一台主机移植到多台主机上。在 U n i x域中提供两个不同的 协议—一个是可靠的,面向连接的,与 T C P相似的字节流协议;一个是不可靠的,无连接 的,与U D P相似的数据报协议。 虽然U n i x域协议可以作为一种同一主机上两进程间的 I P C,但也可以用 T C P / I P来 完成它们之间的通信。进程间通信并不要求使用在不同的主机上的互联网协议。 内核中的联网代码组织成三层,如图 1- 3所示。在图的右侧我们注明了 O S I参考 模型[ P i s c i t e l l o和Chapin 1994] 的七层分别 对应到B S D组织的哪里。 1) 插口层是一个到下面协议相关层的 协议无关接口。所有系统调用从协议无关 的插口层开始。例如:在插口层中的 b i n d 系统调用的协议无关代码包含几十行代码, 它们验证的第一个参数是一个有效的插口 描述符,并且第二个参数是一个进程中的 有效指针。然后调用下层的协议相关代码, 协议相关代码可能包含几百行代码。 进 程 7. 应用 6. 表示 5. 会话 系统调用 (socket, bind, connect, etc.) 插口层 协议层 (TCP/IP, XNS, OSI, Unix) 接口层 (以太网、SLIP、环回等等) 4. 运输 3. 网络 2. 数据链路 媒体 1. 物理 2) 协议层包括我们前面提到的四种协 议族( T C P / I P,X N S,O S I和U n i x域)的实现。 每个协议族可能包含自己的内部结构,在图 1 - 3中我们没有显示出来。例如,在 I n t e r n e t协议族 中,I P (网络层)是最低层, T C P和U D P两运输层在 I P的上面。 图1-3 Net/3联网代码的大概组织 3) 接口层包括同网络设备通信的设备驱动程序。 1.8 描述符 图1 - 2中,一开始调用 s o c k e t,这要求定义插口类型。 I n t e r n e t协议族(P F _ I N E T)和数据 报插口(S O C K _ D G R A M)组合成一个 U D P协议插口。 s o c k e t的返回值是一个描述符,它具有其他 U n i x描述符的所有特性:可以用这个描述 符调用 r e a d和w r i t e;可以用 d u p 复制它,在调用了 f o r k后,父进程和子进程可以共享
8计计TCP/IP详解 卷 2:实现 下载 它;可以调用 f c n t l来改变它的属性,可以调用 c o l s e来关闭它,等等。在我们的例子中可 以看到插口描述符是函数 s e n d t o 和r e c v f r o m的第一个参数。当程序终止时 (通过调用 e x i t ),所有打开的描述符,包括插口描述符都会被内核关闭。 我们现在介绍在进程调用 s o c k e t时被内核创建的数据结构。在后面的几章中会更详细地 描述这些数据结构。 首先从进程的进程表表项开始。在每个进程的生存期内都会有一个对应的进程表表项存 在。 一个描述符是进程的进程表表项中的一个数组的下标。这个数组项指向一个打开文件表 的结构,这个结构又指向一个描述此文件的 i - n o d e或v - n o d e结构。图1 - 4说明了这种关系。 图1-4 从一个描述符开始的内核数据结构的基本关系 在这个图中,我们还显示了一个涉及插口的描述符,它是本书的焦点。由于进程表表项 是由以下C语言定义的,我们把记号 p r o c { }放在进程表项的上面。并且在本书所有的图中都 用它来标注这个结构。 struct proc { ... } [Stevens 1992,3 . 1 0节]显示了当进程调用 d u p和f o r k时,描述符、文件表结构和 i - n o d e 或v - n o d e之间的关系是如何改变的。这三种数据结构的关系存在于所有版本的 U n i x中,但不 同的实现细节有所变化。在本书中我们感兴趣的是 s o c k e t结构和它所指向的 I n t e r n e t专用数 据结构。但是既然插口系统调用以一个描述符开始,我们就需要理解如何从一个描述符导出 一个s o c k e t结构。 如果程序如此执行 a.out 不重定向标准输入 (描述符 0 )、标准输出 (描述符 1 )和标准错误处理 (描述符 2 ),图1 - 5显示 了程序示例中的 N e t / 3数据结构的更多细节。在这个例子中,描述符 0、1和2连接到我们的终 端,并且当 s o c k e t被调用时未用描述符的最小编号是 3。 当进程执行了一个系统调用,如 s o c k e t,内核就访问进程表结构。在这个结构中的项 p _ f d 指向进程的 f i l e d e s c 结构。在这个结构中有两个我们现在关心的成员:一个是
分享到:
收藏