TCP 协议的相关特性

TCP有许多可信特性在这里我们只讲解一部分的特性后续将会进行补充。

 一,确认应答

    接收方收到数据之后,就要给发送方返回一个应答报文(ack),TCP引入序号和确认序号的(由于TCP是面向字节流的所以此处的编号是按照字节编号的,并且由于编号是连续递增的,所以对于一个TCP数据报来说,知到了数据部分的第一个字节序号就知道了后续所有的字节的序号,序号只针对TCP数据报携带的载荷来进行编号的报头不参与)是应答报文和传输的数据可以对应上,TCP中32位序号描述了载荷(数据)部分第一个字节的序号是多少。

     由于我们一直TCP是面向字节流的所以确认序号的设定方式就是按照字节来编排的:

    在这里TCP的确认序号是接受方收到的数据最后一个字节序号的下一个序号,在这里表示的含义1到1000的数据诸如此类。

    在这里主机A就是发送放主机B就是接受方在数据1~1000发送B后B返回了一个1001就是下一个数据序列号的起始,由于此处给予了发送放一个信号也就是确认应答因此表明1~1000的数据已经接收完毕。

    在这里我们序号特别了解一个问题那就是“后发先至”TCP可以针对接收方收到的数据进行重新排序,read读到的数据一定是和发送方发送的数据是一致的!!!比如接收方先收到的数据是1001~2000这是接收方虽然收到了这个数据但是read并不会解除阻塞状态,直至收到了完整的一套数据(包含1~1000和其他的数据TCP会自动会这些数据进行排序使其与发送方的数据保持一致)。

二,超时重传

    实际上网络传输并不是一帆风顺的,网络阐述会存在丢包问题,传输过程中会受到许多外来因素的影响,到时接收方并没有接收到信息,或者接收的信息有误,在这里超时重传的作用是在一定时间之内,没有受到接收方的反馈ack那么就确认数据丢包了但是此处并不能确认是哪一种情况,如果在一定时间之内没有受到ack那么机会重新发送数据。

    当然进行确认应答是也可能会出现丢包的现象如果是由于ack的原因导致发送方以为发送的数据并没有安全到达接收方被其收到那么就发送方就会重新发送一份数据,此时如果这一份数据安全到达的被接收方受到那么由于之前已经接收到了这个数据(接收方有一个接收缓冲区接收的数据都会放到缓冲区中)收到的数据就会按照序号寻找相应的位置如果这个数据已经在缓冲区中存在那么这个数据就会被丢弃了(确保应用程序调用read读取出来的数据是唯一的不重复的)。

    超时重传的时间限定:这里的时间不是固定的而是动态变化的,每多重传一次超时重传的收到函件间隔就会变大/频次减低,当多次重传之后依旧没有顺利到达,说明网络丢包率很高,可能是网络发生了严重故障大概率是没法再使用了,此时再重传也就没有意义了,这就说明重传不会永无止境的进行,当重传次数达到一定程度后TCP就不会再尝试重传了,这个连接就断了,此时TCP会先尝试进行“重置/复位连接”发送一个特殊的数据包“复位报文”如果网络这会恢复了复位报文就会重新建立连接,使通信可以正常进行,如果网络还是有严重问题,那么复位报文也没有得到回应对此事TCP就会单方面放弃连接(连接就是通信双方保存有对方的信息,发送方将之前接收方的相关信息释放掉,这个连接也就断开了)。在这里我们只关注内容而不关注参数。

    确认应答和超时重传相互补充共同构建了TCP“可靠传输机制”。

