Ari的小跟班

  :: :: :: :: :: 管理 ::

参考JavaGuideOSI 和 TCP/IP 网络分层模型详解(基础) | JavaGuide,感谢Guide哥!

OSI 和 TCP/IP 网络分层模型详解(基础)

OSI 七层模型

OSI 七层模型 是国际标准化组织提出一个网络分层模型,其大体结构以及每一层提供的功能如下图所示:

​ 每一层都专注做一件事情,并且每一层都需要使用下一层提供的功能比如传输层需要使用网络层提供的路由和寻址功能,这样传输层才知道把数据传输到哪里去。

OSI 的七层体系结构概念清楚,理论也很完整,但是它比较复杂而且不实用,而且有些功能在多个层中重复出现。上面这种图可能比较抽象,再来一个比较生动的图片。下面这个图片是我在国外的一个网站上看到的,非常赞!

既然 OSI 七层模型这么厉害,为什么干不过 TCP/IP 四 层模型呢?

​ 的确,OSI 七层模型当时一直被一些大公司甚至一些国家政府支持。这样的背景下,为什么会失败呢?我觉得主要有下面几方面原因:

  1. OSI 的专家缺乏实际经验,他们在完成 OSI 标准时缺乏商业驱动力
  2. OSI 的协议实现起来过分复杂,而且运行效率很低
  3. OSI 制定标准的周期太长,因而使得按 OSI 标准生产的设备无法及时进入市场(20 世纪 90 年代初期,虽然整套的 OSI 国际标准都已经制定出来,但基于 TCP/IP 的互联网已经抢先在全球相当大的范围成功运行了)
  4. OSI 的层次划分不太合理,有些功能在多个层次中重复出现(比如流量控制,差错控制等)。

​ OSI 七层模型虽然失败了,但是却提供了很多不错的理论基础。为了更好地去了解网络分层,OSI 七层模型还是非常有必要学习的。最后再分享一个关于 OSI 七层模型非常不错的总结图片!

TCP/IP 四层模型

TCP/IP 四层模型 是目前被广泛采用的一种模型,我们可以将 TCP / IP 模型看作是 OSI 七层模型的精简版本,由以下 4 层组成:

  1. 应用层
  2. 传输层
  3. 网络层
  4. 网络接口层

​ 需要注意的是,我们并不能将 TCP/IP 四层模型 和 OSI 七层模型完全精确地匹配起来,不过可以简单将两者对应起来,如下图所示:

应用层(Application layer)

应用层位于传输层之上,主要提供两个终端设备上的应用程序之间信息交换的服务,它定义了信息交换的格式,消息会交给下一层传输层来传输。 我们把应用层交互的数据单元称为报文。

​ 应用层协议定义了网络通信规则,对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如支持 Web 应用的 HTTP 协议,支持电子邮件的 SMTP 协议,POP3,DHCP,DNS,SSH,FTP,IMAP等等。

常见协议

HTTP:超文本传输协议

超文本传输协议(HTTP,HyperText Transfer Protocol) 主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的,整个过程如下图所示。

​ HTTP 协是基于 TCP协议,发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。目前使用的 HTTP 协议大部分都是 1.1。在 1.1 的协议里面,默认是开启了 Keep-Alive 的,这样的话建立的连接就可以在多次请求中被复用了。

​ 另外, HTTP 协议是”无状态”的协议,它无法记录客户端用户的状态,一般我们都是通过 Session 来记录客户端用户的状态。

SMTP:简单邮件传输(发送)协议

简单邮件传输(发送)协议(SMTP,Simple Mail Transfer Protocol) 基于 TCP 协议,用来发送电子邮件。

注意⚠️:接受邮件的协议不是 SMTP 而是 POP3 协议。

SMTP 协议这块涉及的内容比较多,下面这两个问题比较重要:

电子邮件的发送过程?

比如我的邮箱是“dabai@cszhinan.com”,我要向“xiaoma@qq.com”发送邮件,整个过程可以简单分为下面几步:

  1. 通过 SMTP 协议,我将我写好的邮件交给163邮箱服务器(邮局)。
  2. 163邮箱服务器发现我发送的邮箱是qq邮箱,然后它使用 SMTP协议将我的邮件转发到 qq邮箱服务器。
  3. qq邮箱服务器接收邮件之后就通知邮箱为“xiaoma@qq.com”的用户来收邮件,然后用户就通过 POP3/IMAP 协议将邮件取出。

如何判断邮箱是真正存在的?

很多场景(比如邮件营销)下面我们需要判断我们要发送的邮箱地址是否真的存在,这个时候我们可以利用 SMTP 协议来检测:

  1. 查找邮箱域名对应的 SMTP 服务器地址
  2. 尝试与服务器建立连接
  3. 连接成功后尝试向需要验证的邮箱发送邮件
  4. 根据返回结果判定邮箱地址的真实性

推荐几个在线邮箱是否有效检测工具:

  1. https://verify-email.org/
  2. http://tool.chacuo.net/mailverify
  3. https://www.emailcamel.com/
POP3/IMAP:邮件接收的协议

​ 这两个协议没必要多做阐述,只需要了解 POP3 和 IMAP 两者都是负责邮件接收的协议即可。另外,需要注意不要将这两者和 SMTP 协议搞混淆了。SMTP 协议只负责邮件的发送,真正负责接收的协议是POP3/IMAP。IMAP 协议相比于POP3更新一点,为用户提供的可选功能也更多一点,几乎所有现代电子邮件客户端和服务器都支持IMAP。大部分网络邮件服务提供商都支持POP3和IMAP。

FTP:文件传输协议

FTP 协议 主要提供文件传输服务,基于 TCP 实现可靠的传输。使用 FTP 传输文件的好处是可以屏蔽操作系统和文件存储方式

​ FTP 是基于客户—服务器(C/S)模型而设计的,在客户端与 FTP 服务器之间建立两个连接。如果我们要基于 FTP 协议开发一个文件传输的软件的话,首先需要搞清楚 FTP 的原理。关于 FTP 的原理,很多书籍上已经描述的非常详细了:

​ FTP 的独特的优势同时也是与其它客户服务器程序最大的不同点就在于它在两台通信的主机之间使用了两条 TCP 连接(其它客户服务器应用程序一般只有一条 TCP 连接):

  1. 控制连接:用于传送控制信息(命令和响应)
  2. 数据连接:用于数据传送;

这种将命令和数据分开传送的思想大大提高了 FTP 的效率。

Telnet:远程登陆协议

Telnet 协议 通过一个终端登陆到其他服务器,建立在可靠的传输协议 TCP 之上。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用Telnet并被一种称为SSH的非常安全的协议所取代的主要原因。

SSH:安全的网络传输协议

SSH( Secure Shell) 是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 建立在可靠的传输协议 TCP 之上。

Telnet 和 SSH 之间的主要区别在于 SSH 协议会对传输的数据进行加密保证数据安全性。

传输层(Transport layer)

传输层的主要任务就是负责向两台终端设备进程之间的通信提供通用的数据传输服务。 应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。

运输层主要使用以下两种协议:

  1. 传输控制协议 TCP(Transmisson Control Protocol)--提供 面向连接 的,可靠的 数据传输服务。
  2. 用户数据协议 UDP(User Datagram Protocol)--提供 无连接 的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。

网络层(Network layer)

网络层负责为分组交换网上的不同主机提供通信服务。 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报,简称数据报。

⚠️注意 :不要把运输层的“用户数据报 UDP”和网络层的“IP 数据报”弄混

网络层的还有一个任务就是选择合适的路由,使源主机运输层所传下来的分组,能通过网络层中的路由器找到目的主机。这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称。

​ 互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做 网际层IP 层

网络层常见协议

  • IP:网际协议 :网际协议 IP 是TCP/IP协议中最重要的协议之一,也是网络层最重要的协议之一,IP协议的作用包括寻址规约定义数据包的格式等等,是网络层信息传输的主力协议。目前IP协议主要分为两种,一种是过去的IPv4,另一种是较新的IPv6,目前这两种协议都在使用,但后者已经被提议来取代前者。
  • ARP 协议 :ARP协议,全称地址解析协议(Address Resolution Protocol),它解决的是网络层地址链路层地址之间的转换问题。因为一个IP数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但IP地址属于逻辑地址,而MAC地址才是物理地址,ARP协议解决了IP地址转MAC地址的一些问题。
  • NAT:网络地址转换协议 :NAT协议(Network Address Translation)的应用场景如同它的名称——网络地址转换,应用于内部网到外部网的地址转换过程中。具体地说,在一个小的子网(局域网,LAN)内,各主机使用的是同一个LAN下的IP地址,但在该LAN以外,在广域网(WAN)中,需要一个统一的IP地址来标识该LAN在整个Internet上的位置。

网络接口层(Network interface layer)(又称为链路层)

​ 我们可以把网络接口层看作是数据链路层和物理层的合体。

  1. 数据链路层(data link layer)通常简称为链路层( 两台主机之间的数据传输,总是在一段一段的链路上传送的)。数据链路层的作用是将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
  2. 物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异

总结

简单总结一下每一层包含的协议和核心技术:

应用层协议 :

  • HTTP 协议(超文本传输协议,网页浏览常用的协议)
  • DHCP 协议(动态主机配置)
  • DNS 系统原理(域名系统)
  • FTP 协议(文件传输协议)
  • Telnet协议(远程登陆协议)
  • 电子邮件协议等(SMTP、POP3、IMAP)
  • ......

传输层协议 :

  • TCP 协议
    • 报文段结构
    • 可靠数据传输
    • 流量控制
    • 拥塞控制
  • UDP 协议
    • 报文段结构
    • RDT(可靠数据传输协议)

网络层协议 :

  • IP 协议(TCP/IP 协议的基础,分为 IPv4 和 IPv6)
  • ARP 协议(地址解析协议,用于解析 IP 地址和 MAC 地址之间的映射)
  • ICMP 协议(控制报文协议,用于发送控制消息)
  • NAT 协议(网络地址转换协议)
  • RIP 协议、OSPF 协议、BGP 协议(路由选择协议)
  • ......

网络接口层 :

  • 差错检测技术
  • 多路访问协议(信道复用技术)
  • CSMA/CD 协议
  • MAC 协议
  • 以太网技术
  • ......

网络分层的原因

​ 在这篇文章的最后,我想聊聊:“为什么网络要分层?”。

