目录
5.1概述
5.2传输层的寻址与端口
熟知端口号
套接字(Socket)
5.3 UDP
特点
UDP报文格式
UDP校验
二进制反码求和
5.4 TCP
特点
可靠传输
停止等待协议
流水线方式
累计应答
流量控制
滑动窗口
拥塞控制
三次握手,四次握手
5.1概述
只有主机才会有的层次。
1.传输层提供进程和进程之间的逻辑通信。
2.复用和分用。
3.传输层对收到的报文进行差错检错。【传输层的接收到的报文就是网络层的数据报的数据部分,网络层检验了报文首部,这样只需要让运输层检验报文就好。】
4.传输层的两种协议。【UDP,TCP】
5.2传输层的寻址与端口
2.复用和分用。
复用:应用层所有的应用进程都可以通过传输层再传输到网络层。
分用:传输层从网络层收到的数据交付到指明的应用进程。
端口:
这里的端口是逻辑端口/软件端口。
端口是传输层的SAP【服务访问点】。标识主机中的进程。
端口号只有本地意义,再因特网中不同的计算机的相同端口是没有联系的。
端口号的长度为16bit,能表示665536个不同的端口号。
熟知端口号
套接字(Socket)
在网络中采用发送方和接收方的套接字组合来识别端点,套接字唯一标识了网络中的一个主机上的一个进程。
5.3 UDP
用户数据报协议User Datagram Protocol
特点
UDP只在IP数据报服务之上增加了很少功能,即复用分用和差错检测。
UDP的主要特点:
-
- UDP是无连接的,减少开销和发送数据之前的时延。
- UDP使用最大努力交付,即不保证可靠传输。
- UDP是面向报文的,适合一次性传输少量数据的网络应用。
应用层给UDP多长的报文,UDP就照样发送,即一次发一个完整报文。
-
- UDP无拥塞控制,适合很多实时应用。
- UDP首部开销小,只有8字节,TCP却有20字节。
UDP报文格式
分用时,找不到对应的目的端口号,就丢弃报文,并发送给发送方ICMP“端口不可达”差错报告报文。
UDP校验
使用报文的伪首部进行UDP校验。
伪首部只有在计算校验和时才出现,不想下传送也不向上递交。
17:封装UDP报文的IP数据报首部协议字段是17;
UDP长度:UDP首部8B+数据部分长度(不包括伪首部)。
发送端:
- 添上伪首部
- 全0填充校验和字段
- 全0填充数据部分(UDP数据报要看成许多4B的串联起来)
- 伪首部+首部+数据部分采用二进制反码求和
- 把和求反码填入校验和字段
- 去掉伪首部,发送
接收端:
- 填上伪首部
- 伪首部+首部+数据部分采用二进制反码求和
- 结果全为1则无差错,否者丢弃数据报/交给应用层附上出差错的警告
二进制反码求和
反码算数运算:
两个数进行二进制反码求和的运算很简单。它的规则是从低位到高位逐列进行计算。0和0相加是0,0和1相加是1,1和1相加是0,但要产生一个进位1,加到下一列。如果最高位相加后产生进位,则最后得到的结果要加1。
注意事项:
1.反码运算时,其符号位与数值一起参加运算。
2.反码的符号位相加后,如果有进位出现,则要把它送回到最低位去相加(循环进位)。
3.用反码运算,其运算结果亦为反码。在转换为真值时,若符号位为0,数位不变;若符号位为1,应将结果求反才是其真值。
[例1] 已知X = + 1101 , Y = + 0110 , 用反码计算Z = X-Y。
解: [X]反 = 01101,[-Y]反 = 11001,则[Z]反 =[X]反+[-Y]反 = 01101+11001+1(循环进位)= 00111 , 其真值为Z = +0111。
[例2] 已知X = + 0110 , Y = + 1101 , 用反码计算Z = X-Y。
解: [X]反 = 00110,[-Y]反 = 10010,则[Z]反 =[X]反+[-Y]反 = 00110 + 10010
= 11000 , 其真值为Z = - 0111。
5.4 TCP
传输控制协议Transmission Control Protocol
特点
TCP是面向连接的,可靠的,基于字节流的传输层通信协议。
基于字节流:
很简单,TCP在建立连接时,需要告诉对方MSS(最大报文段大小)。
也就是说,如果要发送的数据很大,在TCP层是需要按照MSS来切割成一个个的TCP报文段的。
切割的时候不管原来的数据是什么意思,只是当做一串毫无意义的字节,在像切割的地方来一刀,再加上个序号,只要对方根据这个序号拼成最终想要的完整数据就行。
可靠传输
停止等待协议
OK,这样你将原本主机(端)到主机(端)的通信,升级为了进程和进程之间的通信。你没有意识到,你不知不觉实现了 UDP 协议!(当然 UDP 协议中不光有源端口和目标端口,还有数据包长度和校验值,我们暂且略过)就这样,你用 UDP 协议无忧无虑地同 B 进行着通信,一直没发生什么问题。
但是此时出现了丢包问题。
由于网络的不可靠,数据包可能在半路丢失,而 A 和 B 却无法察觉。
对于丢包问题,只要解决两个事就好了。
第一个,A 怎么知道包丢了?
答案:让 B 告诉 A
第二个,丢了的包怎么办?
答案:重传
于是你设计了如下方案,A 每发一个包,都必须收到来自 B 的确认(ACK),再发下一个,否则在一定时间内没有收到确认,就重传这个包。
你管它叫停止等待协议。只要按照这个协议来,虽然 A 无法保证 B 一定能收到包,但 A 能够确认 B 是否收到了包,收不到就重试,尽最大努力让这个通信过程变得可靠,于是你们现在的通信过程又有了一个新的特征,可靠交付。
流水线方式
停止等待虽然能解决问题,但是效率太低了,A 原本可以在发完第一个数据包之后立刻开始发第二个数据包,但由于停止等待协议,A 必须等数据包到达了 B ,且 B 的 ACK 包又回到了 A,才可以继续发第二个数据包,这效率慢得可不是一点两点。于是你对这个过程进行了改进,采用流水线的方式,不再傻傻地等。
累计应答
但是网路是复杂的、不可靠的。有的时候 A 发出去的数据包,分别走了不同的路由到达 B,可能无法保证和发送数据包时一样的顺序。
在流水线中有多个数据包和ACK包在乱序流动,他们之间对应关系就乱掉了。难道还回到停止等待协议?A 每收到一个包的确认(ACK)再发下一个包,那就根本不存在顺序问题。应该有更好的办法!A 在发送的数据包中增加一个序号(seq),同时 B 要在 ACK 包上增加一个确认号(ack),这样不但解决了停止等待协议的效率问题,也通过这样标序号的方式解决了顺序问题。
而 B 这个确认号意味深长:比如 B 发了一个确认号为 ack = 3,它不仅仅表示 A 发送的序号为 2 的包收到了,还表示 2 之前的数据包都收到了。这种方式叫累计确认或累计应答。
注意,实际上 ack 的号是收到的最后一个数据包的序号 seq + 1,也就是告诉对方下一个应该发的序号是多少。但图中为了便于理解,ack 就表示收到的那个序号,不必纠结。
流量控制
滑动窗口
有的时候,A 发送数据包的速度太快,而 B 的接收能力不够,但 B 却没有告知 A 这个情况。
怎么解决呢?
很简单,B 告诉 A 自己的接收能力,A 根据 B 的接收能力,相应控制自己的发送速率,就好了。
B 怎么告诉 A 呢?B 跟 A 说"我很强"这三个字么?
那肯定不行,得有一个严谨的规范。于是 B 决定,每次发送数据包给 A 时,顺带传过来一个值,叫窗口大小(win),这个值就表示 B 的接收能力。同理,每次 A 给 B 发包时也带上自己的窗口大小,表示 A 的接收能力。
B 告诉了 A 自己的窗口大小值,A 怎么利用它去做 A 这边发包的流量控制呢?很简单,假如 B 给 A 传过来的窗口大小 win = 5,那 A 根据这个值,把自己要发送的数据分成这么几类。
图片过于清晰,就不再文字解释了。当 A 不断发送数据包时,已发送的最后一个序号就往右移动,直到碰到了窗口的上边界,此时 A 就无法继续发包,达到了流量控制。
但是当 A 不断发包的同时,A 也会收到来自 B 的确认包,此时整个窗口会往右移动,因此上边界也往右移动,A 就能发更多的数据包了。
以上都是在窗口大小不变的情况下,而 B 在发给 A 的 ACK 包中,每一个都可以重新设置一个新的窗口大小,如果 A 收到了一个新的窗口大小值,A 会随之调整。如果 A 收到了比原窗口值更大的窗口大小,比如 win = 6,则 A 会直接将窗口上边界向右移动 1 个单位。
如果 A 收到了比原窗口值小的窗口大小,比如 win = 4,则 A 暂时不会改变窗口大小,更不会将窗口上边界向左移动,而是等着 ACK 的到来,不断将左边界向右移动,直到窗口大小值收缩到新大小为止。
OK,终于将流量控制问题解决得差不多了,你看着上面一个个小动图,给这个窗口起了一个更生动的名字,滑动窗口。
拥塞控制
但有的时候,不是 B 的接受能力不够,而是网络不太好,造成了网络拥塞。
拥塞控制与流量控制有些像,但流量控制是受 B 的接收能力影响,而拥塞控制是受网络环境的影响。拥塞控制的解决办法依然是通过设置一定的窗口大小,只不过,流量控制的窗口大小是 B 直接告诉 A 的,而拥塞控制的窗口大小按理说就应该是网络环境主动告诉 A。但网络环境怎么可能主动告诉 A 呢?只能 A 单方面通过试探,不断感知网络环境的好坏,进而确定自己的拥塞窗口的大小。
拥塞窗口大小的计算有很多复杂的算法,就不在本文中展开了,假如拥塞窗口的大小为 cwnd,上一部分流量控制的滑动窗口的大小为 rwnd,那么窗口的右边界受这两个值共同的影响,需要取它俩的最小值。窗口大小 = min(cwnd, rwnd)含义很容易理解,当 B 的接受能力比较差时,即使网络非常通畅,A 也需要根据 B 的接收能力限制自己的发送窗口。当网络环境比较差时,即使 B 有很强的接收能力,A 也要根据网络的拥塞情况来限制自己的发送窗口。正所谓受其短板的影响嘛~
三次握手,四次握手
有的时候,B 主机的相应进程还没有准备好或是挂掉了,A 就开始发送数据包,导致了浪费。
这个问题在于,A 在跟 B 通信之前,没有事先确认 B 是否已经准备好,就开始发了一连串的信息。就好比你和另一个人打电话,你还没有"喂"一下确认对方有没有在听,你就巴拉巴拉说了一堆。这个问题该怎么解决呢?地球人都知道,三次握手嘛!
A:我准备好了(SYN)
B:我知道了(ACK),我也准备好了(SYN)
A:我知道了(ACK)
A 与 B 各自在内存中维护着自己的状态变量,三次握手之后,双方的状态都变成了连接已建立(ESTABLISHED)。虽然就只是发了三次数据包,并且在各自的内存中维护了状态变量,但这么说总觉得太 low,你看这个过程相当于双方建立连接的过程,于是你灵机一动,就叫它面向连接吧。注意:这个连接是虚拟的,是由 A 和 B 这两个终端共同维护的,在网络中的设备根本就不知道连接这回事儿!但凡事有始就有终,有了建立连接的过程,就要考虑释放连接的过程,又是地球人都知道,四次挥手嘛!
A:再见,我要关闭了(FIN)
B:我知道了(ACK)
给 B 一段时间把自己的事情处理完...
B:再见,我要关闭了(FIN)
A:我知道了(ACK)