TCP那些事儿(1)

来源:互联网 发布:以此为政,不亦惑乎翻译 编辑:程序博客网 时间:2024/06/16 20:14

TCP那些事(一)

我前面写过TCP的三次握手和四次挥手,不了解的同学可以看一看我之前的文章,相比你会对TCP有一个初步的了解。
下面我会分成三篇来对TCP协议的原理和算法进行科普。
一篇:TCP协议的定义
二篇:重传机制。
三篇:TCP的流迭和拥塞处理。
废话少说我们进入正题,我们都知道TCP在OSI七层协议的第四层--transport层,IP在第三层--network层,ARP在第二层--data link层,在第二层上的数据我们叫做frame,在第三层上的数据我们叫做packet,在第四层上的数据叫做segment。
首先,我们要知道,我们程序的数据首先会打到TCP的Frame中,然后TCP的frame会打到IP层的packet中,然后再打到以太网Ethernet的segment中,传到对端后各个层解析自己对应的协议,然后把数据交给更高层的协议处理。
TCP头格式

我们来看一看TCP的头格式:


关于上面这张图,你需要注意的是:

*TCP协议的包没有IP地址,那是IP层上的事情,但是由源端口和目的端口。

*一个TCP协议需要四元组来表示(str_port、str_ip、dst_ip、dst_port),准确的说应该是五元组,还有一个是协议,因为我们说的是TCP协议,所以我们那暂且说四元组。

*我们要注意上图四个非常重要的东西:

1.Sequence Number是包的序列号,用来解决网络包乱序(reordering)问题。

2.Acknowledgement Number就是ACK,用来表示已经收到,解决丢包问题。

3.Window又叫做Advertised window,也就是著名的活动窗口(sliding window),用于解决流控的。

4.TCP Flag也就是包的类型,用来操控TCP状态机的。

关于其他的东西,请看下图:


TCP状态机

其实,网络上的传输是没有连接的,TCP也是一样的,我们之所以说连接,其实是一种链接状态,由通信双方共同维持,就好像有链接一样,所以TCP状态变换是非常重要的。

下面是”TCP状态机“、“TCP建连接”、“TCP断开连接”、“传数据”的对照图。下面的两张图非常重要,一定要记牢。看下面状态变换的图你就感受到TCP的复杂了,复杂的东西一般也很坑,所以TCP协议其实也有很多坑的东西。



很多人都会问,问什么建立连接要三次握手而断开连接需要四次挥手?

*对于建立连接的三次握手,主要要初始化Sequence Number的初始值,通信的双方要互相通知对方自己的Sequence Number值,缩写Inital Sequence Number(ISN),所以叫做SYN,全程是Synchronize Sequence Number。也就是上图中的x和y,这个数值要作为通信双方以后通信的序号,以防止网络包在以太网络中传输而出现乱序的问题。(TCP会用这个序列号来拼接数据包的顺序)。

*四次挥手,你仔细看,其实是两次,因为TCP是全双工的,所以发送方和接收方都需要Fin,Ack,只不过有一方是被动的,所以看上去是四次连接,至于为什么只需要四次,五次六次可以吗,那么我只需要问你一句,那样不浪费系统资源吗,四次就能完成的,不需要多此一举。如果两边同时断开连接,就会进入Closing状态,然后进入TIME_WITE状态,下面是双方同时断开连接的示意图,你同样可以对照状态机来加深理解。


另外,还有几个事情需要注意一下:

*对与建立连接时的SYN超时,试想一下,如果接收端收到了Client发送的SYN后并且回复了syn_ACK后Client掉线了,server没有收到Client回复的ACK,那么这个链接处于中间状态,机没有成功,也没有是失败。于是,server在长时间没有收到回复的情况下会重发SYN_ACK,在linux下,默认的重试次数是五次,每一次时间都是翻倍增长的,五次的时间间隔为1s,2s, 4s, 8s, 16s,在第五次充实以后还要等32s才能确定第五次超时了,所以总共需要2^6-1 = 63sTCP才会断开这个连接。

*关于SYN Flood攻击,一些恶意的人就根据第一种情况制造了SYN Flood攻击--给服务器发送了一个SYN之后就下线了,于是服务器默认63s以后就会断开连接,这样,攻击者就会把服务器的syn连接耗尽,让正常的连接请求不能处理。于是,Linux下给了一个tcp_syncookies的参数来应对这个事,当SYN队列满时,TCP会通过源地址端口,目标地址端口和时间戳打造一个体别的Sequence Number发出去,我们把这个东西叫做cookie,如果是攻击者,测不会有反应,如果是正常的连接,则会把这个cookie发回来,然后服务器会通过这个cookie建立连接,即使你不在SYN队列中,请注意,千万别用tcp_syncookies来处理正常的大负载连接的情况,因为syncookies是妥协版的TCP协议,并不严谨。对于正常的请求,你可以调用三个常用的TCP参数,第一个是tcp_synack_retries可以用它来减少重传次数,第二个是max_syn_ack_backlog,可以增大SYN连接数,第三个是:tcp_abort_on_overflow处理不过来就直接断开连接了。

