看了很多文章的总结以及自己的一些理解
目录
单播,广播,组播
TCP协议 VS UDP协议
1.TCP是面向连接的
2.TCP是可靠的
3.TCP是字节服务,字节流传输
接收缓冲区和接收窗口,发送缓冲区和发送窗口
1.TCP发送缓冲区和发送窗口
2.TCP接收缓冲区和接收窗口
3.UDP的接收缓冲区
4.UDP没有发送缓冲区
TCP/IP四层模型
TCP报文
TCP header,20字节的固定长度包括:
展开说说6位控制标志位:
展开说说窗口大小的值:
TCP的可用选项的种类:kind值不同就是不同的选项
UDP报文
SACK技术(kind=4 kind=5)
1.SACK
2.D-SACK
时间戳选项
MTU
单播:
两个主机之间的端对端的通信(可以在广域网进行)
广播:
一个主机对整个局域网上所有主机的通信(只能在局域网进行,比如192.1168.1.255。这个255就是表示这段网络的所有主机)(单播和广播是两个极端,一个和所有)
多播:
一个主机对一组特定的主机的通信(可以在广域网进行)(讲业务分组,只有在这一组的里面的才能收到。也叫组播。)
只有UDP支持广播和多播,当然肯定支持单播。
TCP只支持端对端的单播,点对点的通信。
TCP是面向连接:
需要三次握手建立连接和需要四次挥手断开连接(见另外一篇文章)。
同时,建立连接后双方的系统内核会为该连接分配必要的系统资源,用来管理连接的状态和传输在连接上的数据(是说系统内核管理这很多TCP连接,每一条连接都需要分配给他相应的资源,应该就是缓冲区这些吧)。
数据交换完成后,通信双方都需要关闭该连接以释放系统资源。
UDP是面向无连接:
没有建立连接和断开连接这些事先和善后步骤,直接发数据包。
TCP是可靠:
有确认机制,也就有重传机制(快重传和超时重传)
超时重传,发包的时候设定一个计时器事件 如果时间内包没有达到 就重发。
快重传,连续收到三个一样的ack(确认号),就对后面的未到达的包进行重发。
UDP是不可靠:
没有确认机制,也就更加没有重传机制。
所以如果要让UDP也是可靠的,就要自己实现确认机制,重传机制,以及实现这些机制所需要的缓冲区和滑动窗口的概念。
UDP是包服务,TCP是字节流服务,包也是字节流组成的,字节流也是组成一个一个包发出去的,从这个角度看好像区分不出什么。但是基于字节流的服务如果说应用层没有做好包的边界以及包的分割的话就会有粘包问题的存在。
下面从应用程序读写和真正从内核发出和接收的角度来看这两者的区别:
TCP是字节服务:
1. 当发送端应用程序连续执行多次写操作时:
(系统内核空间的)TCP模块会先将这些数据放入(内核空间的)TCP发送缓冲区中(实现上,应该是一个TCP连接一个缓存吧)。
当TCP模块需要真正发出数据时,发送缓冲区中待发送的数据会被封装成一个或者多个TCP报文段发出。
2.当接收端应用程序执行读操作时:
TCP接收端具有接收缓冲区,当接收端接收到一个或者多个TCP报文段之后,
TCP模块将报文携带的发送端应用程序数据按照TCP报文段的序号放入该缓冲区(接收缓冲区),
并通知应用程序来读取数据。
接收端应用程序可以一次性将接收缓冲区的内容读出,也可以分多次读取,这取决于用户指定的应用程序读缓冲区的大小。
3.总结一下 :
TCP模块 发送出的TCP报文段 的个数与应用程序执行写操作的次数之间没有固定的数量关系。
TCP模块 接收到的TCP报文段 的个数与应用程序执行读操作的次数之间没有固定的数量关系。
UDP是数据包服务:
发送端应用程序每执行一次写操作,UDP模块就会将之封装成一个UDP数据报并发送出去。
接收端应用程序必须及时针对每一个UDP数据报执行读操作,否则缓冲区满了,再到达的UDP数据就会被丢弃。
上面说的应用程序读写应该就是调用一次那个相应的TCP库或者UDP库的接口吧
TCP发送缓冲区包括:
#1 发送了并且已经确认了(应该马上就要被移除了吧)
#2 发送了但是还没有确认
#3 没有发送,但是允许发送的
#4 没有发送,但是不允许发送的字节
#5 还有空余
#1 #2 #3 #4 就是在发送缓冲区的字节流,可能还有空余的位置可以等待应用程序继续写入的
#2 #3 就是发送窗口的大小 如果#2确认了 就向#4滑动了,移动发送窗口的左边界。
各自的“发送窗口”则要求取决于对端通告的“接收窗口”,要求相同(应该是理论上吧,还是实现上就是相同的,TCP的拥塞控制算法是在接收端实现的吗?拥塞控制算法不是会改变发送窗口的大小吗)。
TCP协议通过动态调整发送窗口的大小来进行拥塞控制和流量控制。
是发送窗口还是接收窗口?
慢启动和拥塞避免算法都会增加发送窗口,算法如何增加,看具体的算法了。在连接刚开始的时候以及在从丢包恢复的过程中,进行增加发送窗口。
接收缓冲区包括
#1 已经接收了(等待上层应用程序读取)
#2 未接受但是可以接收的空间(这其中可以有些位置可能已经是接收的了就是不能滑动)
#3 未接收,但是不可以接收的空间
#2 就是接收窗口的大小 可以理解成如果#2接收了 就向#3滑动了
#1也可以当成了接收窗口,#1#2加起来是最大接收窗口,#2可以理解为当前接收窗口,是返回给发送端的窗口字段的值。
当前接收窗口并对应于接收端允许发送端发送的剩余数据量。
当前接收窗口大小是发送回发送端的 ACK 中通告的“窗口”字段值,
等于最大接收窗口大小与已接收和确认但尚未被应用程序检索的数据量之间的差值。
各自的“接收窗口”大小取决于本端应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。
在实际应用中,实际场景中,系统或者应用对于缓冲区设置的值的大小以及对于窗口的值的大小 挺模糊的,不清楚怎么设置的。
UDP的接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致。
如果缓冲区满了,再到达的UDP数据就会被丢弃。(好像一般不会满)
UDP不存在发送缓冲区,只要有数据就会直接交给内核,然后再由内核转交网络层(IP层就是)协议进行传输。
滑动窗口协议是传输层进行流量控制的一种措施,
接收方通过通告发送方自己的可以接受缓冲区大小(这个字段越大说明网络吞吐量越高),
从而控制发送方的发送速度。
如果接收端的缓冲区一旦面临数据溢出,窗口大小值也会随之被设置一个更小的值通知给发送端从而控制数据发送量(发送端会根据接收端指示,进行流量控制)。
拥塞窗口指某一源端数据流在一个RTT内可以最多发送的数据量。cwnd。
所以发送窗口的大小应该是min(cwnd,rcvwindow)
tcp滑动窗口 和 内核态缓存
整个数据的流程中,
首先网卡接收到的数据存放到内核缓冲区内,
然后内核缓冲区存放的数据,根据TCP信息,将数据移动到具体的某一个TCP连接上的,接收缓冲区内(也就是滑动窗口)。
发送时候也是类似的逻辑,发送的时候应该是先放入发送缓冲区,再放入到内核缓冲区去真正发送出去。
TCP的发送/接受缓冲区(也就是发送/接受滑动窗口),是针对某一个具体的TCP连接来说的。
每一个TCP连接都会有相应的滑动窗口,但是内核的发送/接受缓冲区是针对整个系统的,
里面存放着整个系统的所有TCP连接的接收/发送的数据。
如果包乱序是不是说明不了什么,有可能是以下几种:
(一般都说正向链路乱序,反向链路乱序是指的是ack乱序)
接收端空洞的情况,有可能就是因为链路不同导致的延时,也有可能就是因为丢包了。
硬件的问题会造成的情况可以有丢包 乱序
真正的丢包就是包丢弃了,可以是中间路由缓冲区满了丢弃了,可以是某个包就是错了丢弃了
哪四层呢
应用层
传输层(协议有TCP协议,UDP协议)
网络层(IP层,对这个IP层就是网络层,总感觉网络层是比传输层还上面的。为数据包选择路由)
网络接口层(数据链路层+物理层,传输有地址的帧,帧就是这一层的概念了,网卡)
UDP和TCP都是传输层协议
传输层使用UDP协议,可以参考TCP协议,在应用层实现:确认应答、添加发送缓冲区用于超时重传、滑动窗口和拥塞控制,来达到可靠的需求。
还有一些基于UDP实现的协议(应用层的协议吧)--来自百度百科摘录:
RUDP:可靠的用户数据包协议,这是一个统称吗
RTP:RTP用来为IP网上的语音、图像、传真等多种需要实时传输的多媒体数据提供端到端的实时传输服务。RTP为Internet上端到端的实时传输提供时间信息和流同步,但并不保证服务质量,服务质量由RTCP来提供。有的说属于传输层有的说处于应用层。
UDT:UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。 由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。
都是基于UDP实现的可靠数据传输协议。
基于UDP的应用层协议
DHCP: 动态主机配置协议 68端口
TFTP: 简单文件传输协议 69端口
TCP报文=TCP header + TCP 数据
(
TCP 报文段中的数据部分是可选的。
在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。
如果一方没有数据要发送,也使用仅有 TCP 首部的报文段,只用来作为确认包。
在处理超时的许多情况中,也会使用仅有 TCP 首部的报文段。
)
TCP header = 20字节的固定长度 + 40字节的可选长度(选项+填充)
2个字节的源端口+2个字节的目的端口
( TCP报头中的源端口号和目的端口号同IP数据报头中的源IP与目的IP唯一确定一条TCP连接。从连接的角度来看,端口,就是用来标识一台计算机上的应用程序接口)
4个字节32位序号
4个字节32位确认号
2个字节=4位首部的长度(就是tcpheader的长度,单位是4字节,正符合说的最长60个字节=15*4字节)+6位保留+6位控制标志位
2个字节是窗口大小,16bit,可以表示65535个字节。
2个字节校验和
2个字节紧急指针
+
(最多40个字节的可选长度(选项+填充),要填充是因为选项可能不是4字节对齐,要4字节对齐。)
包括:URG ACK PSH RST SYN FIN,共6个
每一个标志位表示一个控制功能。最常见的比如建立连接的时候,关闭连接的时候。
1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
跟最下面的2个字节紧急指针有关系 具体没有了解
2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。连接请求中就无效。
3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
4)RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
5)SYN:同步序号,用于建立连接过程。在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
connectreq包:SYN=1和ACK=0
connectack包:SYN=1和ACK=1
6)FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
2个字节是窗口大小,16bit,可以表示65535个字节。如果是100ms的RTT,那么吞吐量也只有5.24M/s
但是对于当前千兆接口已经是标配,在数据中心对服务器上开始部署10G接口的现实情况,窗口大小太小。
窗口大小表示的是 本端的可发送的窗口大小。用来进行流量控制。TCP的流量控制由连接的每一端通过声明的窗口大小来提供。
(不是只有连接建立的时候才能写吧,应该是每次包中都会设置这个值吧?)
后面有个 窗口扩大因子kind=3 选项,用来扩大窗口大小的,这个选项只有双端都支持才有效(一般是建立连接的时候商定,看到一个文章上写到:“TCP 窗口缩放”选项只用于在连接建立过程的同步 (SYN) 段中发送数据。)。通过这个来使用更大的窗口来满足高性能和高吞吐量。
用来增加吞吐量。最大窗口可以达到1GB=65535(16位的窗口大小)*(2^14最大的位移位数,kind=3的选项中可以设定0~14的值)
(那一开始的是适用了kind=3以后,以后连接中的窗口大小的字段的值都要默认乘以最初设定的值了吗,还是说每次都带一个kind=3的选项)
窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率(控制发送端的发送窗口,那是不是发送端给接收端的包里面的窗口大小是没有用的,应该有用吧,因为接收端和发送端是相对的,是接收端也是发送端,TCP是全双工),从而达到流量控制。
TCP流控制主要用于匹配发送端和接收端的速度,即根据接收端当前的接收能力来调整发送端的发送速度。
TCP流控制中一个很重要的地方就是,TCP接收缓存大小是如何动态调整的,即TCP确认窗口上限是如何动态调整的?
如果发送方收到接收方的窗口大小为0的TCP数据报,那么发送方将停止发送数据,等到接收方发送窗口大小不为0的数据报的到来
要优化 TCP 吞吐量,必须将连接的 TCP 接收窗口设定为可同时反映该连接传输路径的 BDP 和应用程序检索速率的值。
BDP 速率会因为传输路径中的阻塞情况而变化,
应用程序检索速率会因为单个应用程序有多条连接的数据需要检索而变化
各个系统的实现:
linux下的TCP/IP 协议栈栈
Windows XP 系统的 接收窗口的注册表设定 TCPWindowSize 最大窗口的设置 是适用于系统的所有的连接的
Windows Vista 系统的 接收窗口自动调节
各种标准的提出
https://learn.microsoft.com/zh-cn/previous-versions/technet-magazine/cc162519(v=msdn.10)?redirectedfrom=MSDN
http://wjhsh.net/virusolf-p-4332652.html
https://tsejx.github.io/javascript-guidebook/computer-networks/computer-network-architecture/transport-layer-protocol
选项=kind1个字节+length1个字节(也可以没有)+info待定
其中kind:
Kind Meaning Reference
---- ------------------------------- ---------
0 End of Option List [RFC793] 没有length
1 No-Operation [RFC793] 没有length
2 Maximum Segment Size [RFC793] length=4=1+1+2 因为info表示MSS大小,用2个字节表示。一般在为建立连接而设置SYN标志为1的那个报文段中指明这个选项。
3 WSOPT - Window Scale [RFC1323] length=3=1+1+1 因为info表示移位大小,用1个字节表示 这个就是窗口扩大因子 取值是0~14
4 SACK Permitted [RFC2018] length=2=1+1 没有info 就是个开关,表示是否支持SACK技术
5 SACK [RFC2018] length=1+1+N*8 N块的左边沿+右边沿,该选项的参数部分用于告诉发送方本端已收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。
6 Echo (obsoleted by option 8) [RFC1072]
7 Echo Reply (obsoleted by option 8) [RFC1072]
8 TSOPT - Time Stamp Option [RFC1323] length=1+1+8 info=4个字节的时间戳值(当前时钟的时间值)+4个字节的时间戳回显应答(发送包的时间戳的值 赋值到 回显应答上)
9 Partial Order Connection Permitted [RFC1693]
10 Partial Order Service Profile [RFC1693]
11 CC [RFC1644]
12 CC.NEW [RFC1644]
13 CC.ECHO [RFC1644]
14 TCP Alternate Checksum Request [RFC1146]
15 TCP Alternate Checksum Data [RFC1146]
16 Skeeter [Knowles]
17 Bubba [Knowles]
18 Trailer Checksum Option [Subbu & Monroe]
19 MD5 Signature Option [RFC2385]
20 SCPS Capabilities [Scott]
21 Selective Negative Acknowledgements [Scott]
22 Record Boundaries [Scott]
23 Corruption experienced [Scott]
24 SNAP [Sukonnik]
25 Unassigned (released 12/18/00)
26 TCP Compression Filter [Bellovin]
MSS选项(kind=2)与窗口扩大因子选项(kind=3)只能出现在同步报文段中,否则将被忽略
(那给发送端发送接收窗口大小的时候 不能带扩大因子了选项了吗?这个值就是一开始商定的值了?那不会告知的不准确吗)
但是同步报文段不执行窗口的扩大操作,
即同步报文段的接收通告窗口大小就是该TCP报文段的实际接收通告窗口大小。
当连接建立完毕后,每个数据传输方向的窗口扩大因子就固定不变了。
如果接收方不接受MSS,那就是默认的536字节(怎么告诉对方不接受啊)。
TCP header(20字节+最多40字节) vs UDP header(固定8个字节)
UDP header
2个字节的源端口
2个字节的目的端口
2个字节UDP长度 (包括报头的8字节,所以最小是8。最大是65535。但是以太网的链路层的MTU最大是1500字节,理论上UDP的数据长度最大是1472)
2个字节校验和
思考 当只使用包头中确认字段的时候,在TCP通信时怎么知道丢失包后面传送的数据是否有成功的送到,不过一般来说只能有2中状况:
1. 只重传超时的数据包,只重传超时的数据包,但是如果比较坏的情况下,丢失了很多封包呢? 那就需要一个一个的等待超时了,很浪费时间。
2. 重传这个片段以及之后的所有包,
在最坏的状况下,看起来效率还是挺高的,
在最好的状况下,如果只有一个包丢失,就去重传后面所有的包,流量浪费也是很严重的。
对于这种TCP的确认系统,不是特别的好处理这种不连续确认的状况了,
只有低于ACK number的片段都被收到才有进行ACK,out-of-order的片段只能是等待,同时,这个时间窗口是无法向右移动的。正式因为后续OUT-OF-ORDER的报文段的发送状况也不清楚。
用了SACK技术以后:
使TCP模块可以确认哪些段丢失了,重新发送丢失的TCP报文段,不用发送所有未被确认的TCP报文段。 其实就是我们的cmask技术。
要使用SACK,2个设备必须同时支持SACK才可以
建立连接的时候需要使用SACK Permitted的option,如果允许,后续的传输过程中TCP segment中的可以携带SACK option,
也就是说,在connection建立的时候,SYN包中有SACK-Permitted 的选项为true,同时自身也支持SACK
这个option内容包含一系列的非连续的没有确认的数据的seq range (没有确认,但是已经收到的意思呗)
在接收异常的时候,产生SACK option.(也就是说没有异常,都是顺序到达就不需要这个option了)
如果要发送,SACK中需要携带接收队列中所有没有被确认的数据段信息。如果接收方选择发送带有SACK的ACK,需要遵循如下规则:
1. 第一个block需要指出是哪一个segment触发SACK option ,我认为就是谁乱序了,才会导致SACK
2. 尽可能多的把所有的block填满
3. SACK 要报告最近接收的不连续的数据块
接收端的行为:
1. 数据没有被确认前,都会保持在滑动窗口内
2. 每一个数据包都有一个SACKed的标志(SACKed的标志是啥标志),对于已经标示的segment,重新发送的时候会忽略
3. 如果SACK丢失,超时重传之后,重置所有数据包SACKed 标志
D-SACK主要是使用了SACK来告诉发送方有哪些数据被重复接收了
如果是D-SACK,那么SACK option的第一个block代表被重复发送的序列片段
需要注意的点:
1. D-SACK仅仅是接收端报告一个重复的连续的片段
2. 每个重复的连续片段只能在一个block中
3. 重复片段的序列号
4. 第二个block指的是data没有被确认的
D-SACK还是带了诸多的好处,能否让发送方了解是ACK丢了还是发送的数据包丢了,
重复发送就说明是ACK丢了呗,同时也能够掌握网络上的一些事情,
比如out-of-order,超时重传等,这样了解了网络,才能更好的做流控。
文章:https://blog.csdn.net/wdscq1234/article/details/52503315 图中介绍了SACK 和 DACK
https://wenku.baidu.com/view/5c288b51ad02de80d4d84066.html?_wkts_=1672821593257 介绍窗口大小的意义
抓包工具
tcpdump
wireshark
RACK是基于时间的丢包检测算法,跟上面其实不是并列的意思
之前的丢包算法是基于dupthresh重复ack的个数,有他的缺陷在ack丢失或者窗口太小=2等等
RACK主要用于改善尾部丢包和二次重传的问题,可以进行快速重传,其大概思路是:(不知道是每次,还是特定的时期)用最新被(S)ACK确认的数据包为基准,其发送时间减去一个乱序时间窗口之前的数据包如果没有收到反馈,就可以判断为丢失了。
sack的情况下可能是乱序,也可能是丢包
如果是尾部丢包那就肯定是丢弃了 等不到了 而不是乱序
丢包,初传丢包了
再次重传又丢包了
超时恢复:是说检测到超时丢包了以后的恢复吧
快速恢复:是说执行了快速重传以后的恢复吧
Kind: 8
Length: 10 bytes
+-------+-------+---------------------+---------------------+
|Kind=8 | 10 | TS Value (TSval) |TS Echo Reply (TSecr)|
+-------+-------+---------------------+---------------------+
1 1 4 4
TSval 表示发送端发出该报文时的本地时间戳,
TSecr 则负责回放(Echo) 最近一次收到的对端报文中的 TSval 的值。
(
除了一些特殊情况下,下面这些情况不是最近一次收到的:
1.Delay ACK 。
Echo 最早收到的那个报文的 TSval , 因为只有这样,发送端测量的 RTT 才更加准确.
2.报文丢失造成了接收端序号空洞
让接收方 Echo 稍早时候的 TSval ,而不是序号最大报文的 TSval ,
这样使得发送端估算的 RTT 能偏大,也就是发送报文更保守(conservative),有利于减小拥塞。
3.接收端序号空洞被填上
接收端序号空洞被填上意味着 1. 乱序的报文姗姗来迟; 或者 2.收到了发送端重传报文;
无论哪种情况,这都是反映了网络的真实情况,因此这个时候接收端应该 Echo 这个报文的 TSval。
)
启用 Timestamp 选项需要经过双方的协商,
协商在三次握手时完成,如果协商成功,则在后续的报文中,
除了 RST 之外的所有报文均必须包含 Timestamp 选项。
发送一个报文时将发送时间写入时间戳选项,
在收到的ACK报文时通过其时间戳选项的回显值就能知道它确认的是什么时候发送的报文,
用当前时间减去回显时间就可以得到一个RTT。
https://switch-router.gitee.io/blog/tcp-timestamp/
MTU,最大传输单元,某一种通信协议的某一层上面所能通过的最大数据报大小。
对于以太网来说,在IP层的数据报(包括头和数据)最大是1500字节了。
如果IP报大于1500的话,IP层就会对这个包进行分片。
(一般把数据链路层叫做帧,Frame。TCP层叫做段,Fragment)
一些典型的MTU值:
网络: MTU字节
超通道 65535
16Mb/s信息令牌环(IBM) 17914
4Mb/s令牌环(IEEE802.5) 4464
FDDI 4352
以太网 1500
IEEE802.3/802.2 1492
X.25 576
点对点(低时延) 296