[linux] Linux网络之TCP协议详解

Posted 哦哦呵呵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[linux] Linux网络之TCP协议详解相关的知识,希望对你有一定的参考价值。

目录

1. 传输层

数据是如何从一个主机发送到另外一个主机上,数据从计算机开始如何传输到网络中,这就是传输层干的事情了。传输层服务于它的上层协议应用层,而我们所编写的程序,全部都是应用层的应用程序,传输层及其底层的所有通信细节我们是看不到的。

2. 端口号

端口号标识了一个主机进行通信的不同的应用程序。一台计算机上可以同时运行多个应用程序,如何标识每一个应用程序,每个应用程序绑定一个或多个端口号,就可以标识出这个应用程序在计算机中。

通过IP地址、端口号就可以确定唯一的一台主机上的一个进程

3. TCP协议

3.1 TCP协议的特性

3.1.1 面向连接

TCP提供棉线连接的通信传输,即在数据通信开始之前先准备好通信双方的连接工作。连接建立完成后,才可以进行数据传输。

建立连接的过程 --> 三次握手

问题:为什么需要三次握手才能建立连接?

  • 从双方的连接状态来看,当两次握手后,客户端认为连接建立以完成,但服务端认为连接没有建立完成,并且该连接还在内核中的未完成连接队列中保存,即使程序员调用accpect函数,也不会将该连接从内核中取出。
  • 并且一次后两次握手容易受到SYN洪水攻击
  • 还可以使用最小成本验证全双工

断开连接的过程 --> 四次挥手

