读懂网络协议

Internet

引言

本文大部分内容摘自《网络是怎样连接的》户根勤[日]/著 | 周自恒/译

附带个人理解,整理成文,便于追溯

通过本文可以理解 Http/TCP/IP 协议,知晓专业名词的含义

本文内容摘要

本文以客户端访问 http://www.baidu.com 为例

  1. 客户端解析 URL

  2. 客户端使用 Http 协议生成请求消息

  3. 客户端向 DNS 服务器查询 Web 服务器的 IP 地址

  4. 客户端委托协议栈发送请求消息

  5. IP协议

  6. UDP协议

1. 客户端解析URL

首先分析 http://www.baidu.com 这一段字符串的含义


URI : Uniform Resource Identifier,统一资源标识符

URL : Uniform Resource Locator,统一资源定位符

URN : Universal Resource Name,统一资源名称


应用程序需要一些东西来判断应该使用其中哪种功能来访问相应的数据,而各种不同的URL 就是用来干这个的,比如访问 Web 服务器时用 “http:”,而访问 FTP服务器时用 “ftp:” ,这两者代表了两种不同的应用层协议

FTP: File Transfer Protocol,文件传送协议。这是一种在上传、下载文件时使用的协议。使用 FTP 协议来传送文件的程序也被叫作 FTP。

例如:

ftp://user:[email protected]:21/dir/file1.htm

http://user:[email protected]:80/dir/file1.htm

file://localhost/c:/path/file1.zip

mailto:[email protected]

尽管 URL 有各种不同的写法,但它们有一个共同点,那就是 URL 开头的文字即“http:”“ftp:”“file:”“mailto:”这部分文字都表示浏览器应当使用的访问方法。比如当访问 Web 服务器时应该使用 HTTP 协议,而访问 FTP 服务器时则应该使用 FTP 协议。因此,我们可以把这部分理解为访问时使用的协议类型。尽管后面部分的写法各不相同,但开头部分的内容决定了后面部分的写法,因此并不会造成混乱。

URI 属于 URL 更高层次的抽象,一种字符串文本标准。就是说,URI 属于父类,而 URL 属于 URI 的子类。URL 是 URI 的一个子集,如下图所示

URI---URL---URN

第一步工作就是对 URL 进行解析,从而生成发送给 Web服务器的请求消息

接下来, 客户端会使用 HTTP 协议生成请求消息

2. 客户端使用Http协议生成请求消息


Http : (Hypertext Transfer Protocol,超文本传送协议),定义了客户端和服务器之间交互的消息内容和步骤

请求方法 : 表示需要让 Web 服务器完成怎样的工作,如 Get , Post

状态码 : 它用来表示操作的执行结果是成功还是发生了错误

响应短语 : 是一段文字,用来向人们告知执行的结果。

请求头/响应头 : 有些情况下还需要一些额外的详细信息,而头的功能就是用来存放这些信息


生成请求消息

客户端会向服务器发送 HTTP 请求消息。请求消息中包含的内容是“对什么”(URI)和“进行怎样的操作”(方法)两个部分。

HTTP 消息在格式上是有严格规定的,因此客户端会按照规定的格式来生成请求消息

  <方法><空格><空格>
  <请求头-key>:<请求头-value>
  ...
  <空行>
  <请求体>

服务器收到请求消息之后会对其中的内容进行解析,通过 URI 和方法来判断“对什么”“进行怎样的操作”,并根据这些要求来完成自己的工作,然后将结果存放在响应消息中。

  <空格><状态码><空格><响应短语>
  <响应头-key>:<响应头-value>
  ...
  <空行>
  <响应体>

第二步工作是使用 Http 协议生成请求消息,准备委托系统发送消息到 Web 服务器,但是在这之前...

接下来,需要向 DNS 服务器查询 Web 服务器的 IP 地址

3. 客户端向 DNS 服务器查询 Web 服务器的 IP 地址


DNS : Domain Name System,域名服务系统。

IP : 就是网络中的地址,通过 IP 地址我们可以判断出访问对象服务器的位置,从而将消息发送到服务器

Socket库 : Socket 库是在加州大学伯克利分校开发的 UNIX 系操作系统 BSD 中开发的 C语言库,互联网中所使用的大多数功能都是基于 Socket 库来开发的。因此,BSD 之外的其他操作系统以及 C 语言之外的其他编程语言也参照 Socket 库开发了相应的网络库。可以说,Socket 库是网络开发中的一种标准库。

