什么时候选择TCP,什么时候选择UDP?
在接到网络软件开发项目的时候,首先要考虑到的一个大问题就是,究竟应该使用tcp还是udp,或者是采用混合的做连接来实现, 这是在搭建软件整体框架的时候考虑的最多的地方,也是最困难的选择,每一个新手基本都会在这里栽跟头,吃苦头,然后才能慢慢的成熟.
什么时候选择tcp,什么时候选择udp,什么时候采用多连接,什么时候采用混合连接?雾里看花,头晕了吧,这么简单的选择还有这么多学问?无论是经典的Unix网络编程,还是tcp/ip协议卷1-3,都只是笼统的描述了一下大概如何选择,且不说对与错,但是今天从我的角度看,这几个经典的著作里,其实都是从协议本身特性来指导大家如何选择,却没有考虑到tcp协议栈本身实现存在的各种局限, 网络上有很多文章,什么大师,架构师之类的,往往是从cpu , 缓冲 , 多队列网卡等硬件层,加上 Linux下的epoll,windows下的完成端口等系统调用,来侃侃而谈如何优化web服务之类,什么从1万连接到100万, 说句老实话, 真想做优化, 别的不说, 光是选择的层级我就觉得有问题,因为从1万到100万,造成性能瓶颈的主要方面除了上下文切换导致的开销,其实最大的问题出在tcp/ip协议栈的实现上,不去研究优化tcp/ip协议栈的影响,却抓最底层的硬件和操作系统的系统调用实现,无论如何我是不敢苟同,你把原来的p4 2.0g cpu换成新一点的如xeon e1230 平台, 内存从ddr 400 2g升级到 16g ddr3-1600 , 应对网络的并发连接能力能提升一个等级,大把的钱投进去了,总能听到个响吧.下面是我个人的一点基本总结.
[1] tcp和udp的区别,简单来说, tcp是有序可靠传输, 而udp是不可靠乱序传输,但是实际上你把udp看做IP可能更准确一点,因为可以在udp基础上开发出可靠udp等各种传输.
[2] tcp的优缺点是什么?优点,这是一个国际通行了很多年的标准实现,调用简单,省心,很多应用都基于tcp进行实现的,最关键的是,它的兼容性是跨平台的,也就是,只要你选择的是tcp,那么不管是windows还是linux,unix,只要支持tcp/ip,那么就可以保证实现可靠连接和传输.作为软件的开发者只需要考虑应用层,比如应用协议等的实现就可以了. 至于可靠传输的性能,由于是国际标准,在内核层实现和运行,很多都做了硬件级的优化,因此对比起用udp等实现的可靠传输,本机[注意是本机]的极限传输性能要高很多.
那么tcp有什么明显的缺点呢? 对开发者来说最头大的恐怕就是2条, 第一是每个tcp连接都是1对1连接,这意味着每个连接都需要一个套接字socket,并且需要随时测试是否数据可读可写.当连接数量达到一定的程度,性能会直线下降. 没有经历过大规模并发应用开发的朋友可能很难想像,就一个基本没有数据的空连接怎么会消耗系统的性能呢?其实这个问题,不可以单纯从硬件或者api接口等进行解释,而需要从tcp/ip协议栈的实现中去发现的,从可以查看到的代码,Linux freebsd unix来看,无一列外,在操作系统里使用的是指针链表进行管理, 重要的事情说3遍 , 用的是指针链表 ,指针链表,指针链表 , 看下Linux 下的实现 [不是最新版,不过这个应该不会改变,因为会导致整个代码重构] ,
ip层的接收
for( qp=ipqueue; qp!=NULL; qplast=qp,qp=qp->next)
{
if(iph->id==qp->iph->id && .......
}
新版本4的测试版Linux核心tcp/ip实现, 和之前的比,是使用了rcu_函数来优化,其实换汤不换药,在执行插入和写操作的时候,需要执行rcu_writelock, 这和readlock不同,能且只有一个锁定,而无法多重锁定,性能在这里遇到了瓶颈, 这个瓶颈出现在tcp/ip协议栈的实现中,由于tcp包的构成问题,至少到目前,或者可以遇见的将来,tcp这个严重影响并发性能的问题会一直存在在实现中,这是绝大部分人都始终没有搞明白,为什么tcp在面对海量并发的时候,在超过一定数字后性能出现直线下降的其中一个关键原因. [我曾经尝试过为Linux下Tcp/ip栈的指针链表引入排序等优化,但是经过各种测试,我发现事实上这个实现在大部分情况下性能比排序好,因为排序等反而导致了性能下降,因此除非有革命性的优化出现,目前这个瓶颈会一直存在.]
给出我们自己编写的延迟测试结果 硬件是Intel i3 2350 [2.3g] ddr3 1333 , tcp 连接数量大并频繁小包传输下,协议栈导致延迟非常明显,
看出问题在那里了吗?是个指针遍历操作 , 独占模式 ,一万空连接在目前的cpu前,可能没什么 ,但是如果是并发模式n万小包呢, 那开销n/2......,你再多的cpu核心也没用,一个链表只有一个独占模式的遍历操作,其他cpu只能干着急。所以在面对小数据量海量长时间连接的应用时,选择tcp必须要慎重再慎重. 第二个大问题是tcp是双向流传输,而keepalive这个功能并非普遍支持, 双向流传输意味着,数据是采用流模式过来的,如果切割取决于协议的制定者,例如普遍的采用\r\n作为分割字符, 而keepalive的非普遍实现,则导致另外一个问题,那就是每个连接,必须每过一定时间在应用协议层检测连接是否还存活,最典型的就是ftp,如果没有指令操作,那么客户端每过一定时间必须发送一条指令,通常是noop指令给服务器端,告诉服务器,我还保持着连接,不要中断. 可能有人无法理解,tcp不是可靠连接吗? tcp是可靠连接,要命的时,当tcp建立连接后,如果双方没有应用层的数据传输,那么当物理中断发生的时候,等待的一方是接收不到发生故障的一方的任何消息的,直到没有发生故障的一方,主动发送数据给另一方,出现发送超时的时候,才能给出中断判断,否则就是个死等待,这就是ftp等协议中引入noop等类似机制的原因. 流传输的另一个问题是,无法实现数据的并发传输,而只等排队发送,这在很多应用,特别是游戏类应用是严重的缺陷,无论你有多着急,一个连接就是一个流,你要排队先发送到缓冲,然后由系统负责发送缓冲数据,可能有人说可以使用紧急指针带外数据,但这玩意不是让你用来传输数据的,其实是让你用来发送紧急通知用的,在tcp中使用带外数据,除了带来更复杂的实现,没有什么实质性作用,以ftp为例子,实际上无论你使用使用紧急指针带外数据,都可以中断数据传输的,这个紧急指针在那里除了保持兼容性,没有实际作用.
[3] udp的优缺点是什么? udp是比tcp更接近IP的协议, 通常udp是不可靠传输,但是我们可以在应用层对udp加上校验和序列号,做成可靠传输,这一点不可不知. udp的优点是什么? 书上总是说udp的性能比tcp好,有没有人想过为什么? 其实原因很简单, udp是发射后不管, 不需要对方发送ack包进行确认, tcp由于需要对每一个包进行ack确认,一来一回,就会影响到传输效率,但事实上,这个影响是很小的,如果不考虑丢包和线路不稳定等,这个差距一般只有百分只几,除非你做极限测试. 但实际上,真正用到udp高效传输的场合是非常少的,一个关键的原因在于它的不可靠性,特别是在Internent上,遇到网关路由高负载的时候,优先扔掉udp包,而且有几率发生连续的丢包. 我个人认为,udp最大的优点在于它的可塑性非常强, 我们可以通过各种机制来改造udp,例如实现可靠传输,实现1对多传输, 实现包和流模式同时传输,优先发送,多路双向传输等等, 很多扩展在tcp上是无法实现的,但是通过udp扩展就可以很轻松的做到. 同样,通过扩展udp来实现可靠传输,我们可以避开tcp/ip协议栈实现中指针链表查询导致的性能急剧下降,很多人都没有意识到这点,其实在应对海量连接方面,udp可靠传输能支持的用户数量远超tcp,因为udp不需要那种大规模的链表查询,是个队列操作. 那么udp的缺点是什么,最要命的就是不可靠传输,其次是包的伪造,虽然我们可以通过加入各种机制和扩展,把udp改造成可靠传输,但是由于这个实现是在应用层,因此在面对少量用户大流量传输的时候,极限输出不如tcp,例如本机.
那么如何选择tcp还是udp?
先看下人家怎么选
1 HTTP, http协议现在已经深入影响到我们的方方面面,重要性就不说了, 它采用的是tcp 协议,为什么使用tcp, 因为它传输的内容是不可以出现丢失,乱序等各种错误的,其次它需要跨平台实现,而tcp满足了这个要求,发展到今天,http享受了tcp带来的简洁高效和跨平台,但是也承受了tcp的各种缺点,例如缺少tcp keep alive机制[这个其实是后来添加的支持,并非普遍实现], tcp协议栈的实现问题引发的难以支持海量用户并发连接[只能通过dns等级别的集群或者cdn来实现],协议太复杂导致很难模块化处理[其实这个问题已经在nginx解决了,nginx通过模块化和对协议的分段处理机制,并引入消息机制,避免了多进程[线程]的频繁切换,相比apache等老牌web服务器软件,在应对大量用户上拥有极大的优势。 即使站在今天的角度看,http也确实应该选择tcp.
2 FTP, 这个协议比http更加古老,它采用的也是tcp协议, 因为它的每一个指令,或者文件传输的数据流,都需要保证可靠性,同时要求在各种平台上广泛支持,那么就只能选择tcp, 和http不同,它采用了noop指令机制来处理tcp缺少keep alive机制带来的问题,也就是客户端必须每过一段时间,如果没有发送其他指令,就必须发送一个noop指令给服务器,避免被服务器认为是死连接。 Ftp的缺陷在哪里呢?,其次它的文件传输是采用新的数据连接来执行,等于1个用户需要2个连接,其次当一个文件正在传输的时候,你无法进行其他操作,例如列表,也许你可以把它当作是一一对应的典范,因为这样我们可以直接用命令行进行控制,但是很多用户其实是需要在下载的时候同时进行列表等操作的,为了解决这个问题,很多客户端只要开启多个指令连接[和数据连接],这样一来,无形中额外带给了Ftp服务器很多压力,而采用udp可靠传输就不存在这个问题,但是udp可靠传输是没有跨平台支持的,这样是鱼和熊掌不可兼得,对于这样一个简单的开放协议的实现,tcp是个好选择。
3 POP3/SMTP, 常见的邮件协议,没什么好说的,反应--应答模式,跨平台要求,因此tcp是个选择,这个协议的悲剧在于,当初没有考虑到邮件附件会越来越大的问题,因此它的实现中将附件文件采用了base64编码格式,用文本模式进行发送,导致产生了大量的额外流量。
4 TFTP ,这是一个非常古老的用于内部传输小文件的协议,没有FTP那么多功能,采用的是udp协议,通过在包中加入包头信息,用尽可能简单的代码来实现小文件传输,注意是小文件,是一个值得参考的udp改造应用范例.
5 通常的voip,实时视频流等,通常会采用udp协议,这是以内这些应用可以允许丢包,很多人可能认为是udp的高效率所以才在这方面有广泛应用,这我不敢苟同,我个人认为,之所以采用udp,是因为这些传输允许丢包,这是一个最大的前提
那么现在来归纳一下
[1] 如果数据要求完整,不允许任何错误发生
[A] 应用层协议开放模式 [例如http ftp]
建议选择tcp,几乎是唯一选择.
[B] 应用曾协议封闭模式 [例如游戏]
(1) 大量连接
[a] 长连接
[一] 少量数据传输
优先考虑可靠udp传输 , tcp建议在20000连接以下使用.
[二] 大流量数据传输
只有在10000连接以下可以考虑tcp , 其他情况优先使用udp可靠传输
[b] 短连接
[一] 少量数据传输
建议使用udp标准模式, 加入序列号, 如果连接上限不超2万,可以考虑tcp
[二] 大流量数据传输
10000连接以下考虑tcp ,其他情况使用udp可靠传输
在遇到海量连接的情况下,建议优先考虑udp可靠传输,使用tcp,由于tcp/ip栈的链表实现的影响,连接越多,性能下降越快,而udp可以实现队列,是一条平滑的直线,几乎没有性能影响.
(2) 有限连接 [通常小于2000 , 一般每服务器为几百到1000左右]
[a] 长连接
除非有数据的实时性要求,优先考虑tcp,否则使用udp可靠传输.
[b] 短连接
优先考虑tcp.
在有限连接的情况下,使用tcp可以减少代码的复杂性,增加广泛的移植性,并且不需要考虑性能问题.
[2] 允许丢包,甚至可以乱序
[a] 对实时性要求比较高,例如voip , 那么udp是最优选择.
[b] 部分数据允许丢包,部分数据要求完整,部分有实时性要求,通常这样的需求是出现在游戏里, 这时候, 基于udp协议改造的udp多路可靠传输[同时支持不可靠模式],基本是唯一能满足的,当然也可以使用tcp来传输要求完整的数据,但是通常不建议,因为同时使用tcp和udp传输数据,会导致代码臃肿复杂度增加,udp可靠传输完全可以替代tcp.
[c]部分数据优先传输,部分可丢弃数据在规定时效内传输, 这通常是实时视频流, 在有限连接模式下,可以考虑tcp+udp , 但是通常, 可靠udp传输是最好的选择.
最后的话, tcp/ip协议很伟大, 在这些基础上诞生了很多划时代的应用,但是时代在发展,需求也在改变,几十年前诞生的基础协议,也遇到了各种问题,典型的是32位地址编码问题,虽然通过nat等技术尽可能的支持更多的机器接入,但是很多应用被限制了,由于tcp/ip协议的巨大影响和事实的上的垄断,导致后续的更新必须考虑到完全兼容, ipv6出现了, 它继承了ipv4的几乎所有优点和缺点,只为了一个字,兼容. 我们可以拥有更快的cpu , 内存, 更强大的tcp/ip系统调用api,但是比较遗憾,tcp/ip协议栈的实现,我们始终无法绕开指针链表, 而正是这,导致了tcp模式在面对海量连接的时候,超过一定数量,网络io性能直线下降, 许许多多的工程师始终认为是cpu 内存不够导致,却没有想到是tcp协议栈的实现上存在性能瓶颈. 在目前的情况下,也只有udp能避开这个协议栈的性能瓶颈,为什么? 因为udp采用的是1对多的虚拟连接, 例如,当虚拟[或者实际]通过udp构建的连接数量是1万个的时候,实际上在协议栈增加的单元只有1个, 而同样1万个连接,tcp增加的单元是1万个,每个片的到达平均要查询5千次,而可靠udp采用队列模式,查询次数是1, 因此, 在今天, 如果你希望你的每台服务器能支持更多的连接,除非你的应用协议需要开放或者兼容其他应用,否则尽可能考虑采用udp, 而不是tcp.
---------------------
原文:https://blog.csdn.net/yjxsdzx/article/details/71937886