【Node 基础】Web服务器开发和文件上传
原文来自 我的个人博客1. 创建服务器什么是 Web 服务器?当应用程序(客户端)需要某个资源时,可以向一台服务器,通过 HTTP 请求获取到这个资源提供资源的这个服务器,就是一个 Web 服务器目前有很多开源的 Web 服务器:Nginx、Apache(静态)、Apache Tomcat(静态、动态)、Node.js在 Node 中,提供 web 服务器的资源返回给浏览器,主要是通过 http 模块。我们先简单对它做一个使用:const http = require("http");
const HTTP_PORT = 9000;
const server = http.createServer((req, res) => {
res.end("Hello World");
});
server.listen(HTTP_PORT, () => {
console.log(`服务器在${HTTP_PORT}启动`);
});此时我们在浏览器中输入 localhost:9000,就会出现 Hello World:解释上面这段代码:通过 http 模块的 createServer 方法创建了一个服务器对象,它的底层其实是直接使用 new Server 创建对象的。那么当然,我们也可以自己来创建这个对象:const server = new http.Server((req, res) => {
res.end("Hello World");
});
server.listen(HTTP_PORT, () => {
console.log(`服务器在${HTTP_PORT}启动`);
});创建 Server 时会传入一个回调函数,这个回调函数在被调用时会传入两个参数:req:request 请求对象,包含请求相关的信息;res:response 响应对象,包含我们要发送给客户端的信息;Server 通过 listen 方法来开启服务器,并且在某一个主机的端口上监听网络请求也就是当我们通过 ip:port 的方式发送到我们监听的 Web 服务器上时,我们就可以对其进行相关的处理;listen 函数有三个参数:端口 port:可以不传,系统会默认分配端主机 host:通常可以传入 localhost、ip地址127.0.0.1、或者 ip地址0.0.0.0,默认是 0.0.0.0;localhost:本质上是一个域名,通常情况下会被解析成 127.0.0.1;127.0.0.1:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收;正常的数据包会经过 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 ;而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;比如我们监听 127.0.0.1 时,在同一个网段下的主机中,通过 ip地址 是不能访问的;0.0.0.0:监听 IPV4 上所有的地址,再根据端口找到不同的应用程序;比如我们监听 0.0.0.0 时,在同一个网段下的主机中,通过 ip 地址是可以访问的;回调函数:服务器启动成功时的回调函数;2. Request 请求2.1 Request 对象在向服务器发送请求时,我们会携带很多信息,比如:本次请求的 URL,服务器需要根据不同的 URL 进行不同的处理;本次请求的请求方式,比如 GET、POST 请求传入的参数和处理的方式是不同的;本次请求的 headers 中也会携带一些信息,比如客户端信息、接收数据的格式、支持的编码格式等;等等...这些信息,Node 会帮助我们封装到一个 request 的对象中,我们可以直接来处理这个 request 对象:const server = new http.Server((req, res) => {
// request 对象
console.log(req)
console.log(req.method)
console.log(req.headers)
res.end("Hello World");
});2.2 URL 的处理客户端在发送请求时,会请求不同的数据,那么会传入不同的请求地址:比如 http://localhost:9000/login;比如 http://localhost:9000/products;服务器端需要根据不同的请求地址,作出不同的响应:const server = new http.Server((req, res) => {
const url = req.url;
if (url === "/login") {
res.end("welcome Back~");
} else if (url === "/products") {
res.end("products");
} else {
res.end("error message");
}
});2.3 URL 的解析如果用户发送的地址中还携带一些额外的参数,例如以下情况http://localhost:9000/login?name=why&password=123;这个时候,url 的值是 /login?name=why&password=123;这个时候我们可以使用内置模块 url 处理 const parseInfo = url.parse(req.url);
console.log(parseInfo)此时的 parseInfo 为:接下来我们就可以直接用 qs 或者 URLSearchParams 处理 query 拿到参数了const queryObj = new URLSearchParams(parseInfo.query);
console.log(queryObj.get("name"));
console.log(queryObj.get("password"));2.4 请求头在 request 对象的 header 中也包含很多有用的信息,客户端会默认传递过来一些信息:content-type 是这次请求携带的数据的类型:application/x-www-form-urlencoded:表示数据被编码成以 '&' 分隔的键 - 值对,同时以 '=' 分隔键和值application/json:表示是一个 json 类型;text/plain:表示是文本类型application/xml:表示是 xml 类型;multipart/form-data:表示是上传文件;content-length:文件的大小长度keep-alive:http 是基于 TCP 协议的,但是通常在进行一次请求和响应结束后会立刻中断;在 http1.0 中,如果想要继续保持连接:浏览器需要在请求头中添加 connection: keep-alive;服务器需要在响应头中添加 connection: keep-alive;当客户端再次放请求时,就会使用同一个连接,直接一方中断连接;在 http1.1 中,所有连接默认是 connection: keep-alive 的;不同的 Web 服务器会有不同的保持 keep-alive 的时间;Node 中默认是 5s 中;accept-encoding:告知服务器,客户端支持的文件压缩格式,比如 js 文件可以使用 gzip 编码,对应 .gz 文件;accept:告知服务器,客户端可接受文件的格式类型;user-agent:客户端相关的信息;3. Response 响应3.1 返回响应结果如果我们希望给客户端响应的结果数据,可以通过两种方式:write方法:这种方式是直接写出数据,但是并没有关闭流;end方法:这种方式是写出最后的数据,并且写出后会关闭流;// 响应数据的两个方式
res.write('Hello World)
res.write("Hello Response")
res.edn("message end")如果我们没有调用 end 和 close,客户端将会一直等待结果:所以客户端在发送网络请求时,都会设置超时时间。3.2 返回状态码Http状态码(Http Status Code)是用来表示 Http 响应状态的数字代码:Http状态码 非常多,可以根据不同的情况,给客户端返回不同的状态码;MDN 响应码解析地址:https://developer.mozilla.org/zh-CN/docs/web/http/status常见HTTP状态码状态描述信息说明200OK客户端请求成功201CreatedPOST请求,创建新的资源301Moved Permanently请求资源的URL已经修改,响应中会给出新的URL400Bad Request客户端的错误,服务器无法或者不进行处理401Unauthorized未授权的错误,必须携带请求的身份信息403Forbidden客户端没有权限访问,被拒接404Not Found服务器找不到请求的资源500Internal Server Error服务器遇到了不知道如何处理的情况。503Service Unavailable服务器不可用,可能处于维护或者重载状态,暂时无法访问// 1.
res.statusCode = 400
// 2.
res.writeHead(200)3.3 响应头文件返回头部信息,主要有两种方式:res.setHeader:一次写入一个头部信息;res.writeHead:同时写入 header 和 status;res.setHeader('Context-Type', 'application/json;charset=utf8')
res.writeHead(200, {
"Content-Type": "application/json;charset=utf8"
})Header 设置 Content-Type 有什么作用呢?默认客户端接收到的是字符串,客户端会按照自己默认的方式进行处理;4. 文件上传其实对于后端,手动处理上传的文件是很复杂的,正常情况下我们都会借助于一些插件做处理,下面仅做对于图片上传的一个简单的演示。const http = require("http");
const qs = require("querystring");
const fs = require("fs");
const HTTP_PORT = 9000;
// 1. 创建 sever 服务器
const server = new http.Server((req, res) => {
// 文件设置为二进制
req.setEncoding("binary");
// 获取 content-type 中的 boundary的值
let boundary = req.headers["content-type"]
.split("; ")[1]
.replace("boundary=", "");
const fileSize = req.headers["content-length"];
let curSize = 0;
let body = "";
req.on("data", (data) => {
curSize += data.length;
res.write(`文件上传进度:${(curSize / fileSize) * 100}%\n`);
body += data;
});
req.on("end", () => {
// 切割数据
const payload = qs.parse(body, "\r\n", ":");
// 获取最后的类型(image/png)
const fileType = payload["Content-Type"].substring(1);
// 获取要截取的长度
const fileTypePosition = body.indexOf(fileType) + fileType.length;
let binaryData = body.substring(fileTypePosition);
binaryData = binaryData.replace(/^\s\s*/, "");
const finalData = binaryData.substring(
0,
binaryData.indexOf("--" + boundary + "--")
);
fs.writeFile("./foo.png", finalData, "binary", (err) => {
console.log(err);
res.end("文件上传完成~");
});
});
});
// 2. 开启 server 服务器
server.listen(HTTP_PORT, () => {
console.log(`服务器在${HTTP_PORT}启动`);
});
使用 postman 测试,并查看图片能显示。
前端重点:HTTP协议
原文来自我的个人博客1. HTTP 基础1.1 HTTP 协议是什么?HTTP (HyperText Transfer Protocol),即超文本运输协议,是实现网络通信的一种规范在计算机和网络世界有,存在不同的协议,如广播协议、寻址协议、路由协议等等......而 HTTP 是一个传输协议,即将数据由 A 传到 B 或将 B 传输到 A ,并且 A 与 B 之间能够存放很多第三方,如: A<=>X<=>Y<=>Z<=>B传输的数据并不是计算机底层中的二进制包,而是完整的、有意义的数据,如 HTML 文件, 图片文件, 查询结果等超文本,能够被上层应用识别在实际应用中,HTTP 常被用于在 Web浏览器 和 网站服务器 之间传递信息,以明文方式发送内容,不提供任何方式的数据加密1.2 HTTP 协议的特点应用层协议(下面可以是 TCP/IP)信息纯文本传输支持客户/服务器模式简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于 HTTP 协议简单,使得HTTP 服务器的程序规模小,因而通信速度很快灵活:HTTP 允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记无状态:每次请求独立,请求之间互相不影响(浏览器提供了手段维护状态比如Cookie, Session, Storage 等)1.3 HTTP 协议的历史1991 HTTP 0.9 (实验版本)1996 HTTP 1.0 (有广泛用户)1999 HTTP 1.1 (影响面最大的版本)2015 HTTP 2.0 (大公司基本上都是2.0了)2018 HTTP 3.0 (2022 年 6 月 6 日正式发布了)1.4 Header 和 BodyHTTP 是一个文本传输协议,传输内容是人类可读的文本,大体分成两部分:请求头(Header)/ 返回头消息体(Body)下面演示一个 Node.js 实战 http 请求const net = require("net");
const response = `
HTTP/1.1 200 OK
Data: Tue,30 Jun 2022 01:00:00 GMT
Content-Type: text/plain
Connection: Closed
Hello World
`;
const server = net.createServer((socket) => {
socket.end(response);
});
server.listen(80, () => {
console.log("80端口启动");
});
从浏览器中观察1.5. HTTPS在上述介绍 HTTP 中,了解到 HTTP 传递信息是以明文的形式发送内容,这并不安全。而 HTTPS 出现正是为了解决 HTTP 不安全的特性为了保证这些隐私数据能加密传输,让 HTTP 运行安全的 SSL/TLS 协议上,即 HTTPS = HTTP + SSL/TLS,通过 SSL 证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密SSL 协议位于 TCP/IP 协议与各种应用层协议之间,浏览器和服务器在使用 SSL 建立连接时需要选择一组恰当的加密算法来实现安全通信,为数据通讯提供安全支持流程图如下:1.6. HTTP 和 HTTPS 的区别HTTPS 是 HTTP 协议的安全版本,HTTP 协议的数据传输是明文的,是不安全的,HTTPS 使用了 SSL/TLS 协议进行了加密处理,相对更安全HTTP 和 HTTPS 使用连接方式不同,默认端口也不一样,HTTP 是 80,HTTPS 是 443HTTPS 由于需要设计加密以及多次握手,性能方面不如 HTTPHTTPS需要 SSL,SSL 证书需要钱,功能越强大的证书费用越高2. HTTP 详情2.1. HTTP 常见请求头2.1.1 请求头是什么?HTTP 头字段(HTTP header fields),是指在超文本传输协议(HTTP)的请求和响应消息中的消息头部分它们定义了一个超文本传输协议事务中的操作参数HTTP 头部字段可以自己根据需要定义,因此可能在 Web 服务器和浏览器上发现非标准的头字段下面是一个 HTTP 常见的请求头:下面是一个 HTTP 常见的响应头2.2 常见HTTP头2.2.1 Content-Length发送给接受者的 Body 内容长度(字节)一个 byte 是 8bitUTF-8编码的字符1-4个字节、示例:Content-Length: 3482.2.2 User-Agent帮助区分客户端特性的字符串
- 操作系统
- 浏览器
- 制造商(手机类型等)
- 内核类型
- 版本号
示例:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.362.2.3 Content-Type帮助区分资源的媒体类型(Media Type/MIME Type)text/htmltext/cssapplication/jsonimage/jpeg示例:Content-Type: application/x-www-form-urlencoded2.2.4 Origin:描述请求来源地址scheme://host:port不含路径可以是null示例: Origin: https://yewjiwei.com2.2.5 Accept建议服务端返回何种媒体类型(MIME Type)/代表所有类型(默认)多个类型用逗号隔开衍生的还有Accept-Charset能够接受的字符集 示例:Accept-Charset: utf-8Accept-Encoding能够接受的编码方式列表 示例:Accept-Encoding: gzip, deflateAccept-Language能够接受的回应内容的自然语言列表 示例:Accept-Language: en-US示例:Accept: text/plainAccept-Charset: utf-8Accept-Encoding: gzip, deflate2.2.6 Referer告诉服务端打开当前页面的上一张页面的URL;如果是ajax请求那么就告诉服务端发送请求的URL是什么非浏览器环境有时候不发送Referer常常用户行为分析2.2.7 Connection决定连接是否在当前事务完成后关闭HTTP1.0默认是closeHTTP1.1后默认是keep-alive2.2.8 Authorization用于超文本传输协议的认证的认证信息示例: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==2.2.9 Cache-Control用来指定在这次的请求/响应链中的所有缓存机制 都必须 遵守的指令示例: Cache-Control: no-cache2.2.10 Date发送该消息的日期和时间示例: Date: Tue, 15 Nov 1994 08:12:31 GMT2.3. 基本方法GET 从服务器获取资源(一个网址url代表一个资源)POST 在服务器创建资源PUT 在服务器修改资源DELETE 在服务器删除资源OPTIONS 跟跨域相关TRACE 用于显示调试信息CONNECT 代理PATCH 对资源进行部分更新(极少用)2.4. 状态码1XX: 提供信息100 continue 情景:客户端向服务端传很大的数据,这个时候询问服务端,如果服务端返回100,客户端就继续传 (历史,现在比较少了)101 协议切换switch protocolHTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade告诉客户端把协议切换为Websocket2xx: 成功200 Ok 正常的返回成功 通常用在GET201 Created 已创建 通常用在POST202 Accepted 已接收 比如发送一个创建POST请求,服务端有些异步的操作不能马上处理先返回202,结果需要等通知或者客户端轮询获取203 Non-Authoritative Infomation 非权威内容 原始服务器的内容被修改过204 No Content 没有内容 一般PUT请求修改了但是没有返回内容205 Reset Content 重置内容206 Partial Content 服务端下发了部分内容3XX: 重定向300 Multiple Choices 用户请求了多个选项的资源(返回选项列表)301 Moved Permanently 永久转移302 Found 资源被找到(以前是临时转移)不推荐用了 302拆成了303和307303 See Other 可以使用GET方法在另一个URL找到资源304 Not Modified 没有修改305 Use Proxy 需要代理307 Temporary Redirect 临时重定向 (和303的区别是,307使用原请求的method重定向资源, 303使用GET方法重定向资源)308 Permanent Redirect 永久重定向 (和301区别是 客户端接收到308后,之前是什么method,之后也会沿用这个method到新地址。301,通常给用户会向新地址发送GET请求)4XX: 客户端错误400 Bad Request 请求格式错误401 Unauthorized 没有授权402 Payment Required 请先付费403 Forbidden 禁止访问404 Not Found 没有找到405 Method Not Allowed 方法不允许406 Not Acceptable 服务端可以提供的内容和客户端期待的不一样5XX: 服务端错误500 Internal Server Error 内部服务器错误501 Not Implemented 没有实现502 Bad Gateway 网关错误503 Service Unavailable 服务不可用 (内存用光了,线程池溢出,服务正在启动)504 Gateway Timeout 网关超时505 HTTP Version Not Supported 版本不支持3. TCP vs UDP3.1. TCPTCP(Transmission Control Protocol),传输控制协议,是一种可靠、面向字节流的通信协议,把上面应用层交下来的数据看成无结构的字节流来发送。可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP会根据当前网络的拥塞状态来确定每个报文段的大小。TCP 报文首部有 20 个字节,额外开销大。特点如下:TCP充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)3.2 UDPUDP(User Datagram Protocol),用户数据包协议,是一个简单的面向数据报的通信协议,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层也就是说无论应用层交给UDP多长的报文,它统统发送,一次发送一个报文而对接收方,接到后直接去除首部,交给上面的应用层就完成任务UDP报头包括 4 个字段,每个字段占用 2 个字节(即 16 个二进制位),标题短,开销小特点如下:UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务传输途中出现丢包,UDP 也不负责重发当包的到达顺序出现乱序时,UDP没有纠正的功能。并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况,UDP 也无法进行流量控制等避免网络拥塞行为3.3 区别UDP 与 TCP 两者的都位于传输层,如下图所示:两者区别如下表所示:标题TCPUDP可靠性可靠不可靠连接性面向连接无连接报文面向字节流面向报文效率传输效率低传输效率高双共性全双工一对一、一对多、多对一、多对多流量控制滑动窗口无拥塞控制慢开始、拥塞避免、快重传、快恢复无传输效率慢快TCP 是面向连接的协议,建立连接3次握手、断开连接四次挥手,UDP是面向无连接,数据传输前后不连接连接,发送端只负责将数据发送到网络,接收端从消息队列读取TCP 提供可靠的服务,传输过程采用流量控制、编号与确认、计时器等手段确保数据无差错,不丢失。UDP 则尽可能传递数据,但不保证传递交付给对方TCP 面向字节流,将应用层报文看成一串无结构的字节流,分解为多个TCP报文段传输后,在目的站重新装配。UDP协议面向报文,不拆分应用层报文,只保留报文边界,一次发送一个报文,接收方去除报文首部后,原封不动将报文交给上层应用TCP 只能点对点全双工通信。UDP 支持一对一、一对多、多对一和多对多的交互通信两者应用场景如下图:可以看到,TCP 应用场景适用于对效率要求低,对准确性要求高或者要求有链接的场景,而UDP 适用场景为对效率要求高,对准确性要求低的场景4. HTTP 1.0 / 1.1 / 2.0 / 3.04.1. HTTP1.0HTTP协议的第二个版本,第一个在通讯中指定版本号的HTTP协议版本HTTP 1.0 浏览器与服务器只保持短暂的连接,每次请求都需要与服务器建立一个TCP连接服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求简单来讲,每次与服务器交互,都需要新开一个连接例如,解析html文件,当发现文件中存在资源文件的时候,这时候又创建单独的链接最终导致,一个html文件的访问包含了多次的请求和响应,每次请求都需要创建连接、关系连接这种形式明显造成了性能上的缺陷如果需要建立长连接,需要设置一个非标准的Connection字段 Connection: keep-alive4.2. HTTP1.1在HTTP1.1中,默认支持长连接(Connection: keep-alive),即在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟建立一次连接,多次请求均由这个连接完成这样,在加载html文件的时候,文件中多个请求和响应就可以在一个连接中传输同时,HTTP 1.1还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间同时,HTTP1.1在HTTP1.0的基础上,增加更多的请求头和响应头来完善的功能,如下:引入了更多的缓存控制策略,如If-Unmodified-Since, If-Match, If-None-Match等缓存头来控制缓存策略引入range,允许值请求资源某个部分引入host,实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点并且还添加了其他的请求方法:put、delete、options...4.3. HTTP2.0HTTP2.0在相比之前版本,性能上有很大的提升,添加了如下特性多路复用二进制分帧首部压缩服务器推送4.3.1 多路复用HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”上图中,可以看到第四步中css、js资源是同时发送到服务端4.3.2 二进制分帧帧是HTTP2通信中最小单位信息HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x的文本格式,解析起来更高效将请求和响应数据分割为更小的帧,并且它们采用二进制编码HTTP2中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装,这也是多路复用同时发送数据的实现条件4.3.3 首部压缩HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新例如:下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销4.3.4 服务器推送HTTP2引入服务器推送,允许服务端推送资源给客户端服务器会顺便把一些客户端需要的资源一起推送到客户端,如在响应一个页面请求中,就可以随同页面的其它资源免得客户端再次创建连接发送请求到服务器端获取这种方式非常合适加载静态资源4.4. HTTP3.0我们都知道,HTTP是应用层协议,应用层产生的数据会通过传输层协议作为载体来传输到互联网上的其他主机中,而其中的载体就是 TCP 协议,这是 HTTP 2 之前的主流模式。但是随着 TCP 协议的缺点不断暴露出来, HTTP 3.0 毅然决然切断了和 TCP 的联系,转而拥抱了 UDP 协议,这么说不太准确,其实 HTTP 3.0 其实是拥抱了 QUIC 协议,而 QUIC 又被叫做快速 UDP 互联网连接,QUIC的一个特征就是快。QUIC 具有下面这些优势使用 UDP 协议,不需要三次连接进行握手,而且也会缩短 TLS 建立连接的时间。解决了队头阻塞问题。实现动态可插拔,在应用层实现了拥塞控制算法,可以随时切换。报文头和报文体分别进行认证和加密处理,保障安全性。连接能够平滑迁移。4.5. 总结HTTP 1.0:浏览器与服务器只保持短连接,浏览器的每次请求都需要与服务器建立一个TCP连接。HTTP 1.1:引入了长连接,即TCP连接默认不关闭,可以被多个请求复用引入pipelining管道技术,在同一个TCP连接里面,客户端可以同时发送多个请求,但有队头阻塞问题。新增了一些请求方法新增了一些请求头和响应头HTTP 2.0:采用二进制格式而非文本格式多路复用TCP连接,在一个链接上客户端或服务器可同时发送多个请求或响应,避免了队头阻塞问题(只解决粒度级别为http request的队头阻塞)使用报头压缩,降低开销服务器推送HTTP 3.0:利用QUIC作为底层支撑协议,其融合UDP协议的速度、性能与TCP的安全可靠。彻底解决了队头阻塞问题连接建立更快支持连接迁移
备战八月,Nginx最细最权威面试题,有这一套完全够了!(精心整理)(上)
如何在Nginx中取得当前的时间?要取得Nginx的当前时间,必需用SSI板块、$date_gmt和$date_local的变量。Proxy_set_header THE-TIME $date_gmt;nginx中500、502、503、504 有什么区别?500:Internal Server Error 内部服务错误,比如脚本错误,编程语言语法错误。502:Bad Gateway错误,网关错误。比如服务器当前连接太多,响应太慢,页面素材太多、带宽慢。503:Service Temporarily Unavailable,服务不可用,web服务器不能处理HTTP请求,可能是临时超载或者是服务器进行停机维护。504:Gateway timeout 网关超时,程序执行时间过长导致响应超时,例如程序需要执行20秒,而nginx最大响应等待时间为10秒,这样就会出现超时。是否有可能将Nginx的错误替换为502错误、503?有可能,但是您可以确保fastcgi_intercept_errors被设置为ON,并使用错误页面指令。502 =错误网关503 =服务器超载Location / {
fastcgi_pass 127.0.01:9001;
fastcgi_intercept_errors on;
error_page 502 =503/error_page.html;
}
Nginx是否支持将请求压缩到上游?您可以使用Nginx模块gunzip将请求压缩到上游。gunzip模块是一个过滤器,它可以对不支持“gzip”编码方法的客户机或服务器使用“内容编码:gzip”来解压缩响应。什么是Nginx?Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万并发,现在中国使用nginx网站用户有很多,例如:新浪、网易、 腾讯等。为什么要用Nginx?跨平台、配置简单、方向代理、高并发连接:处理2-3万并发连接数,官方监测能支持5万并发,内存消耗小:开启10个nginx才占150M内存 ,nginx处理静态文件好,耗费内存少。而且Nginx内置的健康检查功能:如果有一个服务器宕机,会做一个健康检查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。使用Nginx的话还能:节省宽带:支持GZIP压缩,可以添加浏览器本地缓存稳定性高:宕机的概率非常小接收用户请求是异步的为什么Nginx性能这么高?因为他的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决。Nginx怎么处理请求的?nginx接收一个请求后,首先由listen和server_name指令匹配server模块,再匹配server模块里的location,location就是实际地址。server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
}
在Nginx中,如何用未定义的服务器名称来阻止解决请求?只要将请求删除的服务器即可以定义为:Server {
listen 80;server_name "" ;return 444;
}Nginx服务器上的Master和Worker进程分别是什么?Master进程:读取及评估配置和维持Worker进程:解决请求Nginx能做什么?反向代理负载均衡HTTP服务器(包含动静分离)正向代理Nginx和Apache之间的不同点什么是正向代理和反向代理?正向代理是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定原始服务器,然后代理向原始服务器转交请求并将获得的内容返回给客户端。代理服务器和客户端处于同一个局域网内。比如说fanqiang。我知道我要访问谷歌,于是我就告诉代理服务器让它帮我转发。反向代理实际运行方式是代理服务器接受网络上的连接请求。它将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给网络上请求连接的客户端 。代理服务器和原始服务器处于同一个局域网内。比如说我要访问淘宝,对我来说不知道图片、json、css 是不是同一个服务器返回回来的,但是我不关心,是反向代理 处理的,我不知道原始服务器。使用反向代理服务器的优点是什么?反向代理服务器可以隐藏源服务器的存在和特征。它充当互联网云和web服务器之间的中间层。这对于安全方面来说是很好的,特别是当您使用web托管服务时。Nginx的优缺点?优点:占内存小,可实现高并发连接,处理响应快可实现http服务器、虚拟主机、方向代理、负载均衡Nginx配置简单可以不暴露正式的服务器IP地址缺点:动态处理差:nginx处理静态文件好,耗费内存少,但是处理动态页面则很鸡肋,现在一般前端用nginx作为反向代理抗住压力。Nginx应用场景?http服务器。Nginx是一个http服务可以独立提供http服务。可以做网页静态服务器。虚拟主机。可以实现在一台服务器虚拟出多个网站,例如个人网站使用的虚拟机反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负载,不会应为某台服务器负载高宕机而某台服务器闲置的情况。nginz 中也可以配置安全管理、比如可以使用Nginx搭建API接口网关,对每个接口服务进行拦截。Nginx目录结构有哪些?[root@localhost ~]# tree /usr/local/nginx
/usr/local/nginx
├── client_body_temp
├── conf # Nginx所有配置文件的目录
│ ├── fastcgi.conf # fastcgi相关参数的配置文件
│ ├── fastcgi.conf.default # fastcgi.conf的原始备份文件
│ ├── fastcgi_params # fastcgi的参数文件
│ ├── fastcgi_params.default
│ ├── koi-utf
│ ├── koi-win
│ ├── mime.types # 媒体类型
│ ├── mime.types.default
│ ├── nginx.conf # Nginx主配置文件
│ ├── nginx.conf.default
│ ├── scgi_params # scgi相关参数文件
│ ├── scgi_params.default
│ ├── uwsgi_params # uwsgi相关参数文件
│ ├── uwsgi_params.default
│ └── win-utf
├── fastcgi_temp # fastcgi临时数据目录
├── html # Nginx默认站点目录
│ ├── 50x.html # 错误页面优雅替代显示文件,例如当出现502错误时会调
用此页面
│ └── index.html # 默认的首页文件
├── logs # Nginx日志目录
│ ├── access.log # 访问日志文件
│ ├── error.log # 错误日志文件
│ └── nginx.pid # pid文件,Nginx进程启动后,会把所有进程的ID号写
到此文件
├── proxy_temp # 临时目录
├── sbin # Nginx命令目录
│ └── nginx # Nginx的启动命令
├── scgi_temp # 临时目录
└── uwsgi_temp # 临时目录
Nginx配置文件nginx.conf有哪些属性模块?worker_processes 1; # worker进程的数量
events { # 事件区块开始
worker_connections 1024; # 每个worker进程支持的最大连接数
} # 事件区块结束
http { # HTTP区块开始
include mime.types; # Nginx支持的媒体类型库文件
default_type application/octet-stream; # 默认的媒体类型
sendfile on; # 开启高效传输模式
keepalive_timeout 65; # 连接超时
server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户
location = /50x.html { # location区块开始,访问50x.html
root html; # 指定对应的站点目录为html
}
}
Nginx静态资源?静态资源访问,就是存放在nginx的html页面,我们可以自己编写如何用Nginx解决前端跨域问题?使用Nginx转发请求。把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址。Nginx虚拟主机怎么配置基于域名的虚拟主机,通过域名来区分虚拟主机——应用:外部网站基于端口的虚拟主机,通过端口来区分虚拟主机——应用:公司内部网站,外部网站的管理后台基于ip的虚拟主机。基于虚拟主机配置域名需要建立/data/www /data/bbs目录,windows本地hosts添加虚拟机ip地址对应的域名解析;对应域名网站目录下新增index.html文件;#当客户端访问www.mzc.com,监听端口号为80,直接跳转到data/www目录下文件
server {
listen 80;
server_name www.mzc.com;
location / {
root data/www;
index index.html index.htm;
}
}
#当客户端访问www.mzc.com,监听端口号为80,直接跳转到data/bbs目录下文件
server {
listen 80;
server_name bbs.lijie.com;
location / {
root data/bbs;
index index.html index.htm;
}
}
基于端口的虚拟主机使用端口来区分,浏览器使用域名或ip地址:端口号 访问#当客户端访问www.mzc.com,监听端口号为8080,直接跳转到data/www目录下文件
server {
listen 8080;
server_name 8080.mzc.com;
location / {
root data/www;
index index.html index.htm;
}
}
#当客户端访问www.mzc.com,监听端口号为80直接跳转到真实ip服务器地址 127.0.0.1:8080
server {
listen 80;
server_name www.mzc.com;
location / {
proxy_pass http://127.0.0.1:8080;
index index.html index.htm;
}
}
Android体系课 之 OkHttp你想知道的都在这里了--
theme: juejin前言开发过程中经常需要使用到网络请求,而OkHttp作为第一代网络框架,经久不衰,在众多框架中脱颖而出,且在近期已被谷歌官方推荐使用,一定有其原因:今天我们就来讲下OkHttp这个高效框架背后的秘密。Android体系课学习 之 开源框架系列[Android体系课学习 之 网络请求库OkHttp看这一篇就够了]()Android体系课学习 之 网络请求库Retrofit使用方式(附Demo)Android体系课学习 之 网络请求库Retrofit源码分析[Android体系课学习 之 RxJava看这篇就够了]()[Android体系课学习 之 RxJava操作符详解]()[Android体系课学习 之 RxJava源码分析 ]()目录简介OkHttp是一个高效的网络请求开源框架特点:1.支持同一个主机的所有请求使用同一个套接字,这个特点让OkHttp对连接池的复用成为了可能2.连接池的复用,大大减少了请求的延时3.使用透明的Gzip压缩请求体大大缩小了请求传输大小,关于Gzip我们只需要知道其是一种高效的数据压缩方式4.对于响应,OkHttp会对其做一个缓存,在某些情况下,可以使用缓存中的内容5.基于拦截器的责任链模式对请求进行阶段处理,可以自定义拦截器,方便对请求二次处理基本使用介绍本次使用的OkHttp版本为:3.14.9 OkHttp使用起来非常方便,下面是OkHttp简单的标准使用方法:fun testOkHttp(){
val client = OkHttpClient()//步骤1:创建一个OkHttpClient对象
val rd1:RequestBody = formBodyBuilder.build()
val request = Request.Builder().get().url("http://host:port/api").build()
val response = client.newCall(request).execute()
print(response.body()?.bytes())
}### 使用步骤:
#### 步骤1:可以通过OkHttpClient.Builder配置一个新的OkHttpClient,重新配置连接超时时间,添加拦截器,添加代理,设置缓存策略等
#### 步骤2:配置请求体:本例中配置的是Form表单格式的请求体,并添加了两个字段key1和key2
#### 步骤3:这个步骤可以添加请求的url信息和添加RequestBody等操作
#### 步骤4:创建网络请求执行器并执行execute操作,这里也可以使用enqueue异步调用请求
高级使用这里先介绍下我们平时使用网络请求的场景:1.上传Form表单数据,如登录场景需要删除用户名和密码2.上传字符串数据3.上传文件4.下载文件现在来分别介绍下这几种情况1.上传Form表单数据,如登录场景需要删除用户名和密码创建一个FormBody对象,并传入对应的key和value值val formBodyBuilder:FormBody.Builder = FormBody.Builder()
.add("key1","value1")
.add("key2","value2"); 2.上传字符串数据上传字符串需要在创建网络请求的时候传入一个RequestBodyRequestBody requestStrBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"),"username:admin password 123456");
Request.Builder requestBuilder = new Request.Builder().post(requestStrBody)这个RequestBody是一个“text/plain”格式,“utf-8”编码格式的body类型,body内容为:`username:admin password 123456`3.使用Post上传文件上传文件擦操作和字符串类似,只是MediaType类型是application/octet-stream表示以任意二进制格式上送,当然也可以使用image/png格式RequestBody requestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file);这里记得添加文件的写操作权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>4.使用get进行文件下载请求时指定get方式请求Request request = new Request.Builder()
.get()
.url("https://www.baidu.com/img/bd_logo1.png")
.build() 回调的时候将数据放入文件:InputStream is = response.body().byteStream();
int len = 0;
File file = new File(Environment.getExternalStorageDirectory(), "n.png");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[128];
while ((len = is.read(buf)) != -1){
fos.write(buf, 0, len);
}
fos.flush();
//关闭流
fos.close();
is.close(); 这里记得添加文件的写操作权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>5.既实现Form表单上送也可以实现文件上送使用MultipartBody可以实现既上传表单又上传文件:FormBody.Builder formBodyBuilder = new FormBody.Builder()
.add("key1","value1")
.add("key2","value2");
RequestBody requestBody = formBodyBuilder.build();
RequestBody rd = RequestBody.create(MediaType.parse("application/octet-stream"), new File("path/1.png"));
RequestBody bodyPart = new MultipartBody.Builder()
.addPart(requestBody)
.addPart(rd)
.build(); 使用上面的方式将表单数据username和password上传到服务器,也将本地文件path/1.png进行了上传**上面已经讲解几种常用的网络请求场景**:[*Demo*](https://github.com/ByteYuhb/demoOkhttp)
源码分析上面简单讲解了OkHttp的使用方式:下面我将会从源码的角度来分析OkHttp的强大之处。先放一张整体架构图:此次分析的源码版本:3.14.9大部分具体分析会在代码内部根据流程来执行,源码外部是对过程的一个总结我们按四个步骤来查看源码:1.分析源码中的重要组件2.使用步骤分析3.根据大概使用流程画出流程图4.根据使用流程对代码进行具体源码分析使用流程贯彻了整个分析过程OkHttp中的几个重要类:了解这些类是为了更好的对其再次封装OkHttpClient:框架核心管理类,内部创建了很多默认属性,和应用直接接触RealCall:负责请求的调度,对于同步请求直接调用execute方法执行,对于异步请求使用enqueue方法,将请求放到他的默认线程池中最终还是调用了execute方法实现后续操作Request:请求实体类,封装了客户端的请求信息,包括url,method,header还有body等信息Response:响应实体类,封装了服务器返回的应答信息,如code,message,header和body等信息Dispatcher分发器,对请求任务进行异步分发,内部使用线程池进行分发Cache:响应缓存,默认的缓存策略使用的DiskLruCache,写入缓存过程也是使用线程池操作,不会阻塞请求线程。 只对Get请求有相应的缓存,这样做性价比比较高,因为大部分Get请求是客户端需要响应数据,而post或者其他情况请求大部分是为了把数据发送给服务器,而不是获取服务器的结果, 如果需要使用其他缓存策略可以在创建OkHttpClient请求的时候从外部设置ConnectionPool:对原有连接的一种复用机制dns:dns服务器解析后的ip和端口信息Interceptor:拦截器使用的是责任链模式对网络请求进行处理主要包括以下几种:自定义用户拦截器:在拦截器开始前使用自定义拦截器对请求状态进行检查,增加网络请求粘性,可以不设置重试拦截器(RetryAndFollowUpInterceptor):这个拦截器主要是对失败进行重试或者重定向操作重试条件:1.应用层禁止重试,则不能重试2.如果请求已经发送,且返回了FileNotFoundException或者isOneShot标志位true,oneShot指请只发送一次,则不会进行重试`3.异常是一个崩溃导致的不会进行重试`4.没有其他路由可以尝试了,不能重试`5.以上情况都没有,则进行重试**桥接拦截器(`BridgeInterceptor`):**
这个拦截器主要作用个如下:
1.负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应,是从应用程序代码到网络代码的桥梁2.设置内容长度,内容编码3.设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦4.添加cookie5.设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现连接复用的必要步骤缓存拦截器(CacheInterceptor)缓存拦截器的使用机制:
1.如果禁止使用网络请求且缓存响应为空,则直接返回一个504的错误码2.如果网络请求被禁止,且获取到cacheResponse不为空,则直接返回缓存数据3.如果缓存数据不为空,且服务器返回了HTTP_NOT_MODIFIED 304则返回缓存数据4.如果cache不为null且Header头部的hasBody为true,且缓存器可使用状态,则将响应数据写到缓存中,并返回5.缓存器如果在使用的过程中出现异常,记得清空缓存,防止内存溢出**连接拦截器(`ConnectInterceptor`)**
`连接拦截器`做了哪些事情? 1.先检查transmitter中的connection是否可用,可用标志位是connection的noNewExchanges,如果为true,则说明不可用,并释放socket,这个是在CallServerIntercept中赋值的2.再检查连接池中是否有可用的连接3.如果1或2中找到可以复用的连接,则可以省略重新创建socket和dns解析,连接,握手等操作,最大限度的缩短通讯时间,提升性能4.如果没有可复用的连接,则创建一个新的RealConnection,并调用tcp的握手操作,如果是https则还会调用TLS的握手操作,调试过程中如果出现握手失败的情况,可以通过这里面查看握手协议是否有更改5.连接成功后,再次查看连接池中是否有可以复用的连接池。这种情况出现在同一主机对服务器有并发连接的情况6.如果5中没有在连接池中获取到可复用的连接,则将4中创建的连接实例放入到连接池中,方便下次连接复用使用7.如果5中在连接池中找到可复用的连接,则将4中创建的连接关闭,返回5中的连接池的连接` **自定义网络拦截器(`networkInterceptors`):**
这个一般情况下不用设置,只有对网络连接有要求的情况下才会去设置 **请求服务拦截器(`CallServerInterceptor`):**
主要职责:发送数据头,发送数据body,接收数据头,如果数据头显示有content,则创建一个body的response,最后调用response.body().bytes()才会去取body数据 核心操作都是之前获取的HttpCodec进行发送和接收我们再来列下OkHttp的使用步骤:步骤1:可以通过OkHttpClient.Builder配置一个新的OkHttpClientval client:OkHttpClient = OkHttpClient.Builder()
.connectTimeout(5000,TimeUnit.MILLISECONDS)
.addInterceptor(userInterceptor)
.build() 步骤2:配置请求体:本例中配置的是Form表单格式的请求体,并添加了两个字段key1和key2val formBody:FormBody = FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build();步骤3:这个步骤可以添加请求的url信息和添加RequestBody等操作val request = Request.Builder().post(requestBody).url(baseUrl).build()步骤4:创建网络请求执行器并执行execute操作,这里也可以使用enqueue异步调用请求val response = client.newCall(request).execute()步骤5:根据响应结果头部数据长度获取响应bodyprintR(response.body()?.bytes())OkHttp流程图:下面我们根据使用步骤对源码进行解析步骤1:可以通过OkHttpClient.Builder配置一个新的OkHttpClientval client:OkHttpClient = OkHttpClient.Builder()
.connectTimeout(5000,TimeUnit.MILLISECONDS)
.addInterceptor(userInterceptor)
.build()我们进入OkHttpClient源码看看:OkHttpClient继承了Call.Factory, WebSocket.Factory 实现了Cloneable接口 所以这是一个网路请求器,也可以作为WebSocket请求器public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
//默认支持HTTP1.1和HTTP2
final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(Protocol.HTTP_2, Protocol.HTTP_1_1);
//这里指定了默认的连接属性: --关注点1--
final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
//这个是请求分发器,用于对请求任务进行同步和异步分发 --关注点2--
final Dispatcher dispatcher;
//如果是请求的代理,可以通过设置代理转发请求事件
final @Nullable Proxy proxy;
//支持的协议类型,默认支持HTTP1.1和HTTP2
final List<Protocol> protocols;
//支持的TLS协议类型,默认支持TLS1.2和1.3
final List<ConnectionSpec> connectionSpecs;
//请求拦截器,用于对事件进行拦截处理。拦截器后面会详细讲解
final List<Interceptor> interceptors;
//网络拦截器,一般不需要设置
final List<Interceptor> networkInterceptors;
//事件回调器,比如请求状态,dns执行状态回调等
final EventListener.Factory eventListenerFactory;
//代理选择器
final ProxySelector proxySelector;
//设置Cookie,可以在外部设置
final CookieJar cookieJar;
//缓存策略, --关注点3--
final @Nullable Cache cache;
//框架内置的缓存策略, --关注点3--
final @Nullable InternalCache internalCache;
//创建Socket请求的工厂类:默认为:DefaultSocketFactory实例对象
final SocketFactory socketFactory;
//Https请求的SSLSocketFactory工厂类
final SSLSocketFactory sslSocketFactory;
//证书链清理者,看名字就知道是用于对证书进行清理的
final CertificateChainCleaner certificateChainCleaner;
//主机名称校验者,通讯过程中可以使用这个方法回调对主机名进行合法性确认
final HostnameVerifier hostnameVerifier;
//这个类主要用来针对有对固定的证书进行限制的情况:
//如你不想要请求访问某个主机名或者代理主机,可以将主机的主机名和对应证书编号sha设置到这个certificatePinner中即可,有一定风险,不建议使用
final CertificatePinner certificatePinner;
//在连接到代理服务器前对代理服务器进行合法身份验证
final Authenticator proxyAuthenticator;
//在连接到web服务器前对web服务器进行合法身份验证
final Authenticator authenticator;
//连接复用池 --关注点4--
final ConnectionPool connectionPool;
//主机dns信息 --关注5--
final Dns dns; //dns解析器,使用系统的dns解析器
...
}-来看--关注点1--:`public static final ConnectionSpec MODERN_TLS = new Builder(true)
.cipherSuites(APPROVED_CIPHER_SUITES)
.tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
.supportsTlsExtensions(true)
.build();
}这里看到OkHttp默认支持的TLS版本为TLS1.3和TLS1.2,这两个TLS版本可以满足大部分服务器要求,所以OkHttp做了这个默认配置再来关注点2,我们来看下分发器内部结构:源作者对Dispatcher的描述:用于对请求任务进行异步分发,内部通过线程池进行处理,用户也可以设置自己的线程池,但是应该提供线程池并发执行的线程最大数量public final class Dispatcher {
//分发器最大请求数
private int maxRequests = 64;
//单个主机最大请求数
private int maxRequestsPerHost = 5;
//分发器空闲状态回调,可以让分发器的没有任务的时候可以回调做一些事情
private @Nullable Runnable idleCallback;
//线程池执行器
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;
//即将被执行的请求任务会放到这个readyAsyncCalls队列中
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在执行的异步任务会被放入到这个runningAsyncCalls队列中
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//正在执行的同步任务会被放入这个runningSyncCalls队列中
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
//可以在外部设置请求执行器,但根据要求需要设置最大并发线程数,默认的线程池执行器:ThreadPoolExecutor
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
//这里是设置默认的executorService为ThreadPoolExecutor
//核心线程数为0,非核心线程数最大值无限制,非核心线程保留时间为60s,阻塞队列使用SynchronousQueue队列FIFO处理
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
}对Dispatcher进行总结:内部使用三个队列:`readyAsyncCalls`:保存“`准备执行`的异步请求”
`runningAsyncCalls`:保存“`正在执行`的`异步`请求”
`runningSyncCalls`:保存“`正在执行`的`同步`请求”
默认线程池执行器:使用核心线程数为0,非核心线程数最大值无限制,非核心线程保留时间为60s,阻塞队列使用SynchronousQueue队列FIFO处理 用户也可以自定义线程池执行器,但必须确保最大并发线程数来看--关注点3--缓存器Cachepublic final class Cache implements Closeable, Flushable {
//这是OkHttp内置的一个缓存策略,调用自身的get方法,请求实体作为参数去获取,
//使用内置缓存而不直接继承缓存并实现,主要是满足开闭原则,让内部接口只对自身开放,用一个内置对象和外部沟通
final InternalCache internalCache = new InternalCache() {
@Override public @Nullable Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override public @Nullable CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
...
};
//这里表面系统默认使用的是DiskLruCache磁盘缓存
final DiskLruCache cache;
//缓存的key是通过对url进行md5加密得到,所以多同一url的请求都会优先从缓存中获取
public static String key(HttpUrl url) {
return ByteString.encodeUtf8(url.toString()).md5().hex();
}
//这是获取缓存的方式
@Nullable Response get(Request request) {
//这里获取缓存的key,上面介绍过使用的是url的md5加密作为key
String key = key(request.url());
/
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
//获取缓存的快照
snapshot = cache.get(key);
//没有缓存直接返回null给上级
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
//从缓存快照中拿到Entry对象
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
//从entry对象中获取响应数据,从这里也可以推断出,
//缓存存储步骤:1.将响应数据放入一个entry对象中,2.然后将entry对象作为数据元放入snapshot缓存快照中,3.最后将快照存入本地磁盘中
Response response = entry.response(snapshot);
//检测快照是否合法
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
//合法直接返回对应的请求的缓存数据
return response;
}
@Nullable CacheRequest put(Response response) {
...
if (!requestMethod.equals("GET")) {
//这里可以看出默认缓存策略只会缓存get请求的响应数据,对于其他方式不会做缓存,
//如果需要缓存其他数据,可以通过自定义缓存策略实现
return null;
}
//创建一个Entry对象包裹response
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
//创建一个url对应的editor对象
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
//将entry写入editor,这里面会将缓存数据写到磁盘中
entry.writeTo(editor);
//返回一个包含editor的CacheRequestImpl对象
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
}这里总结下Cache:系统针对Get请求内置了一个DiskLruCache的磁盘缓存策略,缓存写入过程也是通过线程池(只提供了一个非核心线程,说明同一时间只能有一个线程操作磁盘数据)做的异步缓存操作,不会阻塞网络请求线程用户可以根据需求自定义缓存策略。来看 --关注点4--ConnectionPool.javapublic final class ConnectionPool {
//这里可以看到使用的是一个代理模式:ConnectionPool代理了RealConnectionPool的执行
final RealConnectionPool delegate;
//这里对连接池官方的解释:
//连接池的参数在以后的OkHttp版本中可能会发生变化,连接池中会有5个默认的空闲连接,如果5分钟没有活动的connection则,将会把这些connections删除
/**
* Create a new connection pool with tuning parameters appropriate for a single-user application.
* The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
* this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
*/
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
//这里创建一个真实的RealConnectionPool连接池
this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);//关注点4.1--
}
//返回连接池中空闲连接的个数,如果没有空闲连接,则不会再创建新的连接,会等待前面连接使用完毕
/** Returns the number of idle connections in the pool. */
public int idleConnectionCount() {
return delegate.idleConnectionCount();
}
//返回连接池中的所有连接个数
/** Returns total number of connections in the pool. */
public int connectionCount() {
return delegate.connectionCount();
}
//关闭连接池中所有的连接
/** Close and remove all idle connections in the pool. */
public void evictAll() {
delegate.evictAll();
}
}
//来看`4.1`处:
RealConnectionPool构造方法
public final class RealConnectionPool {
//使用ArrayDeque来存储连接池
private final Deque<RealConnection> connections = new ArrayDeque<>();
//这个方法是请求实体去连接池中查看可复用的连接情况:
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
if (!connection.isEligible(address, routes)) continue;//这里判断连接情况关注点4.1.1
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
}
//来看 --关注点4.1.1--
//这个方法用来判断一个连接是否可用
boolean isEligible(Address address, @Nullable List<Route> routes) {
//如果连接这个链接不能接受新的请求exchanges,则返回false
// If this connection is not accepting new exchanges, we're done.
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
//如果传入的地址的dns和connection中的dns不一致,则返回false,这个equalsNonHost在OkHttpClient中
// If the non-host fields of the address don't overlap, we're done.
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
//如果到这里主机名对应上则返回true
// If the host exactly matches, we're done: this connection can carry the address.
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
//如果以上都对应不上,还有一种复用的可能:要求如下
//1.请求是HTTP/2 2 路由使用的是同一个ip地址 3.连接使用的服务器证书涵盖了新的请求 4.新的请求可以通过原网络connection的挂起证书
//满足以上条件的请求也可以复用
// 1. This connection must be HTTP/2.
if (http2Connection == null) return false;
// 2. The routes must share an IP address.
if (routes == null || !routeMatchesAny(routes)) return false;
// 3. This connection's server certificate's must cover the new host.
if (address.hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
// 4. Certificate pinning must match the host.
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
return true; // The caller's address can be carried by this connection.
}总结ConnectionPool:使用代理模式实现了对连接池的复用操作,实际其作用给的是RealConnectionPool,连接池内部存储了5个空闲的连接,如果五分钟内没有任何请求,则会删除连接池中的空闲连接连接池复用逻辑如下:1.如果超过了连接的最大连接数,则不能复用2.如果传入的地址的dns和connection中的dns不一致,则不能复用3.以上两种情况都可以后,判断主机名是否一致,如果一致则可复用4.在上面情况下,如果只有主机名不一致,则还可以有一种复用情况:1.请求是HTTP/2`2.路由使用的是同一个ip地址3.连接使用的服务器证书涵盖了新的请求4.新的请求可以通过原网络connection的挂起证书 满足上面几个条件,也是可以拿来复用的来看下 --关注点5--public interface Dns {
//这里使用lambda表达式创建了一个系统的DNS解析器SYSTEM:其实就是lookup方法的实现
Dns SYSTEM = hostname -> {
if (hostname == null) throw new UnknownHostException("hostname == null");
try {
return Arrays.asList(InetAddress.getAllByName(hostname));
} catch (NullPointerException e) {
UnknownHostException unknownHostException =
new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
unknownHostException.initCause(e);
throw unknownHostException;
}
};
List<InetAddress> lookup(String hostname) throws UnknownHostException;
}
继续查看InetAddress.getAllByName(hostname):
public static InetAddress[] getAllByName(String host)
throws UnknownHostException {
//看官方解释目前Android使用的是Libcore.os动态库对主机dns进行解析
// Android-changed: Resolves a hostname using Libcore.os.
// Also, returns both the Inet4 and Inet6 loopback for null/empty host
return impl.lookupAllHostAddr(host, NETID_UNSET).clone();
}总结下DNS解析:Android系统的dns解析过程是通过Libcore.os库对主机名进行解析获取,返回的是一个List的ip和端口InetAddress对象,后面就涉及到NDK的内容,这里就不在深入现在我们已经对OkHttpClient中的所有属性做了一个比较深入的讲解,有以上基础再去看下面步骤应该会相对简单点了,继续看后面的步骤步骤2:配置请求体本例中配置的是Form表单格式的请求体,并添加了两个字段key1和key2val formBody:FormBody = FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build(); 这里没有太多可讲的,内部就是创建一个FormBody实体,实体内部包括两个List分别存储names和values步骤3:这个步骤可以添加请求的url信息和添加RequestBody等操作val request = Request.Builder().post(requestBody).url(baseUrl).build()
//来看Request的构造器:
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}构造器中包括:url请求,method请求方法,header头文件信息,body请求信息等信息步骤4:创建网络请求执行器并执行execute操作,这里也可以使用enqueue 异步调用请求1.网路请求器创建client.newCall(request)public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
//继续看:RealCall.newRealCall
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
} 这里创建了一个RealCall执行器,并将请求信息封装到一个transmitter对象中,并把这个transmitter对象放到RealCall对象中,返回RealCall对象来看RealCall内部final class RealCall implements Call {
final OkHttpClient client;
private Transmitter transmitter;
final Request originalRequest;
}可以看到RealCall内部就是对OkHttpClient和Request请求做了一次再封装2.网络请求执行call..execute():这个call是RealCall的实例
public void enqueue(Callback responseCallback) {
//这里判断请求是否已经执行,如果已经执行则报异常退出
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//这里面是将请求信息报告给eventListener
transmitter.callStart();
//这里是真实调用的地方:先用装饰器模式将responseCallback封装到AsyncCall中,关注点1
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}继续看关注点1client.dispatcher().enqueue(new AsyncCall(responseCallback));实际调用的是:Client中的Dispatcher的enqueue方法,传参new AsyncCall(responseCallback)进入看看:void enqueue(AsyncCall call) {
//调用synchronized同步
synchronized (this) {
//将call请求放入到readyAsyncCalls中,这个是准备发送的异步请求
readyAsyncCalls.add(call);
...
}
promoteAndExecute();
}
//继续看promoteAndExecute
private boolean promoteAndExecute() {
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
//将readyAsyncCalls队列中的请求放入到executableCalls中,待请求
executableCalls.add(asyncCall);
//将readyAsyncCalls队列中的请求放入到runningAsyncCalls中
runningAsyncCalls.add(asyncCall);
}
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
//这里发送executableCalls中的请求使用线程池进行分发
asyncCall.executeOn(executorService());
}
...
return isRunning;
}网络请求到这里就是使用线程池对请求进行分发了: 分发的任务:在AsyncCall的父类中:public abstract class NamedRunnable implements Runnable {
@Override public final void run() {
...
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
//可以看到还是执行的AsyncCall的execute方法
protected void execute() {
...
try {
//调用getResponseWithInterceptorChain方法 -- 关注点2--
Response response = getResponseWithInterceptorChain();
//这里调用responseCallback回调响应数据
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
} catch (Throwable t) {
}
}很多同学看我上面可能会一头雾水了,可以先喝杯水,休息休息下面就是我们框架的核心点:拦截器的使用:首先我们要了解这里使用的是一种责任链模式的设计模式我们继续来看关注点2:getResponseWithInterceptorChain Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//这个是应用自定义拦截器,放在所有的责任链head部位,可以在这里对请求做个再次封装或者判断 --拦截器1--
interceptors.addAll(client.interceptors());
//重试或重定向拦截器 --拦截器2--
interceptors.add(new RetryAndFollowUpInterceptor(client));
//桥接拦截器 --拦截器3--
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//缓存拦截器 --拦截器4--
interceptors.add(new CacheInterceptor(client.internalCache()));
//连接拦截器 --拦截器5--
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//用户自定义网络拦截器 --拦截器6--
interceptors.addAll(client.networkInterceptors());
}
//请求服务器拦截器 --拦截器7
interceptors.add(new CallServerInterceptor(forWebSocket));
//这里创建一个RealInterceptorChain并将拦截器列表和交易请求信息封装到这个对象内部
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
try {
//调用proceed方法 --关注点3--
Response response = chain.proceed(originalRequest);
return response;
} catch (IOException e) {
...
} finally {
...
}
}
//来查看 --关注点3--
public Response proceed(Request request) throws IOException {
return proceed(request, transmitter, exchange);
}
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
//首先需要知道这个index是目前执行的拦截器索引:如果超过索引,直接抛出异常
if (index >= interceptors.size()) throw new AssertionError();
calls++;
...
// Call the next interceptor in the chain.
//这里创建了一个新的RealInterceptorChain请求,且index做了+1操作
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
//这里执行了拦截器的intercept方法并传入下一个RealInterceptorChain拦截器责任链 --关注点4--
Response response = interceptor.intercept(next);
...
return response;
}总结这个方法作用:主要是设置多个拦截器,然后执行拦截器下面来看下拦截器的执行过程:来看 --关注点4-- 我们知道第一个拦截器是用户自定义拦截器,如果我们没有自定义则默 认跳过, 那么直接来看 :拦截器2--RetryAndFollowUpInterceptor:重试和重定向拦截器public Response intercept(Chain chain) throws IOException {
//获取网络请求Request
Request request = chain.request();
//将chain强制转换为RealInterceptorChain,本身也是RealInterceptorChain
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//获取realChain中的Transmitter,这个Transmitter中包含了所有的网络请求信息,包括网络执行器OkHttpClient中的信息
Transmitter transmitter = realChain.transmitter();
//初始化重定向次数为0
int followUpCount = 0;
Response priorResponse = null;
//看到这里使用了一个While循环,如果需要重试会调用continue回到这里
while (true) {
//准备连接请求 关注点4.1
transmitter.prepareToConnect(request);
...
try {
// 这里准备调用下一个拦截器 。根据拦截器插入逻辑,下一个是CacheInterceprer
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {--关注点4.2--
//RouteException 表示通过单个路由连接时出现了异常,且可能已经尝试了多次均失败,
// The attempt to connect via a route failed. The request will not have been sent.
//看这里逻辑,如果recover返回false,则不再重试,直接抛出异常 --关注点4.2--
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;//重试
} catch (IOException e) {
//这里如果是一个服务器通讯出现了异常,需要考虑请求是否已经被发送出去
// An attempt to communicate with a server failed. The request may have been sent.
//如果是除了ConnectionShutdownException的异常,则requestSendStarted为true
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
//此时recover传入的requestSendStarted参数为true,前面RouteException传入的是false --关注点4.2--
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;//重试
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
//如果失败或者异常情况下需要释放资源
transmitter.exchangeDoneDueToException();
}
}
//如果priorResponse存在,则将之前的priorResponse作为响应
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//通过原响应获取原响应的请求Exchange信息
Exchange exchange = Internal.instance.exchange(response);
//通过exchange.connection().route()获取原响应信息的路由信息
Route route = exchange != null ? exchange.connection().route() : null;
//这里发送重定向请求,传入原响应信息和原路由信息 --关注点4.3
Request followUp = followUpRequest(response, route);
//响应成功或者不需要重定向的情况,直接返回response
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
//如果重定向不为空且有body
RequestBody followUpBody = followUp.body();
//只能发送一次则直接返回response
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
//重定向次数大于MAX_FOLLOW_UPS,则抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//将重定向请求体作为请求体
request = followUp;
//将请求作为原响应
priorResponse = response;
}
}
/**
来看关注点 “4.1”:
这个方法内部将connectionPool,请求器RealCall还有主机地址等信息封装到一个exchangeFinder对象中
**/
public void prepareToConnect(Request request) {
...
this.request = request;
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
//--关注点4.2--:
/**
如果proceed返回RouteException,则需要通过下面recover判断是否需要重试:这里的数据并没有发生出去
为false不需要重试,true需要重试
**/
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
//应用层禁止重试
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
//如果请求已经发送,且返回了FileNotFoundException或者isOneShot标志位true
// We can't send the request body again.
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
//异常是一个崩溃导致的
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
//没有其他路由可以尝试了
// No more routes to attempt.
if (!transmitter.canRetry()) return false;
//以上都不是,则返回true,需要重试
// For failure recovery, use the same route selector with a new connection.
return true;
}这个方法内部主要是对重试情况进行判断:重试逻辑:1.应用层禁止重试,则不能重试2.如果请求已经发送,且返回了FileNotFoundException或者isOneShot标志位true,则不会进行重试3.异常是一个崩溃导致的不会进行重试4.没有其他路由可以尝试了,不能重试5.以上情况都没有,则进行重试发送重定向信息:这里篇幅比较长,只列出了部分关键代码--关注点4.3--private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH:
//此处需要对代理服务器进行验证返回一个验证后的请求
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED:
//此处需要对服务器进行验证并返回一个验证后的请求
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
//对于HTTP_PERM_REDIRECT和HTTP_TEMP_REDIRECT请求,需要判断是否是GET或者HEAD
//如果都不是,则直接返回null,如果是GET或者HEAD中的一种,则继续下面重定向逻辑
//此处说明HTTP_PERM_REDIRECT和HTTP_TEMP_REDIRECT只有GET请求或者HEAD请求发出
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
//如果应用不支持重定向,则返回null
if (!client.followRedirects()) return null;
//获取新的重定向地址location
String location = userResponse.header("Location");
//location为空,则返回null
if (location == null) return null;
//通过新的location创建一个新的HttpUrl请求
HttpUrl url = userResponse.request().url().resolve(location);
// url为空,则直接返回null
if (url == null) return null;
// If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
//url的Scheme不一致,都不能使用重定向
if (!sameScheme && !client.followSslRedirects()) return null;
//大部分的重定向情况不会返回一个request,需要重新创建一个request
Request.Builder requestBuilder = userResponse.request().newBuilder();
//这里如果是GET或者HEAD请求,则不会使用原响应中的body,且需要删除Header中的Transfer-Encoding,Content-Length,Content-Type等字段
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
//如果url不一致,则删除Header中的Authorization字段
if (!sameConnection(userResponse.request().url(), url)) {
requestBuilder.removeHeader("Authorization");
}
//返回一个新的重定向request
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
//...这种情况比较少见,但是如HAProxy等服务器类型还是在使用这个响应码
case HTTP_UNAVAILABLE:
//如果响应中的priorResponse不为null,则返回null
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}
//判断如果userResponse中的header("Retry-After")字段为0,则可以使用userResponse.request()作为重定向请求体
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}
return null;
default:
//其他情况,返回null
return null;
}
}根据上面源码分析可知:重定向条件:1.返回407需要对代理服务器做验证,返回401需要对服务器进行验证2.返回307和308且返回的是Get请求或者Head请求,则无需发出重定向,其他情况继续按后面逻辑判断3.如果是300,301,302,303或2中的307和308的情况则判断应用是否支持重定向,不支持返回null,支持就去原响应信息中取出重定向地址,返回对应的重定向请求Request4.还有其他如408和503的情况,这些比较少见,且重定向逻辑要求比较复杂,可以看源码中的分析\5.如果重定向次数超过最大重定向允许次数,则不进行重定向6.如果只允许一次通讯oneShot则不能重定向。缓存拦截器CacheIntercepter:直接来看他的intercept方法:public final class CacheInterceptor implements Interceptor {
public Response intercept(Chain chain) throws IOException {
//这里从cache获取缓存数据,之前讲解OkHttpClient的时候分析过,这个内部是使用DiskLruCache磁盘对数据进行缓存,如果数据比较大,可能会有一定延迟
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
//记录当前时间
long now = System.currentTimeMillis();
//这里创建了一个缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// 如果禁止使用网络请求且缓存响应为空,则直接返回一个504的错误码
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 如果网络请求被禁止,且获取到cacheResponse不为空,则直接返回缓存数据
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//这里调用下一个拦截器,直接使用networkRequest
networkResponse = chain.proceed(networkRequest);
} finally {
//如果出现io错误等,且缓存不为空,则需要将缓存清空
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
//如果缓存数据不为空,且服务器返回了HTTP_NOT_MODIFIED 304则返回缓存数据
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
...
return response;
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//如果cache不为null且Header头部的hasBody为true,且缓存器可使用状态,则将响应数据写到缓存中,并返回
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
}
//返回响应数据
return response;
}总结CacheInterceprer:1.如果禁止使用网络请求且缓存响应为空,则直接返回一个504的错误码2.如果网络请求被禁止,且获取到cacheResponse不为空,则直接返回缓存数据3.如果缓存数据不为空,且服务器返回了HTTP_NOT_MODIFIED 304则返回缓存数据4.如果cache不为null且Header头部的hasBody为true,且缓存器可使用状态,则将响应数据写到缓存中,并返回5.缓存器如果在使用的过程中出现异常,记得清空缓存,防止内存溢出连接拦截器(ConnectInterceptor):直接看他的intercept方法:public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
/**
看到这个方法代码很简单:核心代码:Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
我们进入到这里面看看:核心代码exchangeFinder.find(client, chain, doExtensiveHealthChecks);返回一个ExchangeCodec对象
**/
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
//关注点1
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
//将返回的Http1ExchangeCodec又放入到一个Exchange对象中并返回这个Exchange
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
...
return result;
}
//进入关注点1看看:
public ExchangeCodec find(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
try {
//返回一个RealConnection,可能是连接池中的可能是新创建的
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//返回一个Http1ExchangeCodec的对象并包含new Http1ExchangeCodec(client, this, source, sink);这个sink= BufferedSink ,这是okio部分内容了
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
...
}
}
调用了findHealthyConnection方法
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
//这里调用了一个死循环,内部循环调用findConnection方法直到获取到一个RealConnection对象
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0 && !candidate.isMultiplexed()) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
//返回获取的连接
return candidate;
}
}
来看findConnection方法:
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
if (result == null) {
//去连接池中查找可用的连接,这个方法在前面讲解OkHttpClient的时候解析过,是在内存缓存列表中查找对应的缓存连接,可以回头看看,篇幅问题这里不再讲解
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {//关注点2
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
//如果没有找到,看nextRouteToTry是否为空,不为空则返回selectedRoute = nextRouteToTry
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
selectedRoute = transmitter.connection.route();
}
}
//如果找到可复用的连接,则直接返回
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
//这个方法内部包含了对主机dns的解析,这里不再深入,有需要可以自行查看
routeSelection = routeSelector.next();
//没有找到
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
//创建一个RealConnection并设置connectingConnection为这个result
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
return result;
}总结连接池拦截器:主要是去连接池中获取可复用的连接,如果没有就自行创建一个RealConnection,创建之前会对主机进行dns解析。返回的RealConnection包括了解析后的ip和port信息类关系:多层包裹Exchange(Http1ExchangeCodec(RealConnection(selectedRoute)))连接池调用关系图:请求服务器拦截器CallServerInterceptor:public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
//写入请求头部数据
exchange.writeRequestHeaders(request);
//读取请求头响应数据
responseBuilder = exchange.readResponseHeaders(false);
//根据请求头创建响应Response
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build();
return response;
}
/**
进入exchange.openResponseBody(response):
看到返回了一个RealResponseBody对象
**/
public ResponseBody openResponseBody(Response response) throws IOException {
try {
eventListener.responseBodyStart(call);
String contentType = response.header("Content-Type");
long contentLength = codec.reportedContentLength(response);
Source rawSource = codec.openResponseBodySource(response);
ResponseBodySource source = new ResponseBodySource(rawSource, contentLength);
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
} catch (IOException e) {
eventListener.responseFailed(call, e);
trackFailure(e);
throw e;
}
}
/**
可以看到在请求服务器拦截过程中是没有获取服务器数据的,只是将数据头发送给后台,
并根据头部响应数据创建了RealResponseBody对象,内部包含数据body长度等信息,待应用调用response.body().bytes()的时候去后台取数据
我们来看bytes方法
**/
public final byte[] bytes() throws IOException {
...
byte[] bytes;
try (BufferedSource source = source()) {
bytes = source.readByteArray();
//这个内部方法很简单就是使用okio去取后台取数据,长度数据根据之前返回的contentLen去取
}
return bytes;
...
}上面文章后,我们已经将OKHttp所有涉及到的知识点已经通过源码解析了一遍了,这篇文章虽然长,但是可以说是保姆级别的文章,自行阅读源码过程中有不懂的均可以 拿这篇文章来参考。总结OkHttp核心处理逻辑内部拦截器在起作用,每个拦截器都有自己的职责,使用责任链模式创接起来,各司其职相信能坚持看完本文章的同学一定会有所收获,加油,一起进步Android体系课学习 之 开源框架系列[Android体系课学习 之 网络请求库OkHttp看这一篇就够了]()Android体系课学习 之 网络请求库Retrofit使用方式(附Demo)Android体系课学习 之 网络请求库Retrofit源码分析[Android体系课学习 之 RxJava看这篇就够了]()[Android体系课学习 之 RxJava操作符详解]()[[Android体系课学习 之 RxJava源码分析]()
ADB 操作命令及用法
ADB 操作命令及用法一、ADB是什么?adb 称之为:Android 调试桥 (Android Debug Bridge )是一种允许模拟器或已连接的 Android 设备进行通信的命令行工具,可为各种设备操作提供便利,如安装和调试应用,并提供对 Unix shell(可用来在模拟器或连接的设备上运行各种命令)的访问。可以在Android SDK/platform-tools中找到 adb 工具或下载 ADB Kits 。注意: 有部分命令的支持情况可能与 Android 系统版本及定制 ROM 的实现有关。二、ADB有什么作用?ADB 是 Android SDK 里的一个工具, 用这个工具可以直接操作管理Android模拟器或者真实的Android设备。主要功能有:在设备上运行Shell命令;将本地APK软件安装至模拟器或Android设备;管理设备或手机模拟器上的预定端口;在设备或手机模拟器上复制或粘贴文件。ADB 是一个客户端-服务器程序程序,包括三个组件:客户端:该组件发送命令。客户端在开发计算机上运行。可以通过发出 adb 命令从命令行终端调用客户端。后台程序:该组件在设备上运行命令。后台程序在每个模拟器或设备实例上作为后台进程运行。服务器:该组件管理客户端和后台程序之间的通信。服务器在开发计算机上作为后台进程运行。三、ADB命令语法adb 命令的基本语法如下:adb [-d|-e|-s <serial-number>] <command>单一设备/模拟器连接若仅一个设备/模拟器连接时,可以省略掉 [-d|-e|-s <serial-number>] 这一部分,直接使用 adb <command>。多个设备/模拟器连接若有多个设备/模拟器连接,则需要为命令指定目标设备,下表是指定目标设备的命令选项:参数含义-d指定当前唯一通过 USB 连接的 Android 设备为命令目标-e指定当前唯一运行的模拟器为命令目标-s <serial-number>指定相应设备序列号的设备/模拟器为命令目标在多个设备/模拟器连接的情况下较常用的是 -s <serial-number> 参数,serial-number是指设备的设备序列号,可以通过 adb devices 命令获取。4.1 基本命令4.1.1 查看adb的版本信息adb version4.1.2 启动adbadb start-server一般无需手动执行此命令,在运行 adb 命令时若发现 adb server 没有启动会自动调起。4.1.3 停止adbadb kill-server4.1.4 以 root 权限运行 adbdadb root4.1.5 指定 adb server 的网络端口adb -P <port> start-server注意:ADB的默认端口为 5037。4.1.5 查询已连接的设备/模拟器列表adb devices4.2 应用管理4.2.1 查看应用列表查看应用列表的基本命令格式是:adb shell pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]adb shell pm list packages 后面可以跟一些可选参数进行过滤查看不同的列表,可用参数及含义如下:参数显示列表无所有应用-f显示应用关联的 apk 文件-d仅显示 disabled 的应用-e仅显示 enabled 的应用-s仅显示系统应用-3仅显示第三方应用-i显示应用的 installer-u包含已卸载应用<filter>包名包含 <filter> 字符串4.2.1.1 查看所有应用adb shell pm list packages4.2.1.2 查看系统应用adb shell pm list packages -s4.2.1.3 查看第三方应用adb shell pm list packages -34.2.1.4 查看已禁用应用adb shell pm list packages -d4.2.1.5 查看已启用应用adb shell pm list packages -e4.2.1.6 查看应用及其安装信息(安装来源)adb shell pm list packages -i4.2.1.7 查看应用及其未安装信息(安装来源)adb shell pm list packages -u4.2.1.8 查看应用及其相关联的文件adb shell pm list packages -f4.2.1.9 包名包含某字符串的应用比如要查看包名包含字符串 <package> 的应用列表,命令:adb shell pm list packages <package>4.2.2 安装应用安装应用的基本命令格式是:adb install [-l] [-r] [-t] [-s] [-d] [-g] <apk-file>adb install 后面可以跟一些可选参数来控制安装 APK 的行为,可用参数及含义如下:参数含义-l将应用安装到保护目录 /mnt/asec-r允许覆盖安装-t允许安装 AndroidManifest.xml 里 application 指定 android:testOnly="true" 的应用-s将应用安装到 sdcard-d允许降级覆盖安装-g授予所有运行时权限运行命令后可以看到输出内容,包含安装进度和状态,安装状态如下:Success:代表安装成功。Failure:代表安装失败。 APK 安装失败的情况有很多,Failure状态之后有安装失败输出代码。常见安装失败输出代码、含义及可能的解决办法如下:输出代码含义解决办法INSTALL_FAILED_ALREADY_EXISTS应用已经存在,或卸载了但没卸载干净adb install 时使用 -r 参数,或者先 adb uninstall <packagename> 再安装INSTALL_FAILED_INVALID_APK无效的 APK 文件INSTALL_FAILED_INVALID_URI无效的 APK 文件名确保 APK 文件名里无中文INSTALL_FAILED_INSUFFICIENT_STORAGE空间不足清理空间INSTALL_FAILED_DUPLICATE_PACKAGE已经存在同名程序INSTALL_FAILED_NO_SHARED_USER请求的共享用户不存在INSTALL_FAILED_UPDATE_INCOMPATIBLE以前安装过同名应用,但卸载时数据没有移除;或者已安装该应用,但签名不一致先 adb uninstall <packagename> 再安装INSTALL_FAILED_SHARED_USER_INCOMPATIBLE请求的共享用户存在但签名不一致INSTALL_FAILED_MISSING_SHARED_LIBRARY安装包使用了设备上不可用的共享库INSTALL_FAILED_REPLACE_COULDNT_DELETE替换时无法删除INSTALL_FAILED_DEXOPTdex 优化验证失败或空间不足INSTALL_FAILED_OLDER_SDK设备系统版本低于应用要求INSTALL_FAILED_CONFLICTING_PROVIDER设备里已经存在与应用里同名的 content providerINSTALL_FAILED_NEWER_SDK设备系统版本高于应用要求INSTALL_FAILED_TEST_ONLY应用是 test-only 的,但安装时没有指定 -t 参数INSTALL_FAILED_CPU_ABI_INCOMPATIBLE包含不兼容设备 CPU 应用程序二进制接口的 native codeINSTALL_FAILED_MISSING_FEATURE应用使用了设备不可用的功能INSTALL_FAILED_CONTAINER_ERROR1. sdcard 访问失败;2. 应用签名与 ROM 签名一致,被当作内置应用。1. 确认 sdcard 可用,或者安装到内置存储;2. 打包时不与 ROM 使用相同签名。INSTALL_FAILED_INVALID_INSTALL_LOCATION1. 不能安装到指定位置;2. 应用签名与 ROM 签名一致,被当作内置应用。1. 切换安装位置,添加或删除 -s 参数;2. 打包时不与 ROM 使用相同签名。INSTALL_FAILED_MEDIA_UNAVAILABLE安装位置不可用一般为 sdcard,确认 sdcard 可用或安装到内置存储INSTALL_FAILED_VERIFICATION_TIMEOUT验证安装包超时INSTALL_FAILED_VERIFICATION_FAILURE验证安装包失败INSTALL_FAILED_PACKAGE_CHANGED应用与调用程序期望的不一致INSTALL_FAILED_UID_CHANGED以前安装过该应用,与本次分配的 UID 不一致清除以前安装过的残留文件INSTALL_FAILED_VERSION_DOWNGRADE已经安装了该应用更高版本使用 -d 参数INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE已安装 target SDK 支持运行时权限的同名应用,要安装的版本不支持运行时权限INSTALL_PARSE_FAILED_NOT_APK指定路径不是文件,或不是以 .apk 结尾INSTALL_PARSE_FAILED_BAD_MANIFEST无法解析的 AndroidManifest.xml 文件INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION解析器遇到异常INSTALL_PARSE_FAILED_NO_CERTIFICATES安装包没有签名INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES已安装该应用,且签名与 APK 文件不一致先卸载设备上的该应用,再安装INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING解析 APK 文件时遇到 CertificateEncodingExceptionINSTALL_PARSE_FAILED_BAD_PACKAGE_NAMEmanifest 文件里没有或者使用了无效的包名INSTALL_PARSE_FAILED_BAD_SHARED_USER_IDmanifest 文件里指定了无效的共享用户 IDINSTALL_PARSE_FAILED_MANIFEST_MALFORMED解析 manifest 文件时遇到结构性错误INSTALL_PARSE_FAILED_MANIFEST_EMPTY在 manifest 文件里找不到找可操作标签(instrumentation 或 application)INSTALL_FAILED_INTERNAL_ERROR因系统问题安装失败INSTALL_FAILED_USER_RESTRICTED用户被限制安装应用INSTALL_FAILED_DUPLICATE_PERMISSION应用尝试定义一个已经存在的权限名称INSTALL_FAILED_NO_MATCHING_ABIS应用包含设备的应用程序二进制接口不支持的 native codeINSTALL_CANCELED_BY_USER应用安装需要在设备上确认,但未操作设备或点了取消在设备上同意安装INSTALL_FAILED_ACWF_INCOMPATIBLE应用程序与设备不兼容INSTALL_FAILED_TEST_ONLYAPK 文件是使用 Android Studio 直接 RUN 编译出来的文件通过 Gradle 的 assembleDebug 或 assembleRelease 重新编译,或者 Generate Signed APKdoes not contain AndroidManifest.xml无效的 APK 文件is not a valid zip file无效的 APK 文件Offline设备未连接成功先将设备与 adb 连接成功unauthorized设备未授权允许调试error: device not found没有连接成功的设备先将设备与 adb 连接成功protocol failure设备已断开连接先将设备与 adb 连接成功Unknown option: -sAndroid 2.2 以下不支持安装到 sdcard不使用 -s 参数No space left on device空间不足清理空间Permission denied ... sdcard ...sdcard 不可用signatures do not match the previously installed version; ignoring!已安装该应用且签名不一致先卸载设备上的该应用,再安装参考:PackageManager.javaadb install 实际是分三步完成:push apk 文件到 /data/local/tmp。调用 pm install 安装。删除 /data/local/tmp 下的对应 apk 文件。4.2.3 卸载应用卸载应用的基本命令格式是:adb uninstall [-k] <package-name><package-name> 表示应用的包名,-k 参数可选,表示卸载应用但保留数据和缓存目录。4.2.4 清理应用数据与缓存adb shell pm clear <package-name><package-name> 表示应用名包,这条命令的效果相当于在设置里的应用信息界面点击了「清除缓存」和「清除数据」。4.2.5 查看前台 Activityadb shell dumpsys activity activities | grep mFocusedActivity4.2.6 查看正在运行的 Servicesadb shell dumpsys activity services [<package-name>]<package-name> 参数不是必须的,指定 <package-name> 表示查看与某个包名相关的 Services,不指定表示查看所有 Services。<package-name> 不一定要给出完整的包名,可以仅给一部分,那么所给包名相关的 Services 都会列出来。4.2.7 查看应用详细信息adb shell dumpsys package <package-name><package-name> 表示应用包名。运行次命令的输出中包含很多信息,包括 Activity Resolver Table、Registered ContentProviders、包名、userId、安装后的文件资源代码等路径、版本信息、权限信息和授予状态、签名版本信息等。4.2.7 查看应用安装路径adb shell pm path <package-name>4.3 与应用交互与应用交互主要是使用 am <command> 命令,常用的 <command> 如下:command用途start [options] <intent>启动 <intent> 指定的 Activitystartservice [options] <intent>启动 <intent> 指定的 Servicebroadcast [options] <intent>发送 <intent> 指定的广播force-stop <package-name>停止 <package-name> 相关的进程<intent> 参数很灵活,和写 Android 程序时代码里的 Intent 相对应。用于决定 intent 对象的选项如下:参数含义-a <action>指定 action,比如 android.intent.action.VIEW-c <category>指定 category,比如 android.intent.category.APP_CONTACTS-n <component>指定完整 component 名,用于明确指定启动哪个 Activity<intent> 里还能带数据,就像写代码时的 Bundle 一样:参数含义--esn <extra-key>null 值(仅 key 名)`-e\--es `--ez <extra-key> <extra-boolean-value>boolean 值--ei <extra-key> <extra-int-value>integer 值--el <extra-key> <extra-long-value>long 值--ef <extra-key> <extra-float-value>float 值--eu <extra-key> <extra-uri-value>URI--ecn <extra-key> <extra-component-name-value>component name--eia <extra-key> <extra-int-value>[,<extra-int-value...]integer 数组--ela <extra-key> <extra-long-value>[,<extra-long-value...]long 数组4.3.1 启动应用/ 调起 Activityadb shell am start [options] <intent>例如:adb shell am start -a android.settings.SETTINGS # 打开系统设置页面
adb shell am start -a android.intent.action.DIAL -d tel:10086 # 打开拨号页面
adb shell am start -n com.android.mms/.ui.ConversationList # 打开短信会话列表options 是一些改变其行为的选项,支持的可选参数及含义如下:选项含义-D启用调试-W等待启动完成--start-profiler file启动分析器并将结果发送到 file-P file类似于 --start-profiler,但当应用进入空闲状态时分析停止-R count重复 Activity 启动次数-S启动 Activity 前强行停止目标应用--opengl-trace启用 OpenGL 函数的跟踪--user user_id \current4.3.2 调起 Serviceadb shell am startservice [options] <intent>一个典型的用例是,若设备上原本应该显示虚拟按键但是没有显示,可以试试这个:adb shell am startservice -n com.android.systemui/.SystemUIService4.3.3 停止 Serviceadb shell am stopservice [options] <intent>4.3.4 强制停止应用adb shell am force-stop <packagename>4.4 文件管理4.4.1 从模拟器/设备下载指定的文件到计算机从模拟器/设备下载指定的文件到计算机的基本命令格式是:adb pull <remote> [local]参数说明:remote: 模拟器/设备里的文件路径local:计算机上的目录,参数可以省略,默认复制到当前目录例如,将 /sdcard/<package>.apk 下载到当前目录:adb pull /sdcard/<package>.apk将 /sdcard/<package>.apk 下载到相应目录(目录需存在):adb pull /sdcard/<package>.apk D:\Download提示:该操作可结合上文所描述的查看应用安装路径的操作以达到导出相应的应用软件的目的。4.4.2 将指定的文件从计算机上传到模拟器/设备将指定的文件从计算机上传到模拟器/设备的基本命令格式是:adb push <local> <remote>参数说明:local:计算机上的文件路径remote: 模拟器/设备里的目录例如,将 D:\Download\下载到设备的/sdcard/music/目录:adb push D:\Download\ /sdcard/music/4.4.4 列出指定目录的内容列出模拟器/设备上指定目录的内容的基本命令格式是:adb shell ls [options] <directory><directory> 表示指定目录,可以省略,表示列出根目录下的所有文件和目录。 adb shell ls 后面可以跟一些可选参数进行过滤查看不同的列表,可用参数及含义如下:参数显示列表无列出目录下的所有文件和目录-a列出目录下的所有文件(包括隐藏的)-i列出目录下的所有文件和索引编号-s列出目录下的所有文件和文件大小-n列出目录下的所有文件及其 UID和 GID-R列出目录下的所有子目录中的文件4.4.5 切换到目标目录adb shell cd <directory>第一步:执行adb shell命令;第二步:执行cd <directory>命令切换到目标目录。4.4.6 删除文件或目录adb shell rm [options] <files or directory>第一步:执行adb shell命令;第二步:执行rm [options] <files or directory>命令删除文件或目录。rm 后面可以跟一些可选参数进行不同的操作,可用参数及含义如下:参数含义无删除文件-f强制删除文件,系统不提示-r强制删除指定目录中的所有文件和子目录-d删除指定目录,即使它是一个非空目录-i交互式删除,删除前提示rm -d 等同于 rmdir 命令,有些版本不包含-d 参数。4.4.7 创建目录adb shell mkdir [options] <directory-name>第一步:执行adb shell命令;第二步:执行mkdir [options] <directory-name>命令创建目录。 mkdir 后面可以跟一些可选参数进行不同的操作,可用参数及含义如下:参数含义无创建指定目录-m创建指定目录并赋予读写权限-p创建指定目录及其父目录4.4.8 创建空文件或改变文件时间戳adb shell touch [options] <file>第一步:执行adb shell命令;第二步:执行touch [options] <file>命令创建空文件或改变文件时间戳。可通过ls -n <directory> 命令查看文件的时间。4.4.9 输出当前目录路径adb shell pwd第一步:执行adb shell命令;第二步:执行pwd命令输出当前目录路径。4.4.10 复制文件和目录adb shell cp [options] <source> <dest>第一步:执行adb shell命令;第二步:执行cp [options] <source> <dest>命令复制文件和目录。参数说明:source:源文件路径dest: 目标文件路径4.4.11 移动或重命名文件adb shell mv [options] <source> <dest>第一步:执行adb shell命令;第二步:执行mv [options] <source> <dest>命令移动或重命名文件。参数说明:source:源文件路径dest: 目标文件路径4.5 网络管理4.5.1 查看网络统计信息adb shell netstat也可以将网络统计信息输出到指定文件:adb shell netstat > <file-path>例如,可以通过 adb shell netstat > D:\netstat.log 将日志输出到 D:\netstat.log 中。4.5.2 测试两个网络间的连接和延迟ping 命令的格式如下:adb shell ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]
[-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]
[-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]
[-w deadline] [-W timeout] [hop1 ...] destination例如,ping一个域名:adb shell ping <域名>不结束的话会一直ping下去,可以按 Ctrl + C 停止ping操作。也可以指定ping的次数:adb shell ping -c 4 <域名>4.5.3 通过配置文件配置和管理网络连接netcfg 命令的格式如下:adb shell netcfg [<interface> {dhcp|up|down}]输出示例:rmnet_ims10 DOWN 0.0.0.0/0 0x00001002
rmnet_ims00 DOWN 0.0.0.0/0 0x00001002
rmnet_tun04 DOWN 0.0.0.0/0 0x00001002
rmnet_tun03 DOWN 0.0.0.0/0 0x00001002
rmnet_tun02 DOWN 0.0.0.0/0 0x00001002
rmnet_tun01 DOWN 0.0.0.0/0 0x00001002
rmnet_tun00 DOWN 0.0.0.0/0 0x00001002
rmnet_tun14 DOWN 0.0.0.0/0 0x00001002
rmnet_tun13 DOWN 0.0.0.0/0 0x00001002
rmnet_tun12 DOWN 0.0.0.0/0 0x00001002
rmnet_tun11 DOWN 0.0.0.0/0 0x00001002
rmnet_tun10 DOWN 0.0.0.0/0 0x00001002
rmnet1 DOWN 0.0.0.0/0 0x00001002
rmnet0 DOWN 0.0.0.0/0 0x00001002
rmnet4 DOWN 0.0.0.0/0 0x00001002
rmnet3 DOWN 0.0.0.0/0 0x00001002
rmnet2 DOWN 0.0.0.0/0 0x00001002
rmnet6 DOWN 0.0.0.0/0 0x00001002
rmnet5 DOWN 0.0.0.0/0 0x00001002
dummy0 UP 0.0.0.0/0 0x000000c3
rmnet_r_ims10 DOWN 0.0.0.0/0 0x00001002
rmnet_r_ims00 DOWN 0.0.0.0/0 0x00001002
rmnet_emc0 DOWN 0.0.0.0/0 0x00001002
lo UP 127.0.0.1/8 0x00000049
sit0 DOWN 0.0.0.0/0 0x00000080
wlan0 UP 10.0.38.176/23 0x00001043
复制代码4.5.4 显示、操作路由、设备、策略路由和隧道ip 命令的格式如下:adb shell ip [ options ] objectoptions := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |-f[amily] { inet | inet6 | ipx | dnet | link } |-l[oops] { maximum-addr-flush-attempts } |-o[neline] | -t[imestamp] | -b[atch] [filename] |-rc[vbuf] [size]}object := { link | addr | addrlabel | route | rule | neigh | ntable |tunnel | tuntap | maddr | mroute | mrule | monitor | xfrm |netns | l2tp }options 是一些修改ip行为或者改变其输出的选项。所有的选项都是以-字符开头,分为长、短两种形式,支持的可选参数及含义如下:选项含义-V,-Version打印ip的版本并退出-s,-stats,-statistics输出更为详尽的信息(若这个选项出现两次或者多次,输出的信息将更为详尽)-f,-family强调使用的协议种类(包括:inet、inet6或者link)-4是-family inet的简写-6是-family inet6的简写-0是-family link的简写-o,-oneline对每行记录都使用单行输出,回行用字符代替-r,-resolve查询域名解析系统,用获得的主机名代替主机IP地址object 是要管理或者获取信息的对象。目前ip认识的对象包括:参数显示列表link网络设备address一个设备的协议(IP或者IPV6)地址neighbourARP或者NDISC缓冲区条目route路由表条目rule路由策略数据库中的规则maddress多播地址mroute多播路由缓冲区条目tuntap管理 TUN/TAP 设备netns管理网络空间例如,查看 WiFi IP 地址:adb shell ip -f inet addr show wlan04.6 模拟按键/输入在 adb shell 里有个很实用的命令叫 input,通过它可以做一些有趣的事情。可以执行adb shell input命令查看完整 help 信息如下:Usage: input [<source>] <command> [<arg>...]
The sources are:
dpad
keyboard
mouse
touchpad
gamepad
touchnavigation
joystick
touchscreen
stylus
trackball
The commands and default sources are:
text <string> (Default: touchscreen)
keyevent [--longpress] <key code number or name> ... (Default: keyboard)
tap <x> <y> (Default: touchscreen)
swipe <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen)
draganddrop <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen)
press (Default: trackball)
roll <dx> <dy> (Default: trackball)比如使用 adb shell input keyevent <keycode> 命令,不同的 keycode 能实现不同的功能,完整的 keycode 列表详见 KeyEvent,摘引部分觉得有意思的如下:keycode含义3HOME 键4返回键5打开拨号应用6挂断电话24增加音量25降低音量26电源键27拍照(需要在相机应用里)64打开浏览器82菜单键85播放/暂停86停止播放87播放下一首88播放上一首122移动光标到行首或列表顶部123移动光标到行末或列表底部126恢复播放127暂停播放164静音176打开系统设置187切换应用207打开联系人208打开日历209打开音乐210打开计算器220降低屏幕亮度221提高屏幕亮度223系统休眠224点亮屏幕231打开语音助手276若没有 wakelock 则让系统休眠下面是 input 命令的一些用法举例。4.6.1 电源键adb shell input keyevent 26执行效果相当于按电源键。4.6.2 菜单键adb shell input keyevent 824.6.3 HOME 键adb shell input keyevent 34.6.4 返回键adb shell input keyevent 44.6.5 音量控制增加音量:adb shell input keyevent 24降低音量:adb shell input keyevent 25静音:adb shell input keyevent 164.6.6 媒体控制播放/暂停:adb shell input keyevent 85停止播放:adb shell input keyevent 86播放下一首:adb shell input keyevent 87播放上一首:adb shell input keyevent 88恢复播放:adb shell input keyevent 126暂停播放:adb shell input keyevent 1274.6.7 点亮/熄灭屏幕点亮屏幕:adb shell input keyevent 224熄灭屏幕:adb shell input keyevent 2234.6.8 滑动解锁若锁屏没有密码,是通过滑动手势解锁,那么可以通过 input swipe 来解锁。命令(向上滑动手势解锁举例):adb shell input swipe 300 1000 300 500参数 300 1000 300 500 分别表示起始点x坐标 起始点y坐标 结束点x坐标 结束点y坐标。4.6.9 输入文本在焦点处于某文本框时,可以通过 input 命令来输入文本。adb shell input text hello4.7 日志打印Android 系统的日志分为两部分,底层的 Linux 内核日志输出到 /proc/kmsg,Android 的日志输出到 /dev/log。4.7.1 Android 日志查看 Android 设备系统属性的基本命令格式是:adb logcat [option] [filter-specs]若需要停止 logcat 日志打印,可以按 Ctrl + C 停止日志监控。4.7.1.1 按级别过滤日志按级别过滤日志的基本命令格式是:adb logcat [filter-specs]Android 的日志分为如下几个优先级(priority):级别含义*:V过滤仅显示 Verbose 及以上级别(优先级最低)*:D过滤仅显示 Debug 及以上级别*:I过滤仅显示 Info 及以上级别*:W过滤仅显示 Warning 及以上级别*:E过滤仅显示 Error 及以上级别*:F过滤仅显示 Fatal 及以上级别*:S过滤仅显示 Silent 及以上级别(优先级最高,什么也不输出)按某级别过滤日志则会将该级别及以上的日志输出。比如,命令:adb logcat *:W会将 Warning、Error、Fatal 和 Silent 日志输出。注意: 在 macOS 下需要给 *:W 这样以 * 作为 tag 的参数加双引号,如 adb logcat "*:W",不然会报错 no matches found: *:W。4.7.1.2 按 tag 和级别过滤日志按 tag 和级别过滤日志的基本命令格式是:adb logcat [tag:level] [tag:level] ...比如,命令:adb logcat ActivityManager:I MyApp:D *:S表示输出 tag ActivityManager 的 Info 以上级别日志,输出 tag MyApp 的 Debug 以上级别日志,及其它 tag 的 Silent 级别日志(即屏蔽其它 tag 日志)。4.7.1.3 将日志格式化输出可以用 adb logcat -v <format> 选项指定日志输出格式。日志支持按以下几种 <format>:参数显示格式brief<priority>/<tag>(<pid>): <message>process<priority>(<pid>) <message>tag<priority>/<tag>: <message>raw<message>time<datetime> <priority>/<tag>(<pid>): <message>threadtime<datetime> <pid> <tid> <priority> <tag>: <message>long[ <datetime> <pid>:<tid> <priority>/<tag> ] <message>日志格式默认为 brief,指定格式可与上面的过滤同时使用。比如:adb logcat -v long ActivityManager:I *:S4.7.1.3 清空已存在的日志adb logcat -c4.7.1.4 将日志显示在控制台adb logcat -d4.7.1.5 将日志输出到文件adb logcat -f <file-path>4.7.1.6 加载一个可使用的日志缓冲区供查看adb logcat -b <Buffer>Android log 输出量巨大,特别是通信系统的log,因此,Android把log输出到不同的缓冲区中,目前定义了四个log缓冲区:缓冲区含义Radio输出通信系统的 logSystem输出系统组件的 logEvent输出 event 模块的 logMain所有 java 层的 log 以及不属于上面3层的 log缓冲区主要给系统组件使用,一般的应用不需要关心,应用的log都输出到main缓冲区中。默认log输出(不指定缓冲区的情况下)是输出System和Main缓冲区的log。4.7.1.7 打印指定日志缓冲区的大小adb logcat -g4.7.2 内核日志adb shell dmesg输出示例:<6>[14201.684016] PM: noirq resume of devices complete after 0.982 msecs
<6>[14201.685525] PM: early resume of devices complete after 0.838 msecs
<6>[14201.753642] PM: resume of devices complete after 68.106 msecs
<4>[14201.755954] Restarting tasks ... done.
<6>[14201.771229] PM: suspend exit 2016-08-28 13:31:32.679217193 UTC
<6>[14201.872373] PM: suspend entry 2016-08-28 13:31:32.780363596 UTC
<6>[14201.872498] PM: Syncing filesystems ... done.
复制代码中括号里的 [14201.684016] 代表内核开始启动后的时间,单位为秒。通过内核日志可以做一些事情,比如衡量内核启动时间,在系统启动完毕后的内核日志里找到 Freeing init memory 那一行前面的时间就是。4.8 查看 Android 设备系统属性查看 Android 设备系统属性的基本命令格式是:adb shell getprop [options]除了可以查看 Android 设备系统属性之外,还可以设置系统属性,设置系统属性的基本命令格式是:adb shell setprop <key> <value>4.8.1 查看设备型号adb shell getprop ro.product.model4.8.2 查看设备电池状况adb shell dumpsys battery输出示例:Current Battery Service state:
AC powered: false
USB powered: true
Wireless powered: false
status: 2
health: 2
present: true
level: 44
scale: 100
voltage: 3872
temperature: 280
technology: Li-poly其中 scale 代表最大电量,level 代表当前电量。上面的输出表示还剩下 44% 的电量。4.8.3 查看设备屏幕分辨率adb shell wm size输出示例:Physical size: 1080x1920该设备屏幕分辨率为 1080px * 1920px。若使用命令修改过,那输出可能是:Physical size: 1080x1920
Override size: 480x1024表明设备的屏幕分辨率原本是 1080px * 1920px,当前被修改为 480px * 1024px。4.8.4 查看设备屏幕密度adb shell wm density输出示例:Physical density: 360该设备屏幕密度为 360dpi。若使用命令修改过,那输出可能是:Physical density: 420
Override density: 360表明设备的屏幕密度原来是 420dpi,当前被修改为 360dpi。4.8.5 查看设备显示屏参数adb shell dumpsys window displays输出示例:WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)
Display: mDisplayId=0
init=1080x1920 420dpi cur=1080x1920 app=1080x1794 rng=1080x1017-1810x1731
deferred=false layoutNeeded=false
复制代码其中 mDisplayId 为 显示屏编号,init 是初始分辨率和屏幕密度,app 的高度比 init 里的要小,表示屏幕底部有虚拟按键,高度为 1920 - 1794 = 126px 合 42dp。4.8.6 查看设备 android_idadb shell settings get secure android_id输出示例:5a3a3aa2c30421844.8.7 查看设备IMEI在 Android 4.4 及以下版本可通过如下命令获取 IMEI:adb shell dumpsys iphonesubinfo输出示例:Phone Subscriber Info:
Phone Type = GSM
Device ID = 860955027785041其中的 Device ID 就是 IMEI。而在 Android 5.0 及以上版本里这个命令输出为空,得通过其它方式获取了(需要 root 权限):adb shell
su
service call iphonesubinfo 1把里面的有效内容提取出来就是 IMEI 了,比如这里的是 890956027785041。参考:adb shell dumpsys iphonesubinfo not working since Android 5.0 Lollipop4.8.8 查看设备 Android 系统版本adb shell getprop ro.build.version.release4.8.9 查看设备 IP 地址adb shell ifconfig | grep Mask在有的设备上这个命令没有输出,若设备连着 WiFi,可以使用如下命令来查看局域网 IP:adb shell ifconfig wlan0若以上命令仍然不能得到期望的信息,那可以试试以下命令(部分系统版本里可用):adb shell netcfg4.8.10 查看设备 Mac 地址adb shell cat /sys/class/net/wlan0/address这查看的是局域网 Mac 地址,移动网络或其它连接的信息可以通过前面的小节「IP 地址」里提到的 adb shell netcfg 命令来查看。4.8.11 查看设备 CPU 信息adb shell cat /proc/cpuinfo4.8.12 查看设备内存信息adb shell cat /proc/meminfo4.8.13 查看设备更多硬件与系统属性设备的更多硬件与系统属性可以通过如下命令查看:adb shell cat /system/build.prop这会输出很多信息,包括前面几个小节提到的「型号」和「Android 系统版本」等。输出里还包括一些其它有用的信息,也可通过 adb shell getprop <属性名> 命令单独查看,列举一部分属性如下:属性名含义ro.build.version.sdkSDK 版本ro.build.version.releaseAndroid 系统版本ro.build.version.security_patchAndroid 安全补丁程序级别ro.product.model型号ro.product.brand品牌ro.product.name设备名ro.product.board处理器型号ro.product.cpu.abilistCPU 支持的 abi 列表[节注一]persist.sys.isUsbOtgEnabled是否支持 OTGdalvik.vm.heapsize每个应用程序的内存上限ro.sf.lcd_density屏幕密度注意:一些小厂定制的 ROM 可能修改过 CPU 支持的 abi 列表的属性名。若用 ro.product.cpu.abilist 属性名查找不到,可以这样试试:adb shell cat /system/build.prop | grep ro.product.cpu.abi4.9 修改设置注意: 修改设置之后,运行恢复命令有可能显示仍然不太正常,可以运行 adb reboot 重启设备,或手动重启。修改设置的原理主要是通过 settings 命令修改 /data/data/com.android.providers.settings/databases/settings.db 里存放的设置值。4.9.1 修改分辨率adb shell wm size 480x1024表示将分辨率修改为 480px * 1024px。恢复原分辨率命令:adb shell wm size reset4.9.2 修改屏幕密度adb shell wm density 160表示将屏幕密度修改为 160dpi。恢复原屏幕密度命令:adb shell wm density reset手机分辨率对照表宽×高(标准值)240×320320×480480×800720×12801080×19201440×2560DPI等级LDPIMDPIHDPIXHDPIXXHDPIXXXHDPIDPI数值120160240320480640对应比例34681216PX0.7511.52344.9.3 修改显示区域adb shell wm overscan 0,0,0,200四个数字分别表示距离左、上、右、下边缘的留白像素,以上命令表示将屏幕底部 200px 留白。恢复原显示区域命令:adb shell wm overscan reset4.9.4 修改关闭 USB 调试模式adb shell settings put global adb_enabled 0用命令恢复不了了,毕竟关闭了 USB 调试 adb 就连接不上 Android 设备了。去设备上手动恢复吧:「设置」-「开发者选项」-「Android 调试」。4.9.5 修改允许/禁止访问非 SDK API允许访问非 SDK API:adb shell settings put global hidden_api_policy_pre_p_apps 1
adb shell settings put global hidden_api_policy_p_apps 1禁止访问非 SDK API:adb shell settings delete global hidden_api_policy_pre_p_apps
adb shell settings delete global hidden_api_policy_p_apps不需要设备获得 Root 权限。命令最后的数字的含义:值含义0禁止检测非 SDK 接口的调用。该情况下,日志记录功能被禁用,并且令 strict mode API,即 detectNonSdkApiUsage() 无效。不推荐。1仅警告——允许访问所有非 SDK 接口,但保留日志中的警告信息,可继续使用 strick mode API。2禁止调用深灰名单和黑名单中的接口。3禁止调用黑名单中的接口,但允许调用深灰名单中的接口。4.9.6 修改状态栏和导航栏的显示隐藏adb shell settings put global policy_control <key-values><key-values> 可由如下几种键及其对应的值组成,格式为 <key1>=<value1>:<key2>=<value2>。key含义immersive.full同时隐藏immersive.status隐藏状态栏immersive.navigation隐藏导航栏immersive.preconfirms?这些键对应的值可则如下值用逗号组合:value含义apps所有应用*所有界面package-name指定应用-package-name排除指定应用例如:adb shell settings put global policy_control immersive.full=*表示设置在所有界面下都同时隐藏状态栏和导航栏。adb shell settings put global policy_control immersive.status=com.package1,com.package2:immersive.navigation=apps,-com.package3表示设置在包名为 com.package1 和 com.package2 的应用里隐藏状态栏,在除了包名为 com.package3 的所有应用里隐藏导航栏。4.11 实用功能4.11.1 屏幕截图截图保存到电脑:adb exec-out screencap -p > sc.png若 adb 版本较老,无法使用 exec-out 命令,建议更新 adb 版本。无法更新的话可以使用以下麻烦点的办法:先截图保存到设备里:adb shell screencap -p /sdcard/sc.png然后将 png 文件导出到电脑:adb pull /sdcard/sc.png可以使用 adb shell screencap -h 查看 screencap 命令的帮助信息,下面是两个有意义的参数及含义:参数含义-p指定保存文件为 png 格式-d display-id指定截图的显示屏编号(有多显示屏的情况下)实测若指定文件名以 .png 结尾时可以省略 -p 参数;否则需要使用 -p 参数。若不指定文件名,截图文件的内容将直接输出到 stdout。另外一种一行命令截图并保存到电脑的方法: Linux 和 Windowsadb shell screencap -p | sed "s/\r$//" > sc.pngMac OS Xadb shell screencap -p | gsed "s/\r$//" > sc.png这个方法需要用到 gnu sed 命令,在 Linux 下直接就有,在 Windows 下 Git 安装目录的 bin 文件夹下也有。若找不到该命令,可以下载 sed for Windows 并将 sed.exe 所在文件夹添加到 PATH 环境变量里。而在 Mac 下使用系统自带的 sed 命令会报错:sed: RE error: illegal byte sequence需要安装 gnu-sed,然后使用 gsed 命令:brew install gnu-sed4.11.2 录制屏幕录制屏幕以 mp4 格式保存到 /sdcard:adb shell screenrecord /sdcard/filename.mp4需要停止时按 Ctrl-C,默认录制时间和最长录制时间都是 180 秒。若需要导出到电脑:adb pull /sdcard/<filename>.mp4可以使用 adb shell screenrecord --help 查看 screenrecord 命令的帮助信息,下面是常见参数及含义:参数含义--size WIDTHxHEIGHT视频的尺寸,比如 1280x720,默认是屏幕分辨率。--bit-rate RATE视频的比特率,默认是 4Mbps。--time-limit TIME录制时长,单位秒。--verbose输出更多信息。4.11.3 查看连接过的 WiFi 密码注:需要 root 权限。adb shell
su
cat /data/misc/wifi/*.conf4.11.4 设置系统日期和时间注:需要 root 权限。adb shell
su
date -s yyyyMMdd.HHmmss注意:表示将系统日期和时间更改为 yyyy 年 MM 月 dd 日 HH 点 mm 分 ss 秒。设置系统日期和时间后,需使用busybox hwclock -w命令以使得系统时间同步硬件时钟, 否则肯会出现重启不生效的问题。4.11.4.1 读取系统时间adb shell "date"4.11.4.2 读取系统时区adb shell "getprop persist.sys.timezone"4.11.4.3 设置系统时区adb shell "setprop persist.sys.timezone <Region>/<City>"<Region>/<City>为设置的时区,即<地区名>/<城市名>。以设置上海时区为例:Asia/Shanghai。注意:时区更新组件中平台服务功能 (timezone.RulesManagerService)默认处于停用状态。OEM 必须通过相应配置启用该功能。RulesManagerService 在系统服务器进程中运行,并通过写入 /data/misc/zoneinfo/staged 来暂存时区更新操作。RulesManagerService 还可以替换或删除已经暂存的操作。4.11.4.4 设置系统 NTP 服务器adb shell "settings put global ntp_server <NTP服务器地址>"手机重启联网后,将会自动校时。国内常见的NTP服务器地址如下:东北大学:ntp.synet.edu.cnntp.neu.edu.cn上海交大:ntp.sjtu.edu.cnpool.ntp.org:cn.pool.ntp.org阿里云NTP服务器:ntp1.aliyun.comntp2.aliyun.comntp3.aliyun.comntp4.aliyun.comntp5.aliyun.comntp6.aliyun.comntp7.aliyun.com可使用如下命令以查询是否设置成功:adb shell settings get global ntp_server4.11.5 重启手机adb reboot4.11.6 检测设备是否已 rootadb shell
su此时命令行提示符是 $ 则表示没有 root 权限,是 # 则表示已 root。4.11.7 使用 Monkey 进行压力测试Monkey 可以生成伪随机用户事件来模拟单击、触摸、手势等操作,可以对正在开发中的程序进行随机压力测试。简单用法:adb shell monkey -p <packagename> -v 500表示向 <packagename> 指定的应用程序发送 500 个伪随机事件。Monkey 的详细用法参考 官方文档。4.11.8 开启/关闭 WiFi注:需要 root 权限。开启 WiFi:adb root
adb shell svc wifi enable关闭 WiFi:adb root
adb shell svc wifi disable若执行成功,输出为空;若未取得 root 权限执行此命令,将执行失败,输出 Killed。4.12 安全相关命令4.12.1 启用/禁用 SELinux启用 SELinuxadb root
adb shell setenforce 1禁用 SELinuxadb root
adb shell setenforce 04.12.2 启用/禁用 dm_verity启用 dm_verityadb root
adb enable-verity禁用 dm_verityadb root
adb disable-verity4.13 更多 adb shell 命令Android 系统是基于 Linux 内核的,所以 Linux 里的很多命令在 Android 里也有相同或类似的实现,在 adb shell 里可以调用。本文档前面的部分内容已经用到了 adb shell 命令。4.14.1 查看进程状态adb shell ps输出信息各列含义:列名含义USER所属用户PID进程 IDPPID父进程 IDNAME进程名4.14.2 查看处理器实时状态adb shell top [-m max_procs] [-n iterations] [-d delay] [-s sort_column] [-t] [-h]adb shell top 后面可以跟一些可选参数进行过滤查看不同的列表,可用参数及含义如下:参数含义-m最多显示多少个进程-n刷新多少次后退出-d刷新时间间隔(单位秒,默认值5)-s按某列排序(可用col值:cpu, vss, rss, thr)-t显示线程信息-h显示帮助文档输出信息各列含义:列名含义PID进程 IDPR优先级CPU%当前瞬间占用 CPU 百分比S进程状态(R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程)#THR线程数VSSVirtual Set Size 虚拟耗用内存(包含共享库占用的内存)RSSResident Set Size 实际使用物理内存(包含共享库占用的内存)PCY调度策略优先级,SP_BACKGROUND/SPFOREGROUNDUID进程所有者的用户 IDNAME进程名4.14.3 查看进程 UID有两种方案:方案一:adb shell dumpsys package <packagename> | grep userId=例如:adb shell dumpsys package org.mazhuang.guanggoo | grep userId=
userId=10394方案二:通过 ps 命令找到对应进程的 pid 之后 adb shell cat /proc/<pid>/status | grep Uid 如:adb shellps | grep org.mazhuang.guanggoo
u0_a394 28635 770 1795812 78736 SyS_epoll_ 0000000000 S org.mazhuang.guanggoocat /proc/28635/status | grep Uid
Uid: 10394 10394 10394 10394附录:MIUI 预装软件列表(含系统组件)软件名称软件包名一体化位置信息com.android.location.fused万能遥控com.duokan.phone.remotecontroller三方应用异常分析com.miui.thirdappassistant下载管理com.android.providers.downloads.ui下载管理程序com.android.providers.downloads主屏幕提示com.android.protips主题壁纸com.android.thememanager今日头条com.ss.android.article.news传送门com.miui.contentextension健康com.mi.health全球上网com.miui.virtualsim关机闹钟com.qualcomm.qti.poweroffalarm内容中心com.miui.newhome卫星定位com.xiaomi.bsp.gps.nps双刘海屏com.android.internal.display.cutout.emulation.double垃圾清理com.miui.cleanmaster基本互动屏保com.android.dreams.basic声音com.android.soundpicker备份com.miui.backup外部存储设备com.android.externalstorage天星金融com.xiaomi.jr天气com.miui.weather2媒体存储设备com.android.providers.media.module存储已屏蔽的号码com.android.providers.blockednumber存储空间管理器com.android.storagemanager安全核心组件com.miui.securitycore密钥链com.android.keychain小爱同学com.miui.voiceassist小米SIM卡激活服务com.xiaomi.simactivate.service小米云服务com.miui.cloudservice小米云盘com.miui.newmidrive小米互传com.miui.mishare.connectivity小米互联通信服务com.xiaomi.mi_connect_service小米商城com.xiaomi.shop小米商城系统组件com.xiaomi.ab小米安全键盘com.miui.securityinputmethod小米帐号com.xiaomi.account小米换机com.miui.huanji小米支付com.miui.nextpay小米文档查看器(WPS定制)cn.wps.moffice_eng.xiaomi.lite小米智能卡com.miui.tsmclient小米有品com.xiaomi.youpin小米服务框架com.xiaomi.xmsf小米画报com.mfashiongallery.emag小米直播助手com.mi.liveassistant小米社区com.xiaomi.vipaccount小米视频com.miui.video小米设置com.xiaomi.misettings小米钱包com.mipay.wallet小米闻声com.miui.accessibility屏幕录制com.miui.screenrecorder工作设置com.android.managedprovisioning常用语com.miui.phrase应用包管理组件com.miui.packageinstaller应用商店com.xiaomi.market应用程序扩展服务com.miui.contentcatcher开机引导com.android.provision录音机com.android.soundrecorder微博com.sina.weibo快应用服务框架com.miui.hybrid急救信息com.android.emergency性能模式com.qualcomm.qti.performancemode悬浮球com.miui.touchassistant截屏com.miui.screenshot手机淘宝com.taobao.taobao手机管家com.miui.securitycenter打印处理服务com.android.printspooler打孔屏com.android.internal.display.cutout.emulation.hole扫一扫com.xiaomi.scanner投屏com.milink.service投屏服务com.xiaomi.miplay_client抖音短视频com.ss.android.ugc.aweme拼多多com.xunmeng.pinduoduo指南针com.miui.compass指纹测试com.goodix.gftest搜狗输入法小米版com.sohu.inputmethod.sogou.xiaomi搜索com.android.quicksearchbox支付宝com.eg.android.AlipayGphone收音机com.miui.fm收音机调频服务com.miui.fmservice文件com.google.android.documentsui文件管理com.android.fileexplorer日历com.android.calendar日历存储com.android.providers.calendar时钟com.android.deskclock智慧生活com.miui.hybrid.accessory智能出行com.miui.smarttravel智能助理com.miui.personalassistant智能服务com.miui.systemAdSolution服务与反馈com.miui.miservice权限控制器com.android.permissioncontroller权限管理服务com.lbe.security.miui查找手机com.xiaomi.finddevice桌面云备份com.miui.cloudbackup浏览器com.android.browser淘特com.taobao.litetao游戏中心com.xiaomi.gamecenter游戏服务com.xiaomi.gamecenter.sdk.service瀑布刘海屏com.android.internal.display.cutout.emulation.waterfall照片屏幕保护程序com.android.dreams.phototable爱奇艺com.qiyi.video生活黄页com.miui.yellowpage用户反馈com.miui.bugreport用户字典com.android.providers.userdictionary电子邮件com.android.email电话com.android.incallui电话和短信存储com.android.providers.telephony电话服务com.android.phone电量和性能com.miui.powerkeeper番茄免费小说com.dragon.read百度com.baidu.searchbox百度地图com.baidu.BaiduMap百度输入法小米版com.baidu.input_mi相册com.miui.gallery相机com.android.camera相机标定com.xiaomi.cameratools短信com.android.mms笔记com.miui.notes米家com.xiaomi.smarthome米币支付com.xiaomi.payment系统_WLAN_资源com.android.wifi.resources系统打印服务com.android.bips系统更新com.android.updater系统服务组件com.miui.securityadd系统桌面com.miui.home系统界面com.android.systemui系统语音引擎com.xiaomi.mibrain.speech系统跟踪com.android.traceur维修模式com.miui.maintenancemode网络位置服务com.xiaomi.metoknlp网络管理器com.android.networkstack.inprocess耗电检测com.xiaomi.powerchecker联系人存储com.android.providers.contacts腾讯视频com.tencent.qqlive自由窗口com.miui.freeform蓝牙com.android.bluetooth融合位置服务com.xiaomi.location.fused计算器com.miui.calculator讯飞输入法小米版com.iflytek.inputmethod.miui设备信息com.qti.qualcomm.deviceinfo设置com.android.settings设置存储com.android.providers.settings证书安装程序com.android.certinstaller语音唤醒com.miui.voicetrigger输入设备com.android.inputdevices边角刘海屏com.android.internal.display.cutout.emulation.corner运营商默认应用com.android.carrierdefaultapp通知管理com.miui.notification通讯录与拨号com.android.contacts通话管理com.android.server.telecom配套设备管理器com.android.companiondevicemanager银联可信服务安全组件小米版本com.unionpay.tsmservice.mi长型刘海屏com.android.internal.display.cutout.emulation.tall阅读com.duokan.reader音乐com.miui.player音质音效com.miui.misound驾车场景com.xiaomi.drivemodeAI虚拟助手com.xiaomi.aiasst.serviceAndroid_无障碍套件com.google.android.marvin.talkback3_Button_Navigation_Barcom.android.internal.systemui.navbar.threebuttonAdreno_Graphics_Driverscom.qualcomm.qti.gpudrivers.lito.api30AiasstVisioncom.xiaomi.aiasst.visionAnalyticscom.miui.analyticsAndroid_Services_Librarycom.google.android.ext.servicesAndroid_Shared_Librarycom.google.android.ext.sharedAndroid_System_WebViewcom.google.android.webviewAudioEffectcom.miui.audioeffectBlackcom.android.theme.color.blackBluetooth_MIDI_Servicecom.android.bluetoothmidiserviceBokehcom.miui.extraphotoBookmark_Providercom.android.bookmarkproviderCITcom.miui.citCaptivePortalLogincom.android.captiveportalloginCatchLogcom.bsp.catchlogCell_Broadcast_Servicecom.android.cellbroadcastserviceCinnamoncom.android.theme.color.cinnamonCircularcom.android.theme.icon_pack.circular.androidCircularcom.android.theme.icon_pack.circular.launcherCircularcom.android.theme.icon_pack.circular.settingsCircularcom.android.theme.icon_pack.circular.systemuiCircularcom.android.theme.icon_pack.circular.themepickerCit_QRcom.miui.qrCloudServiceSysbasecom.miui.cloudservice.sysbaseCneAppcom.qualcomm.qti.cneConference_URI_Dialercom.qti.confuridialerConfigUpdatercom.google.android.configupdaterDynamic_System_Updatescom.android.dynsystemEid-Servicecom.rongcard.eidFIDO_UAF1.0_ASMcom.fido.asmFIDO_UAF1.0_Clientcom.fido.xiaomi.uafclientFilledcom.android.theme.icon_pack.filled.androidFilledcom.android.theme.icon_pack.filled.launcherFilledcom.android.theme.icon_pack.filled.settingsFilledcom.android.theme.icon_pack.filled.systemuiFilledcom.android.theme.icon_pack.filled.themepickerFingerprintExtensionServicecom.fingerprints.extension.serviceGFManagercom.goodix.fingerprintGestural_Navigation_Barcom.android.internal.systemui.navbar.gesturalGestural_Navigation_Barcom.android.internal.systemui.navbar.gestural_extra_wide_backGestural_Navigation_Barcom.android.internal.systemui.navbar.gestural_narrow_backGestural_Navigation_Barcom.android.internal.systemui.navbar.gestural_wide_backGoogle_One_Time_Initcom.google.android.onetimeinitializerGoogle_Play_服务com.google.android.gmsGoogle_Play_服务更新程序com.android.vendingGoogle_服务框架com.google.android.gsfGoogle通讯录同步com.google.android.syncadapters.contactsHTML_查看器com.android.htmlviewerMIUI+_Beta版com.xiaomi.mirrorMIUI安全组件com.miui.guardproviderMODEM测试工具com.xiaomi.mtbMTP_主机com.android.mtpNFC服务com.android.nfcSIM卡联系人com.qualcomm.qti.simcontactsUC浏览器com.UCMobileUSIM卡应用com.android.stkWAPI证书com.wapi.wapicertmanageX-Divert设置com.qti.xdivertGreencom.android.theme.color.greenIntent_Filter_Verification_Servicecom.android.statementserviceJoyosecom.xiaomi.joyoseLive_Wallpaper_Pickercom.android.wallpaper.livepickerLocationServicescom.qualcomm.locationMConnServicecom.miui.vsimcoreMIUI_Bluetoothcom.xiaomi.bluetoothMIUI_SDKcom.miui.coreMiCloudSynccom.miui.micloudsyncMi_RCScom.xiaomi.mircsMiuiBiometriccom.miui.faceMiuiDaemoncom.miui.daemonMiuiVpnSdkManagercom.miui.vpnsdkmanagerMmsServicecom.android.mms.serviceModule_Metadatacom.android.modulemetadataNetworkStackOverlaycom.android.networkstack.overlayOceancom.android.theme.color.oceanOrchidcom.android.theme.color.orchidOsuLogincom.android.hotspot2.osuloginPacProcessorcom.android.pacprocessorPebblecom.android.theme.icon.pebblePrint_Service_Recommendation_Servicecom.google.android.printservice.recommendationProxyHandlercom.android.proxyhandlerPurplecom.android.theme.color.purpleQColorcom.qualcomm.qti.qcolorQDCM-FFcom.qti.snapdragon.qdcm_ffQualcomm_Mobile_Securitycom.qualcomm.qti.qms.service.telemetryRegServicecom.miui.dmregserviceRounded_Rectanglecom.android.theme.icon.roundedrectRoundedcom.android.theme.icon_pack.rounded.androidRoundedcom.android.theme.icon_pack.rounded.launcherRoundedcom.android.theme.icon_pack.rounded.settingsRoundedcom.android.theme.icon_pack.rounded.systemuiRoundedcom.android.theme.icon_pack.rounded.themepickerSecCamServicecom.qualcomm.qti.seccamserviceSecureElementApplicationcom.android.seSecure_UI_Servicecom.qualcomm.qti.services.secureuiSensor_Test_Toolcom.fingerprints.sensortesttoolSettings_Suggestionscom.android.settings.intelligenceShellcom.android.shellSim_App_Dialogcom.android.simappdialogSoterServicecom.tencent.soter.soterserverSpacecom.android.theme.color.spaceSquirclecom.android.theme.icon.squircleSystemHelpercom.mobiletools.systemhelperSystem_Helper_Servicecom.qualcomm.qti.services.systemhelperTAMservicecom.xiaomi.otrpbrokerTagscom.android.apps.tagTapered_Rectcom.android.theme.icon.taperedrectTeardropcom.android.theme.icon.teardropTetheringcom.android.networkstack.tethering.inprocessVesselcom.android.theme.icon.vesselVpnDialogscom.android.vpndialogsWMServicecom.miui.wmsvcWallPapercom.miui.miwallpaperWfd_Servicecom.qualcomm.wfd.serviceXiaomi_Service_Framework_Keepercom.xiaomi.xmsfkeepercom.android.backupconfirmcom.android.backupconfirmcom.android.carrierconfig.overlay.commoncom.android.carrierconfig.overlay.commoncom.android.carrierconfigcom.android.carrierconfigcom.android.cellbroadcastreceiver.overlay.commoncom.android.cellbroadcastreceiver.overlay.commoncom.android.cellbroadcastreceivercom.android.cellbroadcastreceivercom.android.cts.ctsshimcom.android.cts.ctsshimcom.android.cts.priv.ctsshimcom.android.cts.priv.ctsshimcom.android.localtransportcom.android.localtransportcom.android.onscom.android.onscom.android.overlay.gmstelecommcom.android.overlay.gmstelecommcom.android.overlay.gmstelephonycom.android.overlay.gmstelephonycom.android.phone.overlay.commoncom.android.phone.overlay.commoncom.android.providers.mediacom.android.providers.mediacom.android.provision.overlay.miuicom.android.provision.overlay.miuicom.android.server.NetworkPermissionConfigcom.android.networkstack.permissionconfigcom.android.server.telecom.overlay.commoncom.android.server.telecom.overlay.commoncom.android.server.telecom.overlay.miuicom.android.server.telecom.overlay.miuicom.android.settings.overlay.miuicom.android.settings.overlay.miuicom.android.sharedstoragebackupcom.android.sharedstoragebackupcom.android.smspushcom.android.smspushcom.android.systemui.gesture.line.overlaycom.android.systemui.gesture.line.overlaycom.android.systemui.icon.overlaycom.android.systemui.icon.overlaycom.android.systemui.navigation.bar.overlaycom.android.systemui.navigation.bar.overlaycom.android.systemui.notch.overlaycom.android.systemui.notch.overlaycom.android.systemui.overlay.commoncom.android.systemui.overlay.commoncom.android.systemui.overlay.miuicom.android.systemui.overlay.miuicom.android.wallpaperbackupcom.android.wallpaperbackupcom.android.wallpapercroppercom.android.wallpapercroppercom.android.wifi.resources.overlay.commoncom.android.wifi.resources.overlay.commoncom.android.wifi.resources.overlay.targetcom.android.wifi.resources.overlay.targetcom.android.wifi.resources.xiaomicom.android.wifi.resources.xiaomicom.google.android.cellbroadcastreceiver.overlay.miuicom.google.android.cellbroadcastreceiver.overlay.miuicom.google.android.cellbroadcastservice.overlay.miuicom.google.android.cellbroadcastservice.overlay.miuicom.google.android.overlay.gmsconfigcom.google.android.overlay.gmsconfigcom.google.android.overlay.modules.documentsuicom.google.android.overlay.modules.documentsuicom.google.android.overlay.modules.ext.servicescom.google.android.overlay.modules.ext.servicescom.miui.catcherpatch.BaseApplicationcom.miui.catcherpatchcom.miui.face.overlay.miuicom.miui.face.overlay.miuicom.miui.internal.app.SystemApplicationcom.miui.systemcom.miui.romcom.miui.romcom.miui.smsextra.internal.SmsExtraAppcom.miui.smsextracom.miui.systemui.carriers.overlaycom.miui.systemui.carriers.overlaycom.miui.systemui.devices.overlaycom.miui.systemui.devices.overlaycom.miui.systemui.overlay.devices.androidcom.miui.systemui.overlay.devices.androidcom.miui.translation.kingsoftcom.miui.translation.kingsoftcom.miui.translation.xmcloudcom.miui.translation.xmcloudcom.miui.translation.youdaocom.miui.translation.youdaocom.miui.translationservicecom.miui.translationservicecom.qti.diagservicescom.qti.diagservicescom.qti.dpmserviceappcom.qti.dpmserviceappcom.qti.qualcomm.datastatusnotificationcom.qti.qualcomm.datastatusnotificationcom.qti.service.colorservicecom.qti.service.colorservicecom.qti.slaservicecom.qti.slaservicecom.qualcomm.atfwdcom.qualcomm.atfwdcom.qualcomm.embmscom.qualcomm.embmscom.qualcomm.qcrilmsgtunnelcom.qualcomm.qcrilmsgtunnelcom.qualcomm.qti.autoregistrationcom.qualcomm.qti.autoregistrationcom.qualcomm.qti.devicestatisticsservicecom.qualcomm.qti.devicestatisticsservicecom.qualcomm.qti.dynamicddsservicecom.qualcomm.qti.dynamicddsservicecom.qualcomm.qti.lpacom.qualcomm.qti.lpacom.qualcomm.qti.qms.service.connectionsecuritycom.qualcomm.qti.qms.service.connectionsecuritycom.qualcomm.qti.qtisystemservicecom.qualcomm.qti.qtisystemservicecom.qualcomm.qti.remoteSimlockAuthcom.qualcomm.qti.remoteSimlockAuthcom.qualcomm.qti.server.wigig.tethering.rrocom.qualcomm.qti.server.wigig.tethering.rrocom.qualcomm.qti.telephonyservicecom.qualcomm.qti.telephonyservicecom.qualcomm.qti.uimGbaAppcom.qualcomm.qti.uimGbaAppcom.qualcomm.qti.uimcom.qualcomm.qti.uimcom.qualcomm.qti.workloadclassifiercom.qualcomm.qti.workloadclassifiercom.qualcomm.timeservicecom.qualcomm.timeservicecom.qualcomm.uimremoteclientcom.qualcomm.uimremoteclientcom.qualcomm.uimremoteservercom.qualcomm.uimremoteservercom.xiaomi.bluetooth.overlaycom.xiaomi.bluetooth.overlaycom.xiaomi.micloudsdk.SdkApplicationcom.xiaomi.micloud.sdkkaraokecom.miui.audiomonitormiui.external.Applicationcom.android.thememanager.module
【网络篇】第十五篇——HTTP协议(二)(上)
前面一章初步认识了URL,HTTP请求和相应协议格式,有所忘记的可以看一下前面的博客(3条消息) 【网络篇】第十四篇——HTTP协议(一)(附带电视剧李浔同款爱心+端口号被恶意占用如何清除)_接受平凡 努力出众的博客-CSDN博客HTTP的方法HTTP常见的方法有:方法说明支持的HTTP协议版本GET获取资源1.0,1.1POST传输实体主体1.0,1.1PUT传输文件1.0,1.1HEAD获得报文首部1.0,1.1DELETE删除文件1.0,1.1OPTIONS访问支持的方法1.1TRACE追踪路径1.1CONNECT要求用隧道协议连接代理1.1LINK建立和资源之间的联系1.0UNLINK断开连接关系1.0其中最常用的就是GET方法和POST方法。GET方法和POST方法 GET方法的含义是请求从服务器获取资源, 这个资源可以是文本,页面,图片视频等等。例如,你打开我在CSDN写的文章,浏览器就会发送GET请求给服务器,服务器就会返回文章的所有文字及资源。 POST方法则是相反的,一般用于将数据上传给服务器,它向URI指定的资源提交数据,数据就放在报文的body中.例如,你在我的CSDN文章下面,评论了然后提交,浏览器就会执行一次POST请求,把你的评论放进报文body里,然后拼接好POST请求头,通过TCP协议发送给服务器。GET方法和POST方法都可以带参:GET方法是通过url传参的。POST方法是通过正文传参的。从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,而POST方法通过正文传参就可以携带比它更多的数据。使用POST方法的传参会更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,因为POST方法和GET方法实际上都不安全,要做到安全只能通过加密来完成。GET和POST方法都是安全和幂等的嘛?先说明下安全和幂等的概念:在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。那么很明显 GET 方法就是安全且幂等的 ,因为它是「只读」操作,无论操作多少次,服务器上的数据 都是安全的,且每次的结果都是相同的。 POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以 是不安全 的,且多次提交数据 就会创建多个资源,所以 不是幂等 的。Postman演示GET和POST区别 如果访问我们的服务器使用的是GET方法,此时应该通过url进行传参,可以在Params下进行参数设置,因为Postman当中的Params就相当于url当中的参数,你在设置参数时可以看到对应的url也在随之变化。此时在我们的服务器收到的HTTP请求当中,可以看到请求行中的url就携带上了我们刚才在Postman当中设置的参数。而如果我们使用的是POST方法,此时就应该通过正文进行传参,可以在Body下进行参数设置,在设置时可以选中Postman当中的raw方式传参,表示原始传参,也就是你输入的参数是什么样的实际传递的参数就是什么样的. 此时服务器收到的HTTP请求的请求正文就不再是空字符串了,而是我们通过正文传递的参数。 因为此时响应正文不为空字符串,因此响应报头当中出现了Content-Length属性,表示响应正文的长度。TCP套接字演示GET和POST的区别 要演示GET方法和POST方法传参的区别,就需要让浏览器提交参数,此时我们可以在index.html当中再加入两个表单,用作用户名和密码的输入,然后再新增一个提交按钮,此时就可以让浏览器提交参数了。我们可以通过修改表单当中的method属性指定参数提交的方法,还有一个属性叫做action,表示想把这个表单提交给服务器上的哪个资源。此时当我们用浏览器访问我们的服务器时,就会显示这两个表单。当前我们是用GET方法提交参数的,当我们填充完用户名和密码进行提交时,我们的用户名和密码就会自动被同步到url当中。同时在服务器这边也通过url收到了刚才我们在浏览器提交的参数。如果我们将提交表单的方法改为POST方法,此时当我们填充完用户名和密码进行提交时,对应提交的参数就不会在url当中体现出来,而会通过正文将这两个参数传递给了服务器。 此时用户名和密码就通过正文的形式传递给服务器了。 说明一下:使用GET方法时,我们提交的参数会回显到url当中,因此GET方法一般是处理数据不敏感的。如果你要传递的数据比较私密的话一定要用POST方法,不是因为POST方法安全,实际上GET和POST方法传参都是明文传送,因此都不安全,但是相比于GET而言POST方法更加私密,因为POST是通过正文传参的,不会将参数显示到浏览器上的url框上。HTTP的状态码1xx类:属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。2xx类:表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。 「200 OK]是最常见的成功状态码,表示一切正常。如果是非HEAD请求,服务器返回的响应头都会有body数据。 「204 No Content]也是最常见的成功状态码,也200 OK基本相同,但是响应头没有body数据。 [206 Partial Contentt]:是应用于HTTP分块下载或断点续传,表示响应返回的body数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。3xx类:码表示客户端请求的资源发送了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。 「 301 Moved Permanently 」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。 「 302 Found 」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。301 和 302 都会在响应头里使用字段 Location ,指明后续要跳转的 URL ,浏览器会自动重定向新的 URL。 「 304 Not Modified 」不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,用于缓存控制。 4xx类:表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。「 400 Bad Request 」表示客户端请求的报文有错误,但只是个笼统的错误。 「 403 Forbidden 」表示服务器禁止访问资源,并不是客户端的请求出错。 「 404 Not Found 」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。5xx类:表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。 「 500 Internal Server Error 」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。「 501 Not Implemented 」表示客户端请求的功能还不支持,类似 “ 即将开业,敬请期待 ” 的意思 「 502 Bad Gateway 」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。 「 503 Service Unavailable 」表示服务器当前很忙,暂时无法响应服务器,类似 “ 网络服务正忙,请稍后重试” 的意思。 临时重定向演示进行临时重定向时需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站。我们这里要演示临时重定向,可以将HTTP响应当中的状态码改为307,然后跟上对应的状态码描述,此外,还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我们这里将其设置为CSDN的首页。#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
int main()
{
//创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0){
cerr << "socket error!" << endl;
return 1;
}
//绑定
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8081);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "bind error!" << endl;
return 2;
}
//监听
if (listen(listen_sock, 5) < 0){
cerr << "listen error!" << endl;
return 3;
}
//启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
for (;;){
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0){
cerr << "accept error!" << endl;
continue;
}
if (fork() == 0){ //爸爸进程
close(listen_sock);
if (fork() > 0){ //爸爸进程
exit(0);
}
//孙子进程
char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
cout << "--------------------------http request begin--------------------------" << endl;
cout << buffer << endl;
cout << "---------------------------http request end---------------------------" << endl;
//构建HTTP响应
string status_line = "http/1.1 307 Temporary Redirect\n"; //状态行
string response_header = "Location: https://www.csdn.net/\n"; //响应报头
string blank = "\n"; //空行
string response = status_line + response_header + blank; //响应报文
//响应HTTP请求
send(sock, response.c_str(), response.size(), 0);
close(sock);
exit(0);
}
//爷爷进程
close(sock);
waitpid(-1, nullptr, 0); //等待爸爸进程
}
return 0;
}如果我们用浏览器访问我们的服务器,当浏览器收到这个HTTP响应后,还会对这个HTTP响应进行分析,当浏览器识别到状态码是307后就会提取出Location后面的网址,然后继续自动对该网站继续发起请求,此时就完成了页面跳转这样的功能,这样就完成了重定向功能。此时当浏览器访问我们的服务器时,就会立马跳转到CSDN的首页。HTTP常见的HeaderHTTP常见的Header如下:Content-Type:数据类型(text/html等)Content-Length:正文的长度。Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。User-Agent:声明用户的操作系统和浏览器的版本信息。Referer:当前页面是哪个页面跳转过来的。Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。Host:客户端发送请求时,用来指定服务器的域名。 有了Host字段,就可以将请求发往[同一台]服务器上的不同网站。Content-Length服务器在返回数据时,会有Content-Length字段,表明本次回应的数据长度 如上面则是告诉浏览器,本次服务器回应的数据长度是1000个字节,后面的字节就属于下一个回应了。ConnectionConnection 字段最常用于客户端要求服务器使用 TCP 持久连接,以便其他请求复用。HTTP/1.1 版本的默认连接都是持久连接,但为了兼容老版本的 HTTP ,需要指定 Connection 首部字段的值为 Keep-Alive 。 一个可以复用的 TCP 连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段。 User-AgentUser-Agent代表的是客户端对应的操作系统和浏览器的版本信息。比如当我们用电脑下载某些软件时,它会自动向我们展示与我们操作系统相匹配的版本,这实际就是因为我们在向目标网站发起请求的时候,User-Agent字段当中包含了我们的主机信息,此时该网站就会向你推送相匹配的软件版本。Content-Type Content-Type 字段用于服务器回应时,告诉客户端,本次数据是什么格式。上面的类型表明,发送的是网页,而且编码是UTF-8.客户端请求的时候,可以使用 Accept 字段声明自己可以接受哪些数据格式。 上面代码中,客户端声明自己可以接受任何格式的数据。Content-EncodingContent-Encoding 字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式 。上面表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压。客户端在请求时,用 Accept-Encoding 字段说明自己可以接受哪些压缩方法。
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~Github地址:https://github.com/Tyson0314/Java-learning网络分层结构计算机网络体系大致分为三种,OSI七层模型、TCP/IP四层模型和五层模型。一般面试的时候考察比较多的是五层模型。五层模型:应用层、传输层、网络层、数据链路层、物理层。应用层:为应用程序提供交互服务。在互联网中的应用层协议很多,如域名系统DNS、HTTP协议、SMTP协议等。传输层:负责向两台主机进程之间的通信提供数据传输服务。传输层的协议主要有传输控制协议TCP和用户数据协议UDP。网络层:选择合适的路由和交换结点,确保数据及时传送。主要包括IP协议。数据链路层:在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。物理层:实现相邻节点间比特流的透明传输,尽可能屏蔽传输介质和物理设备的差异。ISO七层模型是国际标准化组织(International Organization for Standardization)制定的一个用于计算机或通信系统间互联的标准体系。应用层:网络服务与最终用户的一个接口,常见的协议有:HTTP FTP SMTP SNMP DNS.表示层:数据的表示、安全、压缩。,确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。会话层:建立、管理、终止会话,对应主机进程,指本地主机与远程主机正在进行的会话.传输层:定义传输数据的协议端口号,以及流控和差错校验,协议有TCP UDP.网络层:进行逻辑地址寻址,实现不同网络之间的路径选择,协议有ICMP IGMP IP等.数据链路层:在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路。物理层:建立、维护、断开物理连接。TCP/IP 四层模型应用层:对应于OSI参考模型的(应用层、表示层、会话层)。传输层: 对应OSI的传输层,为应用层实体提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性。网际层:对应于OSI参考模型的网络层,主要解决主机到主机的通信问题。网络接口层:与OSI参考模型的数据链路层、物理层对应。三次握手假设发送端为客户端,接收端为服务端。开始时客户端和服务端的状态都是CLOSED。第一次握手:客户端向服务端发起建立连接请求,客户端会随机生成一个起始序列号x,客户端向服务端发送的字段中包含标志位SYN=1,序列号seq=x。第一次握手前客户端的状态为CLOSE,第一次握手后客户端的状态为SYN-SENT。此时服务端的状态为LISTEN。第二次握手:服务端在收到客户端发来的报文后,会随机生成一个服务端的起始序列号y,然后给客户端回复一段报文,其中包括标志位SYN=1,ACK=1,序列号seq=y,确认号ack=x+1。第二次握手前服务端的状态为LISTEN,第二次握手后服务端的状态为SYN-RCVD,此时客户端的状态为SYN-SENT。(其中SYN=1表示要和客户端建立一个连接,ACK=1表示确认序号有效)第三次握手:客户端收到服务端发来的报文后,会再向服务端发送报文,其中包含标志位ACK=1,序列号seq=x+1,确认号ack=y+1。第三次握手前客户端的状态为SYN-SENT,第三次握手后客户端和服务端的状态都为ESTABLISHED。此时连接建立完成。两次握手可以吗?之所以需要第三次握手,主要为了防止已失效的连接请求报文段突然又传输到了服务端,导致产生问题。比如客户端A发出连接请求,可能因为网络阻塞原因,A没有收到确认报文,于是A再重传一次连接请求。然后连接成功,等待数据传输完毕后,就释放了连接。然后A发出的第一个连接请求等到连接释放以后的某个时间才到达服务端B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段。如果不采用三次握手,只要B发出确认,就建立新的连接了,此时A不会响应B的确认且不发送数据,则B一直等待A发送数据,浪费资源。四次挥手A的应用进程先向其TCP发出连接释放报文段(FIN=1,seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。B收到连接释放报文段后即发出确认报文段(ACK=1,ack=u+1,seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。B发送完数据,就会发出连接释放报文段(FIN=1,ACK=1,seq=w,ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认。A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL(最大报文段生存时间)后,A才进入CLOSED状态。B收到A发出的确认报文段后关闭连接,若没收到A发出的确认报文段,B就会重传连接释放报文段。第四次挥手为什么要等待2MSL?保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段有可能丢失,B收不到这个确认报文,就会超时重传连接释放报文段,然后A可以在2MSL时间内收到这个重传的连接释放报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的连接释放报文段,所以不会再发送一次确认报文段,B就无法正常进入到CLOSED状态。防止已失效的连接请求报文段出现在本连接中。A在发送完最后一个ACK报文段后,再经过2MSL,就可以使这个连接所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段。为什么是四次挥手?因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。但是在关闭连接时,当Server端收到Client端发出的连接释放报文时,很可能并不会立即关闭SOCKET,所以Server端先回复一个ACK报文,告诉Client端我收到你的连接释放报文了。只有等到Server端所有的报文都发送完了,这时Server端才能发送连接释放报文,之后两边才会真正的断开连接。故需要四次挥手。TCP有哪些特点?TCP是面向连接的运输层协议。点对点,每一条TCP连接只能有两个端点。TCP提供可靠交付的服务。TCP提供全双工通信。面向字节流。说说TCP报文首部有哪些字段,其作用又分别是什么?16位端口号:源端口号,主机该报文段是来自哪里;目标端口号,要传给哪个上层协议或应用程序32位序号:一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。32位确认号:用作对另一方发送的tcp报文段的响应。其值是收到的TCP报文段的序号值加1。4位头部长度:表示tcp头部有多少个32bit字(4字节)。因为4位最大能标识15,所以TCP头部最长是60字节。6位标志位:URG(紧急指针是否有效),ACk(表示确认号是否有效),PSH(缓冲区尚未填满),RST(表示要求对方重新建立连接),SYN(建立连接消息标志接),FIN(表示告知对方本端要关闭连接了)16位窗口大小:是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。16位校验和:由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。16位紧急指针:一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。TCP和UDP的区别?TCP面向连接;UDP是无连接的,即发送数据之前不需要建立连接。TCP提供可靠的服务;UDP不保证可靠交付。TCP面向字节流,把数据看成一连串无结构的字节流;UDP是面向报文的。TCP有拥塞控制;UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如实时视频会议等)。每一条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的通信方式。TCP首部开销20字节;UDP的首部开销小,只有8个字节。TCP 和 UDP 分别对应的常见应用层协议有哪些?基于TCP的应用层协议有:HTTP、FTP、SMTP、TELNET、SSHHTTP:HyperText Transfer Protocol(超文本传输协议),默认端口80FTP: File Transfer Protocol (文件传输协议), 默认端口(20用于传输数据,21用于传输控制信息)SMTP: Simple Mail Transfer Protocol (简单邮件传输协议) ,默认端口25TELNET: Teletype over the Network (网络电传), 默认端口23SSH:Secure Shell(安全外壳协议),默认端口 22基于UDP的应用层协议:DNS、TFTP、SNMPDNS : Domain Name Service (域名服务),默认端口 53TFTP: Trivial File Transfer Protocol (简单文件传输协议),默认端口69SNMP:Simple Network Management Protocol(简单网络管理协议),通过UDP端口161接收,只有Trap信息采用UDP端口162。TCP的粘包和拆包TCP是面向流,没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。为什么会产生粘包和拆包呢?要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包;待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。解决方案:发送端将每个数据包封装为固定长度在数据尾部增加特殊字符进行分割将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。说说TCP是如何确保可靠性的呢?首先,TCP的连接是基于三次握手,而断开则是基于四次挥手。确保连接和断开的可靠性。其次,TCP的可靠性,还体现在有状态;TCP会记录哪些数据发送了,哪些数据被接收了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。再次,TCP的可靠性,还体现在可控制。它有数据包校验、ACK应答、超时重传(发送方)、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。说下TCP的滑动窗口机制TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 TCP会话的双方都各自维护一个发送窗口和一个接收窗口。接收窗口大小取决于应用、系统、硬件的限制。发送窗口则取决于对端通告的接收窗口。接收方发送的确认报文中的window字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将接收方的确认报文window字段设置为 0,则发送方不能发送数据。TCP头包含window字段,16bit位,它代表的是窗口的字节容量,最大为65535。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。接收窗口的大小是约等于发送窗口的大小。详细讲一下拥塞控制?防止过多的数据注入到网络中。 几种拥塞控制方法:慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。慢开始把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。每经过一个传输轮次,拥塞窗口 cwnd 就加倍。 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。当 cwnd < ssthresh 时,使用慢开始算法。当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。拥塞避免让拥塞窗口cwnd缓慢地增大,每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长。无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。快重传有时个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口cwnd又设置为1,因而降低了传输效率。快重传算法可以避免这个问题。快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认,使发送方及早知道有报文段没有到达对方。发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待重传计时器到期。由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。快恢复当发送方连续收到三个重复确认,就会把慢开始门限ssthresh减半,接着把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法,使拥塞窗口缓慢地线性增大。在采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络出现超时时才使用。 采用这样的拥塞控制方法使得TCP的性能有明显的改进。HTTP协议的特点?HTTP允许传输任意类型的数据。传输的类型由Content-Type加以标记。无状态。对于客户端每次发送的请求,服务器都认为是一个新的请求,上一次会话和下一次会话之间没有联系。支持客户端/服务器模式。HTTP报文格式HTTP请求由请求行、请求头部、空行和请求体四个部分组成。请求行:包括请求方法,访问的资源URL,使用的HTTP版本。GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。请求头:格式为“属性名:属性值”,服务端根据请求头获取客户端的信息,主要有cookie、host、connection、accept-language、accept-encoding、user-agent。请求体:用户的请求数据如用户名,密码等。请求报文示例:POST /xxx HTTP/1.1 请求行
Accept:image/gif.image/jpeg, 请求头部
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate
username=dabin 请求体HTTP响应也由四个部分组成,分别是:状态行、响应头、空行和响应体。状态行:协议版本,状态码及状态描述。响应头:响应头字段主要有connection、content-type、content-encoding、content-length、set-cookie、Last-Modified,、Cache-Control、Expires。响应体:服务器返回给客户端的内容。响应报文示例:HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112
<html>
<body>响应体</body>
</html>HTTP状态码有哪些?HTTP 状态码是服务器端返回给客户端的响应状态码,根据状态码我们就能知道服务器端想要给客户端表达的具体含义,比如 200 就表示请求访问成功,500 就表示服务器端程序出错等。HTTP 状态码可分为 5 大类:1XX:消息状态码。2XX:成功状态码。3XX:重定向状态码。4XX:客户端错误状态码。5XX:服务端错误状态码。这 5 大类中又包含了很多具体的状态码。1XX为消息状态码,其中:100:Continue 继续。客户端应继续其请求。101:Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到 HTTP 的新版本协议。2XX为成功状态码,其中:200:OK 请求成功。一般用于 GET 与 POST 请求。201:Created 已创建。成功请求并创建了新的资源。202:Accepted 已接受。已经接受请求,但未处理完成。203:Non-Authoritative Information 非授权信息。请求成功。但返回的 meta 信息不在原始的服务器,而是一个副本。204:No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档。205:Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域。206:Partial Content 部分内容。服务器成功处理了部分 GET 请求。3XX为重定向状态码,其中:300:Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择。301:Moved Permanently 永久移动。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替。302:Found 临时移动,与 301 类似。但资源只是临时被移动。客户端应继续使用原有URI。303:See Other 查看其它地址。与 301 类似。使用 GET 和 POST 请求查看。304:Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源。305:Use Proxy 使用代理。所请求的资源必须通过代理访问。306:Unused 已经被废弃的 HTTP 状态码。307:Temporary Redirect 临时重定向。与 302 类似。使用 GET 请求重定向。4XX为客户端错误状态码,其中:400:Bad Request 客户端请求的语法错误,服务器无法理解。401:Unauthorized 请求要求用户的身份认证。402:Payment Required 保留,将来使用。403:Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求。404:Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面。405:Method Not Allowed 客户端请求中的方法被禁止。406:Not Acceptable 服务器无法根据客户端请求的内容特性完成请求。407:Proxy Authentication Required 请求要求代理的身份认证,与 401 类似,但请求者应当使用代理进行授权。408:Request Time-out 服务器等待客户端发送的请求时间过长,超时。409:Conflict 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突。410:Gone 客户端请求的资源已经不存在。410 不同于 404,如果资源以前有现在被永久删除了可使用 410 代码,网站设计人员可通过 301 代码指定资源的新位置。411:Length Required 服务器无法处理客户端发送的不带 Content-Length 的请求信息。412:Precondition Failed 客户端请求信息的先决条件错误。413:Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个 Retry-After 的响应信息。414:Request-URI Too Large 请求的 URI 过长(URI通常为网址),服务器无法处理。415:Unsupported Media Type 服务器无法处理请求附带的媒体格式。416:Requested range not satisfiable 客户端请求的范围无效。417:Expectation Failed 服务器无法满足 Expect 的请求头信息。5XX为服务端错误状态码,其中:500:Internal Server Error 服务器内部错误,无法完成请求。501:Not Implemented 服务器不支持请求的功能,无法完成请求。502:Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应。503:Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中。504:Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求。505:HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理。总结一下:HTTP 状态码分为 5 大类:1XX:表示消息状态码;2XX:表示成功状态码;3XX:表示重定向状态码;4XX:表示客户端错误状态码;5XX:表示服务端错误状态码。其中常见的具体状态码有:200:请求成功;301:永久重定向;302:临时重定向;404:无法找到此页面;405:请求的方法类型不支持;500:服务器内部出错。HTTP 协议包括哪些请求?HTTP协议中共定义了八种方法来表示对Request-URI指定的资源的不同操作方式,具体如下:GET:向特定的资源发出请求。POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。PUT:向指定资源位置上传其最新内容。DELETE:请求服务器删除Request-URI所标识的资源。TRACE:回显服务器收到的请求,主要用于测试或诊断。CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。HTTP状态码301和302的区别?301:(永久性转移)请求的网页已被永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。302:(暂时性转移)服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。此代码与响应GET和HEAD请求的301代码类似,会自动将请求者转到不同的位置。举个形象的例子:当一个网站或者网页24—48小时内临时移动到一个新的位置,这时候就要进行302跳转,打个比方说,我有一套房子,但是最近走亲戚去亲戚家住了,过两天我还回来的。而使用301跳转的场景就是之前的网站因为某种原因需要移除掉,然后要到新的地址访问,是永久性的,就比如你的那套房子其实是租的,现在租期到了,你又在另一个地方找到了房子,之前租的房子不住了。URI和URL的区别URI,全称是Uniform Resource Identifier),中文翻译是统一资源标志符,主要作用是唯一标识一个资源。URL,全称是Uniform Resource Location),中文翻译是统一资源定位符,主要作用是提供资源的路径。打个经典比喻吧,URI像是身份证,可以唯一标识一个人,而URL更像一个住址,可以通过URL找到这个人。POST和GET的区别?GET 和 POST 最本质的区别是规范上的区别,在规范中,定义 GET 请求是用来获取资源的,也就是进行查询操作的,而 POST 请求是用来传输实体对象的,因此会使用 POST 来进行添加、修改和删除等操作。GET请求参数通过URL传递,POST的参数放在请求体中。GET 请求可以直接进行回退和刷新,不会对用户和程序产生任何影响;而 POST 请求如果直接回滚和刷新将会把数据再次提交。GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把请求头和请求体一并发送出去;而对于POST,浏览器先发送请求头,服务器响应100 continue,浏览器再发送请求体。GET 请求一般会被缓存,比如常见的 CSS、JS、HTML 请求等都会被缓存;而 POST 请求默认是不进行缓存的。GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。如何理解HTTP协议是无状态的当浏览器第一次发送请求给服务器时,服务器响应了;如果同个浏览器发起第二次请求给服务器时,它还是会响应,但是呢,服务器不知道你就是刚才的那个浏览器。简言之,服务器不会去记住你是谁,所以是无状态协议。HTTP长连接和短连接?HTTP短连接:浏览器和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。HTTP1.0默认使用的是短连接。HTTP长连接:指的是复用TCP连接。多个HTTP请求可以复用同一个TCP连接,这就节省了TCP连接建立和断开的消耗。HTTP/1.1起,默认使用长连接。要使用长连接,客户端和服务器的HTTP首部的Connection都要设置为keep-alive,才能支持长连接。HTTP 如何实现长连接?HTTP分为长连接和短连接,本质上说的是TCP的长短连接。TCP连接是一个双向的通道,它是可以保持一段时间不关闭的,因此TCP连接才具有真正的长连接和短连接这一说法哈。TCP长连接可以复用一个TCP连接,来发起多次的HTTP请求,这样就可以减少资源消耗,比如一次请求HTML,如果是短连接的话,可能还需要请求后续的JS/CSS。如何设置长连接?通过在头部(请求和响应头)设置Connection字段指定为keep-alive,HTTP/1.0协议支持,但是是默认关闭的,从HTTP/1.1以后,连接默认都是长连接。HTTP长连接在什么时候会超时?HTTP一般会有httpd守护进程,里面可以设置keep-alive timeout,当tcp连接闲置超过这个时间就会关闭,也可以在HTTP的header里面设置超时时间。TCP 的keep-alive包含三个参数,支持在系统内核的net.ipv4里面设置;当 TCP 连接之后,闲置了tcp_keepalive_time,则会发生侦测包,如果没有收到对方的ACK,那么会每隔 tcp_keepalive_intvl再发一次,直到发送了tcp_keepalive_probes,就会丢弃该连接。HTTP1.1和 HTTP2.0的区别?HTTP2.0相比HTTP1.1支持的特性:新的二进制格式:HTTP1.1 基于文本格式传输数据;HTTP2.0采用二进制格式传输数据,解析更高效。多路复用:在一个连接里,允许同时发送多个请求或响应,并且这些请求或响应能够并行的传输而不被阻塞,避免 HTTP1.1 出现的”队头堵塞”问题。头部压缩,HTTP1.1的header带有大量信息,而且每次都要重复发送;HTTP2.0 把header从数据中分离,并封装成头帧和数据帧,使用特定算法压缩头帧,有效减少头信息大小。并且HTTP2.0在客户端和服务器端记录了之前发送的键值对,对于相同的数据,不会重复发送。比如请求a发送了所有的头信息字段,请求b则只需要发送差异数据,这样可以减少冗余数据,降低开销。服务端推送:HTTP2.0允许服务器向客户端推送资源,无需客户端发送请求到服务器获取。HTTPS与HTTP的区别?HTTP是超文本传输协议,信息是明文传输;HTTPS则是具有安全性的ssl加密传输协议。HTTP和HTTPS用的端口不一样,HTTP端口是80,HTTPS是443。HTTPS协议需要到CA机构申请证书,一般需要一定的费用。HTTP运行在TCP协议之上;HTTPS运行在SSL协议之上,SSL运行在TCP协议之上。什么是数字证书?服务端可以向证书颁发机构CA申请证书,以避免中间人攻击(防止证书被篡改)。证书包含三部分内容:证书内容、证书签名算法和签名,签名是为了验证身份。服务端把证书传输给浏览器,浏览器从证书里取公钥。证书可以证明该公钥对应本网站。数字签名的制作过程:CA使用证书签名算法对证书内容进行hash运算。对hash后的值用CA的私钥加密,得到数字签名。浏览器验证过程:获取证书,得到证书内容、证书签名算法和数字签名。用CA机构的公钥对数字签名解密(由于是浏览器信任的机构,所以浏览器会保存它的公钥)。用证书里的签名算法对证书内容进行hash运算。比较解密后的数字签名和对证书内容做hash运算后得到的哈希值,相等则表明证书可信。HTTPS原理首先是TCP三次握手,然后客户端发起一个HTTPS连接建立请求,客户端先发一个Client Hello的包,然后服务端响应Server Hello,接着再给客户端发送它的证书,然后双方经过密钥交换,最后使用交换的密钥加解密数据。协商加密算法 。在Client Hello里面客户端会告知服务端自己当前的一些信息,包括客户端要使用的TLS版本,支持的加密算法,要访问的域名,给服务端生成的一个随机数(Nonce)等。需要提前告知服务器想要访问的域名以便服务器发送相应的域名的证书过来。服务端响应Server Hello,告诉客户端服务端选中的加密算法。接着服务端给客户端发来了2个证书。第二个证书是第一个证书的签发机构(CA)的证书。客户端使用证书的认证机构CA公开发布的RSA公钥对该证书进行验证,下图表明证书认证成功。验证通过之后,浏览器和服务器通过密钥交换算法产生共享的对称密钥。开始传输数据,使用同一个对称密钥来加解密。DNS 的解析过程?浏览器搜索自己的DNS缓存若没有,则搜索操作系统中的DNS缓存和hosts文件若没有,则操作系统将域名发送至本地域名服务器,本地域名服务器查询自己的DNS缓存,查找成功则返回结果,否则依次向根域名服务器、顶级域名服务器、权限域名服务器发起查询请求,最终返回IP地址给本地域名服务器本地域名服务器将得到的IP地址返回给操作系统,同时自己也将IP地址缓存起来操作系统将 IP 地址返回给浏览器,同时自己也将IP地址缓存起来浏览器得到域名对应的IP地址浏览器中输入URL返回页面过程?解析域名,找到主机 IP。浏览器利用 IP 直接与网站主机通信,三次握手,建立 TCP 连接。浏览器会以一个随机端口向服务端的 web 程序 80 端口发起 TCP 的连接。建立 TCP 连接后,浏览器向主机发起一个HTTP请求。参数从客户端传递到服务器端。服务器端得到客户端参数之后,进行相应的业务处理,再将结果封装成 HTTP 包,返回给客户端。服务器端和客户端的交互完成,断开 TCP 连接(4 次挥手)。浏览器解析响应内容,进行渲染,呈现给用户。DNS 域名解析的过程在网络中定位是依靠 IP 进行身份定位的,所以 URL 访问的第一步便是先要得到服务器端的 IP 地址。而得到服务器的 IP 地址需要使用 DNS(Domain Name System,域名系统)域名解析,DNS 域名解析就是通过 URL 找到与之相对应的 IP 地址。DNS 域名解析的大致流程如下:先检查浏览器中的 DNS 缓存,如果浏览器中有对应的记录会直接使用,并完成解析;如果浏览器没有缓存,那就去查询操作系统的缓存,如果查询到记录就可以直接返回 IP 地址,完成解析;如果操作系统没有 DNS 缓存,就会去查看本地 host 文件,Windows 操作系统下,host 文件一般位于 "C:\Windows\System32\drivers\etc\hosts",如果 host 文件有记录则直接使用;如果本地 host 文件没有相应的记录,会请求本地 DNS 服务器,本地 DNS 服务器一般是由本地网络服务商如移动、联通等提供。通常情况下可通过 DHCP 自动分配,当然也可以自己手动配置。目前用的比较多的是谷歌提供的公用 DNS 是 8.8.8.8 和国内的公用 DNS 是 114.114.114.114。如果本地 DNS 服务器没有相应的记录,就会去根域名服务器查询了。为了能更高效完成全球所有域名的解析请求,根域名服务器本身并不会直接去解析域名,而是会把不同的解析请求分配给下面的其他服务器去完成。图片来源于网络什么是cookie和session?由于HTTP协议是无状态的协议,需要用某种机制来识具体的用户身份,用来跟踪用户的整个会话。常用的会话跟踪技术是cookie与session。cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。说得更具体一些:当用户使用浏览器访问一个支持cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体中的,而是存放于HTTP响应头;当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置。 自此,客户端再向服务器发送请求的时候,都会把相应的cookie存放在HTTP请求头再次发回至服务器。服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。网站的登录界面中“请记住我”这样的选项,就是通过cookie实现的。cookie工作流程:servlet创建cookie,保存少量数据,发送给浏览器。浏览器获得服务器发送的cookie数据,将自动的保存到浏览器端。下次访问时,浏览器将自动携带cookie数据发送给服务器。session原理:首先浏览器请求服务器访问web站点时,服务器首先会检查这个客户端请求是否已经包含了一个session标识、称为SESSIONID,如果已经包含了一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用,如果客户端请求不包含session id,则服务器为此客户端创建一个session,并且生成一个与此session相关联的独一无二的sessionid存放到cookie中,这个sessionid将在本次响应中返回到客户端保存,这样在交互的过程中,浏览器端每次请求时,都会带着这个sessionid,服务器根据这个sessionid就可以找得到对应的session。以此来达到共享数据的目的。 这里需要注意的是,session不会随着浏览器的关闭而死亡,而是等待超时时间。cookie和session的区别?作用范围不同,Cookie 保存在客户端,Session 保存在服务器端。有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。隐私策略不同,Cookie 存储在客户端,容易被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。存储大小不同, 单个 Cookie 保存的数据不能超过 4K;对于 Session 来说存储没有上限,但出于对服务器的性能考虑,Session 内不要存放过多的数据,并且需要设置 Session 删除机制。什么是对称加密和非对称加密?对称加密:通信双方使用相同的密钥进行加密。特点是加密速度快,但是缺点是密钥泄露会导致密文数据被破解。常见的对称加密有AES和DES算法。非对称加密:它需要生成两个密钥,公钥和私钥。公钥是公开的,任何人都可以获得,而私钥是私人保管的。公钥负责加密,私钥负责解密;或者私钥负责加密,公钥负责解密。这种加密算法安全性更高,但是计算量相比对称加密大很多,加密和解密都很慢。常见的非对称算法有RSA和DSA。说说 WebSocket与socket的区别Socket是一套标准,它完成了对TCP/IP的高度封装,屏蔽网络细节,以方便开发者更好地进行网络编程。Socket其实就是等于IP地址 + 端口 + 协议。WebSocket是一个持久化的协议,它是伴随H5而出的协议,用来解决http不支持持久化连接的问题。Socket一个是网编编程的标准接口,而WebSocket则是应用层通信协议。ARP协议的工作过程?ARP解决了同一个局域网上的主机和路由器IP和MAC地址的解析。每台主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址的对应关系。当源主机需要将一个数据包要发送到目的主机时,会首先检查自己 ARP列表中是否存在该 IP地址对应的MAC地址,如果有,就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址、以及目的主机的IP地址。网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址。源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。ICMP协议的功能ICMP,Internet Control Message Protocol ,Internet控制消息协议。ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。它属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。比如我们日常使用得比较多的ping,就是基于ICMP的。什么是DoS、DDoS、DRDoS攻击?DOS: (Denial of Service),翻译过来就是拒绝服务,一切能引起DOS行为的攻击都被称为DOS攻击。最常见的DoS攻击就有计算机网络宽带攻击、连通性攻击。DDoS: (Distributed Denial of Service),翻译过来是分布式拒绝服务。是指处于不同位置的多个攻击者同时向一个或几个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。常见的DDos有SYN Flood、Ping of Death、ACK Flood、UDP Flood等。DRDoS: (Distributed Reflection Denial of Service),中文是分布式反射拒绝服务,该方式靠的是发送大量带有被害者IP地址的数据包给攻击主机,然后攻击主机对IP地址源做出大量回应,从而形成拒绝服务攻击。什么是CSRF攻击,如何避免CSRF,跨站请求伪造(英文全称是Cross-site request forgery),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。怎么解决CSRF攻击呢?检查Referer字段。添加校验token。什么是XSS攻击?XSS,跨站脚本攻击(Cross-Site Scripting)。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。XSS攻击一般分三种类型:存储型 、反射型 、DOM型XSS如何解决XSS攻击问题?对输入进行过滤,过滤标签等,只允许合法值。HTML转义对于链接跳转,如<a href="xxx" 等,要校验内容,禁止以script开头的非法链接。限制输入长度防盗链盗链是指服务提供商自己不提供服务的内容,通过技术手段(可以理解成爬虫)去获取其他网站的资源展示在自己的网站上。常见的盗链有以下几种:图片盗链、音频盗链、视频盗链等。网站盗链会大量消耗被盗链网站的带宽,而真正的点击率也许会很小,严重损害了被盗链网站的利益。被盗网站就自然会防盗链,可以通过经常更换图片名,也可以通过检测referer。因为正常用户访问一张图片一定是从自己的网站点击链接进去的,如果一个请求的referer是其他网站,就说明这是一个爬虫。什么是 Referer?这里的 Referer 指的是 HTTP 头部的一个字段,也称为 HTTP 来源地址(HTTP Referer),用来表示从哪儿链接到目前的网页,采用的格式是 URL。换句话说,借着 HTTP Referer 头部网页可以检查访客从哪里而来,这也常被用来对付伪造的跨网站请求。盗链网站会针对性进行反盗链,可以通过在请求的headers中设置referer来绕过防盗链,我们现在使用爬虫抓取别人的网站也是这样。什么是空 Referer,什么时候会出现空 Referer?首先,我们对空 Referer 的定义为,Referer 头部的内容为空,或者,一个 HTTP 请求中根本不包含 Referer 头部。那么什么时候 HTTP 请求会不包含 Referer 字段呢?根据 Referer 的定义,它的作用是指示一个请求是从哪里链接过来,那么当一个请求并不是由链接触发产生的,那么自然也就不需要指定这个请求的链接来源。比如,直接在浏览器的地址栏中输入一个资源的 URL 地址,那么这种请求是不会包含 Referer 字段的,因为这是一个 “凭空产生” 的 HTTP 请求,并不是从一个地方链接过去的。说下ping的原理ping,Packet Internet Groper,是一种因特网包探索器,用于测试网络连接量的程序。Ping是工作在TCP/IP网络体系结构中应用层的一个服务命令, 主要是向特定的目的主机发送ICMP(Internet Control Message Protocol 因特网报文控制协议) 请求报文,测试目的站是否可达及了解其有关状态。一般来说,ping可以用来检测网络通不通。它是基于ICMP协议工作的。假设机器A ping机器B,工作过程如下:ping通知系统,新建一个固定格式的ICMP请求数据包ICMP协议,将该数据包和目标机器B的IP地址打包,一起转交给IP协议层IP层协议将本机IP地址为源地址,机器B的IP地址为目标地址,加上一些其他的控制信息,构建一个IP数据包先获取目标机器B的MAC地址。数据链路层构建一个数据帧,目的地址是IP层传过来的MAC地址,源地址是本机的MAC地址机器B收到后,对比目标地址,和自己本机的MAC地址是否一致,符合就处理返回,不符合就丢弃。根据目的主机返回的ICMP回送回答报文中的时间戳,从而计算出往返时间最终显示结果有这几项:发送到目的主机的IP地址、发送 & 收到 & 丢失的分组数、往返时间的最小、最大& 平均值最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~Github地址:https://github.com/Tyson0314/java-books