1. TCP协议
1.1 TCP协议的性质
-
面向连接的、可靠的、基于字节流
至于为什么面向连接,又为什么可靠,基于字节流的,等后面便可知道.
1.2 TCP协议栈收发数据的四个阶段
- 创建套接字
- 连接服务器
- 收发数据
- 断开服务器连接,删除套接字
1.3 TCP头部格式
2.创建套接字
2.1首先理解什么是协议栈
我们耳熟能详的协议是指的具体的网络协议,双方共同遵守的一种通信规则和格式,而协议栈顾名思义是协议的栈,是将多个协议按照一定次序组合起来,用来实现这些协议的落脚实现的软件
既然是软件,那么一定和网络相关,又是由操作系统去控制,所以协议栈其实也是我们操作系统中的网络控制软件,他来具体的实现协议做了什么,去配合我们的网络硬件收发信息.当然,不同的操作系统其协议栈的具体实现也有所不同。
2.2协议栈的内部结构
我们平常享受着网络的应用服务,也知晓网络中的常见协议,但是这些协议如何去规范,实现,工作,作为网络受用者是不关心的,而协议栈
具体做了什么,需要我们去了解,当然一个抽象的逻辑概念还是不容易理解
我们的应用程序如浏览器,聊天软件等等应用层需要处理的事情就先置之不理,我们仅需要了解TCP协议收发的数据部分和UDP协议收发的数据部分,至于什么时候用TCP,什么时候用UDP,举个例子:浏览器,邮件这种需要可靠协议的就用TCP,不可靠的数据量较少的DNS查询等,重点不在此
只需要记住TCP协议栈接受来自应用程序处理交付的数据信息,并进行处理递交给下一层也就是IP协议栈去处理.
2.3套接字的实体
套接字(Socket)是什么,可以把他看成一种通用的接口,他不仅适用于TCP/IP协议,还适用于其他网络协议,当然,本质上属于一种数据结构,存放一些关键信息的一种文件描述。例如一个通信对象的IP地址,端口号,操作的进行状态等,作为一个高度抽象的接口,他也就是我们平常的Socket,但他也被理解成中文含义插口,所以也被理解为接口,在编程语言java中更是设计成类去供我们使用,也就是一个实体结构
实体信息的存放位置:协议栈内部,在TCP/IP协议栈中,套接字的实体信息存放在协议控制块(PCB)中。协议控制块是TCP/IP协议栈中的一个重要数据结构,用于保存协议的状态和控制信息。每个套接字在协议栈中都有一个对应的协议控制块,用于保存该套接字的实体信息以及其他与该套接字相关的状态和控制信息。
套接字做了什么?
首先了解一下通常套接字的数据结构:
- 协议族(套接字使用的具体协议,比如TCP/IP,或者UDP协议)
- 本地地址
- 目的地址
- 协议端口号(用来标识哪个应用进程)
可以看出套接字实体中保存的内容对于一个网络通信来说是真正的控制信息,他决定了数据的走向,所以协议栈在进行相关协议操作时就需要参考这些信息,比如:在发送数据时,需要查看通信对象的IP地址和端口号,以便发送数据,但发送数据后的状态响应是不确定的,有可能等不到响应,也有可能中途丢失了数据,因此作为客户端,协议栈需要去知道执行发送数据的操作已经过去了多久,这时候就需要通过套接字去判断下一步的行为。
如果仍然觉得抽象,并不理解套接字,那我们可以在windows系统中用netstat命令去显示其内容:
![](https://liu-materials.oss-cn-hangzhou.aliyuncs.com/computer organization/E53BLLD`%G$NMYP6I{_C70.png)
a:不仅显示正在通信的套接字,还显示包括未开始通信状态的套接字
n:显示ip和端口号
o:显示其某个程序的PID
协议族,本地地址,目的地址,状态,进程号
- 本地地址:这里的地址是IP地址和端口号,0.0.0.0表示不绑定地址,表示任意主机地址,127.0.0.1表示回环地址,即本地地址
0.0.0.0可能比较陌生,先从网卡说起,这里的网卡是指硬件网卡,通常在计算机被设计好时就决定了,网卡的数量决定了ip地址的数量,而不是我们想象中的一个ip地址,我们经常可能会用到ipconfig这一命令,在linux系统中,常常能见到eth0这一类似网卡的信息,那么0.0.0.0就是这样一个地址,当我们希望应用程序不指定这一个ip地址时,就可以使用0.0.0.0这一地址在所有网络接口上进行监听连接等
所以我们可以理解一下客户端 最大TCP连接数:客户端IP数 x 客户端端口数 (当然,这里是理论值,实际连接数中,往往受到硬件CPU,操作系统,应用程序等多处限制,这里是想理解一下客户端的IP数和网卡有关(一张网卡是否就意味着一个IP呢?),因此有多少个ip和端口就可以有多少种组合连接情况,实际连接数都会小于理论情况。
在我们通过协议栈创建完Socket之后,就需要进行调用了
- 套接字(Socket)和Socket库有什么不同?
Socket库是基于套接字的封装,是库,包含了许多API,便于网络应用程序的编写。
3.连接服务器
3.1什么是连接
在我们的套接字创立成功后,协议栈便会通过我们的套接字与服务器的套接字进行连接,这里的连接是指的物理上的连接还是什么连接,其实我们想象中的连接便是建立了网络上的通路,这样的理解其实是不够深入的,既然谈及到了与服务器上的套接字进行连接,那么服务器上的协议栈又是如何知道套接字上的目的地址,也就是知道该和谁通信呢?
所以连接的本质是通信的双方交流控制信息,客户端会向服务端告知必要的信息,比如:我的ip地址是xxx.xxx.xxx.xxx,端口号是:yy
,这样服务器端就能成功的创建套接字,双方就能成功建立准备,但是,数据收发时,数据存放的位置又在哪,缓冲区就成为了应用程序和服务器进程之间进行数据传递的一个中间桥梁,缓冲区这一思想实现在计算机很多地方都有所体现,而我们的缓冲区就是在连接这一过程由协议栈分配好的。至于这个缓冲区的大小是固定的还是动态分配的就需要去了解协议栈了。
3.2通信中的控制信息
- 套接字
- TCP头部
理解完套接字,我们去理解TCP头部,TCP头部其实也就是遵循TCP协议,具有TCP协议特性的控制数据,所以说他才具备控制信息,那TCP数据包是何时创建的呢,具体来讲是由我们的应用程序调用Socket库中的send函数交付给我们TCP协议栈,协议栈会将他封装成一个TCP数据包,并将其添加头部,这也很能理解,应用层的服务并不能理解TCP头部的信息,所以在发送TCP数据包也只是相对于TCP协议层来讲,而且也不包含任何头部信息。
至于头部中各字段的控制信息后面再做说明。
3.3连接操作的实际过程
既然客户端和服务端的套接字都已经准备就绪,也就是两个套接字之间形成了一一对应的关系,那我们TCP头部的控制信息就该发挥他的实际作用了
控制位(SYN):会将这一位比特设置成1,表示想要建立连接。
至于序列号和窗口的大小后续再讲,先明白过程。
- TCP数据信息就这样交付给IP模块,由IP模块委托发送,至于其中的流程不做说明,数据包到达服务器后,TCP模块会根据TCP头部的信息找到端口号对应的套接字,此时,服务器对应的套接字的状态将发生改变,变成正在连接。
- 服务器会将SYN设置成1,表示也想建立连接,同时将ACK控制位设置为1,表示接受成功(客户端在第一次发送数据的时候ACK控制位此时为0,是因为服务器没有接受过网络包),所以ACK控制位的作用就是来确定是否接受到了网络包。
- 然后网络包就会返回给客户端,客户端会根据SYN标志位判断是否连接成功,如果为1就说明连接成功,这时,客户端的套接字会发生变化,正如在未连接时,服务器并不知道客户端应用进程的端口和客户端的IP地址一样,客户端同意也不知道服务器上是哪一端口分配给了这个应用进程,所以目的地址和端口其实是不确定的,所以这时他会向套接字里写入,将状态改为连接完成。(既然目的地址不确定?哪我该如何确定发送网络包的目的地,就交由IP层进行控制了)
- 此后,客户端也会传输一个ACK为1的比特包发送给服务器,告诉服务器刚从的响应完成,当服务器的收到包后,连接才完成
这也就是我们耳熟能详的TCP三次握手,肯定有人问最后一次数据包为什么要发,二次握手之类的。(常问的为什么三次握手)
这时候就该考虑了握手握少了和握手握多了会带来什么?
3.31首先来看看握手握少了会发生什么?
- 两次握手意味着什么:服务端并不确定客户端是否接受到了网络报,也就是确认信息,就默认自身已建立连接
- 不可靠性,网络拥塞或者网络丢包
两次握手意味着服务端在接受到客户端的请求连接时就得改变状态,成为建立连接的状态,既然要成为建立连接状态这就意味着需要分配资源(缓冲区),当然,客户端在接收到ACK号不对应的情况下,也有可能丢弃SYN数据包,但这都不是关键,关键是造成了服务器资源的浪费。
3.2握手握多了会发生什么,为什么三次握手就可靠了?
- 三次握手是客户端告知服务端我已经接受到了你的确认信息,并且无误。
- 最简单的想法便是,客户端宕机了,由于第二次握手服务器还并不能确认建立连接,分配资源,所以发送的数据包也有可能客户端没有接收到,假如这个数据包的ACK确认号无误地发送给客户端,无论是网络拥塞,延迟发送给了客户端,客户端也依然会接受,如果是客户端宕机了,那么服务端只需等待一段时间,如果没有第三次握手便会超时重传。
- 另外一种情况,网络拥堵,客户端多次发送数据包,三次握手是否依旧可靠?
这里其实依旧有一个问题存在,便是倘若RST数据包在第二次重传数据包之后到达,服务端是否又会重新建立两次,这里引出了另一个问题就是已建立的TCP连接,再次受到SYN后会发生什么?这里我们后面在了解
从这里来看,三次连接基本没有什么问题
再来思考四次握手以及N次握手:二次握手服务端无法知道客户端是否接受,那么三次握手客户端就一定能确认服务端是否接受到了吗?
同样服务端存在超时重传,因为是三次握手,若是客户端第三次ACK确认报文并没有到达服务端,且客户端无法得知自己的报文是否传输成功。所以三次握手其实就可以确定服务端和客户端双方已经建立连接,并且能够保证数据有效正确(比如:序列号和ACK号是否正确,第二次握手和第三次握手就能确定的事情,就不需要第三次和第四次再来确认一次了)
3.4收发数据
3.41发送数据的时机
在一系列可靠的连接过程完成之后:协议栈会将应用程序发来的数据包放入到缓冲区,当然数据包的长度是由应用进程来决定的,这是否就意味着缓冲区不能一存在数据就进行发送,倘如许多个应用程序的数据包都是小包,就会导致网络利用率低下,所以要尽可能的累积,至于缓冲区累计多少数据包再发出这便是操作系统和协议栈他们所决定的了。
- MTU:一个网络包的最大长度,以太网中一般为1500字节。
- MSS:去除头部后,一个网络包所容纳的TCP数据的最大长度.(缓冲区大小规定)
最大传输单元MTU,最大容纳数据长度MSS,当应用程序收到的数据长度接近MSS或者超过时,便发出这个数据包
另一个影响因素:时间.
若是应用程序发送数据的频率过高,缓冲区等待的时间太长就会导致发送延迟,所以协议栈内部会有一个计数器,一定时间后,就会将网络包发送出去.
又想要缓冲区内数据尽可能多,又不想缓冲区数据进来的太慢,所以这是一种悖论,他们是矛盾的,我们只需要知道操作系统实际会去考量这两种参数对于不同的应用进程.
3.42对较大的数据进行拆分
MSS:一般情况下是1460个字节,MTU:一般情况是1500个字节,TCP头部和IP头部占40个字节.
如果应用程序的某个数据包一次性超过MSS,就会出现以下情况:
3.43使用ACK号和序列号确认网络数据包的可靠性
在此之前,我们先来回顾一下TCP连接过程中序列号(seq)和ACK号发生了什么
SYN(控制位) | ACK(控制位) | seq | ack |
---|---|---|---|
1 | 0 | x(随机生成) | 0 |
1 | 1 | y(随机生成) | x+1 |
0 | 1 | x+1 | y+1 |
表中的每一栏意味着一次握手,从高到低意味着从第一次握手到第三次握手
这也能保障了即使出现网络拥堵的情况,依然是可靠的传输服务
这里,我们先从简单数据量不大的网络包分析:
因此ACK号是根据上一次序列号和数据段字长决定的,而序列号则是上一次的ack号,这样ACK号就能确保这个序号之前的数据都已经接受了
3.44根据网络包平均往返时间调制发包时机
计算机发送网络包不可能发送一个就傻傻的等待,这段等待的时间也叫超时时间,如果ACK网络包响应的时间长,意味着另一端的等待时间长,然后网络的拥堵情况就如同马路一样,动态变化,所以TCP会根据这次的ACK网络包响应时间来调制相应的超时时间,也就是等待时间,会根据实际的情况调制时间,变长变短都有可能.(这个等待时间基本在0.5s-1s之间)
3.45滑动窗口是什么
TCP头部有一个字段是窗口滑动的有效字段叫窗口大小,那么这个字段肯定跟滑动窗口有关,是这样!
先来看一副图就能明白实际传包的过程:
这样就节约了我们上面所说的等待时间,不必再等待ACK号,而是直接发送一系列的包。但是这样做带来的问题是什么?
之前我们谈及缓冲区是用来存放这些网络数据的一片内存,可能有些人已经想到了,缓冲区溢出,服务端如果没有来得及将缓冲区里的数据交付给上面的应用进程,就有可能导致缓冲区越堆越多,所以我们需要一个字段来告知我此时能接受的数据量,也就是窗口字段
那么窗口字段什么时候更新呢,什么时候告知另一端呢?服务端和客户端都需要更新窗口字段,因为他们都在双向进行着数据交互
3.46窗口的更新和ACK号的发送时机
先来回顾ACK号:用来告知发送端表示已收到的数据量。而窗口大小是用来告知其中一端自身的接受能力,那我们能够将他们放在一个TCP头部同时传送吗?还是单独的传送?
3.461我们先来思考窗口的更新是什么时间段?
- 如果缓冲区的容量足够大时,是否需要告知另一端?
- 如果缓冲区的容量发生改变时,就是否一定需要告知另一端?
- 缓冲区的容量增加还是减少时,需要告知另一端?
我们来考虑问题1:如果接收方的性能很高,在处理缓冲区的速度远大于包到达的速度,那么缓冲区的容量可以说一直很大,那么此时,对于容量一直足够大的缓冲区,不断告知另一端就显得很没必要
问题2:缓冲区的容量发生改变,可能是发送端的数据抵达了缓冲区导致缓冲区的容量减少,也可能是接收端处理了数据,将他交付给了应用进程,导致缓冲区容量增加,那这两种情况我们在问题3讨论。
问题3:如果缓冲区容量减少,对于发送端他是可知的,这是显而易见的,发送给他人的数据必然会使他人的缓冲区容量减少,而窗口的更新减少,发送端是自知的,这种情况下报喜更令人喜儿乐见,当接收方从缓冲区中取出数据传递给应用进程时,就更新窗口的大小。
当然,若是缓冲区数据不断增加,缓冲区的容量减少,为了避免丢包接受不了包 ,也是会告知给另一端的。关于窗口的问题,我们后面详细了解,其实这种更新策略并没有最好的策略,只是相比较更优,理解其中的设计思想更为关键.
3.462新窗口的TCP头部和ACK号的头部是分包还是独立成一个包?
很多人会很自然的认为TCP的头部的控制信息都是捆绑在一起发送的。可是ACK号和窗口字段的作用和含义完全不同,那么就一定需要绑定去发送吗,对于ACK号,在接受到数据后,如果没有问题,就应该向发送端返回ACK号,但此时的窗口大小说不定又没有更新。于是就有可能生成两个独立的包单独发送,但这样显然也会导致使用率低下。因此,ACK号和窗口更新信息可以在同一个数据包中发送,也可以分别发送。具体实现取决于TCP协议栈的设计。但是一般来说,在一个TCP数据包中同时包含ACK号和窗口更新信息更为常见。
4.服务器断开并删除套接字
4.1断开连接的时机
收发数据操作结束的时机点应该是应用程序判断所有数据都已经发送完毕的时候,这是,发送方会发起断开过程,不过,断开的时机多半由应用进程自身决定,满足什么样的业务要求即可。
例如:文件传输需要文件的校验,网络聊天软件则需要告知其他用户已经下线等,不同的业务要求也就决定了什么时候断开连接。
总之,完成业务需求的一方就会先断开连接,于是,我们不得不提起FIN控制位
4.2断开连接的过程
-
首先,服务器一方的应用会调用Socket库的close程序,然后服务器的协议栈会生成包含断开信息的TCP头部,也就是将FIN比特设置为1 客户端受到服务器发来的FIN报文后,客户端会将协议栈内的套接字标记为断开操作状态,然后回返回一个ACK号 之后,客户端应用进程会调用read函数来读取数据(也有可能在断开操作之前就来读取数据,所以称为挂起,也就是应用进程挂起,等到FIN包到达后继续执行). 这时,客户端一方也会调用close程序,同服务器一方发送一个FIN为1的TCP包 在这之后,服务器一端就会返回一个ACK号,到这里,通信就结束了.
至此,就是我们的TCP四次挥手,当然,谁先挥手都有可能,也不一定是服务端一方.
肯定有人要疑问:为啥是四次挥手,前面不都是握了三次手,我挥手三次怎么了?
4.21为什么挥四次手
我们先来理解FIN的含义,表示断开连接,同时不在发送数据,但这代表不能接受数据吗?
- 这里我们拿客户端作为断开连接的主动方(要有始有终),也好理解,客户端去断开连接,表示自己已经发送完数据了,等待服务端的响应
- 服务端的ACK号表示已经接受并应答,但此时服务端依旧有可能有未处理完的信息,就假设我客户端发送了最后一次数据报,立马断开连接,而这时的服务端可能还有需要响应的信息要发送
- 所以等到服务方发送FIN报文就表示要真的关闭连接了,而客户端也会回应一个ACK号表示我知道了.
但这好像不能充分说明一定要挥手四次,
为什么服务端不能一次性将ACK号和FIN报文一起发送,假如服务端已经没有需要发送的数据了,一起发送岂不是很节省空间,时间.
4.212TCP存在三次挥手
首先我们要了解什么是TCP延迟确认机制
- TCP 延迟确认机制是指当接收方收到数据后,并不立即发送 ACK 号确认,而是等待一段时间,看是否还有更多的数据到达,如果在这段时间内有新的数据到达,则会将 ACK 和新的数据一起发送,这样可以减少 ACK 确认的次数,提高网络的利用率。
具体来说,TCP 接收方会等待一定的时间(通常为 200 毫秒),以期待接收到更多的数据,如果在这个时间内没有收到更多的数据,则会立即发送 ACK 确认。如果有新的数据到达,则接收方会将之前接收到的所有数据和 ACK 一起发送。
你是否想起数据的拆分,如果有多个数据包,也就意味着可能有多个ACK号,当需要连续的去发送ACK号时,只要发送最后一个ACK号就可以,表示这个包之前的所有数据都已经接收到了,这也就是因为TCP延迟确认机制
所以,在没有需要发送的数据和开启了TCP延迟确认机制下,TCP就会存在三次挥手
5. TCP常见机制
认识和了解相关的TCP连接状态可以帮助我们更好地了解TCP的原理
CLOSED | 初始状态,表示TCP连接未被打开。 |
---|---|
LISTEN | 服务器端处于等待客户端连接的状态。 |
SYN-SENT | 客户端向服务器端发送连接请求报文段(SYN),等待服务器端确认连接请求。 |
SYN-RECEIVED | 服务器端接收到客户端的连接请求报文段(SYN),并向客户端发送确认连接请求报文段(ACK+SYN)。 |
ESTABLISHED | 双方都确认连接请求,建立连接成功,数据传输时的状态。 |
FIN-WAIT-1 | 表示TCP连接的一方(通常是客户端)已经发送了连接释放报文段(FIN),等待另一方(通常是服务器端)确认 |
FIN-WAIT-2 | 表示TCP连接的一方已经收到了另一方发送的连接释放报文段(FIN),等待自己的连接释放报文段(FIN)得到确认 |
CLOSE-WAIT | 表示TCP连接的一方(通常是客户端)已经收到了另一方(通常是服务器端)发送的连接释放报文段(FIN),等待自己发送连接释放报文段(FIN) |
CLOSING | 表示TCP连接的一方同时收到了对方发送的连接释放报文段(FIN)和对方的确认报文段(ACK),并向对方发送确认报文段(ACK),等待对方的连接释放报文段(FIN) |
LAST-ACK | 表示TCP连接的一方发送连接释放报文段(FIN),等待对方的确认报文段(ACK) |
TIME-WAIT | 表示TCP连接的一方发送了连接释放报文段(FIN)和确认报文段(ACK),但是对方可能没有收到确认报文段,等待一段时间后关闭连接 |
CLOSED-WAIT | 表示TCP连接的一方(通常是服务器端)已经发送了连接释放报文段(FIN),但是没有收到对方发送的确认报文段(ACK),等待一段时间后关闭连接 |
5.1TCP的重传机制
其实TCP的实现机制非常复杂,所以才可以说他是可靠,面向连接,基于字节流的一种服务
5.11超时重传
说到重传,需要思考的是什么时候需要重传,何种情况会出现重传?
丢包?,超时?
- 既然要实现这一机制,就需要一个定时器去控制重传的时间
无论是数据包丢失还是在TCP建立连接时ACK号丢失,都会有所谓的超时重传,比如第一次握手并没有收到ACK号和SYN号,客户端便会超时重传,同样服务端也存在超时重传的情况.
超时重传的时间应该如何去衡量?
- 超时重传时间过长:另一端等待的时间就长,重发的效率就会低,重发的会很慢
- 超时重传时间过短:网络拥塞情况严重时,网络数据包传输慢时,超时重传就会多次传包,导致网络拥塞更为严重,会造成更大的网络负荷.
该如何解决?
RTT:往返时间(发出数据到接受数据的时间差)
RTO:超时重传时间
小于RTT明显是不合理的,那过大与RTO又显得过于浪费,于是略大于RTO成为了思考的一种策略.
至于是动态的去定义还是一刀切静态的定义明显是要根据RTT往返时间决定更为合适的,至于这个时间到底如何确认,是经由大量数据实验和计算得出,本人才学疏浅,也只能明白这个结论:
如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。也就是如果遇到需要超时重传的时间,就将重传时间延长至少两倍,避免复杂的网络情况。
5.12快速重传
我们先来理解一下超时重传是根据RTT,也就是往返决定时间来衡量是否数据是否得到接受,同样也有着另一种思路去衡量数据是否得到了接受,就是根据接受的数据去衡量,也就是我们的ACK号
例如:
假设发送方需要发送10个数据段,在发送第2个数据段时,因为某种网络原因,接收方并没有接收到2号数据段,而发送方在发送3号数据段时,由于接收方并没收到2号数据段,他会发送确认,但这个确认号是第一个数据段的确认号,于是发送方在连续接受3个同样的确认号时,便知道了第2个数据段没有发送成功,便会立即重发2号数据段。
- 但这种重传方式同样存在许多弊端,例如重传时重传的数据不知道该如何选择,是部分重传还是全部重传,对丢失的数据不具备高针对性,同样,也容易被恶意攻击者进行虚假重传发送伪确认来浪费资源和带宽
5.13选择性重传
- 选择性重传的机制叫:SACK,选择性确认.
这种传送方式需要在TCP头部选项加上SACK字段,它可以将已接受到的数据发送给发送方,这样发送方就知道哪些收到了,哪些没收到
- 选择重传这一机制是在快速重传机制上的一种补充,可以让TCP发送方立马清楚是哪一段包的数据丢失
如图,是TCP选择性重传的样例:
![TCP SACK选择性重传](TCP协议的过程细节.assets/TCP SACK选择性重传.jpg)
假设第一个数据段序列号为105的情况下,发送方要接收到ack号为205,才能确认105-205这段数据被准确接受,于是在第二次数据丢失的情况下,接收方理应收到来自205-XXX的数据段(接收方并不知道下一个数据段的长度是多少),但是他并没有收到205开头的序列号,就知道数据出现了丢失,于是在可选字段部分加入SACK字段为当前所收序列号及数据长度部分(305-405),将他交付出去,于是按照快速重传的重传方式,发送方就能明白仅仅是205-305这一段发生了丢失,而仅仅重传205-305这一段
- 注:序列号和所指的段并不是指一个数据的某个字节部分,而是用一种数据序号来分成段,确保每一段的其实序列号,而ACK号则是担保我们下一个序列号的起始部分,如果没有明白,没有问题,通过图解及其抓包即可明白过程
5.131 Seq(序列号)和ACK号的真实面孔
- 让我们直观地先明白什么是序列号,什么是ACK号
如图,这是序列号的递增思想
- 当然,这里我省略了一个过程,那就是TCP建立三次连接,不然第一个数据段的Seq号和Ack号有理我也说不清,明白了序列号其实和字节长度并没有任何关系之后,就是来深入理解序列号和Ack号到底担任了什么角色,这里不理解Ack号为什么没有值并没有关系
下面我们进行抓包,按照一个真实的案例来分析这一个过程
为了方便直接观看,选择由表格形式呈现:
/网络传输 | 发送方 | 接收方 | seq | ack | 数据传输长度 |
---|---|---|---|---|---|
第一次 | √ | 0 | 0 | 0 | |
第二次 | √ | 0 | 1 | 0 | |
第三次 | √ | 1 | 1 | 0 | |
第四次 | √ | 1 | 1 | 32 | |
第五次 | √ | 1 | 33 | 49 | |
第六次 | √ | 33 | 50 | 0 | |
第七次 | √ | 50 | 33 | 1208 | |
第八次 | √ | 33 | 1258 | 0 | |
第九次 | √ | 33 | 1258 | 1056 |
- 注:√(表示由谁发送数据)
这便是网络传输中真实的数据情况,可以看到前三行中便是我们TCP建立三次握手的各字段变化情况,这里第一次的seq序列号是随机生成的,第二次接收方握手的seq号其实也是随机生成的,既然是随机生成的,为何都是0和1,这是因为这两台设备都属于私网地址,因为特定的网络要求,私网下的互相通信比较安全,也为了简化特定的需求,只需要知道大部分情况下其实初始的seq序列号不像这样就行了.因此在建立了三次握手之后,接收方发送数据的seq号是由上一次的ack号决定的,而此次的ack号又是由seq号和数据传输长度决定的,这也就意味着,Ack号其实并不确定,毕竟并不能预卜先知另一方的数据段长度,所以有时候会出现发送ACK号报文来进行确认而不携带数据的情况,也就是说自身的Ack号是用来确认接受对方的数据,来保证双方的可靠性的,这也就是为什么双方都要生成一个随机的序列号,毕竟双方的Ack号都要根据这个序列号去确定,而在上述选择性重传下,也就能解释Seq号对于每一个数据段早已确定好了,应该序列号是由自身决定的,每段的数据包长度已经既定下来,自然也符合包的传送过程,不需要去即时等待也可也发送数据包,当然,TCP延迟机制就是为了减少包的等待而设立的,这样可以减少网络的负荷,不得不说TCP太复杂了.水真的很深.
5.2窗口与流量控制
在前面,我们了解过计算机并不会傻傻地去等待Ack响应,而是会根据窗口大小进行传输数据,这种传输带来了较高的网络利用率
窗口的实质:操作系统的分配的一块内存,也被称为缓冲区.之前我们提及过
- 思考一个问题,发送方仅仅作为发送方还是仅仅作为接收方,如果缓冲区(窗口)被用来接受或者发送数据,那是基于一个窗口还是两个窗口?
虽然我们能遇到双方互相扮演对方的角色,这意味着窗口不仅仅是用来告知我的接受能力或者我的发送能力,并不是两个独立的窗口,而是单独的一个窗口
我们先来理解当扮演发送窗口时,窗口的作用和意义
此时发送方的窗口存放着发送方的数据:大致可分为这几块区域:已发送已经接收到ACK确认号的数据/已发送未接收到ACK确认好的数据/剩余的空间
- 注:窗口是按字节存放的
如果此时又有已确认的数据发送后确认,那么就会窗口移动,其实这是一种描述,已发送已确认的数据收到之后,会右移,其实右移也就是占据了剩余空间的部分,如果右移的部分并未超出剩余空间大小,即数据并不会被丢弃。
也有的地方把剩余空间拆开,或者说更详细的描述.拆分成未发送但等待发送,和遗留的空间,当然都是助于理解的划分方式,只要清楚窗口中数据的性质就能理解了.
再来理解扮演接受窗口时,窗口的作用和意义
此时接收方的窗口可能存着发送方的数据,大致可分为这几块区域:已接收并等待的数据/剩余空间
这里来理解一下对于接收方来说,一旦数据收到,其实这部分数据对于接收方自身而言就已经是已接收的数据,而至于等待不过是让发送方知道这个数据包我已收到,所以才这么命名.
至于流量控制在之前我们便思考过,通过更新窗口便可以实现流量控制,而窗口的更新时机其实是我们很关心的一个问题,但这种人算不如天算的问题,并不存在量子纠缠之间的感应(意思是数据包的传输需要时间),倘若接收方因为某些原因未能即使更新窗口大小,导致发送方认为你很能装,最后害的接收方被撑死也是可能的,所以可见流量控制其实只能一定程度上调节网络传包的问题
5.3拥塞控制
- 流量控制是根据窗口决定的,那窗口的告知需要通过网络传输,网络传输本就是不可控的,期间各种各样的事情都有可能出现,我们的TCP为了使我们数据传输的可靠性,选择了降低风险,既然不能那么急着发,那就慢点发,意思是数据们别内卷了,大致这样一个思想.
于是也有了一个新概念:拥塞窗口
拥塞窗口是发送方维持的一个变量,谁发包谁维护......
如果没有在规定时间内收到ACK确认报文,就会发生超时重传的可能,也就可能导致网络利用率低,出现拥塞的情况.这时我们的滑动窗口(cwnd)就会减小,反之,则会增大,当然,拥塞窗口也会根据发送方和接收方的窗口能力去参考,如果脱离数据传输,看网络,那简直是耍流氓.
所以,拥塞控制是有哪些算法?
- 慢启动
- 拥塞避免算法
- 拥塞重传
- 快速回复
这几部分每一个单独拿出来都很值得探究,网上有很多相关的资料和帖子,自行查看即可.
以上就是个人对于TCP整个流程过程的一些理解和看法,欢迎纠正并指出!
标签:协议,浅谈,ACK,TCP,发送,客户端,连接,重传 From: https://www.cnblogs.com/looktheworld/p/17425377.html