说到分层,我们先从我们平时使用框架开发一个后台程序来说,我们往往会按照每一层做不同的事情的原则将系统分为三层(复杂的系统分层会更多):

  1. Repository(数据库操作)
  2. Service(业务操作)
  3. Controller(前后端数据交互)

复杂的系统需要分层,因为每一层都需要专注于一类事情。网络分层的原因也是一样,每一层只专注于做一类事情。

​ 好了,再来说回:“为什么网络要分层?”。我觉得主要有 3 方面的原因:

  1. 各层之间相互独立:各层之间相互独立,各层之间不需要关心其他层是如何实现的,只需要知道自己如何调用下层提供好的功能就可以了(可以简单理解为接口调用)。这个和我们对开发时系统进行分层是一个道理。
  2. 提高了整体灵活性 :每一层都可以使用最适合的技术来实现,你只需要保证你提供的功能以及暴露的接口的规则没有改变就行了。这个和我们平时开发系统的时候要求的高内聚、低耦合的原则也是可以对应上的。
  3. 大问题化小 : 分层可以将复杂的网络间题分解为许多比较小的、界线比较清晰简单的小问题来处理和解决。这样使得复杂的计算机网络系统变得易于设计,实现和标准化。 这个和我们平时开发的时候,一般会将系统功能分解,然后将复杂的问题分解为容易理解的更小的问题是相对应的,这些较小的问题具有更好的边界(目标和接口)定义。

我想到了计算机世界非常非常有名的一句话,这里分享一下:

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,计算机整个体系从上到下都是按照严格的层次结构设计的。

参考

TCP 与 UDP

参考UDP数据包最大传输长度_含痕连柔的博客-CSDN博客_udp包最大长度

​ 以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的。 这个1500字节被称为链路层的MTU(最大传输单元). 但这并不是指链路层的长度被限制在1500字节,其实这这个MTU指的是链路层的数据区.并不包括链路层的首部和尾部的18个字节. 所以,事实上,这个1500字节就是网络层IP数据报的长度限制. 因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节。而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的。又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节。这个1472字节就是我们可以使用的字节数。

​ 但是在鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时。最好将UDP的数据长度控件在548字节(576-8-20)以内

TCP 与 UDP 的区别(重要)

  1. 是否面向连接 :UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
  2. 是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
  3. 是否有状态 :这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(这很渣男!)。
  4. 传输效率 :由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
  5. 传输形式 : TCP 是面向字节流的,UDP 是面向报文的。
  6. 首部开销 :TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
  7. 是否提供广播或多播服务 :TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;

我把上面总结的内容通过表格形式展示出来了!确定不点个赞嘛?

TCP UDP
是否面向连接
是否可靠
是否有状态
传输效率 较慢 较快
传输形式 字节流 数据报文段
首部开销 20 ~ 60 bytes 8 bytes
是否提供广播或多播服务

什么时候选择 TCP,什么时候选 UDP?

  • UDP 一般用于即时通信,比如: 语音、 视频 、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。
  • TCP 用于对传输准确性要求特别高的场景,比如文件传输、发送和接收邮件、远程登录等等。

HTTP 基于 TCP 还是 UDP?

HTTP 协议是基于 TCP 协议的,所以发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。

使用 TCP 的协议有哪些?使用 UDP 的协议有哪些?

运行于 TCP 协议之上的协议

  1. HTTP 协议 :超文本传输协议(HTTP,HyperText Transfer Protocol)主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
  2. HTTPS 协议 :更安全的超文本传输协议(HTTPS,Hypertext Transfer Protocol Secure),身披 SSL 外衣的 HTTP 协议
  3. FTP 协议:文件传输协议 FTP(File Transfer Protocol),提供文件传输服务,基于 TCP 实现可靠的传输。使用 FTP 传输文件的好处是可以屏蔽操作系统和文件存储方式。
  4. SMTP 协议:简单邮件传输协议(SMTP,Simple Mail Transfer Protocol)的缩写,基于 TCP 协议,用来发送电子邮件。注意 ⚠️:接受邮件的协议不是 SMTP 而是 POP3 协议。
  5. POP3/IMAP 协议: POP3 和 IMAP 两者都是负责邮件接收的协议。
  6. Telent 协议:远程登陆协议,通过一个终端登陆到其他服务器。被一种称为 SSH 的非常安全的协议所取代。
  7. SSH 协议 : SSH( Secure Shell)是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 建立在可靠的传输协议 TCP 之上。

运行于 UDP 协议之上的协议

  1. DHCP 协议:动态主机配置协议,动态配置 IP 地址
  2. DNS域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。 我们可以将其理解为专为互联网设计的电话薄。实际上 DNS 同时支持 UDP 和 TCP 协议。

TCP 三次握手和四次挥手(传输层)

​ 为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。

建立连接-TCP 三次握手

建立一个 TCP 连接需要“三次握手”,缺一不可 :

  • 一次握手:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 SYN_SEND 状态,等待服务器的确认;
  • 二次握手:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 SYN_RECV 状态
  • 三次握手:客户端发送带有带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务器端都进入ESTABLISHED 状态,完成TCP三次握手。

当建立了 3 次握手之后,客户端和服务端就可以传输数据啦!

为什么要三次握手?

​ 三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

  1. 第一次握手 :Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
  2. 第二次握手 :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
  3. 第三次握手 :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常

三次握手就能确认双发收发功能都正常,缺一不可。

​ 更详细的解答可以看这个:TCP 为什么是三次握手,而不是两次或四次? - 车小胖的回答 - 知乎

第2次握手传回了ACK,为什么还要传回SYN?

​ 服务端传回发送端所发送的 ACK 是为了告诉客户端:“我接收到的信息确实就是你所发送的信号了”,这表明从客户端到服务端的通信是正常的。回传 SYN 则是为了建立并确认从服务端到客户端的通信(因为第二次握手时,服务器并不知道客户端是否能正常收到自己发送出去的消息)。

SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。

为什么两次握手不行?(从请求丢失角度考虑)

​ 1.假设建立TCP连接仅需要两次握手,那么如果第二次握手时,服务端返回给客户端的确认报文丢失了,客户端这边认为服务端没有和他建立连接,而服务端却以为已经和客户端建立了连接,并且服务端可能已经开始向客户端发送数据,但客户端并不会接收这些数据浪费了资源。如果是三次握手,不会出现双方连接还未完全建立成功就开始发送数据的情况。

​ 2.如果服务端接收到了一个早已失效的来自客户端的连接请求报文,会向客户端发送确认报文同意建立TCP连接。但因为客户端并不需要向服务端发送数据,所以此次TCP连接没有意义并且浪费了资源。服务端在等着根本不可能到来的数据。

假如三次握手中有一次握手丢失了会发生什么情况?

​ 加入A想和B建立TCP连接:

​ 1.第一次握手的请求(即A发给B的SYN 中途被丢)丢失了:A会周期性超时重传,直到收到B的确认。

​ 2.第二次握手的请求(即B发给A的SYN +ACK 中途被丢)丢失了:B会周期性超时重传,直到收到A的确认。

​ 3.第三次握手的请求(A发给B的ACK 中途被丢)丢失了:

​ A发完ACK,单方面认为TCP为 Established状态,而B显然认为TCP为Active状态(或者说是SYN-RECIVE):

​ (1)假定此时双方都没有数据发送,B会周期性超时重传,直到收到A的确认,收到之后B的TCP连接也为Established状态,双向可以发包。

​ (2)假定此时A有数据发送,B收到A的 Data + ACK,自然会切换为established状态,并接受A的Data。

​ (3)假定B有数据发送,数据发送不了,会一直周期性超时重传SYN +ACK,直到收到A的确认才可以发送数据。

断开连接-TCP 四次挥手

断开一个 TCP 连接则需要“四次挥手”,缺一不可 :

  1. 第一次挥手 :客户端发送一个 FIN(SEQ=X) 标志的数据包->服务端,用来关闭客户端到服务器的数据传送。然后,客户端进入 FIN-WAIT-1 状态。
  2. 第二次挥手 :服务器收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (SEQ=X+1)标志的数据包->客户端 。然后,此时服务端进入CLOSE-WAIT状态,客户端进入FIN-WAIT-2状态。
  3. 第三次挥手 :服务端关闭与客户端的连接并发送一个 FIN (SEQ=y)标志的数据包->客户端请求关闭连接,然后,服务端进入LAST-ACK状态。
  4. 第四次挥手 :客户端发送 ACK (SEQ=y+1)标志的数据包->服务端并且进入TIME-WAIT状态,服务端在收到 ACK (SEQ=y+1)标志的数据包后进入 CLOSE 状态。此时,如果客户端等待 2MSL 后依然没有收到回复,就证明服务端已正常关闭,随后,客户端也可以关闭连接了。

只要四次挥手没有结束,客户端和服务端就可以继续传输数据!

为什么要四次挥手?

​ TCP是全双工通信,可以双向传输数据。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。

举个例子:A 和 B 打电话,通话即将结束后。

  1. 第一次挥手 : A 说“我没啥要说的了”
  2. 第二次挥手 :B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话
  3. 第三次挥手 :于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”
  4. 第四次挥手 :A 回答“知道了”,这样通话才算结束。

为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手?

​ 因为服务器收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复 ACK,表示接收到了断开连接的请求。等到数据发完之后再发 FIN,断开服务器到客户端的数据传送。还有一种原因是,服务端接收到了断开连接请求之后就会自动进行一些收尾工作,以确保必要的数据传输完毕。

如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样?

​ 客户端没有收到 ACK 确认,会重新发送 FIN 请求。

为什么第四次挥手客户端需要等待 2*MSL(报文段最长寿命)时间后才进入 CLOSED 状态?

​ 第四次挥手时,客户端发送给服务器的 ACK 有可能丢失,如果服务端因为某些原因而没有收到 ACK 的话,服务端就会重发 FIN,如果客户端在 2*MSL 的时间内收到了 FIN,就会重新发送 ACK 并再次等待 2MSL,防止 Server 没有收到 ACK 而不断重发 FIN。

MSL(Maximum Segment Lifetime) : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

​ 如果TCP连接已经建立,在通信过程中,客户端突然故障,那么服务端不会一直等下去,过一段时间就关闭连接了。具体原理是TCP有一个保活机制,主要用在服务器端,用于检测已建立TCP链接的客户端的状态,防止因客户端崩溃或者客户端网络不可达,而服务器端一直保持该TCP链接,占用服务器端的大量资源(因为Linux系统中可以创建的总TCP链接数是有限制的)。

