WEB 服务和HTTP协议原理概述
1. WEB 服务和HTTP协议
1.1 HTTP 超文本传输协议
1.1.1 HTTP 相关概念
互联网:是网络的网络,是所有类型网络的母集。
因特网:世界上最大的互联网网络。即因特网概念从属于互联网概念。习惯上,大家把连接在因特网上的计算机都成为主机。
万维网:WWW(world wide web)万维网并非某种特殊的计算机网络,是一个大规模的、联机式的信息贮藏库,使用链接的方法能非常方便地从因特网上的一个站点访问另一个站点(超链技术),具有提供分布式服务的特点。万维网是一个分布式的超媒体系统,是超文本系统的扩充,基于B/S架构实现

URL:万维网使用统一资源定位符(Uniform Resource Locator)来标志万维网上的各种文档,并使每个文档在整个因特网的范围内具有唯一的标识符URL。
HTTP:为解决"用什么样的网络协议来实现整个因特网上的万维网文档”这一难题,就要使万维网客户程序(以浏览器为主,但不限于浏览器)与万维网服务器程序之间的交互遵守严格的协议,即超文本传送协议(HyperText Transfer Protocol)。HTTP是处于应用层的协议,使用TCP传输层协议进行可靠的传送。因此,需要特别提醒的是,万维网是基于因特网的一种广泛因特网应用系统,且万维网采用的是HTTP(80/TCP)和 HTTPS(443/TCP)的传输协议,但因特网还有其他的网络应用系统(如:FTP、SMTP等等)。
HTML:为了解决"怎样使不同作者创作的不同风格的万维网文档,都能在因特网上的各种主机上显示出来,同时使用户清楚地知道在什么地方存在着链接”这一问题,万维网使用超文本标记语言(HyperText Markup Language),使得万维网页面的设计者可以很方便地用链接从页面的某处链接到因特网的任何一个万维网页面,并且能够在自己的主机品目上将这些页面显示出来。HTML与txt一样,仅仅是是一种文档,不同之处在于,这种文档专供于浏览器上为浏览器用户提供统一的界面呈现的统一规约。且具备结构化的特征,这是txt所不具备的强制规定。
1.1.2 浏览器访问网站的过程

1.1.3 HTTP 协议通信过程
HTTP服务通信过程

HTTP协议分层

1.1.4 HTTP 相关技术
1.1.4.1 WEB 开发语言
http:Hyper Text Transfer Protocol 应用层协议,默认端口: 80/tcp
WEB前端开发语言:
- html
- css
- javascript

html
Hyper Text Markup Language 超文本标记语言,编程语言,主要负责实现页面的结构
范例:html 语言
<html>
<head>
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
<title>HTML语言</title>
</head>
<body>
<img src="http://www.wang.com/wp-content/uploads/2017/09/logo.png" >
<h1 style="color:red">欢迎</h1>
<p><a href=http://www.wang.com>马哥教育</a>欢迎你</p>
</body>
</html>
#注意:html文件编码为utf-8编码
CSS
Cascading Style Sheet 层叠样式表, 定义了如何显示(装扮) HTML 元素,比如:字体大小和颜色属性等。样式通常保存在外部的 .css 文件中,用于存放一些HTML文件的公共属性,从而通过仅编辑一个简单的 CSS 文档,可以同时改变站点中所有页面的布局和外观。
范例 :CSS
#test.html 建议用Vscode创建文件,用记事本可能会出现乱码
<html>
<head>
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
<link rel="stylesheet" type="text/css" href="mystyle.css" />
</head>
<body>
<h1>这是 heading 1</h1>
<p>这是一段普通的段落。请注意,该段落的文本是红色的。在 body 选择器中定义了本页面中的默认文本颜色。</p>
<p class="ex">该段落定义了 class="ex"。该段落中的文本是蓝色的。</p>
</body>
</html>
#mystyle.css
body {color:red}
h1 {color:#00ff00}
p.ex {color:rgb(0,0,255)}
Js
javascript,实现网页的动画效果,但实属于静态资源
Java和javascript的关系: 周杰和周杰伦的关系
范例:javascript
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
</head>
<body>
<h2>我的第一个 JavaScript</h2>
<button type="button"
onclick="document.getElementById('demo').innerHTML = Date()">
点击这里来显示日期和时间
</button>
<p id="demo"></p>
</body>
</html>
1.1.4.2 MIME
MIME : Multipurpose Internet Mail Extensions 多用途因特网邮件扩展
文件 /etc/mime.types ,来自于mailcap包
MIME格式:type/subtype txt html jpg bmp
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_Types
http://www.w3school.com.cn/media/media_mimeref.asp
范例:
text/plain txt asc text pm el c h cc hh cxx hxx f90 conf log
text/html html htm
text/css
image/jpeg jpg jpeg
image/png
video/mp4
application/javascript
1.1.4.3 URI和URL
参考链接
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Identifying_resources_on_the_Web
URI: Uniform Resource Identifier 统一资源标识,分为URL 和 URN
URN:Uniform Resource Naming,统一资源命名
示例: P2P下载使用的磁力链接是URN的一种实现
magnet:?xt=urn:btih:1E45C21458681BFECBEB72D77A2500762D6F799F
URL:Uniform Resource Locator,统一资源定位符,用于描述某服务器某特定资源位置
两者区别:URN如同一个人的名称,而URL代表一个人的住址。换言之,URN定义某事物的身份,而URL提供查找该事物的方法。URN仅用于命名,而不指定地址
URL组成

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
scheme:方案,访问服务器以获取资源时要使用哪种协议
user:用户,某些方案访问资源时需要的用户名
password:密码,用户对应的密码,中间用:分隔
Host:主机,资源宿主服务器的主机名或IP地址
port:端口,资源宿主服务器正在监听的端口号,很多方案有默认端口号
path:路径,服务器资源的本地名,由一个/将其与前面的URL组件分隔
params:参数,指定输入的参数,参数为名/值对,多个参数,用;分隔
query:查询,传递参数给程序,如数据库,用?分隔,多个查询用&分隔
frag:片段,一小片或一部分资源的名字,此组件在客户端使用,用#分隔
URL示例

http://www.wangxiaochun.com:8080/images/logo.jpg
ftp://mage:password@172.16.0.1/pub/linux.ppt
rtsp://videoserver/video_demo/ #Real Time Streaming Protocol
gcomm://10.0.0.8,10.0.0.18,10.0.0.28
http://www.wang.com/bbs/hello;gender=f/send;type=title
https://list.jd.com/list.html?
cat=670,671,672&ev=14_2&sort=sort_totalsales15_desc&trans=1
http://apache.org/index.html#projects-list
1.1.4.4 网站访问量
网站访问量统计的重要指标
- IP(独立IP):即Internet Protocol,指独立IP数。一天内来自相同客户机IP 地址只计算一次,记录远程客户机IP地址的计算机访问网站的次数,是衡量网站流量的重要指标
- PV(访问量): 即Page View, 页面浏览量或点击量,用户每次刷新即被计算一次,PV反映的是浏览某网站的页面数,PV与来访者的数量成正比,PV并不是页面的来访者数量,而是网站被访问的页面数量
- UV(独立访客):即Unique Visitor,访问网站的一台电脑为一个访客。一天内相同的客户端只被计算一次。可以理解成访问某网站的电脑的数量。网站判断来访电脑的身份是通过cookies实现的。如果更换了IP后但不清除cookies,再访问相同网站,该网站的统计中UV数是不变的
网站统计:http://www.alexa.cn/rank/
范例:网站访问统计
1. 甲乙丙三人在同一台通过 ADSL 上网的电脑上(中间没有断网),分别访问 www.wang.com 网站,并且每人共用一个浏览器,各个浏览了2个页面,那么网站的流量统计是:
IP: 1 PV:6 UV:1
2. 若三人都是ADSL重新拨号后,各个使用不同的浏览器,分别浏览了2个页面,则
IP: 3 PV:6 UV:3
网站访问量 PV,IP,UV统计方法
- 使用文本工具, 比如awk提取IP,sort排序、uniq统计去重后统计IP数
- 第三方统计工具进行pv统计,比如: 百度统计、腾讯分析、piwik、开发自研统计平台。
1.1.5 HTTP工作机制
一次http事务包括:
- http请求:http request
- http响应:http response
Web资源:web resource, 一个网页由多个资源(文件)构成,打开一个页面,通常会有多个资源展示出来,但是每个资源都要单独请求。因此,一个"Web 页面”通常并不是单个资源,而是一组资源的集合
资源类型:
- 静态文件:无需服务端做出额外处理,服务器端和客户端的文件内容相同
常见文件后缀:.html, .txt, .jpg, .js, .css, .mp3, .avi - 动态文件:服务端执行程序,返回执行的结果,服务器端和客户端的文件内容不相同
常见文件后缀:.php, .jsp ,.asp
HTTP 连接请求

串行和并行连接

串行,持久连接和管道

提高HTTP连接性能
- 并行连接:通过多条TCP连接发起并发的HTTP请求
- 持久连接:keep-alive,重用TCP连接,以消除连接和关闭的时延,以事务个数和时间来决定是否关闭连接
- 管道化连接:通过共享TCP连接,发起并发的多个HTTP请求,但需要也要按发起请求的顺序来接收响应,实现难度很大,很多代理服务器和浏览器不支持
- 复用的连接:交替传送请求和响应报文(实验阶段)
1.1.6 HTTP 协议版本
1.1.6.1 HTTP 1.X 协议
RFC Hypertext Transfer Protocol -- HTTP/1.1
https://tools.ietf.org/html/rfc2616

http/0.9:
1991,原型版本,功能简陋,只有一个命令GET。GET /index.html ,服务器只能回应HTML格式字符串,不能回应别的格式
http/1.0
1996年5月,支持cache, MIME, method
每个TCP连接只能发送一个请求,发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接引入了POST命令和HEAD命令头信息是 ASCII 码,后面数据可为任何格式。服务器回应时会告诉客户端,数据是什么格式,即Content-Type字段的作用。这些数据类型总称为MIME 多用途互联网邮件扩展,每个值包括一级类型和二级类型,预定义的类型,也可自定义类型, 常见Content-Type值:
text/xml image/jpeg audio/mp3
http/1.1
1997年1月,引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive。对于同一个域名,大多数浏览器允许同时建立6个持久连接引入了管道机制,即在同一个TCP连接里,客户端可以同时发送多个请求,进一步改进了HTTP协议的效率
新增方法:PUT、PATCH、OPTIONS、DELETE
同一个TCP连接里,所有的数据通信是按次序进行的。服务器只能顺序处理回应,前面的回应慢,会有许多请求排队,造成"队头堵塞"(Head-of-line blocking)
为避免上述问题,两种方法:一是减少请求数,二是同时多开持久连接。
网页优化技巧,如合并脚本和样式表、将图片嵌入CSS代码、域名分片(domain sharding)等
HTTP 协议不带有状态,每次请求都必须附上所有信息。请求的很多字段都是重复的,浪费带宽,影响速度
HTTP1.0和HTTP1.1的区别

缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-NoneMatch等更多可供选择的缓存头来控制缓存策略
带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如:客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),方便了开发者自由的选择以便于充分利用带宽和连接
错误通知的管理,在HTTP1.1中新增24个状态响应码,如409(Conflict)表示请求的资源与资源当前状态冲突;410(Gone)表示服务器上的某个资源被永久性的删除
Host 头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
长连接,HTTP 1.1支持持久连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,弥补了HTTP1.0每次请求都要创建连接的缺点
HTTP1.0和1.1的问题
- HTTP1.x在传输数据时,每次都需要重新建立连接,无疑增加了大量的延迟时间,特别是在移动端更为突出
- HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,无法保证数据的安全性
- HTTP1.x在使用时,header里携带的内容过大,增加了传输的成本,并且每次请求header基本不怎么变化,尤其在移动端增加用户流量
- 虽然HTTP1.x支持了keep-alive,来弥补多次创建连接产生的延迟,但是keep-alive使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keepalive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间
HTTPS协议:
为解决安全问题,网景在1994年创建了HTTPS,并应用在网景导航者浏览器中。 最初,HTTP是与SSL一起使用的;在SSL逐渐演变到TLS时(其实两个是一个东西,只是名字不同而已),最新的HTTPS也由在2000年五月公布的RFC 2818正式确定下来。HTTPS就是安全版的HTTP,目前大型网站基本实现全站HTTPS
HTTPS特点
- HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费
- HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的
- HTTP和HTTPS使用的是不同的连接方式,端口不同,前者是80,后者是443
- HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题
- HTTPS 实现过程降低用户访问速度,但经过合理优化和部署,HTTPS 对速度的影响还是可以接受的
1.1.6.2 HTTP 2.0 协议
SPDY协议
SPDY:2009年谷歌研发,综合HTTPS和HTTP两者有点于一体的传输协议,主要特点:
- 降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率
- 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,可以保证用户能第一时间看到网页内容
- header压缩。HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量
- 基于HTTPS的加密协议传输,大大提高了传输数据的可靠性
- 服务端推送(server push),采用了SPDY的网页,例如网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了
HTTP2协议
http/2.0:2015年发布,HTTP2.0是SPDY的升级版
- 头信息和数据体都是二进制,称为头信息帧和数据帧
- 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序响应,避免了"队头堵塞",此双向的实时通信称为多工(Multiplexing)
- 引入头信息压缩机制(header compression),头信息使用gzip或compress压缩后再发送;客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,不发送同样字段,只发送索引号,提高速度
- HTTP/2 允许服务器有新数据时未经请求,主动向客户端发送资源,而无需客户端拉取,即服务器推送(server push)
HTTP2.0和SPDY区别:
- HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
- HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE
1.1.7 HTTP 请求访问的完整过程