服务端

  • [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT;

  • [CLOSE_WAIT -> LAST_ACK] 进CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)

  • [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接

客户端

  • [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入
    FIN_WAIT_1;
  • [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段;
  • [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK;
  • [TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态.

CLOSE_WAIT状态

  • 被断开连接方才会存在CLOSE_WAIT状态
  • 如果服务器进入CLOSE_WAIT状态,说明客户端的应用层不再向服务端发送消息,而其下层协议还会给服务器发送断开连接的确认帧。在CLOSE_WAIT到LAST_ACK中,需要被动断开连接方调用close函数,并且被动方还可以向主动断开连接的一方发送数据
  • 如果服务端有大量的CLOSE_WAIT状态,则说明服务器程序中没有调用close函数关闭连接

TIME_WAIT状态
主动断开连接的一方,要进行等待,即要进入TIME_WAIT状态

为什么要进行等待?

  • 尽量保证最后一个ACK被对方收到,进而尽快释放服务器资源。
      如果最后一个ACK丢失,那么被动断开连接方,不清楚FIN报文是否到达主动断开连接方,从而过段时间又会重新发送断开连接的报文,就又会进入TIME_WAIT(超时重传机制
      如果等待了2MSL时间,也没有等待到重传的FIND报文,说明ACK数据一定到达了被动断开连接方
  • 等待历史数据在网络上消散
  • TIME_WAIT = 2MSL
    MSL为网络数据传输的最大时间
    2MSL: ACK数据的MSL + 被动断开连接方重传的FIN报文MSL

注意
  处于TIME_WAIT状态的程序,还需要将端口占用着,等待2MSL之后再释放端口。防止被动断开连接放重传FIN报文。
端口复用
  当服务端作为主动断开连接的一方,并且服务端进程退出了,想要快速启动服务程序,就会出现端口无法绑定的情况,服务端中的端口时写死的,所以服务端进入TIME_WAIT状态后,还需要等待2MSL才能将端口释放(进程虽然已经退出,但是当前的端口还在被网络协议栈所占用)。
  就会出现服务端再次启动时就会提示端口绑定失败,可以将对应端口设置为端口复用的方式,就能避免这种情况的出现。

设置端口为端口复用的方式
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

3.1.2 可靠传输

  TCP通过校验和、序列号、确认应答、重发控制、连接管理、滑动窗口等机制实现可靠传输。

序列号
  TCP将每个字节的数据都进行了编号,接收端查询接受数据TCP首部中的序列号和数据的长度,将自己下一步想要接受的序号作为确认应答发送给对端。

ACK确认序号:

  • 客户端维护了一套序号,当客户想要给服务端发送数据时会使用该序号进行标识数据。
  • 服务端也维护了一套序号,当服务端想要给客户端发送数据的时候,使用服务端序号标识数据。
    两端接收到该数据之后,会给客户端进行确认。进行确认的逻辑是"期望收到客户端的下一个序号的数据包"


结论

  • TCP当中维护了两套序号,客户端维护了一套,服务器维护了一套
  • 双方在给对方发送数据的时候,是消耗自己维护的序号
  • 接收方在确认发送方的数据时,是以期望下一序号的方式进行确认
  • 双方在三次握手当中协商各自发送序号的起始位置,但是双方的起始位置不一定从0开始,可以从任意位置开始,只要后续遵守起始位置进行发送即可
  • 纯ACK不消耗序号,不消耗序号就说明不用对方再次发起确认,否则服务端就会和客户端一直相互确认
  • TCP在发送数据时,数据的每一个字节都会由序号进行标识。如果一次发送了较多字节,那么序号就为这次的字节的个数,接收方给发送方回应时,从上次序号后发送给数据方

3.1.3 面向字节流

传输时以字节为单位进行数据传输。

从发送数据的角度理解
   应用层的应用程序在发送数据的时候,时调用send函数将应用层数据递交给传输层TCP协议之后,数据暂时保存在发送缓冲区当中。TCP在发送数据的时候,不一定是按照应用程序调用send函数的规律来进行发送的。TCP不关心上层发来的数据,而是将数据放到缓冲区中,等到合适的时机进行发送。读取的数据不会直接发送,而是由操作系统内核进行管理,等到合适的时机发送。

从接收方的角度理解
  当数据到达接收方的接收缓冲区之后,接收方的应用程序调用recv函数可以接收任意个字节

面向字节流会导致数据之间没有明显的边界,导致粘包问题

粘包问题
TCP不会处理粘包问题,而是由应用层去处理数据包与数据包之间的边界,按照要求去读取数据包内容,通过在应用层自定制协议解决粘包问题。

3.2 TCP报头

  • 源端口号: 表示发送端的端口号
  • 目的端口号: 表示接收端端口号
  • 序列号: 发送的每条消息都有一个序号,保证消息都是按序到达的,每一发送一条数据则序列号就会+1,序列号不会从0或1开始,而是在建立连接时由计算机生成随机值。
  • 确认应答号: 指下一次期望收到数据的序列号,对发送方发送的序列号+1,发送给发送方,以保证对发送方的序号对应的消息已经收到。
  • 数据偏移: 指的是首部长度,表示该TCP头部的大小,而这个数据偏移指的是,从头部向后多少个字节是有效载荷。
  • 保留位: 为了后期扩展使用
  • 控制位:
    CWR ECE: 用于IP首部的ECN字段,ECE标志位为1时,通知对方已将拥塞窗口缩小。
    URG: 紧急标志位,当前包中携带的数据需要被有限处理
    ACK: 确认标志位
    PSH: 发送数据标志位,标志位为1表示需要将收到的数据立刻交给上层协议,为0时,不需要立即传输而是先进行缓存
    RST: 强制断开连接标志位
    SYN: 请求建立连接标志位
    FIN: 断开连接表示位,为1时,表示不会再发送数据,希望断开连接,通过4次回收断开连接。
  • 窗口大小: 发送方接受缓冲区程序空间的大小,即下次所能接受数据的最大长度。用于流量控制。
  • 校验和: 校验数据是否准确
  • 紧急标志位: 当URG标志位为1时有效,该字段表示报完段中紧急数据的指针。
  • 选项: 这里需要注意MSS选项

MSS 最大报文长度
  用于再建立连接时决定最大段长度,以确定发送数据时应发送多少字节的数据。
  TCP双方在三次握手的过程中协商最大报文段长度的大小。采用双方最小的MSS,作为后续传输数据的最大报文段长度。

  • TCP在传输数据时,严格按照MSS进行传输
  • MSS的大小和本地网卡息息相关
  • MSS + IPHeader + TcpHeader = MTU
    TCP在每一次发送数据时,数据的大小都不会超过MSS。
    (MTU: 最大传输单元,数据链路层对网络数据的显示)

3.3 TCP连接管理及可靠性问题

3.3.1 可靠性

  绝对可靠性: 在互联网中是没有绝对可靠性的,因为总会有最新的一条消息得不到确认。
  相对可靠性: 只要收到了对应的应答,就认为之前的数据对方已经收到。

3.3.2 保证可靠性的机制

1. 确认应答
  上文三次握手与四次挥手就详细的写了确认应答机制如何运作的,当发送方向接收方发送了数据包时,接收方接收到了数据包,就会向发送方会送确认序号,序号表示下一次想接受的数据是多少,如果接收方没有向发送方发送确认应答,那么经过一段时间后就会触发超时重传机制。

2. 超时重传
  当发送方发送数据之后,就会启动一个重传计时器,当重传计时器记录的时间超过重传时间,并且没有接收到确认应答,就会触发重传,重新发送该报文。
  达到超时时间后,发送方就会认为数据已经丢失,并进行重发。即使产生了丢包,仍能保证数据能够到达对端,保证了可靠性。

  未收到确认应答,不一定是数据丢失,也有可能是确认应答丢失,只要发送端没有收到确认应答,就认为数据丢失,一律进行数据的重发。

超时时间一定是固定的吗?

如果超时时间是固定的,那么在网络情况较差的时候,接收方的响应可能在路上还未到达,发送方会认为对方没有收到报文,就会进行重发,这种无用的重发,会增大网络压力,所以超时时间一定不固定。

  • RTO(超时重传时间) RTO = 2 * RTT(报文往返时间)
  • RTT(报文往返时间) RTT(本次) = RTT(上次) * 0.9 + RTT(上上次) * 0.1

3.3.3 保证效率的机制

3.3.3.1 滑动窗口机制

  TCP协议以1个段为单位,每发送一个段就要进行一次确认应答,收到ACK后再继续发送下一个数段,那么包的往返时间越长性能就越差。
  既然一个段一个段的发效率会很低,那么就一堆一堆的发,既可以解决效率问题,所以就出现了滑动窗口机制。

1. 概念
  该机制允许一次性向网络中发一个分组的数据,分组中的个数就是滑动窗口的大小,暂时不需要确认应答,等待数据全都发送出去之后,等待确认应答回来。
  滑动窗口本质就是一块连续的空间,两个下标分别指向窗口的两端。


1. 滑动窗口如何滑动
  以上图为例,发送方一次性发送了4组数据,发送数据时并没有等待确认应答,而是先一次性将数据发送到网络中慢慢等待应答的到来。收到应答之后,将窗口滑动到确认应答中序列号的位置,继续进行发送,一次发送一个窗口大小的数据。
  滑动窗口之外,包括尚未发送的数据和已经确认对端收到的数据,当数据发送后若收到了确认应答就可以不用再进行重发,将数据冲发送缓冲区中清除。

2. 滑动窗口丢包

情况一: 丢失某个分组的ACK

  • 这种情况中,只是某些分组的ACK丢失了,但是后续的ACK收到了。那么发送端没有看到前面分组的ACK,看到了后面分组的ACK,ACK的序号就是下一次期待收到报文的序号,那么发送方就默认前面分组的数据全部收到,不需要进行重发

情况二: 丢失某个分组的数据

如果分组数据丢失了,就一定会进行重传
  当前方有一个数据包丢失,后方到达的数据会被放入接收方的接收缓冲区中,如果没有收到重传数据包,那么缓冲区中的数据是不能够被应用层使用的,必须等待重发数据包到达后才可以被上层协议使用。
  当丢失某一段报文后,发送端会一直收到序号未丢失报文序号的确认应答,提示发送端这个报文丢失了,没收到一个报文都会重发该丢失报文的序号,当发送端连续三次收到同一确认应答,发送端就会对其对应的数据进行重发。快重传

问题?
1. 在TCP双方发送数据的时候,TCP发送发所维护的窗口大小是一成不变的吗? 流量控制
  发送端根据自己的实际情况发送数据,但是接收端缓冲区满了,或者处理速度较慢,那么发送端发送的数据会被丢弃,丢弃之后又会触发重传机制,造成网络的负载变大。

发送方的窗口大小是实时变化的,却决于双方发送接收的能力。
  TCP协议字段中有一个"16位窗口大小"的字段,该字段是消息接收方告知消息发送方自己所可以接收数据的大小。当该字段到达发送方后,发送方分析窗口大小,调整窗口的大小。
  当TCP的接收方接收到数据后,会将数据缓存在TCP的接收缓冲区中。TCP接收缓冲区的大小并不是无限制的大,一旦应用层不调用recv函数从缓冲区中拿数据,那么缓冲区就会逐渐变小,直至溢出。窗口大小的值也会被设置为一个更小的值发送给发送端,从而控制数据发送量。

  如果接收缓冲区满了,那么给发送端告知的窗口大小就为0,发送端就会停止发送数据,何时开始发送数据呢?看下文的窗口探测。

2 0号窗口
  当发送端接收到接收方发送的窗口大小为0时,就会将滑动窗口的大小调整为0,暂时不发送数据给客户端。

何时恢复发送方发送数据?

  • 接收方处理完数据之后,会向发送方发送窗口更新通知,发送方继续发送数据
  • 超过超时时间还没有收到接收方发送的窗口更新通知,发送方就会主动给接收方发送窗口探测包,询问对方的接收能力(探测包大小为固定的1字节)。

3. 根据网络与硬件状况确定窗口大小

  • 网络传输过程中,数据是在”共享网络“中进行传输的,网络被多个网络手法双方所使用,同时转发多个人的数据
  • 网络中的路由器/交换机,转发能力是有上限的
  • 网络链路当中的传输介质是有传输上限的

总结

  • 滑动窗口取决于对方的窗口有多大即接收能力和拥塞窗口中的最小值
  • 窗口大小指的是无需等待确认应答就可以继续发送数据的最大值
  • 发送第一个窗口大小的数据时,无需等待ACK,直接发送
  • 收到第一个ACK后,滑动窗口向后移动,继续发送后续的数据,以此类推
  • 操作系统为了维护滑动窗口,需要开辟缓冲区来记录还有哪些数据没有收到应答,只有确认过应答的数据,才能从缓冲区中删掉
  • 窗口越大,网络的吞吐量越高
  • 如果收到了后面分组的确认应答,但是没有收到前面分组的应答,则窗口不能向后滑动,防止前面分组的数据在网络中丢失了

3.3.3.2 拥塞控制

  解决了上述的滑动窗口,能够高效发送大量数据,如果一开始就发送大量的数据,仍然可能会引发网络的拥塞。在不了解网络状况的情况下不能直接发送大量数据。
  TCP为了避免这种问题的出现,在通信开始前引入了慢启动的机制,对窗口大小做限制。

拥塞窗口
在发送端调节所要发送数据的量,定义了一个叫做”拥塞窗口“的概念。

  • 在发送开始时,定义拥塞窗口的大小为1
  • 每次收到一个ACK应答,拥塞窗口大小 + 1
  • 每次发送数据包时,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小值作为实际发送的窗口大小

最终发送的数据量 = min(发送方窗口大小(取决于接收方的接收能力), 拥塞控制机制当中拥塞窗口的大小(取决于网络拥塞程度))

慢启动
  概念: TCP双方在建立连接之后,先发送少量的数据探测网络转发能力(根据TCP数据的往返时间确定),根据网络转发能力逐渐调整拥塞窗口的大小。

慢启动过程

  • 拥塞窗口的大小随着传输轮次的增加,拥塞窗口呈指数级增长
  • 慢开始门限,在拥塞窗口大小小于满开始门限的时候,呈指数增长。大于时呈线性增长
    当拥塞窗口超过慢开始门限,则切换为拥塞避免算法,拥塞窗口的大小呈线性增长。

总结

  • 数据无穷多时,拥塞窗口增长由指数增长转变为线性增长,向网络当中丢入数据量是依次增大的,但是网络的转发能力是有限的。就会导致TCP数据丢包(一旦发现有数据丢包,发送方就会认为网络发生了拥塞,就会立即下调拥塞窗口大小)
  • 当TCP发现网络拥塞之后,将拥塞窗口调整为1,慢开始门限调整为发生拥塞的一半,继续慢开始。
    快恢复:一旦发生网络拥塞,则重新计算慢开始门限,从新的慢开始门限执行拥塞避免算法。

3.4 其它机制

3.4.1 延迟发送(Nagle算法)

TCP中为了提高网络利用率,使用Nagle算法。指发送端即使还有应该发送的数据,但是如果数据很少的话,就会延迟发送,等待数据量的增多。发送数据的条件是:1.已发送的数据都已经收到了确认应答;2.可以发送最大段长度(MSS)的数据时。一般在使用中都关闭该算法,会影响实时控制领域。

3.4.2 延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.

  • 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
  • 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
  • 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
  • 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;

   窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
  那么所有的包都可以延迟应答么? 数量限制: 每隔N个包就应答一次;时间限制: 超过最大延迟时间就应答一次;具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms;

3.4.3 心跳保活机制

  • 在连接空闲的时候,就会启动一个保活计时器,记录连接空闲时间,一旦当连接的空闲时间超过2小时,则主机B的TCP协议会主动给主机A发送保活探测包。
  • 如果主机B一直没有收到探测包的应答,则会每隔75s发送一次,共计发送10次
  • 如果10次探测包都没有回应,则认为对端状态已经不正常,就会主动断开连接
  • 如果有应答,则认为连接正常,重新启动保活计时器。

3.4.4 捎带应答

  在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的. 意味着客户端给服务器说了 “How are you”, 服务器也会给客户端回一个 “Fine, thank you”; 那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端。

3.5 TCP异常情况

1. 进程终止
  会释放文件描述符,进行四次挥手。因为进程终止了,系统更需要回收资源,并且套接字描述符本质上就是一个文件,所以系统使用close关闭了该文件,底层就是实现了4次挥手的过程,释放连接后,系统正常回收资源

2. 服务器重启
  与进程终止过程相同。

3. 机器掉电/网线断开
  接收端认为连接还在,一旦接收端由写入操作,接收端就会发现连接不见了,会进行rest强制断开连接。即使没有写入操作,在TCP内部内置了一个保活计时器,定期询问对方状态,状态异常,断开连接释放资源。

以上是关于[linux] Linux网络之TCP协议详解的主要内容,如果未能解决你的问题,请参考以下文章

Linux之netstat命令详解

[Linux] Linux网络之数据链路层详解

[Linux] Linux网络之数据链路层详解

[Linux] Linux网络之数据链路层详解

[Linux] Linux网络之网络层协议详解

Linux性能监控命令之lsof详解