​ 保活机制原理:设置TCP保活机制的保活时间keepldle,即在TCP链接超过该时间没有任何数据交互时,发送保活探测报文;设置保活探测报文的发送时间间隔keepInterval;设置保活探测报文的总发送次数keepCount。如果在keepCount次的保活探测报文均没有收到客户端的回应,则服务器端即关闭与客户端的TCP链接。

TCP 传输可靠性保障(传输层)

TCP 如何保证传输的可靠性?

  1. 基于数据块传输 :应用数据被分割成 TCP 认为最适合发送的数据块,再传输给网络层,数据块被称为报文段或段。
  2. 对失序数据包重新排序以及去重:TCP 为了保证不发生丢包,就给每个包一个序列号,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据就可以实现数据包去重。
  3. 校验和 : TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段
  4. 超时重传 : 当发送方发送数据之后,它启动一个定时器,等待目的端确认收到这个报文段。接收端实体对已成功收到的包发回一个相应的确认信息(ACK)。如果发送端实体在合理的往返时延(RTT)内未收到确认消息,那么对应的数据包就被假设为已丢失并进行重传
  5. 流量控制 : TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议(TCP 利用滑动窗口实现流量控制)。
  6. 拥塞控制 : 当网络拥塞时,减少数据的发送。

TCP 如何实现流量控制?

TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

为什么需要流量控制? 这是因为双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方的发送速率太快,会导致接收方处理不过来。如果接收方处理不过来的话,就只能把处理不过来的数据存在 接收缓冲区(Receiving Buffers) 里(失序的数据包也会被存放在缓存区里)。如果缓存区满了发送方还在狂发数据的话,接收方只能把收到的数据包丢掉。出现丢包问题的同时又疯狂浪费着珍贵的网络资源。因此,我们需要控制发送方的发送速率,让接收方与发送方处于一种动态平衡才好。

这里需要注意的是(常见误区):

  • 发送端不等同于客户端
  • 接收端不等同于服务端

​ TCP 为全双工(Full-Duplex, FDX)通信,双方可以进行双向通信,客户端和服务端既可能是发送端又可能是服务端。因此,两端各有一个发送缓冲区与接收缓冲区,两端都各自维护一个发送窗口和一个接收窗口。接收窗口大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。通信双方的发送窗口和接收窗口的要求相同

TCP 发送窗口可以划分成四个部分

  1. 已经发送并且确认的TCP段(已经发送并确认);
  2. 已经发送但是没有确认的TCP段(已经发送未确认);
  3. 未发送但是接收方准备接收的TCP段(可以发送);
  4. 未发送并且接收方也并未准备接受的TCP段(不可发送)。

TCP发送窗口结构图示

  • SND.WND :发送窗口。
  • SND.UNA:Send Unacknowledged 指针,指向发送窗口的第一个字节。
  • SND.NXT:Send Next 指针,指向可用窗口的第一个字节。

可用窗口大小 = SND.UNA + SND.WND - SND.NXT

TCP 接收窗口可以划分成三个部分

  1. 已经接收并且已经确认的 TCP 段(已经接收并确认);
  2. 等待接收且允许发送方发送 TCP 段(可以接收未确认);
  3. 不可接收且不允许发送方发送TCP段(不可接收)。

TCP 接收窗口结构图示

接收窗口的大小是根据接收端处理数据的速度动态调整的。 如果接收端读取数据快,接收窗口可能会扩大。 否则,它可能会缩小。

另外,这里的滑动窗口大小只是为了演示使用,实际窗口大小通常会远远大于这个值。

TCP 的拥塞控制是怎么实现的?

​ 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。

​ 为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。

​ TCP 的拥塞控制采用了四种算法,即 慢开始拥塞避免快重传快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。

  1. 慢开始: 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。
  2. 拥塞避免: 当拥塞窗口大于等于sshresh这个门槛的时候,让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送放的 cwnd 加 1.

​ 接着如果发生了超时情况,就会将拥塞窗口初始化之后再进行慢开始,同时sshresh门槛取发生超时时的拥塞窗口cwnd的一半大小

​ 3.快重传:在网络中如果出现超时或者阻塞,则按慢开始和拥塞避免算法进行调整。但如果只是丢失某一个报文段,如下图(图片来源于网络),则使用快重传算法。

​ 从上图可知,接收方正确地接收到M1和M2,而M3丢失,由于没有接收到M3,在接收方收到M5、M6和M7时,并不会进行确认,也就是不会发送ACK。这时根据前面说的保证TCP可靠性传输中的序列号的作用,接收方这时不会发送M5,M6,M7的确认ACK,这个时候接受放也可以什么都不做,因为发送方长时间未收到M3的确认报文,会对M3进行重传,但是这样会等待一个计时器超时。还有一种办法就是接受放在接受到排在M3之后的报文时重复确认M2,

​ 但是根据快重传算法,要求在这种情况下,需要快速向发送端发送M2的确认报文,在发送方收到三个M2的确认报文后,无需等待重传计时器所设置的时间,可直接进行M3的重传,这就是快重传。(面试时说这一句就够了,前面是帮助理解)

​ 4.快恢复:指将sshresh门限值设置为发生快重传情况时的拥塞窗口的一半大小。

ARQ 协议了解吗?

自动重传请求(Automatic Repeat-reQuest,ARQ)是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认信息(Acknoledgements,就是我们常说的 ACK),它通常会重新发送,直到收到确认或者重试超过一定的次数。

​ ARQ 包括停止等待 ARQ 协议连续 ARQ 协议

停止等待 ARQ 协议

​ 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组;在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。

1) 无差错情况:

​ 发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。

2) 出现差错情况(超时重传):

​ 停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 自动重传请求 ARQ 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。

3) 确认丢失和确认迟到

  • 确认丢失 :确认消息在传输过程丢失。当 A 发送 M1 消息,B 收到后,B 向 A 发送了一个 M1 确认消息,但却在传输过程中丢失。而 A 并不知道,在超时计时过后,A 重传 M1 消息,B 再次收到该消息后采取以下两点措施:1. 丢弃这个重复的 M1 消息,不向上层交付。 2. 向 A 发送确认消息。(不会认为已经发送过了,就不再发送。A 能重传,就证明 B 的确认消息丢失)。
  • 确认迟到 :确认消息在传输过程中迟到。A 发送 M1 消息,B 收到并发送确认。在超时时间内没有收到确认消息,A 重传 M1 消息,B 仍然收到并继续发送确认消息(B 收到了 2 份 M1)。此时 A 收到了 B 第二次发送的确认消息。接着发送其他数据。过了一会,A 收到了 B 第一次发送的对 M1 的确认消息(A 也收到了 2 份确认消息)。处理如下:1. A 收到重复的确认后,直接丢弃。2. B 收到重复的 M1 后,也直接丢弃重复的 M1。

连续 ARQ 协议

​ 连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到

优点: 信道利用率高,容易实现,即使确认丢失,也不必重传。

缺点: 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。

参考

  1. 《计算机网络(第 7 版)》
  2. 《图解 HTTP》
  3. CCNA Training » TCP and UDP Tutorial (9tut.com)
  4. [Waking-Up/Computer Network.md at master · wolverinn/Waking-Up (github.com)](https://github.com/wolverinn/Waking-Up/blob/master/Computer Network.md)
  5. TCP之滑动窗口原理 - 腾讯云开发者社区-腾讯云 (tencent.com)
  6. TCP 流量控制 (Flow Control) - NotFalse 技術客

HTTP

从输入URL 到页面展示到底发生了什么?(非常重要)

类似的问题:打开一个网页,整个过程会使用哪些协议?

​ 图解(图片来源:《图解 HTTP》):

​ 上图有一个错误,请注意,是 OSPF 不是 OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议, 是由 Internet 工程任务组开发的路由选择协议

总体来说分为以下几个过程:

  1. DNS 解析
  2. TCP 连接
  3. 发送 HTTP 请求
  4. 服务器处理请求并返回 HTTP 报文
  5. 浏览器解析渲染页面
  6. 连接结束

具体可以参考下面这两篇文章:

GET请求和POST请求的区别

参考路人zhang的面试手册,感谢路人zhang,可以关注他的同名微信公众号。

参考 GET 和 POST 到底有什么区别? - 知乎 (zhihu.com)

​ 对于浏览器自带的,发出的GET请求和POST请求如下:

  1. 作用不同:GET用于资源的获取,比如图片、网页等等,而POST用于传输实主体。
  2. 参数位置有别:GET的参数放在URL中,POST的参数存储在实体主体中,并且GET方法提交的请求的URL中的数据最多是2048字节,POST请求没有大小限制。
  3. 安全性:GET方法因为参数放在URL中,安全性相对于POST较差一些(但是也只是稍差,本质上说http就不加密的,不管GET和POST,都是可以看到请请求传输的内容的)。但是每个HTTP请求和返回的每个byte都会在网络上明文传播,不管是url,header还是body。这完全不是一个“是否容易在浏览器地址栏上看到“的问题。
  4. 幂等性:GET方法是具有幂等性的,而POST方法不具有幂等性。这里幂等性指客户端连续发出多次请求,收到的结果都是一样的。另一方面来说,浏览器可以缓存GET请求,但是一般情况下是不会缓存POST请求的。

​ 但是如果是用Ajax或者一些工具发送出来的GET和POST请求的话,就没有那么多限制了,GET请求也可以有body,从协议本身看,并没有什么限制说GET一定不能没有body,POST就一定不能把参放到的querystring上。因此其实可以更加自由的去利用格式。比如Elastic Search的_search api就用了带body的GET;也可以自己开发接口让POST一半的参数放在url的querystring里,另外一半放body里;你甚至还可以让所有的参数都放Header里——可以做各种各样的定制,只要请求的客户端和服务器端能够约定好。

​ 于是就有了一系列接口规范/风格。其中名气最大的当属REST。REST充分运用GET、POST、PUT和DELETE,约定了这4个接口分别获取、创建、替换和删除“资源”,REST最佳实践还推荐在请求体使用json格式。

HTTP报文相关

​ HTTP请求报文是由三部分组成: 请求行, 请求报头请求正文

1.请求行

​ 格式如下: Method Request-URL HTTP-Version CRLF

eg: GET index.html HTTP/1.1

​ 常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。

2.请求报头

​ 请求报头允许客户端向服务器传递请求的附加信息客户端自身的信息。PS: 客户端不一定特指浏览器,有时候也可使用Linux下的CURL命令以及HTTP客户端测试工具等。
​ 常见的请求报头有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。

​ 上图是使用Chrome开发者工具截取的对百度的HTTP请求以及响应报文,从图中可以看出,请求报头中使用了Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie等字段。

​ Accept用于指定客户端用于接受哪些类型的信息,

​ Accept-Encoding与Accept类似,它用于指定接受的编码方式。

​ Connection设置为Keep-alive用于告诉客户端本次HTTP请求结束之后并不需要关闭TCP连接,这样可以使下次HTTP请求使用相同的TCP通道,节省TCP连接建立的时间。

3.请求正文

​ 当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的Web应用通常采用Rest架构,请求的数据格式一般为json。这时就需要设置Content-Type: application/json。

HTTP 常见状态码总结(应用层)

​ HTTP 状态码用于描述 HTTP 请求的结果,比如2xx 就代表请求被成功处理。

1xx Informational(信息性状态码)

​ 相比于其他类别状态码来说,1xx 你平时你大概率不会碰到。比如100-continue,可以用于大文件发送前的一些确认工作,以免浪费带宽。

2xx Success(成功状态码)

  • 200 OK :请求被成功处理。比如我们发送一个查询用户数据的HTTP 请求到服务端,服务端正确返回了用户数据。这个是我们平时最常见的一个 HTTP 状态码。
  • 201 Created :请求被成功处理并且在服务端创建了一个新的资源。比如我们通过 POST 请求创建一个新的用户。
  • 202 Accepted :服务端已经接收到了请求,但是还未处理。
  • 204 No Content : 服务端已经成功处理了请求,但是没有返回任何内容。

这里格外提一下 204 状态码,平时学习/工作中见到的次数并不多。

​ 简单来说,204状态码描述的是我们向服务端发送 HTTP 请求之后,只关注处理结果是否成功的场景。也就是说我们需要的就是一个结果:true/false。举个例子:你要追一个女孩子,你问女孩子:“我能追你吗?”,女孩子回答:“好!”。我们把这个女孩子当做是服务端就很好理解 204 状态码了。

3xx Redirection(重定向状态码)

  • 301 Moved Permanently : 资源被永久重定向了。比如你的网站的网址更换了。
  • 302 Found :资源被临时重定向了。比如你的网站的某些资源被暂时转移到另外一个网址。

4xx Client Error(客户端错误状态码)

  • 400 Bad Request : 发送的HTTP请求存在问题。比如请求参数不合法请求方法错误
  • 401 Unauthorized未认证却请求需要认证之后才能访问的资源。
  • 403 Forbidden :直接拒绝HTTP请求,不处理。一般用来针对非法请求
  • 404 Not Found : 你请求的资源未在服务端找到。比如你请求某个用户的信息,服务端并没有找到指定的用户。
  • 409 Conflict : 表示请求的资源与服务端当前的状态存在冲突,请求无法被处理。

5xx Server Error(服务端错误状态码)

  • 500 Internal Server Error : 服务端出问题了(通常是服务端出Bug了,比如报错)。比如你服务端处理请求的时候突然抛出异常,但是异常并为在服务端被正确处理
  • 502 Bad Gateway :我们的网关将请求转发到服务端,但是服务端返回的却是一个错误的响应

参考

TCP粘包问题

参考面试突击70:什么是粘包和半包?怎么解决?_讨论帖_牛客网 (nowcoder.com)

定义

​ 粘包和半包问题是数据传输中比较常见的问题,所谓的 粘包问题是指数据在传输时,在一条消息中读取到了另一条消息的部分数据,这种现象就叫做粘包。 比如发送了两条消息,分别为“ABC”和“DEF”,那么正常情况下接收端也应该收到两条消息“ABC”和“DEF”,但接收端却收到的是“ABCD”,像这种情况就叫做粘。

​ 半包问题是指接收端只收到了部分数据,而非完整的数据的情况就叫做半包。比如发送了一条消息是“ABC”,而接收端却收到的是“AB”和“C”两条信息,这种情况就叫做半包。PS:大部分情况下我们都把粘包问题和半包问题看成同一个问题,所以下文就用“粘包”问题来替代“粘包”和“半包”问题。

为什么会有粘包问题

​ 粘包问题发生在 TCP/IP 协议中,因为 TCP 是面向连接的传输协议,它是以“”的形式传输数据的,而“流”数据是没有明确的开始和结尾边界的,所以就会出现粘包问题。


代码演示如下:

接下来我们用代码来演示一下粘包和半包问题,为了演示的直观性,我会设置两个角色:

  • 服务器端用来接收消息;
  • 客户端用来发送一段固定的消息。

然后通过打印服务器端接收到的信息来观察粘包问题。 服务器端代码实现如下:

/**
 * 服务器端(只负责接收消息)
 */
class ServSocket {
    // 字节数组的长度
    private static final int BYTE_LENGTH = 20;  
    public static void main(String[] args) throws IOException {
        // 创建 Socket 服务器
        ServerSocket serverSocket = new ServerSocket(8888);
        // 获取客户端连接
        Socket clientSocket = serverSocket.accept();
        // 得到客户端发送的流对象
        try (InputStream inputStream = clientSocket.getInputStream()) {
            while (true) {
                // 循环获取客户端发送的信息
                byte[] bytes = new byte[BYTE_LENGTH];
                // 读取客户端发送的信息
                int count = inputStream.read(bytes, 0, BYTE_LENGTH);
                if (count > 0) {
                    // 成功接收到有效消息并打印
                    System.out.println("接收到客户端的信息是:" + new String(bytes));
                }
                count = 0;
            }
        }
    }
}

​ 客户端实现代码如下:

/**
 * 客户端(只负责发送消息)
 */
static class ClientSocket {
    public static void main(String[] args) throws IOException {
        // 创建 Socket 客户端并尝试连接服务器端
        Socket socket = new Socket("127.0.0.1", 8888);
        // 发送的消息内容
        final String message = "Hi,Java."; 
        // 使用输出流发送消息
        try (OutputStream outputStream = socket.getOutputStream()) {
            // 给服务器端发送 10 次消息
            for (int i = 0; i < 10; i++) {
                // 发送消息
                outputStream.write(message.getBytes());
            }
        }
    }
}

​ 以上程序的执行结果如下图所示:

​ 通过上述结果我们可以看出,服务器端发生了粘包问题,因为客户端发送了 10 次固定的“Hi,Java.”的消息,正确的结果应该是服务器端也接收到了 10 次固定消息“Hi,Java.”才对,但实际执行结果并非如此。

解决办法

​ 粘包问题的常见解决方案有以下 3 种:

  1. 发送方和接收方固定发送数据的大小,当字符长度不够时用空字符弥补,有了固定大小之后就知道每条消息的具体边界了,这样就没有粘包的问题了;
  2. 在 TCP 协议的基础上封装一层自定义数据协议,在自定义数据协议中,包含数据头(存储数据的大小)和 数据的具体内容,这样服务端得到数据之后,通过解析数据头就可以知道数据的具体长度了,也就没有粘包的问题了;
  3. 特殊的字符结尾,比如以“\n”结尾,这样我们就知道数据的具体边界了,从而避免了粘包问题(推荐方案)。

固定发送数据的大小

​ 收、发固定大小的数据,服务器端的实现代码如下:

static class ServSocketV1 {
 private static final int BYTE_LENGTH = 1024;  // 字节数组长度(收消息用)
 public static void main(String[] args) throws IOException {
     ServerSocket serverSocket = new ServerSocket(9091);
     Socket clientSocket = serverSocket.accept();// 获取到连接
     try (InputStream inputStream = clientSocket.getInputStream()) {
         while (true) {
             byte[] bytes = new byte[BYTE_LENGTH];
             // 读取客户端发送的信息
             int count = inputStream.read(bytes, 0, BYTE_LENGTH);
             if (count > 0) {
                 // 接收到消息打印
                 System.out.println("接收到客户端的信息是:" + new String(bytes).trim());
             }
             count = 0;
         }
     }
 }
}

​ 客户端的实现代码如下:

static class ClientSocketV1 {
 private static final int BYTE_LENGTH = 1024;  // 字节长度
 public static void main(String[] args) throws IOException {
     Socket socket = new Socket("127.0.0.1", 9091);
     final String message = "Hi,Java."; // 发送消息
     try (OutputStream outputStream = socket.getOutputStream()) {
         // 将数据组装成定长字节数组
         byte[] bytes = new byte[BYTE_LENGTH];
         int idx = 0;
         for (byte b : message.getBytes()) {
             bytes[idx] = b;
             idx++;
         }
         // 给服务器端发送 10 次消息
         for (int i = 0; i < 10; i++) {
             outputStream.write(bytes, 0, BYTE_LENGTH);
         }
     }
 }
}

​ 运行结果如下所示:

优缺点分析

​ 1.这种固定数据大小的传输方式,当数据量比较小时会使用空字符来填充,所以会额外的增加网络传输的负担,因此不是理想的解决方案。

​ 2.数据大于长度时,无法处理

自定义请求协议

​ 这种解决方案的实现思路是将请求的数据封装为两部分:消息头(发送的数据大小)+消息体(发送的具体数据)。自定义消息头内的格式,比如这一块的数据有多少,是否发送完毕等等...自由度很大

以特殊字符结尾

​ 以特殊字符结尾就可以知道流的边界了,它的具体实现是:使用 Java 中自带的 BufferedReaderBufferedWriter,也就是带缓冲区的输入字符流和输出字符流,通过写入的时候加上\n来结尾,读取的时候使用readLine按行来读取数据,这样就知道流的边界了,从而解决了粘包的问题。

​ 但是缺点也很明显,当传输的数据中也带有特殊字符时,就会出现问题。

HTTP vs HTTPS(应用层)

HTTP 协议

HTTP 协议介绍

​ HTTP 协议,全称超文本传输协议(Hypertext Transfer Protocol)。顾名思义,HTTP 协议就是用来规范超文本的传输。超文本,也就是网络上的包括文本在内的各式各样的消息,具体来说,主要是来规范浏览器和服务器端的行为的。并且,HTTP 是一个无状态(stateless)协议,也就是说服务器不维护任何有关客户端过去所发请求的消息。这其实是一种懒政,有状态协议会更加复杂,需要维护状态(历史信息),而且如果客户或服务器失效,会产生状态的不一致,解决这种不一致的代价更高。

HTTP 协议通信过程

HTTP 是应用层协议,它以 TCP(传输层)作为底层协议,默认端口为 80. 通信过程主要如下:

  1. 服务器在 80 端口等待客户的请求。
  2. 浏览器发起到服务器的 TCP 连接(创建套接字 Socket)。
  3. 服务器接收来自浏览器的 TCP 连接。
  4. 浏览器(HTTP 客户端)与 Web 服务器(HTTP 服务器)交换 HTTP 消息。
  5. 关闭 TCP 连接。

HTTP 协议优点

​ 扩展性强、速度快、跨平台支持性好。

HTTPS 协议

HTTPS 协议介绍

​ HTTPS 协议(Hyper Text Transfer Protocol Secure),是 HTTP 的加强安全版本。HTTPS 是基于 HTTP 的,也是用 TCP 作为底层协议,并额外使用 SSL/TLS 协议用作加密和安全认证默认端口号是 443.HTTPS 协议中,SSL 通道通常使用基于密钥的加密算法,密钥长度通常是 40 比特或 128 比特。

HTTPS 协议优点

​ 保密性好、信任度高。

HTTPS 的核心—SSL/TLS协议

​ HTTPS 之所以能达到较高的安全性要求,就是结合了 SSL/TLS 和 TCP 协议,对通信数据进行加密,解决了 HTTP 数据透明的问题。接下来重点介绍一下 SSL/TLS 的工作原理。

SSL 和 TLS 的区别?

SSL 和 TLS 没有太大的区别。

​ SSL 指安全套接字协议(Secure Sockets Layer),首次发布与 1996 年。SSL 的首次发布其实已经是他的 3.0 版本,SSL 1.0 从未面世,SSL 2.0 则具有较大的缺陷(DROWN 缺陷——Decrypting RSA with Obsolete and Weakened eNcryption)。很快,在 1999 年,SSL 3.0 进一步升级,新版本被命名为 TLS 1.0。因此,TLS 是基于 SSL 之上的,但由于习惯叫法,通常把 HTTPS 中的核心加密协议混成为 SSL/TLS。

SSL/TLS 的工作原理

非对称加密

​ SSL/TLS 的核心要素是非对称加密。非对称加密采用两个密钥——一个公钥,一个私钥。在通信时,私钥仅由解密者保存,公钥由任何一个想与解密者通信的发送者(加密者)所知,私钥加密的只能用公钥解密,公钥加密的只能用私钥解密。可以设想一个场景,

​ 在某个自助邮局,每个通信信道都是一个邮箱,每一个邮箱所有者都在旁边立了一个牌子,上面挂着一把钥匙:这是我的公钥,发送者请将信件放入我的邮箱,并用公钥锁好。

​ 但是公钥只能加锁,并不能解锁。解锁只能由邮箱的所有者——因为只有他保存着私钥。

​ 这样,通信信息就不会被其他人截获了,这依赖于私钥的保密性。

​ 常用的非对称加密算法有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。

​ 非对称加密的公钥和私钥需要采用一种复杂的数学机制生成(密码学认为,为了较高的安全性,尽量不要自己创造加密方案)。公私钥对的生成算法依赖于单向陷门函数

​ 单向函数:已知单向函数 f,给定任意一个输入 x,易计算输出 y=f(x);而给定一个输出 y,假设存在 f(x)=y,很难根据 f 来计算出 x。

​ 单向陷门函数:一个较弱的单向函数。已知单向陷门函数 f,陷门 h,给定任意一个输入 x,易计算出输出 y=f(x;h);而给定一个输出 y,假设存在 f(x;h)=y,很难根据 f 来计算出 x,但可以根据 f 和 h 来推导出 x。

​ 上图就是一个单向函数(不是单项陷门函数),假设有一个绝世秘籍,任何知道了这个秘籍的人都可以把苹果汁榨成苹果,那么这个秘籍就是“陷门”了吧。在这里,函数 f 的计算方法相当于公钥,陷门 h 相当于私钥。公钥 f 是公开的,任何人对已有输入,都可以用 f 加密,而要想根据加密信息还原出原信息,必须要有私钥才行。

对称加密

​ 使用 SSL/TLS 进行通信的双方需要使用非对称加密方案来通信,但是非对称加密设计了较为复杂的数学算法,在实际通信过程中,计算的代价较高效率太低,因此,SSL/TLS 实际对消息的加密使用的是对称加密

​ 对称加密:通信双方共享唯一密钥 k,加解密算法已知,加密方利用密钥 k 加密,解密方利用密钥 k 解密,保密性依赖于密钥 k 的保密性。常用的对称加密算法有:DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法。

​ 对称加密的密钥生成代价比公私钥对的生成代价低得多,那么有的人会问了,为什么 SSL/TLS 还需要使用非对称加密呢?因为对称加密的保密性完全依赖于密钥的保密性。在双方通信之前,需要商量一个用于对称加密的密钥。我们知道网络通信的信道是不安全的,传输报文对任何人是可见的,密钥的交换肯定不能直接在网络信道中传输。因此,使用非对称加密,对对称加密的密钥进行加密,保护该密钥不在网络信道中被窃听。这样,通信双方只需要一次非对称加密,交换对称加密的密钥,在之后的信息通信中,使用绝对安全的密钥,对信息进行对称加密,即可保证传输消息的保密性。

公钥传输的信赖性

​ SSL/TLS 介绍到这里,了解信息安全的朋友又会想到一个安全隐患,设想一个下面的场景:

​ 客户端 C 和服务器 S 想要使用 SSL/TLS 通信,由上述 SSL/TLS 通信原理,C 需要先知道 S 的公钥,而 S 公钥的唯一获取途径,就是把 S 公钥在网络信道中传输。要注意网络信道通信中有几个前提:

  1. 任何人都可以捕获通信包
  2. 通信包的保密性由发送者设计
  3. 保密算法设计方案默认为公开,而(解密)密钥默认是安全的

​ 因此,假设 S 公钥不做加密,在信道中传输,那么很有可能存在一个攻击者 A,发送给 C 一个诈包,假装是 S 公钥,其实是诱饵服务器 AS 的公钥。当 C 收获了 AS 的公钥(却以为是 S 的公钥),C 后续就会使用 AS 公钥对数据进行加密,并在公开信道传输,那么 A 将捕获这些加密包,用 AS 的私钥解密,就截获了 C 本要给 S 发送的内容,而 C 和 S 二人全然不知。

​ 同样的,S 公钥即使做加密,也难以避免这种信任性问题,C 被 AS 拐跑了!

​ 为了公钥传输的信赖性问题,第三方机构应运而生——证书颁发机构(CA,Certificate Authority)。CA 默认是受信任的第三方。CA 会给各个服务器颁发证书,证书存储在服务器上,并附有 CA 的电子签名(见下节)。

​ 当客户端(浏览器)向服务器发送 HTTPS 请求时,一定要先获取目标服务器的证书,并根据证书上的信息,检验证书的合法性。一旦客户端检测到证书非法,就会发生错误。客户端获取了服务器的证书后,由于证书的信任性是由第三方信赖机构认证的,而证书上又包含着服务器的公钥信息,客户端就可以放心的信任证书上的公钥就是目标服务器的公钥。

数字签名

​ 好,到这一小节,已经是 SSL/TLS 的尾声了。上一小节提到了数字签名,数字签名要解决的问题,是防止证书被伪造。第三方信赖机构 CA 之所以能被信赖,就是 靠数字签名技术 。数字签名,是 CA 在给服务器颁发证书时,使用散列+加密的组合技术,在证书上盖个章,以此来提供验伪的功能。具体行为如下:

​ CA 知道服务器的公钥,对该公钥采用散列技术生成一个摘要。CA 使用 CA 私钥对该摘要进行加密,并附在证书下方,发送给服务器。

​ 现在服务器将该证书发送给客户端,客户端需要验证该证书的身份。客户端找到第三方机构 CA,获知 CA 的公钥,并用 CA 公钥对证书的签名进行解密,获得了 CA 生成的摘要。

​ 客户端对证书数据做相同的散列处理,得到摘要,并将该摘要与之前从签名中解码出的摘要做对比,如果相同,则身份验证成功;否则验证失败。就是说证书里既包含了铭文的签名也包含了对签名的hash加密了之后的“数字签名”,用CA公钥解密之后对比hash是否一致,若一致则信任证书。

总结来说,带有证书的公钥传输机制如下:

  1. 设有服务器 S,客户端 C,和第三方信赖机构 CA。
  2. S 信任 CA,CA 是知道 S 公钥的,CA 向 S 颁发证书。并附上 CA 私钥对消息摘要(包括S的公钥信息)的加密签名
  3. S 获得 CA 颁发的证书,将该证书传递给 C。
  4. C 获得 S 的证书,信任 CA 并知晓 CA 公钥,使用 CA 公钥对 S 证书上的签名解密,同时对S发送过来的包含在证书内的公钥进行散列处理,得到摘要。比较摘要,验证 S 证书的真实性。
  5. 如果 C 验证 S 证书是真实的,则信任 S 的公钥(在 S 证书中)。

​ 上述流程存在的一个问题是客户端哪里来的数字认证机构的公钥,其实,在很多浏览器开发时,会内置常用数字证书认证机构的公钥

具体可以参考这个视频数字签名 及 数字证书 原理_哔哩哔哩_bilibili

HTTPS建立之后还用HTTPS吗?

​ https建立之后,双方都有私钥了,这个时候用私钥对数据加密后传输,就和http一样了。

总结

  • 端口号 :HTTP 默认是 80,HTTPS 默认是 443。
  • URL 前缀 :HTTP 的 URL 前缀是 http://,HTTPS 的 URL 前缀是 https://
  • 安全性和资源消耗 : HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。

HTTP各个版本

HTTP 1.0 vs HTTP 1.1(应用层)

这篇文章会从下面几个维度来对比 HTTP 1.0 和 HTTP 1.1:

  • 响应状态码
  • 缓存处理
  • 连接方式
  • Host头处理
  • 带宽优化

响应状态码

​ HTTP/1.0仅定义了16种状态码。HTTP/1.1中新加入了大量的状态码,光是错误响应状态码就新增了24种。比如说,100 (Continue)——在请求大资源前的预热请求,206 (Partial Content)——范围请求的标识码,409 (Conflict)——请求与当前资源的规定冲突,410 (Gone)——资源已被永久转移,而且没有任何已知的转发地址。

缓存处理

​ 缓存技术通过避免用户与源服务器的频繁交互,节约了大量的网络带宽,降低了用户接收信息的延迟。

1.HTTP/1.0

​ HTTP/1.0提供的缓存机制非常简单。服务器端使用Expires标签来标志(时间)一个响应体,在Expires标志时间内的请求,都会获得该响应体缓存。服务器端在初次返回给客户端的响应体中,有一个Last-Modified标签,该标签标记了被请求资源在服务器端的最后一次修改。在请求头中,使用If-Modified-Since标签,该标签标志一个时间,意为客户端向服务器进行问询:“该时间之后,我要请求的资源是否有被修改过?”通常情况下,请求头中的If-Modified-Since的值即为上一次获得该资源时,响应体中的Last-Modified的值。

​ 如果服务器接收到了请求头,并判断If-Modified-Since时间后,资源确实没有修改过,则返回给客户端一个304 not modified响应头,表示”缓冲可用,你从浏览器里拿吧!”。如果服务器判断If-Modified-Since时间后,资源被修改过,则返回给客户端一个200 OK的响应体,并附带全新的资源内容,表示”你要的我已经改过的,给你一份新的”。

2.HTTP/1.1

​ HTTP/1.1的缓存机制在HTTP/1.0的基础上,大大增加了灵活性扩展性。基本工作原理和HTTP/1.0保持不变,而是增加了更多细致的特性。其中,请求头中最常见的特性就是Cache-Control,详见MDN Web文档 Cache-Control。HTTP 1.1还引入了Entity tag,If-Unmodified-Since,If-Match等更多可供选择的缓存头来控制缓存策略。

连接方式

HTTP/1.0 默认使用短连接 ,也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个TCP连接,这样就会导致有大量的“握手报文”和“挥手报文”占用了带宽

为了解决 HTTP/1.0 存在的资源浪费的问题, HTTP/1.1 优化为默认长连接模式 。 采用长连接模式的请求报文会通知服务端:“我向你请求连接,并且连接成功建立后,请不要关闭”。因此,该TCP连接将持续打开,为后续的客户端-服务端的数据交互服务。也就是说在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。如果 TCP 连接一直保持的话也是对资源的浪费,因此,一些服务器软件(如 Apache)还会支持超时时间。在超时时间之内没有新的请求达到,TCP 连接才会被关闭。

​ 有必要说明的是,HTTP/1.0仍提供了长连接选项,即在请求头中加入Connection: Keep-alive。同样的,在HTTP/1.1中,如果不希望使用长连接选项,也可以在请求头中加入Connection: close,这样会通知服务器端:“我不需要长连接,连接成功后即可关闭”。

HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。实现长连接需要客户端和服务端都支持长连接。

Host头处理

​ 域名系统(DNS)允许多个主机名绑定到同一个IP地址上(一台服务器拥有一个ip地址,但是由于虚拟主机技术的兴起,可以是实现一个真实服务器上存在多个虚拟主机。也就是一个服务器上可以有多个域名。),但是HTTP/1.0并没有考虑这个问题,假设我们有一个资源URL是http://example1.org/home.html,HTTP/1.0的请求报文中,将会请求的是GET /home.html HTTP/1.0.也就是不会加入主机名。这样的报文送到服务器端(服务端上有多个域名),服务器是理解不了客户端想请求的真正网址。

因此,HTTP/1.1在请求头中加入了Host字段。加入Host字段的报文头部将会是:

GET /home.html HTTP/1.1   ##请求行
Host: example1.org		  ##加在请求头的ho

​ 这样,服务器端就可以确定客户端想要请求的真正的网址了。

带宽优化

范围请求

​ HTTP/1.1引入了范围请求(range request)机制,以避免带宽的浪费。当客户端想请求一个文件的一部分,或者需要继续下载一个已经下载了部分但被终止的文件,HTTP/1.1可以在请求中加入Range头部,以请求(并只能请求字节型数据)数据的一部分。服务器端可以忽略Range头部,也可以返回若干Range响应如果一个响应包含部分数据的话,那么将带有206 (Partial Content)状态码。该状态码的意义在于避免了HTTP/1.0代理缓存错误地把该响应认为是一个完整的数据响应,从而把他当作为一个请求的响应缓存。

​ 在范围响应中,Content-Range头部标志指示出了该数据块的偏移量和数据块的长度。

状态码100

​ HTTP/1.1中新加入了状态码100。该状态码的使用场景为,存在某些较大的文件请求,服务器可能不愿意响应这种请求,此时状态码100可以作为指示请求是否会被正常响应,过程如下图:

​ 然而在HTTP/1.0中,并没有100 (Continue)状态码,要想触发这一机制,可以发送一个Expect头部,其中包含一个100-continue的值。

压缩

​ 许多格式的数据在传输时都会做预压缩处理。数据的压缩可以大幅优化带宽的利用。然而,HTTP/1.0对数据压缩的选项提供的不多,不支持压缩细节的选择,也无法区分端到端(end-to-end)压缩或者是逐跳(hop-by-hop)压缩。

HTTP/1.1则对内容编码(content-codings)和传输编码(transfer-codings)做了区分。内容编码总是端到端的,传输编码总是逐跳的。

​ HTTP/1.0包含了Content-Encoding头部,对消息进行端到端编码。HTTP/1.1加入了Transfer-Encoding头部,可以对消息进行逐跳传输编码。HTTP/1.1还加入了Accept-Encoding头部,是客户端用来指示他能处理什么样的内容编码

总结

  1. 连接方式 : HTTP 1.0 为短连接,HTTP 1.1 支持长连接。
  2. 状态响应码 : HTTP/1.1中新加入了大量的状态码,光是错误响应状态码就新增了24种。比如说,100 (Continue)——在请求大资源前的预热请求,206 (Partial Content)——范围请求的标识码,409 (Conflict)——请求与当前资源的规定冲突,410 (Gone)——资源已被永久转移,而且没有任何已知的转发地址。
  3. 缓存处理 : 在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。
  4. 带宽优化及网络连接的使用 :HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  5. Host头处理 : HTTP/1.1在请求头中加入了Host字段。

HTTP2.0

参考深入理解http2.0协议,看这篇就够了! - 知乎 (zhihu.com)

​ HTTP2.0优化的内容主要有以下部分:

新的二进制格式

​ http2.0之所以能够突破http1.X标准(HTTP 1.x是基于文本的)的性能限制,改进传输性能,实现低延迟和高吞吐量,就是因为其新增了二进制分帧层

​ 在二进制分帧层上,http2.0会将所有传输信息分割为更小的消息和帧,并对它们采用二进制格式的编码将其封装,新增的二进制分帧层同时也能够保证http的各种动词方法首部不受影响兼容上一代http标准。其中,http1.X中的首部信息header封装到Headers帧中,而request body将被封装到Data帧中。

多路复用

​ 在http1.1中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量的限制,超过限制数目的请求会被阻塞。这也是为何一些站点会有多个静态资源 CDN 域名的原因之一。

​ 而http2.0中的多路复用优化了这一性能。多路复用允许同时通过单一的http/2 连接发起多重的请求-响应消息。有了新的分帧机制后,http/2 不再依赖多个TCP连接去实现多流并行了。每个数据流都拆分成很多互不依赖的帧,而这些帧可以交错(乱序发送),还可以分优先级,最后再在另一端把它们重新组合起来。http 2.0 连接都是持久化的,而且客户端与服务器之间也只需要一个连接(每个域名一个连接)即可。http2连接可以承载数十或数百个流的复用,多路复用意味着来自很多流的数据包能够混合在一起通过同样连接传输。当到达终点时,再根据不同帧首部的流标识符重新连接将不同的数据流进行组装。

header压缩

​ 在HTTP 1.x中,header携带大量信息,并且每次都需要重新发送,HTTP 2.0采用编码的方式减小了header的大小,同时通信双方各自缓存一份header fields表,避免了header的重复传输。

服务端推送

​ 客户端在请求一个资源时,会把相关资源一起发给客户端,这样客户端就不需要再次发起请求了。服务器可以对一个客户端请求发送多个响应,服务器向客户端推送资源无需客户端明确地请求。并且,服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端省去了客户端重复请求的步骤

​ 这样还有个好处是,让在遵循同源的情况下,不同页面之间可以共享缓存资源成为可能

HTTP3.0(基于UDP的安全可靠的HTTP2.0协议)

参考了解 HTTP3.0 吗?简要说一下 HTTP 的一个发展历程?_CreatorRay的博客-CSDN博客_http3.0HTTP3.0(QUIC的实现机制) - 快乐~ - 博客园 (cnblogs.com)

定义

​ HTTP3.0,也称作HTTP over QUIC。HTTP3.0的核心是QUIC(读音quick)协议,由Google在 2015年提出的SPDY v3演化而来的新协议,传统的HTTP协议是基于传输层TCP的协议,而QUIC是基于传输层UDP上的协议,可以定义成:HTTP3.0基于UDP的安全可靠的HTTP2.0协议。

解决的问题

​ QUIC 协议针对基于TCP和TLS的HTTP2.0协议解决了下面的问题。

减少了TCP三次握手及TLS握手时间

​ 不管是HTTP1.0/1.1还是HTTPS,HTTP2.0,都使用了TCP进行传输。HTTPS和HTTP2还需要使用TLS协议来进行安全传输。这就出现了两个握手延迟,而基于UDP协议的QUIC,因为UDP本身没有连接的概念,连接建立时只需要一次交互,半个握手的时间。区别如下图:

多路复用丢包的线头阻塞问题

​ QUIC保留了HTTP2.0多路复用的特性,在之前的多路复用过程中,同一个TCP连接上有多个stream,假如其中一个stream丢包,在重传前后的stream都会受到影响,而QUIC中一个连接上的多个stream之间没有依赖。所以当发生丢包时,只会影响当前的stream,也就避免了线头阻塞问题。

​ 换句话说:同HTTP2.0一样,同一条 QUIC连接上可以创建多个stream,来发送多个HTTP请求,但是,QUIC是基于UDP的,一个连接上的多个stream之间没有依赖。这样,假如stream2丢了一个UDP包,后面跟着stream3的一个UDP包,虽然stream2的那个包需要重新传,但是stream3的包无需等待,就可以发给用户。

优化重传策略

​ 以往的TCP丢包重传策略是:在发送端为每一个封包标记一个编号(sequence number),接收端在收到封包时,就会回传一个带有对应编号的ACK封包给发送端,告知发送端封包已经确实收到。当发送端在超过一定时间之后还没有收到回传的ACK,就会认为封包已经丢失,启动重新传送的机制,复用与原来相同的编号重新发送一次封包,确保在接收端这边没有任何封包漏接。

​ 这样的机制就会带来一些问题,假设发送端总共对同一个封包发送了两次(初始+重传),使用的都是同一个sequence number:编号N。之后发送端在拿到编号N封包的回传ACK时,将无法判断这个带有编号N的ACK,是接收端在收到初始封包后回传的ACK还是接收到重传封包后回传的ACK。这就会加大后续的重传计算的耗时(因为HTTP会通过采样往返时间RTT不断调整,就像刚刚的情况一样TCP里面超时的采样存在不准确的问题,没办法准确的判断回传时间)。

​ QUIC为了避免这个问题,发送端在传送封包时,初始与重传的每一个封包都改用一个新的编号,unique packet number,每一个编号都唯一而且严格递增,这样每次在收到ACK时,就可以依据编号明确的判断这个ACK是来自初始封包或者是重传封包。

流量控制

​ TCP的流量控制是通过滑动窗口协议。QUIC的流量控制也是通过window_update,来告诉对端它可以接受的字节数。但是QUIC的窗口是适应自己的多路复用机制的,不但在一个连接上控制窗口,还在一个连接中的每个steam控制窗口

​ 在TCP协议中,接收端的窗口的起始点是下一个要接收并且ACK的包,即便后来的包都到了,放在缓存里面,窗口也不能右移,因为TCP的ACK机制是基于序列号的累计应答,一旦ACK了一个序列号,就说明前面的都到了,所以是要前面的没到,后面的到了也不能ACK,就会导致后面的到了,也有可能超时重传,浪费带宽

​ QUIC的ACK是基于offset的,每个offset的包来了,进了缓存,就可以应答,应答后就不会重发,中间的空档会等待到来或者重发,而窗口的起始位置为当前收到的最大offset,从这个offset到当前的stream所能容纳的最大缓存,是真正的窗口的大小,显然,那样更加准确。

HTTP 是不保存状态的协议, 如何保存用户状态?

​ HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢? Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个 Session)。