一次完整的http请求处理过程
1、建立连接:接收或拒绝连接请求
2、接收请求:接收客户端请求报文中对某资源的一次请求的过程
Web访问响应模型(Web I/O)

- 单进程I/O模型:启动一个进程处理用户请求,而且一次只处理一个,多个请求被串行响应
- 多进程I/O模型:并行启动多个进程,每个进程响应一个连接请求
- 复用I/O结构:启动一个进程,同时响应N个连接请求
- 复用的多进程I/O模型:启动M个进程,每个进程响应N个连接请求,同时接收M*N个请求
3、处理请求:服务器对请求报文进行解析,并获取请求的资源及请求方法等相关信息,根据方法,资源,首部和可选的主体部分对请求进行处理
常用请求Method: GET、POST、HEAD、PUT、DELETE、TRACE、OPTIONS
4、访问资源:
服务器获取请求报文中请求的资源web服务器,即存放了web资源的服务器,负责向请求者提供对方请求的静态资源,或动态运行后生成的资源
5、构建响应报文:
一旦Web服务器识别除了资源,就执行请求方法中描述的动作,并返回响应报文。响应报文中 包含有响应状态码、响应首部,如果生成了响应主体的话,还包括响应主体
1)响应实体:如果事务处理产生了响应主体,就将内容放在响应报文中回送过去。响应报文中通常包括:
- 描述了响应主体MIME类型的Content-Type首部
- 描述了响应主体长度的Content-Length
- 实际报文的主体内容
2)URL重定向:web服务构建的响应并非客户端请求的资源,而是资源另外一个访问路径
3)MIME类型: Web服务器要负责确定响应主体的MIME类型。多种配置服务器的方法可将MIME类型与资源管理起来
6、发送响应报文
Web服务器通过连接发送数据时也会面临与接收数据一样的问题。服务器可能有很多条到各个客户端的连接,有些是空闲的,有些在向服务器发送数据,还有一些在向客户端回送响应数据。服务器要记录连接的状态,还要特别注意对持久连接的处理。对非持久连接而言,服务器应该在发送了整条报文之后,关闭自己这一端的连接。对持久连接来说,连接可能仍保持打开状态,在这种情况下,服务器要正确地计算Content-Length首部,不然客户端就无法知道响应什么时候结束
7、记录日志
最后,当事务结束时,Web服务器会在日志文件中添加一个条目,来描述已执行的事务
1.2 HTTP 协议报文头部结构
http协议:http/0.9, http/1.0, http/1.1, http/2.0,http/3.0
http协议:stateless 无状态, 服务器无法持续追踪访问者来源
解决http协议无状态方法
- cookie 客户端存放
- session 服务端存放
http事务:一次访问的过程
- 请求:request
- 响应:response
HTTP报文结构
协议查看或分析的工具:tcpdump, wireshark,tshark
参考资料:https://developer.mozilla.org/zh-CN/docs/Web/HTTP
1.2.1 HTTP 请求报文

request报文格式
<method> <request-URL> <version>
<headers>
<entity-body>
范例:
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: www.wang.org
User-Agent: HTTPie/0.9.4
范例
#HTML表单,用于收集用户提交的用户名密码等信息
#post.html
<form action="index.html" method="POST"> #属性为post,意味着当用户点击提交按钮时,浏览器会使用 POST 方法将表单数据发送给服务器。在 POST 请求中,数据通常会被包含在请求体中发送
username:<br>
<input type="text" name="username" >
<br>
password:<br>
<input type="text" name="password" >
<br><br>
<input type="submit" value="Submit">
</form>
1.2.2 HTTP响应报文

response报文格式
<version> <status> <reason-phrase>
<headers>
<entity-body>
范例
HTTP/1.1 200 OK
Cache-Control: max-age=3, must-revalidate
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=UTF-8
Date: Thu, 07 Nov 2019 03:44:14 GMT
Server: Tengine
Transfer-Encoding: chunked
Vary: Accept-Encoding
Vary: Accept-Encoding, Cookie
1.2.3 HTTP报文格式详解
1.2.3.1 Method 方法
请求方法,标明客户端希望服务器对资源执行的动作,包括以下:
- GET: 从服务器获取一个资源
- HEAD: 只从服务器获取文档的响应首部
- POST: 向服务器输入数据,通常会再由网关程序继续处理
- PUT: 将请求的主体部分存储在服务器中,如上传文件
- DELETE: 请求删除服务器上指定的文档
- TRACE:追踪请求到达服务器中间经过的代理服务器
- OPTIONS:请求服务器返回对指定资源支持使用的请求方法
- CONNECT:建立一个到由目标资源标识的服务器的隧道
- PATCH:用于对资源应用部分修改
1.2.3.2 version版本
HTTP/<major>.<minor>
范例:
HTTP/1.1
1.2.3.3 status 状态码



