📄 00000006.htm
字号:
网络层 (Network) <BR> 数据链路层 (Data Link) <BR> 物理层 (Physical) <BR>物理层是硬件(串口,以太网等等)。应用层是和硬件层相隔最远的--他是用户和网络交 <BR>互的地方。 <BR>这个模型如此通用,如果你想,你可以把他作为修车指南。把他应用到 Unix,结果是: <BR> <BR> 应用层 (Application Layer) (telnet, ftp, 等等) <BR> 传输层 (Host-to-Host Transport Layer) (TCP, UDP) <BR> Internet 层 (Internet Layer) (IP 和路由) <BR> 网络访问层 (Network Access Layer) (网络层,数据链路层和物理层) <BR>现在,你可能看到这些层次如何协调来封装原始的数据了。 <BR>看看建立一个简单的数据包有多少工作?哎呀,你将不得不使用 "cat" 来完成他们!简 <BR>直是笑话。对于流式套接口你要作的是 send() <BR>发送数据。对于数据报式套接口你按照你选择的方式封装数据然后用 sendto()。内核将 <BR>为你建立传输层和 Internet <BR>层,硬件完成网络访问层。这就是现代科技。 <BR>现在结束我们的网络理论速成班。哦,忘记告诉你关于路由的事情了。但是我不准备谈 <BR>他。如果你真的想知道,那么参考 IP <BR>RFC。如果你从来不曾了解他,也没有关系,你还活着不是吗。 <BR>structs <BR>终于到达这里了,终于谈到编程了。在这章,我将谈到被套接口用到的各种数据类型。 <BR>因为他们中的一些太重要了。 <BR>首先是简单的一个:socket descriptor。他是下面的类型: <BR> int <BR>仅仅是一个常见的 int。 <BR>从现在起,事情变得不可思议了。请跟我一起忍受苦恼吧。注意这样的事实:有两种字 <BR>节排列顺序:重要的字节在前面(有时叫 <BR>"octet"),或者不重要的字节在前面。前一种叫“网络字节顺序 (Network Byte <BR>Order)”。有些机器在内部是按照这个顺序储存数据,而另外一些则不然。当我说某数 <BR>据必须按照 NBO 顺序,那么你要调用函数(例如 htons() <BR>)来将他从本机字节顺序 (Host Byte Order) 转换过来。如果我没有提到 NBO, 那么就 <BR>让他是本机字节顺序吧。 <BR>我的第一个结构(TM)--struct sockaddr. 这个数据结构为许多类型的套接口储存套接口 <BR>地址信息: <BR> struct sockaddr { <BR> unsigned short sa_family; /* address family, AF_xxx */ <BR> char sa_data[14]; /* 14 bytes of protocol address */ <BR> }; <BR>sa_family 能够是各种各样的事情,但是在这篇文章中是 "AF_INET"。 sa_data 为套接 <BR>口储存目标地址和端口信息。看上去很笨拙,不是吗。 <BR>为了对付 struct sockaddr,程序员创造了一个并列的结构: struct sockaddr_in (" <BR>in" 代表 "Internet".) <BR> struct sockaddr_in { <BR> short int sin_family; /* Address family */ <BR> unsigned short int sin_port; /* Port number */ <BR> struct in_addr sin_addr; /* Internet address */ <BR> unsigned char sin_zero[8]; /* Same size as struct sockaddr */ <BR> }; <BR>这个数据结构让可以轻松处理套接口地址的基本元素。注意 sin_zero (他被加入到这个 <BR>结构,并且长度和 struct sockaddr 一样) 应该使用函数 <BR>bzero() 或 memset() 来全部置零。 Also, and this is the important bit, a poin <BR>ter to a <BR>struct sockaddr_in can be cast to a pointer to a struct sockaddr and vice-ve <BR>rsa. <BR>这样的话即使 socket() 想要的是 struct sockaddr *,你仍然可以使用 struct sock <BR>addr_in,and cast it at <BR>the last minute! 同时,注意 sin_family 和 struct sockaddr 中的 sa_family 一致 <BR>并能够设置为 <BR>"AF_INET"。最后, sin_port 和 sin_addr 必须是网络字节顺序 (Network Byte Orde <BR>r)! <BR>你也许会反对道:"但是,怎么让整个数据结构 struct in_addr sin_addr 按照网络字 <BR>节顺序呢?" <BR>要知道这个问题的答案,我们就要仔细的看一看这个数据结构: struct in_addr, 有这 <BR>样一个联合 (unions): <BR> /* Internet address (a structure for historical reasons) */ <BR> struct in_addr { <BR> unsigned long s_addr; <BR> }; <BR>他曾经是个最坏的联合,但是现在那些日子过去了。如果你声明 "ina" 是数据结构 st <BR>ruct sockaddr_in 的实例,那么 <BR>"ina.sin_addr.s_addr" 就储存4字节的 IP 地址(网络字节顺序)。如果你不幸的系统使 <BR>用的还是恐怖的联合 struct in_addr <BR>,你还是可以放心4字节的 IP 地址是和上面我说的一样(这是因为 #define。) <BR>Convert the Natives! <BR>我们现在到达下个章节。我们曾经讲了很多网络到本机字节顺序,现在是采取行动的时 <BR>刻了! <BR>你能够转换两种类型: short (两个字节)和 long (四个字节)。这个函数对于变量类型 <BR> unsigned 也适用。假设你想将 short <BR>从本机字节顺序转换为网络字节顺序。用 "h" 表示 "本机 (host)",接着是 "to",然 <BR>后用 "n" 表示 "网络 (network)",最后用 "s" <BR>表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。 <BR>太简单了... <BR>如果不是太傻的话,你一定想到了组合 "n","h","s",和 "l"。但是这里没有 stolh <BR>() ("Short to Long Host") <BR>函数,但是这里有: <BR> htons()--"Host to Network Short" <BR> htonl()--"Host to Network Long" <BR> ntohs()--"Network to Host Short" <BR> ntohl()--"Network to Host Long" <BR>现在,你可能想你已经知道他们了。你也可能想:"如果我改变 char 的顺序会怎么样呢 <BR>? 我的 68000 机器已经使用了网络字节顺序,我没有必要去调用 <BR>htonl() 转换 IP 地址。" 你可能是对的,但是当你移植你的程序到别的机器上的时候 <BR>,你的程序将失败。可移植性!这里是 Unix <BR>世界!记住:在你将数据放到网络上的时候,确信他们是网络字节顺序。 <BR>最后一点:为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要 <BR>转换为网络字节顺序,而 sin_family <BR>不需要呢? 答案是:sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,他 <BR>们必须要是网络字节顺序。但是 sin_family <BR>域只是被内核 (kernel) 使用来决定在数据结构中包含什么类型的地址,所以他应该是 <BR>本机字节顺序。也即 sin_family 没有 <BR>发送到网络上,他们可以是本机字节顺序。 <BR>IP 地址和如何处理他们 <BR>现在我们很幸运,因为我们有很多的函数来方便地操作 IP 地址。没有必要用手工计算 <BR>他们,也没有必要用 << 操作符来操作 long。 <BR>首先,假设你用 struct sockaddr_in ina,你想将 IP 地址 "132.241.5.10" 储存到其 <BR>中。你要用的函数是 <BR>inet_addr(),转换 numbers-and-dots 格式的 IP 地址到 unsigned long。这个工作可 <BR>以这样来做: <BR> ina.sin_addr.s_addr = inet_addr("132.241.5.10"); <BR>注意:inet_addr() 返回的地址已经是按照网络字节顺序的,你没有必要再去调用 hto <BR>nl()。 <BR>上面的代码可不是很健壮 (robust),因为没有错误检查。inet_addr() 在发生错误的时 <BR>候返回-1。记得二进制数吗? 在 IP 地址为 <BR>255.255.255.255 的时候返回的是 (unsigned)-1!这是个广播地址!记住正确的使用错 <BR>误检查。 <BR>好了,你现在可以转换字符串形式的 IP 地址为 long 了。那么你有一个数据结构 str <BR>uct in_addr,该如何按照 numbers-and-dots <BR>格式打印呢? 在这个时候,也许你要用函数 inet_ntoa() ("ntoa" 意思是 "network t <BR>o ascii"): <BR> printf("%s",inet_ntoa(ina.sin_addr)); <BR>他将打印 IP 地址。注意的是:函数 inet_ntoa() 的参数是 struct in_addr,而不是 <BR> <BR>long。同时要注意的是他返回的是一个指向字符的指针。在 inet_ntoa 内部的指针静态 <BR>地储存字符数组,因此每次你调用 inet_ntoa() <BR>的时候他将覆盖以前的内容。例如: <BR> char *a1, *a2; <BR> . <BR> . <BR> a1 = inet_ntoa(ina1.sin_addr); /* this is 198.92.129.1 */ <BR> a2 = inet_ntoa(ina2.sin_addr); /* this is 132.241.5.10 */ <BR> printf("address 1: %s\n",a1); <BR> printf("address 2: %s\n",a2); <BR>运行结果是: <BR> address 1: 132.241.5.10 <BR> address 2: 132.241.5.10 <BR>如果你想保存地址,那么用 strcpy() 保存到自己的字符数组中。 <BR>这就是这章的内容了。以后,我们将学习转换 "whitehouse.gov" 形式的字符串到正确 <BR>的 IP 地址(请看后面的 DNS 一章。) <BR>socket()--得到文件描述符! <BR>我猜我不会再扯远了--我必须讲 socket() 这个系统调用了。这里是详细的定义: <BR> #include <sys/types.h> <BR> #include <sys/socket.h> <BR> int socket(int domain, int type, int protocol); <BR>但是他们的参数怎么用? 首先,domain 应该设置成 "AF_INET",就象上面的数据结构 <BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -