前言
本文简介传输层TCP和UDP的一些关键特性。适合于对网络有一定了解但又不是很清楚的同学。本人水平有限,如果有错误,欢迎指正,如果对你有帮助,点个喜欢呗。
TCP和UDP是两个最重要的传输层协议。网络层的IP协议提供点到点的传输,而传输层协议提供端到端的传输。一个端是由一个IP地址和一个端口号组成socket(套接字),也对应用于一个网络编程的接口。
TCP与UDP最大的不同是:TCP提供的是面向连接的可靠的字节流服务,而UDP提供的是无连接的不可靠的数据报服务。
TCP
三次握手和四次挥手
为了建立一条TCP连接至少需要三次通信,而拆除一条连接需要四次通信,俗称三次握手,四次挥手。
三次握手流程
1)client发送一个SYN X 发起连接请求。
2)server发回SYN Y作为应答,同时ACK X+1
3)client发送ACK Y+1。
X和Y分别代表图中的ISN(c)和ISN(s),是每个端的初始序列号。ISN随时间而变化,可以看成一个32位的计数器,每4ms加1,每隔9.5小时又回到0。这种做法目的在于:防止在网路中被延迟的分组在以后又被传送,而导致某个连接一方对它作错误的解释。
ACK
发送ACK可以不需要任何代价,因为32位的确认序号字段和ACK标志都是TCP首部的一部分。通常TCP在接收到数据时并不立即发送ACK,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(即数据捎带ACK)。大多数实现采用的时延为200ms,也就是说,TCP将以最大200ms的时延等待是否有数据一起发送。
Why三次握手?
因为只有通过“三次握手”才能建立可能的连接,举个栗子。
client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。
假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的传输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。
采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。
Why四次挥手?
这是由TCP的半关闭造成的,一个TCP连接是全双工的(即数据在两个方向上都能同时传递),因此每个方向必须单独地进行关闭。收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据,尽管应用程序可以利用这种半关闭特性,但在实际应用中很少有TCP应用程序这么做。
TCP的状态变迁图
发起和终止TCP连接的规则,都能从状态变迁图中得出,实线表示正常的客户端状态变迁,虚线表示正常的服务器状态变迁。
TIME_WAIT状态也成为2MSL等待状态,设置这个状态是为了可让TCP再次发送最后的ACK以防这个ACK丢失。MSL(Maximum Segment Lifetime)是报文被丢弃前在网络内的最长时间,RFC793中指出MSL为2分钟。
利用滑动窗口提高速度
TCP以一个段为单位,发送一个段等待一个确认,这样的停等协议是低效的,且RTT(往返时间)越大通信性能越低。为了解决这个问题,TCP的两端都提供了一个发送窗口(由接收方通告也称为提供窗口,TCP头部有个16位的字段携带)和一个接收窗口。
发送端主机在等到确认应答返回前,必须在缓冲区中保留数据,以应对重发。收到确认后窗口可以右移。
慢启动与拥塞控制
算法描述
慢启动为发送方的TCP增加了另一个窗口:拥塞窗口(cwnd)。算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh。算法如下:
1)对于一个给的对连接,初始化cwnd为1个报文段(MSS),ssthread为65535个字节。
2)TCP输出例程的输出不能超过cwnd和接收方提供窗口的大小。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
3)拥塞发生时(超时或收到重复确认),ssthread被设置为当前窗口大小的一半。如果是超时,则cwnd被设置为一个报文段。
4)当新的数据被对方确认,就增加cwnd,增加的方法依赖于是在慢启动还是拥塞控制。如果cwnd小于等于ssthread,则正在在进行慢启动,否则进行拥塞控制。
5)慢启动阶段下,每收到一个确认cwnd的值就翻倍,指数增长。拥塞控制阶段下,每收到一个确认cwnd的值增加一个报文段,线性增长。
TCP窗口变化图如下图所示。
相信很多同学都有这样的经验:开启一个下载任务,起初速度不快,然后眼看着蹭蹭的往上涨。还有同学在下载速度持续低迷时,点下暂停重新启动,享受慢启动阶段指数增长的快感。
超时重传
TCP在发送时设置一个定时器,当定时器溢出时还没有收到确认,它就重传该数据。重传的时间间隔为1、3、6、12、24、48和多个64秒,这个倍乘关系称为“指数退避”。当过了9分钟后TCP还没有收到确认信号,它将放弃并发送一个复位信号。
坚持定时器
ACK的传输并不可靠,也就是说,TCP不对ACK报文段进行确认,TCP只确认那些包含有数据的ACK的报文段。如果一个确认丢失了,连接就会出现死锁:接收方等待接受数据(因为它已经向发送方通告了一个非0的窗口),而发送方在等待允许它继续发送数据的窗口更新。
为了防止这种死锁情况的发生,发送方使用一个坚持定时器(persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查(window probe)。
坚持定时器时也是使用了指数退避的方式,不过它与超时重传不同的是,TCP从不放弃发送窗口探查。这些探查最后会每隔60秒发送一次,直到窗口被打开或者应用进程使用的连接被终止。
UDP
UDP比TCP简单得多,UDP不提供复杂得控制机制,利用IP提供面向无连接的网络通信,其头部如下所示。
UDP没有拥塞控制等功能,其缓冲区是个队列结构,若发送数据过快,UDP缓冲区充满,前面的包将会被顶掉,造成丢包。
最大UDP数据报长度
理论上IP数据报的最大长度是65535字节,这是由IP首部16位总长度字段所限制。去除20字节的IP首部和8字节的UDP首部,UDP数据报中用户数据的最长长度位65507字节。
对于超过链路MTU(最大传输单元)的UDP数据报,IP层将进行分片操作。以太网的链路MTU位1500字节。
适用性
由于UDP面向无连接,它可以随时发送数据。再加上UDP既简单又高效,因此经常用于以下几个方面:
1)包总量较少的通信
2)视频、音频等多媒体通信(即时通信)
3)广播通信
检错机制
咱做纠错编码的最关心的就是通信里的纠检错机制了。那么一条TCP和UDP的链路是如何得知传输数据出错了呢?通过协议我们知道,以太网的链路层有4位的CRC帧尾,IP、TCP、UDP的头部都有16位的校验和,这些都是检错的保障。
为啥光有链路层的CRC不够呢?因为有可能链路层递交上来的包是没有错的,而在路由器的转发过程中,在网络层出错了。
那有为啥光有网络层的校验和不够呢,还要在传输层做检错?因为IP层的校验和仅覆盖IP头部,它无法检测IP包数据字段的错误,IP有可能是分片传输的,它都看不到完整的数据,怎么做检查啊。IP出错时将丢弃该数据报,然后发送ICMP消息给信源端。
TCP和UDP收到数据包检测出错,都将丢弃这个包。
总结
可以看出TCP的特点就是它提供的服务是可靠的,可以保证数据的无错传输,但是连接过程耗时,响应慢,控制机制比较复杂。而UDP比较轻快,但是无法保证数据可靠,数据可能不按顺序到达,或者是在链路上丢失(Erase)。
那么如何克服TCP响应慢的缺点呢?又如何保证UDP的可靠传输呢?欲知后事如何,且听下回分解。