Loading

TCP学习指北

限于博主水平有限不敢说指南,但应该能够避免刚学TCP的同学出现找不着北的情况。

TCP与UDP的区别

区别:

  • UDP是无连接的,而TCP是面向连接的,传数据前要先建立连接。
  • UDP可以一对多,多对多通讯,而TCP只能一对一。
  • UDP使用最大努力交付,即不保证可靠交付。而TCP是可靠交付数据,并且有拥塞控制和流量控制机制。
  • UDP是面向报文的,适合一次性传输少量数据。而TCP是面向字节流的

总的来说UDP不太靠谱,自管把数据发出去,丢了也不管。就好像两个人说话,其实一个人只管自己说自己的。

UDP拟人

相比之下,TCP就靠谱多了,它通过序列号和确认应答机制保证连接的可靠性,任何一方向对方发送消息(SYN),都要收到回应(ACK)。相当于两个人正常聊天,接下来的谈话内容会根据对方的反应做出调整,比如对方开小差没有听到你刚才讲什么,就重复一遍刚刚说的话。

TCP拟人

确认应答

上图写的只是相对序列号(相对于初始序列号,序列号如果一直是一个固定值,会增加被攻击的风险),初始序列号(ISN)的确定是在连接建立也就是三次握手阶段。

三次握手

同步序列号

握手的首要目的是告知对方自己的初始序列号,只有同步了序列号才能实现可靠传输,比如拥塞控制,消息重发等等。

虽然两次握手就能同步双方的序列号,但无法保证客户端是否接收到了服务端发过去的初始序列号,所以我们需要进行第三次握手来确认。

双方都需要确认一次自己发过去的序列号对方有没有收到,三次握手之后双方就都能明确自己和对方的收发能力是正常的。

实际上任意次的握手都不可靠,三次是双方互相明确对方收发能力的最低值。这 一点可以看看两军问题

三次握手首部数据

第一次握手:客户端在给服务端数据通信之前,会发送连接请求并等待确认应答,此时TCP首部SYN为1,seq为随机生成的序列号(X)。

第二次握手:服务器接受到连接请求之后,会进行答复这时SYN仍为1,确认应答号ack为X+1,表示你发过来的请求我接收到了。同时服务器也会生成一个随机序列号y写入seq,等待客户端的确认应答。

第三次握手:前两次都是通过TCP首部发送SYN包作为建立连接的请求,并未携带数据。三次握手的最后一次握手可以在报文段负载中携带数据,并且因为连接已经建立,所以在之后的报文中SYN都为0(包括最后一次握手),双方正式开始通信。

前两次握手都不能携带数据,并且需要消耗掉一个序列号,也就是seq要加一。

第三次握手可以携带数据,如果不携带数据则不消耗序列号。也就是说三次握手结束后,客户端发送的第一个报文中的seq仍然为x+1

实际上,本来是四次握手的

Alice ---> Bob SYNchronize with my Initial Sequence Number of X
Alice <--- Bob I received your syn, I ACKnowledge that I am ready for [X+1]
Alice <--- Bob SYNchronize with my Initial Sequence Number of Y
Alice ---> Bob I received your syn, I ACKnowledge that I am ready for [Y+1]

因为二三步的接收对象相同,所以可以合为一步

认识TCP首部

突然出现SYN,seq这些字母你可能感觉晕乎乎的。特别是ACK和ack,更是让人傻傻分不清楚。现在就来认识一下他们吧。

TCP首部1

图中的序列号就是seq(发送数据的顺序编号),它的值是本报文段所发送数据的第一个字节的序号。比如这次发送的数据包是由文件的第1-100个字节,那么seq的值为1.

小写ack对应确认应答号,表示期望收到对方下一个报文段数据的第一个字节的序号。比如已经收到1-100个字节数据后,那么确认应答号就应为101,这样发送端下次就会发送101字节及其之后的数据过来。

需要注意的是只有控制位ACK为1时,确认应答号ack才有效。

防止历史连接扰乱通讯

如果你有过打游戏突然掉线或者异常卡顿的经历,我相信你对网络通讯并不像“如意如意,随我心意”那么顺利有着深刻的认识😣

假设我们对服务器发送了连接请求,但因为物理链路选择的问题,迟迟没有到达。超过了等待时间之后,重发了一次请求,这一次很快就顺利建立起连接了。但没过多久之前那个请求报文风尘仆仆的来到服务器这,也建立起了连接。

不过还好初始序列号生成的算法比较巧妙:

ISN=M+F(本地IP,本地端口,目的IP,目的端口)

M是过四毫秒就加一的计时器,F是根据上面四个参数进行映射的哈希函数

