UDP为应用程序提供了一种以最少的协议机制将消息发送到目的应用程序的方法,RFC(Request for Comments,请求意见稿)768对它进行了定义。UDP提供了应用程序间根据端口号复用和解复用的方法,以及可选校验,是非常轻量级的协议。UDP首部格式如图1-1所示。
使用UDP的应用程序的数据可以直接在数据报内发送,不需要建立连接,也对可靠性和顺序保证没有要求。UDP效率高、速度快、消耗低、延迟小,适合于实时性要求较高的场景,如视频会议、网络游戏。
由于TCP和UDP出现较早,使用非常广泛,很多中间件和内核都能很好地支持它们,因此互联网协议大多会选择它们。如果不需要TCP提供的全套功能,需要定制传输行为的协议也会选择基于UDP实现。
图1-1 UDP首部格式
TCP起源于20世纪60年代末的一个分组交换网络研究项目,现在已成为全球互联网的基础。TCP是一种面向连接的传输层通信协议,两个端点通过三次握手建立连接后就可以按照顺序收发数据。TCP的实现包括可靠、保序、去重、流控、拥塞控制等功能。
TCP首部格式如图1-2所示。TCP使用四元组(源IP、源端口号、目的IP、目的端口号)来确定一个连接。序号用来确定字节流的位置,应用程序发送的字节流是从0开始的,但序号从一个随机值开始,这主要是为了防止猜测攻击和新旧连接间的冲突。确认号是为了让发送方知道哪些数据被接收到了,哪些需要重传。
图1-2 TCP首部格式
TCP通过三次握手建立连接,如图1-3所示;通过四次挥手关闭连接,如图1-4所示。
图1-3 TCP的三次握手
TCP的细节内容比较多,这里就不详尽地描述了。下面主要介绍几个对参考QUIC(Quick UDP Internet Connections,快速UDP连接)实现比较重要的内容,以及TCP的应对措施。
1.TCP确认
TCP的标准确认机制是累积确认,如果数据乱序到达,接收端只会重复确认最后一个按序到达的报文。TCP应用数据的发送和确认原理如图1-5所示。图1-5中,假设起始序号为100(但实际上随机值一般是个很大的值),第一个包含数据的TCP报文序号是100,长度是5,对应了应用数据0~4字节的绝对偏移,对端确认这个报文中确认号是105,表示105之前的数据都已经收到了,希望下一个报文的序号从105开始(相对于应用数据的绝对偏移量5),这个确认报文中的序号是对端确定的随机值X。第二个包含数据的TCP报文序号从105开始,长度为5,对应了应用数据5~9字节的绝对偏移,对端收到后回复序号X(因为对端没有发送任何应用数据,所以序号不变),确认号110,表示希望下一次发送序号为110的报文。
图1-4 TCP四次挥手
从上文可以看出,TCP的累积确认可能会导致在乱序的情况下重传已经收到的数据,如图1-6所示。
图1-6中,应用数据3~5字节的绝对偏移对应的TCP报文丢失(对应序号103),后面再发送应用数据的6~8字节,回复也只会是确认号103。只能先重传序号103字节的TCP报文,再重传序号106的TCP报文,但实际上序号106的TCP报文之前已经到达了对端。
为了解决这个问题,TCP提供了一种确认的优化方案SACK(Selective Acknowledgement),即选择性确认,可以不按照顺序确认报文。SACK是一个可选机制,需要发送端和接收端协商,SACK选项格式如图1-7所示。
图1-5 TCP应用数据的发送和确认原理
图1-6 TCP丢包情况下的确认
图1-7 SACK选项格式
在TCP实现的默认方式中,只有TCP首部中的ACK字段可用,所以确认时只能传递一个数值——期望序号。如果想要传递更多的确认信息,则是需要扩展TCP选项SACK来实现,SACK选择可以携带几个分散的确认块。由于整个TCP选项的长度不能超过40字节,所以携带的确认块数不能超过4个(SACK选项中类型占1字节,长度占1字节,每个边界占4字节)。这在一定程度上加快了TCP的发送速度,减少了虚假重传。
2.TFO
在传统的TCP实现中,必须经过三次握手才能够发送数据,为了尽快发送数据,人们提出了TCP Fast Open(TCP快速打开),即TFO,RFC 7413对它进行了定义。TFO是一种TCP重新连接时快速发出数据的方法,具体过程如下。
1)首次建立TCP连接时,客户端在发送SYN的同时携带了Fast Open(快速打开)选项,其中Cookie为空,这表明客户端请求服务器提供Cookie;服务器将Cookie发送给客户端。
2)重新连接时,客户端在发送SYN的同时在Fast Open选项中携带了之前服务器提供的Cookie数据;服务器验证Cookie通过后,接收携带的数据。
TFO的使用方法如图1-8所示,虽然TFO可以将数据发送提前,但是需要客户端、服务器和中间件都支持这样做。客户端的TCP实现一般在操作系统中,难以独自升级;中间件可能不受客户和服务提供者的控制;再加上存在的安全问题,所以很难快速广泛应用。
3.MPTCP
改变本地IP地址会导致TCP重新建立连接,而当今移动终端越来越多,需要能够在网络之间无缝切换。解决这个问题的方案是利用MPTCP(MultiPath TCP,多路TCP),标准见RFC 6824。另外,MPTCP也被用于利用多个信道增加传输速率。
既然已经有了可靠传输的TCP和不可靠传输的UDP,我们的场景不就全覆盖了吗?为什么还需要新的传输协议呢?下面从SCTP(Sream Control Transmission Protocol,流控制传输协议)的产生和发展的历史来说明。
从20世纪80年代起,随着IP技术的飞速发展,IP网络变得越来越普及。电话网络跟IP网络仍然是两个网络,且电话网络单独组建的成本较高。通过IP网络传输电话信号更加经济,因此可借助IP网络的多路径实现更可靠传输。为了使IP网络的普遍性可达范围更广,需要选择一种传输协议实现这种需求。
图1-8 TFO的使用方法
当时普遍使用的传输协议是TCP和UDP,如果选择TCP,有如下问题。
●多个用户的电话在TCP中排队传输,可能所有用户都要等待一个用户的重传。
●TCP是基于字节流的,需要使用者实现消息的拆分,也很难做到将过期数据丢弃。
●电话需要高可靠性,TCP不能支持多路径。
●TCP没有内置安全性和认证,容易受到SYN Flood攻击。
如果选择UDP,使用者则需要先实现以下功能。
●消息排序和消除重复内容。
●检测丢包和重传。
●拥塞控制。通过拥塞控制可以更好地利用网络带宽,提高带宽利用率,从而避免大量报文阻塞网络,提高传输效率,还与其他应用公平分享带宽,避免互相影响。
●PMTU(Path Maximum Transmission Unit,路径最大传输单元)探测。不探测PMTU就难以在保证可靠性的条件下提高网络承载比,甚至可靠性都难以实现。
●流量控制。通过流量控制可避免接收端因缓存满了而无法接收的情况下,发送端还在发送消息,白白消耗资源。
可见TCP和UDP都不适用于电话信号传输。于是在1997年,产生了新的传输协议MDTP(Multi-Network Datagram Transmission Protocol,多网数据报传输协议),并于1998年提交给了IETF(The Internet Engineering Task Force,因特网工程任务组)。这促成了SIGTRAN(Signaling Transport,信号传输)工作组的成立,其目标是废除现存电话信号协议,包括ISUP(ISDN User Part,ISDN用户部分)、DSS1(Digital Subscriber Siganaling No.1,1号数字用户信令)等,生成电话信号在纯IP网络中传输的协议。经过1998~2000年集中讨论后MDTP改名为SCTP,2000年标准化为RFC 2960,并移交给TSVWG(Transport Area Working Group,传输领域工作组)。之后又更新为RFC 4960,并于2022年最终更新为RFC 9260。
SCTP最初的目的是为了保证七号信令在无QoS(Quality of Service,服务质量)保证的IP网络上完全可靠传输,语音流则可以半可靠传输,这也就是SIGTRAN工作组的目标。虽然之后转交给了TSVWG,希望走向更通用的传输协议方向,但电话信号传输的出身仍然影响了其适用场景。
SCTP作为一种类似TCP的传输协议,可以按序可靠传输数据并同样有拥塞控制功能,且与UDP一样以消息为粒度发送和交付。SCTP支持应用数据的可靠不按序传输、不可靠传输 和基于时间的部分可靠传输。SCTP还可以支持多宿主和多流,多宿主指的是端点可以在一个偶联中使用多个地址,多流可以将应用数据分开传输,避免队头阻塞。具体来说SCTP有以下特征。
1)基于消息(不同于TCP基于字节流)的顺序传输,可以将应用程序要发送的小块数据组成PMTU范围内的大数据包,把应用程序要发送的大块数据拆分成适合PMTU的相对较小数据包,并在接收端重新组装后发给上层应用。对于基于消息的上层应用来说,这样就不需要自己定义结构去维护消息边界了。
2)多流中的每个流分别可靠地按序传送消息,一个流的数据丢失不会影响其他流的进度,从而解决了TCP的队头阻塞问题。
3)多宿主中的每个端点都可以有多个IP地址,可以接入多个网络。当一个网络出现了问题,SCTP立即切换到其他网络,再利用路径的冗余实现端到端的可靠性和路径间的负载分担,这样就充分利用了网络带宽。
4)基于Cookie的四次握手(见图1-9)针对TCP的三次握手的SYN Flood攻击做出了改进。
图1-9 SCTP四次握手
5)三次挥手机制(见图1-10)可避免TCP的半关闭状态问题。SCTP采用了认证机制,每一个消息都包含了归属SCTP偶联的认证标记,所以不会出现像TCP一样新旧连接的混淆问题,所以也就不需要TIME_WAIT状态。
6)首部中包含了两端协商的验证标签,如果收到的SCTP报文验证标签错误就丢弃,具有更高的安全性。
7)SACK确认方式可以将接收到的不连续的控制块告知发送端,发送端根据这些接收信息重新发送接收方没有收到的数据。不用像经典TCP一样,需要等待没有收到的数据块,才能继续确认后面的数据。这样可以提高发送方的传输效率,避免接收到的不连续数据块的重传,也可以更积极地发现丢失的报文,以便尽快重传。TCP虽然后来也出现了SACK功能,但是将其作为可选项,需要两端协商。SCTP则将SACK作为唯一的确认方式。
图1-10 SCTP三次挥手
SCTP最初是被设计为在IP网路上传输公共交换电话网络(Public Switched Telephone Network,PSTN)消息。目前,SCTP已经被用于4G通信中eNB(Evolved Node B,演进型基站)和eNB的通信,5G通信中gNB(the next Generation Node B,下一代基站)和AMF(Authentication Management Function,认证管理功能)、gNB和gNB的通信。SCTP的主要应用场景还是在运营商内部,终端的应用目前还很少使用,原因如下。
●操作系统支持有限,很多操作系统是不支持SCTP的。实际上最终Linux支持的也不是原生的SCTP,而是基于UDP的SCTP。
●中间设备支持有限,比如NAT(Network Address Translation,网络地址转换)、防火墙等可能会丢弃SCTP报文。
●协议复杂,不容易理解,相对于TCP来说使用也不方便,用户不习惯(尤其是已经习惯了TCP)。
●调试不方便、工具少,不像TCP、UDP有很多工具可以用于调试和分析。
●上层应用协议没有支持,比如HTTP等。
●SCTP源于运营商需求,很多特性都是针对运营商的内部场景定制的,未必契合终端用户的需求。
但是我们也看到一些终端应用传输在尝试使用SCTP。比如WebRTC(Web Real-Time Communications,网络实时通信)支持SCTP传输,但是使用的是SCTP over UDP,使用UDP主要是为了解决内核和中间件的支持问题,但不支持多宿主,丢失了SCTP的部分可靠性。RFC 6951中详细介绍了SCTP over UDP的实现。
有趣的是MDTP最初就是基于UDP的,IETF最终决定SCTP应该直接用于IP,否决了MDTP基于UDP的方案。但是直接基于IP意味着SCTP必须进入内核,这是个非常长久的过程,而且内核可能考虑到中间件的影响和应用的选择,不太愿意接纳原生的SCTP。Linux内核没有选择原生的SCTP,而是从5.11.0版本开始支持SCTP over UDP,可见终端上的传输层还是要适应多样的终端操作系统和复杂的网络中间件,无法完全抛开TCP和UDP。
互联网协议还有一些其他的传输协议,比如KCP、RTP(Real-time Transport Protocol,实时传输协议)等。
KCP也是一种保证可靠性的传输协议,虽然没有规定下层传输方式,但一般使用UDP。TCP更看重带宽利用率,尽量用有限的带宽传输尽量多的数据;而KCP则牺牲了一部分带宽,换取更快的传输,更合适于时延要求比较严格的应用。相对TCP来说,KCP采取了更积极的重传,一般还会启用前向纠错,用更高的带宽占用换取传输效率,多用于游戏加速等场景。
RTP经常与RTCP(Real-time Transport Control Protocol,实时传输控制协议)配合传输实时数据,比如交互式音频和视频数据。RTCP用于传递控制信息,RTP用于传输实时数据,具体实现参见RFC 3550。RTCP为RTP的传输质量提供反馈信息,RTP可以根据这些信息调整发送速率或者发送策略,另外RTCP为RTP提供了全局唯一的标识(CNAME)和用户加入离开等控制信息。为了实现终端和中间件的兼容性,RTP和RTCP通常都是基于UDP传输的,一般用于交互式视频和音频传输,可用于组播场景,如图1-11所示。
图1-11 RTP和RTCP