Skip to content

TCP、IP 协议

计算机网络体系结构

OSI 七层模型

开放系统互连参考模型 (Open System Interconnect 简称 OSI)是国际标准化组织(ISO) 和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架。其目的是为异种计算机互连提供一个共同的基础和标准框架, 并为保持相关标准的一致性和兼容性提供共同的参考。这里所说的开放系统,实质上指的是遵循 OSI 参考模型和相关协议能够实现互连的具有各种应用目的的计算机系统。

OSI 采用了分层的结构化技术,共分七层,物理层、数据链路层、网络层、传输层、会 话层、表示层、应用层。

TCP/IP 模型

OSI 模型比较复杂且学术化,所以我们实际使用的 TCP/IP 模型,共分 4 层,链路层、网络层、传输层、应用层。两个模型之间的对应关系如图所示:

image

无论什么模型,每一个抽象层建立在低一层提供的服务上,并且为高一层提供服务。

TCP 概述

TCP(Transmission Control Protocol)是面向连接的通信协议,通过三次握手建立连接,然后才能开始数据的读写,通讯完成时要拆除连接,由于 TCP 是面向连接的所以只能用于端到端的通讯。

TCP 三次握手

image

TCP 提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。 所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在 socket 编程中,这一过程由客户端执行 connect 来触发。

  • 第一次握手:客户端将标志位 SYN 置为 1,随机产生一个值 seq=J,并将该数据包发送 给服务器端,客户端进入 SYN_SENT 状态,等待服务器端确认。
  • 第二次握手:服务器端收到数据包后由标志位 SYN=1 知道客户端请求建立连接,服务器端将标志位 SYN 和 ACK 都置为 1,ack=J+1,随机产生一个值 seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入 SYN_RCVD 状态。
  • 第三次握手:客户端收到确认后,检查 ack 是否为 J+1,ACK 是否为 1,如果正确则将 标志位 ACK 置为 1,ack=K+1,并将该数据包发送给服务器端,服务器端检查 ack 是否为 K+1, ACK 是否为 1,如果正确则连接建立成功,客户端和服务器端进入 ESTABLISHED 状态,完成 三次握手,随后客户端与服务器端之间可以开始传输数据了。

为什么 TCP 握手需要三次?

TCP 是可靠的传输控制协议,而三次握手是保证数据可靠传输又能提高传输效率的最小次数。为什么?

RFC793,也就是 TCP 的协议 RFC 中就谈到了原因,这是因为: 为了实现可靠数据传输,TCP 协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的。

举例说明:发送方在发送数据包(假设大小为 10 byte)时,同时送上一个序号(假设 为 500),那么接收方收到这个数据包以后,就可以回复一个确认号(510 = 500 + 10) 告诉发送方 “我已经收到了你的数据包,你可以发送下一个数据包,序号从 511 开始” 。

三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。

如果只是两次握手,至多只有连接发起方的起始序列号能被确认,另一方选择的序列号则得不到确认。

至于为什么不是四次,很明显,三次握手后,通信的双方都已经知道了对方序列号起始值,也确认了对方知道自己序列号起始值,第四次握手已经毫无必要了。

TCP 的三次握手的漏洞-SYN 洪泛攻击

但是在 TCP 三次握手中是有一个缺陷的,就是如果我们利用三次握手的缺陷进行攻击。 这个攻击就是 SYN 洪泛攻击。三次握手中有一个第二次握手,服务端向客户端应答请求, 应答请求是需要客户端 IP 的,攻击者就伪造这个 IP,往服务器端狂发送第一次握手的内容, 当然第一次握手中的客户端 IP 地址是伪造的,从而服务端忙于进行第二次握手但是第二次 握手当然没有结果,所以导致服务器端被拖累,死机。

面对这种攻击,有以下的解决方案,最好的方案是防火墙。

  • 无效连接监视释放
  • 延缓 TCB(连接控制资源)分配方法
  • 使用防火墙。防火墙在确认了连接的有效性后,才向内部的服务器(Listener)发起 SYN 请求,

TCP 四次挥手

四次挥手即终止 TCP 连接,就是指断开一个 TCP 连接时,需要客户端和服务端总共发送 4 个包以确认连接的断开。在 socket 编程中,这一过程由客户端或服务端任一方执行 close 来触发。

由于 TCP 连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当甲方完成数据发送任务后,发送一个 FIN 给乙方来终止这一方向的连接,乙方收到一个 FIN 只是意味着不会再收到甲方数据了,但是乙方依然可以给甲方发送数据,直到这乙方也发送了 FIN 给甲方。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

