一.TCP协议格式
对于传输层协议我们之前是学过了UDP,对于传输层协议是存在了一定的了解的,所以现在我们再来看TCP协议格式:
我们之前学过UDP的报文格式,所以源端口和目的端口是不需要进行再次讲解的,对于32序号和确认序号目前我们是先无法学懂的,所以我们可以先将目光焦距到保留六位---即6个标志位:
URG: 紧急指针是否有效
ACK: 确认号是否有效
PSH: 提示接收端应用程序立刻从 TCP 缓冲区把数据读走
RST: 对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文段
SYN: 请求建立连接; 我们把携带 SYN 标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带 FIN 标识的为结束报文段 后续我们可以对他们进行详细讲解,所以这里先大概了解下是哪6个即可!
16 位校验和: 发送端填充, CRC 校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含 TCP 首部, 也包含 TCP 数据部分
4 位 TCP 报头长度: 表示该 TCP 头部有多少个 32 位 bit(有多少个 4 字节); 所以TCP 头部最大长度是 15 * 4 = 6016 位紧急指针: 标识哪部分数据是紧急数据
下面我们会来一一详细讲解
二.三大机制
1.确认应答机制
也叫ACK,在TCP传输的数据中,TCP会对每个数据字节进行编号,称为序列号,而在每次reponse时,ACK都会携带确认序号,如上图中的确认序号,例如:在收到的报文序号为2000,那么响应报文中的确认序号就是2001(前提中间未发生丢包)
确认序号的意义:告诉发送者自己已经收到了那些数据,并且下次从哪个位置发送信息
2.超时重传机制
当我们发送的数据包在网络传输过程中丢失,此时就需要将数据包重新发送,中间需要一个特定的时间间隔,方便确认是否需要重传
注意点:超时重传机制的判断是通过是否收到ACK应答,如果ACK应答丢失,是不会重发ACK应答的,该部分不属于超时重传机制,超时重传机制是针对发送方的
如果是ACK丢失,发送方在规定的时间间隔中就会重新发送数据包,然后等待ACK确认,但是大家要清楚的是我们有确认序号,TCP 协议能够识别出那些包是重复的包, 并且把重复的丢弃掉,并且我们可以利用前面提到的序列号, 就可以很容易做到去重的效果
现在问题来了:这个时间间隔我们如何确定呢?
大家都知道网络是存在波动的,高峰期显然传输需要时间更长,低谷期显然传输效率更快,那么一个合适的时间非常重要,此时TCP使用了动态时间设置,下面我们以linux为例:
当我们第一次发送数据未收到ACK响应时,以500ms位单位,重发一次,再无法收到响应时,以500ms*2为时间间隔,第三次仍未收到响应时,时间间隔为500ms*4,后面依次类推,500ms*2的n-1次方,直到累计到一定次数,TCP认为网络出现拥塞,进行拥塞等待,当然也可能是主机出现问题,关闭连接
3.连接管理机制
对于该机制,我们主要就是讲解大家可能都听过的三次握手和四次挥手
三次握手:
我们首先来先了解下tcp协议中的保留位:
我们知道ACK是应答,那么其他5位呢?
SYN:是指请求建立连接
FIN:是指请求断开连接
URG:确认紧急指针是否有效(后序会讲解紧急指针)
PSH:提示接收端程序从tcp缓冲区中将数据取走
RST:对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文段
要注意的是:对于上面6位,如果对应位置设置为1表示该符号位生效,否则为0
如果此时被问起为什么需要保留位,相信大家答案就非常明确了,因为只有知道不同报文的作用才能知道处理什么问题
现在我们可以来讲解三次握手,如下图:我们联系之前学过的知识,现在来仔细理解下三次握手:
最开始时,客户端和服务端同时处于CLOSED状态,然后客户端想要建立连接,通过socket函数生出fd,然后通过connect(fd,服务器端口)开始连接服务器,与此同时,服务器从CLOSED状态通过socket函数同样得到一个fd,称之为listenfd,然后bind(listernfd,自身端口号),接下来通过listen函数保持监听状态,当客户端发送的SYN包到达服务器时,调用accept函数,将自身状态转换为SYN_RCVD,然后向客户端发送响应包ACK和建立连接的请求SYN,当connect函数收到ACK+SYN时,将SYN_SENT状态转换为ESTABLISHED,同时再次发送响应报文ACK,接下来服务器收到ACK后,也将自身状态转换为ESTABLISHED,同时accept函数返回分配新的描述符,开始双方通信
现在问题来了,为什么不是两次建立连接呢?即客户端收到ACK就直接建立连接,不需要再次发送ACK到服务端?
假设我们第一次发送的SYN在传输过程中发送阻塞,然后客户端第二次发送SYN,如果客户端收到发送ACK给客户端,两者直接建立
为什么不是四次连接呢?
我们发现三次连接就可以确定连接是否建立好,所以完全不需要浪费效率
现在我们要重点知道的是:如果某次传输中丢失数据会如何?
当客户端第一次发送SYN丢失时,相当于连接请求丢失,在一定的时间间隔后会重新发送请求,后面我们会对时间间隔进行详细讲解
当服务端的响应报文SYN+ACK丢失时,在一定的时间内客户端未收到响应报文,会再次重发SYN,当服务端收到SYN时,重新发送响应数据,从而直到客户端收到
客户端收到响应数据后,发送ACK响应服务端,如果该响应在传输中丢失,那么服务器就会重新发送SYN+ACK响应数据,此时客户端收到该响应数据时就会知道刚才发送的数据ACK丢失,重新发送,从而确认连接成功建立
以上就是tcp三次握手的过程,下面我们来讲解四次挥手:
如上图(自己画的不标准请见谅)
下面我们先来讲解具体过程:
1.当客户端(两台中任意一台)主动申请断开连接,会响服务端发送一个FIN数据,并且将自身established状态改变成为FIN_WAIT_1
2.当服务端收到客户端发送的FIN数据后,服务端发送ACK响应报文,并且将自身状态改变成为CLOSED_WAIT状态
3.当客户端收到响应报文,将自身状态变成FIN_WAIT_2,等待服务端结束报文
4.服务端调用close函数,向客户端发送一个FIN,表示断开连接,然后将自身状态变成LAST_ACK,等待客户端最后一个ACK到来
5.客户端收到FIN数据后,将自身状态变成TIME_WAIT,然后发送响应报文ACK
6.服务端收到响应报文后,将服务器对应closed,客户端在等待2msl后,也closed
下面我们再来讲解下如果中间某个过程数据在传输过程中丢失该如何解决:
我们将上图中四个发送红线分为四个步骤:
1.该步骤数据丢失时,即服务器未收到FIN,在一定的时间间隔客户端未收到响应数据,会重新发送FIN
2.该步骤丢失(即ACK响应报文丢失),还是客户端收不到响应报文,结果如上1
3.客户端发送的FIN在传输过程中丢失,在规定的时间间隔中未收到ACK响应,会重新发送FIN
4.ACK丢失,服务端重复步骤3
以上就是四次挥手过程
三.相关时间概念
上面我们多次提到了时间间隔,现在我们就对上面内容进行时间上知识的补充
上面我们是写过一个时间2MSL,MSL是指maximum segment lifetime,在网络通信过程中规定主动断开连接的一方处于TIME_WAIT,然后等待2MSL才能进入CLOSED状态。
其中MSL是指tcp报文最大生存时间,这样如果我们将等待时间确认为2MSL的话,在双方向的数据传输就会保证全部传输结束,同时也是在理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失, 那么服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然可以重发 LAST_ACK
现在大家发现一个问题没?如果我们终止一个tcp连接方,在2MSL时间内是无法使用该对象的端口号的,这也是说明了进程结束但是tcp连接不是立即断开的
关于MSL具体是多少时间,在不同的系统上是不一样的,Linux-centos7上是指1min
下面我们来看这么一个问题:
如果一个服务器需要短时间内处理大量的客户端连接,如果是服务端主动断开连接,那么就会存在大量的TIME-WAIT,每个连接都会占用一个通信五元组(源 ip, 源端口, 目的 ip, 目的端口, 协议). 其中服务器的 ip 和端口和协议是固定的. 如果新来的客户端连接的 ip 和端口号和 TIME_WAIT 占用的链接重复了, 就会出现问题,那么这个问题如何解决呢?
我们可以使用setsockop()解决问题,如下:
setsockopt()设置 socket 描述符的 选项 SO_REUSEADDR 为 1, 表示允许创建端
口号相同但 IP 地址不同的多个 socket 描述符至于我们网络丢包的时间间隔大体和MSL相似,也是网络的一个设置,可以固定一个时间,便于我们网络传输
四.滑动窗口和流量控制
该滑动窗口可不是之前我们算法中的内容,对于TCP滑动窗口大家需要重新学习:
在上面我们讲解了确认应答机制,但是大家可能会发现如果我们是一发一收的方式效率太低了,那么有没有其他办法提高效率呢?
是的,存在的,tcp给出了滑动窗口的方式解决该方案效率低下问题,具体如下:
我们现在假设一次发送多个数据,然后服务端并不是每次都响应,从而实现多发一送的方式,这样就提高了效率,但是我们如何确认数据传输中有没有数据丢失呢?
现在我们来具体来看看滑动窗口的实现,再来解决上面这个问题:
假设我们数据每次传输1000字节,然后4次一起传输,不需要等待ACK,当我们4个数据都发送完,可能会收到第一个ACK,这样通过ACK中的确认序号,如果确认序号的数字不是4001,说明接收端的内容在某处传输失败,这样通过窗口移动,从失败位置再次发送四个数据,如果发送的数据没收到ACK,这样发送端重新发送内容即可,这样就可以实现滑动窗口发送信息,提高效率
我们都知道tcp是存在发送缓冲器和接收缓冲区的,现在我们就可以来理解为什么需要发送缓冲区了?
你可能会认为是为了提高传输效率,当然这是一个原因,但不是主要的,实际上主要还是为了编号,联系上面我们学的滑动窗口,我们知道传输是按照一定的编号的,因此我们需要再发送缓冲区对数据字节编号,这样方便定位和发送
下面我们思考下面两个传输丢包问题:
1.如果ACK丢失,该如何解决?
我们知道ACK是为了确认信号给发送方,也就是说如果这次tcp发送方未收到另一方的ACK应答,可以继续发送后面一个窗口的内容,当下一次的ACK收到后,我们只需要查看确认报文是否与发送缓冲区最后对应的序号相同即可
2.如果发送的数据包丢失,该如何解决?
很显然这是一个不得不处理的问题,但是实际上处理方式还是和我们想象的不一样的,当ACK表示存在丢包问题后,发送方不会立即处理该问题,而是继续发送后面的窗口内容,当连续收到三次ACK都表示存在该问题后,才会去窗口移动的确认报文位置处理该问题,这种机制称为高速重发机制
下面我们来讲讲另一个话题:流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应,此时就需要我们处在一个机制来控制这种情况,这就是流量控制,定义:通过接收端的处理能力, 来决定发送端的发送速度的机制
那么该机制是如何实现的呢?具体如下:
接收端将自身缓冲区大小通过ACK发送给发送方,这也是我们TCP协议格式中窗口大小需要填写的内容,这样发送发就可以知道对方的接收能力,从而控制,当接收缓冲区快满时(也可以到达一定程度时),ACK应答会改变窗口数据大小,从而使发送方减少窗口大小,当接收方缓冲区内容减少到一定程度后,又会增大ACK中窗口数据大小,这就是流量控制
特殊情况:
如果接收端缓冲区满了, 就会将窗口置为 0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端
补充知识:我们知道tcp数据格式中窗口大小是有一个 16 位窗口字段,那么问题来了, 16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么?
实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口字段的值左移 M 位
五.拥塞控制
我们知道网络在传输过程中共同用一个网络,所以网络的拥塞情况非常重要,TCP为了解决该问题,引入了慢启动机制,简单来说就是如果我们在连接刚建立的时候就发送大量数据时非常容易发送拥塞的,导致传输失败等情况,而TCP在连接刚建立的时候先通过发送少量的数据来探测网络情况,这样就可以情况来控制传输速度
具体是如何实现的呢???
我们可以来这样理解,我们定义一个概念:拥塞窗口
当TCP连接建立后,发送方将拥塞窗口设置为1,然后当收到一个ACK后,就将拥塞窗口大小+1,当下次发送时,比较拥塞窗口和接收端主机反馈的窗口大小, 取较小的值作为实际发送的窗口,通过这样的方式,实现了慢启动,但是增长速度快的方式
再引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长
具体如下图所示:
六.延迟应答和捎带应答
首先我们先来讲解下什么是延迟应答?
来看下面这个问题?
我们知道接收缓冲区的存在,现在我们假设其大小为1M,而接收端能够在200ms处理500K,现在我们如果接收端收到数据后立即就换回窗口大小,即500K,那么接收的数据就会显得有点小,但是如果我们延迟200ms应答返回ACK,这样就可以将窗口大小设置为1M
这样网络吞吐量就越大, 传输效率就越高. 而我们的目标是正是在保证网络不拥塞的情况下尽量提高传输效率;
但是这也不意味着所有的包都可以延迟应答,需要看时间处理下面我们再来看看捎带应答:
捎带应答就非常好理解了,我们将发送的信息和响应一起发送回去就可以看成是一次捎带应答
最后我们来进行TCP和UDP对比:
TCP 是可靠连接, 那么是不是 TCP 一定就优于 UDP 呢? TCP 和 UDP 之间的优点和缺点, 不能简单, 绝对的进行比较:
TCP 用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
UDP 用于对高速传输和实时性要求较高的通信领域, 例如, 早期的 QQ, 视频传输等. 另外UDP 可以用于广播;
归根结底, TCP 和 UDP 都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体
的需求场景去判定
以上就是TCP全部内容,感谢大家的支持!!!