一.先唠唠嗑

-
拥塞控制的算法策略分类node
-
拥塞控制出现的意义和目的web
-
实现拥塞控制的几种算法和侧重点面试
-
拥塞控制的主要过程和关键点算法
-
BBR算法的一些原理和思路编程
2. 我和TCP/IP协议
三. 走心推荐
四.聊聊拥塞控制算法
看到一篇文章说到TCP拥塞控制算法并非简单的计算机网络的概念,也属于控制论范畴,感受这个观点很道理。缓存
TCP拥塞控制算法的目的能够简单归纳为:公平竞争、充分利用网络带宽、下降网络延时、优化用户体验,然而就目前而言要实现这些目标就不免有权衡和取舍。安全
可是如今的网络通讯基础设施水平一直在飞速提升,相信在将来的某个时间点这些目标均可以达到,小孩子才选择,咱们大人全都要!服务器
算法演进
在理解拥塞控制算法以前咱们须要明确一个核心的思想:闻道有前后 术业有专攻,笔者以为这是一个很是重要的共识问题,把A踩在泥土里,把B吹捧到天上去,都不是很好的作法。
微信
实际的网络环境十分复杂而且变化很快,并无哪一个拥塞控制算法能够所有搞定,每一种算法都有本身的特定和适用领域,每种算法都是对几个关键点的权衡,在没法兼得的条件下有的算法选择带宽利用率,有的算法选择通讯延时等等。网络
在明确这个共识问题以后,咱们对待各个拥塞控制算法的态度要平和一些,不要偏激地认为谁就是最好,几十年前的网络情况和如今是大相径庭的,咱们永远都是站在巨人的肩膀之上的,这也是科学和文明进步的推进力。
算法分类
传统拥塞控制算法并非一蹴而就的,复杂的网络环境和用户的高要求推进着拥塞控制算法的优化和迭代,咱们看下基于丢包策略的传统拥塞控制算法的几个迭代版本,如图所示:
与此同时还有一类算法是基于RTT延时策略来进行控制的,可是这类算法在发包速率上可能不够激进,竞争性能不如其余算法,所以在共享网络带宽时有失公平性,可是算法速率曲线倒是很平滑。
4.1 流量控制和拥塞控制
大约在1988年以前TCP/IP是没有拥塞控制的,可是随着网络接入规模的发展以前仅有的端到端窗口控制已经没法知足要求,在1986年引起大规模网络瘫痪,此时就要提到一个重量级人物:Van Jacobson范·雅各布森。
这位力挽狂澜的人物入选了计算机名人堂Internet Hall of Fame,Van Jacobson大神提出并设计实施了TCP/IP拥塞控制,解决了当时最大的问题,来简单看下Van Jacobson的维基百科简介(笔者作了部分删减):
范·雅各布森Van Jacobson是目前做为互联网技术基础的TCP/IP协议栈的主要起草者,他以其在网络性能的提高和优化的开创性成就而闻名。
2006年8月,他加入了帕洛阿尔托研究中心担任研究员,并在位于相邻的施乐建筑群的Packet Design公司担任首席科学家。在此以前,他曾是思科系统公司首席科学家,并在位于劳伦斯伯克利国家实验室的网络研究小组任领导者。
范·雅各布森由于在提升IP网络性能提高和优化所做的工做而为人们所知,1988到1989年间,他从新设计了TCP/IP的流控制算法(Jacobson算法),他因设计了RFC 1144中的TCP/IP头压缩协议即范·雅各布森TCP/IP头压缩协议而广为人知。此外他也曾与他人合做设计了一些被普遍使用的网络诊断工具,如traceroute,pathchar以及tcpdump 。
范·雅各布森于2012年4月入选第一批计算机名人堂,计算机名人堂简介:https://www.internethalloffame.org/inductees/van-jacobson
如图为Van Jacobson计算机名人堂的简介:
笔者找了Van Jacobson和Michael J. Karels在1988年11月发布的关于拥塞避免和控制的论文,总计25页,感兴趣的读者能够查阅:
https://ee.lbl.gov/papers/congavoid.pdf
咱们经常使用的traceroute和tcpdump也是van-jacobson大神的杰做,做为互联网时代的受益者不禁得对这些互联网发展早期作出巨大贡献的开拓者、创新者、变革者心生赞叹和敬意。
维基百科对于流量控制Flow Control的说明:
In data communications, flow control is the process of managing the rate of data transmission between two nodes to prevent a fast sender from overwhelming a slow receiver.
It provides a mechanism for the receiver to control the transmission speed, so that the receiving node is not overwhelmed with data from transmitting node.
在数据通讯中,流量控制是管理两个节点之间数据传输速率的过程,以防止快速发送方压倒慢速接收方。它为接收机提供了一种控制传输速度的机制,这样接收节点就不会被来自发送节点的数据淹没。
能够看到流量控制是通讯双方之间约定数据量的一种机制,具体来讲是借助于TCP协议的确认ACK机制和窗口协议来完成的。
图中RcvBuffer是接收区总大小,buffered data是当前已经占用的数据,而free buffer space是当前剩余的空间,rwnd的就是free buffer space区域的字节数。
HostB把当前的rwnd值放入报文头部的接收窗口receive window字段中,以此通知HostA本身还有多少可用空间, 而HostA则将未确认的数据量控制在rwnd值的范围内,从而避免HostB的接收缓存溢出。
4.2 为何须要拥塞控制
-
如何感知拥塞
-
如何利用带宽
-
拥塞时如何调整
4.3 拥塞控制的细节
4.3.1 拥塞窗口cwnd
Congestion Window (cwnd) is a TCP state variable that limits the amount of data the TCP can send into the network before receiving an ACK.
The Receiver Window (rwnd) is a variable that advertises the amount of data that the destination side can receive.
Together, the two variables are used to regulate data flow in TCP connections, minimize congestion, and improve network performance.