​ 在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库 redis 保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。

Cookie 被禁用怎么办?

最常用的就是利用 URL 重写把 Session ID 直接附加在 URL 路径的后面。

URI 和 URL 的区别是什么?

  • URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源
  • URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。

​ URI 的作用像身份证号一样,URL 的作用更像家庭住址一样。URL 是一种具体的 URI,它不仅唯一标识资源,而且还提供了定位该资源的信息

ARP

什么是 Mac 地址?

​ MAC 地址的全称是 媒体访问控制地址(Media Access Control Address)。如果说,互联网中每一个资源都由 IP 地址唯一标识(IP 协议内容),那么一切网络设备都由 MAC 地址唯一标识

​ 可以理解为,MAC 地址是一个网络设备真正的身份证号,IP 地址只是一种不重复的定位方式(比如说住在某省某市某街道的张三,这种逻辑定位是 IP 地址,他的身份证号才是他的 MAC 地址),也可以理解为 MAC 地址是身份证号,IP 地址是邮政地址。MAC 地址也有一些别称,如 LAN 地址、物理地址、以太网地址等。

还有一点要知道的是,不仅仅是网络资源才有 IP 地址,网络设备也有 IP 地址,比如路由器。但从结构上说,路由器等网络设备的作用是组成一个网络,而且通常是内网,所以它们使用的 IP 地址通常是内网 IP,内网的设备在与内网以外的设备进行通信时,需要用到 NAT 协议

