一、HTTP DNS
1. HTTP请求消息
HTTP协议定义了客户端和服务端之间交互的信息内容格式和步骤,请求消息主体主要包含:
- 目标URL
- 请求方法
HTTP消息格式:
<方法> <URL> <HTTP版本>
<字段名1>:<字段值1>
<字段名2>:<字段值2>
...
<消息体>
<消息体> 即客户端向服务端发送的数据,例如POST方法所带的表单数据
HTTP 状态码:
- 1xx 告知处理进度与情况
- 2xx 成功
- 3xx 需进一步操作
- 4xx 客户端错误
- 5xx 服务端错误
2. DNS查询Web服务器IP
(1) IP地址
子网通过路由器相连,形成一个网络。在网络中,每个设备会有一串IP地址,这是一串32bit的数字,其中包含网络号和主机号,但这两部分的具体结构不固定。
- 子网掩码:用于区分何为主机号,何为网络号。这个边界可以划在句点位置,也可以划在字节的中间位置
- 主机号部分的bit全部为0:表示整个子网;全部为1:表示广播
(2) DNS解析
根据域名查询IP时,浏览器会使用Socket库中的解析器来向DNS服务器发送查询消息,其响应消息中包含查询到的IP地址,解析器会将其写入浏览器指定的内存地址中。
解析器本身不具备使用网络收发数据的功能,也要委托给操作系统的协议栈执行。
(3) DNS服务器
DNS客户端的查询消息包含:域名、Class(IN)、记录类型(A/MX/PTR/CNAME/NS/SOA...)
IP地址保存在A记录中,邮件服务器保存在MX记录中。客户端需要提供@后面的一串字符,DNS服务器会返回一个单数值和服务器域名,数值表示优先级,数值较小的邮件服务器更优先.
域名通过句点分隔,同一层级的部分被称为域。比如com一般为最高域。一个域的信息是作为一个整体存放到DNS服务器中的一台DNS服务器可以存放多个域的信息。 一般情况下假设一台DNS服务器只存放一个域的信息,这样DNS服务器也像域名一样有了层次结构.
在现实中上下级的域可能共享同一个DNS服务器,查询过的域名会被缓存,“不存在”这一个响应结果也会被缓存
3. 委托协议栈发送消息
收发数字信息不仅限于浏览器,也适用于任何网络应用程序。Socket库作为一个桥梁,将数据收发操作委托给OS协议栈执行。
浏览器、邮件等应用程序收发数据时使用TCP;DNS查询等收发较短的控制数据使用UDP。
收发数据的主要阶段:
- 创建套接字(socket())。创建后协议栈会返回一个描述符,用于识别不同的套接字
- 连接服务器(connect(描述符, IP, port))
- 收发数据(write(描述符, 数据), read(存放消息的内存地址))
- 断开连接并删除套接字(close())
注意,管道是否断开,在不同的HTTP版本中不同
二、TCP/IP,UDP
1. 创建套接字
套接字的实体就是通信控制信息,包含通信对象的IP、port、通信操作的进行状态、PID等等等等。创建套接字时会分配存放一个套接字所需的内存空间,相当于为控制信息的保存准备一个容器,还需要向这个内存空间中写入初始状态。然后将表示这个套接字的描述符返回给应用程序。
<描述符> = socket(...)
描述符可以认为是套接字的引用,用于应用程序与协议栈之间的收发数据委托,这样协议栈就能获取相关控制信息。
协议栈在执行操作时需要参阅套接字中的控制信息来判断下一步的行动,这也是套接字的作用。
2. 连接服务器
连接实际上是通信双方交换控制信息。控制信息有两类:
- 通信时位于网络包开头的头部。通信时需要交换的就是这类信息。
- 保存在套接字中用于控制协议栈操作的信息。这类信息不需要交换。例如:
- 应用程序传递来的信息
- 从通信对象接收到的信息
- 收发数据操作的执行状态
实际的连接:
- 在TCP模块创建头部,通过TCP头部与找到要连接的套接字,头部SYN比特设为1并设置设置客户端的序号初始值,传递给IP模块发送网络包。服务器收到请求并返回响应时将 ACK 控制位设为 1,表示已经接收到相应的网络包。
- 服务器TCP模块接受连接时设置SYN比特为1并设置服务端的序号初始值,不接受时设置RST比特为1,返回的响应中带有服务端告知客户端的ACK号。客户端相应的将ACK比特设为1并准备发回服务器。
- 客户端发回确认信息,即ACK号。
connect(<描述符>, <IP, port>, ...)
3. 收发数据:connect之后的数据收发
关键概念:
- MTU(Maximum Transmission Unit):最大传输单元,数据 + IP header + TCP header,一个网络包能容纳的最大数据长度
- MSS(Maximum Segment Size):去除头部后的网络包长度,即数据长度。
- “序号”:发送方告知接收方该网络包发送的数据相当于所有发送数据的第几个字节,用于接收方确认。序号一般不是从1开始的(避免攻击)
- ACK号(acknowledge):接收方告知发送方已收到所有数据的第几个字节,用于发送方确认。
- 窗口:接收方告知发送方的无需等待即可一起发送的数据量。等待ACK号的这段时间可以继续发送后面的包(可以理解为异步)以提高效率,这一方式也称为“滑动窗口”。
- 为了避免出现发送的包太多接收方处理不过来的情况,接收方需要告诉发送方窗口大小,然后发送方根据这个值对数据发送操作进行控制,接收方的缓冲区的空间被部分释放后会将新的窗口大小告知发送方。
- 更新窗口的时机是接收方从缓冲区取出数据传递给应用程序的时候,接收方在发送ACK号或者窗口更新时会等待一段时间,以合并多个通知,从而减少包的数量。例如 ①合并ACK号和窗口 ②合并多个ACK号而只发送最后一个ACK号 ③合并多个窗口而只发送最终的窗口。
流程: 从单个发送和接收流程来看:
- 数据拆分和发送:发送到缓冲区的数据会被TCP模块以MSS长度为单位进行拆分,每一块数据都会由TCP模块加上TCP header,并由IP模块添加IP header和MAC header。发送的是序号+数据。
- 使用ACK号确认包发到:TCP数据的收发是双向的,返回ACK号时需要将ACK比特设为1,代表ACK号字段有效。接收的是接收方返回的ACK号。
- 动态调整ACK号等待时间:TCP会在发送数据的过程中持续测量ACK号的返回时间,如果ACK号的返回变慢,则会相应延长等待时间,反之缩短。
4. 断开连接,删除套接字
流程:(Web HTTP1.0)
- 完成数据发送的一方(A)发起断开过程,生成包含断开信息的TCP header,控制位FIN比特设为1,传递给IP模块发送网络包,套接字记录断开操作信息。
- B标记套接字为断开操作状态,返回ACK号以告知发送方已收到FIN为1的包。
- 如果缓冲区没有剩余的已接受数据,协议栈不会再向应用程序传递数据,而是告知应用程序全部数据都已收到。
- B生成包含断开信息的TCP header,控制位FIN比特设为1,传递给IP模块发送网络包。
- A返回ACK号以告知B已收到FIN为1的包。(通信结束)
- B等待一段时间后删除套接字:避免出现A的ACK号丢失,而B别的应用程序恰好分配了同一个端口号,A重发的ACK号将B该新的套接字断开的情况。一般等待时间为几分钟。注意,客户端的端口号是从空闲的端口号中随机选择的。
5. IP和以太网的包的收发
关键概念:
-
包的内容:TCP header + 数据块
-
IP包:IP header + 包的内容
-
以太网包:MAC header + IP包
-
路由器:转发设备,根据IP规则传输包,而IP协议根据目标地址判断下一个IP转发设备(路由器)的位置。转发过程中会持续查询下一个路由器的MAC地址并改写已有的MAC header。会改写MAC header。
-
集线器:转发设备,根据以太网规则传输包,转发过程中根据以太网协议将包传输到下一个转发设备。不会改写MAC header。
-
IP header:用于IP协议,地址是目标服务器。IP协议可以根据IP表查询包的传输方向而找到下一个路由器的MAC地址,并写入MAC header中。IP header本身保持不变。
- 接收方IP地址填写通信对象的IP地址
- 发送方IP地址填写发送所使用的网卡的IP地址,一个计算机可以有多个网卡。协议栈的IP模块和路由器通过IP表(路由表) 判断应该将包交给哪块网卡。
- 协议号:TCP(60), UDP(11), ICMP(01)
-
MAC header:用于以太网协议,地址是下一个路由器。以太网协议用于根据已有的MAC地址转发包。
- 以太类型(16进制):0800(IP), 0806(ARP)
流程:
- 发送方网络设备包装包,协议栈的IP模块进行路由表查询,IP模块为网络包添加IP header。
| Network Destination | Newtmask | Gateway | Interface | Metric |
| --- | --- | --- | --- | --- |
| 目标地址 | 子网掩码 | 下一个路由器的IP地址 | 网卡等网络接口 | 通过这条路由传输包的成本 |
- 目标IP通常会和Network Destination从左进行匹配
- 如果Gateway与Interface相同,则表示不需要路由器转发,直接向目标IP地址发送包。
- 表的第一行中,目标地址和子网掩码通常都是0.0.0.0,表示默认网关,如果其他所有路由项都无法匹配,则会自动匹配这一行。
- 网关在TCP/IP中即为路由器
- IP模块为网络包添加MAC header。
- MAC地址的48比特看作为一个整体