Network
一、以浏览器的一次请求开始
概述:主要是讲解浏览器 解析网址、生成HTTP请求消息、委托协议栈发送消息等步骤。
当浏览器输入一个地址,会有如下几步:
- 解析地址,生成请求消息
- 如何解析?
- 请求消息长什么样?
- 向DNS服务器查询Web服务器的IP
- 获得IP地址是浏览器的职责
- 委托操作系统发出请求前,需要提供对方IP
- DNS服务器如何得到域名对应IP?
- 委托协议栈发出请求
解析地址
URL & URI
URL(Uniform Resource Locator)
和URI(Uniform Resource Identifier)
都是用于标识互联网上资源的标准方法;
URI是一个用于标识某一互联网资源的字符串,URL是URI的子集,提供找到该资源的路径。
URL元素: http: // Web服务器名称 / 目录名 / …… / 文件名
其中目录名、文件名都可以省略,
当文件名被省略时,如https://www.baidu.com/,可以理解为后面的文件名被省略了,服务器会设置一个默认文件名(如index.html)
请求消息
请求行: <方法> <空格>
消息头(可以多行):<字段名>:<字段值>
<空行>
<消息体>
如果响应里包含图片标签(其他的也一样),会进一步解析,发起更多的请求,并渲染在页面上。
响应消息
这里提一下响应消息
状态行:<HTTP版本><空格><状态码><空格><响应短语>
消息头:<字段名>:<字段值>
<空行>
<消息体>
状态码
- 100 告知请求处理进度和情况,没怎么见过
- 200 成功
- 3xx 需要进一步操作
- 4xx 客户端错误
- 5xx 服务器错误
DNS查询IP
先了解一下TCP/IP的基本思路。
一些小的子网,通过路由器连接起来,组成一个大的网络。
这里子网可以理解为用集线器连接起来的几台计算机,将它看作一个单位,称为子网。
网络中,所有设备都会被分配一个地址,相当于“xx号xx室”,其中号被分配给整个子网,室分配给子网中的计算机。
这里“号”称为网络号,“室”称为“主机号”,这个地址整体称为IP地址。
通过IP地址我们可以判断出对象服务器的位置,从而发送消息到正确的位置。
消息传送的具体过程概述:
发送者发出的消息先经过子网中的集线器,转发到距离最近的路由器上,
路由器会根据消息的目的地判断下一个路由器的位置,发送到下一个路由器,不断重复这个过程,把消息送达目的地
IP地址
实际IP地址是一串32比特的数字,按照8比特为一组分为4组。
仅凭一串数字无法区分哪部分是网络号,哪部分是主机号,我们还需要一个子网掩码。
格式上是一串32比特的数字,左边全部是1,右边全部是0,其中为1的部分表示主机号
可以简写成数字,表示左边有多少个1
主机号部分全部为0,表示整个子网而不是某个设备
主机号部分全部为1,表示向子网所有设备发送包,即广播
使用Socket库查询IP地址
socket库提供了查询DNS的函数,可以直接调用,可以认为是 ip = gethostbyname("www.baidu.com")
库的具体实现也是需要委托操作系统协议栈来发出网络请求
DNS服务器
DNS维护了一个域名和IP的对照表(实际上DNS会更复杂一些,有一个类型的概念,但我们可以简单认为都是Web服务器在查)
接收到请求后,从表里查找相应的记录并返回。
但是世界上的服务器信息,不可能放在一台DNS服务器中(真放不下
这个时候看DNS服务器是如何处理的?
简单来说,是把信息放在不同的服务器中,通过接力配合查找出对应的信息。
要理解这个机制具体怎么运行,要先了解域名的层次结构:
DNS的域名通过句点来分割,每个层级都被称为域,越靠右的位置表示层级越高,这种具有层次结构的域名信息会注册到DNS服务器中。
一台DNS服务器会管理一个或多个域,由此DNS服务器也具有和域名一样的层次结构,每个域的信息都存放在相应层级的DNS服务器中。
管理一个域的DNS服务器会把自己的信息注册到管理上一级域的DNS服务器中
最上层的DNS服务器称为根域,所有DNS服务器都会记录根域的信息
具体如何运行呢:
- 客户端访问某一台DNS服务器
- 这台DNS服务器会请求到根域服务器
- 这台DNS服务器从根域服务器一路找到下层的某台目标DNS服务器,获得IP地址
- 这台DNS服务器把对应的IP地址返回给客户端
DNS服务器有缓存功能,可以记住之前查询过的域名,加速查找(冷热数据的一种应用)
委托协议栈发出请求
创建套接字并连接,就是著名的三次握手。
- 客户端创建套接字
- 将自己的套接字与服务端的套接字连接起来,形成管道
- 需要指定刚创建的套接字、服务器IP地址、端口号
- 收发数据
- 发送完成后,断开管道并删除套接字
- 一开始是服务端来断开,服务端判断这次请求内容发完了就断开
- 后面发现一次连接只传输一次内容 太低效了。于是设计出一次连接收发多个请求和响应的方法,当所有数据都请求完成后,客户端会主动断开连接
补充一点服务器这边套接字的内容
服务器包含两类套接字:
- 监听套接字
- 服务端首先创建一个监听套接字,这个套接字绑定到特定的IP地址和端口上。
- 这个套接字用来监听来自客户端的连接请求。
- 连接套接字
- 当服务端的监听套接字接受(accept)一个来自客户端的连接请求时,它会为这个特定的连接创建一个新的套接字,我们称之为“连接套接字”。
- 这个连接套接字是唯一的,专门用于服务器和该特定客户端之间的通信。
- 每个连接套接字都有一个独特的套接字对(Socket Pair),包括:客户端的IP地址和端口号,服务器的IP地址和端口号。
所以不同客户端实际上连接的不是同一个套接字。
即使不同客户端连接到服务端的同一个IP地址和端口,服务端也能通过不同的连接套接字来区分和管理不同的客户端连接。
二、TCP/IP & 协议栈
概述:讲解协议栈如何处理数据发送请求
创建套接字
协议栈内部结构
协议栈包含TCP、UDP、IP
TCP和UDP 接受应用程序的委托执行收发数据的操作
IP 负责把网络包发送给通信对象
套接字是什么
套接字记录了用于控制通信操作的各种控制信息。例如 客户端的IP、端口 服务端的IP、端口
协议栈根据这些信息判断下一步的行动。
win中可以用 netstat
命令显示套接字的内容
连接服务器
通信双方交换控制信息,在套接字中记录这些信息。
还要分配一块用于临时存放收发数据的内存空间,称为缓冲区。
补充一些关于控制信息的。
有一些控制信息不仅连接时需要,数据收发和断开连接都需要。
所以这些信息会被添加在客户端和服务器之间传递的网络包的开头。连接阶段没有实际数据,只有控制信息。
这种控制信息被称为头部。
以太网、IP协议都有自己的控制信息,也叫头部。为了区分,我们会分别记作TCP头部、以太网头部、IP头部
客户端和服务器在通信中会将必要信息记录在头部并互相确认。
理解了头部各个字段的含义,才能理解通信的过程。
具体的操作
- 在TCP模块创建表示连接控制信息的头部 【SYN=1】
- TCP头部创建好以后,把信息传递给IP模块并委托它发送
- 请求通过网络到达服务器,IP模块把接受到的请求传递给TCP模块。TCP模块找到对应的套接字并写入相应信息(这块可以看上面,实际是新建一个套接字用来负责客户端的请求)
- 服务器返回响应【SYN=1 ACK=1】
- 请求通过网络到达客户端,套接字中写入服务器的IP地址、端口号信息。返回一次响应【ACK = 1】
其实就是双方各自确认对方可以收到自己的信息。
就是
A:歪歪歪,能听到不
B:能听到 你呢
A:可以
收发数据
应用程序调用write
将要发送的数据交给协议栈
- 协议栈不关心应用程序传来的数据是什么内容。对于协议栈都是一定长度的二进制字节序列
- 协议栈不会一收到数据就发送出去,而是会存放在内部的发送缓冲区,等待后面的数据一起传送
- 因为一次将多少数据交给协议栈是应用程序自行决定的。有些应用程序会小段传送数据,这种情况下如果一收到数据就发送,在网络中会出现大量小包,导致网络效率下降。
- 因此会积累一定数量的数据才会发送,这个判断标准不同的操作系统会有不同,但是都是由以下要素来判断的
- MTU:每个网络包的长度(包含头部),去除头部叫MSS,收到数据长度接近MSS时发送出去,就可以避免大量小包的问题
- 时间:避免为了等待导致发送延迟
交给协议栈后
较大的数据会进行拆分。TCP会拆分数据,给每块数据前面加上TCP头部,交给IP模块来执行发送数据的操作