三位数字,标记请求处理过程中发生的情况
参考资料:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
http协议状态码分类
1xx:100-101 信息提示
2xx:200-206 成功
3xx:300-307 重定向
4xx:400-415 错误类信息,客户端错误
5xx:500-505 错误类信息,服务器端错误
http协议常用的状态码
200: 成功,请求数据通过响应报文的entity-body部分发送;OK
301: Moved Permanently,请求的URL指向的资源已经被删除;但在响应报文中通过首部Location指明了资源现在所处的新位置
302: Moved Temporarily,响应报文Location指明资源临时新位置
304: 客户端发出了条件式请求,但服务器上的资源未曾发生改变,则通过响应此响应状态码通知客户端;Not Modified,但无需再发送原始数据即实体给客户端
307: 浏览器内部重定向,而无需再向服务器发送请求
401: 需要输入账号和密码认证方能访问资源;Unauthorized
403: 请求被禁止;Forbidden,一般是因为权限错误或主页文件不存在
404: 服务器无法找到客户端请求的资源;Not Found
405: 表明服务器禁止了使用当前 HTTP 方法的请求 ethod Not Allowed
413: 上传的资源超过了最大限制值
499: 客户端主动断开连接。然而在实际业务开发中,当出现 HTTP 499 状态码时,大部分都是由于服务端请求时间过长,导致客户端等的“不耐烦”了,因此断开了连接。比如:慢SQL问题,499是客户端读超时关闭
连接造成的,推荐从超时时间或者优化响应速度入手,web服务器发现客户端主动关闭连接后,记录到access日志中的。可能是客户端接收响应超时了,可以先在客户端统计下是不是这个原因,再调查为什么会导致超时
500: 服务器内部错误;Internal Server Error,比如:cgi程序没有执行权限,或连接数据库失败,rewrite死循环
502: Bad Gateway,代理服务器从后端服务器收到了一条错误响应或伪响应,如无法连接到网关;Bad Gateway,比如:后端服务端口没有打开,或后端服务不可用或关机,iptable -j REJECT,确定服务器是down,相当于"确定人不存在了"
503: 服务不可用,临时服务器维护或过载,服务器无法处理请求,比如:超过连接数和连接频率
504: Gateway Timeout,网关超时,或者后端服务器无回应报文,比如:服务端口虽然打开,但服务返回结果时间过长,iptable -j DROP,不确定服务器down,相当于"人失踪了,不确定存在或不存在
1.2.3.4 reason-phrase原因短语
状态码所标记的状态的简要描述
1.2.3.5 headers首部字段头
首部字段包含的信息最为丰富。首部字段同时存在于请求和响应报文内,并涵盖 HTTP 报文相关的内容信息。使用首部字段是为了给客服端和服务器端提供报文主体大小、所使用的语言、认证信息等内容
首部字段是由首部字段名和字段值构成的,中间用冒号":”分隔字段值对应,key/value 键/值对
单个 HTTP 首部字段可以有多个值
参考资料:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
1.2.3.6 entity-body实体
请求时附加的数据或响应时附加的数据,例如:登录网站时的用户名和密码,博客的上传文章,论坛上的发言等。
1.2.4 Cookie 和 Session

无状态协议是指协议对事物处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它应答就很快。
HTTP是超本文传输协议,顾名思义,这个协议支持超文本的传输。什么是超文本?说白了就是使用HTML编写的页面。通常,我们使用客户端浏览器访问服务器的资源,最常见的URL也是以html为后缀的文件,因此可以说超文本是网络上最主要的资源。
既然HTTP协议的目的是在于支持超文本的传输,也就是资源的传输,那么客户端浏览器向HTTP服务器发送请求,继而HTTP服务器将响应资源发回给客户端这样一个过程中,无论对于客户端还是服务器,都没有必要记录这个过程,因为每一次请求和响应都是相对独立的,一般而言,一个URL对应着一个唯一的超文本,正是因为这样的唯一性,使得记录用户的行为状态变得毫无意义,所以,HTTP协议被设计为无状态的连接协议符合它本身的需求。
HTTP协议这种特性有优点也有缺点,优点在于解放了服务器,每一次请求"点到为止",不会造成不必要的连接占用,缺点在于如果为了保留状态,每次请求都会传输大量的重复信息内容。
可是随着 Web 的不断发展,很多业务都需要对通信状态进行保存.
如果是一次性会话的过程: 打开浏览器 -> 访问一些服务器内容 -> 关闭浏览器
但目前有很多WEB访问场景,并不是一次性会话,而是多次相关的会话,比如:
登录场景:
打开浏览器 -> 浏览到登陆页面 -> 输入用户名和密码 -> 访问到用户主页(显示用户名) -> 修改密码(输入原密码)-> 修改收货地址.......
问题:在此处登录会话过程中产生的数据(用户会话数据)如何保存下来呢?
购物场景:
打开浏览器 -> 浏览商品列表 -> 加入购物车(把商品信息保存下来) -> 关闭浏览器
打开浏览器-> 直接进入购物车 -> 查看到上次加入购物车的商品 -> 下订单 -> 支付
问题: 在购物会话过程中,如何保存商品信息?
以上场景都需要保留会话数据,需要会话管理机制。
会话管理: 管理浏览器客户端和服务器端之间会话过程中产生的会话数据。
为了会话管理,HTTP就需要传输大量重复信息内容的问题,造成大量的网络带宽消耗。于是 Cookie 和Session 技术闪亮登场了,它们可以为用户进行会话管理,实现保存状态。
1.2.4.1 Cookie
Cookie 又称为"小甜饼”。类型为"小型文本文件”,指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。由网景公司的前雇员卢·蒙特利在1993年3月发明
因为HTTP协议是无状态的,即服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料。最后结帐时,由于HTTP的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么,所以Cookie就是用来绕开HTTP的无状态性的"额外手段”之一。服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。
在上面的购物场景中,当用户选购了第一项商品,服务器在向用户发送网页的同时,还发送了一段Cookie,记录着那项商品的信息。当用户访问另一个页面,浏览器会把Cookie发送给服务器,于是服务器知道他之前选购了什么。用户继续选购饮料,服务器就在原来那段Cookie里追加新的商品信息。结帐时,服务器读取发送来的Cookie就行了。
Cookie基于HTTP协议,也叫Web Cookie或浏览器Cookie,是服务器发送到用户浏览器并保存在客户端本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
cookie 的获取过程

- 第一次请求过程
浏览器第一次发送请求时,不会携带任何cookie信息
服务器接收到请求之后,发现请求中没有任何cookie信息
服务器生成和设置一个cookie,并将此cookie设置通过set_cookie的首部字段保存在响应报文中返回给浏览器
浏览器接收到这个响应报文之后,发现里面有cookie信息,浏览器会将cookie信息保存起来 - 第二次及其之后的过程
当浏览器第二次及其之后的请求报文中自动 cookie的首部字段携带第一次响应报文中获取的cookie信息
服务器再次接收到请求之后,会发现请求中携带的cookie信息,这样的话就认识是谁发的请求了
之后的响应报文中不会再添加set_cookie首部字段
Cookie主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
使用 Cookie 的状态管理
Cookie 技术通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。当服务器收到HTTP请求时,服务器可以在响应头里面添加一个Set-Cookie选项。浏览器收到响应后通常会保存下Cookie,之后对该服务器每一次请求中都通过Cookie请求头部将Cookie信息发送给服务器。服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息.另外,Cookie的过期时间、域、路径、有效期、适用站点都可以根据需要来指定。
Set-Cookie首部字段
- NAME=VALUE 赋予 Cookie 的名称和其值,此为必需项
- expires=DATE Cookie 的有效期,若不明确指定则默认为浏览器关闭前为止
会话期Cookie
基于内存保存,会话期Cookie是最简单的Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。会话期Cookie不需要指定过期时间(Expires)或者有效期(Max-Age)。需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期Cookie也会被保留下来,就好像浏览器从来没有关闭一样。
持久性Cookie
基于硬盘保存,和关闭浏览器便失效的会话期Cookie不同,持久性Cookie可以指定一个特定的过期时间(Expires)或有效期(Max-Age)。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
提示:当Cookie的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。
-
path=PATH 指定了主机下的哪些路径可以接受Cookie(该URL路径必须存在于请求URL中)。若不指定则默认为文档所在的文件目录,以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。
-
例如,设置 Path=/docs ,则以下地址都会匹配:
- /docs
- /docs/Web/
- /docs/Web/HTTP
-
domain=域名 指定了哪些主机可以接受Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了Domain,则一般包含子域名。
-
例如,如果设置 Domain=wang.com,则Cookie也包含子域名(如:study.wang.com)
-
Secure 标记为 Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过Cookie传输,因为Cookie有其固有的不安全性,Secure标记也无法提供确实的安全保障。从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用Cookie的 Secure 标记。
-
HttpOnly 加以限制使 Cookie 不能被 JavaScript 脚本访问,为避免跨域脚本 (XSS) 攻击,通过JavaScript的 Document.cookie API无法访问带有 HttpOnly 标记的Cookie,它们只应该发送给服务端。如果包含服务端 Session 信息的 Cookie 不想被客户端 JavaScript 脚本调用,那么就应该为其设置 HttpOnly 标记
浏览器对cookie的限制:
Cookie 存储的限制是不一样的。例如:单个域名可存储的 Cookie 数量、Cookie 大小等。

在进行页面 Cookie 操作的时候,应该尽量保证 Cookie 的个数小于 20 个,总大小小于 4KB,这是一个安全且保险的范围。
范例:响应报文中的set-cookie首部
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
范例:请求报文中的cookie首部字段
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
范例:响应报文set-cookie中的Secure 和 HttpOnly
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
范例:浏览器查看cookie

范例:chrome 浏览器禁止cookie


