谈谈用户态 TCP 协议实现

导语

TCP 协议是目前名气最大、使用最广泛的传输层网络协议。 TCP 是一个可靠的(reliable)、面向连接的(connection-oriented)、基于字节流(byte-stream)、全双工的(full-duplex)协议。 正是因为这些优点,TCP 协议成为了网络协议重点中的重点,是学习、面试、考试上的常客,这也导致了 TCP 的资料很多,但是普遍集中在“形”上面,很多人将三次握手、四次挥手、滑动窗口等知识点背得滚瓜烂熟,但却没有理解 TCP “可靠” 协议的精髓。

TCP 概述

TCP 协议是目前名气最大、使用最广泛的传输层网络协议。

TCP 是一个可靠的(reliable)、面向连接的(connection-oriented)、基于字节流(byte-stream)、全双工的(full-duplex)协议。

正是因为这些优点,TCP 协议成为了网络协议重点中的重点,是学习、面试、考试上的常客,这也导致了 TCP 的资料很多,但是普遍集中在“形”上面,很多人将三次握手、四次挥手、滑动窗口等知识点背得滚瓜烂熟,但却没有理解 TCP “可靠” 协议的精髓。

因此,本着实践加深理解的初衷,笔者跟随 CS144 这门课,会从头到尾实现了一个用户态简易版 TCP 协议。

(说明:CS144 最终会实现一个全栈的网络协议栈,包含数据链路层、网络层和传输层,但是本文的重点聚焦在 TCP 协议上,因此对于其它层协议不做详细介绍,感兴趣的可以自行查询。)

TCP 简单介绍

下面我们分别简单介绍一下 TCP 的特点。

面向连接

面向连接是 TCP 显著特点,在正式数据传输之前 TCP 需要三次握手来协商建立连接,结束传输后又需要四次挥手来结束连接。

以三次握手为例,TCP 需要通过三次握手来确认对端状态,交换起始序号、窗口大小等信息,如下:

 

三次握手流程如下:

  1. 客户端向服务器发送 SYN 包;
  2. 服务器收到 SYN 包后,向客户端发送 SYN+ACK 包;
  3. 客户端收到 SYN+ACK 包后,回复 ACK 包至客户端。

在三次握手的过程中,重点在于 SYN 和 ACK 包的交互,当然也涉及到初始化序号、窗口大小、状态转换等工作,这些细节后面会在实现中详细介绍。

可靠性

可靠性是 TCP 最大的功能点(个人观点,欢迎斧正),TCP 为了保证数据传输的可靠性,做了很多事情,虽然这增加了实现的复杂性,但却是值得的:

  • 校验和,TCP 每个报文都有校验和字段,防止数据丢失或出错;
  • 序列化和确认号,保证每个序号的字节都交付,解决丢失、重复等问题;
  • 超时重传,对于超时未能确认的报文,TCP 会重传这些包,确保数据达到对端;
  • 等等

如下:

 

虽然 TCP 在可靠性上做了很多努力,但仍然不能保证完美的可靠性,只能做到尽最大努力交付。对于可靠性的细节,我们将在后面的实现中详细介绍。

基于字节流

TCP 数据传输是基于流的,意味着 TCP 传输数据是没有边界的,没有大小限制的,可以传输无限量的字节。

但是 TCP 报文大小是有限制的,这主要取决于滑动窗口大小、路径最大传输单元 MTU 等因素。

TCP 数据写、读、传入都是基于字节流,因此常常会有字节流乱序发生,所以 TCP 需要一个重组器组件专门用于流序号的重组工作,当然这涉及到 TCP 具体实现,我们将实现部分详细介绍。

全双工

全双工意味着 TCP 协议通信的双方既可以发送数据,又可以接受数据,双方拥有独立的序号、窗口等信息。

简单来说,一个 TCP 连接既可以是 sender 也可以是 receiver,同时连接拥有两个字节流,一个输出流,被 sender 控制,另一个是输入流,由 receiver 管理。关于这二者的细节,我们将在实现部分详细介绍。

Sponge 协议介绍

Socket API

绝大多数操作系统会在内核中提供 TCP 协议的实现,并对外暴露 socket API,借助 nc 工具,我们可以快速的使用 socket API,并领略它的风采,如下:

 

首先,在一个窗口中通过 -l 参数来监听本地的 9090 端口,然后在另一个窗口中连接该端口:

回到 nc 端口,会发现多出了如下日志:

可以看到,通过 nc 和 telnet 这两个工具,我们很轻易的就建立起了 TCP 连接,而这两个工具本身都是在调用 socket API。 TCP 由操作系统实现在了内核态,并提供 socket API,虽然使用方便,但是却屏蔽了大量信息,给错误调试和定位带来了很大困难。

用户态协议

在用户态上实现协议是一个很有趣且很有挑战的事情,目前最著名的当属 QUIC 协议,QUIC 协议是建立在 UDP 上的一个用户态可靠协议。而 CS144 实现的 TCP 协议,也就是本文后面所实现的 TCP 协议和 QUIC 协议定位非常类似,当然 QUIC 协议在功能特性上完胜,但是这不影响我们以此来学习一个简单可靠的类 TCP 协议。

为了与其它协议进行区分,本文实现的协议我们统称为 Sponge 协议。下面,对 Sponge 协议做一些简单的介绍:

  1. Sponge 协议建立在 UDP 之上(也可以建立在 IP 协议之上,为了避免引入 TUN/TAP 带来复杂,暂时不做延伸);
  2. Sponge 协议是一种简易版 TCP 协议,和 TCP 协议一样有滑动窗口、重传、校验和等功能,但是一些复杂的特性暂时不支持,如:紧急指针、拥塞控制、Options 中的一些选项均不支持;
  3. Sponge 协议并不特别复杂,在 CS144 的课程带领下,完全可以自主实现,并没有什么高深莫测的技术,每个人都有能力去理解和实现。

Sponge 协议概览

下面,我们就以先整体后局部的方式来详细介绍 Sponge 协议。Sponge 协议的主体类为 TCPConnection,该类主要维护 TCP 连接、TCP 状态机等信息数据,并将接收到的报文交给 TCPReceiver 处理,从 TCPSender 获取报文并发送。类图如下:

 

  • TCPConnection 负责维护连接,报文发送、接收分别由 TCPSender 和 TCPReceiver 来负责;
  • TCPSender 负责发送报文,接收确认号(ackno)确认报文,记录发送但未确认的报文,对超时未确认的报文进行重发;
  • TCPReceiver 负责接收报文,对报文数据进行重组(报文可能乱序、损坏等,由 StreamReassembler 负责重组);
  • StreamReassembler 负责对报文数据进行重组,每个报文中的每个字节都有唯一的序号,将字节按照序号进行重组得到正确的字节流,并将字节流写入到 ByteStream 中;
  • ByteStream 是 Sponge 协议中的字节流类,一个 TCPConnection 拥有两个字节流,一个输出流,一个输入流。输出流为 TCPSender 中的 _output 字段,该流负责接收程序写入的数据,并将其包装成报文并发送,输入流为 StreamReassembler 中的 _output 字段,该流由 StreamReassembler 重组报文数据而来,并将流数据交付给应用程序。

Sponge 协议巧妙地将连接分为了 sender 和 receiver 两个部分,并通过重组器、字节流等类将 TCP 连接完美抽象,使代码更易维护和阅读,也使功能迭代和完善更加方便。

Sponge 的数据流图如下所示:

 

从这个图中可以总结出 Sponge(基于 UDP 而非 IPv4) 数据流过程:

  1. 内核态下 UDP 数据包中的 payload 被解析为 TCPSegment(TCP 报文)后,交给用户态下的 TCPConnection,即调用 segment_received 方法;
  2. TCPConnection 收到报文后,将报文交给 TCPReceiver,即调用 TCPReceiver.segment_received 方法,并将报文中的 ackno(确认号)与 window_size(窗口大小)交给 TCPSender,即调用 ack_received 方法;
  3. TCPReceiver 处理 TCP 报文,并将报文中的 payload 推入 StreamReassembler 中,并重组后交给应用程序,随后尝试发送报文;
  4. TCPConnection 调用 TCPSender.fill_window 方法尝试得到待发送报文(可能得不到,视具体情况而定),若有报文,则设置报文 payload 以及其它字段,如 SYN、ackno(从 receiver 获取)、window_size 等,设置完毕后包装为 TCP 报文,将报文交给 UDP;
  5. UDP 将其打包为数据报,并发送给远端。

Sponge 协议实现

在粗略介绍 Sponge 协议后,我们一起来看看 Sponge 协议的具体实现逻辑和一些细节。

在介绍 Sponge 协议时,采用的是先整体后局部的方式,而在说明具体实现时,需要先从局部出发,逐渐上升到整体,因此我们会先从 ByteStream 数据流开始,逐步添砖加瓦最后实现一个完成的 TCPConnection。

这里先给出 Sponge 协议代码核心文件:

<

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值