网络是怎样连接的 笔记
第一章 浏览器生成消息
URL
代码语言:javascript复制协议类型
http:
https:
ftp:
file:
mailto:
HTTP协议
Request:Method URI Response: Status Code Header Body .......
客户端向Web服务器发送数据时,会先发送头字段
收到请求消息后,服务器会对内容进行解析,通过URI和方法来进行处理,然后将结果放在响应消息中,响应消息开头有一个状态码,后面就是头字段和数据。
响应消息会被发送回客户端,客户端收到之后,浏览器会从消息读出所需的数据并显示在屏幕上
格式
代码语言:javascript复制// 请求方法 请求URI HTTP版本
[Method] [URI] HTTP1.1
// 消息头 存放例如 cookie,日期,支持语音,压缩格式等
[ReqHeader]
// 消息体
// 完全没有内容的空行
[Block line]
// 需要发送的数据
[ReqBody]
状态码
通过DNS查询IP地址
生成HTTP消息后,下一步就是根据域名查询IP地址
互联网是基于TCP/IP设计的,由一些小的子网,通过路由器连接起来组成一个大的网络。
IP
IP地址是一串32位的数字,按照8位一组,分为4组,用十进制表示,再用 .
隔开
在IP地址的规则中,网络号和主机号连起来总共是32位,但这两部分的具体结构是不固定的,
因此我们还需另外的附加信息来表示IP地址的内部结构,即 子网掩码
。
子网掩码的格式如下图 ② 所示,是一串32位数字,其左侧为1,右侧为0 其中,为1的部分表示网络号,为0的部分表示主机号 也可把1的部分用十进制表示并写在IP右侧。 主机号全0代表整个子网 主机号全1代表向子网的全部设备发包,即广播 (例:UDP广播)
web_p28.jpg
域名和IP地址并用的理由
使用IP地址只需要处理4字节的数字,而域名需要处理几十个到255字节的字符,增加了路由器的负担,也增加了延迟。
Socket库 查询IP
注:Socket库是用于调用网络功能的程序组件集合
对于DNS服务器,我们计算机上一定有相应的DNS客户端,而相对于DNS客户端的部分称为DNS解析器,通过DNS查询IP地址的操作称为域名解析。 解析器是一段程序,包含在Socket库中
根据域名查询IP时,浏览器会调用解析器,解析器会向DNS服务器发送查询消息,然后DNS服务器 会返回响应消息,其中包含查询到IP地址,解析器会将IP地址写入到浏览器指定的内存地址中。
解析器 原理
当浏览器调用解析器时,程序的控制流程就会转移到解析器的内部。 当控制流程转移后,解析器会生成要发送给DNS服务器的查询消息,与生成HTTP消息的过程类似,并将它发送给DNS服务器,发送这一操作是委托给操作系统的协议栈执行的。当解析器调用协议栈后,控制流程再次转移,协议栈会执行发送消息的操作,然后通过网卡将消息发送给DNS服务器。
当DNS服务器收到消息后,它会根据消息中的查询内容进行查询,如果要访问的Web服务器已在DNS服务器上注册,那么这条记录就能被找到,然后其IP地址会被写入响应消息并返回给客户端。消息经过网络到达客户端,再经协议栈传递给解析器,然后解析器读取出IP地址,并将IP地址写入指定的内存地址。
注:向DNS服务器发送消息是,我们需要知道DNS服务器的IP地址,只不过这个IP地址作为 TCP/IP 的一个设置项目事先准备好了,不需要再进行查询。
DNS服务器 原理
客户端发送的查询消息包含以下3种信息:
代码语言:javascript复制(a) 域名
// 在最早设计DNS时,DNS在互联网以外的其他网络中的应用也被考虑到了,Class就是用来识别网络的信息。不过如今除了互联网并没有其他网络,因此Class的值永远是 IN .
(b) Class
// 对于不同记录的类型,返回的信息也会不同
// 例:A IP地址
// 例:MX 邮件服务器
(c) 记录类型
域名的层次结构
域名与IP地址的对照信息存放在多台DNS服务器中,这些DNS服务器相互接力配合,从而查找出要查询的信息。
DNS服务器中的所有信息都是按照域名以分层次的结构来保存的。
DNS中的域名都是用 .
进行分割的看,例如 blog.mashiro.ski
在域名中,越靠右的位置表示其层级越高。
这种具有层次结构的域名信息会注册到DNS服务器中,而每个域都是作为一个整体来处理的 即,一个域的信息是作为一个整体存放在DNS服务器中的,不能将一个与拆开存放在多台DNS服务器中,但一台DNS服务器可以存放多个域的信息。
寻找相应的 DNS服务器 并获取 IP地址
首先,将负责管理下级域的DNS服务器的IP地址注册到它们的上级DNS服务器中去,然后上级DNS服务器的IP再向上注册,以此类推。
在 com, net, org
等顶级域的上面还有一级域名,被称作 根域
根域
不像顶级域一样拥有自己的名字,因此书写时经常被忽略,如果要明确表示根域,应该像 cloudflare.com.
一样,在最后加一个 .
,就代表根域
不过一般都不写,因此根域的存在经常被忽略
根域的DNS服务器中保管着顶级域的DNS服务器信息,由于上级DNS服务器保管着所有下级DNS服务器的信息,所以可以自顶而下,找到任意一个域的DNS服务器。
因此,只要客户端能找到任意一台DNS服务器,就可以通过它找到根域的DNS服务器,然后自顶而下,找到存有所需信息的DNS服务器。
分配给根域的DNS服务器的IP地址在全世界仅有13个,而这些地址几乎不发生变化,因此将这些地址保存在所有的DNS服务器中也并不是一件难事 (根域的DNS服务器信息已经保存在DNS服务器程序的配置文件中了)
注:根域DNS服务器在运营商使用多台服务器来对应一个IP地址,所以尽管IP数量只有13个,但实际的服务器数量还是很多的。
web_p42.jpg
web_p43.jpg
DNS缓存
DNS服务器有一个缓存功能,可以记住之前查询过的域名,如果要查询的域名和相关信息已经在缓存中,那么就可以直接从缓存中得到所需的信息。
但原本的信息可能会发生改变,此时缓存的信息就变得不正确了,因此缓存有一个有效期,当缓存的信息超过有效期后,这些信息就会从缓存中删除
委托协议栈发送信息
同样需要调用Socket库,但需要按指定顺序调用多个程序组件
我们可以将数据通道想象成一条管道,将数据从一段送入管道,数据就会到达管道的另一端然后被取出,数据的流动是双向的,这些通道的出入口称为 套接字
。
web_p46.jpg
管道的生命周期是这样的: 1.服务器创建套接字,等待客户端向该套接字连接管道 (创建套接字阶段) 2.客户端创建一个套接字,连接到服务器的套接字上 (连接阶段) 3.收发数据 (通信阶段) 4.断开管道并删除套接字 (断开阶段)
管道在连接时是有客户端发起的,但在断开时可以由客户端或服务器任意一方发起 当管道断开后,套接字就会被删除
注:以上四个操作都是由协议栈来执行的,浏览器等应用程序并不会自己去做,而是委托给协议栈代劳
创建套接字阶段
应用程序调用Socket库中的socket程序组件 控制流程会转移到socket内部并执行创建套接字的操作,然后控制流程会回到应用程序
套接字创建完成后,协议栈会返回一个 描述符
,应用程序会将其存放在内存中
在同一台计算机上可能同时存在多个套接字,我们需要一种方法来识别出某个特定的套接字,也就是描述符
的作用
当创建套接字后,我们就可以使用这个套接字来执行收发数据的操作 这时我们只要出示描述符,协议栈就能够判断出我们希望用哪一个套接字来连接或收发数据
连接阶段
应用程序调用Socket库的connect的程序组件
需要传入 描述符、IP地址、端口号
三个参数
服务端端口号是根据应用的种类实现规定好或设定好的 例如:80, 443, 25, 22是规定的 而一些程序可以在配置文件内修改端口
客户端在创建套接字时,协议栈会为这个套接字随便分配一个端口号 当协议栈进行连接操作时,会将这个端口号通知给服务器
连接操作的对象是某个具体的套接字,因此必须要识别到具体的套接字才行,端口号就是这样一种识别方式。当同时指定IP地址和端口号时,就可以明确识别出某台具体的计算机上的某个具体的套接字。
描述符是和委托创建套接字的应用程序交互时使用的,并不是用来告诉网络连接的另一方的 如果说描述符是用来在一台计算机内部识别套接字的机制,那么端口号就是用来让通信的另一方能识别出套接字的机制
通信阶段
发送
应用程序调用Socket库的write程序组件 需要传入 描述符、发送数据
接收
应用程序调用Socket库的read程序组件 调用read时需要指定用于存放接收到的响应消息的内存地址,这一内存地址称为接收缓冲区 当消息被存放到内存缓冲区时,就相当于已经转交给了应用程序
断开阶段
调用Socke库的close程序组件 连接在套接字之间的管道会被断开,套接字本身也会被删除
Web使用的HTTP协议规定,当Web服务器发送完响应消息后,应该主动执行断开操作 因此服务器会首先调用close来断开连接,断开操作传达到客户端之后,客户端的套接字也会进入断开阶段 接下来,浏览器调用read执行接受数据操作时,read会告知浏览器收发数据操作已结束,连接已断开,浏览器得知后,也会调用close进入断开阶段
第二章 用电信号传递 TCP/IP 数据
学习 网络控制软件(协议栈)和网络硬件(网卡)是如何发送数据的
创建套接字
协议栈的内部结构
web_p61.jpg
协议栈的上半部分有两块,分别是负责用TCP协议收发数据的部分和用UDP协议收发数据的部分,它们会接受应用程序的委托执行收发数据的操作
下面一半是用IP协议控制网络包收发操作的部分 在互联网上传送数据时,数据会被切分成一个一个网络包,而将网络包发送给通信对象的操作就是由IP来负责的。 还包括ICMP协议和ARP协议 ICMP用于告知网络包传送过程中产生的错误以及各种控制信息 ARP用于根据IP地址查询的 MAC地址
套接字的实体就是通信控制信息
在协议栈内部有一块用于存放控制信息的内存空间,记录了用于控制通信操作的控制信息 协议栈是根据套接字中记录的控制信息来工作的
调用socket时的操作
创建套接字阶段
协议栈首先会分配用于存放一个套接字所需的内存空间 写入表示初始状态的控制信息 将表示这个套接字的描述符告知应用程序
连接服务器
连接实际上是通信双方交换控制信息 连接操作中所交换的控制信息是根据通信规则来确定的
当执行收发数据操作时,需要一块用来临时存放要收发的数据的内存空间,这块内存空间称为缓冲区
负责保存控制信息的头部
控制信息可以分为两类 第一类是客户端和服务器相互联络时交换的控制信息 这些内容在TCP协议的规格中进行了定义 这些字段是固定的,每次客户端与服务器进行通信时,都需要提供
web_p71.jpg
这些信息被添加在客户端与服务器传递的网络包的开头,因此被称为头部 为避免各种不同的头部发成混淆,一般记作 TCP头部、MAC头部、IP头部
第二类是保存在套接字中,用来控制协议栈操作的信息 应用程序传递的信息、从通信对象接收到的信息以及收发数据操作的状态等信息会保存在这里 协议栈会根据这里的信息来执行每一步操作
套接字的控制信息和协议栈的程序本身其实是一体的,因此协议栈需要的信息会因为协议栈本身的实现方式不同而不同
连接操作的实际过程
从应用程序调用connect
开始,传入了 描述符、IP地址、端口号
其中IP地址、端口号被传递给协议栈中的TCP模块
TCP头部
客户端先创建一个包含很多开始数据收发操作的控制信息的头部
通过头部中的发送方和接收方端口可以找到需要连接的套接字
然后将头部中的控制位的 SYN位
设置为1,它表示连接。还需要设置适当的序号和窗口大小
TCP握手
TCP模块会将信息传递给IP模块并委托它发送 IP模块执行网络报发送操作后,网络包会通过网络到达服务器,然后服务器上的IP模块将收到的数据传递给TCP模块,TCP模块根据TCP头部中的信息找到端口号对应的套接字 找到套接字后,套接字中会写入相应的信息,并将状态改为正在连接
上述操作完成后,服务器TCP模块会返回响应,这个过程和客户端一样,需要在TCP头部中设置发送方和接收方以及SYN位 (如果拒绝连接,则设置RST位为1),此外还需要将控制位的ACK (Acknowledge character) 位设为1,表明已接收到相应的网络包 之后,服务器上的TCP模块会将TCP头部传递给IP模块,并委托IP模块向客户端返回响应
网络包通过网络回到客户端,通过IP模块到达TCP模块,并通过TCP头部的信息确认连接服务器的操作是否成功 (SYN位为1),这时会向套接字中写入服务器的IP地址、端口号等信息,同时还会将状态修改为连接完毕
最后,客户端也需要将控制位的ACK位设置为1并发回服务器,表明刚刚的响应包已收到
收发数据
从应用程序调用write
将要发送的数据交给协议栈开始
应用程序在调用write
时会指定发送数据的长度
协议栈在接收到数据后会将数据存放在内部的发送缓冲区中,并等待下一段数据
这样做的原因是: 应用程序交给协议栈发送的数据长度是由应用程序本身来决定的,不同的应用程序会在实现上有所不同 一次将多少数据交给协议栈是由应用程序决定的,协议栈并不能控制,因此如果收到就马上发送,可能会发送大量的小包,到网络效率下降 因此需要在数据积累到一定量再发送出去,至于要积累多少,不同种类和版本的操作系统会有所不同,但都是根据以下因素判断的
第一个要素是MSS
- MTU MTU (Maximum Transmission Unit) 最大传输单元,表示一个网络包的最大长度 在以太网中一般是1500字节 (在ADSL等网络中,会小于1500),包含头部长度
- MSS MSS (Maximum Segment Size) 最大分段大小,由MTU减去头部长度得到 TCP和IP头部加起来一般是40字节,因此MSS的大小一般是1460字节
web_p77.jpg
另一个要素是时间 当应用程序发送数据的频率不高时,如果每次都要等到长度接近MSS再发送,可能会因为等待时间过长导致发送延迟 为此,协议栈内部有一个计时器,当经过一定时间 (毫秒级) 之后,就会把网络包发送出去
但是上面两个要素是互相矛盾的 如果长度有限,那么网络效率会提高,但是延迟会增大 如果时间有限,那么延迟会减小,但是网络效率会降低
因此发送时需要综合考虑两个要素以达到平衡,但TCP协议规格中没有规定如何平衡 因此不同种类和版本的操作系统在相关操作上也就存在差异
数据拆分
发送缓冲区中的数据超过MSS的长度时,数据就会被以MSS长度为单位进行拆分,拆分出来的每块数据会被放进单独的网络包中 当需要发送这些数据时,就在每一块数据的前面加上TCP头部,然后交给IP模块执行发送操作
web_p78.jpg
使用ACK位确认网络包已收到
TCP具备确认对方是否成功收到网络包,以及对方没收到时重发的功能,因此在发送网络包之后,接下来还需要进行确认操作
这一机制非常强大,无论网络中发生任何错误,我们都可以发现并重新发包 因此,网卡、集线器、路由器都没有错误补偿机制,一旦检测到错误就直接丢弃相应的包
如果发生网络中断等问题,在怎么重新发包都没有用的情况下,TCP会在尝试几次重传无效后强制结束通信,并向应用程序报错
原理
首先,TCP模块在拆分数据时,会先算好每一块数据相当于从头开始的第几个字节,在发送数据时,将算好的字节数写在TCP头部中,“序号” 字段就是用来填写这个数据的 然后,发送数据的长度也需要告知接收方,不过是通过整个网络包长度减去头部长度得到的 有了上面两个数值,我们就可以知道发送的数据是从第几个字节开始的,长度是多少了
通过这些信息,接收方可以检查收到的网络包没有遗漏 例如: 上次接收到1460字节,那么下一个包的序号应为1461,如果收到了序号为2921的包,那么就说明中间的包有遗漏
如果确认没有遗漏,接收方会将到目前为止接收到的数据长度加起来,计算出一共已经收到了多少个字节,然后将这个数值写入TCP头部的ACK号中发送给发送方 (同时需要将控制位的ACK位设置为1) 这个返回的ACK号的操作被称为确认响应,通过这样的方式,发送方法就能够确认对方到底收到了多少数据 注:收到一个包就返回一个ACK号
在实际的通信中,序号并不是从1开始,而是需要用随机数计算出一个初始值,这是因为如果序号都从1开始,整个通信过程会非常容易预测,从而导致攻击的发生 但如果是随机的,对方就不清楚了,因此需要在开始收发数据之前将初始值告知通信对象
在之前的操作中,有一步将 SYN控制位 设为1的操作,就是在这一步将序号的初始值告知对方的 在设置SYN控制位为1的同时,还需要设置序号字段的值为序号的初始值
注:SYN (Synchronize),意思是通过告知初始序号使通信双方保持步调一致,以便完成后续的数据收发检查
web_p80.jpg
双向传输
上文只考虑了单相的数据传输,但TCP数据的收发是双向的,在客户端向服务器发送数据的同时,服务器也会向客户端发送数据 因此只需增加一种左右相反的情形就可以了
首先,客户端先随机出一个序号,然后将序号和数据一起发送给服务器,服务器收到之后会计算ACK并返回给客户端 相反的,服务器也先随机出一个序号,然后将序号和数据一起发送给客户端,客户端收到后计算ACK号返回给访问
客户端和服务器双方都需要各自计算序号,因此双方需要在连接过程中胡先告知自己计算出的序号初始值
web_p82.jpg
TCP采用这样的方式确认对方是否收到了数据,才对方确认之前,数据会被保存在发送缓冲区中 如果对方没有返回某些包对应的ACK号,那么就重新发送这些包
根据网络包平均往返时间调整ACK号等待时间
返回ACK号的等待时间 (超时时间) 当ACK号的返回变慢,这是我们就必须将等待时间设置得稍微长一点,否则可能会发生已经重传了包之后,上一次的ACK号才刚刚收到的情况 在局域网中,ACK号几毫秒就可以返回;而互联网拥堵时,可能需要几百毫秒才能返回ACK号
正因为波动如此之大,所以将等待时间设置为一个固定值并不是一个好办法 因此,TCP采用了动态调整等待时间的方法 即,在发送数据的过程中持续测量ACK号的返回时间 如果ACK号返回变慢,则相应延长等待时间 如果ACK号马上返回,则相应缩短等待时间
注:由于计算机的时间测量精度较低,ACK返回时间过短时无法被正确测量,因此等待时间有一个最小值,这个值在每个操作系统上不一样,基本上是0.5s~1s之间
使用窗口有效管理ACK号
如果发送一个包就等待一个ACK号,那么中间的等待时间就会浪费 因此TCP采用滑动窗口来管理数据发送和ACK号的操作 滑动窗口就是 在发送一个包之后,不等待ACK号返回,直接发送后续一系列包
web_p85.jpg
使用滑动窗口可能会出现发送包的频率超过接收方处理能力的情况 具体解释就是: 当接收方接收到TCP收到包之后,会先将数据存放到接收缓冲区中,然后接收方计算ACK号,将数据块组装起来还原成原本的数据并传递给应用程序 如果这些操作还没完成,之后又有很多包到达,数据还是会存放在接收缓冲区里,但如果缓冲区溢出,之后的数据就进不来了,因此接收方就接收不到后面的包了
因此,接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制,来避免上述情况的发生
具体工作方式
接收方将数据暂存到接收缓冲区中并执行接收操作
当接收完成后,接收缓冲区中的空间会被释放出来,也就可以接收更多的数据了
这时接收方会通过TCP头部中的 窗口字段
将自己能接收的数据量告知发送方
这样发送方就不会发送过多的数据,导致接收缓存区溢出
和序号、ACK号一样,发送操作也是双向进行的
前面提到的能接收的最大数据量称为窗口大小 (一般与接收缓冲区大小一致),它是TCP调优参数中非常有名的一个
web_p87.jpg
ACK 与 窗口 的合并
每收到一个包,就向发送方分别发送ACK号和窗口更新这两个单独的包 接收方给发送方发送的包太多了,会导致网络效率下降
因此接收方在发送ACK号和窗口更新时,并不会马上把包发送出去,而是会等待一段时间 在这个过程中成功很可能会出现其他的通知操作,这样就可以把两种通知合并在一个包里发送了
当需要连续发送多个ACK号时,也可以减少包的数量,这是因为ACK号表示的是已接收到的数据量 也就是说,它是告诉发送方目前已经接收的数据的最后为在哪里,因此需要连续发送ACK号时,只要发送最后一个ACK号就可以了,中间的可以全部省略
当需要连续发送多个窗口更新时也可以减少包的数量,因为连续发生窗口更新说明应用程序连续请求了数据,接收缓冲区的剩余空间连续增加了,这时也只需要发送中间的结果就可以了
接收 HTTP 响应消息
发送HTTP请求消息后,需要等待Web服务器返回响应消息,浏览器需要对其进行接收,这一操作需要协议栈的参与
浏览器在委托协议栈发送请求消息之后,会调用 read 程序,和发送数据一样,接收数据需要将数据暂存到接收缓冲区
首先,协议栈尝试从接收缓冲区取出数据传递给应用程序,但这时候数据刚发出去,响应消息可能还没返回,这时,协议栈会将应用程序的委托,也就是从缓冲区取出数据并传递给应用程序的工作暂时挂起,等服务器返回的响应消息到达之后再继续执行接收操作
首先,协议栈会检查收到的数据块和TCP头部的内容,判断是否有数据丢失,如果没有问题则返回ACK号 然后,协议栈将数据块暂存到数据缓冲区中,并将数据块按顺序连接起来还原出原始的数据,最后将数据交给应用程序,之后,协议栈要找到合适的时机向发送方发送窗口更新
从服务器断开并删除套接字
数据发送完毕后断开连接
收发数据的时间点应该是应用程序判断所有数据都已经发送完毕的时候 数据发送完毕的一方会发起断开过程,不同的应用程序会选择不同的断开时机 协议栈允许任意一方发起断开过程
以服务器发起断开过程为例 服务器 首先服务器的应用程序调用Socket库的close程序 然后服务器的协议栈生成包含断开信息的TCP头部,即 将控制位的FIN位设为1 并委托IP模块向客户端发送数据
客户端 首先,收到服务器发来的FIN位为1的TCP头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态 然后,未告知服务器已收到FIN位为1的包,客户端会向服务器返回一个ACK号 注:客户端的应用程序可能在收到FIN包之前就来读取数据,这时读取操作会被挂起,直到FIN包到达 之后应用程序来读取数据,如果接收缓冲区中还有数据,那么这些数据将会传递给应用程序,协议栈会告知应用程序来自服务器的数据已经全部收到了 接着客户端应用程序会调用close来结束数据收发操作,客户端也会像服务器一样发送一个FIN位为1的包,然后服务器会返回一个ACK号给客户端 之后,服务器和客户端的通信就全部结束了
web_p91.jpg
删除套接字
和服务器的通信结束之后,套接字并不会立即被删除,而是会等待一段时间之后再被删除 等待的这段时间是为了防止误操作
例如:客户端返回的ACK号丢失了,服务器没有收到,可能会重发一次FIN包,如果这时客户端的套接字已经删除,端口被释放,而恰巧又有新的套接字使用了这个端口,收到了服务器重发的FIN包,新的套接字开始执行断开操作,错误就发生了
至于具体的等待时间,协议没有明确规定,这和包的重传方式有关,通常持续几分钟 一般来说等待几分钟之后再删除套接字
数据收发小结
web_p91.jpg
IP与MAC的包收发操作
网络包的基本知识
包是由头部和数据两部分构成的 头部包含目的地址等控制信息 头部后面就是委托方发送给对方的数据
首先,发送方的网络设备会负责创建包,创建包的过程就是生成含有正确控制信息的头部,然后附上要发送的数据 接着,包会被发往最近的网络转发设备,转发设备会根据头部中的信息判断接下来应该发往哪里,这个过程需要用到一张表 (路由表),这张表里记录了每一个地址对应的发送方向,也就是按照头部里记录的目的地址在表里进行查询,并根据查询到的信息判断接下来应该发往哪个方向
经过多个转发设备的接力之后,包最终就会到达接收方的网络设备
发送方和接收方是相对的,因此我们不需要明确区分,在这里将发送方和接收方统称为 终端节点
,相应的,转发设备被称为 转发节点
或 中间节点
路由器是按照IP规则传输包的设备 集线器是按照以太网协议传输包的设备
即: IP协议根据目标地址判断下一个IP转发设备的位置 子网中的以太网协议将包传输到下一个转发设备
web_p96.jpg
如图 2.14(b) 所示,TCP/IP包有两个头部,分别是MAC头部和IP头部 这两个头部有不同的作用 首先,发送方将包的目的地,即服务器IP写入IP头部中,IP协议根据这一地址查找包的传输方向,从而找到下一个路由器的地址,并委托以太网协议将包传输过去。这时IP协议会查找下一个路由器的MAC地址,并将这个地址写入MAC头部,这样一来,以太网协议就知道要将这个包发到哪个路由器上了
集线器是根据以太网协议工作的设备。为了判断包接下来应该向哪里传输,集线器内有一张表(用于以太网协议的表)可以根据以太网头部中记录目的信息查出相应的传输方向 当存在多个集线器时,网络包会按顺序逐一通过这些集线器进行传输
包会到达下一个路由器,路由器中有一张用于IP协议的表,可根据这张表和IP头部中记录的目的地信息查出接下来要发往那个路由器 为了将包发到下一个路由器,我们还需要查处下一个路由器的MAC地址,并覆盖记录到MAC头部中,可以理解为改写了MAC头部 注:收到包的时候MAC头部会被舍弃,而当再次发送的时候又会加上包含新的MAC地址的新MAC头部
web_p99.jpg
上文讲了IP和以太网的分工,其中以太网的部分也可以替换成其他的东西,例如无线局域网、ASDL、FTTH等,它们都可以替代以太网的觉得帮助IP协议来传输网络包 因此,将IP和负责传输的网络分开,可以更好地根据需要使用各种通信技术。像互联网这样庞大复杂的网络,在架构上需要保证灵活性 注:使用除以太网之外的其他网络进行传输时,MAC头部也会被替换为适合所选通信规格的其他头部
包收发操作概览
实际上将包从发送方传输到接收方的工作是由集线器、路由器等网络设备完成的,因此IP模块仅仅是整个包传输过程的入口而已
IP模块的工作
包收发操作的起点是TCP模块委托IP模块发送包的操作,这个委托的过程就是TCP模块在数据块前面加上TCP头部,然后传递给IP模块。与此同时,TCP模块还需要制定通信对象的IP地址
收到委托后,IP模块会将包的内容当做一整块数据,在前面加上包含块控制信息的头部 IP模块会添加IP头部和MAC头部这两种头部 IP头部中包含IP协议规定的、根据IP地址将包发往目的地所需的控制信息 MAC头部包含通过以太网的局域网将包传输至最近的路由器所需的控制信息
加上这两个头部之后,包就封装好了,这就是IP模块负责的工作 注:凡是局域网所使用的头部豆角MAC头部,但其内容根据局域网的类型有所不同
封装好的包会被交给网络硬件,后文将它们统称为“网卡” 传递给网卡的网络包是由一连串的 0 和 1 组成的数字信息,网卡会将这些数字信息转换为电信号或光信号,并通过网线(或光纤)发送初期,然后这些信号就会到达集线器、路由器等转发设备,再由转发设备一步一步地送达接收方
接受过程和发送过程是相反的,信息先以电信号的形式传进来,然后网卡将其转换为数字信息并传递给IP模块。接下来,IP模块会将MAC头部和IP头部后面的内容,也就是TCP头部加上数据块,传递给TCP模块
因为IP模块并不关心数据的内容,也不关心TCP阶段的操作 因此,接下来的关于IP的工作方式,可以使用于任何TCP委派的收发数据
生成包含接收方IP地址的IP头部
web_p103.jpg
IP模块会生成IP头部附加在TCP头部前面,其中最重要的内容就是IP地址,这个地址是TCP模块告知的
IP不会自行判断包的目的地,而是将包发往应用程序指定的接收方,即便有应用程序指定了错误的IP地址,IP模块也只能照做
IP头部中还需要填写发送方的IP地址,可以认为是发送方计算机的IP地址,但这种说法并不准确 更准确的说是发送这个包的网卡的IP地址
在填写发送方IP地址时就需要判断到底应该填写哪个地址,这个判断相当于在多块网卡中判断应该使用哪一块网卡来发送这个包,也就相当于判断应该把包发往哪个路由器 因此只要确定了目标路由器,也就确定了应该使用哪块网卡,也就确定了发送方的IP地址
web_p105.jpg
上文提到路由器中有一张IP协议的表,这张表叫做路由表
首先,对套接字中记录目的地IP地址与路由器左侧的 Network Destination 栏进行比较
例如,目标地址为 192.168.1.21,那么就对应第6行,因为它和 192.168.1 的部分匹配
目标地址为 10.10.1.166,那么就和 10.10.1 的部分匹配,所以对应第3行
我们只需要找到与IP地址左边部分相匹配的条目,找到相应的条目之后,接下来看从右数第2列和第3列的内容
右起第 2 列 Interface ,表示网卡等网络接口, 右起第 3 列 Gateway ,表示下一个路由器的地址,将包发送给这个IP地址,该地址对应的路由器就会将包转发到目标地址 注:如果Gateway和Interface列的IP地址相同,就表示不需要路由器进行转发,可以直接将包发给接收方的IP地址
路由表的第一行中,目标地址和子网掩码都是 0.0.0.0
,这表示默认网关,如果其它所有条目都无法匹配,就会自动匹配这一行
注:子网掩码 用来判断IP地址中网络号与主机号分界线的值
这样我们就可以判断用哪块网卡来发包了,然后在IP头部的发送方IP地址中填上这块网卡对应的IP地址
接下来还需要填写协议号,它表示包的内容是来自哪个模块的 如果是TCP模块委托的内容,则设置为 06 (十六进制),如果是UDP模块委托的内容,则设置为 17 (十六进制) 浏览器中,HTTP请求消息都是通过TCP来传输的,因此这里就会填写表示TCP的 06 (十六进制)
生成以太网用的MAC头部
IP模块还需要在IP头部前面加上MAC头部
以太网在判断网络包目的地时和TCP/IP的方式不同,因此必须采用相匹配的方式才能在以太网中将包发往目的地,而MAC头部就是干这个用的
MAC头部包含了接收方和发送方的MAC地址等信息
web_p107.jpg
MAC头部的开头是接收方和发送方的MAC地址,可以认为它们和IP头部中的接收方和发送方IP地址的功能差不多,只不过IP地址的长度为 32位,而MAC地址的长度为 48位 此外,IP地址是层次化结构,而MAC地址的 48位 可看做一个整体 但从表示接收方和发送方的意义上来说,它们之间是没有区别的
web_p110.jpg
在IP中,协议号表示IP头部后面的包内容的类型 而在以太网中,我们可以认为以太网类型后面就是以太网包的内容,而以太类型就表示后面内容的类型 以太网包的内容可以是IP、ARP等协议的包,他们都有对应的值,这也是根据规则来确定的
在生成MAC头部时,只要设置上表中的三个字段就可以了
以太类型
,填写表示IP协议的值 0800
(十六进制)
发送方MAC地址,填写网卡本身的MAC地址
接收方MAC地址,对方的MAC地址
网卡的MAC地址是出厂时写入ROM的,只需要经这个值读出来写入即可 注:实际上只有在操作系统启动过程中对网卡进行初始化时才会读取MAC地址,读取出来之后会存放在内存中 读取MAC地址的操作是由网卡驱动程序来完成的,因此网卡驱动也可以不从网卡ROM中读取地址,而是将配置文件中设定的MAC地址拿出来放到内存中并用于设定MAC头部,或者也可以通过命令输入MAC地址
通过ARP查询目标路由器的MAC地址
在以太网中,有一种叫做广播的方法(前文提过),可以将包发给连接在同一以太网中的所有设备 ARP就是利用广播对所有设备提问:“XX这个IP地址是谁的”,然后就会有人回答:“XX这个IP是我的,我的MAC地址是XXXX” 如果对方和自己处在同一子网中,那么通过上面的操作就可以得到对方的MAC地址 然后,我们将这个MAC地址写入MAC头部,MAC头部就完成了
注:不是这个IP地址的设备会忽略广播,什么都不回答 如果路由表设置的正确,双方应该在同一子网,否则对方无法作出ARP响应,这是只能认为对方不存在,包的发送操作就会失败
如果每次发包都要这样查询一次,网络中就会增加很多ARP包,因此我们会将查询结果放到一块叫作ARP缓存的内存空间中 在发送包的时候,先查询一下ARP缓存没如果其中已经保存了对方的MAC地址,就不需要发送ARP查询,直接使用ARP缓存中的地址,而当ARP缓存中不存在时,发送ARP查询
ARP缓存中的值会在几分钟左右被删除,这个策略能够在几分钟后消除缓存和现实的差异,但IP地址刚刚发生改变的时候,ARP缓存中依然会保留老的地址,这时就会发生通信异常
以太网的基本知识
以太网是一种为多台计算机能够彼此自由和廉价的相互通信而设计的通信技术
web_p112.jpg
这种网络的本质其实就是一根网线,如图(a)所示,图上还有种叫做收发器的小设备,它的功能只是将不同网线之间的信号连接起来而已 因此,当一台计算机发送信号时,信号就会通过网线流过整个网络,最终到达所有的设备 为了控制这一操作,就需要MAC头部,通过头部中接收方的MAc地址,就能够知道包是发送给谁的通过发送方MAC地址,就能知道包是谁发出的 注:多台设备同时发送信号会造成碰撞,当然也有相应的解决方案 随着交换机的普及,信号已经不会发生碰撞了
这个原型后来变成了图(b)中的结构 这个结构是将主干网线替换成了一个中继式集线器(集线器),收发器网线替换成了双绞线 虽然网络的结构有所变化,但信号会发送给所有设备这一基本性质并没有改变
后来,图(c)这样的使用交换式集线器(交换机)的结构普及开来,现在我们说的以太网指的都是这样的结构 这个结构看上去和(b)很像,但其实里面有一个重要的变化,即信号会发送给所有设备 这一性质变了,现在信号只会流到根据MAC地址指定的设备,而不会到达其它设备了 根据MAC地址传输包这一点并没有变,因此MAC头部的设计也得以保留
尽管以太网经历了多次变迁,但其基本的3个性质至今仍未改变,即 1.将包发送到MAC头部的接收方MAC地址代表的目的地 2.用发送方MAC地址识别发送方 3.用以太类型识别包的内容 因此可以认为具备这三个性质的网络就是以太网
以太网和IP一样,并不关心网络包的实际内容,因此以太网的收发操作也和TCP的工作阶段无关,都是共同的
将IP包转换成电信号或光信号发送出去
IP生成的网络包只是存放在内存中的一串数字信息,没办法直接发送给对方 我们需要将数字信息转换为电信号或光信号,才能在网线或光纤上传输,这才是真正的数据发送过程
负责执行这一操作的是网卡,但网卡也无法单独工作,要控制网卡还需要网卡驱动程序 如下图所示,这是一张网卡主要构成要素与的概念图,并不代表硬件的实际结构
web_p115.jpg
网卡并不是通上电之后就可以马上开始工作的,而是和其他硬件一样都需要进行初始化 也就是打开计算机启动操作系统的时候,网卡驱动程序会对硬件进行初始化操作,然后硬件才进入可以使用的状态,操作包括硬件错误检查、初始设置等步骤,这些步骤对于很多其他硬件也是共通的 但也有一些操作是以太网特有的,那就是在控制以太网收发操作的MAC模块中设置MAC地址 注:MAC Media Access Control 网卡的ROM中保存着全世界唯一的MAC地址,将这个值读出之后就可以对MAC模块进行设置 旁注:之前在某个群里见过两块MAC地址相同的网卡,运气也确实不错
也有一些特殊的方法,比如从命令或配置文件中读取MAC地址并分给MAC模块,这种情况下,网卡会忽略ROM中的MAC地址 注:设置MAC地址时,必须注意不能喝网络中其它设备的MAC地址重复,否则网络将无法正常工作
给网络包再加 3 个控制数据
网卡驱动从IP模块获取包之后,会将其复制到网卡内的缓冲区中,然后向MAC模块发送发送包的命令
首先,MAC模块会将包从缓冲区中取出,并在开头加上报头和起始帧分界符,在末尾加上用于检测错误的FCS(帧校验序列) 注:IEEE处于历史原因使用了“帧”而不是“包”,因此在以太网数据中都说是“帧”,可以认为包和帧是一回事
web_p117_1.jpg
报头是一串像 10101010...
这样1和0交替出现的比特序列,长度为 56
位
它的作用是确定读取包的时机
当这些 1010 的比特序列被转换为电信号后,会形成下图这样的波形 接收方在收到信号时,遇到这样的波形就可以判断读取数据的时机
web_p117_2.jpg
用电信号来表达数字信息时,我们需要让 0 和 1 两种比特分别对应特定的电压和电流 通过电信号来读取数据的过程就是将这种对应关系颠倒过来 也就是说,通过测量信号中的电压和电流变化,还原出 0 和 1 两种比特的值 实际的信号并不像下图那样有分割每个比特的辅助线,因此在测量电压和电流是必须先判断出每个比特的界限在哪里 但是像下图(a)右边这种连续的信号,由于电压和电流没有变化,我们就没办法判断出其中每个比特到底该从哪里去切分
web_p118.jpg
要解决这个问题,最简单的方法就是在数据信号之外再发送一组用来区分比特间隔的时钟信号
如上图(b)所示,当时钟信号从下往上变化时,读取电压和电流的值,然后和 0 或 1 进行对应就可以了
但这种方法也存在问题,当距离较远,网线较长时,两条线路的长度会发生差异,数据信号和时钟信号的传输会产生时间差,时钟就会发生偏移
要解决这个问题,可以采用将数据信号和时钟信号叠加在一起的方法,如上图(c)所示 由于时钟信号是像上图(b)那样按固定频率进行变化的,只要能够找到这个变化的周期,就可以从接收到的信号(c)中提取出时钟信号(b),进而计算出数据信号(a),这和发送方将数据信号和时钟信号叠加的过程正好相反 然后,只要根据时钟信号(b)的变化周期,我们就可以从数据信号(a)中读取相应的电压和电流值,并将其还原为 0 或 1 的比特
重点在于如何判断时钟信号的变化周期,时钟信号是以 10Mbit/s 或 100 Mbit/s 这种固定频率进行变化的,只要对信号进行一段时间的观察,就可以找到其变化的周期 因此我们不能一开始就发送包的数据,而是要在前面加上一段用来测量时钟信号的特殊信号,这就是报头的作用 注:如果在包信号结束之后,继续传输时钟信号,就可以保持时钟同步的状态,下一个包就无需重新进行同步。有些通信方式采用了这样的设计,但以太网的包结束之后时钟信号也跟着结束了,没有通过这种方式来保持时钟同步,因此需要在每个包的前面加上报头,用来进行时钟同步
以太网根据速率和网线类型的不同分为许多派生方式,每种方式的信号形态也有差异,并不都是像本例中讲的这样,单纯通过电压和电流来表达 0 和 1 的。 因此,101010……这样的报头数字信息在转换成电信号后,其波形也不一定都是图2.25的样子,而是根据方式的不同而不同,但报头的作用和基本思路是一致的
起始帧分界符的末尾比特排列有少许变化,接收方以这一变化作为标记,从这里开始提取网络包数据
末尾的帧校验序列(FCS)用来检查包传输过程中因噪声导致的波形紊乱、数据错误 它是一串 32 位的序列,是通过一个公式对包中从头到尾的所有内容进行计算而得出来的 它和磁盘等设备中使用的CRC错误校验码是同一种东西,当原始数据中某一个比特发生变化时,计算出来的结果就会发生变化 接收方计算出的FCS和发送方计算出的FCS就会不同,这样我们就可以判断出数据有没有错误
向集线器发送网络包
发送信号的操作分为两种,一种是使用集线器的半双工模式,另一种是使用交换机的全双工模式 注:发送和接收同时并行的方式叫做全双工;发送和接收某一时刻只能执行一个的方式叫做半双工
在半双工模式中,为了避免信号碰撞,首先要判断网线中是否存在其他设备发送的信号,如果有,则需等待该信号传输完毕
首先,MAC模块从报头开始将数字信息按每个比特位转换成电信号,然后由叫做 MHU(PHY)
的信号收发模块发送出去
将数字信息转换为电信号的速率就是网络的传输速率,例如每秒将 10Mbit 的数字信息转换为电信号发送出去,则速率就是 10Mbit/s
接下来,MAU模块会将信号转换为可在网线上传输的格式,并通过网线发送出去 以太网规格中对不同的网线类型和速率以及其对应的信号格式进行了规定,但MAC模块并不关心这些区别,而是将可转换为任意格式的通用信号发送给MAU模块,然后MAU模块再将其转换为可在网线上传输的格式
可以认为MAU模块的功能就是对MAC模块产生的信号进行格式转换
网线中实际传输的信号就是这个样子的 下图是一个梨子
web_p121.jpg
MAU的职责并不是仅仅将MAC模块传递进来的信号通过网线发送出去,它还需要监控接收线路中有没有信号进来
以太网不会确认发送的信号对方有没有收到。根据以太网的规格,两台设备之间的网线不能超过100米,采用光纤则可以更长,在这个距离内极少会发生错误,万一发生错误,协议栈的TCP协议也会负责搞定
在半双工模式中,如果在发送信号的同时有其他信号进来,两组信号就会相互叠加,无法彼此区分出来,这就是所谓的信号碰撞
这种情况下,继续发送信号是没有意义的,因此发送操作会终止
为了通知其它设备当前线路已发生碰撞,还会发送一段时间的阻塞信号,然后所有的发送操作会全部停止。为了通知其他设备当前线路已发生碰撞,还会发送一段时间的阻塞信号
,然后所有的发送操作会全部停止
等待一段时间之后,网络中的设备会尝试重新发送信号 但如果所有设备的等待时间都相同,那肯定还会发生碰撞,因此必须让等待时间互相错开 具体来说,等待时间是根据MAC地址生成一个随机数计算出来的
当网络拥塞时,发生碰撞的可能性就会提高没重新发送的时候可能又会和另一台设备的发送操作冲突,这时会将等待时间延长一倍,最多重试 10 次,如果还是不行就报告通信错误
全双工模式会在第三章介绍,在全双工模式中,发送和接收可以同时进行,不会发生碰撞
接收返回包
在使用集线器的半双工模式以太网中,一台设备发送的信号会到达连接在集线器上的所有设备 这意味着无论是不是发给自己的信号都会通过接收线路传进来,因此接受操作的第一步就是把这些信号全部接收进来
信号的开头是报头,通过报头的波形同步时钟,然后遇到起始帧分界符时开始将后面的信息转换成数字信息 这个操作和发送时是相反的,即 MAU模块会将信号转换成通用格式并发送给MAC模块,MAC模块再从头开始将信号转换为数字信息,并存放到缓冲区中 当到达信号的末尾时,还需要检查FCS,具体地说就是将包开头到结尾的比特位套用到公式中,计算出FCS,然后和包末尾的FCS进行对比,正常情况下两者应该是一致的,如果中途收到噪声感染而导致波形发生紊乱,则两者的值会产生差异,这时这个包会被当做错误包而被丢弃
如果FCS校验没问题,接下来就要比对MAC头部中接收方的MAC地址和网卡初始化过程中分配的MAC地址是否一致,如果不一致则直接丢弃;如果一致,则将包放入缓冲区中 接下来网卡会通知计算机收到了一个包 注:有一种特殊的模式 “混杂模式” 可以让网卡不检查包的接收方地址
通知计算机的操作会使用一个叫作 中断
的机制
在网咖执行接受报的操作过程中,计算机并不是一直监控着网卡的活动,而是去继续执行其他的任务
因此,如果网卡不通知计算机,计算机是不知道包已经收到了这件事的
网卡驱动也是在计算机中运行的一个程序,因此它也不知道包到达的状态
在这种情况下,我们需要一种机制能够打断计算机正在执行的任务,让计算机注意到网卡中发生的事情,这种机制就是 中断
中断的工作过程是这样的 首先,网卡想拓展总线中的中断信号线发送信号,该信号线通过计算机中的中断控制器连接到CPU 当产生中断信号时,CPU会暂时挂起正在处理的任务,切换到操作系统中的中断处理程序 然后,中断处理程序会调用网卡驱动,控制网卡执行相应的接收操作
中断是有编号的,网卡在安装的时候就砸硬件中设置了 中断号
,在中断处理程序中则是将硬件的中断号和相应的驱动程序绑定
现在的硬件设备都遵循即插即用规范自动设置中断号,我们没有必要去关心中断号了
注:即插即用规范 PnP(Plug and Play),是一种自动对拓展卡和周边设备进行配置的功能
网卡驱动被中断处理程序调用后,会从网卡的缓冲区中取出收到的包,并通过MAC头部中的以太类型字段判断协议的类型 除了TCP/IP外还有很多协议,这些协议都被分配了不同的以太协议,网卡会把这样的包交给对应的协议栈,前提是操作系统内部存在以太类型所对应的协议栈,如不存在,则会视为错误,直接丢弃这个包
将服务器的响应包从 IP 传递给 TCP
假设Web服务器返回一个网络包,服务器返回的包的类型应该是0800 (TCP),因此网卡驱动会将其交给TCP/IP协议栈处理 接下来就轮到IP模块,第一步是检查IP头部,确认格式是否正确 如果没有问题,下一步就是查看接收方IP地址,服务器返回的包的接收方IP地址应该与客户端网卡的地址一致,检查确认后我们就可以接收这个包了
如果接收方IP地址不是自己的地址,那一定是发生了什么错误 客户端计算机不负责对包进行转发,因此不应该收到不是发给自己的包 注:服务器的操作系统具备与路由器相同的包转发功能,当打开这一功能时,就可以像路由器一样对包进行转发
当发生这样的错误时,IP模块会通过ICMP消息将错误告知发送方
web_p126.jpg
当我们遇到这个错误时,IP模块会通过上表中的 Destination unreachable
消息通知对方
从这张表的内容中我们可以看到在包的接收和转发过程中能够遇到的各种错误
如果接收方的IP地址正确,则这个包会被接收下来,这时还需完成另一项工作
IP协议有一个叫作 分片
的功能
简单来说,网线和局域网中只能传输小包,因此需要将大的包却分成多个小包。如果接收到的包是经过分片的,那么IP模块会将它们还原成原始的包
分片的包会在IP头部的标志字段中进行标记,当收到分片的包时,IP模块会将其暂存在内部的内存空间中,然后等待IP头部中具有相同ID的包全部到达,这是因为同一个包的所有分片都具有相同的ID
此外,IP头部还有一个 分片偏移量
字段,它到达后,表示当前分片在整个包中所处的位置
根据这些信息,在所有分片全部收到之后,就可以将它们还原成原始的包,这个操作叫做 分片重组
接下来包会被交给TCP模块,TCP模块会根据IP头部中的接收方和发送方IP地址,一级TCP头部中的接收方和发送方端口来查找对应的套接字 找到对应的套接字之后,就可以根据套接字中记录的通信状态,执行相应的操作 注:因为IP头部是IP模块负责的,TCP模块去查询它等于是越权了 然而,如果根据这种阉割的划分来开发程序的话,IP模块和TCP模块之间的交互过程必然会产生成本,而IP模块和TCP模块进行类似交互的场景其实非常多,总体的交互成本就会很高,程序的运行效率就会下降。因此就像之前提到的一样,不妨将责任范围划分得宽松一点,将TCP和IP作为一个整体来看待,这样可以带来更大的灵活性
UDP 协议的收发操作
不需要重发的数据用 UDP 重发更高效
有些程序不使用TCP协议,而是使用UDP协议来收发数据,像DNS服务器查询IP地址
TCP为什么设计的如此复杂呢? 因为我们需要将数据高效且可靠地发送给对方 为了实现可靠性,我们就需要确认对方是否收到了我们发送的数据,如果没有还需要重发一遍
实现上面的要求,最简单的方法是数据全部发送完毕之后让接收方返回一个接收确认,这样一来,如果没收到直接全部发送一遍就好了 但是,如果漏掉了一个包就要全部重发一遍,怎么看都很低效 为了实现高效的传输,我们要避免重发已送达的包,而只是重发那些出错的或未送达的包 TCP之所以复杂,是为了实现这一点
不过,在某种情况下,即便没有TCP这样复杂的机制,我们也能够高效的重发数据 这种情况就是数据很短,用一个包就能装得下 如果只有一个包,就不用考虑哪个包未送达了,因为全部重发也只是重发一个包而已,这种情况下我们就不需要TCP这样复杂的机制 而且,如果不使用TCP,也不需要发送那些用来建立和断开连接的控制包 此外,我们发送了数据,对方一般都会给出回复,只要将回复的数据当做接收确认就行了,也就不需要专门的接收确认包了
控制用的短数据
UDP没有TCP的接收确认、窗口等机制,因此在收发数据之前也不需要交换控制信息,也就是说不需要建立和断开连接的步骤,只要在从应用程序获取的数据前面加上UDP头部,然后交给IP模块发送就可以了 接收也很简单,只要根据IP头部中的接收方和发送方IP地址,以及UDP头部中的接收方和发送方端口号,找到相应的套接字并将数据交给相应的应用程序就可以了
web_p130.jpg
除此之外,UDP协议没有其他功能了,遇到错误或丢包也一概不管 因为UDP只是负责发送包而已,并不像TCP协议一样会对包的送达进行监控,所以协议栈也不知道有没有发生错误,因此出错时就收不到来自对方的回复,应用程序会注意到这个问题,并重新发送一遍数据
UDP可发送的数据的最大长度为IP包的最大长度减去IP头部和UDP头部的长度 不过这个长度与MTU、MSS不是一个层面上的概念,MTU和MSS是基于以太网和通信线路上网络包的最大长度来计算的,而IP包的最大长度是由IP头部中的“全场”字段决定的 全长字段的长度为16位,因此从IP协议规范来看,IP包的最大长度为65535字节,再减去IP头部和UDP头部的长度,就是UDP协议所能发送的数据最大长度 如果不考虑可选字段的话,一般来说IP头部为20字节,UDP头部为8字节,因此UDP协议的最大数据长度为65507字节 当然这个数据长度已经超过了以太网和通信线路的最大传输长度,因此需要让IP模块使用分片功能后再传输
音频和视频数据
还有一个场景会使用UDP,就是发送音频和视频数据的时候 音频和视频数据必须在规定的时间内送达,一旦送达晚了,就会错过播放时机,导致声音和图像卡顿 如果像TCP一样通过接收确认响应来检查错误并重发,重发的过程需要耗费一定时间,因此重发的数据很可能已经错过了播放的时机 当然,我们可以用高速线路让重发的数据能够在规定的时间内送达,但这样需要增加几倍的带宽才行 注:UDP经常会被防火墙阻止,因此当需要穿越防火墙传输音频和视频数据时,尽管需要消耗额外的带宽,但有时也只能使用TCP 此外,音频和视频数据中缺少了某些包并不会产生严重的问题,只是会产生一些失真或者卡顿而已,一般都是可接受的
在这些无需重发,或是重发了也没什么意义的情况下,使用UDP发送数据的效率会更高
其他
施乐(Xerox)公司过去在研究未来的计算机架构,计算机和以太网只是其中一个环节的产物