范例:php语言实现cookie的管理
#设置cookie
#cat setcookie.php
<?php
setcookie('title','cto'); #有效期为会话级
setcookie('user','wang',time()+3600*12); #有效期为12小时
echo "<h1>test setcookie </h1>"
?>
#说明:setcookie设置的cookie,只有下一次http请求才能生效
#显示cookie
cat showcookies.php
<?php
echo "<h1>test showcookie </h1>";
echo $_COOKIE["user"]; #显示user的这一个cookie
echo "<br />";
var_dump($_COOKIE); #显示所有cookie
//print_r($_COOKIE); #不如上面方式详细
?>
#删除cookie,通过设置过期时间实现
#vim delcookie.php
<?php
setcookie('user','wang',time()-3600*12);
echo "<h1>cookie:user is deleted </h1>";
?>
1.2.4.2 Session

session
session是相对于cookie的另外一个状态保持的解决方案,它是通过服务器来保持状态的。session指的是服务器上为每个客户端所开辟的独立存储空间,在其中保存的信息就是用于保存状态的。
Session是服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同创建session的方法。在创建了session的同时,服务器会为该session生成唯一的sessionId,而这个sessionId被创建了之后,就可以调用session相关的方法往session中增加内容了,而这些内容只会保存在服务器中,每个sessionid就像数据库中主键,可以根据SessionId 关联每个session的相关信息,比如:购物车里的商品,登录用户等。但发送给客户端浏览器的只有sessionId。当客户端浏览器再次发送http请求时,会自动地将这个sessionId 附加在请求报文中 ,服务器收到请求之后就会根据sessionId找到对应的session,从而再次使用,使得用户的状态得以保持。
每个session都有一个sessionId,这个ID存放有两种方式:
1、通过URL存取,比如:Java程序中,URL会带上一个jsessionId=xxxxxx等,这样每次重新请求的时候都传了sessionId给服务器,但此方式不安全,所以很少使用,所以一般session是依赖于cookie的.即如果浏览器禁用了cookie,则session无法实现
2、通过cookie存取(Tomcat默认如此),这种cookie是session cookie,区别于persistent cookies也就是我们常说的cookie,session cookie要注意的是存储在浏览器内存中,而不是写到硬盘上。程序一开始执行,服务器就生成一个sessionId并通过cookie携带客户端浏览器的缓存中,当下一次访问的时候,服务器先检测一下是否有这个cookie,如果有就取它的ID,如果没有就再生成一个。这就是为什么关闭浏览器之后,再进去session已经没有了,其实在服务器端session并没有清空,而是sessionId变了。
当将浏览器关闭,服务器保存的session数据不是立即释放的,此时数据还会存在一段时间(可以在程序中加以设置,Tomcat默认15分钟),只要我们知道那个sessionId,就可以继续通过请求获得此session的信息。session里面的数据都放在服务器端,通过sessionId保证不会访问错误,服务端自动对session进行管理,如果在规定的时间内没有访问,则释放掉这个session。
最后提两点:
1、sessionId通常在浏览器地址中是看不到的,但是当我们把浏览器的cookie禁止之后,Web服务器会采用URL重写的方式传递sessionId,这样就可以在地址栏看到sessionId了
2、session cookie不可以跨窗口使用,但可以跨同一个窗口的多个标签页。
session 的工作流程
第一次请求:
- 浏览器发起第一次请求的时候可以携带一些信息(比如: 用户名/密码) cookie中没有任何信息
- 当服务器接收到这个请求之后,进行用户名和密码的验证,验证成功后则可以设置session信息
- 在设置session信息的同时(session信息保存在服务器端) .服务器会在响应头中设置一个随机的session id的cookie信息
- 客户端(浏览器)在接收到响应之后,会将cookie信息保存起来(保存session id的信息)
第二次及其之后的请求:
- 第二次及其之后的请求都会携带session id信息
- 当服务器接收到这个请求之后,会获取到session id信息,然后进行验证
- 验证成功,则可以获取session信息(session信息保存在服务器端)
范例:PHP的PHPSESSID
<?php
session_start();
echo session_id();
?>
#执行结果如下图

范例:JAVA的JSESSIONID
[root@centos8 ~]#cat /usr/local/tomcat/webapps/ROOT/session.jsp
<h1>
sessionId: <%=request.getSession().getId()%>
</h1>



