应用层
HTTP 协议
HTTP 概述
- HTTP 协议是「Hyper Text Transfer Protocol(超文本传输协议)」的缩写,是用于从万维网(World Wide Web,WWW)服务器传输超文本到本地浏览器的传输协议。
- HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层协议。
HTTP 特点
- 灵活:HTTP 允许传输任意类型的数据对象,传输的数据类型由
Content-Type
标识。 - 无连接:HTTP 协议的通信双方在交换 HTTP 报文之前不需要建立 HTTP 连接。
- 无状态:无状态是指协议对于事务处理没有记忆能力。这意味着,如果后续处理需要前面的信息,则必须进行重传,这可能导致每次连接传送的数据量增大。而当服务器不需要前面信息时应答会较快些。
- 支持 B/S 模式和 C/S 模式
- B/S 模式:浏览器(browser)/服务器(server)模式。只需要在一端部署服务器程序,另一端使用浏览器即可完成数据的传输。
- 优点:使用客户端主机上的浏览器进行通信,工作量小;此外使用浏览器显示数据使得客户端不受平台的限制,可移植性较好。
- 缺点:使用第三方浏览器作为客户端使得网络应用支持受限。没有自己的客户端程序使得缓存效率比较差,而且必须使用标准的 HTTP(S) 协议进行通讯。
- C/S 模式:客户端(client)/服务器(server),模式。需要在通讯两端分别部署服务器程序和客户端程序来完成数据通信。
- 优点:一般来说,客户端和服务器程序由同一个开发团队创作,采用的协议相对灵活。而且客户端程序可以将数据缓存到客户端本地,从而提高数据传输效率。因此传统的网络应用程序以及较大型的网络应用程序都首选使用 C/S 模式进行开发,比如大型游戏、3D 画面传输等,数据量较为庞大,使用 C/S 模式可以提前在本地进行大量数据的缓存处理。
- 缺点:开发团队要同时开发和维护客户端和服务器端程序,工作量增加,开发周期长。从用户的角度出发,需要在用户主机上安装客户端程序,有可能对用户主机的安全性构成威胁。
- B/S 模式:浏览器(browser)/服务器(server)模式。只需要在一端部署服务器程序,另一端使用浏览器即可完成数据的传输。
- 默认端口:HTTP 使用 80 端口,HTTPS 使用 443 端口。
- 传输层协议:使用 TCP 协议。
HTTP 工作原理
HTTP 协议定义 Web 客户端如何从 Web 服务器请求 Web 页面,以及服务器如何把 Web 页面传输给客户端。HTTP 请求和响应的步骤如下:
- 客户端连接到 Web 服务器。一个 HTTP 客户端通常是浏览器,与 Web 服务器的 HTTP 端口建立一个 TCP 套接字连接。
- 发送 HTTP 请求。通过 TCP 套接字,客户端向 Web 服务器发送一个 HTTP 请求报文,一个 HTTP 请求报文由请求行、请求头部、空行和请求数据四部分组成。
- 服务器接受请求并返回 HTTP 响应报文。Web 服务器解析请求,定位请求资源,然后将请求的资源副本封装到一个 HTTP 响应报文写到 TCP 套接字中。一个 HTTP 响应报文由状态行、响应头部、空行和响应数据四部分组成。
- 释放 TCP 连接。若
Connection
字段设置为close
,则服务器主动关闭 TCP 连接,客户端被动关闭 TCP 连接;若Connection
字段设置为keep-alive
,则该 TCP 连接还会保持一段时间,在该时间内还可以继续接受请求。 - 客户端解析 HTTP 响应报文。客户端浏览器首先解析状态行,根据状态码判断请求是否成功。然后解析响应头的每一个字段。随后客户端浏览器读取响应数据,根据 HTML 的语法对其格式化渲染后显示在浏览器窗口。
请求报文
HTTP 请求报文由请求行、请求头部、空行(无论有没有请求数据,都必须有空行)和请求数据四个部分组成。
请求行
请求行由三个部分组成:
- 请求方法:说明请求类型,支持以下请求方法
- POST:传输实体主体。POST 将请求参数封装在 HTTP 的请求数据中,可以传递大量的数据。
- GET:获取资源。使用 GET 方法时请求参数和对应的值附加在 URL 后面,并使用
?
来标识 URL 的结尾和请求参数的开始,传递的参数长度受到限制,一般最多不超过 1024 个字符。 - PUT:传输文件。
- DELETE:删除文件。
- HEAD:获取报文头部。
- OPTIONS:询问支持的请求方法
- TRACE:追踪路径
- CONNECT:要求使用隧道协议连接代理
- 请求 URL:说明要访问的资源,URL(Uniform Resource Locator) 一般由四个部分组成:
<协议>://<主机>:<端口>/<路径>
- 主机一般为域名,需要通过 DNS 解析出 IP 地址
- 协议版本:说明所使用的 HTTP 协议版本;
POST 和 PUT 方法的区别:
- PUT 请求是幂等的方法,如果两个请求相同,后一个请求会把第一个请求覆盖掉(所以 PUT 用来改资源);
- POST 请求是非幂等的方法,后一个请求不会把第一个请求覆盖掉(所以 POST 用来增加资源)
- (幂等的方法有 GET、DELETE、PUT,非幂等的方法有 POST、PATCH)
GET 和 POST 方法的区别:
- GET 方法的参数通过 URL 传递,提交的参数会在地址栏中显示;POST 方法将参数放在请求体中,提交时地址栏不会发生变化(也正因如此,POST 方法的安全性要比 GET 方法的安全性高)。
- GET 请求在 URL 中传递的参数是有长度限制的(不是 HTTP 协议规范给出的限制,而是特定浏览器和服务器端给出的限制),而 POST 没有。
- GET 方法产生一个 TCP 数据包,而 POST 方法产生两个 TCP 数据包
- 对于 GET 方法的请求,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应
200 OK
; - 对于 POST 方法的请求,浏览器先发送 Header,服务器响应
100 Continue
后浏览器再继续发送 Data,服务器响应200 OK
;
- 对于 GET 方法的请求,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应
- GET 请求会被缓存,而 POST 不会(除非手动设置),因此 GET 请求支持收藏为书签而 POST 不可以
- 编码方式:GET 请求只能进行 URL 编码,而 POST 支持多种编码方式
- 参数的数据类型:GET 只接受 ASCII 字符,而 POST 没有限制
请求头部
请求头部由键值对组成,关键字和值之间用分号 :
分割,一般使用 unordered_map
来存储请求头。请求头封装了客户端附加的请求信息。常见的请求头信息包括:
User-Agent
:产生请求的浏览器类型,User-Agent
请求报头域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。Accept
:客户端可识别的响应内容类型列表,例如image/gif
、text/html
。Accept-Language
:客户端可接受的自然语言。Accept-chartset
:客户端可接受应答的字符集。Accept-Encoding
:客户端可接受的数据编码格式。Connection
:连接方式,close
或keep-alive
。
请求数据
请求数据也叫主体,可以添加任意的其他数据。请求数据不在 GET 方法中使用,而是在 POST 方法中使用。
响应报文
HTTP 响应报文由状态行、响应头部、空行和响应包体四个部分组成。
状态行
状态行也由三部分组成:
- HTTP 协议版本。
- 状态码:状态码由三位数字组成,第一位定义了响应的类别,可能有五种取值
- 响应类别:
- 1XX:表示服务器端已经收到客户端请求,客户端可以继续发送请求;
- 2XX:表示服务器端已经成功接受请求并处理;
- 3XX:表示服务器要求客户端重定向;
- 4XX:客户端请求有问题;
- 5XX:服务器端未能正常处理客户端的请求,出现错误;
- 常见的状态码及描述文本:
- 200 OK:请求成功
- 400 Bad Request:客户端请求语法有问题,不能被服务器端理解
- 401 Unauthorized:请求未经授权,必须与
Authorization
请求报头域一起使用。 - 403 Forbidden:服务器收到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因。
- 404 Not Found:请求的资源不存在
- 500 Internal Server Error:服务器发生错误,无法完成客户端请求
- 503 Service Unavailable:表示服务器当前不能处理客户端请求,一段时间后可能恢复正常
- 响应类别:
- 状态码描述文本。
响应头部
响应头通常可能包含以下信息: Server
:Server
响应报头域包含了服务器用来处理请求的软件信息及其版本。它和User-Agent
请求报头域是相对应的,分别发送客户端与浏览器以及服务器端软件与操作系统的信息。Vary
:指示不可缓存的请求头列表。Connection
:连接方式(同客户端)。WWW-Authenticate
:WWW-Authenticate
响应报头域必须被包含在401 (未授权的)响应消息中,这个报头域和前面讲到的Authorization
请求报头域是相关的,当客户端收到 401 响应消息,就要决定是否请求服务器对其进行验证。如果要求服务器对其进行验证,就可以发送一个包含了Authorization
报头域的请求。响应包体
服务器返回给客户端的文本信息。
HTTPS 协议
HTTPS 概述
HTTPS = HTTP + SSL
,但是现在 SSL 都已被 TLS 取代。
一个安全的机制应该满足三个特性:机密性、完整性和不可否认性。HTTP 协议使用明文进行通信,且没有采取任何其他保护措施。
- 机密性方面,由于使用明文进行通信,导致信息会被窃听泄漏;
- 完整性方面,明文在传输过程中很容易被劫持篡改;
- 不可否认性方面,HTTP 的请求和响应不会对通信双方的身份进行确认;
但是 HTTPS 能满足上述三个特性。HTTPS 并不是应用层的一种新协议,只是 HTTP 通信接口部分用 SSL (Secure Socket Layer)或 TLS(Transport Layer Security)协议封装一遍而已。
通常,HTTP 直接使用 TCP 通信,而当使用 SSL 后,就变为 HTTP 先和 SSL 通信,然后再由 SSL 和 TCP 通信。简言之,HTTPS 就是身披 SSL 协议的 HTTP 协议,SSL 协议位于 HTTP 协议和 TCP 协议之间。SSL 主要包括以下三个部分: - 机密性部分:对请求和应答消息进行加密。非对称加密相比对称加密效率较低,因此对于密钥协商部分,通信双方先使用非对称加密协商对称加密的密钥,然后用对称加密密钥对消息加密;
- 仅使用对称加密算法可行么?:如果通信双方都各自持有同一个对称加密密钥,且没有其他人知道,那么是可以保证安全的,但问题是如何安全协商出这个密钥而不被其他人知晓;
- 仅使用非对称加密算法可行么?:通信双方可以各自持有自己的非对称加密私钥,然后用对方的公钥来加密数据并传输,这种方法可以保证机密性,但非对称加密算法的加密和解密开销太大;
- 非对称加密 + 对称加密:通过非对称加密算法协商对称加密算法的密钥,这种方法既能保证机密性,又能降低加解密的开销;
- 完整性部分:依靠单向散列函数实现,例如 MD5 或 SHA1 算法;
- 不可否认性部分:依靠数字签名来实现;
数字证书
概述中提到可以仅使用非对称加密算法或者使用非对称加密算法结合对称加密算法的方式来实现机密性。但这存在一个前提,也即通信的一方必须能确认自己所持有的公钥确实是属于通信目标的。否则就存在漏洞,产生中间人攻击:
- 某网站拥有用于非对称加密的公钥
A
和私钥A'
; - 浏览器向网站服务器发起请求,服务器把公钥
A
明文传输给浏览器; - 中间人劫持到公钥
A
,并将数据包中的公钥 A 替换为自己的公钥B
,然后转发给浏览器; - 浏览器随机生成一个用于对称加密的密钥
X
,用收到的公钥B
加密后发送给服务器(注意,浏览器并不知道来自服务器的公钥已被劫持和替换,也即浏览器无法验证收到的公钥是否是服务器的) - 中间人再次拦截数据包,然后用自己的私钥
B'
解密得到浏览器发送的对称加密密钥X
,然后用服务器的公钥A'
加密此公钥发送给服务器; - 服务器接收到数据后,用自己的私钥
A'
解密得到对称加密密钥X
; - 这样虽然通过协商通信双方得到了对称加密密钥,但因为再协商过程中浏览器无法确定收到的公钥确实是属于服务器的,使得攻击者能神不知鬼不觉的得到了对称加密密钥;
那么如何证明浏览器收到的公钥一定是来自请求网站的公钥呢?在现实生活中我们是可以通过身份证来确认一个人的身份的,这里政府机构起到了「公信」的作用,它本身的权威可以对一个人的身份信息作出证明。
那么互联网中能不能搞这么个公信机构来给网站颁发一个「身份证」呢?「数字证书」应运而生。数字证书是由权威的 CA(Certificate Authority)机构给服务端进行颁发,CA 机构通过服务端提供的相关信息生成证书,证书内容包含了证书持有者的相关信息,服务器的公钥,签署者签名信息(数字签名)等,最重要的是公钥在数字证书中。
网站在使用 HTTPS 之前,需要向 CA 机构申请一份数字证书,当浏览器请求与服务通信时,服务器把证书发送给浏览器,浏览器从证书里取公钥即可。尤其重要的是:数字证书使用「数字签名」来防止证书在传输过程中被篡改。
数字签名的制作过程:
- CA 机构拥有自己的私钥和公钥;
- CA 先对证书的明文内容进行哈希运算得到经过报文摘要后的 Hash 值;
- 然后用私钥对此 Hash 值进行加密得到数字签名;
- 数字签名和证书的明文共同组成了数字证书,然后这样一份数字证书便可以颁发给网站;
浏览器验证收到的数字证书:
- 浏览器拿到网站发送的数字证书,得到明文和数字签名;
- 浏览器用 CA 机构的公钥对数字签名解密,得到加密前的哈希值;
- 然后浏览器用证书中描述的哈希算法计算得到明文的哈希值;
- 比较两份哈希值是否相同,相同则证书可信,证书中携带的网站公钥没有问题;
为了得到 CA 机构的可信公钥,操作系统和浏览器本身需要预装一些它们信任的根证书,根证书中包含了 CA 机构的公钥。而且证书之间的认证也不止一层,通过层层认证信任,可以形成一条信任链。HTTPS 加密算法
- HTTP 协议通信过程:浏览器直接将 HTTP 报文信息传输到 TCP,TCP 再通过 TCP 套接字发送到目的主机;
- HTTPS 协议通信过程:
- 先将 HTTP 报文信息传输给 SSL 套接字进行加密;
- SSL 将加密后的报文发送给 TCP 套接字;
- TCP 套接字将加密后的报文发送给目标主机;
- 目标主机通过 TCP 套接字获取到加密后的 HTTP 报文,并将其交给 SSL 解密;
- SSL 将解密后的 HTTP 报文交给上层应用;
完整过程如下:
- 认证服务器。浏览器内置一个受信任的 CA 机构列表,并保存了这些 CA 机构的证书。第一阶段服务器会提供经 CA 机构认证颁发的服务器证书,如果认证该服务器证书的 CA 机构,存在于浏览器的受信任 CA 机构列表中,并且服务器证书中的信息与当前正在访问的网站(域名等)一致,那么浏览器就认为服务端是可信的,并从服务器证书中取得服务器公钥,用于后续流程。否则,浏览器将提示用户,根据用户的选择,决定是否继续。当然,我们可以管理这个受信任 CA 机构列表,添加我们想要信任的 CA 机构,或者移除我们不信任的 CA 机构。
- 协商会话密钥。客户端在认证完服务器,获得服务器的公钥之后,利用该公钥与服务器进行加密通信,协商出两个会话密钥,分别是用于加密客户端往服务端发送数据的客户端会话密钥,用于加密服务端往客户端发送数据的服务端会话密钥。在已有服务器公钥,可以加密通讯的前提下,还要协商两个对称密钥的原因,是因为非对称加密相对复杂度更高,在数据传输过程中,使用对称加密,可以节省计算资源。另外,会话密钥是随机生成,每次协商都会有不一样的结果,所以安全性也比较高。
- 加密通讯。此时客户端服务器双方都有了本次通讯的会话密钥,之后传输的所有 HTTP 数据,都通过会话密钥加密。这样网络上的其它用户,将很难窃取和篡改客户端和服务端之间传输的数据,从而保证了数据的私密性和完整性。
使用 HTTPS 时,并不需要每次都通过 SSL 或 TLS 来进行协商传输密钥:
- 服务器会为每个浏览器(或客户端软件)维护一个 session ID,在 TSL 握手阶段传给浏览器,浏览器生成好密钥传给服务器后,服务器会把该密钥存到相应的 session ID 下
- 之后浏览器每次请求都会携带 session ID,服务器会根据 session ID 找到相应的密钥并进行解密加密操作,这样就不必要每次重新制作、传输密钥了!
DNS 协议
DNS 是应用层协议,不过实际上他是为其他应用层协议工作的,包括但不限于 HTTP、SMTP 和 FTP,用于将用户提供的主机名解析为 IP 地址。
所有 DNS 请求和回答报文使用的 UDP 数据报经过端口 53 发送。因为一次 UDP 交换可以短到两个包:一个查询包、一个响应包。而一次 TCP 交换则至少包含 9 个包:三次握手初始化 TCP 会话、一个查询包、一个响应包以及四次分手的包交换。考虑到效率原因,TCP 连接的开销大得,故采用 UDP 作为 DNS 的运输层协议,这也将导致只有 13 个根域名服务器的结果。
DNS 解析过程
DNS 服务器一般分三种:根 DNS 服务器,顶级 DNS 服务器,二级 DNS 服务器。如果某个用户正在用浏览器 mail.baidu.com
的网址,当你敲下回车键的一瞬间:
- 检查浏览器缓存中是否存在该域名与 IP 地址的映射关系,如果有则解析结束,没有则继续;
- 到系统本地查找映射关系,一般在
hosts
文件中,如果有则解析结束,否则继续; - 到本地域名服务器去查询,有则结束,否则继续;
- 本地域名服务器查询根域名服务器,该过程并不会返回映射关系,只会告诉你去下级服务器 (顶级域名服务器) 查询;
- 本地域名服务器查询顶级域名服务器(即
com
服务器),同样不会返回映射关系,只会引导你去二级域名服务器查询; - 本地域名服务器查询二级域名服务器(即
baidu.com
服务器),引导去三级域名服务器查询; - 本地域名服务器查询三级域名服务器(即
mail.baidu.com
服务器),此时已经是最后一级了,如果有则返回映射关系,则本地域名服务器加入自身的映射表中,方便下次查询或其他用户查找,同时返回给该用户的计算机,没有找到则网页报错; - 如果还有下级服务器,则依此方法进行查询,直至返回映射关系或报错;
- 递归查询:像上述过程中的第 1、2、3 点,仅限于在
本地域名服务器
中查找,如果有则直接返回映射关系,否则就去其他DNS
服务器中查询,这种查询方式我们叫做递归查询。 - 迭代查询:上述 4、5、6、7、8 过程,他们只会给出下级
DNS
服务器的地址,并不会直接返回映射关系,这种查询方式叫做迭代查询
传输层
TCP 和 UDP 的区别
- 连接方面
- TCP 是面向连接的,传输数据前要先建立好连接;
- UDP 是无连接的;
- 可靠性
- 通过 TCP 传输的数据无差错,不丢失,不重复,而且按序到达;
- UDP 是尽最大努力交付,不保证数据到达后的可靠性;
- 服务对象
- TCP 链接是点到点服务,一条连接只能有两个端点;
- UDP 可以一对一、一对多、多对多、多对一交互;
- 传输方式
- TCP 是面向字节流的,且保证顺序和可靠;
- UDP 面向报文;
- 拥塞和流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性;
- UDP 没有拥塞控制,导致网络出现拥塞时不会使得源主机发送数据速率降低;
- 首部开销
- TCP 首部开销 20 字节,如果使用了「选项」字段则会变长的;
- UDP 首部 8 字节并且是固定不变的,开销较小(发送时会加上 12 字节的伪首部);
- 分片不同
- TCP 在传输层分片。TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片;
- UDP 在网络层分片。UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU;
- 报文长度
- TCP 是动态报文长度,即 TCP 报文长度是根据接收方的窗口大小和当前网络拥塞情况决定的,流式传输;
- UDP 面向报文,不合并,不拆分,保留上面(应用层)传下来报文的边界,直接传输报文;
- 应用场景
32位序号seq
:一次 TCP 通信过程中某一个传输方向上的字节流的每个字节的编号。假设主机 A 和主机 B 进行通信,A 发送给 B 的第一个 TCP 报文段中,序号被系统初始化位某个随机值 ISN。那么在该传输方向上,后续的 TCP 报文段中序号值将被系统设置成 ISN 加上该报文段所携带数据的第一个字节在整个字节流中的偏移。32位确认号ack
:用作对另一方发来的 TCP 报文段的响应,其值是收到的 TCP 报文段的序号值+1。4位首部长度
:标识该 TCP 头部有多少个 32bit 字(4 字节),因为 4 位最多表示 15,所以说整个首部最多有 4×15 = 60 个字节,除去固定的 20 个字节,还有 40 个字节,也就是说可变长度部分最多占 40 个字节。6位保留位
:为将来定义新的用途保留,现在一般置 0URG
:标记位表示紧急指针是否有效。ACK
:表示确认号 ack 是否有效,为 0 确认号就无效,发送通信请求时为 0,允许通信后就变为 1。PSH
:提示接收端应用程序应该立即从 TCP 接收缓冲区中读走数据,为接收后续数据腾出空间。RST
:表示要求对方重新建立连接。若为 1,表示 TCP 会话出现严重错误,需要释放连接,需重新建立连接才可正常通信,比如点开一个网页,还在加载时,点击❌就会中断连接,后面的数据报 RST 就为 1,点击刷新就相当于重新建立连接。SYN
:表示请求建立连接,发起会话的话 SYN 就为 1,当会话建立后 SYN 就变为 0 了。FIN
:表示通知对端,本端要关闭连接了,在数据通信结束后,要释放连接,后面的数据报 FIN 位就为 116位窗口大小
:发送方和接收方都需要发送窗口和接收窗口,双方在建立会话时,会确定各自的窗口大小,以此来协商发送数据包的大小16位检验和
:由发送端填充,接收端对 TCP 报文执行 CRC 算法以检验 TCP 报文段在传输过程中是否损坏。16位紧急指针
:是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。UDP 首部格式
源端口号。需要对方回信时候可以用,不需要回复可以设置为 0
目的端口。必须有,交付报文要用到。
长度。表示 UDP 首部和 UDP 数据的长度和,最小为 8 个字节。因为首部就 8 个字节
校验和。发送端计算,接收端验证,为了发现在数据收发间有无改动
UDP 伪首部:UDP 伪首部是在 UDP 数据报文前额外添加的一个虚拟首部,用于计算 UDP 校验和。在 UDP 通信中,发送方计算校验和并将其附加到 UDP 首部,接收方在接收数据时也会重新计算校验和,以验证数据在传输过程中是否发生了错误或损坏。
UDP 伪首部共 12 字节,包含以下字段:
- 源 IP 地址:表示 UDP 数据报文的源 IP 地址(4 字节)。
- 目标 IP 地址:表示 UDP 数据报文的目标 IP 地址(4 字节)。
- 保留字段:通常为 0,保留用于将来的扩展(1 字节)。
- 协议字段:指示上层协议的类型,UDP 对应的协议号为 17(1 字节)。
- UDP 长度:表示 UDP 首部和数据的总长度(以字节为单位),包括 UDP 伪首部在内(2 字节)。
TCP 的三次握手
三次握手过程
- 开始建立连接:服务器主动监听某个端口,处于
LISTEN
状态。 - 第一次握手:客户端会随机初始化一个序号(
client_isn
),将此序号置于 TCP ⾸部的「序列号」字段中,同时把SYN
标志位置为 1 ,表示这是一个SYN
报⽂。接着把第⼀个SYN
报⽂发送给服务端,表示向服务端发起连接,该报⽂不包含应⽤层数据,之后客户端处于SYN-SENT
状态。 - 第二次握手:服务端收到客户端的
SYN
报⽂后,⾸先服务端也随机初始化⾃⼰的序列号(server_isn
),将此序号填⼊TCP ⾸部的「序列号」字段中,其次把 TCP ⾸部的「确认应答号」字段填⼊client_isn + 1
, 接着把SYN
和ACK
标志位置为 1 。最后把该报⽂发给客户端,该报⽂也不包含应⽤层数据,之后服务端处于SYN-RCVD
状态。 - 第三次握手:客户端收到服务端报⽂后,还要向服务端回应最后⼀个应答报⽂,⾸先该应答报⽂ TCP ⾸部
ACK
标志位置为 1 ,其次「确认应答号」字段填⼊server_isn + 1
,最后把报⽂发送给服务端,这次报⽂可以携带客户到服务器的数据,之后客户端处于ESTABLISHED
状态。等到服务器收到客户端的应答报⽂后,也进⼊ESTABLISHED
状态。
三次握手的前两次是不能携带数据的,只有第三次可以携带数据。因为经过两次握手,客户端收到 ACK
确认报文段之后就已经建立了连接,表明服务端的接受和发送能力是正常的,当然可以传输数据。等到服务器收到来自客户端的确认报文后,也表明客户端的接受和发送能力是正常的,就可以进行正常的全双工通信了。
三次握手原因
- 避免历史连接:避免旧的重复连接造成混乱。在网络拥堵的情况下,客户端可能会发送多个
SYN
报文来请求建立连接,在阻塞一段时间后旧的SYN
报文可能还是会先到达服务器端,服务器端收到SYN
报文会回复一个ACK+SYN
报文给客户端,客户端会根据自己的上下文判断这是否是一个历史连接,如果客户端判断这是一个历史连接,就会发送一个RST
报文给服务器端表示终止此连接。 - 同步双方序号:「序号」是 TCP 保持可靠顺序传输的关键,报文接收方借助需要可以完成三件事
- 去除重复连接;
- 根据数据报的序号按序接收报文;
- 检查哪些发出的报文被接收了;
- 避免资源浪费:通过三次握手可以完成所有需求,再多的交互便是浪费资源了。
初始序号(ISN)
初始序号(Initial Sequence Number,ISN)是 TCP 发送方要发送的数据按字节编号的起始点,告诉接收方要发送数据的初始化序列号。
ISN 并不是固定的,而是动态生成的,这是为了避免攻击者猜到后续的确认序号后伪造报文发起攻击。
补充:那么初始化序号 ISN 是如何生成的呢?
- ISN = M + F (localhost, localport, remotehost, remoteport)
- M 是一个计时器,这个计时器每隔 4 毫秒加 1
- F 是一个 Hash 算法,根据源 IP、⽬的 IP、源端⼝、⽬的端⼝⽣成⼀个随机数值
半连接队列
- 半连接队列:服务器第一次收到客户端的
SYN
之后,就会处于SYN_RCVD
状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。- 服务器发送完 SYN-ACK 包,如果未收到客户确认包,服务器进行首次重传,等待一段时间(超时时间)仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
- 全连接队列:已经完成三次握手,建立起连接的就会放在全连接队列中,如果队列满了就有可能会出现丢包现象。
SYN 攻击
SYN
攻击:SYN
攻击就是攻击者在短时间内伪造大量不存在的 IP 地址,并向服务器端不断地发送SYN
包,服务器端则回复ACK + SYN
报文,并等待客户端的ACK
报文,由于源地址不存在,因此服务器端需要不断重发直至超时,这些伪造的 SYN 包将长时间占用半连接队列,导致正常的SYN
请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。SYN
攻击检测:当在服务器上看到大量的半连接状态时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN 攻击。在 Linux/Unix 上可以使用netstat -n -p TCP | grep SYN_RECV
命令来检测 SYN 攻击。SYN
攻击预防:
四次挥手过程
- 第一次挥手:客户端打算关闭连接,此时会发送⼀个 TCP 首部
FIN
标志位被置为 1 的报⽂,也即FIN
报⽂,之后客户端进入FIN_WAIT_1
状态; - 第二次挥手: 服务端收到该报文后,就向客户端发送
ACK
应答报文,接着服务端进入CLOSED_WAIT
状态。客户端收到服务端的ACK
应答报文后,之后进入FIN_WAIT_2
状态; - 第三次挥手:等待服务端处理完数据后,也向客户端发送
FIN
报文,之后服务端进入LAST_ACK
状态; - 第四次挥手:客户端收到服务端的
FIN
报文后,回一个ACK
应答报文,之后进入TIME_WAIT
状态。服务器收到了ACK
应答报文后,就进入了CLOSED
状态,至此服务端已经完成连接的关闭。客户端在经过2MSL
一段时间后,自动进入CLOSED
状态,至此客户端也完成连接的关闭。四次挥手原因
- 当客户端确认发送完数据且知道服务器已经接收完了,想要关闭发送数据窗口(
ACK
确认报文还是可以发的),就会发送FIN
报文给服务器; - 服务器收到客户端发送的
FIN
报文,表示收到了,就会回复ACK
报文; - 但这时候服务器可能还在发送数据,没有想要关闭数据口的意思,所以服务器的
FIN
与ACK
不是同时发送的,而是等到服务器数据发送完了,才会发送FIN
给客户端。 - 客户端收到服务器发来的
FIN
报文,知道服务器端的数据也发送完了,就回复ACK
报文;客户端等待2MSL
时间后,没有收到服务器传来的任何消息,知道服务器已经收到自己的ACK
确认报文了,客户端就关闭链接,服务器也关闭链接(服务器比客户端早关闭)。TIME_WAIT 状态
TIME_WAIT
状态:四次挥手期间,客户端和服务器端都可主动释放连接,**谁主动释放,谁将进入TIME_WAIT
状态;MSL
时间:MSL(Maximum Segment Lifetime)也即最长报文寿命,一般为 2 分钟,2MSL 即 4 分钟- 为什么存在
TIME_WAIT
状态:- 保证最后一个 ACK 报文到达对方:(这里假设是客户端先主动释放连接的)如果客户端最后发送的
ACK
报文因为某种原因丢失了,那么服务器端一定会重新发送FIN
报文,这样因为有TIME_WAIT
的存在,客户端会重新发送ACK
报文给服务器端; - 防止已失效的连接的报文段出现在当前连接:如果没有
TIME_WAIT
,那么无论服务器端有没有收到ACK
报文,客户端都已经关掉连接了,此时服务器端重新发送FIN
报文,客户端将不会发送ACK
报文,而是RST
报文,从而使服务器端报错。也就是说,**TIME_WAIT
有助于可靠地实现 TCP 全双工连接的终止**。
- 保证最后一个 ACK 报文到达对方:(这里假设是客户端先主动释放连接的)如果客户端最后发送的
- 为什么是
2MSL
时间: - 服务器端出现大量
TIME_WAIT
状态的原因:如果服务器使用的短连接,那么每次客户端请求后,服务器都会主动发送FIN
关闭连接,最后进入TIME_WAIT
状态。因此,在高并发、短连接的 TCP 服务器上,服务器端会出现大量的 Socket 处于TIME_WAIT
状态。有以下解决方案:- 客户端:HTTP 请求的头部,
connection
设置为keep-alive
,保持存活一段时间(现在的浏览器,一般都这么进行了) - 服务端:允许
TIME_WAIT
状态的 Socket 被重用;缩减TIME_WAIT
时间,设置为 1MSL
(即,2 mins)
- 客户端:HTTP 请求的头部,
TCP 的重传机制
- 超时重传:超时重传指的是发送数据包后在一定的时间周期内没有收到相应的
ACK
报文,等待一定的时间,超时之后就认为这个数据包丢失,就会重新发送。这个等待时间被称为RTO
。 - 快速重传:如果数据接收方接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认报文指出的数据段丢失了,并立即重传这些丢失的数据段。
SACK
:SACK
(Selective Acknowledgment,选择性确认)是 TCP 中的一种扩展选项,用于改进丢包恢复和拥塞控制机制。SACK
允许接收方向发送方报告丢失的数据段的详细信息,从而使发送方能够更加精确地恢复丢失的数据,提高数据传输的效率和可靠性。- 在传统的 TCP 中,接收方只能确认成功接收的数据段,对于未收到的数据段只能用累积确认(Cumulative Acknowledgment)的方式进行确认。这意味着如果某个数据段丢失,发送方只能重传该数据段之后的所有数据,而无法知道接收方实际上已经成功接收了部分数据。
SACK
选项的引入解决了这个问题,它允许接收方在 TCP 首部中的选项字段中包含一个或多个SACK
块,每个SACK
块表示接收方成功接收的一段连续数据。通过使用SACK
选项,接收方可以向发送方报告已经成功接收的数据段,包括数据段的起始序号和结束序号。
TCP 的滑动窗口
最开始 TCP 是每发送一个数据就要进行一次确认应答,当上一个数据包收到了应答,再发送下一个。这种模式就像是面对面聊天,效率比较低下。
因此引入滑动窗口的概念。而窗口大小就是无需等待确认应答,可以继续发送数据的最大值。窗口的实现实际上是操作系统开辟了一个缓存空间,发送方主机在确认应答返回之前必须在缓冲区中保留发送的数据,如果收到确认应答就可以抹去缓存的数据。
TCP 头里有一个字段叫 Window
,也就是窗口大小。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来(也即进行点对点的流量控制)。
总结:滑动窗口机制是 TCP 的一种流量控制方法,该机制允许发送方在停止并等待确认前连续发送多个分组,而不必每发送一个分组就停下来等待确认,从而增加数据传输的速率提高应用的吞吐量。
发送方窗口
发送方的窗口根据处理的情况可以分为四个部分:
- 已发送并收到 ACK 确认的数据;
- 已发送但还未收到 ACK 确认的数据;
- 未发送但总大小在接收方处理范围内的数据;
- 未发送但总大小超过接收方处理范围的数据;
TCP 滑动窗口方案使用三个指针来跟踪滑动窗口中每个部分的数据。其中有两个指针是绝对指针,一个是相对指针:
SND.WND
:发送方窗口的大小,由接收方指定;SND.UNA
:一个绝对指针,指向已发送但尚未收到确认的第一个字节的序号;SND.NXT
:一个绝对指针,指向未发送但在可发送范围内的第一个字节的序号;- 指向窗口内第四部分第一个字节的是一个相对指针,其偏移位置为
SND.UNA + SND.WND
则有可用窗口大小 = SND.WND - (SND.NXT - SND.UNA)
。
接收方窗口
接收方窗口根据处理情况可以分为三个部分:
- 已成功接受并确认的数据(等待应用进程读取)
- 未收到数据,但可以接受的数据的大小
- 未收到数据,且不能接受的数据的大小
三个部分由一个绝对指针和一个相对指针来进行划分:
RCV.WND
:接收方窗口的大小,在发送ACK
确认报文时会捎带给发送方;RCV.NXT
:指针,指向期望发送方下一个发送的数据字节的序列号;- 指向第三部分的第一个字节的是一个相对指针,其偏移位置为
RCV.NXT + CV.WND
TCP 的流量控制
流量控制是点对点通信量的控制,是一个端到端的问题,主要就是抑制发送端发送数据的速率,以便接收端来得及接收。
TCP 使用滑动窗口来实现流量控制。在确认应答策略中,对每一个发送的数据段,都要给一个 ACK 确认应答,收到 ACK 后再发送下一个数据段,这样做有一个比较大的缺点,就是性能比较差,尤其是数据往返的时间长的时候。TCP 使用滑动窗口实现流量控制的过程为:
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的「窗口大小」字段,通过 ACK 来通知发送端(窗口大小字段越大,说明网络的吞吐率越高);
- 操作系统内核为了维护滑动窗口,需要开辟发送缓冲区,来记录当前还有那些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端,发送端收到这个值后,就会减慢自己的发送速度;
- 如果接收端发现自己的缓冲区满了,就会将窗口的大小设置为 0,此时发送端将不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端;
TCP 的拥塞控制
拥塞控制是防止过多的数据注入到网络中,可以使网络中的路由器或链路不致过载,是一个全局性的过程。
网络拥塞的标志为:只要发送方没有在规定时间内收到 ACK 确认报文,也就是发生了超时重传,就认为网络出现了拥塞。
为了能够达到发送方随时调节发送数据量的问题,定义了一个叫做拥塞窗口的概念。拥塞窗口 (Congestion Window,cwnd) 是发送方维护的一个状态变量,根据网络阻塞情况实时的变化。当加入拥塞窗口的概念后,发送窗口 = min(接收方窗口,拥塞窗口)
。
拥塞窗口的变化规则也非常的简单:
慢启动算法:
- 当一条 TCP 连接开始时,cwnd 的值通常初始置为一个 MSS 的较小值,这就使得初始发送速率大约为 MSS/RTT。
- 由于对于发送方而言,可用带宽可能比 MSS/RTT 大得多,TCP 发送方希望迅速找到可用带宽的数量;因此,在慢启动(slow-start)状态,cwnd 的值以 1 个 MSS 开始并且每当传输的报文段首次被确认就增加一个 MSS。这一过程使得每过一个 RTT,发送速率就翻倍。
- 结束指数增长的条件:
- 若存在一个由超时指示的丢包事件(发生拥塞),TCP 发送方将 cwnd 设置为 1,并将慢启动阈值 ssthresh 设置为当前拥塞窗口的一半,重新开始慢启动过程(由超时指示的丢包表明网络拥塞很严重,所以重新进入慢启动过程);
- 当 cwnd 的值等于 ssthresh 时,结束慢启动并且 TCP 转移到拥塞避免模式(cwnd 到达 ssthresh 表明很可能要到上次拥塞的的状态了,不易快速增加 cwnd,所以进入拥塞避免状态,以避免再次发生网络拥塞);
- 如果检测到连续三个重复的 ACK 报文指示的可能丢包事件,这时 TCP 执行一种快速重传并且进入快速恢复状态(由重复确认指示的丢包事件表明网络拥塞不是很严重,因为后续的数据包已经到达了接收方,所以进入快恢复状态);
拥塞避免算法:
- 一旦进入拥塞避免状态,cwnd 的值大约是上次出现拥塞时的一半,也即距离拥塞状态并不遥远;因此,TCP 无法每过一个 RTT 就将 cwnd 翻倍,而是采用了一种较为保守的方法,每个 RTT 只将 cwnd 的值增加一个 MSS,也即 cwnd 进入线性增长模式。
- 拥塞避免状态下线性增长的结束条件:
- 若存在一个由超时指示的丢包事件(发生拥塞),TCP 发送方将 cwnd 设置为 1,并将慢启动阈值 ssthresh 设置为当前拥塞窗口的一半,重新开始慢启动过程;
- 如果检测到连续三个重复的 ACK 报文,这时 TCP 执行一种快速重传并且进入快速恢复状态;
快速重传和快速恢复算法
- 快速重传算法:要求接收方每收到一个失序的报文段后就立即发出重复确认。这样做可以让发送方及早知道有报文段没有到达接收方。
- 快速恢复算法:当发送端收到连续三个重复的确认时,就执行「乘法减小」算法,把慢开始门限 ssthresh 减半,但拥塞窗口 cwnd 现在不设置为 1,而是设置慢开始门限 ssthresh 减半后的值,然后开始执行拥塞避免算法(「加法增大」),使拥塞窗口缓慢地线性增大(加发增大和乘法减小合称 AIMD,也即 Additive-Increase,Multiplicative-Decrease)。
TCP 的粘包问题
Nagle 算法
Nagle 算法是计算机网络中的一种流量控制算法,用于优化小数据包的传输。它的目标是减少网络中不必要的小数据包,从而提高网络性能和吞吐量。Nagle 算法的基本定义是任一时刻,最多只能有一个未被确认的小段。所谓「小段」,指的是长度小于 MSS
尺寸的数据块,而未被确认则是指没有收到对方的 ACK 数据包。Nagle 算法的规则如下:
- 如果包长度达到
MSS
,则允许发送; - 如果该数据包含有
FIN
,则允许发送; - 设置了
TCP_NODELAY
选项,则允许发送; - 上述条件都未满足,但发生了超时(一般为 200ms),则立即发送;
什么是 TCP 粘包问题
发送端为了将多个发往接收端的包,更加高效的的发给接收端,于是采用了优化算法(Nagle 算法),将多次间隔较小、数据量较小的数据,合并成一个数据量大的数据块,然后进行封包。那么这样一来,接收端就必须使用高效科学的拆包机制来分辨这些数据。
简而言之,TCP 粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。TCP 粘包问题原因
发送方原因:
- TCP 默认使用 Nagle 算法(主要作用:减少网络中报文段的数量),而 Nagle 算法主要做两件事:
- 只有上一个分组得到确认,才会发送下一个分组;
- 收集多个小分组,在一个确认到来时一起发送;
接收方原因:
- TCP 将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果 TCP 接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,就会导致接收方在读取缓冲区时缓冲区存在多个数据包。在 TCP 协议中接收方是一次读取缓冲区中的所有内容,所以不能反映原本的数据信息。
解决 TCP 粘包问题
如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时就不需要处理粘包现象;但如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了。
- 发送方处理粘包问题:对于发送方造成的粘包问题,可以通过关闭 Nagle 算法来解决,使用
TCP_NODELAY
选项来关闭算法。 - 接收方处理粘包问题:TCP 接收方没有办法来直接处理粘包现象,只能将问题交给应用层协议来处理
UDP 则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
网络层
IP 协议
- 版本:IP 协议的版本
- 首部长度:IP 报文首部的长度,其中固定部分为 20 字节。首部长度用 4 位标识,最大表示 15,每个单位表示 4 字节,15 能表示 60 个字节,也就是说首部长度最大为 60 字节,可变部分最大为 40 字节。
- 区分服务:服务类型。
- 总长度:IP 报文的总长度,头部和数据部分的长度之和。
- 标识:唯一标识主机发送的每一份数据报。通常每发送一个报文它的值就加一。当 IP 报文的长度超过最大传输单元 MTU 时需要对 IP 报文进行分片,这个「标识」字段会被复制到所有 IP 分片,使得这些分片在到达最终目的地时可以按照标识字段的内容重新组成原来完成的数据。
- 标志:三位,分别为 R、DF 和 MF。目前只有后两位有效,DF 为 1 表示不分片,为 0 表示分片;MF 为 1 表示还有更多的分片,为 0 表示这是最后一个分片。
- 片偏移:当前分片在原始未分片报文中相对首字节的偏移位,单位为字节。
- 生存时间:Time To Live,IP 报文所允许经过的路由器的最大数量。每经过一个路由器
TTL
减 1,当TTL
为 0 时,路由器停止转发将其丢弃。TTL
由 8 位表示,RFC 推荐初始值为 64。 - 协议:指出 IP 报文所携带的数据使用的是哪种传输层协议,以便目的主机的网络层将数据报交给指定的进程处理(不同的协议有专门不同的进程处理)。和端口号类似,此处使用的是协议号来表示上层协议,TCP 的协议号为 6,UDP 的协议号为 17,ICMP 的协议号为 1, IGMP 的协议号为 2.
- 首部检验和:计算 IP 首部的检验和,用于检查 IP 首部的完整性。
- 源地址:发送 IP 数据报的源端 IP 地址,在传输过程中不会发生变化。
- 目的地址:发送 IP 数据报的目的端 IP 地址,在传输过程中不会发生变化。
其他
报文分片
数据发送时,将数据从「应用层->传输层->网络层->数据链路层」依次封装,其中传输层是 TCP 和 UDP,网络层是 IP 协议。
- MTU 是以太网帧能够发送的有效载荷大小,为 1500 字节,所能接收的传输层数据段最大为 1480 个字节(以太网帧中的数据包括 IP 协议的报头信息,IP 协议的报头信息为 20 字节)
- $以太网最大帧大小 = 以太网帧报头 + MTU + CRC 检验和 = 14 + 1500 + 4 = 1518$
- 在计算 MSS (网络传输数据最大值)的时候,用 MTU 减去网络层报头长度以及传输层报头长度即可。
- UDP
- 一旦 UDP 携带的数据超过了 1472 (MTU - IP 报头 - UDP 报头 = 1500 - 20 - 8),那么在 IP 层就会对该数据分片,一旦分片就意味着增加了 UDP 传输丢包的可能性。由于 UDP 协议传输本身就不负责可靠性,再加上分片,那么丢包的可能性就大大增加
- 一旦 UDP 携带的数据超过了 1472 (MTU - IP 报头 - UDP 报头 = 1500 - 20 - 8),那么在 IP 层就会对该数据分片,一旦分片就意味着增加了 UDP 传输丢包的可能性。由于 UDP 协议传输本身就不负责可靠性,再加上分片,那么丢包的可能性就大大增加
TCP 网络编程本质
TCP 网络编程最本质的是处理三个半事件:
- 连接建立:包括服务器端被动接受连接(
accept
)和客户端主动发起连接(connect
)。TCP 连接一旦建立,客户端和服务端就是平等的,可以各自收发数据。 - 连接断开:包括主动断开 (
close
、shutdown
) 和**被动断开 (read()
返回 0)**。 - 消息到达:文件描述符可读。这是最为重要的一个事件,对它的处理方式决定了网络编程的风格(阻塞还是非阻塞,如何处理分包,应用层的缓冲如何设计等等)。
- 消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;另外,这里的「发送完毕」是指数据写入操作系统缓冲区(内核缓冲区),将由 TCP 协议栈负责数据的发送与重传,不代表对方已经接收到数据。
正确关闭 TCP 连接
默认情况下,close ()/closesocket ()
会立即向网络中发送 FIN
包,不管输出缓冲区中是否还有数据,而 shutdown ()
会等输出缓冲区中的数据传输完毕再发送 FIN
包。也就意味着,调用 close ()/closesocket ()
有丢失输出缓冲区中的数据的风险,而调用 shutdown ()
不会。因此正确关闭 TCP 连接的流程为:
- 发送方不再发送数据后,使用
shutdown(sock, SHUT_WR)
关闭本端套接字的输出流。 shutdown ()
会向对方发送FIN
包。FIN
包通过四次挥手过程断开连接,可以有效的等待数据发送完成再断开连接。- 调用
read()
函数,read()/resv()
将会返回 0,代表对方也不再发送数据(对方可能也调用了shutdown()
函数)。此时连接已断开。(这里read()
返回 0 应考虑客户端存在 Bug 或恶意的不返回 0 的情况,使得本端永远不满足read()
返回结果 0 的情况。因此这里因考虑有超时机制,在shutdown
之后若干秒内如果没有满足read()
结果为0,则强制断开连接并有相应的错误处理) - 调用
close()
函数关闭套接字。
正确的关闭逻辑为:
- 发送方:
send()
→shutdown(WR)
→recv() == 0
(由接收方close
导致) →close()
- 接收方:
recv() == 0
(由发送方shutdown
导致) → more to send? →close()
网络代理
正向代理:proxy 和 client 同属于一个 LAN,对 Server 透明;
反向代理:proxy 和 server 同属于一个 LAN,对 Client 透明;
traceroute 原理
traceroute
用来跟踪一个分组从源点到终点的路径,及到达其中每一个路由器的往返时间。其基本原理为源主机向目标主机发送 IP 数据报,并按顺序将 TTL 设置为从 1 开始递增的数字(假设为 N),导致第 N 个节点(中间节点 or 目标主机)丢弃数据报并返回出错信息。源主机根据接收到的错误信息,确定到达目标主机路径上的所有节点的 IP,以及对应的耗时。过程如下:通过发送 UDP 报文,设置目的端口为一个不可能的值;
将 IP 首部中的 TTL 分别设置从 1 到 N,每次逐个增加;
每次设置 TTL 后,重新发送数据报,路由器接收到数据报后,将 TTL 减 1,若当前的路由器接收到数据报,发现 TTL 为 1 时,会将 TTL 减 1 变为 0,然后丢弃数据报,发送 ICMP 时间超过报文;
如果最后一个数据报刚刚达到主机,数据报的 TTL 是 1,此时主机不把 TTL 减 1。因 IP 数据报中封装的是无法交付的 UDP 数据报,此时目的主机向源主机发送 ICMP 终点不可达差错报文,表示达到目的主机;
问题 1:用的 UDP 还是 TCP 协议
- 答:UDP
*问题 2:当 TTL 为 0 时,接收节点会丢弃接数据报,并向源主机报错。这里的报错信息是什么?
- 答:报错信息是 ICMP(Internet Control Message Protocol)报文,它用于在主机、路由之间传递控制信息。
问题 3:假设到达目标主机一共有 N 跳,且 TTL 刚好设置为 N,那么,目标主机成功收到数据报,此时并没有错误回报,traceroute 如何确定已经到达目标主机?
- 答:traceroute 发送 UDP 报文时,将目标端口设置为较大的值( 33434 - 33464),避免目标主机 B 上该端口有在实际使用。当报文到达目标主机 B,目标主机 B 发现目标端口不存在,则向源主机 A 发送 ICMP 报文(Type=3,Code=3),表示目标端口不可达。所以知道已经到达了目标主机