TCP 协议

目录

内容简介

1. 前言

2. 可靠的 TCP 协议

3. Socket 套接字


内容简介


  1. 前言
  2. 可靠的 TCP 协议
  3. Socket 套接字

1. 前言


上一课我们阐述了为什么 OSI 第 4 层有两个协议:UDP 和 TCP。

我们也学习了 UDP 协议,它是一种无连接的协议,不在乎数据包有没有被接收方收到,讲究快速高效,所以是不可靠的。但 UDP 在有些领域是非常适用的,因为非常快捷高效。

这一课,我们就来学习著名的 TCP 协议吧。

2. 可靠的 TCP 协议


TCP 是 Transmission Control Protocol 的缩写,表示 “传输控制协议”。transmission 表示 “传输”,control 表示 “控制”,protocol 表示 “协议”。

TCP 协议的原则是确认收到的每个信息字节。与 UDP 协议不同,TCP 报文的头部(或简称 “报头”)包含许多信息,为的是能够正确地跟踪连接。

我们首先来学习 TCP 协议的基本原理。

交谈之前,先建立通信


我们可以用打电话来做比喻。在正式交谈之前,打电话的人首先要确保对方在听。例如:

  • 保罗拨打电话,嘟嘟嘟…
  • 彼得接起电话:“Hello ?”(hello 表示 “喂,您好,你好”)
  • 保罗开始了谈话:“Hello,我是保罗!”

可以看到,在正式交谈之前,必须先建立通信。对 TCP 协议来说也是一样的道理。

因此,发送的前三个数据包只是用于建立通信。像交谈前的问候一样,这些将是空的数据包(不包含具体信息),仅用于确保对方愿意与我们交谈。

像打电话一样,TCP 连接将如下建立:

  • 你想和我聊天吗?
  • 是的,我已经准备好了。
  • 好的,收到。让我们开始聊天吧。

为此,TCP 将使用报头中的信息来判断,到底此数据包是一个连接请求,还是一个普通的数据包。

Flag:标志位


既然用于初始化连接的数据包是空的(不包含具体信息),因此在报头中就需要某些标记信息来标明此数据包是一个连接请求、一个响应或是一个确认(确认就是一个空的响应,仅用于告诉另一端的机器我们已经接收到它的信息。就像我们在电话上说 “好的,好的” 来表明我们听到对方讲话一样)。

TCP 报头使用 flag 来作为标记信息。flag 就是 “标志位”,在英语中 flag 是 “旗帜,信号旗,标示” 的意思。

这些标志位只不过是可以取值为 0 或 1 的二进制位(bit)而已。因此,TCP 报头中将有一些二进制位,这些位用于标明所发送的 TCP 消息的类型。

连接的建立


发送的第一个数据包将是一个同步请求,就像打电话时说的 “Hello” 一样,对应的标志位是 SYN(SYN 是 synchronous 的缩写,表示 “同步的”)。所有的标志位都用其英语的三个字母的缩写来表示。

因此,如果我是一个使用 TCP 的客户端应用程序,我想连接到使用 TCP 的服务器应用程序,我将发送一个数据包,该数据包的 SYN 标志位设置为 1(表示开启),表明我想与此应用程序通信,这相当于说 “你想和我交谈吗?”

接收到 SYN 请求的服务器应用程序通常会回复说同意与客户端应用程序进行通信(如果不同意,那么也就没有下文了)。为此,它将发送 ACK 标志位作为响应(ACK 是 acknowledgement 的缩写,表示 “确认”)。除此以外,服务器应用程序还会询问客户端是否要与自己通信,因此除了 ACK 标志位以外,也要设置 SYN 标志位。因此,将在其响应中设置 SYN + ACK 标志位(就是 SYN 和 ACK 这两个标志位都设置为 1)。

但是,如果客户端要求与服务器通信,服务器为什么还要问它是否要与自己通信呢?这不是多此一举吗?

这个问题的答案对于理解 TCP 协议至关重要。事实上,当你想使用 TCP 进行通信时,你不是建立一个连接,而是建立两个连接!

TCP 认为在一个方向上有一个通信,在另一个方向也有一个通信。因此,它为每个通信方向建立一个连接。所以说 TCP 是全双工(Full Deplex)的。我们以前有一课已经讲过单工、半双工和全双工的概念了,我们可以复习一下。

全双工的英语是 Full Duplex,full 是 “满的,全的” 的意思。duplex 是 “双工,双面” 的意思,在网络通信领域表示 “可以双向发送信息”。半双工的英语是 Half Duplex,half 是 “一半” 的意思。

虽然这两个术语好像有点高端,什么全双工、半双工,其实它们的意思非常简单。除了双工,还有一种叫单工的方式,单工的英语是 Simplex。

  • 单工(Simplex):数据传输是单向的。通信双方中,一方固定为发送端,另一方则固定为接收端,数据只能沿一个方向传输,类似汽车的单行道。
  • 半双工(Half Duplex):数据传输是双向的。数据在通信双方之间能够在两个方向上进行发送,但不能同时发送,因此又被称为双向交替通信。无线对讲机就是一种半双工设备,在同一时间内只允许一方讲话:“长江,长江,我是黄河,收到请回答,完毕”;“黄河,黄河,我是长江,已经收到,完毕”。
  • 全双工(Full Duplex):数据传输是双向的。通信双方在发送数据的同时也能够接收数据,两者可以同步进行,类似汽车的双向车道。目前我们打电话,以及手机的通话,都是全双工的例子。