1.2.4.3 Cookie 和 Session 比较
cookie和session的相同和不同:
- cookie通常是在服务器生成,但也可以在客户端生成,session是在服务器端生成的
- session 将数据信息保存在服务器端,可以是内存,文件,数据库等多种形式,cookie 将数据保存在客户端的内存或文件中
- 单个cookie保存的数据不能超过4K,每个站点cookie个数有限制,比如IE8为50个、Firefox为50个、Opera为30个;session存储在服务器,没有容量限制
- cookie存放在用户本地,可以被轻松访问和修改,安全性不高;session存储于服务器,比较安全
- cookie有会话cookie和持久cookie,生命周期为浏览器会话期的会话cookie保存在缓存,关闭浏览器窗口就消失,持久cookie被保存在硬盘,知道超过设定的过期时间;随着服务端session存储压力增大,会根据需要定期清理session数据
- session中有众多数据,只将sessionID这一项可以通过cookie发送至客户端进行保留,客户端下次访问时,在请求报文中的cookie会自动携带sessionID,从而和服务器上的的session进行关联
cookie缺点:
1、使用cookie来传递信息,随着cookie个数的增多和访问量的增加,它占用的网络带宽也很大,试想假如cookie占用200字节,如果一天的PV有几个亿,那么它要占用多少带宽?
2、cookie并不安全,因为cookie是存放在客户端的,所以这些cookie可以被访问到,设置可以通过插件添加、修改cookie。所以从这个角度来说,我们要使用sesssion,session是将数据保存在服务端的,只是通过cookie传递一个sessionId而已,所以session更适合存储用户隐私和重要的数据
session 缺点:
1、不容易在多台服务器之间共享,可以使用session绑定,session复制,session共享解决
2、session存放在服务器中,所以session如果太多会非常消耗服务器的性能
cookie和session各有优缺点,在大型互联网系统中,单独使用cookie和session都是不可行的
1.3 Web 相关工具
1.3.1 wget
格式:
wget [OPTION]... [URL]...
选项:
#启动
-V, -version 显示wget的版本后退出
-h, -help 打印语法帮助
-b, -background 启动后转入后台执行
-e, -execute=COMMAND 执行`.wgetrc'格式的命令,wgetrc格式参见/etc/wgetrc或~/.wgetrc
#记录和输入文件
-o, -output-file=FILE 把记录写到FILE文件中
-a, -append-output=FILE 把记录追加到FILE文件中
-d, -debug 打印调试输出
-q, -quiet 安静模式(没有输出)
-v, -verbose 冗长模式(这是缺省设置)
-nv, -non-verbose 关掉冗长模式,但不是安静模式
-i, -input-file=FILE 下载在FILE文件中出现的URLs
-F, -force-html 把输入文件当作HTML格式文件对待
-B, -base=URL 将URL作为在-F -i参数指定的文件中出现的相对链接的前缀
-sslcertfile=FILE 可选客户端证书
-sslcertkey=KEYFILE 可选客户端证书的KEYFILE
-egd-file=FILE 指定EGD socket的文件名
#下载
-bind-address=ADDRESS
指定本地使用地址(主机名或IP,当本地有多个IP或名字时使用)
-t, -tries=NUMBER 设定最大尝试链接次数(0 表示无限制).
-O -output-document=FILE 把文档写到FILE文件中
-nc, -no-clobber 不要覆盖存在的文件或使用.#前缀
-c, -continue 接着下载没下载完的文件
-progress=TYPE 设定进程条标记
-N, -timestamping 不要重新下载文件除非比本地文件新
-S, -server-response 打印服务器的回应
-spider 不下载任何东西
-T, -timeout=SECONDS 设定响应超时的秒数
-w, -wait=SECONDS 两次尝试之间间隔SECONDS秒
-waitretry=SECONDS 在重新链接之间等待1…SECONDS秒
-random-wait 在下载之间等待0…2*WAIT秒
-Y, -proxy=on/off 打开或关闭代理
-Q, -quota=NUMBER 设置下载的容量限制
-limit-rate=RATE 限定下载输率
#目录
-nd -no-directories 不创建目录
-x, -force-directories 强制创建目录
-nH, -no-host-directories 不创建主机目录
-P, -directory-prefix=PREFIX 将文件保存到目录 PREFIX/…
-cut-dirs=NUMBER 忽略 NUMBER层远程目录
#HTTP 选项
-http-user=USER 设定HTTP用户名为 USER.
-http-passwd=PASS 设定http密码为 PASS.
-C, -cache=on/off 允许/不允许服务器端的数据缓存 (一般情况下允许).
-E, -html-extension 将所有text/html文档以.html扩展名保存
-ignore-length 忽略 `Content-Length'头域
-header=STRING 在headers中插入字符串 STRING
-proxy-user=USER 设定代理的用户名为 USER
-proxy-passwd=PASS 设定代理的密码为 PASS
-referer=URL 在HTTP请求中包含 `Referer: URL'头
-s, -save-headers 保存HTTP头到文件
-U, -user-agent=AGENT 设定代理的名称为 AGENT而不是 Wget/VERSION.
-no-http-keep-alive 关闭 HTTP活动链接 (永远链接).
-cookies=off 不使用 cookies.
-load-cookies=FILE 在开始会话前从文件 FILE中加载cookie
-save-cookies=FILE 在会话结束后将 cookies保存到 FILE文件中
#FTP 选项
-nr, -dont-remove-listing 不移走 `.listing'文件
-g, -glob=on/off 打开或关闭文件名的 globbing机制
-passive-ftp 使用被动传输模式 (缺省值).
-active-ftp 使用主动传输模式
-retr-symlinks 在递归的时候,将链接指向文件(而不是目录)
#递归下载
-r, -recursive 递归下载--慎用!
-l, -level=NUMBER 最大递归深度 (inf 或 0 代表无穷).
-delete-after 在现在完毕后局部删除文件
-k, -convert-links 转换非相对链接为相对链接
-K, -backup-converted 在转换文件X之前,将之备份为 X.orig
-m, -mirror 等价于 -r -N -l inf -nr.
-p, -page-requisites 下载显示HTML文件的所有图片
#递归下载中的包含和不包含(accept/reject)
-A, -accept=LIST 分号分隔的被接受扩展名的列表
-R, -reject=LIST 分号分隔的不被接受的扩展名的列表
-D, -domains=LIST 分号分隔的被接受域的列表
-exclude-domains=LIST 分号分隔的不被接受的域的列表
-follow-ftp 跟踪HTML文档中的FTP链接
-follow-tags=LIST 分号分隔的被跟踪的HTML标签的列表
-G, -ignore-tags=LIST 分号分隔的被忽略的HTML标签的列表
-H, -span-hosts 当递归时转到外部主机
-L, -relative 仅仅跟踪相对链接
-I, -include-directories=LIST 允许目录的列表
-X, -exclude-directories=LIST 不被包含目录的列表
-np, -no-parent 不要追溯到父目录
常用选项:
-q 静默模式
-c 断点续传
-P /path 保存在指定目录
-O filename 保存为指定文件名,filename 为 - 时,发送至标准输出
--limit-rate= 指定传输速率,单位K,M等
范例:
#限定下载速率为1M,并制定下载到/data目录中
[root@centos8 ~]#wget --limit-rate 1M -P /data
https://mirrors.aliyun.com/centos/8/isos/x86_64/CentOS-8-x86_64-1905-dvd1.iso
[root@centos8 ~]#ls /data
CentOS-8-x86_64-1905-dvd1.iso
范例:实现浏览器功能
[root@centos8 ~]#wget -qO - http://10.0.0.6/
<h1>welcome to wang.org</1>
1.3.3 curl
curl是基于URL语法在命令行方式下工作的文件传输工具,它支持FTP, FTPS, HTTP, HTTPS, GOPHER,TELNET, DICT, FILE及LDAP等协议。curl支持HTTPS认证,并且支持HTTP的POST、PUT等方法, FTP上传, kerberos认证,HTTP上传,代理服务器,cookies,用户名/密码认证, 下载文件断点续传,上载文件断点续传, http代理服务器管道( proxy tunneling),还支持IPv6,socks5代理服务器,通过http代理服务器上传文件到FTP服务器等,功能十分强大
格式:
curl [options] [URL...]
常见选项:
-A/--user-agent <string> 设置用户代理发送给服务器
-e/--referer <URL> 来源网址
--cacert <file> CA证书 (SSL)
-k/--insecure 允许忽略证书进行 SSL 连接
--compressed 要求返回是压缩的格式
-H/--header "key:value” 自定义首部字段传递给服务器
-i 显示页面内容,包括报文首部信息
-I/--head 只显示响应报文首部信息
-D/--dump-header <file>将url的header信息存放在指定文件中
--basic 使用HTTP基本认证
-u/--user <user[:password]>设置服务器的用户和密码
-L 如果有3xx响应码,重新发请求到新位置
-O 使用URL中默认的文件名保存文件到本地
-o <file> 将网络文件保存为指定的文件中
--limit-rate <rate> 设置传输速度
-0/--http1.0 数字0,使用HTTP 1.0
-v/--verbose 更详细
-C 选项可对文件使用断点续传功能
-c/--cookie-jar <file name> 将url中cookie存放在指定文件中
-x/--proxy <proxyhost[:port]> 指定代理服务器地址
-X/--request <command> 向服务器发送指定请求方法
-U/--proxy-user <user:password> 代理服务器用户和密码
-T 选项可将指定的本地文件上传到FTP服务器上
--data/-d 方式指定使用POST方式传递数据
-s --silent Silent mode 静默模式运行。在这种模式下,它不会显示进度条以及错误信息,仅输出请求得到的内容
-b name=data 从服务器响应set-cookie得到值,返回给服务器
-w <format> 显示相应的指定的报文信息,如:%{http_code},%{remote_ip}等
-m, --max-time <time> 允许最大传输时间
范例:
[root@centos8 ~]#curl -I http://www.163.com
HTTP/1.1 403 Forbidden
Date: Thu, 12 Dec 2019 01:18:11 GMT
Content-Type: text/html
Content-Length: 234
Connection: keep-alive
Server: web cache
Expires: Thu, 12 Dec 2019 01:18:11 GMT
X-Ser: BC14_lt-tianjin-tianjin-3-cache-3
Cache-Control: no-cache,no-store,private
cdn-user-ip: 123.118.223.243
cdn-ip: 125.39.21.14
X-Cache-Remote: HIT
cdn-source: baishan
[root@centos8 ~]#curl -I -A ie10 http://www.163.com
HTTP/1.1 200 OK
Date: Thu, 12 Dec 2019 01:19:30 GMT
Content-Type: text/html; charset=GBK
Connection: keep-alive
Expires: Thu, 12 Dec 2019 01:20:45 GMT
Server: nginx
Cache-Control: no-cache,no-store,private
Age: 5
Vary: Accept-Encoding
X-Ser: BC20_dx-lt-yd-fujian-xiamen-8-cache-2, BC57_dx-lt-yd-fujian-xiamen-8-
cache-2, BC5_lt-tianjin-tianjin-3-cache-3, BC13_lt-tianjin-tianjin-3-cache-3
cdn-user-ip: 123.118.223.243
cdn-ip: 125.39.21.13
X-Cache-Remote: HIT
cdn-source: baishan
[root@centos6 ~]#curl -H "user-agent: firefox" 192.168.100.8
范例: 判断网站正常
[root@centos8 ~]#if [ "$(curl -sL -w '%{http_code}' http://www.wangxiaochun.com -o /dev/null)" = "200" ]; then
echo "Success"
else
echo "Fail"
fi
[root@centos8 ~]#if curl -sL --fail http://www.wangxiaochun.com -o /dev/null;
then
echo "Success"
else
echo "Fail"
fi
范例:利用curl 获取响应码和远程主机IP
[root@ubuntu ~]#curl -s -I -m10 -o /dev/null -w %{http_code} http://www.baidu.com/
200
[root@ubuntu ~]#curl -s -I -m10 -o /dev/null -w %{remote_ip}
http://wangxiaochun.com/
58.87.87.99
[root@centos8 ~]#curl -s -I -m10 -o /dev/null -w %{local_ip}
http://wangxiaochun.com/
10.0.0.8
[root@centos8 ~]#curl -s -I -m10 -o /dev/null -w %{local_port}
http://wangxiaochun.com/
45304
[root@centos8 ~]#curl -s -I -m10 -o /dev/null -w %{remote_port}
http://wangxiaochun.com/
80
1.3.5 压力测试工具
httpd的压力测试工具:
- ab, webbench, http_load, seige
- Jmeter 开源
- Loadrunner 商业,有相关认证
- tcpcopy:网易,复制生产环境中的真实请求,并将之保存
ab 来自httpd-tools包
命令格式
ab [OPTIONS] URL
常见选项:
-n:总请求数
-c:模拟的并发请求数
-k:以持久连接模式测试
说明:并发数高于1024时,需要用 ulimit -n # 调整能打开的文件数
范例:
#客户端一次性向服务器发送 200 个请求,然后持续这个并发状态,直到完成总共 -n 参数指定的 1000 个请求。
[root@rocky8 html]#ab -n 1000 -c 100 http://www.baidu.com/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking www.baidu.com (be patient) #测试进度信息
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests
#服务器信息:软件、主机名、端口
Server Software: bfe
Server Hostname: www.baidu.com
Server Port: 80
#请求文档信息:请求路径、响应文档长度
Document Path: /
Document Length: 2381 bytes
#并发和请求统计信息
Concurrency Level: 100 #并发请求级别,也就是同时发起的请求数量,这里设置为 100。
Time taken for tests: 3.091 seconds #整个测试所花费的时间
Complete requests: 1000 #成功完成的请求数量
Failed requests: 0 #失败的请求数量
Total transferred: 2497000 bytes #总共传输的数据量,包括响应头和响应体
HTML transferred: 2381000 bytes #传输的 HTML 数据量,即响应体的大小
Requests per second: 323.57 [#/sec] (mean) #每秒处理的请求数,平均值为 323.57 个 / 秒,反映了服务器的处理能力。
Time per request: 309.056 [ms] (mean) #单个请求的平均处理时间,为 309.056 毫秒,这是从客户端角度看每个请求的平均耗时。
Time per request: 3.091 [ms] (mean, across all concurrent requests) #所有并发请求下每个请求的平均处理时间,该值是考虑并发因素后的平均耗时。
Transfer rate: 789.01 [Kbytes/sec] received #数据传输速率,每秒接收 789.01 千字节的数据。
#连接时间统计信息
Connection Times (ms)
min mean[+/-sd] median max
Connect: 21 150 47.8 162 222 #连接建立时间,包括最小、平均、标准差、中位数和最大值。
Processing: 27 137 34.9 140 232 #服务器处理请求的时间。
Waiting: 22 135 35.2 136 232 #从请求发出到收到第一个字节的等待时间。
Total: 48 286 57.5 290 402 #总时间,即从连接建立到请求处理完成的时间
#不同百分比请求的响应时间
Percentage of the requests served within a certain time (ms)
50% 290
66% 303
75% 312
80% 321
90% 358
95% 369
98% 377
99% 383
100% 402 (longest request)
#展示了不同百分比的请求在特定时间内完成的情况,例如 50% 的请求在 290 毫秒内完成,100% 的请求(即最长请求)在 402 毫秒内完成。这有助于了解请求响应时间的分布情况。
1.4 Web 服务介绍
Netcraft公司于1994年底在英国成立,多年来一直致力于互联网市场以及在线安全方面的咨询服务,其中在国际上最具影响力的当属其针对网站服务器,域名解析/主机提供商,以及SSL市场所做的客观严谨的分析研究。
https://www.netcraft.com/resources/?type=blog

1.4.1 Apache 经典的 Web 服务端
Apache起初由美国的伊利诺伊大学香槟分校的国家超级计算机应用中心开发,目前经历了两大版本分别是1.X和2.X,其可以通过编译安装实现特定的功能
Apache HTTP服务器最初是基于阻塞式 I/O 模型实现的。在阻塞式 I/O 模型中,每个连接都需要一个单独的线程来处理它的 I/O 操作。因此,如果有大量的并发连接,服务器将会创建大量的线程,这会导致服务器性能下降。
为了解决这个问题,Apache 2.0 引入了基于事件驱动的架构,以提高服务器的性能和可伸缩性。在基于事件驱动的架构中,服务器使用一组事件处理程序来处理多个连接。这些事件处理程序使用基于 select或 epoll 的多路复用技术来处理多个连接。这使得服务器可以使用更少的线程来处理大量的连接,从而提高了服务器的性能和可伸缩性
。
因此,Apache HTTP服务器在 2.0 版本及以后的版本中使用基于事件驱动的架构,并使用基于 select 或epoll 的多路复用技术来处理多个连接。
Apache的MPM(Multi-Processing Module)是Apache服务器用来管理进程或线程的模块,它决定了Apache服务器的工作方式,包括如何处理并发连接、如何分配资源等
MPM multi-processing module 有三种工作模式
1.4.1.1 Apache prefork 模型
预派生模式,有一个主控制进程,然后生成多个子进程,每个子进程有一个独立的线程响应用户请求,相对比较占用内存,但是比较稳定,可以设置最大和最小进程数,是最古老的一种模式,也是最稳定的模式,适用于访问量不是很大的场景。
优点:稳定
缺点:每个用户请求需要对应开启一个进程,占用资源较多,并发性差,不适用于高并发场景

1.4.1.2 Apache worker 模型
一种多进程和多线程混合的模型,有一个控制进程,启动多个子进程,每个子进程里面包含固定的线程,使用线程程来处理请求,当线程不够使用的时候会再启动一个新的子进程,然后在进程里面再启动线程处理请求,由于其使用了线程处理请求,因此可以承受更高的并发。
优点:相比prefork 占用的内存较少,可以同时处理更多的请求,可以基于select的IO多路复用技术实现使用更少的线程支持更多的并发连接
缺点:使用keepalive的长连接方式,某个线程会一直被占据,即使没有传输数据,也需要一直等待到超时才会被释放。如果过多的线程,被这样占据,也会导致在高并发场景下的无服务线程可用。(该问题在prefork模式下,同样会发生)

1.4.1.3 Apache event 模型
Apache中最新的模式,2012年发布的apache 2.4.X系列正式支持event 模型,属于事件驱动模型(epoll),每个进程响应多个请求,在现在版本里的已经是稳定可用的模式。它和worker模式很像,最大的区别在于,它解决了keepalive场景下,长期被占用的线程的资源浪费问题(某些线程因为被keepalive,空挂在哪里等待,中间几乎没有请求过来,甚至等到超时)。event MPM中,会有一个专门的线程来管理这些keepalive类型的线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放。这样增强了高并发场景下的请求处理能力。
优点:单线程响应多请求,占据更少的内存,高并发下表现更优秀,会有一个专门的线程来管理keepalive类型的线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放,,可以基于epoll 更加高效的IO多路复用技术实现使用更少的线程支持更多的并发连接
缺点:没有线程安全控制

1.4.2 Nginx 高性能的 Web 服务端
Nginx是由俄罗斯国立莫斯科鲍曼科技大学在1994年毕业的学生为俄罗斯rambler.ru公司开发的,开发工作最早从2002年开始,第一次公开发布时间是2004年10月4日,版本号是0.1.0
2019年3月11日F5 与 NGINX达成协议,F5 将收购 NGINX 的所有已发行股票,总价值约为 6.7 亿美元。6.7亿美金约合44.97亿人民币,nginx核心模块代码长度198430(包括空格、注释),所以一行代码约为2.2万人民币
官网地址: http://www.nginx.org
Nginx历经十几年的迭代更新(https://nginx.org/en/CHANGES), 目前功能已经非常完善且运行稳定,分为社区版本和商业版,另外Nginx的社区版本分为开发版(奇数)、最新稳定版(偶数)和过期版,nginx以功能丰富著称,它即可以作为http服务器,也可以作为反向代理服务器或者邮件服务器,能够快速的响应静态网页的请求,支持FastCGI/SSL/Virtual Host/URL Rwrite/Gzip/HTTP Basic Auth/http或者TCP的负载均衡(1.9版本以上且开启stream模块)等功能,并且支持第三方的功能扩展。
天猫 淘宝 京东 小米 163 新浪等一线互联网公司都在用Nginx或者进行二次开发
基于Nginx的工作场景:

1.4.3 用户访问体验和性能
1.4.3.1 用户访问体验统计
互联网存在用户速度体验的1-3-10原则,即1秒最优,1-3秒较优,3~10秒比较慢,10秒以上用户无法接受。用户放弃一个产品的代价很低,只是换一个URL而已。

全球最大搜索引擎 Google:慢500ms = 20% 将放弃访问。
全球最大的电商零售网站亚马逊:慢100ms = 1% 将放弃交易
有很多研究都表明,性能对用户的行为有很大的影响:
79%的用户表示不太可能再次打开一个缓慢的网站
47%的用户期望网页能在2秒钟以内加载
40%的用户表示如果加载时间超过三秒钟,就会放弃这个网站
页面加载时间延迟一秒可能导致转换损失7%,页面浏览量减少11%
8秒定律:用户访问一个网站时,如果等待网页打开的时间超过8秒,会有超过30%的用户放弃等待
请珍惜每一毫秒的时间 !
1.4.3.2 影响用户体验的因素
客户端
- 客户端硬件配置
- 客户端网络速率
- 客户端与服务端距离.
服务器
- 服务端网络速率
- 服务端硬件配置
- 服务端架构设计
- 服务端应用程序工作模式
- 服务端并发数量
- 服务端响应文件大小及数量 buffer cache
- 服务端I/O压力
1.4.4 服务端 I/O 流程
I/O在计算机中指Input/Output, IOPS (Input/Output Per Second)即每秒的输入输出量(或读写次数),是衡量磁盘性能的主要指标之一。IOPS是指单位时间内系统能处理的I/O请求数量,一般以每秒处理的I/O请求数量为单位,I/O请求通常为读或写数据操作请求。
一次完整的I/O是用户空间的进程数据与内核空间的内核数据的报文的完整交换,但是由于内核空间与用户空间是严格隔离的,所以其数据交换过程中不能由用户空间的进程直接调用内核空间的内存数据,而是需要经历一次从内核空间中的内存数据copy到用户空间的进程内存当中,所以简单说I/O就是把数据从内核空间中的内存数据复制到用户空间中进程的内存当中。
Linux 的 I/O
- 磁盘I/O
- 网络I/O : 一切皆文件,本质为对socket文件的读写
1.4.4.1 磁盘 I/O
磁盘I/O是进程向内核发起系统调用,请求磁盘上的某个资源比如是html 文件或者图片,然后内核通过相应的驱动程序将目标文件加载到内核的内存空间,加载完成之后把数据从内核内存再复制给进程内存,如果是比较大的数据也需要等待时间
1.4.4.2 网络 I/O
网络通信就是网络协议栈到用户空间进程的IO就是网络IO

网络I/O 处理过程
获取请求数据,客户端与服务器建立连接发出请求,服务器接受请求(1-3)
构建响应,当服务器接收完请求,并在用户空间处理客户端的请求,直到构建响应完成(4)
返回数据,服务器将已构建好的响应再通过内核空间的网络 I/O 发还给客户端(5-7)
不论磁盘和网络I/O
每次I/O,都要经由两个阶段:
第一步:将数据从文件先加载至内核内存空间(缓冲区),等待数据准备完成,时间较长
第二步:将数据从内核缓冲区复制到用户空间的进程的内存中,时间较短
1.5 I/O 模型
1.5.1 I/O 模型相关概念
1.5.1.1 同步和异步
关注的是消息通信机制,即调用者在等待一件事情的处理结果时,被调用者是否提供完成状态的通知。
- 同步:synchronous,被调用者并不提供事件的处理结果相关的通知消息,需要调用者主动询问事情是否处理完成
- 异步:asynchronous,被调用者通过状态、通知或回调机制主动通知调用者被调用者的运行状态
1.5.1.2 阻塞和非阻塞
关注调用者在等待结果返回之前所处的状态
- 阻塞:blocking,指IO操作需要彻底完成后才返回到用户空间,调用结果返回之前,调用者被挂起,干不了别的事情。
- 非阻塞:nonblocking,指IO操作被调用后立即返回给用户一个状态值,而无需等到IO操作彻底完成,在最终的调用结果返回之前,调用者不会被挂起,可以去做别的事情。
1.5.2 网络 I/O 模型
阻塞型、非阻塞型、复用型、信号驱动型、异步
1.5.2.1 阻塞型 I/O 模型(blocking IO)

阻塞IO模型是最简单的I/O模型,用户线程在内核进行IO操作时被阻塞
用户线程通过系统调用read发起I/O读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作
用户需要等待read将数据读取到buffer后,才继续处理接收的数据。整个I/O请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够
优点:程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用 CPU 资源
缺点:每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销较大,apache 的prefork使用的是这种模式
同步阻塞:程序向内核发送I/O请求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回,则进程将一直等待并不再接受新的请求,并由进程轮询查看I/O是否完成,完成后进程将I/O结果返回给Client,在IO没有返回期间进程不能接受其他客户的请求,而且是有进程自己去查看I/O是否完成,这种方式简单,但是比较慢,用的比较少。
1.5.2.2 非阻塞型 I/O 模型 (nonblocking IO)

用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。即 “轮询”机制存在两个问题:如果有大量文件描述符都要等,那么就得一个一个的read。这会带来大量的Context Switch(read是系统调用,每调用一次就得在用户态和核心态切换一次)。轮询的时间不好把握。这里是要猜多久之后数据才能到。等待时间设的太长,程序响应延迟就过大;设的太短,就会造成过于频繁的重试,干耗CPU而已,是比较浪费CPU的方式,一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
非阻塞:程序向内核发送请I/O求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回IO结果,进程将不再等待,而且继续处理其他请求,但是仍然需要进程隔一段时间就要查看内核I/O是否完成。
查看上图可知,在设置连接为非阻塞时,当应用进程系统调用 recvfrom 没有数据返回时,内核会立即返回一个 EWOULDBLOCK 错误,而不会一直阻塞到数据准备好。如上图在第四次调用时有一个数据报准备好了,所以这时数据会被复制到 应用进程缓冲区 ,于是 recvfrom 成功返回数据
当一个应用进程这样循环调用 recvfrom 时,称之为轮询 polling 。这么做往往会耗费大量CPU时间,实际使用很少
1.5.2.3 I/O 多路复用型 (I/O multiplexing)
网络I/O复用是指利用操作系统提供的一种机制,同时监控多个I/O事件(如可读、可写、异常等),当有I/O事件发生时,通知应用程序进行相应的处理。
通常,一个应用程序需要对多个网络连接进行处理,传统的方法是使用多线程或多进程模型,为每个连接创建一个线程或进程进行处理。这种方法存在一些问题,例如线程或进程的创建和销毁需要消耗大量的系统资源,且容易导致线程或进程的数量过多,进而导致系统崩溃或运行缓慢。
相比于传统的多线程或多进程模型,网络I/O复用模型可以有效地减少线程或进程的数量,从而提高系统的性能和稳定性。通过网络I/O复用模型,应用程序可以通过一个线程或进程同时监控多个网络连接,当有网络I/O事件发生时,操作系统会通知应用程序进行相应的处理,从而实现多个网络连接的并发处理。
上面的模型中,每一个文件描述符对应的IO是由一个线程监控和处理,即如果一个I/O流进来,就开启一个进程处理这个I/O流。那么假设现在有一百万个I/O流进来,那就需要开启一百万个进程一一对应处理这些I/O流(——这就是传统意义下的多进程并发处理)。一百万个进程,CPU占有率会多高,所以提出了I/O多路复用这个模型,一个线程,通过记录I/O流的状态来同时管理多个I/O,可以提高服务器的吞吐能力
多路复用IO指一个线程可以同时(实际是交替实现,即并发完成)监控和处理多个文件描述符对应各自的IO,即复用同一个线程
一个线程之所以能实现同时处理多个IO,是因为这个线程调用了内核中的SELECT,POLL或EPOLL等系统调用,从而实现多路复用IO

I/O multiplexing 主要包括:select,poll,epoll三种系统调select/poll/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。
它的基本原理就是select/poll/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
在Apache中,MPM模块可以使用select或epoll技术来实现并发连接的处理。具体来说,当Apache使用Prefork模型时,它使用阻塞式I/O来处理连接,而在Worker和Event模型中,它们使用select或epoll来实现并发连接的非阻塞I/O处理。
IO多路复用(IO Multiplexing) :是一种机制,程序注册一组socket文件描述符给操作系统,表示“我要监视这些fd是否有IO事件发生,有了就告诉程序处理”
IO多路复用一般和NIO一起使用的。NIO和IO多路复用是相对独立的。NIO仅仅是指IO API总是能立刻返回,不会被Blocking;而IO多路复用仅仅是操作系统提供的一种便利的通知机制。操作系统并不会强制这俩必须得一起用,可以只用IO多路复用 + BIO,这时还是当前线程被卡住。IO多路复用和NIO是要配合一起使用才有
实际意义
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,就通知该进程
多个连接共用一个等待机制,本模型会阻塞进程,但是进程是阻塞在select或者poll这两个系统调用上,而不是阻塞在真正的IO操作上
用户首先将需要进行IO操作添加到select中,同时等待select系统调用返回。当数据到达时,IO被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视IO,以及调用select函数的额外操作,效率更差。并且阻塞了两次,但是第一次阻塞在select上时,select可以监控多个IO上是否已有IO操作准备就绪,即可达到在同一个线程内同时处理多个IO请求的目的。而不像阻塞IO那种,一次只能监控一个IO
虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只是注册自己需要的IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率
IO多路复用是最常使用的IO模型,但是其异步程度还不够“彻底”,因它使用了会阻塞线程的select系统调用。因此IO多路复用只能称为异步阻塞IO模型,而非真正的异步IO
优缺点
- 优点:可以基于一个阻塞对象,同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程),这样可以大大节省系统资源
- 缺点:当连接数较少时效率相比多线程+阻塞 I/O 模型效率较低,可能延迟更大,因为单个连接处理需要 2 次系统调用,占用时间会有增加
IO多路复用适用如下场合:
- 当客户端处理多个描述符时(一般是交互式输入和网络套接口),必须使用I/O复用
- 当一个客户端同时处理多个套接字时,此情况可能的但很少出现
- 当一个服务器既要处理监听套接字,又要处理已连接套接字,一般也要用到I/O复用
- 当一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用
- 当一个服务器要处理多个服务或多个协议,一般要使用I/O复用
1.5.2.4 信号驱动式 I/O 模型 (signal-driven IO)

信号驱动I/O的意思就是进程现在不用傻等着,也不用去轮询。而是让内核在数据就绪时,发送信号通知进程。
调用的步骤是,通过系统调用 sigaction ,并注册一个信号处理的回调函数,该调用会立即返回,然后主程序可以继续向下执行,当有I/O操作准备就绪,即内核数据就绪时,内核会为该进程产生一个 SIGIO信号,并回调注册的信号回调函数,这样就可以在信号回调函数中系统调用 recvfrom 获取数据,将用户进程所需要的数据从内核空间拷贝到用户空间
此模型的优势在于等待数据报到达期间进程不被阻塞。用户主程序可以继续执行,只要等待来自信号处理函数的通知。
在信号驱动式 I/O 模型中,应用程序使用套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞
当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。
优点:线程并没有在等待数据时被阻塞,内核直接返回调用接收信号,不影响进程继续处理其他请求因此可以提高资源的利用率
缺点:信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知
异步阻塞:程序进程向内核发送IO调用后,不用等待内核响应,可以继续接受其他请求,内核收到进程请求后进行的IO如果不能立即返回,就由内核等待结果,直到IO完成后内核再通知进程。
1.5.2.5 异步 I/O 模型 (asynchronous IO)

异步I/O 与 信号驱动I/O最大区别在于,信号驱动是内核通知用户进程何时开始一个I/O操作,而异步I/O是由内核通知用户进程I/O操作何时完成,两者有本质区别,相当于不用去饭店场吃饭,直接点个外卖,把等待上菜的时间也给省了
相对于同步I/O,异步I/O不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。
信号驱动IO当内核通知触发信号处理程序时,信号处理程序还需要阻塞在从内核空间缓冲区拷贝数据到用户空间缓冲区这个阶段,而异步IO直接是在第二个阶段完成后,内核直接通知用户线程可以进行后续操作了
优点:异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠
缺点:要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的异步 I/O,在 Linux 系统下,Linux 2.6才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编程时以 IO 复用模型模式+多线程任务的架构基本可以满足需求
Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent、libev、libuv。
异步非阻塞:程序进程向内核发送IO调用后,不用等待内核响应,可以继续接受其他请求,内核调用的IO如果不能立即返回,内核会继续处理其他事物,直到IO完成后将结果通知给内核,内核在将IO完成的结果返回给进程,期间进程可以接受新的请求,内核也可以处理新的事物,因此相互不影响,可以实现较大的同时并实现较高的IO复用,因此异步非阻塞使用最多的一种通信方式。
1.5.3 五种 IO 对比
这五种 I/O 模型中,越往后,阻塞越少,理论上效率也是最优前四种属于同步 I/O,因为其中真正的 I/O操作(recvfrom)将阻塞进程/线程,只有异步 I/O 模型才与 POSIX 定义的异步 I/O 相匹配

1.5.4 I/O 的具体实现方式
1.5.4.1 I/O常见实现
网络I/O复用模型常见的机制包括select、poll、epoll等。这些机制都提供了一种高效的方式来同时监控多个网络连接,从而提高系统的性能和稳定性。
Nginx支持在多种不同的操作系统实现不同的事件驱动模型,但是其在不同的操作系统甚至是不同的系统版本上面的实现方式不尽相同,主要有以下实现方式:
1、select:
select库是在linux和windows平台都基本支持的 事件驱动模型库,并且在接口的定义也基本相同,只是部分参数的含义略有差异,最大并发限制1024,是最早期的事件驱动模型。I/O多路复用这个概念被提出来以后, select于1983年第一个在BSD里面实现
2、poll:
在Linux 的基本驱动模型,windows不支持此驱动模型,是select的升级版,取消了最大的并发限制,在编译nginx的时候可以使用--with-poll_module和--without-poll_module这两个指定是否编译select库。poll出现在1997年
3、epoll:
epoll是库是Nginx服务器支持的最高性能的事件驱动库之一,是公认的非常优秀的事件驱动模型,它和select和poll有很大的区别,epoll是poll的升级版,但是与poll有很大的区别.在2002年Davide Libenzi 实现了epoll.
epoll的处理方式是创建一个待处理的事件列表,然后把这个列表发给内核,返回的时候在去轮询检查这个表,以判断事件是否发生,epoll支持一个进程打开的最大事件描述符的上限是系统可以打开的文件的最大数,同时epoll库的I/O效率不随描述符数目增加而线性下降,因为它只会对内核上报的“活跃”的描述符进行操作。
4、kqueue:
用于支持BSD系列平台的高校事件驱动模型,主要用在FreeBSD 4.1及以上版本、OpenBSD 2.0级以上版本,NetBSD级以上版本及Mac OS X 平台上,该模型也是poll库的变种,因此和epoll没有本质上的区别,效效率和epoll相似
5、IOCP:
Windows系统上的实现方式,对应第5种(异步I/O)模型。
6、/dev/poll:
用于支持unix衍生平台的高效事件驱动模型,主要在Solaris 平台、HP/UX,该模型是sun公司在开发Solaris系列平台的时候提出的用于完成事件驱动机制的方案,它使用了虚拟的/dev/poll设备,开发人员将要见识的文件描述符加入这个设备,然后通过ioctl()调用来获取事件通知,因此运行在以上系列平台的时候
请使用/dev/poll事件驱动机制。效率和epoll相似
7、rtsig:
不是一个常用事件驱动,最大队列1024,不是很常用
8、eventport:
该方案也是sun公司在开发Solaris的时候提出的事件驱动库,只是Solaris 10以上的版本,该驱动库可防止内核崩溃等情况的发生。
1.5.4.2 常用I/O实现比较


上图中测试工具为deadcon. 横轴Dead connections 就是链接数,纵轴是每秒处理请求的数量
可以看到,epoll每秒处理请求的数量基本不会随着链接变多而下降的,poll 和/dev/poll 相对就很差了。

Select:
实现过程
1. 从用户空间将fd_set拷贝到内核空间
2. 注册回调函数
3. 调用其对应的poll方法
4. poll方法会返回一个描述读写是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。
5. 如果遍历完所有的fd都没有返回一个可读写的mask掩码,就会让select的进程进入休眠模式,直到发现可读写的资源后,重新唤醒等待队列上休眠的进程。如果在规定时间内都没有唤醒休眠进程,那么进程会被唤醒重新获得CPU,再去遍历一次fd。
6. 将fd_set从内核空间拷贝到用户空间
优点:
POSIX所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点
缺点
单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义
FD_SETSIZE,再重新编译内核实现,但是这样也会造成效率的降低
单个进程可监视的fd数量被限制,默认是1024,修改此值需要重新编译内核
对socket是线性扫描,即采用轮询的方法,效率较低
select 采取了内存拷贝方法来实现内核将 FD 消息通知给用户空间,这样一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
poll:
实现过程本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态其没有最大连接数的限制,原因是它是基于链表来存储的
大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义
poll特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd
select是边缘触发即只通知一次
epoll:
在Linux 2.6内核中提出的select和poll的增强版本
支持水平触发LT和边缘触发ET
最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次
使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
优点:
1)没有最大并发连接的限制:能打开的FD的上限远大于1024(1G的内存能监听约10万个端口),具体查看/proc/sys/fs/file-max,此值和系统内存大小相关
2)效率提升:非轮询的方式,不会随着FD数目的增加而效率下降;只有活跃可用的FD才会调用callback函数,即epoll最大的优点就在于它只管理“活跃”的连接,而跟连接总数无关
3)内存拷贝,利用mmap(Memory Mapping)加速与内核空间的消息传递;即epoll使用mmap减少复制开销
缺点:
只有linux支持。在BSD上面对应的实现是kqueue。
总结:
1、epoll只是一组API,比起select这种扫描全部的文件描述符,epoll只读取就绪的文件描述符
2、epoll比select等多路复用方式来说,减少了遍历循环及内存拷贝的工作量,因为活跃连接只占总并发连接的很小一部分。
3、epoll基于事件的就绪通知机制,使用回调机制,所以性能比较好
4、基于epoll的IO多路复用减少了进程间切换的次数,使得操作系统少做了相对于用户任务来说的无用功。
1.5.4.3 I/O 选择
- 完全跨平台,可以使用select、poll。但是性能较差
- 针对不同操作系统自行选择支持的最优技术,比如: Linux选用epoll,Mac选用kqueue,Windows 选用IOCP,提高IO处理的性能
范例: 最大并发连接数和内存有直接关系
#内存1G
[root@centos8 ~]#free -h
total used free shared buff/cache available
Mem: 952Mi 168Mi 605Mi 12Mi 178Mi 629Mi
Swap: 2.0Gi 0B 2.0Gi
[root@centos8 ~]#cat /proc/sys/fs/file-max
92953
#内存2G
[root@centos8 ~]#free -h
total used free shared buff/cache available
Mem: 1.9Gi 258Mi 1.3Gi 12Mi 341Mi 1.6Gi
Swap: 2.0Gi 0B 2.0Gi
[root@centos8 ~]#cat /proc/sys/fs/file-max
195920
范例: 内核限制
#内核源码目录搜索
[root@centos8 ~]#grep -R FD_SETSIZE linux-5.8/*
linux-5.8/Documentation/userspace-api/media/v4l/func-select.rst:
``FD_SETSIZE``.
linux-5.8/include/uapi/linux/posix_types.h:#undef __FD_SETSIZE
linux-5.8/include/uapi/linux/posix_types.h:#define __FD_SETSIZE 1024 #单个进程能够监视的文件描述符的文件最大数量
linux-5.8/include/uapi/linux/posix_types.h: unsigned long fds_bits[__FD_SETSIZE
/ (8 * sizeof(long))];
linux-5.8/tools/include/nolibc/nolibc.h:#define FD_SETSIZE 256
linux-5.8/tools/include/nolibc/nolibc.h:typedef struct { uint32_tfd32[FD_SETSIZE/32]; } fd_set;
linux-5.8/tools/include/nolibc/nolibc.h: if (fd < 0 || fd >= FD_SETSIZE)
linux-5.8/tools/testing/selftests/net/nettest.c: rc = select(FD_SETSIZE,
NULL, &wfd, NULL, tv);
1.6 零拷贝
1.6.1 零拷贝介绍
1.6.1.1 传统 Linux中 I/O 的问题

