TCP协议如何保证可靠传输?

栏目: IT技术 · 发布时间: 3年前

内容简介:协议是计算机通信的基石,为了在世界范围内建立统一的计算机互联网络,国际标准化组织(ISO)于 1984 年发布 ISO/IEC 7498 标准,该标准定义了网络互联的七层框架,自下而上依次为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而 TCP/IP 协议是参考 ISO 制定的理论模型的具体实现,因此 ISO 七层模型又被称为 OSI(Open System Interconnection)参考模型。TCP/IP 协议分为网络接口层、网际层、运输层和应用层,总共 4 层,它将 OSI 参考模

文章正文 编辑

  • 目錄

    • TCP 协议基础

    • 面向连接的协议

    • 善始善终的连接管理

    • TCP 容错功能

    • 从编程实现角度看 TCP 连接

    • TCP 大包分裂和重组

    • TCP 重传机制

    • TCP 滑动窗口机制

    • TCP 拥塞控制

    • 慢启动

    • 拥塞避免

    • 拥塞发生

    • 快速恢复

    • TCP 长连接提升上层应用性能

    • 总结

协议是计算机通信的基石,为了在世界范围内建立统一的计算机互联网络,国际标准化组织(ISO)于 1984 年发布 ISO/IEC 7498 标准,该标准定义了网络互联的七层框架,自下而上依次为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

而 TCP/IP 协议是参考 ISO 制定的理论模型的具体实现,因此 ISO 七层模型又被称为 OSI(Open System Interconnection)参考模型。TCP/IP 协议分为网络接口层、网际层、运输层和应用层,总共 4 层,它将 OSI 参考模型的会话层,表示层和应用层合为一个应用层,将物理层和数据链路层合为一个网络接口层。而五层协议模型是业界跟 TCP/IP 结合产生的非官方模型。三套协议模型的对比见下图:

TCP协议如何保证可靠传输?

虽然三套模型分层略有差别(无实质差别),但都遵从了层次划分的原则:

  • 网络中各结点都具有相同的层次

  • 不同结点的同等层具有相同的功能

  • 不同结点的同等层通过协议来实现对等层之间的通信

  • 同一结点内相邻层之间通过接口通信

  • 每个层可以使用下层提供的服务,并向其上层提供服务

有了公共的通信协议,网络中的任意两台计算机通信都按照相同的逻辑进行:发送方先对数据层层封包,加上本层协议的头部信息,然后通过物理层以比特流的形式到达对方,最后接收方层层去掉头部信息,还原出原来的数据。当然实际过程远比这些复杂,比如数据正确性校验、失败重传等。原始数据经过每层的处理如下图所示(以 OSI 参考模型为例):

TCP协议如何保证可靠传输?

TCP/IP 协议准确的说是一个协议族,包括 TCP 协议、IP 协议和 ICMP 协议和 HTTP 协议等。按照模型层次从上到下依次列出每层常见协议如下图所示:

TCP协议如何保证可靠传输?

接下来我们把观察的“镜头”拉近,聚焦在运输层的 TCP 这个具体的协议上,看看这个面向连接、字节流的通信协议如何保证其可靠性?

TCP 协议基础

TCP/IP 协议能达成的功能或者作用由其协议头结构组成,如下图所示:

TCP协议如何保证可靠传输? (图片来源:http://c.biancheng.net/view/6427.html)

协议头主要由源端口和目的端口、序列号、确认序列号、标志位、窗口大小、TCP 校验和、紧急指针组成。其中

  • 序列号和确认序列号能够保证数据接收的顺序;

  • 标志位中的 RST、SYN 和 FIN 跟连接有关,用于建立和释放连接;

  • 标志位中的 CWR、ECE 和窗口大小字段涉及到滑动窗口,用于保证服务质量;

  • 标志位中的 URG 和 PSH 跟数据的紧急程度和优先级有关,实现数据传输的控制功能。

跟 TCP 协议相对应的 UDP——面向无连接、不可靠的数据传输协议——协议头要简单得多,同时提供的功能和服务也少得多。不同的是多了一个表示数据长度的字段。

那自然要问,如何知道 TCP 包的数据长度呢?因为在 IP 分组的数据头部有表示分组的长度字段,因此可以根据 IP 包数据大小减去 IP 包头和 TCP 包头的大小计算,UDP 协议头格式如下:

TCP协议如何保证可靠传输?

面向连接的协议

不同于 UDP 的尽最大努力交付,TCP 在正式传输之前先跟接收端就某些“事项”达成一致,然后再开始数据传送,其中某些“事项”包括:如套接字、窗口大小、序列号、MSS(Maxitum Segment Size),这个达成一致的过程就是连接建立的过程,实际上连接双方之间并不存在真正的连接,仅仅是双方维护着双方的状态信息而已。

三步建立连接的过程见下图:

TCP协议如何保证可靠传输?

有几点需要注意和说明的是:

  1. 第一步中 ACK=0,后两步 ACK=1。首先明确 ACK 的含义是指示确认序列号(Ackknowledgement Number,图例中的 ack)字段是否有效,ACK=0,ack 无效。TCP 规定,连接建立后,ACK 必须为 1。

  2. 前两步中的 SYN=1,第三步的 SYN=0。首先明确 SYN 表示在连接时同步序列号,SYN=1 时,说明这是一个请求建立连接或同意建立连接的报文。

  3. 连接发起方和接收方的序列号分别为 x 和 y,表示双方独立初始化序列号。实际上初始序列号根据时钟和连接双方的 IP 地址和端口的哈希结果生成的。至于使用不同的随机序列号就是防止因为在网络中延迟、重复的数据包出现导致连接的混乱。

  4. 恰好三步建立连接,不多不少。其主要原因是防止旧的重复 SYN 包出现:假设出现了旧的 SYN 包,接收方通过 SYN+ACK 回应,客户端就可以根据确认序列号判断该连接过期无效,从而发送 RST 报文终止此连接。同时,只有第二步和第三步完成,双方才同步完序列号,少一步不行,多一步浪费!

善始善终的连接管理

因为连接的双方一直维护着双方的状态信息,当数据传输完毕,理应释放连接。相比连接的建立,释放连接要略微复杂一些,见下图:

TCP协议如何保证可靠传输?

有几点需要注意和说明的是:

  1. 第一步和第三步都是 FIN 报文。FIN 标志表示数据是否传送完毕,如果是说明可以释放连接,跟 RST 不同。从发送 FIN 到对方响应 FIN 之间有一个间隔过程。在此期间,被动关闭的一方还可以继续传送数据,直到其传送完成为止。

  2. 第二步和第三步中的确认序列号 ack 相同。这两个报文中的序列号都表示希望接下来从主动关闭方接受一个相同序号的报文。

  3. 双方发送 FIN 报文都需要一个 ACK,两边对称,各两个报文,一共 4 步,完成连接释放。

  4. 发起 CLOSE 的一方经过三个报文,状态依次经过 FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT,在进入 CLOSE 状态先等待 2MSL(Maximum Segment Lifetime)。这是因为最后一个 ACK 可能会丢失,假如主动关闭一方直接从 TIME_WAIT 进入 CLOSE,被动关闭的一方没有收到 ACK,就会重新发送 FIN 报文,这就导致被动关闭一方无法释放连接。同时由于重新发送的报文还在网络中,可能会导致其他正常连接的关闭。

TCP 容错功能

从编程实现角度看 TCP 连接

一个同步阻塞式的 TCP 连接有两部分组成:服务端通过 bind 方法绑定一个事先选定的端口,并通过 listen 方法监听客户端的连接请求。客户端通过 connect 发起连接请求,在该方法中发送 SYN 报文。服务端接到 SYN 报文后将客户端连接信息放在 syns_queue 半连接队列,同时回应 SYN+ACK 给客户端,服务端收到客户端的 ACK 之后,将 syns_queue 的连接信息移到 accept_queue 全连接队列,到此才表示一个完整的连接已经建立。之后上层应用通过 accept 方法调用将 accept_queue 队列的连接信息取出,如下图:

TCP协议如何保证可靠传输?

这里面有几点需要注意和说明的是:

1. syns_queue 队列是有界队列,容量不够怎么办?

首先看看容易导致容量不够的一大原因:当客户端持续发送 SYN,但不对服务端发送的 SYN+ACK 确认,此时半连接队列的连接信息就不会移动到全连接队列。这种持续性的 Flood 攻击极容易导致 syns_queue 队列满。

2. accept_queue 队列是有界队列,容量不够怎么办?

当应用程序不能及时处理连接请求,造成 accept_queue 满,之后新发送的 SYN 或者 ACK(用于建立连接)将不会被处理,除非队列出现空闲位置。

对以上问题的解决方法,详细参考:https://coolshell.cn/articles/11564.html

TCP 大包分裂和重组

因为链路层的通信协议对每次传送的数据传输单元(maximum transmission unit,MTU)大小有限制,比如以太网的 MTU 为 1500 字节,802.3 的 MTU 为 1492 字节。MTU 也就是最大数据长度,反应到上层协议就是 IP 数据分组的最大长度,TCP 的最大数据分段大小(maximum segment size,MSS)。两者的关系为 MSS=MTU-sizeof(IP Header +TCP Header),如下图所示:

TCP协议如何保证可靠传输?

这就引出以下几个问题:

1. 如何设置 MSS 的值?

MSS 的值过小或者过大都不行,过小的数据负载,比如 TCP 传输 1 个字节同样占用 20 个字节的通信头,占用网络带宽,通信效率太低,所以这种一般用于传递控制信息。过大的数据负载,导致 IP 的数据分片,接收端收到分片需要还原原来的数据包,如果任意一个数据包传输失败,还需要重试,从而导致传输开销太大。所以最佳的大小是满负载传输。

2. 通信双方如何确定 MSS 的值?

在 TCP 连接的前两步中,通过头部可选数据部分传递 MSS,取两者的最小值。

3. 当 TCP 传输的数据不是按照最大负荷 MSS 会发生什么现象?

因为 TCP 报文头中没有表示数据长度的字段,虽然对单个数据包的数据长度可以由 IP 包的数据大小计算出来,但是对连续的 TCP 数据包,上层协议是无法得知 TCP 包有多少,何时结束,对于接收端来说,TCP 数据是无界的字节流,实际上在 Socket 实现的时候也是发送端通过系统调用 send 将上层数据发送到发送缓存队列即返回(记为 SendQ),接收端只管从接收缓存队列(记为 RecvQ)。只要发送缓存队列没有满,发送操作就不会阻塞,同样的,接收缓存队列不为空就不会阻塞,数据发送端和接收端的收发示意图如下:

TCP协议如何保证可靠传输?

当上层发送数据太多或者过小,进行 TCP 封包的时候就可以根据当前环境(比如缓存队列的数据量、根据网络拥塞情况动态设定的接收窗口大小等)灵活设定数据包的大小,比如将大包拆小或者将小包合并成大包,于是在网络上传送的 2 个包可能会出现以下 4 种情况:

TCP协议如何保证可靠传输?

上图中四种情况解释如下:

  • 两个包相互独立,互不影响;

  • 两个包合为一体(即粘包:同一个包头,但数据合并在一起);

  • 某一个包拆分成两个(即拆包成两个或多个),其中一部分合并到另一个包。

这就是 TCP 的粘包和拆包现象,粘包的原因可能是协议为了优化网络传输效率,将多个小包合并到一起等,而拆包的原因无外乎是上层协议传输的数据过大,为保证正常传输,将大包拆小。

这种现象只会发生在 TCP 协议上,UDP 协议因为是无连接的,数据的分片是在 IP 层完成的,在接收端的传输层可以根据包长度自动完成数据的组合还原,因此接收端收到的仍是完整的数据包。相对于 TCP 的无界数据来说,UDP 的包数据是有界的。

TCP 重传机制

重传是保证 TCP 可靠性的重要手段之一,因为数据包可能因为网络等问题导致数据包丢失,甚至是接收方发送的 ACK 包本身丢失,它们都会导致发送方重新发送数据包。这里面涉及到很多问题,逐条列举如下:

1. 什么时候重发数据包?

这里自然引入超时机制,在发送包的同时启动一个定时器,如果没有在超时之前收到 ACK,那么发送方就会重发。

2. 如何确定超时时间 RTO(ReTransmission Timeout)?

正常情况下,收到确认包需要一来一回的往返时间 RTT(Round Trip Time),而 RTT 本身到网络拥挤等情况影响波动会较大,实际上在确定 RTO 的时候在 RTT 的测量结果基础上做平滑处理,自适应调整的。

3. 如何提高重传效率?

使用超时重传需要等到超时时间过期为止,假如当前通信网络比较糟糕,势必带来大量的超时等待。为了优化超时等待的时间,发送方连续接收到接收方 3 次 ACK 就立即重传,减少不必须的等待。如下图所示的快速重传,2 号包发送失败,虽然之后的包都收到了,但是接收方仍然回复对 2 号包的确认,当重复次数达到 3 次就立即重传 2 号包:

TCP协议如何保证可靠传输? 当 2 号包收到之后,通过如何处理后续已经收到的包,可以引申出两种优化后的通信协议:

  • 回退协议 :比如上图中收到重传的 2 号包后继续重传 3-5 号包,这种回退直接发送后续包的做法虽然处理起来较快,但是占用网络带宽。另一种处理做法是只重传失败的包。

  • 选择重传 :比如上图中收到重传的 2 号包接着确认上次已经接收到的 5 号包(即将接收 6 号包)。这就是选择重传 Selective Acknowledgment(SACK),它需要接收方额外存储已经收到的包并且对其重排序,处理较慢,但是节约网络带宽。实际上,选择重传可以一般化描述为,接收方可以收到很多不连续的包,即存在包“空洞”,此时接收端需要明确告诉接收方哪些包已经收到,哪些需要重传。使用选择性重传,需要通信双方都支持,并且在 TCP 头部 option 字段中使用 SACK,一个示例如下图所示:

TCP协议如何保证可靠传输? 4. 重传何时结束?

换个问法就是如果超时重传或者快重传之后,发送方仍然没有收到接收方的确认会怎么办?这种情况一般意味着网络比较拥堵或者接收方出现故障,发送方采取的办法是按照指数避让(指数基数为 2)的方式,增加后续重发的时间间隔,当达到最大重试次数(默认 15 次)就不再重发。

TCP 滑动窗口机制

在 TCP 重传机制的示例中,我们已经见到了滑动窗口的影子:发送方在没有接到接收方的确认时,可以连续发送多个数据包,如 1~5 号数据包,接收方异步发送确认。从发送方来看,接收方可以接受发送包的数量就是发送窗口大小。在详细描述滑动窗口前先看下常规的停止-等待协议的示意图:

TCP协议如何保证可靠传输?

每个数据包接收到确认,发送端才会继续发送下一个数据包,这种发送效率很低,自然地可以通过连续发送数据包,如下图:

TCP协议如何保证可靠传输?

发送方和接收方窗口大小都是 3,发送端可以连续发送 3 个数据包,而不需等待每个都收到确认应答才继续发送数据包,这就是滑动窗口的基本思想,相比逐一确认的方式,效率大大提高。下面以一个示例来看看滑动窗口的组成部分:

TCP协议如何保证可靠传输?

假设将所有数据包连起来形成一个队列,对发送端来说,该队列可以分为 4 个部分,具体划分见上图。其中中间 2 部分就是我们关心的发送窗口:由已发送但没有收到确认的数据包(#2)和没有发送但可以发送的数据包(#3)组成。

关于滑动窗口需要注意的点:

  1. 图示中将一个数据包抽象为一个字节,发送窗口以字节为单位往前推进。

  2. 当发送窗口中有几个字节数据收到确认,发送窗口的左边沿就前进几个。

  3. 收到的确认可能不是严格按照左边沿从左到右的顺序,比如收到了 #2 中间部分的确认(捎带确认),此时发送窗口左边沿同样可以移动到收到确认的位置。

  4. 发送端的滑动窗口大小是动态变化的,由接收端可用缓冲区大小决定。比如刚开始发送端和接收端窗口大小一样(都是 20),当接收端接收并确认 10 个,但是接收端上层应用没有读取数据,此时接收端仍剩余存储空间大小 10。此时发送端虽然收到了 10 个确认包,但是其滑动窗口右边沿仍然保持不变,除非接收端有更多存储空间出来。

  5. 如果接收端接收窗口为零发送端如何处理?如果接收端无法接收数据,最终反馈到发送端导致发送端发送窗口为零,此时发送端持续性地发送窗口探测报文(比如同样可以使用指数退避算法),直到接收端窗口再次打开。

  6. 接收端如果每次有少量可用窗口就给发送端发送窗口改变的通知,这样就会导致发送端产生大量的小包(数据长度没有达到 MSS 长度),该现象即糊涂窗口综合征(Silly Window Syndrome)。为解决这个问题,发送端和接收端都可以考虑将数据积攒到一定大小,比如采用类似 Nagle 算法,当窗口大小超过一个 MSS 再发送。

TCP 拥塞控制

上面介绍的滑动窗口机制实际上是基于连接的处理端到端的流量控制,是一种微观层面的“治堵”策略,因为没有考虑到网络的整体运行情况。相对应的有一种基于网络整体宏观层面考量的“治堵”策略,这就是拥塞控制。在拥塞控制中引入了一个拥塞窗口(记为 cwnd),此时发送端的发送窗口 swnd 就取拥塞窗口和接收窗口(rwnd)的较小值:swnd=min(cwnd,rwnd)。cwnd 如何取值将在下面介绍。

拥塞控制整体分为 4 个阶段,涉及 4 个算法:慢启动,拥塞避免,拥塞发生,快速恢复。

慢启动

该阶段主要解决如何初始化拥塞窗口大小的问题,即发送方每收到一个 ACK,cwnd 加 1,于是经过一个轮次(窗口中的包全部发送完并且都收到确认)之后,cwnd 大小将翻倍,如下图所示:

TCP协议如何保证可靠传输?

拥塞避免

当 cwnd 增加到 ssthresh (slow start threshold,通常为 65535 字节 )阈值后,进入拥塞避免阶段。该阶段 cwnd 继续增长,但是从指数增长转为线性增长:每收到一个 ACK,cwnd 增加 1/cwnd,经过一个轮次,cwnd 增加 1,如下图所示:

TCP协议如何保证可靠传输?

拥塞发生

cwnd 继续增长,直到发生丢包重传现象。此时根据丢包重传的不同应对策略,有两种拥塞发生策略:

  • 如果是超时重传,首先将 ssthresh 减半,然后 cwnd 重置为 1,然后进入慢启动阶段;

TCP协议如何保证可靠传输?

  • 如果是快重传,首先将 cwnd 减半,然后 ssthresh 调整为减半后的 cwnd,最后进入快速恢复阶段。

快速恢复

快速恢复跟快速重传一起使用,考虑到已经收到快重传收到的 3 个 ACK,所以设置 cwnd = sshthresh + 3,接着重传丢失的数据包。然后根据收到的确认是重传得数据包还是新的数据包设置 cwnd,如果是前者 cwnd=cwnd+1,否则 cwnd=ssthresh;最后进入拥塞避免阶段。

TCP协议如何保证可靠传输?

上面 4 个阶段从状态转换的角度总结为如下图:

TCP协议如何保证可靠传输?

(图片来源:segmentfault.com)

TCP 长连接提升上层应用性能

从上文介绍的 TCP 连接和释放需要经过 3 次握手和四次挥手过程,在高访问量的情况下,大量 TCP 连接和关闭势必严重影响网站性能,为了在这种情况下优化网络性能,可以使用 TCP 长连接。这里就涉及到 TCP 协议之上的 HTTP 协议。早期的 HTTP/1.0,每次响应一个请求就会断开 TCP 连接,除非服务器支持请求头: Connection: keep-alive ,然而在 HTTP/1.1 默认开启了请求头,支持长连接。这样就涉及到长连接的若干问题,一并列举如下:

1. TCP 长连接是否支持复用?

答案是可以的。每个 HTTP 连接都可以共享同一个 TCP 连接,但是得串行使用,也就是当一个 HTTP 响应返回后下一个 HTTP 请求才可以发送。因为 HTTP 协议是纯文本协议,如果多个 HTTP 请求并行发送,客户端对服务器的返回内容就无法区分每个请求的对应关系,至少是处理起来比较困难。虽然 HTTP/1.1 存在 Pipelining 技术,但是还不能保证浏览器能支持,所以浏览器默认都关闭这项功能。

2. 浏览器如何限制并发请求数量?

当浏览器打开一个页面,理论上可以同时打开多个连接请求,如果没有对连接数量的限制,客户端或者服务器肯定无法接受。实际上,浏览器对同一个域名的访问,默认并发连接数量限制为 6 个。

3. HTTP/2 Multiplexing 多路复用功能如何工作的?

在 TCP 连接复用情况下有一个问题是如果前面有个连接处理阻塞,后面的请求也将被阻塞,虽然可以通过多个 TCP 连接来解决这个问题,但是意味着管理更多的 TCP 连接,另一种更优雅的方式是使用一个 TCP 连接,但是可以并行多个请求。不同于 HTPP/1.1 的 Pipeline 串行响应,HTTP/2 Multiplexing 通过并行的方式发送请求和响应,而且对请求和响应的顺序不做要求,作为两者的对比示意图如下:

TCP协议如何保证可靠传输?

(图片来源:freecontent.manning.com)

图示中,HTTP/1.1 客户端发送 3 个请求,通过 3 个 HTTP 连接发送请求和接收响应。HTTP/2 客户端 3 个请求共用一个 TCP 连接,实现多路复用的目的。如何实现的呢?一句话总结就是 HTTP/2 通过二进制分帧技术将 HTTP 请求和响应消息分割为不同类型的帧,然后以帧为单位,在一个双向流通道上交替传输,仍然保持 HTTP/1 的消息格式和语义。

帧分层

对比 HTTP/1 的文本协议,HTTP/2 使用二进制格式,其处理效率自然要高于文本格式,但对高层应用使用透明,因为帧结构并没有改变原来的协议语义。作为对比,看下如下图:

TCP协议如何保证可靠传输?

示例中 HTTP/1 的请求头部分对应 HTTP/2 的 HEADERS 帧,HTTP/1 的请求内容部分对应 HTTP/2 的 DATA 帧,可见 HTTP/2 只是对原有协议内容做了个包装。

帧结构

每个类型的帧首部前 9 个字节固定,之后是变长的载荷,见下图:

TCP协议如何保证可靠传输?

各字段含义见下表:

TCP协议如何保证可靠传输?

其中最重要的是帧类型的定义,其支持 10 种不同的帧数据类型的传输,详情见下表:

TCP协议如何保证可靠传输?

另外在帧结构定义中,我们看到每个帧都有一个流唯一标识 ID,这样发送端和接收端都可以根据该字段对帧数据重排。有了帧结构的理解,对如何实现消息推送,流量控制以及优先级就比较容易理解。

可靠 TCP 的进化

HTTP/2 中多个连接共享同一个 TCP,虽然它通过分帧技术让多个连接并发进行,但仍没有完全解决因为 TCP 丢包导致的阻塞问题,换句话说此时 TCP 本身成了 HTTP/2 的最大瓶颈问题。为了彻底解决该问题,Google 直接另起炉灶,从底层传输协议开始,使用了基于 UDP 的“QUIC”协议,引入了 HTTP/2 的流和多路复用概念,但是多个逻辑流独立,这样不仅让连接速度更快,也解决了阻塞问题。但是 UDP 是非可靠的,无连接协议,如何实现上层的可靠传输呢?QUIC 不仅实现了类似 TCP 的流量控制、传输可靠性的功能还集成了 TLS 加密功能。为了方便比较 HTTP 各版本的区别,可以看下面这个图:

TCP协议如何保证可靠传输?

由图可见,从 HTTPS 开始,协议都建立在 TLS 之上,而传统的 HTTP 并不保证安全性。由此也可推知,数据传输安全的重要性。

总结

本文从 TCP 协议开始,通过面向连接管理的协议、容错功能、失败重传、滑动窗口机制、网路拥塞控制和长连接优化上层应用等方面论述实现 TCP/IP 可靠传输的原理。最后介绍了基于 TCP 之上的 HTTP 协议的一些最新进展,虽然可靠的 TCP 看起来已经要被不可靠的 UDP 取代,虽然看起来像是一个“笑话”,但是这需要一个过程。更重要的是,即使未来 TCP 真的被完全抛弃,但是其“精神”还在,其可靠性仍然通过另一种形式继续存在,如 QUIC。从 TCP 设计之初,安全性和可靠性一直是最重要的两个方面,本文把重点都放在可靠性的讲述上,并不是说安全性不重要,而是越来越重要。全文完。

更多精彩文章,请关注微信公众号:码上观世界。

参考链接:


以上所述就是小编给大家介绍的《TCP协议如何保证可靠传输?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms

Algorithms

Robert Sedgewick、Kevin Wayne / Addison-Wesley Professional / 2011-3-19 / USD 89.99

Essential Information about Algorithms and Data Structures A Classic Reference The latest version of Sedgewick,s best-selling series, reflecting an indispensable body of knowledge developed over the ......一起来看看 《Algorithms》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

html转js在线工具
html转js在线工具

html转js在线工具