*关于ISN的初始化,isn是不能hard code,不然会出现一些问题,比如,如果连接好后,始终用1作为哦ISN,如果client发送量30个segment过去,但是网络断了,于是CLIENT重连,又用了1作为ISN,但是之前连接的那些包到了,于是就被当成了新连接的包,这时候,比如,client的Sequence Number可能是3,而server端认为client端的序列号为30,全乱了。RFC793中说,ISN是和一个固定的始终绑定在一起,这个始终会每4微秒对ISN进行一次操作,知道2^32后,又从0开始,这样,一个ISN的中期大约是4.55个小时,因为,我们假设TCP segment在网络中的存活时间不会超过4.55小时Maxinum Segment Lifetime(MSL),所以只要MSL小于4.55小时,那末我们就不会重用到ISN.

*关于MSL和TIME_WAIT,通过上面ISN的描述,你应该也知道MSL是用来干嘛的了,我们注意到,在tcp连接的状态图中,从TIME_WAIT到CLOSED状态有一个超时设置,这个时间是2MSL(RFC793中设置为2分钟,在linux中设置为30s)为什么要又TIME_WAIT而不直接转换到CLOSED状态呢?(1),TIME_WAIT保证对方有足够的时间收到ACK,如果server没有收到对面的ACK,那么server就会重新发送FIN这样一来而去的时间正好2MSL。(2)有足够的时间让当前连接不和后面的连接混在一起,因为有些自作主张的路由器会缓存ip数据报,如果连接被重用了,那么延迟收到的包有可能跟新的连接混在一起。

*关于TIME_WAIT数量太多,从上面知道,TIME_WAIT是一个很重要的状态,但是在大并发的短链接下,会产生很多数量的TIME_WAIT,这小号了很多系统资源,只要搜一下你就会发现网上大多数的处理方式就是让你设置两个参数,一个是tcp_tw_reuse,另一个是tcp_tw_recycle.后者比前者更为激进,另外如果你想使用tcp_tw_reuse,必须设置tcp_timetamps = 1,否则无效。这里你一定要注意的是打开这两个参数会有比较大的坑,可能会让TCP连接出现一些诡异的问题,比乳像上面说的,不等待超时重用连接的话,新的连接可能链接不上。

(1)、关于tcp_tw_reuse,官方文档说tcp_tw_reuse加tcp_timetamps,叫做PAWS(for Protection Against Wrapped Sequence Numbers)可以保证协议角度上的安全,但是你需要timetamps在两边都打开。

(2)、关于tcp_tw_recycle如果tcp_tw_recycle被打开了,就会默认对方开启了tcp_timetamps,然后会去比较时间戳,如果时间戳放大了,就可以重用,但是如果对方是一个NAT网络的话(如:一个公司只用一个IP出公网),或者对端的IP被另一台及其重用了,这个事情就很麻烦了,建立连接的SYN可能直接被丢掉了,系统可能会回复一个connection time out 的错误。

(3)、关于tcp_max_tw_buckets这是控制TIME_WAIT并发的数量,默认值为180000,如果超限,系统会把多余的destory掉,然后在日志里打一个警告(time wait bucket table overflow)官网文档说这个参数是用来防御ddos攻击的,也有说这个默认值并不小,这得根据实际情况考虑。

注意:使用tcp_tw_reuse和tcp_tw_recycle来解决TIME_WAIT问题是费城非常危险的,因为这两个参数违反了TCP协议。

其实TIME_WAIT表示的是你主动断开连接,如果是server端主动断开连接,那么TIME_WAIT就是它的事了,另外,如果你的服务器是HTTP服务器,那么设置一个HTTP的KEEPalive有多么重要,浏览器会重用一个TCP连接来处理多个HTTP请求,然后让客户断连接。(你要小心,浏览器可能非常贪婪,它们不到万不得已可能不会主动断开连接)。

数据传输中的Sequence Number

来看wireshark中截得的数据:

·

上图中你可以看出来seqnumber的增加是根据传输的字节数来决定的,三次握手后,来了两个len:1440的包,而第二个包的seqnumber就成了1441,然后第一个ack回的是1441,表示第一个包收到了。

osi七层模型参考:http://blog.csdn.net/yaopeng_2005/article/details/7064869

原创粉丝点击