传统的 Linux 系统的标准 I/O 接口(read、write)是基于数据拷贝的,也就是数据都是 copy_to_user或者 copy_from_user,这样做的好处是,通过中间缓存的机制,减少磁盘 I/O 的操作,但是坏处也很明显,大量数据的拷贝,用户态和内核态的频繁切换,会消耗大量的 CPU 资源,严重影响数据传输的性能,统计表明,在Linux协议栈中,数据包在内核态和用户态之间的拷贝所用的时间甚至占到了数据包整个处理流程时间的57.1%
1.6.1.2 什么是零拷贝
零拷贝就是上述问题的一个解决方案,通过尽量避免拷贝操作来缓解 CPU 的压力。零拷贝并没有真正做到“0”拷贝,它更多是一种思想,很多的零拷贝技术都是基于这个思想去做的优化
1.6.2 零拷页相关技术
1.6.2.1 MMAP ( Memory Mapping )

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问。
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
内存映射减少数据在用户空间和内核空间之间的拷贝操作,适合大量数据传输


上面左图为传统读写,右图为MMAP.两者相比mmap要比普通的read系统调用少了一次copy的过程。因为read调用,进程是无法直接访问kernel space的,所以在read系统调用返回前,内核需要将数据从内核复制到进程指定的buffer。但mmap之后,进程可以直接访问mmap的数据(page cache)。
1.6.2.2 SENDFILE

SENDFILE的主要作用是高效地在文件和套接字之间传输数据,避免了数据在用户空间和内核空间之间的多次拷贝,从而提升了数据传输的效率。在传统的 I/O 操作里,数据要从磁盘读取到内核空间,接着再从内核空间复制到用户空间的缓冲区,最后从用户空间的缓冲区复制到套接字的内核缓冲区。而sendfile系统调用可以直接在内核空间完成数据从文件到套接字的传输,减少了两次数据拷贝过程,极大地提高了传输效率。
1.6.2.3 DMA辅助的 SENDFILE

DMA 控制器能够直接在设备(如磁盘)和内存之间传输数据,无需 CPU 的持续干预。这样一来,在数据传输期间,CPU 可以去处理其他任务,从而提高了系统的整体性能。
由于减少了 CPU 的参与,DMAsendfile能够更快速地完成数据传输,尤其是在处理大量数据时,优势更为明显。
DMAsendfile的实现依赖于硬件的支持和操作系统的优化。只有当硬件具备 DMA 功能,并且操作系统对其进行了合理的调度和管理时,才能发挥出 DMAsendfile的优势。

浙公网安备 33010602011771号