TCP详解

计算机网络

在如今的社会中,我们无时无刻不在使用着网络,如此重要的一个网络,其中的工作原理以及细节是怎样的呢?

怎样才叫学好计算机网络了呢?我认为就是明白了从一个数据包的发送之前到发送之后的细节,最终到达目的地。

  • 我们所熟知的网络是一个巨大的分组交换网络,从发送方发出的数据可能被分为多个数据包,然后通过路由器的存储转发,最终到达接收方。

TCP/IP参考模型

  • 网络是有分层的,底层给上层提供服务,上层使用下层提供的服务来实现自己的功能。以TCP/IP 5层模型为例来观察网络结构:
    TCP/IP 参考模型

从底向上看

  1. 物理层:将上层包装好的数字数据,转换成物理信号(光电信号),然后传送出去,在传输介质中运输。
  2. 数据链路层:包装上层的数据,加上帧头和帧尾,然后传递给下层发送出去,这一层的数据单元为帧,在本层工作的设备是交换器(Swtich),依赖数据链路层及其下层的服务,可以实现局域网内的点到点的信息传输。
  3. 网络层:包装上层的数据,加上包头(IP首部),然后传递给下层,这一层的数据单元为包,在本层工作的设备是路由器(Route),依赖网络层及其下层的服务,可以实现,跨网络的主机到主机的信息传输。
  4. 传输层:包装上层的数据,加上报头(TCP或UDP等等的首部),然后传递给下层,这一层的数据单元为报文(TCP为报文段,UDP为数据报),本层及本层以上,传递的网络设备几乎已经不会到达了(可能还是会有设备去修改,这里就不介绍了,例如NAT转换时的路由器),这里都是OS在做了,发送方发送之后,中间是不会修改的,依赖传输层及其下层的服务,可以实现跨网络的进程到进程的通信。
  5. 应用层:这就是最上层的,即提供最一开始数据的层次,程序员一般都是在这一层做自己的工作,然后通过下层的服务,把自己的数据发送出去。(这里最经典的协议就是HTTP协议)
  • 我们可以看出来,这一层一层的封装,然后在一层一层的解包和分用,就完成了数据的传递。发送方就是在不断的封装数据并发送出去,接收方就是在不断的接收数据,然后解包和分用。、

端口

刚刚在上面讲到了进程和进程之间的跨网络通信,那么如何标识一个主机中的一个进程呢?

  • 我们并没有使用进程号来标志进程,我们使用的是一个新的名称叫做端口。
  • 端口号是一个16位的无符号整数,端口号分为3中,①熟知端口号(0至1023)②注册端口号(1024至49151)③动态端口号(49152至65535)。我们通常使用的HTTP服务就是80端口,HTTPS则是443端口,等等一系列,端口,当服务端的端口固定之后(且服务器打开该端口之后),我们才可以更方便的去使用他的服务,而我们的端口则可以是动态的,我们的进程的端口如果不自己去申请,则操作系统会从池中挑出一个未使用的来给我们使用。
  • 一个进程可以绑定多个端口,但是一个端口只能被一个进程绑定。
  • TCP的端口和UDP的端口是不同的,所以,他们是可以拥有相同的端口号的。

UDP协议

UDP 用户数据报协议

  • 无连接的
  • 不可靠的
  • 面向数据报的
  1. 无连接的:TCP中详细讲解什么是连接
  2. 不可靠的:①发送出去的数据,无法确保其可以到达目的地②发送出去的多个数据,到达的时候,不知道先后顺序
  3. 面向数据报的:应用层传递下来的包会被完整的发送出去(除非太大,被UDP裁剪了),所以只要接收方收到,就是一个完整的包。

TCP协议

TCP传输控制协议

  • 有连接的
  • 可靠的
  • 面向字节流的
可靠的
  • 通过确认应答机制,保证发送出去的数据,对方一定收到了
  • 有了确认应答,怎么知道应答的是哪一个报文段,通过对报文段进行编序号的机制,序号机制同时也应用于接收方对数据的排序,也就可以解决数据无序到达的问题。
  • 如果我们没有收到应答报文:

①我们的包丢了
②接收方发送的应答报文丢了

这两种情况对于发送方可以统一处理,就是包丢了,我重新发一份就好了,对于第一种情况,他就是需要我们的重发,对于第二种情况,我们重新发送过去的包他已经收到了(根据序号就可以判定),他会把我们的包丢弃或者丢弃原来的保存本次的新包(不同的TCP协议栈实现略有不同),然后再次发送一个确认信息。

  • 所谓的没有收到应答报文,其实是有时间判定的,等到固定的时间就不等了,认为已经丢失了,这个机制叫做超时重传

对于超时重传的时间,现在TCP的实现是一个动态的计算方法,每次的数据传输都是样本,所以时间都是动态变化的,但是,基本是就是往返时间多一点点,大致就是这样的时间,如果时间设定比往返时间还短,那么所有包都会触发超时重传,这显然是不行的,如果超时时间过长,又无法保证其时效性,这个包早就丢了,却迟迟不予重发,这显然也是不行的,这个时间的设定应当是确定包大概率已经丢了,就可以重发了。

有连接的
  • 通信双方共同维护这同一个连接对象,数据现在传到哪里了,哪些数据是确认过的,哪些需要重发,哪些不能发等等信息。
  • 如何去理解 连接 这个呢?

在操作系列里面,进程的概念和这里的连接有所类似,都是比较抽象的一个概念。操作系统为了管理一个一个程序的运行实例,其实就是需要去管理一些数据,进程id是多少,占用了哪些数据,程序执行到哪里了,优先级情况怎么样等等就是一组一组的数据。然后,连接其实也是一组一组的数据,在这里可以理解操作系统在管理网络连接的时候,也是管理着一组一组的连接数据或者一个一个的连接对象。

  • 现在就可以去说TCP和UDP对于连接这里的区别了,TCP是有连接的,就是说通信的双方在通信前是已经“建立连接”的,这里的建立连接不是真正的电气建立连接,而是虚拟的,而UDP是无连接的,就是说通信双方在通信的时候,是没有任何连接的,只是简单的把数据发送出去,至于发送多少,发送的数据,收到没有,等等的一些信息都是不管的,可以简单的理解,UDP的发送方只管发。
面向字节流的
  • 上面说到,UDP是面向数据报的,一次发送就是上层所提供的完整数据,然后作为一个包发送出去,与之对比的就是这里的TCP,他是面向字节流的,那么什么是面向字节流的?
  • 因为,TCP协议的实现,他是保证尽可能的去完成数据的传递的(在忽略下层的影响下,在本层的视角下,是可以保证的),所以对于从应用层传下来的数据,他是绝对不可以丢弃多余的(比MTU大),但是下层又存在一个MTU的存在,一个包又放不下,那么只能对数据进行拆分,然后进行编号,在接收方进行重组了,就是这里的拆分,就造成了一个问题,TCP就只能做成面向流的,在接收方接收到数据包的时候,他们可能是不完整的,所以这里的数据就抽象成了流,字节的水流,你看待数据的时候,他就是水流中的一段,那么数据一定是有边界的,该怎么读取呢,就需要应用层在明确一个边界,接收方通过这个边界去区分一个一个的数据单元,完成读取,HTTP就是典型的一个案例,请求行、请求头部、空行和请求数据这四部分之间都是有一个换行符存在的,接收方就可以依靠这种来完成信息的识别。
TCP的报文格式

TCP报文格式
刚刚上面支持可靠性的一些字段,在这里可以找到,自己可以找找。

TCP协议的连接管理

刚刚说到了,TCP的是有连接的,他就是在管理一组一组的连接对象,那么具体是怎么管理的呢?

  • 首先,看一幅图,TCP的生命周期:
    TCP的生命周期
  • 为什么需要连接建立阶段?
  1. 因为连接对象中有一些信息需要对方告诉你,这里可以理解需要建立一个对象,对象的初始化,需要一些信息,需要对方提供。
  2. 这些信息,包括,MSS(最大报文段长度),窗口大小,起使序号等等一些连接建立必需的信息。
  3. 注意:这里的起始序号实际不是从0开始的,而是操作系统计算给出的,为什么,不从0开始呢?因为,如果每次都是从0开始,连接的中间方就可以进行序号攻击,不从0开始,可以提高攻击的难度,而且同一连接的2次建立,所传递的数据也是不受干扰的。
