TCP协议
1.确认应答 实现可靠传输的核心机制
2.超时重传 实现可靠传输的核心机制
3.连接管理 网络部分最高频的面试题
4.滑动窗口 提高传输效率的机制
5.流量控制 依据接收方的处理能力,限制发送方的发送速度。
6.拥塞控制 依据传输链路的处理能力,限制发送方的发送速度。
做实验
1.先从比较小的窗口开始 慢启动
2.指数增长
3.达到阈值,线性增长
4.遇到丢包,重新设定阈值,把窗口大小设为阈值的大小,继续线性增长
TCP核心机制
核心机制七:延时应答
为了提高传输效率。
承接滑动窗口,让传输效率提高一些。让窗口尽量大一些(在可靠性的前提下)
不立即返回 ack,而是稍微等一等。接收方应用程序,就是消费者。 为了给接收方留出一些时间 好能够多消费一些,接收缓冲区的剩余空间更大一点。
核心机制八 捎带应答
网络通信中,经常是“一问一答”的模型
客户端发起request,服务器返回response (不是ack.....)
核心机制九:面向字节流
读取/写入的时候,读写操作有很多种方式,非常灵活。
读100字节,
1. 一次读10字节,10次完成
2. 一次读20字节,5次完成
3. 一次读50字节,2次完成 ……
因此引起了粘包问题
接收方用的时候 去掉报头,把载荷内容放到一个 接收缓冲区中
接收方的应用程序,read的时候,就有很多种read的可能性
read的可能性:
1) a a a b b b c c c
2) aa ab bb cc c
3) aaa bbb ccc
4) aaab bbcc c
5) aa abb bcc c ......
取决于应用程序的代码是咋写的
具体怎样来读,才能确保读到的是一个“完整的应用层数据包”?
粘包,粘的是应用层的数据包。
TCP字节流的特性,收到多个TCP数据报的时候把所有的载荷都给混到一起 放到接收缓冲区里
包的边界比较模糊,就好像“粘上了”一样
解决粘包,从应用层入手,合理的设计应用层协议,让包之间的边界,能够比较清晰
1. 通过特殊的分隔符,来作为包边界的区分
比如,约定,每个应用层数据包,都以;为结尾
包的数据里面,不能包含分隔符
需要找合适的符号,确保这个符号在正文中不会重复出现
再比如,之前写的回显服务器当时是使用 \n 作为分隔符的(空白符)
读到空白符就结束了。 空白符是统称,包括不限于: 空格,换行,回车,制表符,分页符,垂直制表符……
发请求的时候,使用 \n 作为结束标记的
由于是通过控制台来进行输入请求内容的
控制台里输入的内容本来就不会包含 \n
即使ascii码表中,也有不少的字符,可以用来作为分隔符
有一些特殊的“不可见字符” 历史遗留
2. 在应用层数据包开头的地方,通过固定长度,约定整个应用层数据包的长度
应用程序read的时候,先固定read 2字节,看看2字节里的内容是啥 => 发现是3
接下来再read 3字节,读到的aaa就是完整的应用层数据包
粘包问题只针对字节流的传输。对于文件操作,(使用文件存储多个结构化数据...也是可能涉及到粘包的)
比如,通过文件,保存若干个学生信息。
class Student { name id age classId ... }
比如约定成,每个学生信息占一行(使用 \n 作为结束标记,作为分隔符)
对于UDP来说,不存在粘包问题,UDP的接收缓冲区和TCP的不太一样
应用层不需要做区分
应用程序每次调用 recv 得到但就是一个完整的 DatagramPacket
也就对应一个完整的应用层数据包了。
未来实际开发中,很多时候是基于一些现成的框架/库进行开发的
(很可能粘包问题已经被框架/库帮我们解决了)
知名的框架和库,都是“通用的”
核心机制十 异常情况
1. 进程崩溃 [正常的流程]
进程崩溃,意味着对应的文件描述符就被关闭了(调用close,干掉进程)。
只要是进程退出,都会释放PCB,释放文件描述符表
TCP的连接并没有因为进程的结束立即结束,保留一会
触发FIN
2. 主机关机 (正常流程)
正常流程下的主机关机,就会先杀死所有的进程
此时也会触发FIN,进而进入四次挥手
有可能挥不完
如果关机的速度比较慢,有很大可能四次挥手挥完了
如果关机的速度比较快,刚发FIN,机器就关了
对端可以正常返回ack,也会继续正常发送FIN,这里的FIN就没有收到ack,尝试重传几次FIN. 还是没有ack,对端就直接放弃连接(删除之前保存的对端信息)
四次挥手,如果挥完了, 双方可以确认,双方都能顺利把保存的信息删掉, 如果挥不完,至少自己可以把保存的信息删掉, 对端就不管了 此时对端关机了,内存的数据全没了
3. 主机掉电(直接拔电源)
直接啥都没了
来不及发起FIN 台式机
1) 如果掉电的一方是接收方,对方是发送方。
对方继续发送数据,没有ack=> 超时重传 => 仍然没有ack => 继续超时重传
达到一定程度,掉电方仍然没有ack,发送方发送一个复位报文,就是表示要重置连接
放弃当前的连接
2) 如果掉电的一方是发送方,对方是接收方。
接收方感受到的是,发送方,突然停下了
接收方会继续阻塞等待,等待发送方发来新的数据
心跳包。
接收方会周期性的和发送方交换“心跳包”
A给B发一个无业务数据的报文
B给A返回一个ack 如果对方有应答,就可以认为对方是正常工作的。
如果心跳包也没有应答,就可以认为,对方挂了
如果发现心跳包没有,就可以单方面的释放连接了
这个机制非常重要,尤其是分布式系统中
分布式系统中,知道某个节点存活,非常重要的
4. 网线断开
本质上和主机掉电是一样的.
网线断开的两端,一个是发送方,一个是接收方 对于发送方来说,没有ack => 超时重传 => 仍然没有ack => 超时重传 => 达到一定程度,放弃连接
对于接收方来说,周期性触发心跳包 => 发现对端下线 => 放弃连接
1. 确认应答 2. 超时重传 3. 连接管理(三次握手,四次挥手) 4. 滑动窗口(快速重传) 5. 流量控制 6. 拥塞控制 7. 延时应答 8. 捎带应答 9. 面向字节流(粘包问题) 10. 异常情况(心跳包)
TCP有连接,可靠传输,面向字节流
还有一系列的机制……十个机制
大部分情况优先考虑TCP
UDP无连接,不可靠传输,面向数据报 UDP传输效率高
机房内部的传输,不太会丢包,效率要求更高 还有的场景,既需要可靠性(不需要那么严格的可靠性),又需要效率(比TCP更高)
用UDP实现可靠传输(经典面试)
其实是在考察 TCP 基于UDP,在应用层,自己写代码,实现可靠传输.... 参考TCP的做法。
1) 确认应答 序号/确认序号 2) 超时重传 3) 滑动窗口 4) 流量控制,拥塞控制 ……