​ MAC 地址的长度为 6 字节(48 比特),地址空间大小有 280 万亿之多($2^{48}$),MAC 地址由 IEEE 统一管理与分配,理论上,一个网络设备中的网卡上的 MAC 地址是永久的。不同的网卡生产商从 IEEE 那里购买自己的 MAC 地址空间(MAC 的前 24 比特),也就是前 24 比特由 IEEE 统一管理,保证不会重复。而后 24 比特,由各家生产商自己管理,同样保证生产的两块网卡的 MAC 地址不会重复。

​ MAC 地址具有可携带性、永久性,身份证号永久地标识一个人的身份,不论他到哪里都不会改变。而 IP 地址不具有这些性质,当一台设备更换了网络,它的 IP 地址也就可能发生改变,也就是它在互联网中的定位发生了变化。

​ 最后,记住,MAC 地址有一个特殊地址:FF-FF-FF-FF-FF-FF(全 1 地址),该地址表示广播地址。

ARP 协议解决了什么问题地位如何?

​ ARP 协议,全称 地址解析协议(Address Resolution Protocol),它解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。

ARP 协议的工作原理?

​ 每当我们学习一个新的网络协议的时候,都要把他结合到 OSI 七层模型中,或者是 TCP/IP 协议栈中来学习,一是要学习该协议在整个网络协议栈中的位置,二是要学习该协议解决了什么问题,地位如何?三是要学习该协议的工作原理,以及一些更深入的细节。

