通过了过年的忙碌和年初的懈怠一切的日子,我又开始从新更新了~这是最新的一篇~完整版能够去gitbook(https://rogerzhu.gitbooks.io/-tcp-udp-ip/content/)看到。linux
若是对和程序员有关的计算机网络知识,和对计算机网络方面的编程有兴趣,虽说如今这种“看不见”的东西真正能在实用中遇到的机会很少,可是我始终以为不管计算机的语言,热点方向怎么变化,做为一个程序员,不少基本的知识都应该有所了解。而当时在网上搜索资料的时候,这方面的资料真的是少的可怜,因此,我有幸前两年接触了这方面的知识,我以为我应该把我知道的记录下来,虽然写的不必定很好,可是但愿能给须要帮助的人多个参考。个人计划是用半年时间来写完这一系列文章,这个标题也是我对太多速成文章的一种态度,好了,废话再也不多扯了,下面是其中的一节内容,更多内容能够去gitbook上找到。git
前面对于UDP已经阐述了有一些内容了,UDP能够完成一些数据的传输,那么为何还要再研究出另一种传输层协议呢?由于在不少时候,不可靠的传输会形成上面的应用层协议变得毫无心义,并且面对愈来愈复杂的网络,没有管理控制的传输层协议更是会致使网络拥堵不断加重直至瘫痪。能够设想一下,UDP就像是寄信,当你把信寄出去的时候,你是没法知道这封信可不能够到达收信人的的,若是说惟一你能作的就是相信邮递机构。这封信在沿途是丢了仍是寄到什么其余地方去了,你彻底不知道(虽说在如今这个快递信息极端透明的状况下看起来不太可能,可是在快递刚刚开始的时候,这种状况太常见了)。可是若是是打电话,这种模式就不行了,不能说你播出一个号码,说一段留言,而后还不知道对方能不能接收到这个留言,若是是这样,我要电话还有个什么用。再换个角度,即便我真的能有这么个留言的模式打电话,可是因为同时有太多留言,形成电话网络压力过大,你的留言可能由于延迟一年后才到达对方,很明显,电话不是这么设计的,就像TCP同样,设计他就是为了能提供可靠的通讯。程序员
TCP里最初级也是最重要的概念之一就是链接,UDP是没有链接的协议,通俗点说就是UDP的两端在通讯的时候只要任一方想发送消息给其余方,他只要发就能够了。UDP是一个很任性的协议,想发就发,想断就断,不须要实现通知对方,也不须要作些什么准备工做。TCP就不一样了,TCP是一个很绅士的协议,在发以前,发送方和接收方会先进行协调,结束的时候呢,双方一样也会进行相互的沟通并积极的作好本身的清理工做,英文中对这种行为有个很恰当的词语,叫作graceful。面试
前面说过,TCP是一个绅士的协议,在发送数据以前,双方会进行友好的协商,这种协商也就是在全部介绍TCP的文章里都会提到的“三次握手”。如今有个广泛的现象,如今问面试者什么是“三次握手”,基本都没有答不出来可是若是再进一步,问一下,若是在某一个步骤的时候出现了丢失,那么会怎么样,基本上就只剩百分之二十的人能答的出来。这就是像你只知作别人的绅士行为是什么样的,殊不知道这些行为的来源,因此若是盲目的学习只能给人一种学到皮毛的感觉。不过,这也是一篇介绍TCP的文章,因此固然也绕不开TCP的三次握手,我用一张wireshark里真实抓包获得的TCP流来进行图示:编程
左边是发送方,右边是接收方,在介绍三次握手以前,首先你们得回忆下前面介绍过的TCP的报头中的标识符位。TCP报头中有6位标识符,在置1以后分别表明这一个TCP包有不一样的含义。其中第五位是SYN位,当这一位置1时表示链接的开始或者同步序号请求,SYN就是英文同步synchronize的缩写。除了这一个以外,另外一个会在三次握手中出现的就是ACK,这个是六个标识符中的第二个标识符,英文acknowledgement的缩写,主要用来表示对于对端消息的回应,简单粗暴的理解的话,能够理解为,“啊,我知道了”。网络
为何我说TCP是一个绅士的协议呢?从其三次握手的过程当中就能够体会的到,请求的发起方先发送一个编号为0的SYN包到接收方,接收方接收到这个SYN包以后,首先确定是要通知发送方我已经接受到了你的SYN请求,也就是咱们上面说的ACK。但同时按照上面描述的,若是想创建链接,就必须发送SYN,因此,对于接收方,就有两个须要发送的包,亦或是说两个被置不一样标识的包,可是很明显,这两个包是能够合并的,因此说,发送方就会发送一个TCP包,这个包里,SYN位和ACK位同时被置上。回到发送方,在接收到这个对端发送来的SYN包以后,一样要回一个ACK包给对端。此时,TCP链接就创建好了,后面的通讯中,两端就能够自由的发送数据和消息了。并发
这里还能够了解到的就是贯穿整个TCP的确认消息,TCP如何让对端知道本身已经收到了哪些包?前面一篇说过,TCP报头中是有一个序列号的字段的,这个字段用来给每个报文编号,这个编号在一次通讯中是不断递增的,因此理论上接收端只要告诉发送端本身已经收到的包的序号就等于告诉发送端我已经收到比这个序号小的全部的报文。回到上面的图中,能够看到第一个SYN包的序号是0,那么当接收端告知对方的ACK中所使用的序列号是1,表示标识符比1小的包我都接收到了。在这个特定状况下,也就等于发送端已经知道了接收端已经良好的收到了本身的SYN请求。固然,这个序列号,确认号具体在TCP报头的什么位置,在上一篇文章中,能够很容易的找出来。tcp
TCP的三次握手其实给人和人之间的交流提供了一个很好的模型,就拿开车并道这件事来讲,若是人人都能遵循三次握手的原则,那么我相信全部的由于并道而产生的事故都能避免。前车要并道以前先闪三次转向灯,后车闪灯表示本身已经收到前车并道信号而且能够并道,前车再次打转向灯,而后开始并道。简直是一个标准的三次握手过程,简洁而有效,惋惜的就是在实际生活中,按照这样的规则作并道的人太少。学习
虽说三次握手的设计是一个很绅士的设计,可是全部的时候理想和现实都是有差距的,因为网络的复杂性,三次握手的每个消息都有可能在传递的过程当中面临三种状况,丢失,延迟到达,重复。这三种状况贯穿于整个的TCP通讯的每一步,而TCP中的不少设计也是由于解决这三个状况而应运而生。spa
在创建链接的阶段主要是丢失的问题,在介绍丢失问题的解决思路以前,先要介绍的一个概念是发送计时器。在TCP中,发送消息的时候会启动一个计时器,这个计时器在收到相应回复的时候会重置而从新计时,而若是一直没有收到相应的回复,在计时器到期的时候发送端就会重发消息,这是TCP重传机制里面第一层的保障。
由于TCP发起链接的时候只有三条消息,因此丢失也就三种状况:
第一个SYN消息丢失,也就是发起者的发起请求丢失了,因此接收者也就不会回送SYN-ACK消息,由于他没有得消息刺激他回应。因此过一段时间后发起者发现本身没有收到回应消息,因而在计时器到期后,发起端会重发SYN消息。若是在通过了几回重传仍然没有成功之后,尝试链接过程就终止了。
第二个SYN-ACK消息丢失,发送端本质上和上一种状况相同。接收者由于确实已经收到了SYN消息并发送了回复消息,因此其计时器已经启动了。在计时器到期以后,接收端会重发SYN-ACK消息,若是几回以后尚未成功,那么接收端会发送RST终止链接,RST的含义在后文中会详细介绍。
第三个来自发送端的ACK丢失,接收端本质上会上一种状况相同,最终会发送RST消息终止链接。
单独分别从两端看这三个错误的处理方式,并不难理解和理清楚其中的过程,可是若是从两端一块儿考虑,那么稍微想一下就知道过程变得极端的复杂,由于在发生丢失的状况下,两端都有定时器在计时。
在linux的TCP-IP协议的实现中,分别使用两个不一样的计时器,在发送端启动是普通的超时计时器,在接收端启动的是SYN-ACK计时器。超时计时器就是在发送端发送SYN的时候开始计时,默认是1秒,若是过了1秒没有收到确认,会再次发送SYN,而后将计时器设置成为2秒,而后依4秒,8秒,16秒,以此类推。固然,在代码中有一个重试上限,在linux上的默认是设置为5次。一样的SYN-ACK计时器在接收端接收到SYN以后发出SYN-ACK消息以后启动,间隔和重试次数和普通计时器都是一致的,固然他会作一些其余的事情因此和普通计时器是有一些区别的。
咱们考虑实际中的状况二,发送端发送SYN后未收到SYN-ACK消息,同时启动计时器A,过了一小段时间以后,接收端接收到了SYN消息,启动计时器B,发送SYN-ACK消息,可是这个消息丢失了。1秒钟后,发送端因为A到期,重发SYN,而几乎与此同时接收端也会因为B到期重发SYN-ACK消息。那么问题来了,假设这个时候重发的SYN又一次成功的到达了接收端会怎样?答案很简单,接收端会忽略它,由于seq序号重复了。接收端既不会再一次发送SYN-ACK消息,也不会重置计时器。因而就避免不断重复的重发,形成网络混乱甚至崩溃。
若是用一句话总结的话,就是经过超时计时器和序号的重复检测,TCP能够一样能够很绅士的解决这些不绅士的打断。