一、首先和UDP作比较谈谈TCP的特点
(1)UDP是数据报协议,每个数据报都有长度,数据报的长度和数据一起发送和接受,各个数据报的发送和接受相互独立,互不影响。而TCP是字节流协议,所有经TCP发送的数据没有记录边界。
(2) UDP是无连接、不可靠的协议,而TCP是有连接,可靠协议。TCP的可靠性主要指经TCP发送的数据要么准确无误的发送到了对端,要么发送失败并且以合适的方式通知应用程序,也就是可靠的传输数据和可靠地报告错误,TCP协议每一次发送数据以后先暂时将数据保存在缓存区,直到接收到对端的ACK然后将缓存区中已发送的数据清除或者因为等待ACK超市而重新发送,如果重新发送多次后任未成功发送,TCP将通知应用程序数据发送失败。同时TCP为每次发送的数据进行编号,到对端接收到数据后TCP会按照编号有序上传到应用。而UDP则既不对接收的数据进行确认,也不会对发送的数据进行编号以保证按序到达。
(3)TCP有流量控制和拥塞控制,TCP的流量控制可以随时告知对端他自己的缓冲区可以容纳多少数据,已让对端控制数据发送速度,以免淹没本地缓冲区,TCP使用滑动窗口来实现流量控制。而UDP对于超出缓冲区容量的数据则直接丢弃。TCP的拥塞控制是为了防止过多的数据拥入网络,造成数据链路的负载过大而使得整个网络的传输效率过低。
(4)由于TCP需要重传、拥塞控制、流量控制等机制,所以TCP的实现在kernel内维护一个发送缓冲区,一个接受缓冲区,在TCP协议的socket fd上调用write/send等发送函数,返回成功只表明数据成功复制到了TCP的发送缓冲区,并不能表明数据成功发送到了对端。如果成功发送到TCP缓冲区的数据在发送到对端的过程中出现错误,TCP会在随后的调用中通知应用程序。而UDP发送成功则是指成功发送到了链路层.
(5)既然TCP协议在kernel内维护了一个发送缓冲区,那么这个缓冲区内的数据什么时候会正真被发送呢?这是有Nagle算法来控制的。具体的来说:
<1>.缓冲区中的数据达到MSS
<2>.设置了NODELAY选项
<3>.该包含有FIN
<4>.所有已发送的数据包均已得到确认(未设置TCP_CROCK时)
<5>.发生了超时
(6)TCP/IP中不仅仅有nagle算法,还有一个TCP确认延迟机制 。当Server端收到数据之后,它并不会马上向client端发送ACK,而是会将ACK的发送延迟一段时间(假设为t),它希望在t时间内server端会向client端发送应答数据,这样ACK就能够和应答数据一起发送,就像是应答数据捎带着ACK过去。这也就意味着服务器如果只接受数据不发送数据,那么对接收到的数据确认会延迟较长一段时间(TCP在等待数据以便和ACK一起发送),而客户端在这段时间由于没有接收到上一个数据包的ACK,因此根据Nagle算法也会一直等待。 当然,TCP确认延迟并不是一直不变的,TCP连接的延迟确认时间一般初始化为最小值40ms,随后根据连接的重传超时时间(RTO)、上次收到数据包与本次接收数据包的时间间隔等参数进行不断调整。另外可以通过设置TCP_QUICKACK选项来取消确认延迟。
(7)TCP_CORK
选项:所谓的CORK就是塞子的意思,形象地理解就是用CORK将连接塞住,使得数据先不发出去,等到拔去塞子后再发出去。设置该选项后,内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去,当然若一定时间后(一般为200ms,该值尚待确认),内核仍然没有组合成一个MTU时也必须发送现有的数据(不可能让数据一直等待吧)。
然而,TCP_CORK的实现可能并那么完美,CORK并不会将连接完全塞住。内核其实并不知道应用层到底什么时候会发送第二批数据用于和第一批数据拼接以达到MTU的大小,因此内核会给出一个时间限制,在该时间内没有拼接成一个大包(努力接近MTU)的话,内核就会无条件发送。也就是说若应用层程序发送小包数据的间隔不够短时,TCP_CORK就没有一点作用,反而失去了数据的实时性(每个小包数据都会延时一定时间再发送),实际上TCP_CROK选项一般在传输文件时用的多一些,其他地方很少见用。
二、TCP流量控制的原理:
从图中可以看出,该TCP的接收窗口为500字节(因为第一次rwnd是以500字节为至的),同时可以看出每次对接收数据的ack发送同时发送了rwnd,而且rwnd在实时变化,也就是所谓的滑动窗口。可以看到B向A的确认中,ack=501哪一项的rwnd=100,也就是说之前接收到的1-500字节中1-100字节数据已被应用程序读取,从而空出100字节的缓冲区,而101-500字节的数据仍然在缓冲区。同时到ack=601的时候,101-600字节共500字节的内容仍然在缓冲区内,填满了整个缓冲区,所以rwnd=0。这儿有个问题,此时A接收到B的rwnd=0,因此不会再向B发送数据,而B把所有缓冲区的数据都处理完了,在等待A新的数据到达,这会造成A和B之间的死锁。TCP的解决方案是,A收到B的rwnd=0时启动定时器,定时器时间到达后A会向B发送一个窗口探测报文段(经携带一字节数据),B收到这个报文段对A确认时就发送了新的rwnd,从而避免了死锁。
二、TCP的拥塞控制:
(1)慢开始和拥塞避免
如果一个新的连接刚建立成功就向网络中发送大量的数据包,很可能会导致网络中路由器缓存不够用(路由器缓存不够用时就会将超出其缓存的数据包丢包,而TCP又要重传该包,可想而知很快会导致网络出现严重拥塞,实际上网络中传输的大部分是路由器由于缓存不够而丢掉导致重复传输的包),所以一开始建立的新连接不能发送大量报文,而是先从一个较低的起点开始发送,如果发送得到及时确认,那么就将这个起点增大一点。
发送方维护着一个拥塞窗口(cwnd),拥塞窗口的大小随着网络负载的变化而实时变化,发送方每次发送数据的大小不得大于拥塞窗口和对端接受窗口的最小值。
这里图方便以报文段大小为单位说明TCP的拥塞控制,实际上是以字节为单位的。
发送发每发送成功一字节就把窗口cwnd加1,这样其实我们注意到发送窗口是以2的指数增长的,所谓乘性增。因此,说是慢开始指得是刚开始发送的初始窗口比较小,但是如果网络通畅的话,发送窗口的增长速度实际上是相当快的。
如果一直以这样的速度增长下去最终也会造成网络拥塞,所以TCP除了cwnd意外还设置了一个慢开始门限ssthresh变量。
当cwnd < ssthresh的时候使用慢开始算法,当cwnd>ssthresh时使用拥塞避免算法。拥塞避免算法是每经过一个RTT的时候就将cwnd加1(注意到这里,慢开始算法是每发送成功一字节就把cwnd加1,经过RTT发送成功了多少字节就增加了多少字节,也就是发送的字节数*2,而拥塞避免算法则不管发送成功多少字节都仅将cwnd加1),此时cwnd的变化开始平缓。无论是在慢开始阶段还是在拥塞避免阶段,只要网络发生拥塞(判断的依据是丢包),都将慢开始门限ssthresh减小为原来的一般并将发送窗口cwnd置为1,执行慢开始算法。
(2)快重传和快恢复
我们知道TCP在发送一个数据包后会设置一个定时器,如果这个定时器到时后仍然没有接受到对端的确认,那么就确认该报文已丢失需要重新发送,但是如果每个已丢失的报文都需要等到定时器到达在重新发送那么传输效率势必会造成影响。因此TCP采用快重传来使得TCP拥有更高效的传输速率。快重传的意思是指接受方没接受到一个失序的报文就立即确认该报文,而不是采用上文所讲的延时确认。当接收方连续接收到三个失序的确认报文后就马上重新发送导致失序的那个报文(这个报文未被确认而导致收到了三个失序的确认报文),而不必等待重传定时器到时。
配合快重传算法的还有快恢复算法,当发送方连续收到三个重复的确认报文时就采用快恢复算法。此时先把sthresh减半,cwnd设置为sthresh相同大小,同时执行拥塞避免算法。注意到此次并未将cwnd置为1,因为TCP认为能够连续收到三个确认报文,所以网络可能并不拥塞,因此只采用了快恢复算法。
版权声明:本文为博主原创文章,未经博主允许不得转载。