ARP 协议,可以说是在协议栈中属于一个偏底层的、非常重要的、又非常简单的通信协议。

开始阅读这篇文章之前,你可以先看看下面几个问题:

  1. ARP 协议在协议栈中的位置? ARP 协议在协议栈中的位置非常重要,在理解了它的工作原理之后,也很难说它到底是网络层协议,还是链路层协议,因为它恰恰串联起了网络层链路层。国外的大部分教程通常将 ARP 协议放在网络层。
  2. ARP 协议解决了什么问题,地位如何? ARP 协议,全称 地址解析协议(Address Resolution Protocol),它解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。
  3. ARP 工作原理? 只希望大家记住几个关键词:ARP 表、广播问询、单播响应

​ ARP 协议工作时有一个大前提,那就是 ARP 表

​ 在一个局域网内,每个网络设备都自己维护了一个 ARP 表,ARP 表记录了某些其他网络设备的 IP 地址-MAC 地址映射关系,该映射关系以 <IP, MAC, TTL> 三元组的形式存储。其中,TTL 为该映射关系的生存周期典型值为 20 分钟,超过该时间,该条目将被丢弃。

ARP 的工作原理将分两种场景讨论:

  1. 同一局域网内的 MAC 寻址
  2. 从一个局域网到另一个局域网中的网络设备的寻址

同一局域网内的 MAC 寻址

​ 假设当前有如下场景:IP 地址为137.196.7.23的主机 A,想要给同一局域网内的 IP 地址为137.196.7.14主机 B,发送 IP 数据报文。

再次强调,当主机发送 IP 数据报文时(网络层),仅知道目的地的 IP 地址,并不清楚目的地的 MAC 地址,而 ARP 协议就是解决这一问题的。

​ 为了达成这一目标,主机 A 将不得不通过 ARP 协议来获取主机 B 的 MAC 地址,并将 IP 报文封装成链路层帧,发送到下一跳上。在该局域网内,关于此将按照时间顺序,依次发生如下事件:

  1. 主机 A 检索自己的 ARP 表,发现 ARP 表中并无主机 B 的 IP 地址对应的映射条目,也就无从知道主机 B 的 MAC 地址。

  2. 主机 A 将构造一个 ARP 查询分组,并将其广播到所在的局域网中。

    ​ ARP 分组是一种特殊报文,ARP 分组有两类,一种是查询分组,另一种是响应分组,它们具有相同的格式,均包含了发送和接收的 IP 地址、发送和接收的 MAC 地址。当然了,查询分组中,发送的 IP 地址,即为主机 A 的 IP 地址,接收的 IP 地址即为主机 B 的 IP 地址,发送的 MAC 地址也是主机 A 的 MAC 地址,但接收的 MAC 地址绝不会是主机 B 的 MAC 地址(因为这正是我们要问询的!),而是一个特殊值——FF-FF-FF-FF-FF-FF之前说过,该 MAC 地址是广播地址,也就是说,查询分组将广播给该局域网内的所有设备。

  3. 主机 A 构造的查询分组将在该局域网内广播,理论上,每一个设备都会收到该分组,并检查查询分组的接收 IP 地址是否为自己的 IP 地址,如果是,说明查询分组已经到达了主机 B,否则,该查询分组对当前设备无效,丢弃之。

  4. 主机 B 收到了查询分组之后,验证是对自己的问询,接着构造一个 ARP 响应分组,该分组的目的地只有一个——主机 A,发送给主机 A。同时,主机 B 提取查询分组中的 IP 地址和 MAC 地址信息,在自己的 ARP 表中构造一条主机 A 的 IP-MAC 映射记录(不浪费信息)。ARP 响应分组具有和 ARP 查询分组相同的构造,不同的是,发送和接受的 IP 地址恰恰相反,发送的 MAC 地址为发送者本身,目标 MAC 地址为查询分组的发送者,也就是说,ARP 响应分组只有一个目的地,而非广播

  5. 主机 A 终将收到主机 B 的响应分组,提取出该分组中的 IP 地址和 MAC 地址后,构造映射信息,加入到自己的 ARP 表中。

在整个过程中,有几点需要补充说明的是:

  1. 主机 A 想要给主机 B 发送 IP 数据报,如果主机 B 的 IP-MAC 映射信息已经存在于主机 A 的 ARP 表中,那么主机 A 无需广播,只需提取 MAC 地址并构造链路层帧发送即可。
  2. ARP 表中的映射信息是有生存周期的,典型值为 20 分钟。
  3. 目标主机接收到了问询主机构造的问询报文后,将先把问询主机的 IP-MAC 映射存进自己的 ARP 表中,这样才能获取到响应的目标 MAC 地址,顺利的发送响应分组。

总结来说,ARP 协议是一个广播问询,单播响应协议。

不同局域网内的 MAC 寻址

​ 更复杂的情况是,发送主机 A 和接收主机 B 不在同一个子网中,假设一个一般场景,两台主机所在的子网由一台路由器联通。这里需要注意的是,一般情况下,我们说网络设备都有一个 IP 地址和一个 MAC 地址,这里说的网络设备,更严谨的说法应该是一个接口。路由器作为互联设备,具有多个接口,每个接口同样也应该具备不重复的 IP 地址和 MAC 地址。因此,在讨论 ARP 表时,路由器的多个接口都个各自维护一个 ARP 表,而非一个路由器只维护一个 ARP 表。

​ 接下来,回顾同一子网内的 MAC 寻址,如果主机 A 发送一个广播问询分组,那么 A 所在子网内的所有设备(接口)都将不会捕获该分组,因为该分组的目的 IP 地址在另一个子网中,本子网内不会有设备成功接收。那么,主机 A 应该发送怎样的查询分组呢?简而言之先找到能传给目标子网的接口的MAC地址,再由接口进行进一步的查询(这时就是内网中查找了)。