连接的建立(3次握手)

TCP的连接建立,采用3次握手的机制

TCP三次握手

  • 其中左右两侧的为TCP连接的状态,那么为什么需要状态呢?
  • 很简单,我们使用状态来标志着现在的连接是什么状态,才可以进行特定的动作,就比如,只有双方都处于ESTABLISHED状态才可以进行数据的传输。说白了,就是为了更好的管理连接。
  • 解释图中流程:

首先,在被动连接方,需要主动打开特定的端口(比如Web服务器,打开80、443端口)随机进入LISTEN状态,主动连接方,就可以随时发起连接请求,(第一次握手)主动连接方需要发送的第一个报文,需要把SYN字段(同步)置1,并且携带来自操作系统计算的Seq序号,SYN置位说明这是一个连接请求同步报文,该报文携带了一些连接的必要信息,供同步使用,然后进入SYN_SENT状态,当被动连接方收到这个报文之后,进入SYN_RCVD状态,(第二次握手)同时发送自己的响应报文,同时携带自己的必要信息,所以该报文既需要确认上一个报文又需要完成一些同步功能,因此,ACK和SYN字段同时置位,其中确认号为上一个报文的序号+1,以完成对上一个报文的确认,自己同时也需要携带自己的序号,也是由系统随机生成。当主动连接方收到由被动连接方发送的报文之后,进入ESTABLISHED状态,(第三次握手)同时发送确认报文,即ACK置位,确认号为收到报文的序号+1,完成对其的确认,同时,该报文的序号字段为自己第一个发送报文的序号+1,当被动连接方接收到该报文之后,进入ESTABLISHED状态,至此,连接正式建立,连接建立阶段完成,进入连接可用阶段。

  • 为什么连接的建立握手是3次,而不是2次,1次,4次或者其他呢?
  • 我们说过,TCP是可靠的应用层协议,所以,为了完成数据的交互,连接的双方需要把自己的信息提供给对方,所以需要发送2次,再加上确认应答机制,就需要2个确认,因此,需要4次,所以2次和1次太少了,无法完成数据的安全传输。但是,为什么又说是3次握手呢,因为,在这4次中,第二次和第三次是可以合并的,即对连接发起方的确认报文和自己的信息报文,其实是可以合并的,这依赖于TCP报文的格式,是可以完成这个动作的,而要想再压缩是不可能的。
  • 连接的建立这3次握手中,可以携带数据吗?
  • 我们观察3次握手的图,可以发现,前两次握手,对于连接的双方发送数据包的时候,连接都没有建立,因此这个时候,传递数据是不靠谱的,而且如果第一次允许携带数据,其实是很危险的行为,连接的建立,双方需要把这个包完整的接收下来,那么如果第一次携带了数据,黑客以这种手段,频繁的对服务器发起连接的建立请求,服务器就需要把大量的数据包都接收下来,服务器的压力就太大了,攻击手段却很简单。第三次握手其实是可以携带数据的,因为对于连接的主动发起方,他已经进入ESTABLISHED状态了,站在他的视角连接其实已经建立了,因此这个时候可以携带一定的数据。

连接建立以后,就进入连接可用阶段了,就是双方不断的通信的过程,当通信需要结束的时候,又该如何结束呢?

连接的释放(4次挥手)
  • 连接的释放,可以直接断开连接吗?为什么还要进行所谓的连接的释放这么一个阶段?
  • 举个例子:当你和人进行聊天的时候,你把你的话说完,你就直接走了。不管别人听清楚没有,听到没有,别人有没有要说的话,你就直接消失了,这显然是不行的。所以,在TCP的连接管理中,连接的释放也是需要进行管理的,连接的主动释放方,发出连接释放报文之后,被动释放方可能还会有数据要进行传输,或者是一些应答确认报文的重发等等,所以是不可以直接进行走人的这种行为的。