我们可以用下图来表示这三种基本的通信方式:

TCP 协议

因此,当服务器响应 SYN 请求时,它会通过设置 ACK 标志位来表示 “确认”,并通过设置 SYN 标志位来表示另一个通信方向(从服务器到客户端)的连接请求。因此,响应的报文中同时设置 SYN 和 ACK 两个标志位,一般写作 SYN + ACK。

但是,我们的连接还没有完全建立。客户端必须接受服务器发出的连接请求。因此,客户端将发送带有 ACK 标志位的数据包。

可以用下图来总结上面所述的建立连接的过程:

TCP 协议

上图中,我们用蓝色标识从 Client A 到 Server B 这个方向的通信,用红色标识从 B 到 A 这个方向的通信。我们还用颜色标识相关的标志位:用蓝色标识从 A 到 B 的连接请求的 SYN 标志位,以及响应中对其作出确认的 ACK 标志位;用红色标识从 B 到 A 的连接请求的 SYN 标志位,以及最后一个数据包中的 ACK 确认标志位。

因此,TCP 连接是通过交换三个数据包来建立的,这就是为什么此过程被称为 “三次握手”(Three Way Handshake)的原因。three 表示 “三”,way 表示 “路,方向,方法”,handshake 表示 “握手”。

连接的保持


经过 “三次握手” 的过程,双向通信已经建立起来了,应用程序之间可以互传数据包了。在互传数据的过程中,发送的所有数据包上都会设置 ACK 标志,以确认收到了先前的数据包。

可以用下图来总结连接的保持:

TCP 协议

连接的终止


就像通话会有结束的时候,TCP 连接也有终止的时候。

“早知道伤心总是难免的,你又何苦一往情深…”

当应用程序完成通信后,还须要关闭它们之间的 TCP 连接。是的,我们不会无限期地保持连接状态。如果我们从不释放它们,那么所有的端口很快都会被占用。

因此,与使用标志位来建立连接的方式相同,我们将执行类似的操作以关闭连接。我们将使用与 SYN 标志位具有相反作用的 FIN 标志位。FIN 是 finish 的缩写,表示 “结束,完成”。

因为 TCP 连接是全双工的连接,所以需要终止两个方向的连接。连接的终止将以如下步骤进行:

  1. 客户端 A(Client A)发送一个设置了 FIN 标志位的数据包给服务器 B(Server B),请求关闭从 A 到 B 的连接。

  2. 服务器 B 收到这个设置了 FIN 标志位的数据包,发回一个 ACK 数据包作为确认。从 A 到 B 这个方向的连接被终止。

  3. 服务器 B 请求关闭从 B 到 A 的连接,发送一个 FIN 数据包给客户端 A。

  4. 客户端 A 发回 ACK 数据包确认关闭请求。从 B 到 A 这个方向的连接被终止。

可以用下图来总结上面所述的终止连接的过程:

TCP 协议

与之前建立 TCP 连接时的 Three Way Handshake(“三次握手”)对应,TCP 连接终止的过程被称为 Four Way Wavehand,表示 “四次挥手”。four 表示 “四”;way 表示 “路,方向,方法”;wavehand 其实是一个缩合词,wave 表示 “挥手,挥动”,hand 表示 “手”。

至于为什么 TCP 连接建立时,“三次握手” 的第 2 步中 SYN 和 ACK 可以合并在一起设置,那是因为此时连接没有建立,还没有数据传输。而 TCP 连接终止时,“四次挥手” 的第 2 步的 ACK 和第 3 步的 FIN 没有合并在一起设置,是因为大多数情况下从 B 到 A 的方向还有数据要发送。服务器 B 将最后的数据发送完毕后,再向客户端 A 发送 FIN 报文来请求关闭从 B 到 A 的连接。

当然,上图中,编号为 (1) 和 (3) 的步骤中,数据包中除了 FIN 这个标志位被设置,一般也设置了 ACK 标志位,用于回复上一个收到的数据包。这里为了不引起大家的混淆,所以我没有写成 FIN + ACK 的形式。很多教材上编号为 (1) 和 (3) 的步骤上写的是 FIN + ACK。
.
很好,我们刚刚看到了 TCP 连接的过程:

  • 使用 “三次握手” 来建立连接;
  • 连接的保持;
  • 使用 “四次挥手” 来终止连接。

接着,我们就来看看 TCP 报头的详细信息,尤其是我们所说的标志位。

TCP Segment(TCP 报文段)


这对我们来说是一个新术语:TCP Segment,又称为 “TCP 报文段”,或 “TCP 段”。segment 表示 “段,部分”。

我们之前学习过以太网帧,IP 数据报, UDP 数据报,现在又多了一个 TCP 报文段。其实,TCP 报文段也没什么神秘的,它就是 TCP 协议传输的报文(message)单元(unit)。

