《网络是怎样连接的》笔记

date
Nov 9, 2022
slug
how-to-connect
status
Published
tags
计算机网络
summary
输入 URL 后到底发生了什么?
type
Post

简介

快速翻阅了一下这本书,内容主要是回答“浏览器输入URL到接收数据到底发生了什么”这个经典问题。
 

大体流程

notion image
  1. 浏览器输入URL,查询DNS服务器(基于UDP)获得目标IP地址,构造HTTP请求报文,将HTTP请求报文交给TCP/IP协议栈
DNS服务器,HTTP控制协议
  1. TCP模块收到HTTP报文后,按照TCP协议封装数据包,然后创建socket进行链接
socket通信,TCP协议(三次握手、四次挥手、可靠传输)
  1. TCP模块将TCP数据包交给IP模块,IP模块继续封装IP报文,然后通过网卡传输到路由器,由路由器进行转发
IP协议
  1. 路由器接力转发,直到进入服务器端的网络中(入口路由器
  1. 防火墙过滤数据包,经过缓存服务器
这里缓存服务器的操作和HTTP字段有关
  1. 数据包进到服务器(实际上是通过socket拿到数据),先是暂存在网卡缓存里面,等到CPU进行读取
  1. CPU读取后,交给IP模块处理数据,此时根据协议号来决定交给UDP还是TCP(假设是TCP)
  1. TCP收到数据报文后,根据首部字段决定操作
如果SYN=1,则是建立连接的请求,进行三次握手 如果FIN=1,则是断开连接的请求,进行四次挥手 剩下的情况是数据传输过程的数据包,接收并处理
  1. TCP会将处理好的数据包交给HTTP模块,HTTP处理数据包,然后交给服务器程序进行处理
  1. 服务器处理完毕之后,将内容封装成HTTP数据包,然后用同样的方式返回给客户端
下面还是从浏览器输入URL开始,到接收数据结束,详细讲解一下每一步发生了什么。

浏览器 —— 输入URL,构造HTTP数据包

当我们输入URL之后,实际上构造的是HTTP数据包
URL是统一资源定位符的一种,它用来表示目标服务器上的一个资源,这个资源可能是网页,可能是图片、视频。
notion image
例如我们输入一个网址,实际上对应的是目标服务器中的file1.html这个网页
notion image
确定了URL之后,我们会根据用户发起的请求构造HTTP数据包。
HTTP数据包总的来说就是两部分:
  1. 控制信息 —— 比如头部字段、请求方法、状态码等这些描述数据包信息的字段
  1. 数据 —— 需要传输的数据
notion image
而根据包的传输方向,可以分为:
  1. 请求消息
  1. 响应消息
至此HTTP数据包构造完成,我们需要向目标URL的web服务器发送数据、
发送数据靠的是IP地址而不是URL,因此我们首先需要获取目标服务器的IP地址

客户端查询DNS服务器 —— 获取目标IP地址

当用户在浏览器输入网址时,浏览器首先会去DNS服务器查询域名对应的IP地址
DNS服务器就像在远端维护的一个hashmap,key是URL,value是IP地址
这一步可能涉及到多个DNS的接力查询。
(所谓DNS污染就是在这一步破坏查询结果,如果我们拿到一个不正确的IP地址,自然没有办法进行通信了)
notion image
这一步是通过UDP协议查询得到结果,当我们查询到IP地址的时候,我们就可以准备发送数据了,接下来我们需要委托协议栈帮助我们发送数据

TCP/IP协议栈与socket —— 概念

notion image
这里协议栈指的是TCP、UDP、IP等这些网络通信协议组成的模块。
协议栈定义了一系列的通信规则,比如TCP的超时重传,建立连接、断开链接等规定,IP的校验码等。
这些通信规则相当于约定,当我们双方都遵守约定的时,就能达到某种通信效果。
比如TCP可以在不可靠的网络环境下建立起可靠的通信信道。
表面上我们使用协议栈就进行通信,但是实际上我们还需要socket(套接字)的帮助。
notion image
形象一点的比喻是:TCP/IP协议是电,socket是插座。当一个管道两端都插上插座,电才能在管道中传输。
这个比喻背后蕴含的意思是:TCP/IP等协议是对数据内容的抽象,规定数据内容供双方通信;而socket是通信实际实现的工具,没有socket我们无法传播信号。
明白了协议栈和socket的概念后,我们来看看它们是怎么运行的。

TCP/IP协议栈的工作流程

TCP/IP协议栈的工作是复杂的,下面我从一个数据包的流动来描述整个过程,中途涉及到的知识点后面再具体展开。

构造数据包

首先我们介绍数据包如何构造:
notion image
当我们把HTTP数据包构造完成后,首先交给TCP模块。
TCP模块会根据实际情况将数据进行拆分,然后写入自己的头部控制信息,这一步是在构造TCP数据包,当TCP数据包构造完成后,会交给IP模块处理。
IP模块收到TCP数据包后,会添加IP协议的头部控制信息。
这里涉及到两个长度:
  1. MTU:IP数据包的极限大小,一般是1500字节
  1. MSS:IP数据包最大的数据量,一般是1460字节,40字节是IP头部信息的占用大小。
notion image
直到这里我们传输用的数据包就构造完成了。

传输数据

notion image
我们将数字信号的数据包交给网卡,然后网卡会将数据发送给路由器(中途可能有集线器和交换机,还有传输依赖的网线,这里忽略掉它们的工作),路由器多次转发后,可以找到服务器所在的网络,然后根据IP地址转发至web服务器

web服务器处理

这里web服务器整体收到数据包的过程是这样的:
  1. 防火墙过滤
  1. 如果有缓存服务器,经过缓存服务器
  1. web服务器网卡收到数据包,检查IP数据包是否出错,没有出错的话通知CPU读取
  1. CPU读取后,根据IP数据包的协议号交给TCP或者UDP模块处理,这里HTTP依赖TCP,所以交给TCP梳理
  1. TCP模块收到数据包后会检查头部字段是否正常,如果正常,交给web服务器软件进行处理。常见的web服务器软件有tomcat、jetty等。
  1. web服务器软件读取到头部信息和数据,针对要求执行具体操作,比如说HTTP GET操作或者HTTP POST操作,这一部分的处理方法是我们写的业务代码。
notion image
当我们处理完毕后,会按照反过来的顺序进行传输,最终就能传输到客户端的浏览器里面,然后浏览器会根据数据进行相应处理。

TCP/IP协议栈的具体细节

上面对TCP/IP协议栈的工作流程做了简要的叙述,这里我们拆开来仔细看每一步到底是怎么工作的。
我们都知道,TCP协议建立的传输有三个结果:
  1. 三次握手
  1. 信号传输
  • 这里涉及到拥塞控制,可靠传输等知识
  1. 四次挥手
下面我们详细描述一下流程

TCP的控制字段

之前提到协议栈里面的数据包,其实都可以笼统的看为两部分的内容:
  1. 控制信息
  1. 数据
控制信息是对要传输的数据进行描述,TCP的控制信息如下图:
notion image
我们需要注意的地方是:
  1. 发送方端口号,接收方端口号
  1. 序号,ACK号
  1. 控制位
  • 其中ACK、SYN、FIN是常见的控制位
  1. 窗口

三次握手

notion image
三次握手
三次握手大家都很熟悉了
  1. 客户端发送SYN = 1的TCP数据包,请求建立链接
  1. 服务器收到后,返回SYN=1,ACK=1的数据包,表示同意建立链接
  1. 客户端收到后,返回ACK=1的数据包,表示收到同意
经过三个步骤,一个可靠的传输信道就建立起来了,接下来就可以进行数据传输了。
然而这里面有几个问题
TCB是什么?为什么是三次握手?SYN-SENT和SYN-RCVD是什么?
TCB是什么?
TCB(Transmission Control Block,传输控制块)是TCP用来传输数据的数据结构,它的组成是两个部分:
  1. socket,读写数据,传输
  1. 缓冲区,存放数据
我们所谓的通信,实际上是TCB之间的通信。
为什么要三次握手?
这部分可以参考下面的文章,写得很好,这里不赘述了。简单来说有以下三个特点:
  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

信号传输

这里有三大部分:
  1. 消息的确认
  1. 消息重传和滑动窗口
  1. 拥塞控制

消息的确认

我们都说TCP是可靠的连接,那么TCP到底如何实现可靠连接?
实际上这和序列号和TCP控制字段里面的ACK有关。
notion image
notion image
上面这张图描述了ACK和序列号到底如何工作
  1. 客户端 => 服务端
  • 当我们传输一个序号为1,长度1460的数据包时。服务端如果收到,会返回一个ACK=1461的数据包给客户端,表示1461字节之前的数据都已经收到了
  1. 客户端 => 服务端
  • 客户端收到ACK=1461的数据包后,继续传输接下来的字节
    • ...
如此,我们就能通过ACK和序列号来进行消息传输的确认
这里的序列号是从1开始, 这种实际上是不安全的,因为外界可以根据序列号和ACK号分析客户端和服务器的通信情况
notion image
所以我们回过头来看这幅图,注意里面的seq字段
这个字段是我们双方约定的随机开始的序列号,x是客户端约定的起点,y是服务端约定的起点,而ack是对起点的确认
这里我们就能避免上述不安全的情况。

消息重传和滑动窗口

上面提到的传输是理想的情况,实际传输的时候可能会发生丢失,这个时候就需要重传机制了。
notion image
实际上传输并不是一来一回,而是通过双方维护的一个滑动窗口进行传输
对于客户端:发送滑动窗口内的数据,当收到ACK=x的时候,滑动窗口的左端移动到x+1的位置
对于服务器:接收滑动窗口内的数据,当x之前的数据都已经收到的时候,返回ACK=x的数据包
注意我们服务器是收到x之前的所有数据才会返回ACK=x,这样能确保数据的连续性。
那么我们ACK丢失的情况呢?TCP应该怎么处理?
实际上这涉及到重传机制了,重传分为四种
  1. 超时重传
  1. 快速重传
什么是超时重传?
ACK数据包丢失了,此时客户端并不知道对方是否收到数据,所以客户端会等待一段时间,超过了这个时间后会重新发送数据包
那么等待多久比较合理呢?
这里涉及到两个概念:
RTO (Retransmission Timeout 超时重传时间)
RTT 就是数据从网络一端传送到另一端所需的时间,也就是包的往返时间。
RTO的值会略大于RTT,而它们具体的值会由网络环境决定
什么是快速重传?
存在一种情况,ACK数据包没有丢失,但是服务器反复发送
当客户端收到3次相同ACK=x的时候,立即重传ACK=x+1的数据包

拥塞控制

拥塞控制是指我们应当有节制的发送数据包到网络环境中,因为网络环境是公用的,一个地方阻塞必然造成整个网络的阻塞,TCP针对这种情况设计了阻塞机制。
这里涉及到两个重要参数,一个是cwnd即滑动窗口大小,另一个是ssthresh窗口的临界值,分别有四个要点:
  1. 慢启动
  • cwnd = 1,指数速度增长
  1. 拥塞避免
  • cnwd >= ssthresh时,cwnd += 1
  1. 快重传
  • 当客户端收到3次相同ACK=x的时候,立即重传ACK=x+1的数据包
  1. 快恢复
  • 发送拥塞时,ssthresh /=2 ,cwnd /= ssthresh,然后执行拥塞避免算法

四次挥手

notion image
当我们数据传输完毕后,TCP将会断开连接。
这里断开链接的过程是四次挥手,过程以上图为例:
  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态
  1. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
  1. 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  1. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认
  1. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态
  1. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
MSL是Maximum Segment Lifetime,一个数据分片(报文)在网络中能够生存的最长时间,一个MSL大概2分钟的时间。
为什么客户端还需要等待2MSL的时间呢?
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

从socket的角度看TCP/IP的工作

终于到了最后的部分,我们TCP/IP协议的工作是离不开socket的,之前提到的半连接、全连接也是和socket有关的状态。
notion image
回过头来看这张图,当我们申请进行TCP通信的时候,实际上背后的工作者是socket
我们一个tcp连接标识为<clientIP, clientPort, serverIP, serverPort>
其中socket就是用来监听端口号和通信的。

客户端的socket

notion image
首先客户端通过socket函数创建一个socket,这个socket会随机分配一个端口号
然后使用connect去连接web服务器,这里需要的参数是<serverIp, serverPort>,默认的HTTP端口号是80,而IP是我们从DNS服务器里面获取的
如果建立完毕,那么就可以开始传输了
使用write写入数据,使用read读取数据
最后关闭连接时使用close断开

服务端的socket

服务端的socket稍微有点不同
首先socket分为监听socket和连接socket,我们大家都知道socket是用来监听端口的。
比如说我们创建一个web服务,监听8080端口,那么此时web服务器是创建一个监听socket去监听8080端口
然后当请求进来的时候,监听socket收到请求申请,那么就会通知CPU,CPU就会分配新的连接socket和缓存(加一起就是TCB)去监听8080端口,真正实际通信的是该连接socket。
这样一个端口是可以被多个socket监听的,它们辨识的方法就是客户端的IP地址和端口号,用这两个信息来区分一个端口号到底是哪个socket来执行通信。
notion image
明白了上述概念,我们再来看服务器端的socket如何操作。
  1. 首先通过socket来创建一个socket
  1. 通过bind来绑定指定端口
  1. 使用listent来将socket转换为监听socket
  1. 调用accpect等待客户端发起连接请求,此时获得的描述符2是连接socket,我们交给通信模块处理
  1. 使用writeread来通信
  1. 使用close来结束通信

三次握手中的socket

以下内容参考:
介绍了socket的基本使用后,我们来看三次握手中的socket和资源分配吧
notion image
半连接队列与全连接队列
  1. 首先服务端进行bindlisten调用,创建监听socket
  1. 然后客户端创建socket后,执行connect建立连接,发送SYN=1的数据包
  1. 服务器收到SYN=1的数据包后,会将该连接加入到半连接队列(SYN队列),然会返回SYN+ACK给客户端
  1. 当服务器再次收到ACK的时候,会把连接从半连接队列里面取出来,加入到全连接队列
  1. 然后服务器调用accept可以从全连接队列中取出连接进行处理
notion image

总结

本文简单的回答了浏览器输入URL到返回数据到底发生了什么这个问题.
中间涉及到TCP/IP的通信过程和socket等内容。

参考

 

© hhmy 2019 - 2024