关于TCP协议一定要知道的
Posted 深林学苑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于TCP协议一定要知道的相关的知识,希望对你有一定的参考价值。
上篇简单写了下网络层的IP协议,以及两个直连的网络设备是如何相互通信,也简单介绍了下NAPT技术缓解了IPv4的资源紧张问题同时也阻碍了IPv6的推进,然后整体简单介绍了网络数据包是怎么从发送端被发送到目标端。
今天再继续简单了解下在IP协议之前的一个传输层协议,Transport Control Protocol,简称TCP。
首先,TCP是面向连接的协议,自然也有非面向连接的协议,比如同属于传输层协议的UDP协议,包括TCP下面的IP协议,也不是面向连接的协议。事实上也没有真实的连接,连接只是一个状态,这个状态由使用TCP协议来通信的两端主机自己来维持。
TCP提供的不仅仅是一个连接状态,还有稳定的数据传输,这也是TCP协议复杂的原因之一,在复杂的网络条件下,达到这样的目的,注定不会太简单容易。
所有的协议都是由最起码两部分构成:Head和Body,头部用来存储和协议相关的所有信息,数据体就是用来存放具体需要传输的数据了。
TCP的协议头东西还挺多的,不一一说明,只说下几个比较重要的。
一个TCP协议的数据包大概就是这样,头部中还有别的一些信息,这里仅仅列出一些比较重要关键的信息,每个字段占据的长度(字节数)就不具体标明了。
SourcePort和DestinationPort:之前介绍网络层的时候,并没有怎么提到端口,还是在解释NAPT时才提到端口。IP只能让即将进行数据传输的源主机通过一些路由器最终定位到目标主机,把数据包仍过去。我们知道现在的计算机都是多进程,就像以前的独门独户,现在变成了一栋楼,而这个数据包只能送到这栋楼下面。剩下的需要将这个IP数据包拆开,拆开后会看到上面说送到哪层哪户的,这个就可以理解为端口。端口的目的是为将TCP包送到指定端口所绑定的进程,这样进程才能对这个TCP数据包进行一系列的操作,没有端口,就无法知道这个数据包该由哪一个进程来接收。从更小的粒度看,可以认为是两台主机间的两个进程在通信,所以标识一个TCP连接需要四个要素:源端口,源IP,目标IP,目标端口。
Sequence Number:序列号,TCP实现有序传输的基础。为什么会需要这个?是因为底层的数据传输能力有限,不同的线材能力不同,称作MTU(Maximum Transport Unit),在进行TCP传输时,会使用整个线路中最小的MTU来作为TCP包的最大值,而通常我们一次需要传输的数据都比较大,所以要进行数据拆分。那么为什么要以路径MTU来作为最大TCP包的值?是因为如果TCP层面不这么做,数据链路层还有可能会对单个大于MTU的IP包做分片传输,在目标主机再进行重组。如果有必要的话路由器也会进行IP包的分片。TCP直接使用路径中最小MTU,就能避免更底层的分片操作,也能提高性能。
Acknowledgement Number:确认序号,通知发送端发送过来的数据包被成功接收到,用于解决丢包问题。一问一答的形式,有来必有回,才是所谓的面向连接。
Window:窗口,解决TCP的传输流量控制。如果每次都是发送-确认接收--发送-确认接收的循环,那么对带宽是一种浪费,尤其是等待确认消息耗费的时间以及可能发生的确认包丢失问题,严重影响TCP包的传输性能。于是就采用了一种方式,每次发送TCP包,不再是一来一回,而是发送方连续发几个包后,再慢慢的等前面几个包的确认信息。这就是所谓的窗口,也称为TCP滑动窗口,窗口大小就是可以连续发几个TCP包,这些包作为一个窗口数据被连续发送。
TCP Flags:用来标识这个包的类型,比如是建立连接的包,还是关闭连接的包,还是普通的数据包,这个值和TCP连接的状态有关。
Sequence Number的生成,是由系统生成的,保证每次初始化的序列号都不同就可以了,这里不是重点。
上图就是一个TCP连接建立的过程,也称作TCP的三次握手。
发起方称为Client,接收方称为Server。
服务端的端口处于Listen状态,客户端发过来一个建立连接的请求包,客户端发送后将自己的TCP状态设置为SYN-SENT。服务端接收到SYN后,会去初始化自己的SYN值,然后发送给客户端自己的SYN以及ACK,然后会进入SYN-RECEIVED状态。客户端在接收这个SYN和ACK后,会直接发送一个ACK之后立马进入ESTABLISHED状态。而服务端在接收到ACK后,也会直接进入ESTABLISHED状态。至此就完成了三次握手,建立了TCP连接。
连接建立后,就是进入具体的数据发送阶段
整个数据发送阶段,差不多就是这样,如果客户端数据传输完后,会带上标识PSH,表示将数据立即传给上层应用协议。也就意味单向数据传输完成。注意SYN的标识只会出现在连接阶段。ACK可以出现在连接/传输/关闭三个中。维持包的顺序正是通过seq的值和ack的值,来保证不会出现包的乱序。
窗口的大小不是不变的,是动态调整的,会发送窗口探测的包用于更新窗口大小,以避免接收方的缓冲区空间不足导致接收方丢弃接下来包,从而出现丢包的问题。滑动窗口解决的是流控问题。
关于包的丢失问题,如果发送的数据包丢了,有超时重传和快速重传两种。超时重传就是发送方在多久没有收到某一个包的ACK时,就重新发送这个包。快速重传是在接收方应该接收到某一个包时,却接收到了下一个编号的包,然后后面几次的ACK都是在重复丢失包的上一个ACK,这样发送方就知道需要重传这个ACK的下一个包。
ACK包也会丢失,丢失了就要去想办法确保它所对应的包收到了,这个问题这里不讲,不会,菜。
TCP连接也是一种计算机资源,资源都是有限的,用完了就得释放,所以一个TCP连接用完了就得去关闭。
上图展示了一个TCP连接由连接状态到关闭状态的过程,x和y表示两边在关闭前的序列号。TCP的关闭过程也称作四次挥手,其实看作两次也没有问题,无论是主动发起关闭方还是被动关闭方,都需要向对象发出FIN包以及接收对方响应自己FIN包的ACK包。
由于实际的传输过程中,可能出现后发送的包先到达的情况,所以TCP主动关闭连接的一方有可能先收到FIN包或者先收到ACK包,TCP协议并没有强制要求ACK包先到达,而是对这种情况做了额外的处理-通过新增状态。
这种被动关闭方的FIN先到达的话,主动关闭方就立即发送一个ACK,然后进入CLOSING状态,之后收到被动关闭方发来的对应ACK后,进入TIME-WAIT状态,随后等待超时,进入最后的关闭。
由上面的过程可以看到,主动关闭方需要等待超时后,才能进入最终的关闭。
如果在实际使用中,作为服务端的应用主动关闭TCP连接的话,会产生大量处于TIME-WAIT状态的TCP连接,占用系统资源。因此最好由客户端来发起Close操作,而不是由服务端来发起。
下图是一个实际的TCP报文
抓的一组包里,没有找到最后服务端响应的ACK,虽然客户端在没有收到ACK时,重发了很多次FIN。
关于TCP的东西实在是太多了,这里只是针对正常情况下没有丢包,网络拥堵等问题时,描述的一个理想状态下的TCP工作过程,目的是可以了解TCP的一些基础知识。至于TCP实际使用中遇到的各种问题,解决方案,提升性能的各种算法,需要额外的功夫去学习钻研。
最后再说下TCP和Socket的关系,其实我们说到Socket时,不是在使用TCP就是在使用UDP,每一个TCP连接或者UDP发送/接收消息时,都会有一个与之对应的Socket的对象,这个是由操作系统来做的,Socket对象负责从TCP或者UDP的包中读取数据和写入数据。在Linux下,一个端口可以有多少个TCP连接,并没有具体的上限,取决于操作系统能打开的最大文件句柄数(Linux下每一个资源都是一个文件句柄)。
TCP的基础知识就先说这么多,以后可能遇到实际问题时再说吧,文中难免有错误和疏漏,不可尽信,仅供参考。
参考书籍:图解TCP/IP 【日】竹下隆史
左耳朵耗子的博客:https://coolshell.cn/articles/11564.html
以上是关于关于TCP协议一定要知道的的主要内容,如果未能解决你的问题,请参考以下文章