我们不会详细介绍 TCP 报文段的头部的所有信息,而是重点关注我们感兴趣的元素。之后我们再详细介绍。毕竟不能一次摄入太多,会消化不良的。

TCP 报文段的格式如下图:

TCP 协议

可以看到,上图中仍然有一些问号区域。不用担心,我们将在以后的课程详细介绍。

TCP 报文段的头部占 20 字节(Byte),比 UDP 报头的 8 字节要大很多。让我们详细介绍一下上图中可以看到的区域(没有用问号标注的):

  • 源端口号和目标端口号:我们已经知道这两个区域的概念了,它们各占 2 字节。

  • 标志位(flag):一共有 6 个标志位,我们已经知道其中 3 个(SYN,ACK,FIN)了。每个标志位占 1 个 bit(二进制位),所以一共是 6 Bit(二进制位)。

    • SYN
    • ACK
    • FIN
    • RST
    • PSH
    • URG
  • Checksum(校验和):和 UDP 报头中的类似,都是为了确保发送的数据和接收的数据是相同的。

我们还剩三个标志位要说明,其中 RST 这个标志位一般比其他两个更重要。

在之后的课程中,我们将看到在 TCP 中发送的每个数据都必须被确认。如果发送的数据和接收的数据之间存在不一致,则该连接就被认为是异常的。

意识到该连接异常的机器必须警告连接的另一端的机器,需要停止该连接,并建立一个新的连接。这是通过发送设置 RST 标志位的数据包来完成的。RST 是 reset 的缩写,表示 “重置”,所以 RST 标志位用于重置连接。

如果机器 A 和机器 B 建立了 TCP 连接,并且在进行了一些交换之后,机器 A 意识到连接中存在信息的不一致,它将发送一个包含 RST 标志位的数据包,请求机器 B 同意关闭此连接。这一次,连接的终止不是用 “四次挥手” 了。

同样地,如果我将 SYN 数据包(用于请求建立连接)发送到已关闭的机器的端口,该机器会回复 RST 数据包,以告诉我所请求的端口未在监听。

用 RST 标志位来说明端口已关闭的这一概念很重要,因为当我们想要扫描机器上的开放端口并扮演初级 “黑客” 时,我们将使用此标志位。

至于 PSH 和 URG 这两个标志位,我们可以设置以表明接收方机器必须优先处理此数据包。但是我们不会详细说明这两个标志位的使用,因为对于理解网络的运作方式不是那么重要。

PSH 是 push 的缩写,表示 “推送,传送”。URG 是 urgent 的缩写,表示 “紧急的”。如果你想了解关于 PSH 和 URG 更多信息,可以去网上搜索相关资料。

3. Socket 套接字


我们再来谈一个知识点,就是 socket。socket 表示 “套接字”,socket 的原意是 “插座”

我们常听说 socket 编程。一般的主流编程语言里都有对 socket 的API(Application Programming Interface,应用程序接口)。

光看 “套接字” 这个术语,好像很深奥的样子。但 socket 其实很简单,就是我们之前说过的 IP 地址 + 端口号,例如 192.168.0.106:80,IP 地址和端口号之间用冒号来分隔,用来表示连接到某台机器上的某个应用程序。IP 地址用于标识一台机器,端口号用于标识机器上的一个应用程序。

socket 就是操作系统(Operating System)提供的独立于网络协议的一种抽象(abstraction),应用程序可以通过 socket 来发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。

socket 的原理图如下:

TCP 协议

当然了,上图中的应用程序,也可以写成 进程(process),因为进程简单来说就是运行起来的应用程序。

这样,程序员进行网络编程时就轻松多了,不需要实现复杂的 “三次握手”、“四次挥手” 和其他的一些操作,只需要用 socket 提供的一些 API 就可以了。

socket 最初是加利福尼亚大学 Berkeley 分校为 Unix 操作系统开发的网络通信接口。后来随着 TCP/IP 网络的发展,socket 成为了最为通用的 API,也是在互联网上进行应用开发最为通用的 API。

不同的应用程序之间要沟通,就先用 socket 来连接两端,好像用两个插座插入两个应用程序一样。就像下图的形象比喻:

TCP 协议

关于 socket 编程,我们就不详述了,你如果感兴趣可以去网上搜索资料,一般对应好几个函数 / 方法。

像 C 语言这样面向过程的编程语言中,就有 socket (),bind (),connect (),listen (),accept (),send () /recv (),write () /read (),close (),等等函数。

像 Java 这样的面向对象的编程语言中,一般有 Socket 这个类,还有其他的一些类,类的方法实现了连接的建立,连接的终止,数据发送,等等操作。


很好,我们暂时已经看到了 TCP 报文段的部分内容,以及对连接的追踪。也了解了 socket(套接字)其实就是一种抽象,相当于 IP 地址 + 端口号,是为了方便进行网络编程而创建的。

上一篇:交换机端口扫描工具小课堂


下一篇:网络概念与常见问题全解析(学会这篇,网络面试题基本都能答上来)