【网络协议】网络协议系列十一 - 应用层

对于HTTP大家都不陌生,但发起HTTP请求的过程都发生了什么呢?

一、应用层常见协议

超文本传输协议:HTTPHTTPS
文件传输:FTP
电子邮件:SMTPPOP3IMAP
动态主机配置:DHCP
域名系统:DNS

1.1. 域名(Domain Name

由于IP地址不方便记忆,并且不能表达组织的名称和性质,人们设计出了域名(比如baidu.com)。但实际上,为了能够访问到具体的主机,最终还是得知道目标主机的IP地址。

为什么不直接用域名?
IP地址固定4个字节,域名随随便便都至少10几个字节,这无疑会增加路由器的负担,浪费流量。

根据级别不同,域名可以分为:

  • 顶级域名(Top-level Domain,简称TLD。例,www.baidu.com
  • 二级域名(例,graph.baidu.com
  • 三级域名(例,**)

1.1.1. 顶级域名的分类

  • 通用顶级域名(General Top-level Domain,简称gTLD
    • .com(公司)net(网络结构).org(组织结构,公益性居多).edu(教育).gov(政府部门).int(国际组织)
  • 国家及地区顶级域名(Country Code Top-level Domain,简称ccTLD
    • .cn(中国).jp(日本).uk(英国)
  • 新通用顶级域名(New Generic Top-level Domain,简称New gTLD
    • .vip.xyz.top.club.shop

1.1.2. 二级域名

二级域名是指顶级域名之下的域名。

  • 在通用顶级域名下,它一般指域名注册人的名称,例如google,baidu,microsoft等
  • 在国家及地区顶级域名下,它一般指注册类别的,例如com,edu,gov,net等

例如baidu.com,其实完整的写法是www.baidu.com.,最后是有一个.的,这个点就代表根服务器,DNS解析的时候会自动加上去。

1.2. DNS(Domain Name System

DNS英文译为:域名系统。

利用DNS协议,可以将域名(比如baidu.com)解析对应的IP地址(比如220.180.38.120)。

DNS可以基于UDP协议,也可以基于TCP协议,服务器占用53端口。

设备访问一个域名的时候会优先从本地名称服务器查找对应的IP,如果没有找到就会请求根服务器,一级一级往下找,直到找到对应的域名为止,最后将域名对应的IP返回到设备并在本地服务器做一个缓存。

常用命令:

  • ipconfig /displaydns:查看DNS缓存记录
  • ipconfig /flushdns:清空DNS缓存记录
  • ping 域名:测试指定域名服务器的连接状态
  • nslookup 域名:查看指定域名的IP地址

二、IP地址的分配

IP地址按照分配方式可以分为:静态IP地址、动态IP地址。

2.1. 静态IP地址

静态IP地址是手动设置的。

适用场景:不怎么挪动的台式机(比如学校机房中的台式机),服务器等。

2.2. 动态IP地址(DHCP)

从DHCP服务器自动获取IP地址。

适用场景:移动设备、无线设备等。

DHCP(Dynamic Host Configuration Protocol),动态主机配置协议。

DHCP协议基于UDP协议,客户端是68端口,服务器是67端口。

DHCP服务器会从IP地址池中,挑选一个IP地址“出租”给客户端一段时间,时间到期就回收它们。平时家里上网的路由器就可以充当DHCP服务器。

2.2.1. 分配IP地址的4个阶段

  1. DISCOVER:发现服务器。
    • 发广播包(源IP是0.0.0.0,目标IP是255.255.255.255,目标MAC是FF:FF:FF:FF:FF:FF
  2. OFFER:提供租约
    • 服务器返回可以租用的IP地址,以及租用期限、子网掩码、网关、DNS等信息
  3. REQUEST:选择IP地址
    • 客户端选择一个OFFER,发送广播包进行回应
  4. ACKNOWLEDGE:确认
    • 被选中的服务器发送ACK数据包给客户端

DHCP服务器可以跨网段分配IP地址么?(DHCP服务器、客户端不在同一个网段)

可以借助DHCP中继代理(DHCP Relay Agent)实现跨网段分配IP地址。

2.2.2. 自动续约

客户端会在租期不足的时候,自动向DHCP服务器发送REQUEST信息申请续约。

常用命令:

  • ipconfig /all:可以看到DHCP相关的详细信息,比如租约过期时间、DHCP服务器地址等。
  • ipconfig /release:释放租约
  • ipconfig /renew:重新申请IP地址、申请续约(延长租期)

三、HTTP

HTTP(Hyper Text Transfer Protocol),超文本传输协议。是互联网中应用最广泛的应用层协议之一。

设计HTTP最初的目的是:提供一种发布和接收HTML页面的方法(这也是为什么叫HTTP,因为HTTP最初就是用来传输HTML的),由URI来标识具体的资源。后面用HTTP来传递的数据格式不仅仅是HTML,应用非常广泛。

URL是URI的子级,URI是统一资源标识符,在服务器中是唯一的。URL是统一资源定位符,是为了定位资源位置的。

HTML(Hyper Text Markup Language),超文本标记语言(用标签标记普通文本使其表达超出文本范围的内容)。用来编写网页的。

对比百度百科和维基百科的词条内容,就知道为什么维基百科更加权威和受欢迎。

3.1. 版本历史

  • 1991年,HTTP/0.9
    • 只支持GET请求方法获取文本数据(比如HTML文档),且不支持请求头、响应头等,无法向服务器传递太多信息
  • 1996年,HTTP/1.0
    • 支持POST、HEAD等请求方法,支持请求头、响应头等,支持更多种数据类型(不再局限于文本数据)
    • 浏览器的每次请求都需要与服务器建立一个TCP连接,请求处理完成后立即断开TCP连接
  • 1997,HTTP/1.1(最经典,使用最广泛的版本
    • 支持PUT、DELETE等请求方法
    • 采用持久连接(Connection keep-alive),多个请求可以共用同一个TCP连接
  • 2015,HTTP/2.0(正在取代1.1)
  • 2018,HTTP/3.0(处于草稿阶段)

3.2. 标准

HTTP的标准是由万维网协会(W3C)、互联网工程任务组(IETF)协调制定,最终发布了一系列的RFC。

RFC(Request For Comments),请求意见稿。申请的内容被审核通过后就会成为HTTP标准。

  • HTTP/1.0最早是在1997年的RFC_2068中记录的,该规范在1999年的RFC_2616中已作废,2014年又由RFC_7230系列的RFC取代。
  • HTTP/2.0标准于2015年5月以RFC_7540正式发表,取代HTTP/1.1成为HTTP的实现标准

1996年3月,清华大学提交的适应不同国家和地区中文编码的汉字统一传输标准被IETF通过为RFC_1922,成为中国大陆第一个被认可为RFC文件的提交协议。

通过抓包本地服务器探索请求过程:

请求报文和响应报文格式:

HTTP的请求头和响应头格式都是固定的,必须按照指定格式收发数据,否则就无法正常建立通信。比例空格、换行、键值对的键(字段名)。

每个字符(16进制)都有对应的ASCII码值:

为什么使用0d0a呢(既有回车又有换行)?主要是为了兼容不同的操作系统,因为有些操作系统0a代表换行,有些操作系统换行的操作符是0d

3.3. ABNF

ABNF(Augmented BNF),是BNF(Backus-Naur Form,译:巴克斯-瑙尔范式)的修改/增强版。在RFC_5234中表明ABNF用作internet中通信协议的定义语言。

ABNF是最严谨的HTTP报文格式描述形式,脱离ABNF谈论HTTP报文格式,往往都是不严谨的。

关于HTTP报文格式的定义:

ABNF核心规则:

3.3.1. 报文格式

1
2
3
4
5
6
HTTP-message   = start-line
*( header-field CRLF )
CRLF
[ message-body ]

start-line = request-line / status-line
  • start-linerequest-line代表请求报文(请求行),status-line代表响应报文(状态行)
  • *:0个或多个。2*表示至少2个,3*6表示3到6个
  • 每一个header-field后面都必须加上回车换行符CRLFheader-field整体后面也必须加上回车换行符CRLF
  • ():组成一个整体
  • []:可选(请求体、响应体)

3.3.2. request-line、status-line

start-line内部包含了空格,所以在报文格式中没有看到换行符CRLF

ABNF中的注释格式是:分号(;)+内容,例;这里描述的是注释内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SP = Space(空格),DIGIT代表数字,具体可参考上面的ABNF核心规则
// 请求行(例:GET /hello/ HTTP/1.1
request-line = method SP request-target SP HTTP-version CRLF
// HTTP-version格式(例:HTTP/1.1
HTTP-version = HTTP-name "/" DIGIT "." DIGIT
// HTTP-name格式(%x48.54.54.50是HTTP的16进制ASCII码值)
HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive

// 状态行(例:HTTP/1.1 200
status-line = HTTP-version SP status-code SP reason-phrase CRLF
// 状态码由3个数字组成(例:200300500等)
status-code = 3DIGIT
// 状态描述,可以是Tab、空格、字符或obs文字,*代表是可选项(例:OK)
reason-phrase = *( HTAB / SP / VCHAR / obs-text )

3.3.3. header-field、message-body

1
2
3
4
5
6
7
8
9
10
11
// 请求头键值对(例:Host: localhost:8080)
header-field = field-name ":" OWS field-value OWS
// 指定键名
field-name = token
// 值(可有可无)
field-value = *( field-content / obs-fold )
// 空格或Tab键(*代表该值可有可无,也就是说有OWS的地方可以没有空格也可以有多个空格)
OWS = *( SP / HTAB )

// 消息体(只要是字节就可以)
message-body = *OCTET

3.3.4. telnet

使用telnet可以直接面向HTTP报文与服务器交互,可以更清晰、直观的看到请求报文和响应报文的内容,也可以检验请求报文格式的正确与否。

Windows系统用户可以使用Xshell软件,Mac系统用户直接命令安装即可(brew install telnet)。

3.4. URL编码

URL中一旦出现了一些特殊字符(比如中文、空格),需要进行编码。在浏览器地址栏输入URL时,是采用UTF-8进行编码。比如https://www.baidu.com/s?wd=你好编码后就是https://www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD

3.5. 请求方法

RFC_7231,section-4: Request methods:描述了8种请求方法。

RFC_5789,section-2: Patch methods文档中描述了PATCH方法。

所以到目前为止一共有9种HTTP请求方法。最常用的请求方法是GETPOST

  • GET:常用于读取的操作,请求参数直接拼接在URL的后面(浏览器或服务器对URL是有长度限制的,ABNF并没有限制URL长度的说明)
  • POST:常用于添加、修改、删除的操作,请求参数可以放到请求体中(没有大小限制)
  • HEAD:请求得到与GET请求相同的响应,但没有响应体
    • 使用场景举例:在下载一个大文件前,先获取其大小,再决定是否要下载,以此可以节约带宽资源
  • OPTIONS:用于获取目的资源所支持的通信选项,比如服务器支持的请求方法
    • 使用telnet连接服务器后输入OPTIONS * HTTP/1.1(*代表所有路径),返回Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS
  • PUT:用于对已存在的资源进行整体覆盖(很少使用,因为是直接覆盖数据,所以不太安全)
  • DELETE:用于删除指定的资源(很少使用,不安全)
  • PATCH:用于对资源进行部分修改(资源不存在,会创建新的资源。很少使用,不安全)
  • CONNECT:可以开启一个客户端与所请求资源之间的双向沟通的通道,它可以用来创建隧道(tunnel)
    • 可以用来访问采用了SSL(HTTPS)协议的站点
  • TRACE:请求服务器回显其收到的请求信息(请求什么内容就响应什么内容),主要用于HTTP请求的测试或诊断

3.6. 请求头字段

头部字段(Header Field)可以分为4种类型:

  • 请求头字段(Request Header Fields
    • 有关要获取的资源或客户端本身信息的消息头
  • 响应头字段(Response Header Fields
    • 有关响应的补充信息,比如服务器本身(名称和版本等)的消息头
  • 实体头字段(Entity Header Fields
    • 有关实体主体的更多信息,比如主体长度(Content-Length)或其MIME类型
  • 通用头字段(General Header Fields
    • 同时适用于请求和响应消息,但与消息主体无关的消息头,比如时间(Date)

1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

上面请求头的接收类型包含了很多种,每一种使用逗号,隔开,q=0.8是权重的意思,权重值越大,优先级越高(优先返回哪一种类型)。如果不指定q值,默认是1.0(最大值)。

3.7. 响应头字段

3.8. 状态码(Status Code

状态码是在RFC_2616,section-10:Status Code Definitions规范中定义的。状态码指示HTTP请求是否已成功完成。

3.8.1. 状态码分类

状态码分为5类:

  • 信息响应:100~199
  • 成功响应:200~299
  • 重定向:300~399
  • 客户端错误:400~499
  • 服务器错误:500~599

3.8.2. 常见状态码

100(Continue)
请求的初始化部分已经被服务器收到,并且没有被服务器拒绝。客户端应该继续发送剩余的请求,如果请求已经完成,就忽略这个响应。

应用场景:允许客户端发送带请求体的请求前,判断服务器是否愿意接收请求(服务器通过请求头判断)。在某些情况下,如果服务器在不看请求体就拒绝请求时,客户端就发送请求体是不恰当的或低效的。

200(OK)
请求成功

302(Found)
请求的资源被暂时的移动到了由Location头部指定的URL上。

304(Not Modified)
说明无需再次传输请求的内容(也就是说客户端可以使用之前302缓存的内容,服务器只返回响应头)。

400(Bad Request)
由于语法无效,服务器无法理解该请求。

401(Unauthorized)
由于缺乏目标资源要求的身份验证凭证。

403(Forbidden)
服务端有能力处理该请求,但是拒绝授权访问。

404(Not Found)
服务器端无法找到所请求的资源。

405(Method Not Allowed)
服务器禁止了使用当前HTTP方法的请求(比如应该使用POST请求而客户端使用的是GET请求)。

406(Not Acceptable)
服务器端无法提供与Accept-Charset以及Accept-Language指定的值相匹配的响应。

408(Request Timeout)
服务器想要将没有在使用的连接关闭(一些服务器会在空闲连接上发送此信息,即便在客户端没有发送任何请求的情况下)。

500(Internal Server Error)
所请求的服务器遇到意外的情况并阻止其执行请求。

501(Not Implemented)
请求的方法不被服务器支持,因此无法被处理。

服务器必须支持的方法(即不会返回这个状态码的方法)只有GET和HEAD。

注意和405的区分:405代表服务器支持该请求方法,只是客户端使用了错误的请求方法;而501是服务器不支持该请求方法。

502(Bad Gateway)
作为网关或代理角色的服务器,从上游服务器(如tomcat)中接收到的响应是无效的。

503(Service Unavailable)
服务器尚未处于可以接受请求的状态。通常造成这种情况的原因是由于服务器停机维护或者已超载。

3.9. form(表单)提交

常用属性:

  • action:请求的URI
  • method:请求方法(只支持GET和POST)
  • enctype:POST请求时,请求体的编码方式
    • application/x-www-form-urlencoded,默认值。用&分割参数,用=分割键和值,字符用URL编码方式进行编码
    • multipart/form-data:文件上传时必须使用这种编码方式

multipart/form-data参考RFC_1521

请求头:

1
Content-Type: multipart/form-data; boundary=xxx

请求体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
multipart-body := preamble 1*encapsulation
close-delimiter epilogue

encapsulation := delimiter body-part CRLF

delimiter := "--" boundary CRLF ; taken from Content-Type field.
; There must be no space
; between "--" and boundary.

close-delimiter := "--" boundary "--" CRLF ; Again, no space
by "--",

preamble := discard-text ; to be ignored upon receipt.

epilogue := discard-text ; to be ignored upon receipt.