整个过程按照时间顺序发生的事件如下:

  1. 主机 A 查询 ARP 表,期望寻找到目标路由器的本子网接口的 MAC 地址。

    目标路由器指的是,根据目的主机 B 的 IP 地址,分析出 B 所在的子网,能够把报文转发到 B 所在子网的那个路由器。

  2. 主机 A 未能找到目标路由器的本子网接口的 MAC 地址,将采用 ARP 协议,问询到该 MAC 地址,由于目标接口与主机 A 在同一个子网内,该过程与同一局域网内的 MAC 寻址相同。

  3. 主机 A 获取到目标接口的 MAC 地址,先构造 IP 数据报,其中源 IP 是 A 的 IP 地址,目的 IP 地址是 B 的 IP 地址,再构造链路层帧,其中源 MAC 地址是 A 的 MAC 地址,目的 MAC 地址是本子网内与路由器连接的接口的 MAC 地址。主机 A 将把这个链路层帧,以单播的方式,发送给目标接口。

  4. 目标接口接收到了主机 A 发过来的链路层帧,解析,根据目的 IP 地址,查询转发表,将该 IP 数据报转发到与主机 B 所在子网相连的接口上。

    到此,该帧已经从主机 A 所在的子网,转移到了主机 B 所在的子网了。

  5. 路由器接口查询 ARP 表,期望寻找到主机 B 的 MAC 地址。

  6. 路由器接口如未能找到主机 B 的 MAC 地址,将采用 ARP 协议,广播问询,单播响应,获取到主机 B 的 MAC 地址。

  7. 路由器接口将对 IP 数据报重新封装成链路层帧,目标 MAC 地址为主机 B 的 MAC 地址,单播发送,直到目的地。

DNS(应用层)

参考路人zhang的面试手册,感谢路人zhang,可以关注他的同名微信公众号。

参考计算机网络核心知识点大梳理 | Java 程序员进阶之路 (tobebetterjavaer.com)

​ DNS的定义:DNS的全称是domain name system,即域名系统。DNS是因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的去访问互联网而不用去记住能够被机器直接读取的IP地址。

DNS是集群式的工作方式还是单点式的,为什么?

​ 答案是集群式的,很容易想到的一个方案就是只用一个DNS服务器,包含了所有域名和IP地址的映射。尽管这种设计方式看起来很简单,但是缺点显而易见,如果这个唯一的DNS服务器出了故障,那么就全完了,因特网就几乎崩了。为了避免这种情况出现,DNS系统采用的是分布式的层次数据数据库模式,还有缓存的机制也能解决这种问题。

工作原理

​ 主机向本地域名服务器的查询一般是采用递归查询(直接得到了返回),而本地域名服务器向根域名的查询一般是采用迭代查询(被踢皮球了,只能自己亲力亲为)

​ 简单来说,递归查询就是,小明问了小红一个问题,小红不知道,但小红是个热心肠,小红就去问小王了,小王把答案告诉小红后,小红又去把答案告诉了小明。迭代查询就是,小明问了小红一个问题,小红也不知道,然后小红让小明去问小王,小明又去问小王了,小王把答案告诉了小明。

​ 递归查询主机向本地域名发送查询请求报文,而本地域名服务器不知道该域名对应的IP地址时,本地域名会继续向根域名发送查询请求报文,不是通知主机自己向根域名发送查询请求报文。迭代查询是,本地域名服务器向根域名发出查询请求报文后,根域名不会继续向顶级域名服务器发送查询请求报文,而是通知本地域名服务器向顶级域名发送查询请求报文。

具体的流程如下(以www.baidu.com为例):

​ 1.在浏览器中输入www.baidu.com域名,操作系统会先检查自己本地的hosts文件是否有这个域名的映射关系,如果有,就先调用这个IP地址映射,完成域名解析。

​ ⒉.如果hosts文件中没有,则查询本地DNS解析器缓存,如果有,则完成地址解析。

​ 3.如果本地DNS解析器缓存中没有,则去查找本地DNS服务器,如果查到,完成解析。

​ 4.如果没有,则本地服务器会向根域名服务器发起查询请求。根域名服务器会告诉本地域名服务器去查询哪个顶级域名服务器

​ 5.本地域名服务器向顶级域名服务器发起查询请求,顶级域名服务器会告诉本地域名服务器去查找哪个权限域名服务器

​ 6.本地域名服务器向权限域名服务器发起查询请求,权限域名服务器告诉本地域名服务器www.baidu.com所对应的IP地址。

​ 7.本地域名服务器告诉主机www.baidu.com所对应的IP地址。

DNS记录

​ 这部分如果自己买了域名的话,到域名供应商那块配置DNS记录的时候会有更好的实践和了解。

1.A记录

​ A 是记录的类型,A 记录代表着这是一条用于解析 IPv4 地址的记录。

www.example.com.     IN     A     139.18.28.5;

​ 从这条记录可知,www.example.com的 IP 地址是 139.18.28.5。

2.CNAME(别名标识)

​ CNAME用于定义域名的别名,如下面这条 DNS 记录:

a.example.com.          IN     CNAME   b.example.com.

​ 这条 DNS 记录定义了 a.example.comb.example.com 的别名。用户在浏览器中输入 a.example.com 时候,通过 DNS 查询会知道 a.example.comb.example.com 的别名,因此需要实际 IP 的时候,会去拿 b.example.com 的 A 记录。

​ 当你想把一个网站迁移到新域名,旧域名仍然保留的时候;还有当你想将自己的静态资源放到 CDN 上的时候,CNAME 就非常有用。

3.AAAA记录

​ A 记录是域名和 IPv4 地址的映射关系。和 A 记录类似,AAAA 记录则是域名和 IPv6 地址的映射关系。

4.MX记录

​ MX 记录是邮件记录,用来描述邮件服务器的域名。在工作中,我们经常会发邮件到某个同事的邮箱。比如说,发送一封邮件到 xiaoming@xiaoflyfish.com,那么如何知道哪个 IP 地址是邮件服务器呢?

​ 这个时候就可以用到下面这条 MX 记录:

IN MX mail.xiaoflyfish.com

mail.xiaoflyfish.com 的 IP 地址可以通过查询 mail.xiaoflyfishcom 的 A 记录和 AAAA 记录获得。

5.NS 记录

​ NS记录是描述 DNS 服务器网址。从 DNS 的存储结构上说,Name Server 中含有权威 DNS 服务的目录。也就是说,NS 记录指定哪台 Server 是回答 DNS 查询的权威域名服务器

​ 当一个 DNS 查询看到 NS 记录的时候,会再去 NS 记录配置的 DNS 服务器查询,得到最终的记录。如下面这个例子:

a.com.     IN      NS      ns1.a.com.
a.com.     IN      NS      ns2.a.com.

​ 当解析 a.com 地址时,我们看到 a.com 有两个 NS 记录,所以确定最终 a.com 的记录在 ns1.a.comns2.a.com 上。从设计上看,ns1 和 ns2 是网站 a.com 提供的智能 DNS 服务器,可以提供负载均衡分布式 Sharding 等服务。

​ 比如当一个北京的用户想要访问 a.com 的时候,ns1 看到这是一个北京的 IP 就返回一个离北京最近的机房 IP。

​ 上面代码中 a.com 配置了两个 NS 记录。通常 NS 不会只有一个,这是为了保证高可用,一个挂了另一个还能继续服务。通常数字小的 NS 记录优先级更高,也就是 ns1 会优先于 ns2 响应。配置了上面的 NS 记录后,如果还配置了 a.com 的 A 记录,那么这个 A 记录会被 NS 记录覆盖。

DNS用TCP还是UDP?

参考域名系统 - 维基百科,自由的百科全书 (wikipedia.org) DNS用的是TCP协议还是UDP协议_lingshengxiyou的博客-CSDN博客_dns基于什么协议

​ DNS使用TCP和UDP端口53,即DNS既使用TCP也使用UDP。DNS在进行区域传输的时候使用TCP协议,其它时候则使用UDP协议;

​ DNS的规范规定了2种类型的DNS服务器,一个叫主DNS服务器,一个叫辅助DNS服务器。在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做区传送(zone transfer)。

为什么既使用TCP又使用UDP?

​ UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。通常传统的UDP报文一般不会大于512字节。

​ 区域传送时使用TCP,主要有一下两点考虑:

  1. 辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多
  2. TCP是一种可靠的连接,保证了数据的准确性。

​ 域名解析时使用UDP协议:

​ 客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。

其他问题

重定向和转发

参考什么是转发和重定向_涩即是Null的博客-CSDN博客_转发和重定向转发和重定向区别 - 三只小菜鸟 - 博客园 (cnblogs.com)

​ 1.转发主要发生在服务器内部(不能转到外面去),对浏览器来说实际上是一次请求。这个过程浏览器不知道是不是自己请求的服务直接完成的业务处理,只会知道,自己发起了请求,由原服务返回了请求结果。所以浏览器的地址是不会发生变化的。

​ 2.重定向和发送两个请求

​ 客户发送一个请求到服务器,服务器匹配servlet,servlet处理完之后调用了sendRedirect()方法,立即向客户端返回这个响应,响应行告诉客户端你必须要再发送一个请求,去访问student_list.jsp,紧接着客户端收到这个请求后,立刻发出一个新的请求,去请求student_list.jsp,这里两个请求互不干扰,相互独立,在前面request里面setAttribute()的任何东西,在后面的request里面都获得不了。可见,在sendRedirect()里面是两个请求,两个响应。(服务器向浏览器发送一个302状态码以及一个location消息头,浏览器收到请求后会向再次根据重定向地址发出请求)

区别总结如下:

​ 1、请求次数:重定向是浏览器向服务器发送一个请求并收到响应后再次向一个新地址发出请求,转发是服务器收到请求后为了完成响应跳转到一个新的地址;重定向至少请求两次,转发请求一次

​ 2、地址栏不同:重定向地址栏会发生变化,转发地址栏不会发生变化;

​ 3、是否共享数据:重定向两次请求不共享数据,转发一次请求共享数据(在request级别使用信息共享,使用重定向必然出错);

​ 4、跳转限制:重定向可以跳转到任意URL,转发只能跳转本站点资源

​ 5、发生行为不同:重定向是客户端行为,转发是服务器端行为

posted on 2022-09-29 22:06  Ari的小跟班  阅读(145)  评论(0)    收藏  举报