image

由此可见,TCP 建立一个连接需 3 个分节,终止一个连接则需 4 个分节。

  1. 某个应用进程首先调用 close,我们称该端执行主动关闭(active close)。该端的 TCP 于是发送一个 FIN 分节,表示数据发送完毕,应用进程进入 FIN-WAIT-1(终止等待 1)状态。
  2. 接收到这个 FIN 的对端执行被动关闭(passive close),发出确认报文。这个 FIN 由 TCP 确认。因为 FIN 的接收意味着接收端应用进程在相应连接上再无额外数据可接收。接收端进 入了 CLOSE-WAIT(关闭等待)状态,这时候处于半关闭状态,即主动关闭端已经没有数据 要发送了,但是被动关闭端若发送数据,主动关闭端依然要接受。这个状态还要持续一段时 间,也就是整个 CLOSE-WAIT 状态持续的时间。主动关闭端收到确认报文后进入 FIN-WAIT-2 (终止等待 2)状态。
  3. 一段时间后,被动关闭的应用进程将调用 close 关闭它的套接字。这导致它的 TCP 也 发送一个 FIN。
  4. 接收这个最终 FIN 的原发送端 TCP(即执行主动关闭的那一端)确认这个 FIN 发出一 个确认 ACK 报文,并进入了 TIME-WAIT(时间等待)状态。注意此时 TCP 连接还没有释放, 必须经过 2∗MSL(最长报文段寿命/最长分节生命期 max segement lifetime,MSL 是任何 IP 数据报能够在因特网中存活的最长时间,任何 TCP 实现都必须为 MSL 选择一个值。RFC 1122[Braden 1989]的建议值是 2 分钟,不过源自 Berkelcy 的实现传统上改用 30 秒这个值。 这意味着 TIME_WAIT 状态的持续时间在 1 分钟到 4 分钟之间)的时间后,当主动关闭端撤 销相应的 TCB 后,才进入 CLOSED 状态。
  5. 被动关闭端只要收到了客户端发出的确认,立即进入 CLOSED 状态。同样,撤销 TCB 后,就结束了这次的 TCP 连接。可以看到,被动关闭端结束 TCP 连接的时间要比主动关闭 端早一些。

既然每个方向都需要一个 FIN 和一个 ACK,因此通常需要 4 个分节。我们使用限定词“通 常”是因为:某些情形下步骤 1 的 FIN 随数据一起发送;另外,步骤 2 和步骤 3 发送的分节 都出自执行被动关闭那-一端,有可能被合并成一个分节。

为什么 TCP 的挥手需要四次?

TCP 是全双工的连接,必须两端同时关闭连接,连接才算真正关闭。

如果一方已经准备关闭写,但是它还可以读另一方发送的数据。发送给 FIN 结束报文给对方,对方收到后,回复 ACK 报文。当这方也已经写完了准备关闭,发送 FIN 报文,对方回复 ACK。两端都关闭,TCP 连接正常关闭。

为什么需要 TIME-WAIT 状态?

TIME_WAIT 状态存在的原因有两点

  1. 可靠的终止 TCP 连接。
  2. 保证让迟来的 TCP 报文有足够的时间被识别并丢弃。

根据前面的四次握手的描述,我们知道,客户端收到服务器的连接释放的 FIN 报文后, 必须发出确认。如最后这个 ACK 确认报文丢失,那么服务器没有收到这个 ACK 确认报文, 就要重发 FIN 连接释放报文,客户端要在某个状态等待这个 FIN 连接释放报文段然后回复确认报文段,这样才能可靠的终止 TCP 连接。

在 Linux 系统上,一个 TCP 端口不能被同时打开多次,当一个 TCP 连接处于 TIME_WAIT 状态时,我们无法使用该链接的端口来建立一个新连接。反过来思考,如果不存在 TIME_WAIT 状态,则应用程序能过立即建立一个和刚关闭的连接相似的连接(这里的相似,是指他们具 有相同的 IP 地址和端口号)。这个新的、和原来相似的连接被称为原来连接的化身。新的化身可能受到属于原来连接携带应用程序数据的 TCP 报文段(迟到的报文段),这显然是不该发生的。这是 TIME_WAIT 状态存在的第二个原因。

常用的网络工具 Wireshark

为什么要抓包?

  1. 定位网络问题;
  2. 分析接口数据;
  3. 学习网络协议,使用抓包工具分析网络数据更直观。