4.3.2 拥塞控制基本策略

4.4 拥塞控制过程详解
-
慢启动

-
拥塞避免
-
超时重传和快速重传


-
快速恢复
4.5 TCP算法版本和拥塞控制
实际上TCP算法有不少版本,每一个版本存在一些差别,在这里简单看一下维基百科的介绍:
-
算法命名规则
TCP+算法名的命名方式最先出如今Kevin Fall和Sally Floyd1996年发布的论文中。
-
TCP Tahoe 和TCP Reno
这两个算法代号取自太浩湖Lake Tahoe和里诺市,二者算法大体一致,对于丢包事件判断都是以重传超时retransmission timeout和重复确认为条件,可是对于重复确认的处理二者有所不一样,对于超时重传RTO状况两个算法都是将拥塞窗口降为1个MSS,而后进入慢启动阶段。
TCP Tahoe算法:若是收到三次重复确认即第四次收到相同确认号的分段确认,而且分段对应包无负载分段和无改变接收窗口的话,Tahoe算法则进入快速重传,将慢启动阈值改成当前拥塞窗口的一半,将拥塞窗口降为1个MSS,并从新进入慢启动阶段。
TCP Reno算法:若是收到三次重复确认,Reno算法则进入快速重传只将拥塞窗口减半来跳过慢启动阶段,将慢启动阈值设为当前新的拥塞窗口值,进入一个称为快速恢复的新设计阶段。TCP New Reno
TCP New Reno是对TCP Reno中快速恢复阶段的重传进行改善的一种改进算法,New Reno在低错误率时运行效率和选择确认SACK至关,在高错误率仍优于Reno。
-
TCP BIC 和TCP CUBIC
TCP BIC旨在优化高速高延迟网络的拥塞控制,其拥塞窗口算法使用二分搜索算法尝试找到能长时间保持拥塞窗口最大值,Linux内核在2.6.8至2.6.18使用该算法做为默认TCP拥塞算法。
CUBIC则是比BIC更温和和系统化的分支版本,其使用三次函数代替二分算法做为其拥塞窗口算法,而且使用函数拐点做为拥塞窗口的设置值,Linux内核在2.6.19后使用该算法做为默认TCP拥塞算法。
-
TCP PRR
TCP PRR是旨在恢复期间提升发送数据的准确性,该算法确保恢复后的拥塞窗口大小尽量接近慢启动阈值。在Google进行的测试中,能将平均延迟下降3~10%恢复超时减小5%,PRR算法后做为Linux内核3.2版本默认拥塞算法。TCP BBR
TCP BBR是由Google设计于2016年发布的拥塞算法,该算法认为随着网络接口控制器逐渐进入千兆速度时,分组丢失不该该被认为是识别拥塞的主要决定因素,因此基于模型的拥塞控制算法能有更高的吞吐量和更低的延迟,能够用BBR来替代其余流行的拥塞算法。
Google在YouTube上应用该算法,将全球平均的YouTube网络吞吐量提升了4%,BBR以后移植入Linux内核4.9版本。
其中比较有名的Vegas算法是大约在1995年由亚利桑那大学的研究人员拉里·彼得森和劳伦斯·布拉科夫提出,这个新的TCP拥塞算法之内华达州最大的城市拉斯维加斯命名,后成为TCP Vegas算法。
关于基于RTT的TCP Vegas算法的详细介绍能够查阅文档:
http://www.cs.cmu.edu/~srini/15-744/F02/readings/BP95.pdf
文档对Vegas算法和New Reno作了一些对比,咱们从直观图形上能够看到Vegas算法更加平滑,相反New Reno则表现除了较大的波动呈锯齿状,如图所示:
实际上还有更细粒度的分类,因为不是今天的重点,就再也不深刻展开了,当前使用的拥塞控制算法仍是基于丢包Loss-Based做为主流。
五.复杂的网络环境和拥塞控制
咱们知道在网络链路中链接的数量是动态变化且数量巨大的,每一条链接都面临着一个黑盒子式的网络环境,这并不像咱们平时出行时看看地图就知道哪里堵了,为了维护一个好的网络环境,每一条链接都须要遵照一些约定。
若是链接端都无所顾忌地发生数据包,那么网络链路很快就到了瓶颈了,数据通讯彻底没法保障,因此要到达一个稳定高效的网络环境仍是须要费很大心思的,这其中有两个重要的概念:公平性和收敛性。
说来惭愧笔者在网络上找了不少资料去理解TCP拥塞控制的公平性和收敛性,可是仍然没有得到一个很好的权威解释,因此只能结合一些资料和自身的理解去阐述所谓的公平性和收敛性。
5.1 AIMD特性
笔者认为公平性是相对于网络链路中的全部链接而言的,这些共享链路的链接启动和结束的时间不一样,在实际的交互过程当中每条链接占有带宽的机会是均等的,而且因为带宽限制链接双方通讯的数据量是动态调整而且近似收敛于某个值,也就是呈现一个锯齿状或者更加平滑的波动曲线,对于基于丢包的拥塞控制算法而言AIMD线性增乘性减策略起了关键控制做用。
接下来咱们来重点看下AIMD特性,先来贴一张经典的图,直观看AIMD的过程:
看看维基百科对于AIMD的定义:
The additive-increase/multiplicative-decrease(AIMD) algorithm is a feedback control algorithm best known for its use in TCP congestion control.
AIMD combines linear growth of the congestion window with an exponential reduction when congestion is detected.
Multiple flows using AIMD congestion control will eventually converge to use equal amounts of a shared link.
The related schemes of multiplicative-increase/multiplicative-decrease (MIMD) and additive-increase/additive-decrease (AIAD) do not reach stability.
简单翻译一下:线性增长乘性减小算法是一个反馈控制算法,因其在TCP拥塞控制中的使用而广为人知,AIMD将线性增长拥塞窗口和拥塞时乘性减小窗口相结合,基于AIMD的多个链接理想状态下会达到最终收敛,共享相同数量的网络带宽,与其相关的乘性增乘性减MIMD策略和增性加增性减小AIAD都没法保证稳定性。
AIMD相比MIMD和AIAD在链接进入拥塞避免阶段使用试探线性加策略而不是乘性加策略更加安全,在探测丢包时则大幅度乘性减小到1/2这样对于缓解拥塞会有比较好的效果更加快速,相反若是探测到丢包时采用线性减小AD可能拥塞持续的时间会更长,整体来讲AIMD算是一个比较简单实用的工程版本的反馈控制,也具有可工程收敛性,于是被普遍实用。
5.2 弱网络环境下的AIMD
时间拉回20多年前,在互联网早期几乎全部的设备都是经过有线网络进行链接通讯的,这也是拥塞控制在设计以后一直都起到不错做用的重要因素,有线链接的网络稳定性比较好,所以把丢包做为网络拥堵的一个特征也很正常。
再拉回到如今,从2010年以后移动互联网蓬勃发展,移动终端的持有量已经能够称为海量,无线网络的引入让网络环境变得更加复杂,所以不稳定丢包变得更加频繁,可是这时的丢包就不必定是网络拥堵形成的了,由于整个数据包通过多重路由、交换机、基站等基础通讯设备每一个环节均可能发生异常。
在弱网环境下,尤为是移动互联网中以前的基于AIMD的拥塞控制策略可能会因为丢包的出现而大幅下降网络吞吐量,从而对网络带宽的利用率也大大降低,这时咱们采用更加激进的控制策略,或许能够得到更好的效果和用户体验。
恶意丢包的状况下,基于AIMD的拥塞控制确实就至关于被限速了,由于AIMD确实有些保守谨慎了,这个其实也很好理解的哈。
咱们都知道在移动网络环境下是由终端以无线形式和附近的基站交互数据,以后数据传输至核心网,最后落到具体的服务器所在的有线网络,其中最后一千米的区域属于高延时场景,有线网络属于低延时高带宽场景。
在国外有相关实验证实弱网环境下RTT的变化对于使用传统拥塞控制算法下网络吞吐量的影响,数据和曲线如图所示:
实验含义:RTT的增大影响了好比CUBIC这类拥塞控制算法的慢启动等阶段,咱们知道慢启动阶段每通过1个RTT周期拥塞窗口cwnd将加倍,可是更大的RTT就意味着发送方以很低的速率发送数据,更多的时间是空闲的,发包的加速度极大将低了,因此整个吞吐量就降低很明显。
看下实验者的原文表述:
The delay before acknowledgment packets are received (= latency) will have an impact on how fast the TCP congestion window increases (hence the throughput).
When latency is high, it means that the sender spends more time idle (not sending any new packets), which reduces how fast throughput grows.
六.强悍的BBR算法
BBR算法是个主动的闭环反馈系统,通俗来讲就是根据带宽和RTT延时来不断动态探索寻找合适的发送速率和发送量。
看下维基百科对BBR算法的说明和资料:
相关文献:https://queue.acm.org/detail.cfm?id=3022184
TCP BBR(Bottleneck Bandwidth and Round-trip propagation time)是由Google设计,并于2016年发布的拥塞算法,以往大部分拥塞算法是基于丢包来做为下降传输速率的信号,而BBR基于模型主动探测。
该算法使用网络最近出站数据分组当时的最大带宽和往返时间来建立网络的显式模型。数据包传输的每一个累积或选择性确认用于生成记录在数据包传输过程和确认返回期间的时间内所传送数据量的采样率。
该算法认为随着网络接口控制器逐渐进入千兆速度时,分组丢失不该该被认为是识别拥塞的主要决定因素,因此基于模型的拥塞控制算法能有更高的吞吐量和更低的延迟,能够用BBR来替代其余流行的拥塞算法例如CUBIC。
Google在YouTube上应用该算法,将全球平均的YouTube网络吞吐量提升了4%,在一些国家超过了14%。BBR以后移植入Linux内核4.9版本,而且对于QUIC可用。
6.1 丢包反馈策略存在的问题
基于丢包反馈属于被动式机制,根源在于这些拥塞控制算法依据是否出现丢包事件来判断网络拥塞作减窗调整,这样就可能会出现一些问题:
-
丢包即拥塞
现实中网络环境很复杂会存在错误丢包,不少算法没法很好区分拥塞丢包和错误丢包,所以在存在必定错误丢包的前提下在某些网络场景中并不能充分利用带宽。 -
缓冲区膨胀问题BufferBloat
网络链接中路由器、交换机、核心网设备等等为了平滑网络波动而存在缓冲区,这些缓存区就像输液管的膨胀部分让数据更加平稳,可是Loss-Based策略在最初就像网络中发生数据相似于灌水,此时是将Buffer所有算在内的,一旦buffer满了,就可能出现RTT增长丢包等问题,就至关于有的容量本不应算在其中,可是策略是基于包含Buffer进行预测的,特别地在深缓冲区网络就会出现一些问题。 -
网络负载高但无丢包事件
假设网络中的负载已经很高了,只要没有丢包事件出现,算法就不会主动减窗下降发送速率,这种状况下虽然充分利用了网络带宽,同时因为一直没有丢包事件出现发送方仍然在加窗,表现出了较强的网络带宽侵略性,加剧了网络负载压力。 -
高负载丢包
高负载无丢包状况下算法一直加窗,这样能够预测丢包事件可能很快就出现了,一旦丢包出现窗口将呈现乘性减小,由高位发送速率迅速下降会形成整个网络的瞬时抖动性,整体呈现较大的锯齿状波动。 -
低负载高延时丢包
在某些弱网环境下RTT会增长甚至出现非拥塞引发丢包,此时基于丢包反馈的拥塞算法的窗口会比较小,对带宽的利用率很低,吞吐量降低很明显,可是实际上网络负载并不高,因此在弱网环境下效果并非很是理想。
6.2 TCP BBR算法基本原理
前面咱们提到了一些Loss-Based算法存在的问题,TCP BBR算法是一种主动式机制,简单来讲BBR算法再也不基于丢包判断而且也再也不使用AIMD线性增乘性减策略来维护拥塞窗口,而是分别采样估计极大带宽和极小延时,并用两者乘积做为发送窗口,而且BBR引入了Pacing Rate限制数据发送速率,配合cwnd使用来下降冲击。
提及BBR算法总让我想起一款小游戏flappy bird:
咱们须要不断调整bird的飞行高度,可是太高太低震荡太多就很容易挂掉,因此若是可以平滑一些或许会飞得更远哦!
在开始BBR算法以前,咱们先来了解几个有用的术语:
BDP带宽延时积
长肥网络
咱们把具备长RTT往返时间和高带宽的网络成为长肥网络或者长肥管道,它的带宽延时积BDP很大大,这种网络理论上吞吐量很大也是研究的重点。
TCP Pacing机制
能够简单地理解TCP Pacing机制就是将拥塞控制中数据包的作平滑发送处理,避免数据的突发下降网络抖动。
6.2.1 TCP带宽和延时的测量
BBR算法的一些思想在以前的基于延时的拥塞控制算法中也有出现,其中必有有名的是TCP WestWood算法。
TCP Westwood改良自New Reno,不一样于以往其余拥塞控制算法使用丢失来测量,其经过对确认包测量来肯定一个合适的发送速度,并以此调整拥塞窗口和慢启动阈值。其改良了慢启动阶段算法为敏捷探测和设计了一种持续探测拥塞窗口的方法来控制进入敏捷探测,使连接尽量地使用更多的带宽。
TCP WestWood算法也是基于带宽和延时乘积进行设计的,可是带宽和延时两个指标没法同时测量,由于这两个值是有些矛盾的极值,要测量最大带宽就要发送最大的数据量可是此时的RTT可能会很大,若是要测量最小的RTT那么久意味着数据量很是少最大带宽就没法得到。
TCP BBR算法采用交替采样测量两个指标,取一段时间内的带宽极大值和延时极小值做为估计值,具体的实现本文就不展开了。
6.2.2 发送速率和RTT曲线
前面提到了BBR算法核心是寻找BDP最优工做点,在相关论文中给出了一张组合的曲线图,咱们一块儿来看下:
1. 曲线图示说明:
这张图是由两个图组合而成,目前是展现[数据发送速率vs网络数据]和[RTTvs网络数据]的关系,横轴是网络数据数量。
两个纵轴从上到下分别为RTT和发送速率,而且整个过程分为了3个阶段:应用限制阶段、带宽限制阶段、缓冲区限制阶段。
2. 曲线过程说明:
-
app limit应用限制阶段
在这个阶段是应用程序开始发送数据,目前网络通畅RTT基本保持固定且很小,发送速率与RTT成反比,所以发送速率也是线性增长的,能够简单认为这个阶段有效带宽并无达到上限,RTT是几乎固定的没有明显增加。 -
band limit带宽限制阶段
随着发送速率提升,网络中的数据包愈来愈多开始占用链路Buffer,此时RTT开始增长发送速率再也不上升,有效带宽开始出现瓶颈,可是此时链路中的缓存区并无占满,所以数据还在增长,RTT也开始增长。 -
buffer limit缓冲区限制阶段
随着链路中的Buffer被占满,开始出现丢包,这也是探测到的最大带宽,这个节点BDP+BufferSize也是基于丢包的控制策略的做用点。
3. 一些见解
网上有一些资料都说起到了这张图,其中的一些解释也并不算很是清晰,结合这些资料和本身的认识,笔者认为在网络链路的缓存区没有被使用时RTT为最小延时MinRTT,在网络链路缓冲区被占满时出现最大带宽MaxBW(链路带宽+链路缓存),可是此时的MaxBW和MinRTT并非最优的而是水位比较高的水平,有数据代表按照2ln2的增益计算此时为3BDP,整个过程当中MinRTT和MaxBW是分开探测的,由于这两者是不能同时被测量的。
6.2.3 BBR算法的主要过程
BBR算法和CUBIC算法相似,也一样有几个过程:StartUp、Drain、Probe_BW、Probe_RTT,来看下这几个状态的迁移状况:
-
StartUp慢启动阶段 BBR的慢启动阶段相似于CUBIC的慢启动,一样是进行探测式加速区别在于BBR的慢启动使用2ln2的增益加速,过程当中即便发生丢包也不会引发速率的下降,而是依据返回的确认数据包来判断带宽增加,直到带宽再也不增加时就中止慢启动而进入下一个阶段,须要注意的是在寻找最大带宽的过程当中产生了多余的2BDP的数据量,关于这块能够看下英文原文的解释:
To handle Internet link bandwidths spanning 12 orders of magnitude, Startup implements a binary search for BtlBw by using a gain of 2/ln2 to double the sending rate while delivery rate is increasing. This discovers BtlBw in log2BDP RTTs but creates up to 2BDP excess queue in the process.
-
Drain排空阶段
排空阶段是为了把慢启动结束时多余的2BDP的数据量清空,此阶段发送速率开始降低,也就是单位时间发送的数据包数量在降低,直到未确认的数据包数量<BDP时认为已经排空,也能够认为是RTT再也不降低为止,排空阶段结束。 -
ProbeBW带宽探测阶段
通过慢启动和排空以后,目前发送方进入稳定状态进行数据的发送,因为网络带宽的变化要比RTT更为频繁,所以ProbeBW阶段也是BBR的主要阶段,在探测期中增长发包速率若是数据包ACK并无受影响那么就继续增长,探测到带宽下降时也进行发包速率降低。 -
ProbeRTT延时探测阶段
前面三个过程在运行时均可能进入ProbeRTT阶段,当某个设定时间内都没有更新最小延时状态下开始下降数据包发送量,试图探测到更小的MinRTT,探测完成以后再根据最新数据来肯定进入慢启动仍是ProbeBW阶段。
我们来看一下这四个过程的示意图:
曲线说明:这两个坐标给出了10Mbps和40msRTT的网络环境下CUBIC和BBR的一个对比过程,在上面的图中蓝色表示接收者,红色表示CUBIC,绿色表示BBR,在下面的图中给出了对应上图过程当中的RTT波动状况,红色表明CUBIC,绿色表明BBR。
6.2.4 BBR算法的一些效果
有一些文章认为BBR有鲜明的特色,把拥塞控制算法分为BBR以前和BBR以后,可见BBR仍是有必定影响,可是BBR算法也不是银弹,不过能够先看看BBR算法在谷歌推进下的一些应用效果,其中包括吞吐量、RTT、丢包率影响:
从图中咱们能够看到在YouTube应用BBR算法以后,就吞吐量广泛有4%左右的提高,特别地在日本的提高达到14%,RTT的降低更为明显平均下降33%,其中IN(猜想是印度地区)达到50%以上,在丢包率测试中BBR并不想CUBIC那么敏感,在丢包率达到5%是吞吐量才开始明显降低。
写在最后
TCP/IP协议内容很是多,同时也带有年代感,因此我的建议不要总拘泥于网上的那些俗套问题,能够多看看当前的新趋势,好比HTTP3.0、SDN、DPDK这些新鲜的东西,并且不少书籍也比较早,有条件的同窗能够看一些最新的外文资料来了解前沿信息,尤为是对于作音视频或者通讯领域的同窗仍是颇有好处的。
送福利,公众号后台回复“模拟题”获取算法笔试模拟题精解合集,该书籍为阿里云开发电子书系列之一(阿里云开发者社区有很多高质量技术文章,你们能够去观摩学习,有不少书籍都是能够直接免费下载的),涵盖 70+算法题目,近 30 种大厂笔试常考知识点。但愿在你“面试战场”上可以助你一臂之力。

- 欢迎加入互联网大厂内推群 & 技术交流群 -
本文分享自微信公众号 - 程序猿石头(tangleithu)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。