+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
张贺,多年互联网行业工作经验,担任过网络工程师、系统集成工程师、LINUX系统运维工程师
个人网站:www.zhanghehe.cn
笔者微信:zhanghe15069028807,现居济南历下区
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
前言
无论是程序员、网络工程师、还是运维人员在面试的时候总是会问到有关于tcp连接问题,tcp非常基础,也非常重要。虽然我是学网络出身,并且已经毕业两年了,但我总觉得对tcp的理解并不是那么深刻,每每再次学习时都有新的收获。
再提一点,在学习tcp的时候很多人是通过书上或者看视频去学习,看这些资源没有问题,但如果你不亲身去验证的话,是无法真正理解的,别人的火点不亮自己心中的灯,老祖宗早就看透了,“纸上得来终觉浅,绝知此事要躬行”,真不是说说而已!
基本概念
seq是什么?
seq是数据段的序号,废话!什么意思呢?当应用层产生了很多数据时,比如要发送一个大文件,系统当中有发送缓存,上层应该将这个大文件放置在这个缓存当中进行发送,这个缓存也是我们进行系统调优的时候要注意的地方,如果你的内存很大,不妨将缓存调大一点。应用将文件放到发送缓存之后就不管了,要内核沿着tcp虚拟通道发送到接收方,在发送给接收方的时候,发送方要封装报文,每一个报文里面封装多少数据是由接收方的窗口字段决定的,而不是发送方决定的,也就是说一个大的文件在传输层很有可能被切割成好几部分,分开发送给接收方,接收方收到这些零碎的数据之后,组装成一个整体,那组装的依据是什么呢?就是这个序号,这个序号表示,当前这个数据包携带的数据的开头在整个文件当中的绝对位置。
ack号?ack位?
在tcp包头里面有两个名字相似的字段,一个是ack号,一个是ack位,有的时候真的傻傻分不清楚,很多书里面并没有对这两个字段进行详细的说明,往往是一带而过,这两个字段所表示的意思还是很不一样的。
- ack号,在seq号下方,它有两个意思,一个意思表示收到了接收方之前的数据包,还有一个意思是提示对方下一个数据包的seq号。
- ack位,这个ack位是指USG、SYN、RST那六位当中的一位,这里的ack位等于1的时候上面的ack号才生效。
上面的解释可能有点难以理解,如果你觉得难以理解,最好还是抓个包好好分析分析。有了这样的理解,我们再来分析seq号和ack号的增加规律就比较简单了。
增涨规律
seq
seq刚开始都会有一个初始值,这个初始值是多少,我们无需关心,只要知道它的增涨规律。
seq序号=上一包的序号(实际是上一个包的开头)+长度(上一个包的结尾)
如上图所示,第一包的序号是317,长度是0,可以推断出其发送的下一包的序号是317+0=317,通过第3个包可以验证,这是一种方法,通过自己的序号和长度来推断自己下一个包的序号;还有一种方法,就是看对方的期待,也就是对方的ack号来决定自己下一个数据包的序号,如下图所示:
第6个包是80.9这台主机发的,ACK号是69,说明它期待的对方也就是80.11这台主机的发送的下一个包的seq是69,我们通过第8个包就可以验证了。
正常数据传输时的seq和ack的增长规则都符合以上的规律,但是三次握手和四次挥手的时候,这种规律就不好使了,为什么?因为tcp三次握手时候仅有tcp包头,里面没有数据,没有数据也就没有长度len就会一直等于0,按理说客户端的第三次握手的序号等于第一次握手的序号0加上数据包的长度0,那这样的话,第三次握手时客户端的seq还是等于0,其实并不是这样的,第三次握手时的seq就变成1了,如下所示:
ack
确认号表示接收方通知发送方确认已经收到了哪些字节。
比如甲给乙方发送了:”seq=x,len=y“,那么乙方要回复的ack位的值就应该是x+y,表示它收到了x+y之前的所有字节。
我们将seq的增长规律和ack的规律整合一下,模拟几个交互:
客户端:seq=100,len=5,ack=0
服务端:seq=200,len=10,ack=105(客户端的seq+len)
客户端:seq=105,len=8,ack=210(服务端的seq+len)
服务端:seq=210,len=15,ack=113(客户端的seq+len)
也就是说对方的ack就等于自己将要发送的seq号。
以上规律同样不适合分析三次握手和四次挥手,还是因为三次握手里面没有真正的数据。
为什么要用三个包来建立连接呢?两个不行吗?其实是可以的。
假设客户端在第一次建立握手时数据包在半路阻塞了,客户端维护一个超时时间,时间到了就认为这个包已经丢了,于是又重新发送一个请求连接包,这个包很快到达了服务器建立好通道之后传递完数据之后紧接着把连接就给关闭了。但之前阻塞那个数据又来到了服务端,服务器并不知道这是一个无效的请求,所以按照惯例,服务器又回复了,回复给客户端,如果是两次握手的机制,这样又建立起来一个无效的数据连接。但现在有了三次握手,客户端不会立马跟服务器建立连接,知道这个包不是它想要的,于是就回复一个拒绝包,干脆利落的拒绝,服务器立马就把维护的套接字关闭掉,做别的事情去了。
三次握手比两个握手就多了一次回复,这一次回复是比较重要的,就像一个男生追一个女生,如果女生不拒绝,也不同意,就浪费了男生太多精力,让男生猜不透女生的心思。假如说女生明确给一个回复,同意就同意,不同意就拉倒,这样男生也死心了,也能抓紧去追别的女生。其实有关于tcp为什么是三次握手而不是两次,不同的书里面有不同的说法,详情见我这篇博文: https://www.cnblogs.com/yizhangheka/p/11665524.html
在三次握手和四次挥手当中上述的seq和ac听增加规律就不好使了,ack的规律变成了对方的seq+1,seq号等于对方发送的的ack号。
分析三次挥手
三次握手很多人都分析过,但我总是觉得分析的不好,还是那句话,用别人的火无法点燃自己心中的灯。
三次握手因为没有数据量,它的seq和ack的增涨规律与携带数据量的数据包增长规律不一样,三次握手我觉得应该这么表述:
-
客户端:我能跟你建立连接吗?我的初始序号是x,如果你同意的话,就在你的ack号上将我的序号加1.
-
服务器:收到了(ack号=x+1,ack位=1),我也想跟你建立连接(syn=1),我的序号是Y,如果你同意的话就在你的ack号上将我的序号上加1
-
客户端:收到了(ack号Y+1,ack位=1)
分析四次挥手
-
客户端:请求断开连接,FIN和ACK都置位
-
服务器:知道了,ACK置位
-
服务器:我这边也想断开了,FIN和ACK都置位
-
客户端:知道了,断开吧,ACK置位
以上标准的上次挥手过程,有时候也是三次挥手,如下所示:
四次挥手变成三次挥手了,服务器省略了第二次挥手包,怎么会这样?这是因为延迟确认的问题,将第二次挥手的确认和第三次挥手的请求结合到一个报文当中了,这样可以提高效率,节省资源。