目录
1、UDP协议
1.1、UDP报头
2、TCP协议
2.1、tcp协议段格式
2.2、TCP三次握手的过程
2.3、TCP四次挥手的过程
2.4、流量控制
2.5、滑动窗口
2.6、延迟应答
2.7、拥塞控制
2.8、面向字节流
2.9、数据粘包
2.10、TCP连接异常问题
1、UDP协议
学习目标:
a.1、报文和有效载荷分离 2、有效载荷应该交付给上层哪一个协议(对应的协议字段)
b.认识报头
1.1、UDP报头
UDP协议的报头是固定大小。
所以解包也很简单: 提取前8个字节的数据, 解析16位UDP长度. 拿到长度后截断整个报文数据!
UDP是16位的,那么UDP可发送的单个数据长度是2^16,即64kb。如果要发送的数据报超过64kb了,就需要应用层将数据包拆成64kb大小的进行发送
UDP传输过程类似于寄信:
无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;面向数据报: 不能够灵活的控制读写数据的次数和数量
UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后 续的传输动作;UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果 缓冲区满了, 再到达的UDP数据就会被丢弃;UDP的socket既能读, 也能写,是全双工的。
2、TCP协议
为什么说tcp是传输控制协议??
用户缓冲区的数据通过write、read、recv、send(这些函数其实是拷贝函数) 这些接口把数据其实本质不是发送到网络中的,而是写到内核的tcp的发送缓冲区。数据什么时候发,发送多少,出错了怎么办?这些问题都是由TCP自主决定的。 当客户端要把数据发送到服务器,就是从c的发送缓冲区发送到s的接收缓冲区。
系统调用recv,read并不是从网络从将数据读取到内存, 而是将接受缓冲区的数据读取上来. 同理, send和write函数也是讲数据写入发送缓冲区, 而不是直接写入网络
2.1、tcp协议段格式
其实每一种数据在协议的不同层都有不同的名称。应用层把数据叫作请求和相应、传输层叫作数据段、网络层叫作数据报、数据链路层叫作数据帧。
1、报头和有效载荷如何分离??
标准报头固定长度是20, 4位首部长度(4位的基本的大小单位是:4字节)是标准首部长度+选项
即[0,60] 所以,60-标准报头就是选项大小,有就读,没有就忽略。 剩余就是有效载荷
2、TCP凭什么保证可靠性,最基本的一个特点:确认应答机制
3、16位窗口大小:可以知道对方的接收能力的大小。有很好的流量控制的能力
因为数据的发送和应答是完整的报文,通过这个字段就可以知道
发包的一方需要填写窗口大小, 而收包的一方需要解析对端的窗口大小.
4、client和server基于tcp协议进行通信的时候,互发消息的时候,发送的是完整的tcp报文!即一定携带完整的tcp报头!
5、数据可以批量的发送,也可以批量的应答。数据发送的顺序,不一定是服务器接收到的顺序。造成乱序,本身就是不可靠的一种。在报头中 32位序号就可以保证发送数据的顺序性。---- 保证数据的按序到达
TCP将发送缓冲区的每个字节的数据都进行了编号. 即为序列号(将发送缓冲区想象为字符串,编号就是字符串下标).
比如主机A给主机B发送了1000字节的数据, 那么这个TCP包中的序号就为1000. 当B主机收到TCP包后, 会给A主机发送确认应答, 并且会将确认序号设置为1001, 代表1001以前的数据我都收到了. 可以从1001个字节开始给我发数据了
6、32位的确认序号:填充的是它收到报文的序号+1 确认序号的意义:表示确认序号之前的数据我已经全部收到了!下一次发送请从确认序号指定的数字开始发送!
为什么协议里面要把32位序号和32位确认序号分开,不复用呢?
因为一个应答可能是捎带应答,即可能会携带数据。当携带数据的时候就需要序号,作为应答就需要确认序号,所以两个要分开。
7、TCP协议中的6个标志位:
因为TCP进行通信的时候,要建立连接、数据通信、断开连接等请求。也就是说收到的TCP报文本身就是有类型的。不同类型的报文决定了s要做的动作。 那么接收方如何得知报头的类型是什么呢? 6个标记位-----区分tcp报文的类型!
ACK:确认号是否有效 确认报文是有应答属性的。 默认被置1
当接收方成功收到数据包后,会发送一个 ACK 包 给发送方,告知对方“数据已收到”。ACK 包中会携带一个 确认号,表示接收方期望收到的下一个字节的序列号(即已成功接收的最后一个字节的序列号 + 1)。
SYN:表示这个客户端请求建立连接;把携带SYN标识的称为同步报文段。
FIN:想和对方断开连接
PSH:设置为1时,提示接收端应用程序立刻从TCP缓冲区把数据读走。
RST:对方要重新建立连接;我们把携带RST的标识称为复位报文段。
客户端在三次握手中,认为只要把三次握手中第三次报文发出,就认为连接建立好了。万一第三次报文c已经发出了,可是s并没有收到,这时c就认为连接已经建立成功了,可是s却认为连接,没有建立成功。这时c向s发送一次数据,s就会向c发送携带RST字段的报文,告诉c连接建立失败,要重新建立连接。
URG:紧急指针是否有效 URG设置为1,16位紧急指针有效,代表这个报文里面携带紧急数据,代表当前必须进行高优先级处理。 默认紧急数据携带1个字节。
8、对确认应答机制的理解:
确认应答机制只能保证历史发的数据都被接收了, 但是最新的数据对方是否接收了是未知数. 比如B端向A端发送ACK应答后, B端怎么知道A端有没有收到这条消息?
答案是B端是不知道的!
超时重传:主机对与发出去的报文是否丢失,无法判定!必须通过规定,表征是否重传。 如果超过特定的时间主机a向主机b再次发送相同的报文,那么此时主机b收到重复报文就是不可靠的。所以,我们要进行去重。 因为报文有序号,我们可以利用序号进行去重。
超时时间如何设置?? 是动态的,和网络状况相关。
2.2、TCP三次握手的过程
要明确一点, 三次握手的目的是为了建立连接. 在TCP报头中的六位标记位中, 有一个标记位是SYN. 它代表请求建立连接, TCP三次握手离不开SYN标记位
TCP三次握手的过程可以总结为: 客户端向服务器发送一个包含SYN标记位的报文. 服务器收到此报文后, 向客户端发送一个包含了ACK和SYN标记的报文, 标识着服务器收到了客户端的SYN, 并且服务器也想和客户端建立连接. 客户端收到此报文后, 会再给服务端发送一个ACK, 一旦发送此ACK后, 客户端就已经建立好了连接. 同理, 服务器收到此ACK后也就建立好了连接
三次握手其实也可以看作是“四次握手”,因为SYN+ACK其实是捎带应答。
客户端向服务端发起连接请求,调用connect函数,实际上是要求服务器构建一个SYN报文发给服务端。连接建立成功connect会返回。connect函数只负责发起连接,双方三次握手是系统自主完成的。三次握手成功之后,connect才会返回。accept是获取连接函数,本身并不参与三次握手,他只是将建立好的连接拿上来,连接没建立好就会一直阻塞住。 客户端和服务端的ESTABUSHED的建立时间是有一定时间差的。
为什么要进行三次握手???
1、三次握手可以可靠的验证全双工通路是否通畅。三次可以确保客户端和服务端都进行了一次收一次发。那么两次可不可以? 答案是:不行! 服务器进行发送之后,如果没有收到客户端再次发送的报文,他就不能确定自己的发送是否正常。因此,必须是三次!
2、奇数次握手,可以确保一般情况握手失败的连接成本是嫁接在client端的。因为如果是一次握手,客户端就会一直向服务器发送连接,就会造成“SYN洪水”; 如果是两次握手,c向s发送,s给c恢复,因为最后一次握手是服务器发送的,如果这次握手失败,就会将连接的成本挂到服务器上,由因为客户端和服务器是一对多的,这样就会造成s的负担。 也就是说一次两次有明显的硬伤。 三次握手,最后一次是由c发的,就算丢失,也是在c上,因此可以很好的避免给服务器造成的负担,可以保证服务器的稳定性。
其实三次握手是验证全双工的最小次数,验证太多次反而会增加成本。
s上会有很多c建立的连接,当还没有accept的时候就会有很多,所以OS要使用队列的方式来管理底层已经建立好的连接。listen的第二个参数 backlog+1 表示底层已经建立好的连接队列的最大长度 这个队列叫作全连接队列。
server端,不会长时间维护syn_recv,被建立连接的一方,处于syn_recv,半连接不会长时间维护。 进入全连接的前提是进入半连接。 如果半连接队列里面有大量的恶意请求,就会将半连接队列占满,如果有真正的连接想访问服务器,就无法进入。 长连接队列不能太长??? 因为如果这个队列太长,队列里面就会存在有些连接来不及被上层处理,但是仍要被维持。 因为如果服务器很忙,没有时间从下层拿连接,但是因为你把这个队列设置的很长,还要把连接放在队列里,而这些放在队列里的连接,并不会创造任何价值,只会占用资源。
2.3、TCP四次挥手的过程
四次挥手的目的是为了断开连接. 在TCP的六位标记位中有一个叫FIN的标记位, 它代表要和对端断开连接. 四次挥手离不开这个标记位
建立连接一方主动,双方就维持资源,断开连接是需要双方同意的。 断开连接:没有数据给对方发了,发送数据时双方都可能发,所以必须断开两次。
客户端会先给服务器发送一个带有FIN标志位的报文, 服务器收到此请求后会给客户端返回一个ACK, 并且会将没发完的数据发给客户端. 发完后会给客户端发送FIN,标识我的数据已经给你发完, 可以关闭连接了. 此时客户端收到此消息后给服务器发送一个ACK就断开连接了.
主动断开的一方,在四次挥手完成之后,要进入time_wait状态,等待若干时常之后会自动释放。
如果主动断开连接的是s方,那么s方就要进入time_wait状态,连接没有被彻底断开,也就是ip和port正在被使用,如果想重新
为什么进行等待(TIME_WAIT)???
1、让通信双方历史数据得以消散
2、让我们断开连接,4次挥手,具有较好的容错性。
对TIME_WAIT状态的理解
为什么是TIME_WAIT的时间是2MSL?
若C端发送FIN时, s端恰好也想和c端断开连接, 那么s端可以把FIN和ACK一起发送给c端. 所以四次挥手在某种情况下也是三次挥手
2.4、流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应. 因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制
第一次发送数据的时候,怎么保证发送数据量是合理的???
不要理解三次握手只是三次握手,双方也交换了报文!!! 已经协商了双方的接受能力。
第三次其实可以携带数据(捎带应答)
流量控制直接提高了可靠性,间接也提高了效率
2.5、滑动窗口
为了提高发送的效率,可以一次发送多条数据来提高效率。 已经发出去但是暂时没有收到应答的报文,要被tcp暂时保存起来。 而且这样的数据可能会在发送发存在多个, 那么已经发出去,但是暂时没有收到应答的多个报文,会被保存在哪里?
问题:
1、滑动窗口在哪里??
滑动窗口其实是发送缓冲区的一部分。
2、滑动窗口的范围大小是多少??
目前理解为 至少滑动窗口的大小,不能超过对方的接受缓冲区的剩余空间的大小,即应答报文的窗口大小。(即对方的接受能力)3、如何理解区域划分???
通过指针/下标来进行区分即可 窗口的滑动其实就是指针的右移
因为有滑动窗口区域,我们才可以一次向对方一次发送大量的tcp报文!
发送缓冲区其实可以被分为:已发送已确认、已发送未确认、待发送
已发送已确认就是可以被覆盖,即可以从tcp缓冲区中移除。
已发送未确认:这部分区域叫作”滑动窗口“可以发/已经发 但未收到应答的区域。如果有报文已经收到了应答,就将已经收到的纳入前一个区域,这些数据就可以被清理掉了。
问题:
1、如果丢包了怎么理解滑动窗口??
ACK丢失(应答丢失):如果滑动窗口中间的ACK丢失了,我们只收到了前面的和后面的数据的ACK,我们不怕,因为确认序号是只它之前的报文我们都收到了。 如果是最后一个ACK丢失也不怕,因为可以补发。
数据丢失:如果中间的数据丢失了,前面和后面的数据收到了。比如2 丢失了1、 3 和 4 收到了 3返回的ACK不能是4001,必须返回2001,不能跳过2.也就是说不会跳过。 所以不用怕数据丢失。
确认序号的保证了滑动窗口,线性的连续向后更新,不会出现跳跃的情况。
快重传:
当1001到2000的数据丢了,之后发送的数据收到的应答都是1001,如果主机A收到3个同样的确认应答时,就需要重新发送1001到2000, 发完之后下一个应答直接是7001
那么,已经有了快重传,为什么还要有超时重传??
因为快重传是有条件的。它是为了提到效率。 超时重传是给我们兜底的。 有快重传就用快重传,没有触发快重传就等超时,用超时重传。
2、滑动窗口可以向左滑动吗?移动的时候大小会变吗?怎么变化?
不能向左移动。 滑动窗口的大小是动态变化的(取决于对方的接受能力)。变小、变大、不变。变小:左移动,右不动 变大:左右都移动,范围扩大 (也可以范围缩小)
int start = 确认序号
int end = 确认序号 + min(win大小,有效数据,拥塞窗口)
所以,流量控制是怎么实现的?? 通过滑动窗口实现的!
3、滑动窗口,会在发送缓冲区中越界吗?
tcp采用了类似于环状的算法。 基于数据的一个环形缓冲区。 不用担心溢出的问题。
2.6、延迟应答
发送方一次发送更多的数据,发送的效率就越高。然而,发送方一次发送更多的数据,取决于对方告诉我他能接受更多的数据。 如果接受方,给发送方通告一个更大的窗口大小,就会发送更多的数据。 那么,如何让接受方向发送发通告一个更大的窗口呢??? 收到一条报文,不着急给对方应答。给上层充分的时间,来取走数据。 再给应答,就会更大。
在写代码时,要尽快的通过read、recv把数据从内核中拿上来。 因为只有这样,才能给对方应答(我有更大的空间) 对方给我发更多的数据,通信效率就会变高。
假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
延迟应答是为了提高效率
TCP小节:
可靠性:
校验和:保证数据正确性
序列号(按序到达):保证按序到达,也可以去重。
确认应答:可以保证局部的可靠性。
超时重发
连接管理:三次握手
流量控制、拥塞控制
提高性能:
滑动窗口、快速重传、延时应答、捎带应答
TCP三次握手:1、建立连接 2、协商序号(以两个随机值中较小的一个作为序号) 3、协商双方的接受缓冲区的大小
几乎上面的这些策略,起作用都是在两端主机上的! 其实TCP还为我们考虑了网络。
2.7、拥塞控制
如果发送数据出现问题,不仅仅是对方主机出现问题,也可能是网络出现了问题!
1、通信的时候,出现了少量的丢包? 常规情况
2、通信的时候,出现了大量的丢包?
网络出现了问题。 硬件设备出问题。数据量太大,引起阻塞。 即如果出现大量的数据包丢失,tcp会判断网络出现了问题(这就是网络拥塞了)
发送方应该怎么办?? 不能立即对报文进行超时重发。因为这会加重网络拥塞。因为网络资源是共享的,用·TCP协议实现了多主机面对网络出现拥塞时的“共识” 每台主机都会少发。(在两台毫不相关的机器上也有约定) 其实网络的拥塞不同,它的影响范围也就不同(即影响的主机数就不同)
解决拥塞控制的策略----每台识别主机拥塞的机器都要做!
拥塞窗口:主机判断网络健康程度的指标,超过拥塞窗口,会引发网络拥塞,否则不会。拥塞窗口本身肯定不是静态的!因为网络状况是动态的!
滑动窗口大小 = min(窗口大小,拥塞窗口)
窗口大小:考虑的是对方主机的接受能力;拥塞窗口:考虑的是动态的,网络的接收能力
上面这种发送方法是怎么做到的呀??你怎么能控制主机A发送数据的多少呢?
把拥塞窗口设置的比较小,对方给我的窗口虽然比较大,但是取min,所以拥塞窗口起作用了,就影响了滑动窗口,进而就可以控制。 这叫做“慢启动” --- 指数增长(其实是初始的时候慢,增长幅度高)
为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.此处引入一个叫做慢启动的 阈值当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长慢启动的阈值:最近一次发生网络拥塞时,拥塞窗口的大小/2拥塞控制的算法:
当 TCP 开始启动的时候 , 慢启动阈值等于窗口最大值 ;在每次超时重发的时候 , 慢启动阈值会变成原来的一半 , 同时拥塞窗口置1可以将这个过程比作“热恋”
2.8、面向字节流
什么是面向字节流??
写可以写一次也可以写十次,读也一样,不需要考虑写的时候是怎么写的,可以用read一次读100个字节,也可以一次读一个字节,重复100次。(读和写不需要完全匹配的次数)
数据像水流一样以连续的字节序列传输,没有固定的边界划分
而UDP是发几次收几次--- 面向数据报(类似于寄快递,寄几次对方就收几次)
每次收发都是完整报文
因为UDP的报头里面有它自身报文的大小---16位UDP长度
独立的数据报(固定边界)
TCP报头是没有TCP报文的长度的,只有首部长度。TCP只有字节的概念,不关系上层协议,不关心上层报文格式。 面向字节流像自来水,自己需要多少,接多少完全由自己来定。
需应用层处理消息边界
2.9、数据粘包
在用户层对报文进行处理必须一个一个的处理,报文的概念是用户层关心的。 用户层需要将字节流变成一个一个完整的请求。
什么是数据粘包??
它指的是发送方多次发送的数据在接收方缓冲区中被合并成一个数据包的现象,导致接收方无法正确区分原始消息的边界。
粘包问题的原因:
(1) TCP 协议特性
字节流传输:TCP 将数据视为连续的字节流,不保留应用层的消息边界。
Nagle 算法:为减少小包数量,TCP 可能合并多个小数据包发送(可通过
TCP_NODELAY
禁用)。(2) 缓冲区机制
发送缓冲区:发送方的多次
send()
数据可能被合并后一次性发送。接收缓冲区:接收方的
recv()
可能一次性读取多个数据包。
粘包常见的表现:
发送方发送两条消息:"Hello" 和 "World"。
接收方可能收到:"HelloWorld"(合并为一个包)。
发送方发送一条长消息 "1234567890"。
接收方可能分两次收到:"12345" 和 "67890"(拆包)。
如何解决粘包问题??? 在应用层通过协议,明确报文和报文之间的边界
1、客户端和服务器规定好,采用定长报文
2、使用特殊字符(以特殊字符作为分割)
3、使用自描述字段 + 定长报头:规定报头是八个字节, 其中前四个比特位用来描述有效载荷是几个字节(报头是定长的,包头中有一个字段描述有效载荷是多少) 上层先根据约定先读取8字节,然后再解析8字节,拿出有效载荷,然后再根据有效载荷的长度来读取有效载荷
4、使用自描述字段 + 特殊字符:http ,按钮行读取,读到一行是空行就认为把报头读完了,在这里面一定有一个lens,lens表示有效载荷的长度,再去按照有效载荷的长度再去读就读完了。
2.10、TCP连接异常问题
进程终止: 连接正常断开
连接其实是和文件相关的。因为每创建一个连接,新增一个套接字,一个文件描述符。文件的生命周期是随进程的。当进程终止了,连接就进行四次挥手。
机器重启:先要杀掉所有进程,和第一种情况做法相同
机器掉电/网线断开:收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset. 即会定期询问对方是否还在 使没有写入操作 . 如果对方不在 , TCP自己也内置了一个保活定时器
如何用UDP实现可靠传输???(面试题)
1、引入序列号保证数据顺序
2、引入确认应答,保证接受方收到了数据
3、引入超时重传, 如果隔一段时间没有应答, 就重发数据;
4、发送方未收到 ACK 时,触发重传
5、通过滑动窗口限制未确认分片的数量:流量控制
6、动态调整发送速率:拥塞控制