常常在前端的面试群中发现有人会碰到面试官去询问tcp的握手和挥手问题,诸如你了解tcp吗,解释一下tcp的三次握手和四次挥手,我认为若是只是简单的了解这2个问题,真的那么有意义吗?因此,不防试着去多了解一点网络通讯的内容,记得在上家公司的时候有个老哥说过,网络通讯其实仍是蛮重要的,毕竟咱们如今不管是工做仍是生活基本都处于互联网之中,尤为做为开发者基本上天天都在和http请求打交道,so 了解网络传输的原理仍是有必要的,下面咱们稍微深刻的来看下网络传输的内容。html
OSI(Open Systems Interconncection,开放系统互联)网络分层前端
从上到下分别是:linux
基础内容不作过多的讲解,有须要的能够出门右转 ok 咱们今天主要关注的是tcp层的内容,下面的内容,若是有兴趣建议你们按照步骤实际操做去看看,首先介绍一个工具wireshark,这个工具能够帮助咱们抓到tcp以及更底层的包,下载到这里,打开后有个download,下载本身系统能用的就好,一路安装到所有完成,接下来咱们开始一次实战抓包。面试
我这里利用了百度的首页作了一次抓包实验,首先要设置一个过滤算法
ok 看到ip是180.97.33.108chrome
而后咱们到wireshark中设置一下查询的ipshell
先看下tcp的头部报文结构 1.tcp协议层是不关心ip的,具体ip的定位是由ip层来决定的,可是tcp层须要肯定端口号,因此他会携带source 和 destination的port信息,以便能找到对应的端口号;服务器
2.sequence number 实际中使用的SEQ,也就是序号,这个序号起了很重要的做用,咱们都知道tcp和udp最大的区别在于tcp是稳定而且有序的,其中seq就能够保证有序,当A向B发送一个数据包的时候,seq会叠加,每个传输方在传送数据的时候都会带上这个信息,另外一端能按照这个序号来排序收到信息的顺序,从未保证了信息的传递是有序的,也能经过它来确认有没有出现丢包的状况;另外要注意的是当有数据须要发送的时候,seq会随该序列号为原点,对本身将要发送的每一个字节的数据进行编号,好比当前seq = 10,本次要发送的数据包大小是200字节,那么实际发送的时候会更新seq=210,以便保证传输的数据的顺序;网络
3.acknowledge number,实际中使用的ACK,是另外一端对对方seq的一个回应,通常会把对方给的seq+1而后下一次发包的时候带上,这样的话对方就知道咱们是收到前面的消息的;并发
4.windown表明的是滑动窗口,实际中用win来表示,win的大小很重要,win越大的传输越快,由于win的大小直接决定了某一端一次能够同时发送多少个数据包,而不用等待对方的应答ACK回来,可是win会随着每个数据包的发送而变小(稍后解释);
5.reserved 是tcp传输很重要的角色,标志位,响应方会根据对方给的信号执行对应的操做,好比执行断开链接的时候通常都是使用FIN标志位;
基础内容不作过多介绍,不懂的能够移步这里先看下概念,后面咱们会结合实际来介绍
A:B,你好,我是A 请求创建链接,个人seq是0,个人win是65535,我但愿本次回应个人内容长度len为0,我本次能接收的最大内容是1460,over; B:A,你也好,收到你的信息了,我是B,我本次的seq是0(注意,双方的序号是独立计算的,这里都从0开始的),我回应你的ack是1(A的seq+1,表明我收到你seq是0的消息了),个人窗口大小是8192,我但愿你回应我本次消息的len也是0,我这边能接收的最大回应大小是1452,over; A:好的,我收到你的回应了,我如今给你发送的seq是1(上一次是0,此次是1),我回应你的ack是1(B的seq+1),我当前的窗口大小是25984,我但愿的回应长度是0;咱们创建好链接了,over;
到这里,完整的三次握手就结束了,后面就能够执行别的数据传输了,到这里,不知道有没有想过,为何肯定一次链接须要三次握手,不是1次,也不是2次,也不是4次,
A:喂喂喂,我是A,你听的到吗? B:在在在,我能听到,我是B,你能听到我吗? A:(听到了,老子不想理你) B:喂喂喂?听不听到?我X,对面死了,我挂了。。
A:喂喂喂,我是A,你听的到吗? B:在在在,我能听到,我是B,你能听到我吗? A:听到了,你呢?你能听到吗? B:??你是智障?我不是说了我能听到吗,不想跟xx说话。。。
A:喂喂喂,我是A,你听的到吗? B:在在在,我能听到,我是B,你能听到我吗? A:听到了。咱们今天去钓鱼吧。。balabala
so,就是这样,其实不是不能更多,可是可靠的同时,还要考虑性能和时间问题,因此,目前公认的握手次数仍是三次比较合理。
咱们知道tcp的链接是全双工的,A和B是能够互相通讯的,不理解的话,能够想一想打电话(类比,不要当真),打电话的场景就是单双工的,由于同一时间只能一我的说话,另外一我的听,若是2我的一块儿说话,那谁都听不清楚了,没有意义,可是tcp是全双工的,就是A 正在给 B发信息的同时,B也在给A发信息,因此当断开的时候,必需要求双方都得知道,若是只有一方知道,确定不行,所以,断开的时候,就须要下面这样:
A:B,很差意思,我这边须要关闭链接了,你准备一下?(发了一个fin信号给B,等待回应)
B:好的A,我收到你的关闭信号了,我还有数据没发好,你等我下(回应A,带回去ACK的最后一个信息,失败能够重发)
B:A老弟,我好了,我能够关闭了,给你最后说一下,等下你回应个人话,我就直接关了;
A:好的老哥,我回应你一下,你收到就关闭吧,不用理我(发完这条信息后,进入time_wait状态)
B:(收到ack信息,直接就关闭了),此过程不产生数据的交互,不算挥手次数
A:等待2MSL(最大报文段生存时间)后,B没东西给过来,我也关了;
到这里4次挥手就结束了,2个问题:
握手的时候,A和B打个招呼,B能够直接把本身的SYN信息和对A的回应ACK信息一块儿带上,可是挥手的时候,A说我要断开了,B还没发完最后的数据,所以须要先回应一下A,我收到你的断开的请求了,可是你要等我把最后的内容给你,因此这里分开了2步: (1)回应A; (2)发送本身的最后一个数据
缘由是,担忧网络不可靠而致使的丢包,最后一个回应B的ACK万一丢了怎么办,在这个时间内,A是能够从新发包的,可是超过了最大等待时间的话,就算收不到也没用了,因此就能够关闭了。
从上面的内容,咱们简单了解了三次握手和四次挥手的内容,而后也知道了一些报文字段的意义,可是网络自己是不稳定的,也就是说中间没法保证数据包必定会到对面,那么tcp是如何在尽量少的时间内实现稳定和有序传输的?
咱们知道SYN信息中会带上本身的seq,序号,这样能够保证另外一方接受到后知道如何排序,可是若是发送必须都是同步的,想象,A 给 B发送的时候,须要给B 1,2,3,4,5个包,发了1后,死等1的ack回来,再给2,死等2的ack回来,在linux下每一个tcp的timeout最大是2^5 - 1 = 63
s(默认的retrytime是5次)的时间,由于当发了一个包出去后,在必定时间内没收到ACK回应,为了确认不能丢包的问题,会启动重试机制,重试5次,它们的延迟分别是:1 秒、3 秒、7 秒、15 秒、31 秒,其中31s是前5次重试的时间1+2+4+8+16=31s,最后的32s是等待最后一次重试也超时(等待的时间是2的N次方秒),因此一共就是63s,若是一个一个等,是否是有点太恐怖了,万一网络环境比较差,因此为了能在不丢包的状况下,尽可能减小时间的损耗,引入了滑动窗口的概念,window
因为窗口由16位bit所定义,因此接收端TCP,窗口能最大提供65535个字节的缓冲,其实这个滑动窗口主要就是作限流和缓冲用的,每个tcp传输中的win提供的是对方的窗口大小,当A向B发数据的时候,超过B的win长度的数据会被丢掉,同时窗口还能够提升发送数据的效率,经过相似于并发的行为,以下图:
能够看到A向B连续发了3条数据,可是回应B的ACK没有变,也就是说都是回应同一个B的同一个响应,可是A本身的seq更新了3次,先是1,而后是69最后是1521,说明这三个包是连续发出去的,实际上只要当前数据包的大小不超过对方的window大小,就能够连续发的,接着看:
看下面这个图
1.timeout后只从新传2; 2.timeout后从新给二、三、四、5;
2种方案有好有坏,第一种比较慢,第二种浪费带宽,因此tcp引入了一种快速超时重试机制(Fast Retransmit算法),不以时间计算,而以数据作驱动从新传送,若是包没有连续到达,好比1到了,2没到,3,4,5也到了,这个时候,B始终返回ACK=2,表明只确认1,而后A就知道2没到,从新发2,可是B一旦收到2会直接ACK=6给A,这个的意思就是说2拿到后,345也收到了,直接给6就ok,以下图:
上面说的只是一种特别简单的方案,目前,linux2.4以后,采用了一种更先进的方式,有想了解的能够走这里
典型的场景是DDOS攻击,也能够说是tcp的SYN Flood攻击,又叫洪水攻击; 根据上面的分析,咱们知道tcp的握手环节是比较耗时的,当client端发起链接请求的时候,server端会回应,而后等待client的最终确认信息,默认状况下的linux会等待1到63s这样(若是有特殊的设置,这个时间能够到1-2min这样),默认最长是63s以后才会断开,以前这段时间内属于半链接的状态,服务器不会丢弃掉这些链接,而是会等,试想若是有一我的忽然想你的server瞬间以内发送了几千万个链接请求,可是对服务端的响应不作理睬,这样很容易就致使咱们正常的tcp链接进不去,从而出现服务拒绝的状况,而他只须要一个简简单单的脚本去给你丢包就能够了,这种状况就会致使服务器对正常的客户端表现为宕机。。此种攻击的成本比较低,可是防御却特别麻烦,由于你必需要保证正常的不能由于访问次数的提升而出现拒绝。
另一个没有这个状况严重的攻击是ACK Flood攻击,有兴趣的能够自行去查看。