三,连接管理

    连接管理的核心内容就是建立连接和断开连接俗称三次握手四次挥手这里的次数指的是网络通信的次数,这里的握手和挥手只是形象的比喻没有时机的含义。另外这里的握手和挥手不涉及载荷只包含报头,起到打招呼的作用。我们先来用一个图来表示建立连接这个过程:

       这里的SYN是同步的意思并不是我们之前所学到的synchronized(互斥),在这里对于气筒TCP的一些状态我们不做过多了解,在这张图中显示的是建立连接的过程也就是三次握手的过程但是这里说的三次握手其实是将服务器方的两个过程分别是SYN和ACK这两个过程都是在操作系统内核中完成的,因此这两个过程可以合并,合并自然也是有合并的好处,因为在网络组传输过程中需要不停地进行封装和分用因此会消耗一定的时间,分两次发送会使效率打折扣。建立连接是一个“双向操作”,双方都必须保存对方的信息,咱们此处的连接只是抽象的链接并不是物理意义上的链接。

    那么三次握手既然存在那么一定有它的作用:

              一,投石问路,初步验证通信双方的链路是否畅通(也是进行可靠传输的前提条件)。

              二,确认通信双方各自的发送能力和接收能力是否正常。

              三,让通信双方在进行通信之前对通信过程中需要用到的一些关键参数进行协商。

    四次挥手(优雅地断开连接):我们前面所说到的超时重传时也讲到了断开连接但是它的断开连接是“单方面的释放”,而五次挥手则是双方各自把对端的信息删除掉(断开连接不一定是客户端主动地也可能是服务器主动地)。

     通信双方通过四次的网络通信是的建立的连接断开,它的过程也是十分明了的,,这里的四次挥手与三次握手的差别在于接收方返回的ACK和FIN这两个过程是分开的就说明这连个过程无法进行合并,这是因为ACK是在操作系统内核中完成的而FIN是在应用程序中完成的,两者的触发条件不同,这两个过程不在同一时机,因此两者是不能合并的,因为这两个过程的时机不同因此,在这两个过程中会存在时间间隔,如同代码中的socket.close(),就是接收方发送FIN的操作,而这个操作如何使用在哪里使用,是由程序员决定的。

    三次握手和四次挥手的完整过程如下:

    这两个操作中间的过程是业务数据传输,不做重点了解。另外TCP的状态就是紧挨着链条竖线的左右的字母表示,我们在这里只需要了解几个关键的状态。

    USTEN   服务器进入状态,此时服务器把端口绑定好了相当于进入listen状态了,此时服务器就已经初始化完成了,准备好随时迎接客户端了。

    ESTABLISHED    客户端和服务器都会进入状态,TCP连接建立完成(双方都保存了对端的信息),接下来就可以进行业务数据的通信了。

    CLOSE_WAIT  被动断开连接,被动一方将会进入这种状态(先收到FIN的一方),随后等待代码执行close方法(如果此时有大量的CLOSE_WAIT状态的TCP连接说明代码可能有BUG,排查close是否写了,以及是否及时执行了)。

    TIME_WAIT  主动断开连接的一方会进入这种状态,此处的TIME_WAIT按照时间来等待,达到一定时间之后,连接也就释放了。

     在TIME_WAIT中威慑呢不直接释放而是要等待一段时间呢?这是因为防止丢包,如果一段时间之后由于丢包接收方没有收到ACK,此时发送方由于TIME_WAIT扔在等待过程中并没有释放,因此还可以在一定时间内重新发送ACK(重传)使接收方进行释放。

四,滑动窗口

    由于实现可靠传输就需要付出一定的代价,牺牲效率来换取可靠传输,这里的滑动窗口就是为了在可靠传输的基础上来提高效率(这里的提高效率只是亡羊补牢是传输效率的损失尽可能降到最低,但是不可能是传输效率比UDP还高)。

    刚才我们讨论了确认应答的一种策略简单来说就是一问一答:

    但是这种方式明显的缺点就是每一次发送数据都会返回一个ACK,由于网络传输是一个不断地封装和分用的过程所以进行多次这样的过程效率是非常低的,因此我们引入了滑动窗口来尽可能的提高传输的效率。

    既然我们已经知到效率降低的原因因此滑动窗口就是从这方面下手的:

 

    这种改进方案就是把“发一个等待一个”改成“发一批等待一批”,把多次等待ack的时间合并成一份时间了,批发的数据越多,此时的效率可以认为是越高(批发发送的数据,不需要等待的数据的量称为窗口)。

    

    我们可以理解为主机A的窗口就是批量发送的数据,如果主机B收到了1001~2000的数据那么主机B的窗口就会向右移动,也就是窗口的大小没变只是窗口的位置变了,此时等待ack的范围就是2001——6000,当收到2001的ack就说明1001——2000的数据得到了应答,然后立刻发送5001——6000这个数据。

    这种情况下自然也会出现丢包的问题,其中丢包也会出现几种情况:

        一,数据报已经抵达ACK丢了

            由于此处我们是批量发送ACK多个ACK只是丢失其中的一部分不可能全部都丢了,在这里我们就需要充分了解确认序号的喊一个,表示收到的数据后一个字节,也就可以理解为如果确认序号之前的都收到了那么接下来要发送的数据就从确认序号这里往后发。也就是如果2001-3000的数据已经到了那么1001-2000的数据也已到了,返回接收到2001-3000的ack也就证明了20001之前的数据已经收到了,后面的ack可以确定前面数已经收到。

          二,数据报丢失
  

      如果数据包直接丢失那么接收方在多次返回的ACK都是相同的确认序号那么主机B就会发现其中哪一段的数据丢失了,那么之后返回的ACK就不再是后续的确认序号而是丢失的那一部分的的确认序号,相当于主机B在向主机A索要丢失的数据(其他已经顺利传输的数据不会进行重传),之后主机B就会重新传输这个确认序号的数据,当重新传输之后就会将该确认序号的数据补回来之后就按照正常的操作数据传到哪里,就返回它下一个的确认序号,这个过程称为“快速重传”。

