【大家好,我是爱干饭的猿,本文介绍了计算机网络UDP/TCP协议格式和特点、重点介绍保证TCP可靠性的 确认应答机制(数据编码+超时重传)、连接管理机制(TCP三次握手、四次挥手)、流量控制机制、拥塞控制机制、快重传、延时应答、捎带应答机制等。
后续会继续分享网络层IP协议及其他重要知识点总结,如果喜欢这篇文章,点个赞,关注一下吧】
上一篇文章:《【web】计算机网络编程(重点:UDP数据报/TCP流套接字编程)》
目录
1. UDP协议(User Datagram Protocol)
1.1 UDP协议数据报格式
1.2 UDP 发送接收数据过程
1. 发送端
2. 接收端
1.3 UDP 的特点
2. TCP协议(Transmission Control Protocol)
2.1 TCP协议段格式
2.2 TCP 的可靠性
2.3 TCP 做了哪些机制来保证可靠性
1. 确认应答机制(数据编码机制 + 超时重传机制)
2. 超时重传机制
3. 连接管理机制(三次握手 & 四次挥手)*重点*
4. 流量控制机制(滑动窗口机制)
5. 拥塞控制机制
6. 快重传机制(效率机制)
7. 延迟应答(效率机制)
8. 捎带应答(效率机制)
2.4 TCP 的特点
2.5 TCP的粘包问题
2.5 TCP异常情况
3. TCP/UDP对比
传输层重点协议TCP/UDP:负责数据能够从发送端传输接收端。
职责:在网络层的基础之上,实现进程to 进程的通信。
UDP协议的包头(header)信息:UDP添加的封装(解包、分用)
1. UDP 包头定长8字节:容易做解包
2. 源端口+目的端口:则用作分用 | 进程 to 进程
3. 16位UDP长度:表示整个数据报(UDP首部+UDP数据)的最大长度
4. UDP校验和(checksum):为防止传输数据错误,类似于hash算法
发送端:hash(有效数据) => 得到 h1
接收端:hash(有效数据) => 得到 h2
如果 h1 != h2,数据出错,丢弃数据
如果 h1 == h2,大概率认为数据还是源数据
1. 填充端口
2. 计算长度,填充包头UDP长度
3. 计算校验和checksum,填充校验和checksum
4. 立即将数据交给网络层
注意:UDP协议栈内部没有发送缓存区
1. 从网络层接收数据
2. 通过计算校验和,检查数据是否出错,如果数据出错,丢弃数据
3. 如果数据没有出错,根据自己内部维护的 Map
4. 看对应的进程是否准备好一块内存,如果暂时没有,需要在接收缓存区暂时保存一会
5. 把数据复制到对应的内存空间 byte[] buf
UDP传输的过程类似于寄信
1. 无连接
知道对端的IP和端口号就直接进行传输,不需要建立连接
2. 不可靠
没有任何安全机制,发送端发送数据报以后,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息
3. 面向数据报文
应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并
4. 缓冲区
UDP只有接收缓冲区,没有发送缓冲区
UDP的socket既能读,也能写,这个概念叫做 全双工
5. 大小受限
UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)
6. socket.send(); 方法返回了,代表什么意思?
代表数据已经发送到网络中,不知道对方主机或者进程是否收到。
职责:在网络层的基础之上,实现进程to 进程的通信。
TCP包头:用于解包,分用
1. 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
2. 32位序号/32位确认号:下面详细介绍
3. 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是 15 * 4 = 60
4. 6位标志位:
URG:紧急指针是否有效(配合16位紧急指针使用)
ACK:确认号是否有效
PSH:PSH=0允许TCP把收到的数据暂存一段时间,集齐一定数量再给应用层
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
SYN:(synchronize)请求建立连接;我们把携带SYN标识的称为同步报文段
FIN:通知对方,本端要关闭了(四次挥手),我们称携带FIN标识的为结束报文段
5. 16位窗口大小
6. 16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不光包含TCP首部,也包含TCP数据部分。
7. 16位紧急指针:标识哪部分数据是紧急数据;
8. 40字节头部选项:暂时忽略;
TCP 如何保证可靠性?
1. TCP 尽自己最大的努力,将数据发送给对方
2. 如果真遇到发送不过去的情况,TCP 至少会告诉发送进程,数据发送失败
3. 保证不会收到错误的数据(通过校验和checksum)
4. TCP 保证收到的数据一定是有序的
5. TCP 会根据对方的接收能力和网络线路的承载能力,进行流量控制
确认应答机制:接收方(对方的TCP)有责任对接收到的数据进行(acknowledge)应答。
如果同时收到多份数据,如何知道应答的是哪部分数据,所有有了数据编码机制
对数据进行编号:序列号(sequence number)SN
32位序列号:携带本次发送数据时的数据编码,每个字节占用一个序号
32位确认序列号:填写下一次期望收到的第一个字节的序号
序号:本次发送数据的第一个字节的序号
比如:发送数据为 [abcd] [efg],则第一次发送的序列号SN为1,第二次发送的序列号SN为5
确认段(segment):一份数据即可以起到发送数据的角色,也可以起到确认的角色
发送:(SN + 携带数据)携带有数据,填写正确的SN,就是发送segment
确认:(ack + ASN)标志位ack置1.代表起到了确认的作用。需要填写确认序列号(ASN)。
当我发送了一份数据,没有收到应答时,可能发生了什么?
可能情况:
1.对方没有收到数据
2.对方收到了,并且应答了,但应答没有发送过来
解决方案:真的遇到没有收到应答
1. 不应该无限期的等待下去(超时timeout机制)
2. 重新发送数据(超时重传机制)!!
主机B第一次收到数据,无影响
主机B收到过2次1~1000,所以主机B可以根据数据中的序号检查进行去重操作。
TCP的发送端不用关心超时没有收到应答的原因是什么,采用统一的超时重传机制即可。如果接收端真的收到了重复的数据,直接丢弃即可。
a. 超时重传次数是无限制的么?
会有一定的上限!! 达到上限,发送方(TCP)认为本次数据线路出现重大问题了
1.TCP关闭本次连接
2.TCP会通知进程(在Java 中,采用异常方式IOException)
3.TCP会发送一个reset segment出去(RST 置为1)
b. 关于超时时间的设置
一般这个时间不是一个固定的长度,大多数是逐步变长
10s ->20s -> 40s -> 80s
站在进程角度思考,向一个有问题的TCP线路中发送数据,请问多久之后,“我(进程)”知道线路有问题了10 +20 +40 + 80s之后,发现线路有问题
c. 并且只有发送方知道线路可能有问题
作为TCP的发送方,经过一段时间之后,是可以知道线路有问题的!
作为TCP的接收方,无法得到线路有问题的(无法确认对方是没有数据发送还是发送失败了)
a. 连接的概念
b. 为什么TCP要设计建立连接。
1. 必须确认对方存在,才能“可靠”地传输
2. 交换一些必要的数据SN不是直接从1开始的,会双方各自随机生成,随后需要交换
c. 建立连接(三次握手)
在正式通信之前,需要一个建立连接的阶段
1)确认对方在线
2) “同步(synchronize)”一些基本信息
【建立连接阶段】【正式通信】【断开连接阶段】
segment的种类:发送segment、确认segment、同步信息(握手阶段使用) segment、挥手segment
多个职责可以同时存在于一个segment 中
如图:2和3基本是同一时间发生的,TCP的segment可以既承载发送数据,又承载应答的职责
所有TCP建立连接需要三次握手,两次握手建立不了连接,四次握手降低性能。
简化:
c. 断开连接(四次挥手)
标志位是fin
1. 主动断开方不一定是主动连接方
2. 可以合并,但并不是必须合并
断开连接可能分三种情况:
A. 主动连接方 断开连接,被动连接方收到后也断开连接(三次挥手)
B. 主动连接方 断开连接,被动连接方收到后过了一段时间断开连接(四次挥手)
C. 主动连接方和 和 被动连接方同时断开连接 (同时挥手)
服务器上如果出现大量的CLOSE_WAIT状态
1. CLOSE_WAIT:出现在挥手阶段
2. CLOSE_WAIT:出现在被动关闭方这一层
3. CLOSE_WAIT:对方进程要求关闭连接,但我方进程还没要求关闭连接
进一步判断,通过检查我们的代码中是否漏写了socket.close()操作了
为什么要有一个状态TIME_WAIT,不能直接走到CLOSED状态么?
1. TIME_WAIT 出现在主动连接方
2. TCP协议内部怎么区分连接的?通过五元组信息
当一个连接关闭之后,如果之前释放,则五元组信息可能被再次使用。此时,当收到发给这条连接的数据时不知道数据是给老的还是新的
3. 防止最后一个ack 丢失,对方重新发送
为什么是TIME_WAIT的时间是2MSL?
MSL: Maximum Segment Live
1. MSL是TCP报文的最大生存时间,因此TIME_WAIT持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程的迟到的数据,但是这种数据很可能是错误的); ·
2. 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发一个FIN,这时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK) ;
TCP还提供了一种异常关闭连接的方式(了解)
收到了reset segment之后,TCP直接关闭连接。进程收到一个异常!
接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制(FlowControl)
1. 减少无用功
2. 根据对方的接收能力 or 线路的承载能力 控制发送数据的多少
3. 接收能力:接收窗口(receive window)在包头 16位窗口大小
TCP协议栈每次发送segment,都会携带当前的接收窗口大小!
发送方利用 滑动窗口机制 进行发生量的控制。
1.如何得知当前网络的承载能力——拥塞窗口
2. 发送量(发送窗口) = f(拥塞窗口,接收窗口) 取两者较小的
3. 同样按照滑动窗口机制进行控制
a. 如何得知当前网络的承载能力
采用一个算法推算出拥塞窗口
1. 慢开始 + 快增长,慢开始,一开始 cwud 很小 cwnd = 1,在增长阶段,成指数增长cwnd = cwnd * 2,到达一个阈值(yu)后,一次 cwnd + 2
2. 如果遇到网络堵塞,单位时间重传次数超过一定值,(像老鼠一样胆小),先会计算一个新的阈值(ssthresh) ssthresh = cwnd / 2,然后cwnd = 1。
TCP拥塞控制这样的过程,就类似 热恋的感觉(博主单身狗)
当某一段报文段丢失之后,发送端会一直收到 1001 这样的ACK,就像是在提醒发送端 "我想 要的是 1001" 一样;
如果发送端主机连续三次收到了同样一个 "1001" 这样的应答,就会将对应的数据 1001 - 2000 重新发送;
这个时候接收端收到了 1001 之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端 其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中;
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。所以如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;提升效率。
那么所有的包都可以延迟应答么?
肯定也不是;
数量限制:每隔N个包就应答一次;
时间限制:超过最大延迟时间就应答一次;
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的。意味着客户 端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you";
那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine,thank you" 一起回给客户端
1. 有链接
2. 可靠性
3. 面向字节流
4. 有接收缓冲区,有发送缓冲区
5. 大小不受限
首先要明确,粘包问题中的 "包" ,是指的应用层的数据包。 在TCP的协议头中,没有如同UDP一样的 "报文长度" 这样的字段,但是有一个序号这样的字 段。 站在传输层的角度,TCP是一个一个报文过来的。按照序号排好序放在缓冲区中。 站在应用层的角度,看到的只是一串连续的字节数据。 那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个 完整的应用层数据包。
那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界。
对于定长的包,保证每次都按固定大小读取即可;
对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位 置;
对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序猿自己来定 的,只要保证分隔符不和正文冲突即可);
思考:对于UDP协议来说,是否也存在 "粘包问题" 呢?
对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在。同时,UDP是一个一个把数据交付给应用层。就有很明确的数据边界。
站在应用层的站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收。 不会出现"半个"的情况。
所以UDP 没有粘包问题。
A.直接使用任务管理器关闭一个进程,请问被这个进程持有的连接会怎么办?
虽然进程不会执行关闭操作,但OS会执行四次挥手的正常关闭
B.点击重启,运行着的进程管理的连接会怎么办?
OS会执行四次挥手的正常关闭点击关机,同样道理
C.断电关闭电脑,进程持有的连接会怎么办?
什么都不会发生。 我们这侧的内存中的数据没有了,所以连接从我们这侧,认为已经不存在了(没有走任何关闭流程)连接的另一侧,不知道发生了什么。
D. 对方怎么就能知道连接出问题了?
当对方有写操作时,由于多次重试失败,会发现问题(但区分不了是对端的问题还是线路的问题)这个感受不是立即的 当对方只有读操作时,永远发现不了出问题了。
E.我们应该如何进下操作,才能必然永远维护着“死连接”?
1. 会给读增加一个超时时间(1天)
2. 定期给对方发消息,报平安(heartbeat 心跳包)
TCP/UDP的socket都既能读,也能写,这个概念叫做 全双工
我们说了TCP是可靠连接,那么是不是TCP一定就优于UDP呢?
TCP和UDP之间的优点和缺点,不能简单,绝对的进行比较TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;
UDP用于对高速传输和实时性要求较高的通信领域,例如,早期的QQ,视频传输等。另外UDP可以用于广播;
TCP:一对一
UDP:一对多
分享到此,感谢大家观看!!!
如果你喜欢这篇文章,请点赞加关注吧,或者如果你对文章有什么困惑,可以私信我。