TCP IP详解卷2:实现
TCP-IP详解卷2:实现,本章介绍伯克利 ( B e r k e l e y ) 联 网 程 序 代 码 。 开 始 我 们 先 看 一 段 源 代 码 并 介 绍 一 些 通 篇 要 用 的印刷约定。对各种不同代码版本的简单历史回顾让我们可以看到本书中的源代码处于什么 位置。接下来介绍了两种主要的编程接口,它们在 U n i x 与非 U n i x 系统中用于编写 T C P / I P 协议。住乱,n4会.§M山(亮7前叫∈F,"4→WⅥ临7山E,FS。愿M呀14→依S。3()F3ρ倍③A填⑩7抱G山ay击A抵-扳兆围a抽A堡L吊哪aC否FT手扼§堡吊哪T7军圾且_F堡)T′几W否FS。RsJ§手扼15N9RE8ⅸn会B喻备B正0g7F受cY7S§Y埠恤慆j冈¨±会BOFN叹ar7I啤世伦).Oγ⑩UNE8§传会,册升带m7*§a(ρ准堡L吊哪ar估O手扼7FⅥ临7§iF‖呀冻7凌∠独乱n4○填⊙§vⅥF(3)n十复by屋η7§依○£协境§:i协尘呀抱受Ui.两利)劈3FF十3F填c73倍u§依F④6坚117M临抱受UFa7尤准§剥73FGf☆§b「p∂且且受Uˇ截7伦塔§下G且e8b「复iy@y7抱 AOe bl「唱割④受Uˇ截67ⅷ切7亥N③Ofz「S2圾2厅σlmWb「%抱f2Ⅰ几小jf4)XXiA[j^ec[Yec)hw小c’Z+,-(jcb剥O受c叶地F≮惟b「冻3)(2)b7M临§3冻ξ坛叫≮十抖7啡§(2M临Sε叫后0(9⑤FnU3分§W倾即7ε’y山n7抱Ⅵ临受C呀冻▲叫nb姆《④凡£叫nb6Ⅻ剂5受Ⅵ临劈乱⑨`α7↓圾&且复0伦SN影今Jy则驶乱廊&中ba"εe伤S廾则A2十N7()吻亮墨U伦SN影3倍殆’④(3)nⅥ临§依们W④7乱XauI倍B兑b749§下G击世丰争且e受UN影8§④07乱XE卢yBU块抱4依受UN影Ⅵ3n割0假下∑困割5ES7bl受UN∑r、713山墨 UNI HY N则额P割半困y0巴府抱0F,≮之ⅷ复则A§b「fu函则山B受UN影§y叫7危5M1单墨▲,倍A∂87山b(3)2(2)7Ⅵ临抱则Ai僧◎XXi^[j^ec[eC+区A出今Y叫供⑤M临33(2)7β…+士Y§冻M呀S,(A山E供L免伦SN影5…+Ⅻ且0N影§√▲会SuE供E复YU7I§Ⅱr十un§3y后c巴府W2+NU亥Uz函则§μ§y叫7危/15M/函则7否F伦SN影§2复否y刀④n9女V题2#NUz函则FG+≯E7抱bbs theithome com第1章概述1.1引言本章介绍伯克利( Berkeley)联网程序代码。开始我们先看一段源代码并介绍一些通篇要用的印刷约定。对各种不同代码版本的简单历史回顾让我们可以看到本书中的源代码处于什么位置。接下来介绍了两种主要的编程接口,它们在Uniⅸx与非Unix系统中用于编写TCP/P协议。然后我们介绍一个简单的用户程序,它发送一个UDP数据报给一个位于另一主机上的日期/时间服务器,服务器返回一个UDP数据报,其中包含服务器上日期和时间的ASCI码字符串。这个进程发送的数据报经过所有的协议栈到达设备驱动器,来自服务器的应答从下向上经过所有协议栈到达这个进程。通过这个例子的这些细节介绍了很多核心数据结构和概念这些数据结构和概念在后面的章节中还要详细说明。本章的最后介绍了在本书中各源代码的组织,并显示了联网代码在整个组织中的位置1.2源代码表示不考虑主题,列举15000行源代码本身就是一件难事。下面是所有源代码都使用的文本格式tcp subr.c381 void382 tcp quench(inp, errno)383 struct inpcb *inp;384 inte卫385386struct tcpcb *tp intotcpch(inp)i387if (tp)388tp->snd_cwnd tp->t_maxseg:389}tcp subr.c.行國H置小387-388这是文件 tcp subr.c中的函数 tcp quench。这些源文件名引用44BSD-Lite发布的文件。4.4BSD在1.13节中讨论。每个非空白行都有编号。正文所描述的代码的起始和结束位置的行号记于行开始处,如本段所示。有时在段前有一个简短的描述性题头,对所描述的代码提供一个概述。这些源代码同4.4BSD-Lite发行版一样,偶尔也包含一些错误,在遇到时我们会提出来并加以讨论,偶尔还包括一些原作者的编者评论。这些代码已通过了GNU缩进程序的运行,使它们从版面上看起来具有一致性。制表符的位置被设置成4个栏的界线使得这些行在一个页面中显示得很合适。在定义常量时,有些# ifdef语句和它们的对应语句#endi被删去(如:GAE吶AY和 ROUTING,因为我们假设系统被作为一个路由器或多播路由器)。所有 register说bbs theithome com2TCP/P详解卷2:实现明符被删去。有些地方加了一些注释,并且一些注释中的印刷错误被修改了,但代码的其他部分被保留下来。这些函数大小不一,从几行(如前面的 tcp quench)到最大1100行( tcp input)。超过大约40行的函数一般被分成段,一段一段地显示。虽然尽量使代码和相应的描述文字放在同页或对开的两页上,但为了节约版面,不可能完全做到。本书中有很多对其他函数的交叉引用。为了避免给每个引用都添加一个图号和页码,书封底内页中有一个本书中描述的所有函数和宏的字母交叉引用表和描述的起始页码。因为本书的源代码来自公开的4.4 BSD Lite版,因此很容易获得它的—个拷贝:附录B详细说明了各种方法。当你阅读文章时,有时它会帮助你搜索一个在线拷贝[例如Unⅸx程序grep(1)]。描述一个源代码模块的各章通常以所讨论的源文件的列表开始,接着是全局变量、代码维护的相关统计以及一个实际系统的一些例子统计,最后是与所描述协议相关的SNMP变量。全局变量的定义通常跨越各种源文件和头文件,因此我们将它们集中到的一个表中以便于参考。这样显示所有的统计,简化了后面当统计更新时对代码的讨论。卷1的第25章提供了SNMP的所有细节。我们在本文中关心的是由内核中的TCP/IP例程维护的、支持在系统上运行的SNMP代理的信息1.2.2印刷约定通篇的图中,我们使用一个等宽字体表示变量名和结构成员名( m next),用斜体等宽字体表示定义常量(NULL)或常量的值(512)的名称,用带花括号的粗体等宽字体表示结构名称(mbuf{})。这里有一个例子mu【nextNULLm len512在表中,我们使用等宽字体表示变量名称和结构成员名称,用斜体等宽字体表示定义的常量。这里有一个例子:m flag说明M BCAST以链路层广播发送/接收通常用这种方式显示所有的# define符号。如果必要,我们显示符号的值( M BCAST的值无关紧要)并且所列符号按字母排序,除非对顺序有特殊要求。通篇我们会使用像这样的缩进的附加说明来描述历史的观点或实现的细节。我们用有一个数字在圆括号里的命令名称来表示Unix命令,如grep(1)。圆括号中的数字是44BSD手册“ manual page”中此命令的节号,在那里可以找到其他的信息。1.3历史本书讨论在伯克利的加利福尼亚大学计算机系统研究组的TCPP实现的常用引用。历史上,它曾以4ⅹBSD系统(伯克利软件发行)和“BSD联网版本”发行。这个源代码是很多其他实现的起点,不论是Uniⅸ或非Unⅸx操作系统图1-1显示了各种BSD版本的年表,包括重要的TCP/P特征。显示在左边的版本是公开可bbs theithome com第l章概述3用源代码版,它包括所有联网代码:协议本身、联网接口的内核例程及很多应用和实用程序(如 Telnet和FTP)4.2BSD(1983)第一次被广泛应用的TCPP版本4.3BSD(1996改进了TCP性能4.3BSD Tahoe(1988慢启动,防拥塞,快速重传BSD联网软件版本1.0(1989):Net/14.3BSD Reno(1990)快速恢复,TCP首部预测,SLIP首部压缩,路由表改变BSD联网软件版本2.0(1991):Net24.4BSD(1993)多播,长肥管道的修改4.4BSD-Lite(1994)在正文中用Net3表示图1-1带有重要TCPP特征的各种BSD版本虽然本文描述的软件的官方名称为44BSD-Lite发行软件,但我们简单地称它为Net3.虽然源代码由U.C. Berkeley发行并被称为伯克利软件发行,但TCP/P代码确实是各种研究者的工作的融合,包括伯克利和其他地区的研究人员。通篇我们会使用术语源于伯克利的实现来谈及各厂商的实现,如 SunOs4x、系统Ⅴ版本(SVR4)和AX3.2,它们的TCP/P代码最初都是从伯克利源代码发展而来的。这些实现有很多共同之处,通常包括同样的错误在图1-1中没有显示的伯克利联网代码的第1版实际上是1982年的4.1cBSD,但是广泛发布的是1983年的版本42BSD。在4. ICBSD之前的BSD版本使用的一个TCP/IP实现,是由 Bolt beranek andNewman(BBN)的 Rob gurwitz和 Jack Haverty开发的。[ Salus1994]的第18章提供了另外一些合并到42BSD中的BBN代码细节。其他对伯克利TCPP代码有影响的实现是由 Ballistics研究室的 Mike muuss为PDP-11开发的 TCP/IP实现描述联网代码从一个版本到下一个版本的变化的文档有限。[ Karels andMcKusick1986]描述了从4.2BSD到43BSD的变化,并且[ Jacobson1990d]描述了从4.3 BSD Tahoe到4.3 BSD Reno的变化。1.4应用编程接口在互联网协议中两种常用的应用编程接口(API)是插口( socket)和TLI(运输层接口)。前者bbs theithome comTCPP详解卷2:实现有时称为伯克利插口( Berkeley socket),因为它被广泛地发布于4.2BSD系统中(见图1-1)。但它已被移植到很多非 BSD Uniⅸx系统和很多非Uniⅸx系统中。后者最初是由AT&T开发的,由于被X/Open承认,有时叫作XTI( X/Open传输接口)。 X/Open是一个计算机厂商的国际组织,它制定自己的标准。XTI是TL的一个有效超集。虽然本文不是一本程序设计书,但既然在Net/3(和所有BSD版本)中应用编程用插口来访问 TCP/IP,我们还是说明一下插口。在各种非Uniⅸx系统中也实现了插口。插口和TL的编程细节在[ Stevens1990中可以找到。系统Ⅴ版本4(SVR4)也为应用编程提供了一组插口API,在实现上与本文中列举的有所不同。在SVR4中的插口基于“流”子系统,在[Rago193]中有所说明。1.5程序示例在本章我们用一个简单的C程序(图1-2)来介绍一些BSD网络实现的很多特点1234Send a uDP datagram to the daytime server on some other hostread the reply, and print the time and date on the server5#include6 #include7 includeknetinet/in. h>8 # include 9井inc1ude10 include11 #include 12 *define BUFFSIZE150/ arbitrary size *13 int14 main()16struct sockaddr in serycharbuEf【 BUFFS工zE];18intsockfd, n19if ((sockfd socket(PF_INET, SOCK DGRAM, 0))<0err_sys("socket error")zero((char *)&serv, sizeof(serv)2ervsin_family AF_INET;23serv. sin addr. s addr =inet addr ("140. 252. 1. 32")i24serv. sin_ port = htons(13)25if (sendto( sockfd buff, BUFFSIZE, 026struct sockaddr *)&serv, sizeof(serv))!= BUFFSIZE)27err_sys("sendto error)28f ((n recvfrom( sockfd, buff, BUFFSIZE,029struct sockaddr *)NULL, (int *)NULL))< 2)30err_sys("recvfrom error")31buf[n-2]=0/* null terminate *32 print£("暑sn",buff);33exit(0)34图1-2程序示例:发送一个数据报给UDP日期小间服务器并读取一个应答bbs theithome com第l章概述51.创建一个数据报插口19-20 socket函数创建了一个UDP插口,并且给进程返回一个保存在变量 sockfd中的描述符。差错处理函数 err sys在[ Stevens1992]的附录B.2中给出。它接收任意数量的参数,并用 vsprintf对它们格式化,将系统调用产生的 errno值对应的Unix错误信息打印出来,并中断进程。我们在不同的地方使用术语插口:(1)为42BSD开发的程序用来访问网络协议的AP通常叫插口API或者就叫插口接口;(2) socket是插口API中的一个函数的名字(3)我们把调用 sockett创建的端点叫做一个插口,如评注“创建一个数据报插口”。但是这里还有一些地方也使用术语插口:(4) socket函数的返回值叫一个插口描述符或者就叫一个插口;(5)在内核中的伯克利联网协议实现叫插口实现,相比较其他系统如:系统Ⅴ的流实现。(6)—个P地址和一个端口号的组合叫一个插口,IP地址和端口号对叫一个插口对。所幸的是引用哪一种术语是很明显的2.将服务器地址放到结构 sockaddr in中21-24在一个互联网插口地址结构中存放日期时间服务器的IP地址(140.252.1.32)和端口号(13)。大多数TCP/IP实现都提供标准的日期时间服务器,它的端口号为13[ Stevens1994,图1-9]。我们对服务器主机的选择是随意的——直接选择了提供此服务的本地主机(图1-17)。函数 inet addr将一个点分十进制表示的IP地址的ASCI字符串转换成网络字节序的32bit二进制整数。( Internet协议族的网络字节序是高字节在后)。函数 tons把一个主机字节序的短整数可能是低字节在后)转换成网络字节序(高字节在后)。在 Sparc这种系统中,整数是高字节在后的格式, h tons典型地是一个什么也不做的宏。但是在低字节在后的80386上的BSD/386系统中, tons可能是一个宏或者是一个函数,来完成一个16bit整数中的两个字节的交换。3.发送数据报给服务器25-27程序调用 sendto发送—个150字节的数据报给服务器。因为是运行时栈中分配的未初始化数组,150字节的缓存內容是不确定的。但没有关系,因为服务器根本就不看它收到的报文的内容。当服务器收到一个报文时,就发送一个应答给客户端。应答中包含服务器以可读格式表示的当前时间和日期。我们选择的150字节的客户数据报是随意的。我们有意选择一个报文长度在100~208之间的值,来说明在本章的后面要提到的mbuf链表的使用。为了避免拥塞,在以太网中,我们希望长度要小于14724.读取从服务器返回的数据报28-32程序通过调用 recvfrom来读取从服务器发回的数据报。Unix服务器典型地发回个如下格式的26字节字符串Sat dec1111:28:051993\r\nr是一个ASCI回车符,、n是ASCI换行符。我们的程序将回车符替换成一个空字节,然后调用 printf输出结果。在本章和下一章我们在分析函数 socket、 sendto和 recvfrom的实现时,要进入这个例子的一些细节部分bbs theithome comTCP∥P详解卷2:实现1.6系统调用和库函数所有的操作系统都提供服务访问点,程序可以通过它们请求内核中的服务。各种Unix都提供精心定义的有限个内核入口点,即系统调用。我们不能改变系统调用,除非我们有内核的源代码。Uniⅸx第7版提供大约50个系统调用,4.4BSD提供大约135个,而SVR4大约有120在《Unⅸx程序员手册》第2节中有系统调用接口的文档。它是以C语言定叉的,在任何给定的系统中无需考虑系统调用是如何被调用的。在各种Unx系统中,每个系统调用在标准C函数库中都有一个相同名字的函数。一个应用程序用标准C的调用序列来调用此函数。这个函数再调用相应的内核服务,所使用的技术依赖于所在系统。例如,函数可能把一个或多个C参数放到通用寄存器中,并执行几条机器指令产生一个软件中断进入内核。对于我们来说,可以把系统调用看成C函数。在《Unⅸx程序员手册》的第3节中为程序员定义了一般用途的函数。虽然这些函数可能调用一个或多个内核系统调用但没有进入内核的入口点。如函数pint可能调用了系统调用 write去执行输出,而函数 strcpy(复制一个串)和atoi将ASCI码转换成整数)完全不涉及操作系统。从实现者的角度来看,一个系统调用和库函数有着根本的区别。但在用户看来区别并不严重。例如,在4.4BSD中我们运行图1-2中的程序。程序调用了三个函数: socket、sendto和 recvfrom,每个函数最终调用了一个内核中同样名称的函数。在本书的后面我们可以看到这三个系统调用的BSD内核实现。如果我们在SVR4中运行这个程序,在那里,用户库中的插口函数调用“流”子系统,那么三个函数同内核的相互作用是完全不同的。在SVR4中对 socket的调用最终调用内核open系统调用,操作文件/deⅴ/udp并将流模块socκmoα放置到结果流。调用seηαto导致一个putmsg系统调用,而调用 recvfrom导致一个 getmsg系统调用。这些SR4的细节在本书中并不重要,我们仅仅想指出的是:实现可能不同但都提供相同的AP给应用程序。最后,从一个版本到下一个版本的实现技术可能会改变。例如,在Net/1中,send和 sendto是分别用内核系统调用实现的。但在Net3中,send是一个调用系统调用 sendto的库函数:send (int s, char *msg, int len, int flagsreturn(sendto(s, msg, len, flags,(struct sockaddr * NULL, 0))用库函数实现send的好处是仅调用 sendto,减少了系统调用的个数和内核代码的长度。缺点是由于多调用了一个函数,增加了进程调用send的开销。因为本书是说明TCP/IP的伯克利实现的,大多数进程调用的函数( socket、bindconnect等)是直接由内核系统调用来实现。1.7网络实现概述Net3通过同时对多种通信协议的支持来提供通用的底层基础服务。的确,4.4BSD支持四种不同的通信协议族l)TCPP(互联网协议族),本书的主题。2)XNS( Xerox网络系统),一个与TCP/P相似的协议族;在80年代中期它被广泛应用于连bbs theithome com第l章概述7接 Xerox设备(如打印机和文件服务器),通常使用的是以太网。虽然Net3仍然发布它的代码但今天已很少使用这个协议了,并且很多使用伯克利 TCP/IP代码的厂商把XNS代码删去了(这样他们就不需要支持它了)3)OSI协议[Rose190; Piscitello and Chapin1993]。这些协议是在80年代作为开放系统技术的最终目标而设计的,来代替所有其他通信协议。在90年代初它没有什么吸引力,以致于在真正的网络中很少被使用。它的历史地位有待进一步确定。4)Unix域协议。从通信协议是用来在不同的系统之间交换信息的意义上来说,它还不算是一套真正的协议,但它提供了一种进程间通信(IPC)的形式。相对于其他IPC,例如系统Ⅴ消息队列,在同一主机上两个进程间的IPC使用Unix域协议的好处是Unⅸ域协议用与其他三种协议同样的API(插口)访问。另一方面,消息队列和大多数其他形式IPC的AP与插口和TL完全不同。在同一主机上的两进程间的IPC使用网络API,更容易将一个客户服务器应用程序从一台主机移植到多台主机上。在Unix域中提供两个不同的协议——个是可靠的,面向连接的,与TCP相似的字节流协议;一个是不可靠的,无连接的,与UDP相似的数据报协议虽然Unⅸ域协议可以作为一种同一主机上两进程间的IPC,但也可以用TCP/P来完成它们之间的通信。进程间通信并不要求使用在不同的主机上的互联网协议。内核中的联网代码组织成三层,如图7.应用1-3所示。在图的右侧我们注明了OS参考进程6.表示模型[ Piscitello和 Chapin1994的七层分别5.会话对应到BSD组织的哪里系统调用( socket,bind, connect,etcl)插口层是一个到下面协议相关层的协议无关接口。所有系统调用从协议无关插口层的插口层开始。例如:在插口层中的bind协议层4.运输系统调用的协议无关代码包含几十行代码,(TCP/IP, XNS, OSI, Unix3.网络它们验证的第一个参数是一个有效的插口接口层(以太网、SLIP、环回等等)2.数据链路描述符,并且第二个参数是一个进程中的有效指针。然后调用下层的协议相关代码协议相关代码可能包含几百行代码媒体1.物理2)协议层包括我们前面提到的四种协图1-3Net3联网代码的大概组织议族(TCP/P,XNS,OSI和Unⅸx域)的实现。每个协议族可能包含自己的内部结构,在图1-3中我们没有显示出来。例如,在 Internet协议族中,IP(网络层)是最低层,TCP和UDP两运输层在IP的上面。3)接口层包括同网络设备通信的设备驱动程序。1.8描述符图1-2中,一开始调用 socket,这要求定义插口类型。 Internet协议族( PF INET)和数据报插口( SOCK DGRAM)组合成一个UDP协议插口。socket的返回值是—个描述符,它具有其他Unix描述符的所有特性:可以用这个描述符调用read和 write;可以用aup复制它,在调用了fork后,父进程和子进程可以共享bbs theithome com
暂无评论