TCP四次挥手

  • 解释图中详细过程

首先,连接的双方都处于ESTABLISHED状态,连接的主动释放方,(第一次挥手)发起FIN置位的报文,即发起连接释放的报文,表明自己想要释放连接,同时自己进入FIN_WAIT_1状态,当连接被动释放方收到报文之后,(第二次挥手)给予ACK报文,即对收到报文的确认,代表自己已经收到释放的消息,同时自己进入CLOSE_WAIT状态,而对于这个报文,连接主动释放方收到这个报文是不会给予确认应答的,但是他会进入FIN_WAIT_2状态,此期间连接被动释放方会把自己要发的消息或者是自己的处理工作做完之后,(第三次挥手)发出一个FIN置位的连接释放报文,同时也会再次确认第一次握手的报文,同时自己进入LAST_ACK状态,即等待最后一个ACK报文,连接主动释放方收到这个FIN报文之后,(第四次挥手)发处对该报文确认的报文,同时进入TIME_WAIT状态,等待的时间一般为(2MSL),连接被动释放方收到这个ACK报文之后,进入CLOSED状态,连接关闭,在连接主动释放方等待完成之后,也会进入CLOSED状态,至此连接全部关闭就,完成通信。

  • 连接主动方就是连接主动释放方吗?
  • 答案是:可能是,也可能不是,连接的建立关系和连接的释放关系没有任何关系,他们是分离的,所以说,谁都可以释放连接。
  • 为什么连接的主动释放方要等待2MSL?
  • 首先先解释一个名词,MSL(Maximum Segment Lifetime)最大报文生存时间,即在互联网中,一个报文的最长可以存活的时间。我们必须假设网络条件并不好,那么假如,连接主动释放方的最后一个ACK报文丢失了,那么连接被动释放方会认为自己的FIN报文丢失,重发了FIN报文,但是,此时连接主动方却已经把连接关闭,这样连接被动释放方就会一直处于LAST_ACK状态,这是我们不希望看到的,所以,主动连接方在发出最后一个ACK报文之后,需要等待,以防止ACK出现丢失的情况,那么具体要等待多久呢?答案就是需要等待2MSL的时间,因为我们的ACK报文最长需要经过1个MSL的时间到达了对方,而对方的超时重传的FIN报文,最长也需要经过了1个MSL传到了我们这里,因此如果在这2MSL里面,我们没有收到报文,那么我们就可以认为连接被动释放方已经正常的释放连接。同时,在这个2MSL中,我们也可以接收到,在通信过程中,滞留在网络上的报文,也避免了下一个连接建立之后,收到这些可能会导致错误的报文。
  • 第二次挥手和第三次挥手是可以合并的
  • 如果,连接的被动释放方,在收到FIN包之后,发现自己没有什么事情要干,是可以直接释放连接的话,那么第三次挥手和第四次挥手是可以合并的,效果图如下:
    四次挥手2
  • 如果连接的双方同时发起FIN怎么办?
    四次挥手3
  • TCP的状态转换图
    TCP状态转换图
    可以结合上图,去理解如何进行状态的转移。
  • 服务器上存在大量的连接处于TIME_WAIT状态,如何解决呢?
  • 首先,这是一种不合理的现象,会消耗服务器的大量内存。因为TIME_WAIT状态是只存在于连接主动释放方的,因此,解决方案就是,服务器不要作为连接的主动释放方。
  • 服务器上存在大量的连接处于CLOSE_WAIT状态,如何解决?
  • 造成这个问题出现的原因可能有2个:
  • ①编写该段代码的程序员,压根就没有写close()方法,因此,当连接主动释放方,发出释放申请的时候,服务器这边就直接进入CLOSE_WAIT状态,等待close方法的执行,但是,不存在close方法。解决方法,就是重新修改代码。
  • ②服务器进行了大量的文件读写操作,让服务器过于卡顿,导致,close方法不能尽快的执行,都卡在读写方法上了。解决方法,扩容。