这样就能保证客户端能过凭借序列号判断这是否是一个过期连接(通过跟记住的最后接收到的seq进行比对)。

如果是,则发送RST报文给服务端,终止这个过期连接。不会让服务端还傻傻的等着客户端发数据,避免了资源的浪费。

从这里也可以看出,序列号seq是多么重要。因为TCP没有时间戳,所以通过同步双方的序列号来“校准双方的时间”就显得尤为重要。

TCP的可靠连接是通过序列号和确认应答来实现的

SYN洪水攻击

SYN洪水攻击(SYN flood attack)利用了TCP协议 3次握手的原理,攻击者发送大量建立连接的网络包,但却不实际建立连接(不进行第三次握手),导致服务器的连接资源耗尽。

这种攻击因为SYN cookie技术的产生,在现代网络已经不奏效了。

SYN cookie的具体做法是:

服务器只在握手完成之后分配资源,在这之前,它会根据特殊的算法生成一个cookie(seq序列号)发给对方。服务器本身并不记忆该cookie。

因为该cookie的生成与对方的IP和端口号有关,所以服务器可以凭此重新算出cookie,并与发过来的报文段中的ack进行比对。

如果对方是合法的连接,服务器就生成一个具有套接字的全开连接。如果不合法,那咱也不亏,服务器没有为它分配过任何资源。

一般SYN cookie只在半连接队列爆满的情况下才会启用。除此之外,常见的防范方式还有SYN Proxy防火墙,Syn Cache技术等。

多亏了序列号和确认应答

滑动窗口

如果传输比较大的数据,一般会将其分为小包,进行分段传输。如果每发送一个数据包都要等待对方确认之后再发下一个,这段时间内什么都不做,看起来太浪费了。

TCP采用滑动窗口来避免资源的浪费:在等待ACK号的这段时间,继续发送后面的数据包。如果收到了ACK号,那么就将该ACK之前的数据包从缓存中清除。

滑动窗口

不过这样做有些问题,因为对方可能无法在短时间内处理大量的数据包,导致后来的都被丢弃(缓冲区溢出)。所以在连接的时候,双方应该要明确发送和接收能力。

在TCP首部的可选字段中,双方会协商好能够接收的最大数据量(一般称为窗口大小),它的值一般与接收方缓冲区大小相等。

连接的两端都有各自的接收缓存和发送缓存,无论哪方都能同时收发信息,因此TCP提供的是全双工服务。

双工通讯

拥塞控制

拥塞控制的目的是防止过多数据传输,使得传输链路过载,甚至造成瘫痪。在网络畅通的时候,我们就多发点,猛踩油门;网络差的时候,就别一个劲的发送大量报文添堵了。

TCP采取慢启动、拥塞避免、快速重传、快速恢复来进行拥塞控制。

拥塞控制

  • 慢启动:
    刚建立起的连接,拥塞窗口大小从默认初始值开始,大胆进行试探性指数增长。
  • 拥塞避免:
    当达到慢启动阈值(ssthresh)时,再进行指数增长显得有点鲁莽了。这时候我们改变策略,采取加法的形式谨慎增加cwnd
  • 快速重传:
    当发现收到四个相同的ACK时(检测到三个冗余ACK),进行快速重传。即立马发送这个丢失的报文段,无需等待超时重传定时器。
  • 快速恢复:
    发生快速重传之后就会进入快速恢复阶段,此时ssthresh=cwnd/2,cwnd=cwnd/2,之后迁移到慢启动阶段。

四次挥手

断开连接的步骤居然要比建立连接还多,这是怎么回事?难道不能像握手那样:

  • 客户端告诉服务器我要断开连接了
  • 服务端回复好的,并且告诉客户端我也准备断开了
  • 客户端收到服务端的确认之后,再给服务端发送一个确认消息

这样三步不就解决了吗?

断开连接与建立连接还是有些不同的,因为建立连接之前,双方是没有任何通讯的。但在结束连接的时候,双方可能还存在着没有传输完成的数据包。

所谓上山容易下山难,多加一次挥手是为了防止出现误操作。

如果是三次挥手,这边客户端要求服务器重发的请求刚出去,就收到对面说断开连接的消息,实在是让人错手不及😰

所以我们得分四步进行。

四次挥手

并且连接关闭之后,并不会马上删除相关数据。这是为了防止乌龙事件的产生。

如果因为网络原因,最后一步ACK丢了。服务器迟迟未收到客户端对其断开连接的确认,重发了FIN。

但好巧不巧,客户端的新连接就建立在这个之前断开的端口号上。得,执行断开操作,全乱套了。

posted @ 2020-08-10 09:32  AD_milk  阅读(566)  评论(1编辑  收藏  举报