域名解析器 : 对于 DNS 服务器,我们的计算机上一定有相应的 DNS 客户端,而相当于 DNS 客户端的部分称为 DNS 解析器,或者简称解析器。通过 DNS 查询 IP 地址的操作称为域名解析,因此负责执行解析(resolution)这一操作的就叫解析器(resolver)了


尽管浏览器能够解析网址并生成 HTTP 消息,但它本身并不具备将消息发送到网络中的功能,因此这一功能需要委托操作系统来实现。在进行这一操作时,我们还有一个工作需要完成,那就是查询网址中服务器域名对应的 IP 地址

Socket 库 gethostbyname 组件提供查询 IP 地址的功能

调用解析器后,解析器会向 DNS 服务器发送查询消息,然后 DNS 服务器会返回响应消息。响应消息中包含查询到的 IP 地址,解析器会取出 IP地址,并将其写入浏览器指定的内存地址中。

第三步工作是使用 Socket 库中的解析器功能 , 解析出 Web 服务器的 IP 地址

接下来需要委托操作系统内部的协议栈向这个目标 IP地址,也就是我们要访问的 Web 服务器发送消息了

4. 委托协议栈发送请求消息


TCP/IP : 即传输控制协议(TCP)和因特网协议(IP),它是因特网采用的协议标准,也是目前全世界 采用的最广泛的工业标准。通常所说的 TCP/IP 是指因特网协议簇,它包括了很多种协议,如电子邮件、 远程登录、文件传输等,而 TCP 和 IP 是保证数据完整传输的两个最基本的重要协议。因此,通常用 TCP/IP 来代表整个因特网协议系列。TCP/IP 协议既可以应用在局域网内部,也可以工作在广域网,是目前应用 最为广泛的协议。

image

协议栈 : 操作系统内部的网络控制软件,也叫“协议驱动”“TCP/IP 驱动”等。

TCP : (Transmission Control Protocol,传输控制协议) 定义一种收发数据的方式 , 头部 20 个字节

UDP : (User Datagram Protocol,用户数据报协议) 定义一种收发数据的方式 , 头部 8 个字节

描述符 : 用来在一台计算机内部识别套接字的机制

端口号 : 用来让通信的另一方能够识别出套接字的机制, 默认Web 是 80 号端口,电子邮件是 25 号端口

网络包 : 网络中的数据会被切分成几十字节到几千字节的小块,每一个小数据块被称为一个包。以下是一个包包含的内容

  报头/起始帧分界符 - MAC头部 - IP头部 - TCP头部 - 数据 - FCS帧校验序列

ICMP : 用于告知网络包传送过程中产生的错误以及各种控制消息

ARP : 用于根据 IP 地址查询相应的以太网 MAC 地址

控制信息 : 协议栈内部有一块用于存放控制信息的内存空间,,这里记录了用于控制通信操作的控制信息,例如通信对象的 IP 地址、端口号、通信操作的进行状态等。本来套接字就只是一个概念而已,并不存在实体,如果一定要赋予它一个实体,我们可以说这些控制信息就是套接字的实体,或者说存放控制信息的内存空间就是套接字的实体。类似于我们在笔记本上记录的日程表和备忘录。我们可以根据笔记本上的日程表和备忘录来决定下一步应该做些什么,同样地,协议栈也是根据这些控制信息来决定下一步操作内容的。

套接字 : 套接字中记录了用于控制通信操作的各种控制信息,协议栈则需要根据这些信息判断下一步的行动,这就是套接字的作用。

接收缓冲区 : 当执行数据收发操作时,我们还需要一块用来临时存放要收发的数据的内存空间,这块内存空间称为缓冲区, 接收缓冲区位于应用程序.

MTU : Maximum Transmission Unit,最大传输单元 (包含 IP 头 , TCP头 , 数据块)

MSS : Maximum Segment Size,最大分段大小,TCP 和 IP 的头部加起来一般是 40 字节,因此 MTU 减去这个长度就是 MSS


Socket 库提供收发数据的功能 , 我们需要先创建套接字,然后再将套接字连接起来形成管道, 就可以进行数据收发操作了.

应用程序并不能直接操作协议栈 , 都是通过委托 Socket 库的方式进行操作.