TCP的发送控制
  • 首先,我们现想一想,为什么TCP要对发送进行控制?
  • 既然TCP具有可靠性,在可靠性中,存在确认应答的机制,但是有这个还是不够的,我们可以试想,如果发送方很快的发送大量的数据,接收方可以来得及接收吗?答案很明显,是不一定的,而且大概率是无法接收这么多的数据的。所以,TCP需要对发送量进行控制,以及MSS的设置。对于接收方的接收能力,TCP存在流量控制,接收方的能力有了之后,还是不够的,网络条件不好怎么办呢,TCP又存在拥塞控制,那么接下来就来看一看,这两个控制。
  • 由于TCP需要对发送的数据进行控制,因此,不是将上层的数据立即发送的,所以存在一个发送缓冲区,当然也存在接收缓冲区,那么发送缓冲区是个什么样子呢?
    发送缓冲区
  • 在连接的建立阶段,就已经完成了发送窗口和接收窗口的同步,在以后的正常通信过程中,如果窗口不进行更新,那么能发送的数据的量就一直不会变,直到接收方,发来窗口的更新,窗口才会滑动起来,这个机制被叫做滑动窗口机制,这是一个抽象的概念,代表着在发送的过程中,区域的变换能力。而发送窗口代表着发送方对该区域的控制力。拥塞窗口代表着当前的网络情况。接收窗口代表着接收方的接收能力。如果窗口更新,那么已确认的部分可能就会变为可用空间,整个缓冲区(无论是发送缓冲区还是接收缓冲区)其实是一个循环队列,一直在转圈圈的利用空间。
  • 在流量控制中,主要就是通过,不断的更新窗口大小,来完成对流量的控制。
  • 而在拥塞控制中,需要不断的采集数据包的应答确认时间,来完成对网络状况的评估,以完成对拥塞窗口的修改,那么如何修改呢?
  • 慢开始算法:
  • 所谓慢开始算法,就是一开始拥塞窗口从1开始,每收到对一个报文的应答确认,窗口大小就+1,直到到达一个起初设定的阈值,然后开始执行拥塞避免算法。
  • 拥塞避免算法:
  • 执行拥塞避免算法的时候,每收到一个应答确认的包,窗口大小就+1,这里的最大窗口大小是无上限的,然后一直增加一直增加,直到遇到网络拥塞,即出现很多的超时重传包,代表此时网络发送拥塞,此时窗口大小锐减到1,阈值变为发生拥塞时的窗口大小的一半,然后重新开始执行慢开始算法,到达阈值之后在执行拥塞避免算法,就这样一直往复。
  • 快重传机制与快恢复机制
  • 快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。而当发送方一连收到3个重复的确认,就将阈值减为当前窗口的一半,然后开始执行拥塞避免算法,我们可以这么理解:我们一连收到了3个重复的确认,说明现在的网络情况其实还好,仅仅可能只是不下心丢了这一个报文段而已,不至于直接将窗口下降至1,这个机制就叫做快恢复机制。

滑动窗口的大小 = fun(拥塞窗口,发送窗口),这里的fun一般为 MIN.

  • TCP和UDP的对比
  1. 其中最鲜明的区别就是,TCP是可靠的,而UDP是不可靠的。
  2. TCP是面向字节流的(为了保障可靠性,不得已的,是个缺点),UDP是面向数据报的。
  3. TCP是有连接的,UDP是无连接的,对于这一点,为了可靠性,TCP引入了对连接的管理和对发送数据的控制,又为了管理这些东西(数据),所以才有了连接的概念。对于上层来说,通信执行必须先进行连接的建立,在实时性要求比较高的场景下,可靠性带来的低速是无法忍受的,况且,这些场景对数据的出错和丢失是可以进行一定的容忍的,所以,UDP在这种场景下使用却变加合适了。同时UDP还拥有另一个优点,就是可以进行多播和广播,在直播方面有着非常好的应用场景,这是TCP所不及的。

因此,其实这两个应用层协议都挺好的,他们适用于不同的场景,随着社会的发展,网络条件越来越好,TCP好像变的不是那么需要了,对UDP适当的在应用层添加一些处理,好像更能适合当前的网络,因此,当我们需要用到应用层协议的时候,我们只需要会选取那个合适的就好。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值