五,流量控制

    滑动窗口的窗口大小对于传输数据的效率是直接相关的,但是窗口的大小并不是越大越好,这是由于通信是双方的事情,发送方发送的多了也要看接收方能不能及时的处理这些数据。

    接收方会将接收的数据放到接收缓冲区(内核中的空间,每个socket对象都会有一个缓冲区)中,这个缓冲区类似于一个阻塞队列如果发送方的速度太快而接收方的接受速度比,较慢,那么这个缓冲区很快就满了,那么此时如果发送方仍然发送数据,那么这些数据将会丢失(丢包),相当于被接收方丢弃了,通过这个例子我们就可以知到要让接收方和发送方达到一种平衡,两者进行相互制约,避免出现这种情况,在这里我们通过“定量”的方式来实现制约的,也就是看缓冲区的剩余空间大小,如果剩余空间越大,那么即可以为应用程序处理的速度比较快,那么发送方就可以发送的快一些,设置一个较大的窗口,在TCP的基本结构中有一部分是16位窗口大小这部分就体现了接收方的接收缓存区剩余空间的大小(只有当ACK报文中ACK这一位为1的时候才有效),当然这个窗口大小也不是固定的,选项中可以设置一个特殊选项“窗口拓展因子”关系为 发送方窗口大小 = 窗口大小 <<窗口拓展因子。

    当数据不断地进行传输是返回的ack中也会携带有接收缓冲区的剩余空间大小,当剩余空间大小为0时此时发送方就不会继续传输数据了,但是过了一段时间发送方会发送一个窗口探测包来探测接收方的缓冲区是否有空间,接收方也会返回一个窗口的更新通知如果还是满的那么还是不会发送信息,直到缓冲区有剩余空间后才会重新传输数据(由于更新窗口通知十分重要如果丢包则会导致无法继续沟通信息,锁一个发送端主机时不时的就会发送“窗口探测包”)。

六,拥塞控制

    拥塞控制与流量控制有一定关联的,如果说流量控制是站在接收方的角度,那么拥塞控制就是站在传输链路的视角老限制发送方的速度。

    在发送方向接收方传输数据的时候,中途会有许多的传输节点,由于这些传输节点的存在导致每一次进行数据传递的时候走的线路都可能会有所不同,加入一个节点本身负载已经很高了,此时发送方仍然发送的很快(因为很快所以极大概率还是会经过这个节点)此时这个节点很有可能就会丢包了,拥塞控制就是为了解决这一问题。

    如果考虑到这些中间节点呢么这个问题就非常麻烦了,在这里我们可以通过做实验的方式来找到一个合适的发送速度(面多加水,水多加面)。

    因为如果我们将这些中间节点分别进行分析那么这样是非常麻烦的,因此我们把这些中间节点看成一个整体,这样解决起来就十分简单了。

    做实验时我们先按照一个较小的速度发送数据,如果数据十分畅通,没有丢包,那么就说明网络上的数据传输是十分畅通的,此时就可以加快传输数据的速度,当增大到一定速度后发现出现丢包的情况了此时说明网络上存在丢包的情况了,此时就可以降低发送速度,减速之后发现又不丢包了,再进行加速,加速知乎法相有丢包了再进行减速,这个过程是持续的动态变化。

    流量控制和拥塞控制都会限制发送窗口,这两个机制会同时起作用,但最终以窗口大小最小的那一个决定。

    拥塞控制中,窗口大小的具体变化过程。

    这个过程我们只需要简单的了解一下即可。 

七,延时应答

    延时应答是提高效率的机制,尽可能的降低可靠传输带来的性能影响,在接受缓冲区中我们正在接收数据的时候也在不断地接收缓冲区中的数据。

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

八,捎带应答

    捎带应答是在延时应答的基础上引入的提高效率的机制,把返回的业务数据和ACK两者合二为一了,在实际上网络通信中大部分情况下都是“一问一答”这样的形式。

     由于ACK报文是不需要载荷的,在正常情况下ACK和响应在不同时机,无法合并,但是ACK会涉及到延时应答就会使ACK往后拖,这样延迟就可能赶上接下来发送响应数据的操作了,于是就可以把之前的ACK的信息也带上。

九,面向字节流(粘包问题)

    我们通过面相字节流传输数据会涉及到一些问题(粘包问题),粘的是TCO携带的载荷(应用层数据包),比如我们传输两次数据分别为bbb和aaa那么我们可以在bbbaaa中可以读出许多可能性,因为当我们传输了两组数据,这两组数据都是在DatagreamPacket一个二进制的字节数据,就是一个完整的应用层数据包了这也就是存储的结构化为题。

    这种问题自然也有解决方式:

            方案一:指定分隔符,使用于文本类文件。

                              例如我们以“\n”为分隔符,只要遇到“\n”就可以认为接下来的一部分数据与上一一部分数据是分开的。

           方案二:指定数据的长度。

                                就比如指定数据包的长度,这样也就可以指定长度了,这种方式非常适合传输二进制数据。

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熬到半夜敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值