TCP 协议通信的大概步骤

首先,服务器一方先创建套接字,然后等待客户端向该套接字连接管道 。当服务器进入等待,客户端就可以连接管道了。具体来说,客户端也会先创建一个套接字,然后从该套接字延伸出管道,最后管道连接到服务器端的套接字上。当双方的套接字连接起来之后,通信准备就完成了。

我们再来看一看收发数据操作结束时的情形。当数据全部发送完毕之后,连接的管道将会被断开。管道在连接时是由客户端发起的,但在断开时可以由客户端或服务器任意一方发起 A。其中一方断开后,另一方也会随之断开,当管道断开后,套接字也会被删除。到此为止,通信操作就结束了。

综上所述,收发数据的操作分为若干个阶段,可以大致总结为以下 4 个。

(1)创建套接字(创建套接字阶段)
(2)将管道连接到服务器端的套接字上(连接阶段)
(3)收发数据(通信阶段)
(4)断开管道并删除套接字(断开阶段)

(1)创建套接字(创建套接字阶段)

调用 Socket 库的 socket 组件完成套接字创建, 协议栈 会返回一个代表该套接字的描述符,应用程序会将收到的 描述符 存放在内存中。

因为同一个计算机里可能存在多个套接字,在这样的情况下,我们就需要一种方法来识别出某个特定的套接字,这种方法就是 描述符 。这时,只要我们出示 描述符协议栈就 能够判断出我们希望用哪一个套接字来连接或者收发数据了

  1. 协议栈 首先会分配用于存放一个套接字所需的内存空间,这相当于为控制信息准备一个容器
  1. 协议栈 将表示这个套接字的 描述符 告知应用程序
(2)将管道连接到服务器端的套接字上(连接阶段)

调用 Socket 库中的名为 connect 的程序组件来完成这一操作

需要指定描述符、服务器 IP 地址和端口号这 3 个参数

连接实际上是通信双方交换控制信息,在套接字中记录这些必要信息并准备数据收发的一连串操作,首先,客户端在连接时需要计算出与从客户端到服务器方向通信相关的序号初始值,并将这个值发送给服务器。接下来,服务器会通过这个初始值计算出 ACK 号并返回给客户端。初始值有可能在通信过程中丢失,因此当服务器收到初始值后需要返回 ACK 号作为确认。同时,服务器也需要计算出与从服务器到客户端方向通信相关的序号初始值,并将这个值发送给客户端。接下来像刚才一样,客户端也需要根据服务器发来的初始值计算出 ACK 号并返回给服务器。到这里,序号和 ACK 号都已经准备完成了,接下来就可以进入数据收发阶段了

  1. 客户端先创建一个包含表示开始数据收发操作的控制信息的头部,头部中的控制位的 SYN 比特设置为 1,可以认为它表示连接,此外还需要设置适当的序号和窗口大小
客户端 TCP 模块会将信息传递给 IP 模块并托它进行发送
  1. 服务器上的 IP 模块会将接收到的数据传递给 TCP 模块,服务器的 TCP 模块根据 TCP 头部中的信息找到端口号对应的套接字,当找到对应的套接字之后,套接字中会写入相应的信息,并将状态改为正在连接,接着服务器的 TCP 模块会返回响应,这个过程和客户端一样,需要在 TCP 头部中设置发送方和接收方端口号以及 SYN 比特,将 ACK 控制位设为1,表示已经接收到相应的网络包
服务器 TCP 模块会将 TCP 头部传递给 IP 模块,并托 IP 模块向客户端返回响应
  1. 客户端上的 IP 模块会将接收到的数据传递给 TCP 模块,并通过 TCP 头部的信息确认连接服务器的操作是否成功。如果 SYN 为 1 则表示连接成功,这时会向套接字中写入服务器的 IP 地址、端口号等信息,同时还会将状态改为连接完毕。客户端也需要将 ACK 比特设置为 1 并发回服务器,告诉服务器刚才的响应 包已经收到。当这个服务器收到这个返回包之后,连接操作才算全部完成。

​ 现在,套接字就已经进入随时可以收发数据的状态了,大家可以认为这时有一根管子把两个套接字连接了起来。当然,实际上并不存在这么一根管子,不过这样想比较容易理解,网络业界也习惯这样来描述。这根管子,我们称之为连接 A。只要数据传输过程在持续,也就是在调用 close 断开之前,连接是一直存在的。

