模拟局域网中有网线的主机向外网服务器发送 HTTP 请求,获取
dzapathy.github.io
页面
浏览器和服务器是应用层程序,使用 HTTP / HTTPS 协议进行网络通信,HTTP 的底层协议是 TCP 协议。为了能够进行通信,首先需要知道源 IP 地址和目的 IP 地址
源 IP:DHCP
假设主机没有 IP 地址以及相关信息,需要使用 DHCP 协议获取 IP 地址
- 主机发送 DHCP 发现报文
- 主机应用层生成 DHCP 发现报文,并向下传送给传输层
- 主机传输层将 DHCP 发现报文封装成 UDP 报文段,源端口号是 68,目的端口号是 67,并向下传送给网络层
- 主机网络层将 UDP 报文段封装成 IP 数据报,源 IP 地址是 $0.0.0.0$,目的 IP 地址是 $255.255.255.255$,向下传输给数据链路层
- 主机数据链路层将 IP 数据报封装成数据帧,源 MAC 地址是主机 MAC 地址,目的 MAC 地址是 $FF:FF:FF:FF:FF:FF$,并发送给物理层
- 主机物理层以比特流的方式传输数据帧,将数据帧发送到交换机
- 交换机将主机的 MAC 地址及对应的接口记录在交换表中,然后在交换机的所有出口广播这个数据帧
- DHCP 服务器(路由器可以作为 DHCP 服务器)收到广播的数据帧,拆开首部和尾部得到 IP 数据报并向上传送给网络层
- DHCP 网络层拆开 IP 数据报首部得到 UDP 报文段,并将其发送给传输层
- DHCP 传输层拆开 UDP 数据报得到 DHCP 报文,并将其发送给应用层
- DHCP 应用层服务器得到 DHCP 发现报文
- DHCP 服务器发送提供报文
- DHCP 应用层生成 DHCP 提供报文,包含 IP 地址、子网、网关 IP 和 DNS 服务器 IP 信息,并向下传送给传输层
- DHCP 传输层将 DHCP 提供报文封装成 UDP 报文段,源端口号是 67,目的端口号是 68,并向下传送给网络层
- DHCP 网络层将 UDP 报文段封装成 IP 数据报,源 IP 地址是 DHCP 服务器 IP 地址,目的 IP 地址是 $255.255.255.255$,向下传输给数据链路层
- DHCP 数据链路层将 IP 数据报封装成数据帧,源 MAC 地址是DHCP MAC 地址,目的 MAC 地址是 $FF:FF:FF:FF:FF:FF$,并发送给物理层
- DHCP 物理层以比特流的方式传输数据帧,将数据帧发送到交换机
- 交换机将 DHCP 服务器的 MAC 地址及对应的接口记录在交换表中,然后在交换机的所有出口广播这个数据帧
- 主机收到广播帧,依次经过数据链路层、网络层、传输层、应用层得到 DHCP 提供报文
- 主机发送 DHCP 请求报文
- 源端口号是 68,目的端口号是 67,源 IP 地址是 $0.0.0.0$ ,目的 IP 地址是 $255.255.255.255$,依次经过应用层、传输层、网络层、数据链路层和物理层发送给 DHCP 服务器,DHCP 服务器经过数据链路层、网络层、传输层、应用层得到 DHCP 请求报文
- DHCP 服务器发送 DHCP 确认报文
- 源端口号是 67,目的端口号是 68,源 IP 地址是 DHCP IP地址,目的 IP 地址是 $255.255.255.255$
- 主机收到 DHCP 确认报文,得到自己的 IP 地址、子网掩码、DNS 服务器 IP 地址和网关 IP 地址
至此,主机获得 IP!
网关 MAC: ARP
假设主机没有网关路由器的 MAC 地址的 ARP 高速缓存
主机在局域网中,要访问局域网之外的主机需要通过网关路由器来进行访问。由于局域网内的 IP 地址可能比较紧缺,所以使用 DHCP 协议进行动态主机配置。DHCP 是有租期的,也就是说 IP 地址可能会改变,所以在局域网内使用 MAC 地址作为主机的唯一标识。对于主机来说,唯一不变的是 MAC 地址。所以要获取网关服务器的 MAC 地址
- 主机应用层生成 ARP 查询报文,源 IP 地址是主机 IP 地址,目的 IP 地址是主机的默认网关路由器 IP 地址。经过传输层、网络层、数据链路层和物理层将数据帧发送给交换机
- 交换机将数据帧广播出去
- 网关路由器接收到数据帧,层层解析得到 ARP 查询报文,发现 ARP 查询报文的目的 IP 地址与自己某个网络接口 IP 地址匹配,发送 ARP 应答报文,报文中包含网关 MAC 地址
至此,主机能够与默认网关进行通信!
转换 IP: NAT
假设主机 IP 地址是私有 IP 地址
如果在局域网内部使用私有地址就无法与外部网络进行通信。为了保证通信,需要在网关处使用网络地址转换协议 NAT 将私有地址转化为公有地址
- 在 NAT 路由器转换表中记录
公有IP,新端口号
和源IP,源端口号
的映射关系,转换方式包括静态转换、动态转换和端口多路复用等 - 内网向外网发数据:根据 NAT 转换表,利用
公有IP,新端口号
替换每个外出 IP 分组的源IP,源端口号
- 外网向内网发数据:根据 NAT 转换表,利用
源IP,源端口号
替换每个 IP 分组的公有IP,新端口号
至此,主机能够通过默认网关与外网进行通信!
目的 IP:DNS
假设主机没有 DNS 缓存,并且 DNS 查询使用迭代查询
- 首先主机将查询发送给本地域名服务器
- 本地域名服务器无法解析域名时,本地域名服务器作为代理转发查询访问根域名服务器
- 请求在网络核心通过路由器进行路由转发,由于路由器实现了内部网关协议(RIP,OSPF)和外部网关协议(BGP),所以请求能够正确的到达 DNS 域名服务器
- 根域名服务器无法解析域名时,返回顶级域名服务器
io
- 本地域名服务器查询顶级域名解析服务器
io
- 顶级域名解析服务器无法解析域名时,返回权威域名服务器
github.io
- 本地域名服务器查询权威域名服务器
github.io
- 权威域名服务器
github.io
返回dzapathy.github.io
的 IP 地址 - 本地域名服务器将
dzapathy.github.io
的 IP 地址返回给客户端
至此,主机获得目的 IP 地址!
建立连接:TCP 三次握手
现在已经获得了源 IP 地址,源端口号,目的 IP 地址和目的端口号,就可以进行 TCP 三次握手了
- 服务器一直处于 LISTEN(监听)状态,等待客户的连接请求
- 客户端向服务器发送请求报文,SYN=1,ACK=0,随机初始化序号 x
- 服务器收到请求报文,如果同意建立连接则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,随机初始化序号 y
- 客户端收到服务器的确认报文后进行第三次握手,确认号为 y+1,序号为 x+1
- 服务器收到客户端的第三次握手后,连接 TCP 建立
至此,主机和服务器建立了 TCP 连接,能够进行 HTTP 通信!
请求:HTTP
假设服务器使用 SpringMVC
- 主机发送 HTTP GET 请求报文,经过传输层、网络层、数据链路层和物理层交付给服务器
- 服务器层层拆分得到 HTTP GET 请求报文
- 服务器生成 HTTP 响应报文,将页面数据放入报文实体部分并发送给主机
- SpringMVC 前端控制器接收请求,并将请求发送给处理器映射器
- 处理器映射器根据 URL 查找处理器,并将处理器返回给前端控制器
- 前端控制器将处理器发送给处理器适配器,通过处理器适配器访问处理器
- 执行处理器
- 处理器会返回一个
ModelAndView
对象给处理器适配器 - 处理器适配器将
ModelAndView
返回给前端控制器,包括数据和视图 - 前端控制器向视图解析器发送视图解析请求
- 视图解析器返回视图对象
- 前端控制器进行视图渲染,将
model
转行成responce
- 浏览器收到 HTTP 响应报文,使用浏览器渲染页面,最后显示页面
至此,主机获得请求页面!
断开连接
HTTP/1.1 之前默认是短连接的,一旦服务器发送完数据就会主动断开 TCP 连接,进行 TCP 四次挥手。HTTP/1.1 默认是长连接,不会立刻断开连接,可以设置 keep-alive 的 timeout,当到 timeout 后仍然没有数据传输就断开连接