大部分场合都可以通过程序调试来定位问题,但有些场景使用抓包来定位接口问题更准 确、更方便,如以下场景:

  1. 你发送数据给后台,但后台没有收到,可以对接口进行抓包分析,看是后台处理有问题,还是没有将数据发出去,或是发送数据格式有误;
  2. 你和后台接口联调测通,但业务数据对不上,你认为是后台问题,后台认为是你发的问题,可以抓包确认问题所在;
  3. 线上出现 bug 需要定位,但你没在公司,没有代码可调试,可直接抓包分析;
  4. 系统性能不佳,抓包看下接口响应时长,是不是后台出现性能问题。

常用的抓包工具有:F12(浏览器自带的抓包工具)、Fiddler、Charles,Wireshark。而 Wireshark 在支持的协议,用户友好度、价格(开源)、程序支持、支持的操作系统上都很好,所以 Wireshark 也是我们最常用的抓包工具和报文分析工具。

HTTP

HTTP 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网 (WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。

HTTP 协议

我们使用 http 来访问 Web 上某个资源,比如 html/文本、word、avi 电影、其他资源。 官方协议网站:https://tools.ietf.org/html/rfc2608

HTTP 使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。

URL 是一种特殊类型的 URI,包含了用于查找某个资源的足够的信息。 URL,全称是 UniformResourceLocator, 中文叫统一资源定位符,是互联网上用来标识某一处资源的地址。

URI 和 URL 的区别

URI 是个纯粹的句法结构,用于指定标识 Web 资源的字符串的各个不同部分。URL 是 URI 的一个特例,它包含了定位 Web 资源的足够信息。其他 URI,比如 mailto:cay@horstman.com 则不属于定位符,因为根据该标识符无法定位任何资源。

URI 是统一资源标识符,而 URL 是统一资源定位符。因此,笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称 (URN),它命名资源但不指定如何定位资源。上面的 mailto 就是一个 URN 的示例。

URL 是 uniform resource locator,统一资源定位器,它是一种具体的 URI,即 URL 可以用 来标识一个资源,而且还指明了如何 locate 这个资源。

一次完整 http 请求的过程

  1. 首先进行 DNS 域名解析(本地浏览器缓存、操作系统缓存或者 DNS 服务器),首先会搜索浏览器自身的 DNS 缓存(缓存时间比较短,大概只有 1 分钟,且只能容纳 1000 条缓存)
    • 如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的 DNS 缓存
    • 如果还没有找到,那么尝试从 hosts 文件里面去找
    • 在前面三个过程都没获取到的情况下,就去域名服务器去查找,
  2. 三次握手建立 TCP 连接 在 HTTP 工作开始之前,客户端首先要通过网络与服务器建立连接,HTTP 连接是通过 TCP 来完成的。HTTP 是比 TCP 更高层次的应用层协议,根据规则,只有低层协议建立之后,才能进行高层协议的连接,因此,首先要建立 TCP 连接,一般 TCP 连接的端口号是 80;
  3. 客户端发起 HTTP 请求
  4. 服务器响应 HTTP 请求
  5. 客户端解析 html 代码,并请求 html 代码中的资源浏览器拿到 html 文件后,就开始解析其中的 html 代码,遇到 js/css/image 等静态资源时,就向服务器端去请求下载
  6. 客户端渲染展示内容
  7. 关闭 TCP 连接

一般情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接,然后如果客户端或者服务器在其头信息加入了这行代码 Connection:keep-alive ,TCP 连接在发送后将仍然保持打开状态,于是,客户端可以继续通过相同的连接发送请求,也就是说前面的 3 到 6,可以反复进行。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

HTTP 协议报文结构

用于 HTTP 协议交互的信息被称为 HTTP 报文。请求端(客户端)的 HTTP 报文叫做请求报文;响应端(服务器端)的叫做响应报文。HTTP 报文本身是由多行(用 CR+LF 作换行符)数据构成的字符串文本。

HTTP 报文大致可分为报文首部和报文主体两部分。两者由最初出现的空行(CR+LF) 来划分。通常,并不一定有报文主体。

请求报文结构

请求报文的首部内容由以下数据组成:

  • 请求行 —— 包含用于请求的方法、请求 URI 和 HTTP 版本。
  • 首部字段 —— 包含表示请求的各种条件和属性的各类首部。(通用首部、请求首部、 实体首部以及 RFC 里未定义的首部如 Cookie 等)

image

响应报文结构

  • 状态行 —— 包含表明响应结果的状态码、原因短语和 HTTP 版本。
  • 首部字段 —— 包含表示请求的各种条件和属性的各类首部。(通用首部、响应首部、 实体首部以及 RFC 里未定义的首部如 Cookie 等)

image