(3)收发数据(通信阶段)

调用 Socket 库中的名为 write/read 的程序组件来完成这一操作

应用程序需要在内存中准备好要发送的数据。根据用户输入的网址生成的 HTTP 请求消息就是我们要发送的数据。接下来,当调用 write时,需要指定描述符和发送数据,然后协议栈就会将数据发送到服务器

read 会负责将接收到的响应消息存放到接收缓冲区中。由于接收缓冲区是一块位于应用程序内部的内存空间,因此当消息被存放到接收缓冲区中时,就相当于已经转交给了应用程序。

细节
  • 协议栈并不是一收到数据就马上发送出去,而是在数据积累到一定量时再发送出去,至于要积累多少数据才能发送,不同种类和版本的操作系统会有所不同,根据下面几个要素来判断的.用程序在发送数据时可以指定一些选项,比如如果指定“不等待填满缓冲区直接发送”,则协议栈就会按照要求直接发送数据

    • MTU在以太网中一般是 1500 字节,MTU 是包含头部的总长度,因此需要从MTU 减去头部的长度,然后得到的长度就是一个网络包中所能容纳的最大数据长度,这一长度叫作 MSS。当从应用程序收到的数据长度超过或者接近 MSS 时再发送出去,就可以避免发送大量小包的问题了。

    • 另一个判断要素是时间。当应用程序发送数据的频率不高的时候,如果每次都等到长度接近 MSS 时再发送,可能会因为等待时间太长而造成发送延迟,这种情况下,即便缓冲区中的数据长度没有达到 MSS,也应该果断发送出去。为此,协议栈的内部有一个计时器,当经过一定时间之后,就会把网络包发送出去

  • 对较大的数据进行拆分

    当消息长度超过一个网络包所能容纳的数据量,发送缓冲区中的数据会被以 MSS 长度为单位进行拆分,拆分出来的每块数据会被放进单独的网络包中。

  • 使用 ACK 号确认网络包已收到 ( 序号 , ACK号 , 控制位ACK的用法 )

    • TCP 具备确认对方是否成功收到网络包,以及当对方没收到时进行重发的功能,因此在发送网络包之后,接下来还需要进行确认操作。

    • 前面连接过程中,有一个将 SYN 控制位设为1 并发送给服务器的操作,就是在这一步将序号的初始值告知对方的。实际上,在将 SYN 设为 1 的同时,还需要同时设置序号字段的值,而这里的值就代表序号的初始值,初始值是随机的

    • 首先,客户端在连接时需要计算出与从客户端到服务器方向通信相关的序号初始值,并将这个值发送给服务器。接下来,服务器会通过这个初始值计算出 ACK 号并返回给客户端。初始值有可能在通信过程中丢失,因此当服务器收到初始值后需要返回 ACK 号作为确认。同时,服务器也需要计算出与从服务器到客户端方向通信相关的序号初始值,并将这个值发送给客户端。接下来像刚才一样,客户端也需要根据服务器发来的初始值计算出 ACK 号并返回给服务器。到这里,序号和 ACK 号都已经准备完成了,接下来就可以进入数据收发阶段了。数据收发操作本身是可以双向同时进行的,但 Web 中是先由客户端向服务器发送请求,序号也会跟随数据一起发送。然后,服务器收到数据后再返回 ACK 号。从服务器向客户端发送数据的过程则正好相反。

    • TCP 采用这样的方式确认对方是否收到了数据,在得到对方确认之前,发送过的包都会保存在发送缓冲区中。如果对方没有返回某些包对应的 ACK 号,那么就重新发送这些包。

  • 发送方根据网络包平均往返时间调整 ACK 号等待时间

    • 返回 ACK 号的等待时间(这个等待时间叫超时时间)

    • TCP 采用了动态调整等待时间的方法,这个等待时间是根据 ACK 号返回所需的时间来判断的。具体来说,TCP 会在发送数据的过程中持续测量 ACK 号的返回时间,如果 ACK 号返回变慢,则相应延长等待时间;相对地,如果 ACK 号马上就能返回,则相应缩短等待时间

  • 使用窗口有效管理 ACK 号( 窗口大小的用法 )

    • 每发送一个包就等待一个 ACK 号的方式是最简 单也最容易理解的,但在等待 ACK 号的这段时间中,如果什么都不做那实在太浪费了

    • 窗口大小 : 一般和接收方的缓冲区大小一致

    • 为了减少这样的浪费,TCP 采用滑动窗口方式来管理数据发送和 ACK 号的操作。所谓滑动窗口,就是在发送一个包之后,不等待 ACK 号返回,而是直接发送后续的一系列包。这样一来,等待 ACK 号的这段时间就被有效利用起来了。

  • ACK 与窗口的合并

    • 接收方在发送 ACK 号和窗口更新时,并不会马上把包发送出去,而是会等待一段时间,在这个过程中很有可能会出现其他的通知操作,这样就可以把两种通知合并在一个包里面发送了
