UDP协议
- 报文中应该包含
- 源IP,源端口号
- 目的IP,目的端口号
- UDP/TCP
- 一个进程是否可以绑定多个端口号? 可以。
- 多个进程是否可以绑定一个端口号? 不可以,因为端口号的主要作用是唯一标识一台计算机上的一个特定服务或应用程序,以便网络通信能够准确无误地发送到正确的接收者。
UDP的格式
- 16位UDP长度:就是数据的大小
- 16位UDP检验和:用于检验报文的完整性
UDP如何解包和分用?
- 解包:前八个字节是固定格式,然后根据UDP长度读取数据就可以了。
- 分用:有目的端口号,知道交付给上层的哪个进程。
UDP的缓冲区
- UDP有接收缓冲区,但没有真正意义上的发送缓冲区–>因为UDP是无连接,不可靠的,面相数据报的,不需要保证发送成功,直接交付给下一层就可以
TCP协议
传输控制协议
- 应用层将数据拷贝给了OS,未来数据的发送都有OS控制
协议格式
- 4位首部长度:表示的是报头+选项的大小;基本单位是4字节,占4比特,能表示的大小为[0,15],表示的意思为4*[0,15] = [0,60];报头是固定的格式,大小为20字节,所以实际表示的大小为[20,60].
如何解包和分用?
- 解包:固定格式,读取出报头,根据4位首部长度得到选项,剩下的就是数据了
- 分用:16位目的端口号
确认应答
- 保证TCP传输可靠性的方式之一。
- 如何保证消息被对方收到?
收到应答 - 如何保证应答被收到呢?
那就需要给对方应答,如何保证自己的应答被收到呢?这就套娃了。 - 长距离的通信没有100%的可靠性,因为总有最新的一条消息没有应答。这句话的意思也就是说老消息是有应答的。
序号和确认序号
- 确认序号 = 序号+1;表示的意义是确认序号之前的内容都已经被全部收到。
- 序号的作用:
- 对报文进行区分,可以对报文进行去重
- 报文可能是乱序的,保证报文的顺序到达
根据上面的信息只需要一个序号就可以了,为什么要有两个序号?
捎带应答机制
- 假设客户端给服务器发送数据,此时服务器接收到请求并且想要给服务器发送数据。此时如果有两个序号就可以用一个报文来完成应答和发送数据,否则就需要发送两次,应答用的是确认序号,发送数据用的是序号。
- TCP报文在很大概率上即是应答,又是数据。
16位窗口大小
- 16位窗口大小表示的是,接受缓冲区剩余空间大小。
- 万一对方来不及接收?
丢弃吗?如果丢弃就需要重新发送一份,浪费时间。并且OS不做浪费时间和空间的事,所以不是丢弃,那么怎么办呢? - 凭什么知道对方来不及接收?
根据报文中的16为窗口大小,也就可以知道对方接收缓冲区剩余空间大小来进行流量控制。 - 流量控制双方都要进行。
- 控制 != 减少;既可以是减少,也可以是增加。
面相字节流
- 缓冲区就是一个char outbuffer[N],也就是一个字符数组。
- 读写文件就是面相字节流的。
- 保证数据的完整性,需要对数据在应用层定协议,比如http。
超时重传机制
- 发送数据,在一定的时间间隔内没有收到应答,那么就认为报文丢失,就需要对数据进行重传。
- 报文丢失分为两类:1.数据丢 2.应答丢
数据丢
应答丢
- OS如何做到超时重传?
在系统方面,有时钟中断等可以来实现。
时间间隔不能太长,不能太短,更不能固定。怎么设置的?
- 以500ms为单位,指数增长。
连接管理
建立连接:三次握手
- 以客户端发起建立连接的请求为例。
客户端向服务器发送建立连接的报文,服务器应答并且服务器向客户端发送建立连接的报文,最后就是客户端应答。 - 三次握手也可以是四次握手,只不过是有捎带应答机制,将应答和数据用了同一个报文。
断开连接:四次挥手
- 断开连接需要双方同意
- 客户端发送断开连接的报文,服务器应答;服务器发送断开连接的报文,客户端应答。
connect 和 accept
- connect只是触发三次握手;accept并不参与三次握手,只是等待三次握手的结束。
为什么要有三次握手和四次挥手?
- 三次握手
- TCP是全双工的,必须保证双方通信的意愿,也就是必须双方同意。
- 以最小的代价来验证双方通信信道是通畅的。—>网络是通畅的。
- 四次挥手
TCP是全双工的,断开连接需要双方同意。
为什么要有标志位?
报文是由类型的,所以标志位用来区分报文的类型。
- ACK:应答报文,需要关心确认序号。
有捎带应答机制,所以大部分的TCP报文的ACK为1 - SYN:同步报文,建立连接
- FIN:断开连接
理解连接
- 一条连接和一个文件对应(也就是一个文件描述符fd)
- 在OS内有多个连接–>OS要对其进行管理(先描述,再组织)–>维护连接是有成本的(时间+空间)
断开连接
- close是直接关闭读写端
- shutdown可以选择性关闭读端,写端或读写端
理解TIME_WAIT
- 主动发送断开连接的一方,发送最后一次挥手后,等待两个MSL(Maximum Segment Lifetime,报文段最大生存时间)时间。
- 为什么等待两个MSL?
1.等待历史的游离报文在网络中消散
2.确保ACK被对方收到
确保所有报文段都被处理:在TIME_WAIT状态下,TCP连接可以确保所有先前发送的报
文段都被对端正确接收和处理,包括最后的ACK报文段。
允许延迟的重复报文段被丢弃:如果网络中存在延迟的重复报文段(例如,由于网络拥塞
或路由问题),这些报文段可能会在连接关闭后到达。通过等待两个MSL时间,TIME_WAIT
状态可以确保这些延迟的报文段被丢弃,而不是被错误地处理为新的连接的一部分。
- TIME_WAIT时,bind失败。
为什么? 连接关闭了吗?没关闭,端口正在被使用–>端口冲突
解决办法(这个问题在HTTP时遇到过,现在讲的是原理)
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR,&reuse, sizeof(reuse));
流量控制
- 如何实现?
通过16为窗口大小,告知对方自己接收缓冲区的剩余空间大小(不准确)
- 16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么?
实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口字段的值左移 M 位
窗口探测和窗口更新
- 如果接收缓冲区为0,怎么办?
通过窗口探测和窗口更新,来了解对方缓冲区是否有剩余空间
- 窗口探测就是一个裸的TCP报头
- 首次发送如何知道对方的接收能力?
三次握手是做过报文交换和窗口协商的,所以知道对方的接收能力。
滑动窗口
- 进一步理解滑动窗口
滑动窗口只需要用start和end控制就可以了
滑动窗口的大小 = 对方接收窗口的大小 (不准确)
- 串行发送,效率低
- 一次发多条数据(其实是将多个段的等待时间重叠在一起了)
- 为什么不能直接将这些数据当成一个大报文发送呢?而是一段一段呢?
数据链路层不允许发送大的报文。
问题
- 如何滑动?start,end+移动加法
start = 确认序号
end = start + 对方窗口大小
确认序号,对方窗口大小都可以从对方发来的报文的报头中获取。
流量控制是如何实现的? 通过滑动窗口 - 只能向右移动,能向左移动吗?
不能向左移动,因为左面的数据都是已发送的数据。 - 滑动窗口可以变大,变小,不变,为0吗?
可以 - 超过发送缓冲区,会导致越界吗?
可以将发送缓冲区当成一个环形数组。 - 异常丢包问题
滑动窗口的最左侧丢包
最左侧丢包滑动窗口不会移动。滑动窗口的中间丢包
滑动窗口右移,就变成了最左侧丢包问题滑动窗口的最右侧丢包
滑动窗口右移,就变成了最左侧丢包问题本质都是最左侧丢包问题。
快重传
- 重传的两种情况:
- 数据到达,但应答丢包,部分 ACK 丢了并不要紧, 因为可以通过后续的 ACK 进行确认;
- 数据丢包
- 收到三次重复的确认应答,就会对数据进行补发。
- 为什么还要有超时重传?
如果发送的报文数少于三个或每次的最后几个报文不能使用快重传,超时重传是用来兜底的,快重传提高了效率。
PSH和RST
- 对方接收缓冲区剩余空间大小为0,并且也迟迟不将缓冲区中的数据交付给上层
PSH(push):告诉对方,请尽量将缓冲区的数据交给层—>侧面告诉OS数据很重要,请尽快交付 - RST(reset):链接重置
这是因为服务器没有与客户端建立连接,服务器知道建立连接错误了,所以就会向客户端发起带有RST为1的报文,重新进行三次握手建立连接。 - URG:紧急报文
情景:TCP是有序的,一些需要紧急处理的信息怎么办?
16位紧急指针:偏移量(紧急数据的位置),那么数据的大小呢?数据就是固定的1字节
- 识别并处理
- 总结以上的话题都是就端对端来讲的,以下的部分是网络方面
拥塞控制
- 大量数据包丢失–>判定当前网络拥塞–>全局层面,不止你网络拥塞,大家都网络拥塞。
- 解决办法:重传?不可以,大家都重传,那么只会加剧网络拥塞。
慢启动
拥塞窗口
- 拥塞窗口也可以控制发送的数据量。
- 发送数据量不是由滑动窗口控制吗?
发送的数据量由对方的接收能力和网络共同决定。
滑动窗口 = min(对端缓冲区剩余空间大小,拥塞窗口大小)
- 为什么是指数增长?
前期慢,但增长快(尽快让网络通信恢复)。 - 慢启动阈值 = 上次拥塞窗口大小/2
- 在阈值之后,拥塞窗口的大小是加法增大。
- 拥塞窗口为什么要一直增大?
评估网络健康情况,网络健康,拥塞等情况是一直变化的,所以需要一直增大,来评估网络的情况。
延迟应答
- 策略:
数量限制: 每隔 N 个包就应答一次;
时间限制: 超过最大延迟时间就应答一次;
- 三次握手,交换双方窗口的起始序号–>支持随机—>这样就可以一定程度上防止游离报文
粘包问题
- TCP是基于字节流的,读取应用层报文时,没有读取到完整的报文。
- 解决办法: 在应用层明确报文的边界
TCP异常
- 进程终止:文件(sockfd)生命周期随进程,会在进程终止前释放文件描述符,双方进行四次挥手
- 机器重启:重启前,关闭所有的启动的进程,之后和进程终止一样。
- 掉电/掉网: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已
经不在了, 就会进行 reset. 即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.
另外, 应用层的某些协议, 也有一些这样的检测机制. 例如 HTTP 长连接中, 也会定期检测对方的状态. 例如 QQ, 在 QQ 断线之后, 也会定期尝试重新连接.
连接保活