(4)断开管道并删除套接字(断开阶段)

调用 Socket 库的 close 程序组件进入断开阶段

断开的过程如下。Web 使用的 HTTP 协议规定,当 Web 服务器发送完响应消息之后,应该主动执行断开操作 ,因此 Web 服务器会首先调用close 来断开连接。断开操作传达到客户端之后,客户端的套接字也会进入断开阶段。接下来,当浏览器调用 read 执行接收数据操作时,read 会告知浏览器收发数据操作已结束,连接已经断开。浏览器得知后,也会调用close 进入断开阶段。

如果是多次请求,重复连接和断开显然是效率很低的,因此后来人们又设计出了能够在一次连接中收发多个请求和响应的方法。在 HTTP 版本 1.1 中就可以使用这种方法,在这种情况下,当所有数据都请求完成后,浏览器会主动触发断开连接的操作。

服务器会先发起断开过程 。在这个过程中,服务器先发送一个 FIN 为 1 的 TCP 包,然后客户端返回一个表示确认收到的 ACK 号。接下来,双方还会交换一组方向相反的 FIN 为 1 的 TCP 包和包含 ACK 号的 TCP 包。最后,在等待一段时间后,套接字会被删除

(1)客户端发送 FIN(2)服务器返回 ACK 号(3)服务器发送 FIN(4)客户端返回 ACK 号

5. IP协议

包 : 包是由头部和数据两部分构成的 , 头部包含目的地址等控制信息,大家可以把它理解为快递包裹的面单;头部后面就是委托方要发送给对方的数据,也就相当于快递包裹里的货物

IP 模块负责添加如下两个头部。(1) MAC 头部:以太网用的头部,包含 MAC 地址(2) IP 头部:IP 用的头部,包含 IP 地址

IP 头部中包含 IP 协议规定的、根据 IP 地址将包发往目的地所需的控制信息

MAC 头部包含通过以太网的局域网将包传输至最近的路由器所需的控制信息

6. UDP

UDP头部只有 8 字节

当要发送的信息很小,一个包就可以装下的时候,我们不需要使用到 TCP 这么复杂的机制,没有收到消息最简单的方法是将消息重发一遍,由于数据小,重发一遍不是问题.并且也不需要使用连接和断开连接,把对方接收到数据后的响应当成回复就不需要专门的接收确认包了.

UDP 没有 TCP 的接收确认、窗口等机制 , 不需要建立和断开连接的步骤。只要在从应 用程序获取的数据前面加上 UDP 头部,然后交给 IP 进行发送就可以了。接收也很简单,只要根据 IP 头部中的接收方和发送方 IP 地址, 以及 UDP 头部中的接收方和发送方端口号,找到相应的套接字并将数据交 给相应的应用程序就可以了。除此之外,UDP 协议没有其他功能了,遇到 错误或者丢包也一概不管。因为 UDP 只负责单纯地发送包而已,并不像 TCP 一样会对包的送达状态进行监控,所以协议栈也不知道有没有发生错 误。但这样并不会引发什么问题,因此出错时就收不到来自对方的回复, 应用程序会注意到这个问题,并重新发送一遍数据。这样的操作本身并不 复杂,也并不会增加应用程序的负担。

使用场景有DNS查询比较小数据量的,还有发送音频和视频数据的时候。音频和视频数据必须在规定的时间内送达,一旦送达晚了,就会错过播放时机,导致声音和图像卡顿。所以重发机制没有什么用

UDP协议要求包小于64K。TCP没有限定

你可能感兴趣的:(读懂网络协议)