学习方法。建立计网知识架构。追本溯源。持续。设定预期。
网络层与传输层的区别
传输层位在网络层之上,网络层提供主机到主机的通信,为传输层送来的分组,选择合适的路由和交换节点进行发送,传输层提供主机上端到端的通信。
应用层的作用
为下层提供接口,直接为用户提供网络服务。比如HTTP、DNS、HTTPS。
HTTP
超文本传输协议,实现网络通信的一种规范。传输的报文是完整、有意义的数据,比如HTML文件、图片、文本等。
请求报文由4个部分组成:
- 请求行:请求方法、请求URL、HTTP版本
- 首部:通用首部、请求首部、实体首部
- 空行
- 请求体
响应报文由3个部分组成:
- 状态行:HTTP版本、原因短语、响应状态码
- 首部:通用首部、响应首部、实体首部
- 空行
- 响应正文
特点
- 请求-应答模式;
- 无状态:http协议不保存之前的请求、响应报文信息,方便快速处理每一次请求。
- 简单快速:HTTP协议传输只需带上请求方法、请求路径、参数,数据以明文方式传递。
- 数据类型灵活:HTTP允许传输多种数据类型,通过
Content-Type
字段标记。 - 短连接:请求-响应方式完成数据的传输。每一次数据的传输都需要发起请求、连接建立、传输数据、断开连接。针对数据请求很频繁、关联度大的业务不友好,因为需要频繁建立连接,这会造成额外的资源浪费。
常用方法
方法名 | 特点 | 方法作用 | 支持版本 |
---|---|---|---|
GET | 无副作用、幂等 | 获取指定资源,服务端只需要查询 | 1.0、1.1 |
POST | 副作用、不幂等 | 传输报文主体到指定资源,可以对新建资源,调用1次新建一个资源,调用N次新建N个。 | 1.0、1.1 |
PUT | 副作用、幂等 | 传输文件,对资源进行全量更新 | 1.0、1.1 |
HEAD | 无副作用、幂等 | 获取报文头部 | 1.0、1.1 |
DELETE | 副作用、幂等 | 删除文件,调用1次和N次的结果是一样的。 | 1.0、1.1 |
OPTIONS | 无副作用、幂等 | 访问支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 | |
CONNECT | 要求用隧道协议连接代理 | 1.1 |
GET和POST有什么区别?(字节飞书云表格一面)
首先我们了解两个概念,什么是副作用
和幂等性
。副作用
就是指对服务器上的资源进行了改变,比如注册操作就会产生副作用,而搜索是无副作用的,幂等性
是指对服务器发起1次或N次请求对某个资源进行处理,对资源的影响都是一样的。例如对一篇文章发起多次修改请求,自始至终都是同一个资源,这个操作就是幂等的。但如果是注册操作的话,就不是幂等的,注册1次和注册3、4次是不一样,账号资源在增加。
细说RESTful API之幂等性 - nuccch - 博客园 (cnblogs.com)
所以从应用场景来说,他们之间的主要区别
是 Get 主要用于获取数据,这类没有副作用,操作幂等的场景,Post 主要用于新增服务器资源这类具有副作用、不幂等的场景。(这是他们最根本的区别)。
HTTP的POST与PUT的区别(幂等性)_sc179的博客-CSDN博客_put幂等性
其余技术上的区别可以从三个方面来讲
- 浏览器对
URL长度
有限制。服务器为了防止恶意访问造成服务器资源的浪费也对URL做了限制。这就导致了Get能携带的参数有限,如果要携带数据量较大,可以选择post
方法。(HTTP协议对URL是没有长度限制的,对HTTP头和Body也没有长度限制。) - 从安全方面来讲,GET将请求包含在
URL
里,post将请求放在body
里,post比Get稍微安全些(抓包的话都是一样暴露的)(HTTP协议中,方法和数据是互不影响的,也就是说get
请求的报文正文也可以放在body
里。) - GET 请求会被浏览器
缓存
,而 POST 不会;
(GET和POST有什么区别?及为什么网上的多数答案都是错的。 - 南柯之石 - 博客园 (cnblogs.com)
HTTP常见状态码的意义
状态码代表的是HTTP
完成时的状态,设置状态码的目的是为了更清楚的表示http请求的状态,是是成功还是失败,失败的原因又是什么。
2XX
200 OK,客户端发出的请求被服务端处理,并成功返回资源。
201 Created,成功请求,服务端创建了新的资源。一般用在`post`请求。
202 Accepted,服务端已收到请求,但是没有处理。
204 No content,客户端请求成功了, 但是不需要返回资源。
3XX
301 Moved permanently,永久性重定向,资源被分配到了新的`URL`。浏览器会自动跳转到`header`的`location`对应的`url`,如果不是`get`请求就不会重定向。可以缓存。
302 found,资源被临时分配到了新的`URL`。浏览器也会自动跳转到新的`url`,但是只获取新的内容,不保存旧的地址。只有`cache-control或expires`指定的情况,才能被缓存。
303 see other,资源存在另一个`URL`,应该使用`Get`方法获取。不会被缓存。
304 not modified,客户端向服务端请求资源,客户端缓存的资源是最新的,响应体中没有数据。
307 temporary redirect,临时重定向,与302类似,但是希望客户端保持请求方法不变向新的地址发起请求。
详解重定向(HTTP状态码301/302/303/307/408)附例子 - 汕大小吴 - 博客园 (cnblogs.com)
4XX
400 bad request,客户端的请求报文出现语法错误;
401 unauthorized,发出的请求需要带上用户的身份认证信息;
403 forbidden,发出的请求被服务器拒绝;
404 not found,服务器没有找到客户端请求的资源。
5XX
500 internal server error,服务器处理请求时内部发生错误;
501 Not Implemented,服务器不支持的请求方法;
502 Bad Gateway,网关错误。后端服务器的问题,应用服务问题分两种,应用本身有问题,另一种是依赖服务问题。知乎链接跳转失败,一种可能是应用挂了。偶尔出现,可能是CPU利用率高,QPS增加;应用本身`nginx read`超时配置;接入层`nginx read`配置。[(2条消息) http状态码502与504区别_wangtingting_100的博客-CSDN博客_http502](https://blog.csdn.net/wangtingting_100/article/details/81106767)
503 service unavailable,服务不可用,无法处理请求。
客户端登录失败后端要返回什么状态码?
如果是请求报文出现语法错误,应该返回400
,如果是需要携带认证信息,应该返回401
,如果是因为账号密码没有通过认证,那应该返回200
,在响应报文中返回错误信息。这个没有绝对的说法,facebook
就全用的200。
别吵了,Rest API的状态码和错误处理最佳实践来了! - 知乎 (zhihu.com)
常见首部字段
HTTP的首部分为请求首部、响应首部、通用首部、实体首部。
通用首部
Cache-Control,用于缓存
。可以设置缓存是否可用、有效期。
Connection 用于设置浏览器想要使用的连接类型
,比如设置这个字段为 keep-alive,使用持久连接,和 upgrade 结合使用,用于升级协议
,比如升级为 websocket 协议,建立 WebSocket 连接,upgrade 设置为 websocket。
Date,表示创建报文的时间
。
请求首部
Accept,能正确接收
的媒体类型;Accpet-Charset,能正确接收的字符集;Accept-Encoding,能正确接收的编码格式列表;Accept-Language,能正确接收的语言列表。
If-Modified-Since 和 if-none-mathc 用于协商缓存
,if-modified-since 表示上次服务端响应的资源最新更新时间,If-None-Match 表示资源的哈希摘要值,如果服务器已存资源里面没有匹配的资源,那么条件为真,返回200以及请求的资源。
Origin,表明请求来自哪个站点,值是协议加域名,用于防范CSRF
攻击,所有跨域请求都会携带Origin,同源请求中post/put/options/delete
都会添加,get/head
不会。
Referer,表示发起请求的那个页面,值是完整的URL
,链接发起的请求都会携带这个值,防盗链,如果请求发送到服务器,服务器发现referer
不符合要求,就会拦截;防止恶意请求;如何防盗链?防而已请求?
Host
,服务器的域名;
User-Agent
,客户端信息;
响应首部
ETag,资源标识,用于协商缓存
;
Access-Control-Allow-Origin/ Access-Control-Allow-Methods,允许跨域
的源和http方法。当请求origin允许访问服务器时,xxx-origin就是这个访问的源。也可以是通配符。xxx-credentials 为真时,xxx-origin 不能为通配符。
Access-Control-Allow-Credentials,允许客户端携带认证信息,用于 cors 策略,如果类似于Axios请求,发起请求时设置了withCredentials:true,那么请求会携带上认证信息,那么客户端就必须设置Access-Control-Allow-Credentials为true。否则会报错。
Access-Control-Allow-Credentials 跨域 - 简书 (jianshu.com)
Location,客户端重定向
到某个URL;
Server,服务器名字。
实体首部
Content-Type,传输内容的媒体类型
。
Expires,资源的过期时间。Last-Modified,资源的最后修改时间。都用于缓存
。
非标准首部字段
Cookie,客户端存储得cookie
字符串,存储了一些与用户相关的信息。
set-cookie,服务器用于设置用户在当前域下的cookie。一个cookie需要一个set-cookie,设置多个cookie需要多个set-cookie。
传输数据格式
application/json
序列化之后的 json 字符串。
application/octet-stream
application/x-www-form-urlencoded
只能上传键值对,一般用于post请求上传表单数据。请求方式为get时,作为url的参数。请求方式为post时,经过url编码,放在body里。
multipart/form-data
既可以上传键值对,也可以上传文件等二进制数据。因为用 boundary 隔离。
keep-alive 机制
http 短连接有哪些代价呢?
主要有两方面的代价。一方面是创建销毁tcp连接的资源消耗,另一方面是每次tcp连接创建后的满启动提速的等待过程。
如果不复用 tcp 连接,每次发起 http 请求都创建新的 tcp 连接,其中主要的消耗是出自 tcp 的创建和关闭带来的消耗。tcp 连接需要操作系统分配内存、进行握手,挥手时需要回收内存、进行4次挥手,tcp 挥手时客户端可能还会进入 time_wait 状态,仍占用端口。如果客户端和后端需要频繁交互,那么客户端的端口很容易被占满。不仅如此,由于 tcp 的拥塞控制算法,每次 tcp 连接创建完还有一个慢启动提速的过程。
tcp 连接不仅需要分配端口,还需要文件描述符、缓冲区、结构体等资源,这三个等价的都是内存的分配。一个 tcp 连接大概需要占用 3.155kB。首先明确一点,tcp连接在创建时并不会真的去分配接受缓冲区和发送缓冲区。tcp协议控制块是 struct tcp_sock,每个tcp连接对应一个tcp_sock对象,为了能从文件描述符映射到tcp_sock,还需要struct file/ struct dentry/ struct socket_alloc/ struct socket_wq,分别表示每个打开的文件,文件所在目录,连接vfs和tcp_sock的桥梁,包含struct socket和struct inode,用于wait queue。
根据tcp_sock对象的结构体,每个tcp连接最少占用内存是256+192+640+1792+64=2944字节。实际比这个数值大一些。因为
Linux 中每个 TCP 连接最少占用多少内存? - 知乎 (zhihu.com)
keep-alive 机制就维护一个TCP
连接,在一个 tcp 连接上发送尽量多的http
请求,而不是每次发送http
请求,都重新建立一个tcp
连接。客户端与服务端约定好,服务端在返回 http 响应后不会关闭 TCP 连接,同样客户端在收到响应后也不会关闭 TCP 连接,下一个 http 请求会复用这个 TCP 连接。这样做减少TCP
重复建立销毁带来的资源和时延开销,很适合两端频繁交互的场景。
一个网页通常有很多资源需要请求,html文档、js、css、图片等静态资源,还有 ajax 请求。有时候页面可能需要引入十几个js、图片、样式表,只有这些资源都加载完才会呈现完整的页面。启用 keep-alive 机制后,在加载一个页面中的资源时,尽量复用一个 TCP 连接。
对于客户端来说,keep-avlie 机制只需要在请求头中添加 connection: keep-alive,现代浏览器一般都是默认开启的。而 tcp 连接的复用时间或次数是由服务端设置的,几十秒,几十次或者几百次。
http 请求与 tcp 连接
如何判断一个 http 请求完成了呢?
非持久连接的情况下,响应中有 content-length,那么浏览器就根据这个字段来接受和处理数据,数据接收完之后,这个请求就完成了。浏览器在接受数据的时候需要回复 ack,所以在接受完之后,才算没有数据要发送,那么可以由浏览器主动发送 fin,也可以由服务端来。如果没有这个字段,浏览器会一直接受数据,直到服务器主动断开 tcp 连接。
使用持久连接的情况下。如果响应数据是流式的,动态生成的,那么响应头有一个 transfer-encoding 字段,值为 chunked,表示分块传输,chunked 编码的数据在最后有一个空 chunked 块,表示本次传输数据结束。如果响应的数据已知大小,那么响应头中有一个 content-length 字段,表示 body 的大小,浏览器根据这个字段来接收处理数据,处理完这个长度的数据也就完成了这次请求。
http 请求完成后会关闭 tcp 连接吗?tcp连接一般由谁主动关闭?**
非持久连接的话,http请求完成就会关闭 tcp 连接,如果请求很频繁,那么会在服务端创建很多 time_wait 的 tcp 连接。根据 tcp 连接的原理,由 socket 四元组唯一标识一个 tcp 连接,而 tcp 的状态是针对这个连接而言。服务端的 ip / port 固定,客户端发起一个http 短连接请求,交互完毕。如果是服务端主动关闭
,在服务端这个 tcp 连接就进入 time_wait 状态,短时间内不会再接受客户端这个 socket 发起的 tcp 建立连接,所以客户端会分配新的端口,创建新的 socket 建立新的 tcp 连接。如果是客户端主动关闭
,那么客户端会出现 time_wait 状态,客户端发送下一个请求时会用新的端口及逆行 tcp 连接。
非持久连接会造成2个问题,客户端端口占满
和服务器tcp连接挂满
。如果客户端需要频繁请求服务器,那么会频繁分配端口,可能会导致客户端机器上的端口被分配完,没有端口可用。服务端能够维持的 tcp 连接也有上限,频繁的短连接请求可能会在某一时刻将服务器能支撑的 tcp 连接挂满。服务端tcp连接挂满,等待一段时间又会有可用的tcp连接。或者可以复用socket,或者减少服务端tcp连接的 time_wait 时间。使用持久连接。
http1.1默认使用持久连接,现在服务器一般都实现了 keep-alive。在首次加载页面的时候,第一次向某个服务器发起的 http 请求会首先进行 dns 查询、初始 tcp 连接,使用https的话还要 ssl 握手,后续的 http 请求就算没有 connection: keep-alive 字段,也会复用
了第一次请求建立的 tcp 连接。刷新页面也不会导致 tcp 断开。除非超过设定的时间或处理请求的最大次数。一般由服务器主动断开
。tcp 长连接虽然在一定程度上解决了短链接造成的问题,但是在并发量大,用户频繁操作的情况下,可能没法提供服务,应为长连接会在一段时间或次数上限内保持 tcp 连接状态,也就是说会短时间内占用服务器的资源。@todo 那么如何解决呢?
使用了持久连接的情况,tcp 连接由服务器主动断开
。客户端发起下一个 http 请求的时候需要分配一个新的端口,服务器的 ip/port 是不变的。tcp连接中主动发起 fin 的一方会进入 time_wait 状态,如果客户端不分配新的端口创建新的 socket 进行连接,那么服务器就没法在 time_wait 这个状态下处理这个旧socket的 tcp 连接。所以客户端需要分配一个新的端口,创建新的 socket 四元组,对于服务器来说,这又是一个新的 tcp 连接。主要是
在服务端设置的定时器和最大请求次数,服务端不主动断开,浏览器怎么直到定时器到了或者最大请求数到了。@todo待考证。
一次http请求,谁会先断开TCP连接?什么情况下客户端先断,什么情况下服务端先断? - web21 - 博客园 (cnblogs.com)
Http——Keep-Alive机制 - 曹伟雄 - 博客园 (cnblogs.com)
可能会搞砸你的面试:你知道一个TCP连接上能发起多少个HTTP请求吗? - 知乎 (zhihu.com)
对于请求频繁的场景,有两个术语 qps/tps。重点在于 tps,每秒事务数
,事务的定义可以是一次接口请求,也可能是多次接口请求,是一个完整的业务流程。一个事务是从发起第一个请求到接收最后一个请求的响应这样的过程。每个请求要经过发起、服务器处理、返回响应这样的过程。如果一个事务需要 n 次请求,1秒内可以完成 m 次 n 个请求,那么 tps 就是 m。注意和 qps 的区别,qps 指的是每秒查询的次数,不执行增删改操作,不够全面。
性能测试:TPS和QPS的区别 - 全栈测试笔记 - 博客园 (cnblogs.com)
但是存在队头阻塞
问题。也就是在同一个TCP连接上的HTTP请求必须串行发送,等待上一个请求的响应返回,才能发送下一个请求。
管线化的原理是什么?为什么没人用?
管线化是在持久连接的基础上,实现多条流水线,也就是在一个 TCP 链接上同时发送多个 http 请求,不用等待上一个请求的响应返回再发送。
短轮询与长轮询
HTTP 协议并不是为了双向通信程序准备的,起初的 web 应用程序只需要 “请求-响应
” 就够了。在应用中想要双向通信
,就只能利用 HTTP 轮询的方式,由此产生了短轮询和长轮询。
通过客户端定期轮询
来询问服务端是否有新的信息产生,缺点有2个
,轮询间隔大了则信息不够实时,轮询间隔过小又会消耗过多的流量,增加服务器的负担。
长轮询
是对短轮询的优化,需要服务端做相应的修改来支持。客户端向服务端发送请求时,如果此时服务端没有新的信息产生,并不立刻返回,而是将请求挂起
,Hang住一段时间等有新的信息或者超时再返回,客户端收到服务器的应答后继续轮询。可以看到长轮询比短轮询可以减少大量无用的请求,并且客户端接收取新消息也会实时不少。相应的,长轮询也就减少了网络流量的浪费、减少了服务器的负担。
总结:长轮询弥补了短轮询的大部分缺点,比如,轮询时间短,消耗流量过多,增加服务器负担、轮询时间长,信息不够实时;长轮询会将http请求挂起,直到有新消息,或者超时再返回。
虽然长轮询比短轮询优化了不少,但是还是有2个缺点
,第一个http请求被挂起也会消耗资源,而且每次请求还会带上请求头部,第二个是长轮询的连接结束之后,服务器端积累的新消息
要等到下次客户端请求才能获取到。@todo 请求被挂起联系到服务器并发处理。
更好的方式是只用一个TCP连接来实现客户端和服务端的双向通信,WebSocket协议正是为此而生。
长连接与短连接
长短连接与长短轮询不同,长短连接是针对 tcp 的说法,http1.1 的持久连接底层就是用了 tcp 长连接,http1.0 短连接就是用的 tcp 短连接。持久连接是由 connection 头决定的,需要客户端和服务端的支持。而轮询是一种应用层面的编程实现方式,短轮询只需要客户端编程实现,长轮询还需要服务端通过编程的方式挂起请求。
长连接就是双方建立连接之后,传输完一波数据,并不立即关闭连接,可以传输n波数据,没有数据传输时通过探测包保活,可以由任一方主动断开连接。websocket 利用了 tcp 长连接,并且实现了应用层面的长连接,全双工通信。
短连接是双方建立连接后,传完一波数据,就关闭连接。
(1条消息) http的长连接和短连接(史上最通俗!)以及应用场景这瓜保熟么的博客-CSDN博客长连接和短连接的使用场景
轮询、长轮询、长连接、websocket - 听风。 - 博客园 (cnblogs.com)
全双工与半双工
全双工就是通信双方可以在同时向对方发送消息。而半双工在同一时刻只能由一方向另一方发送消息。单工就是只允许一方向另一方发送消息。
HTTP2
HTTP1.1存在的问题
- 队头阻塞。因为HTTP部分1.0/1.1实现的
持久连接是
在同一个TCP
连接依次串行进行的,这样的技术虽然解决了反复创建销毁 tcp,以及 tcp 慢启动问题。但是又引发了新的问题,当一个请求迟迟没有响应时,后续的请求都会排队等待。虽然引入了管线化技术,但是管线化技术实现和管理难度较大。 - 多个TCP连接。引入了多个并行
TCP
连接,同一域名下限制为6个。 - 头部冗余,采用文本格式。HTTP1.0/1.1都采用文本格式,首部未压缩,每个请求都会带上cookie、user-agent等完全相同的首部。
- 不支持服务端主动推送。
HTTP2的改进
针对HTTP1.1的不足,HTTP2做了如下改进:
- 核心变化是采用
二进制帧
进行数据的传输。通过二进制实现了真正的多路复用。解决了HTTP1.1
队头阻塞的问题。采用二进制帧划分应用层报文,划分成更小的块,可以在TCP连接上交错发送多个HTTP报文的块。 - 采用 hpack 算法对头部进行压缩,减小了头部的体积。
- 服务端主动推送。
二进制帧
HTTP的核心变化是采用了二进制分帧层
。HTTP2是二进制协议,不是文本协议。HTTP2.0的数据报划分为HEADERS
帧和DATA
帧,并采用二进制编码。用帧Frame
作为通信的基本单位,消息Message
由一个或多个帧组成。流Stream
是TCP连接上的双向字节流,可以承载一个或多个消息。一个TCP连接上可以有任意多个流。一个完整的HTTP请求只能在一个流上传输,http报文拆分为多个帧,属于一个 http 请求的帧通过流 id 来标识。
面试官问:你了解HTTP2.0吗? - 掘金 (juejin.cn)
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
length,表示整个帧的长度。
type,表示帧的类型,1个字节,有10种类型,headers / data / priority / rst_stream / settings / push_promise / ping / goaway / window_update / continuation。
stream identifier,表示流id,用于表示帧属于哪个特定的流。
frame payload,表示主体内容。
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
pad length,消息的长度。
data,传递的数据,
padding,填充字节,发送时必须设为0.
HTTP2.0服务端推送
可以替代websocket吗
可以替代,但是要搭配SSE
技术。HTTP2.0
对头部进行了压缩,但是WebSocket
更小;HTTP2.0
可以服务端推送,但是只能在响应客户端请求后,将一些客户端需要的资源,例如脚本、CSS
样式、图片等再发给客户端。无法真正意义上做到服务端向主动客户端推送。而且HTTP2
的推送不能发送给应用程序,只是发送给浏览器,浏览器自动处理。HTTP2.0
和WebSocket
都可以进行二进制传输,多路复用,全双工通信(因为一个TCP链接上是全双工的)。
http/2将淘汰websocket? http/3将使用udp? http新闻 - 云+社区 - 腾讯云 (tencent.com)
什么是SSE(Server sent event,服务端发送事件)?
SSE和WebSocket是H5提出的技术,SSE仅支持从服务端向客户端
发送消息,客户端无法向服务端发送消息。与WebSocket相比,SSE是基于HTTP运行的,不需要其他组件。SSE的兼容性比WebSocket差,IE和Edge不支持SSE。
SSE连接建立过程:客户端创建EventSource,实例向服务端发送请求,使用addEventListener
监听message
事件,然后照常发送ajax请求,服务端收到EventSource
请求后,设置响应头为text/event-stream
,为该客户端建立SseEmitter
实例。
但是HTTP2.0
加上SSE
是可以做到实时且高效的服务端主动推送的。
头部压缩
有了gzip压缩,为什么 http2.0 对header还需要进行压缩,为什么gzip不作用于header?
因为gzip
压缩是针对内容进行的压缩,并不能对头部进行压缩。请求头通过设置Accept-Encoding
表示支持哪些压缩算法。
HTTP3
HTTP3与HTTP2/1.1不同,是一个基于QUIC的应用层协议,而QUIC协议是基于UDP
的。在HTTP2中,存在一个问题,如果网络环境不好,TCP连接出现丢包,就会导致性能急剧下降。这是因为HTTP2是使用一个TCP连接同时传输多个http请求的报文,如果当前网络环境不好,其中一个http请求的报文出现丢包,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。这就是TCP队头阻塞
。
由于TCP协议存在时间长,由操作系统实现,已经应用在各种设备中,对TCP进行更新是不可能的,所以基于这个原因,Google就搞了一个基于UDP协议的QUIC协议。QUIC协议也具有
多路复用、0-RTT、重传等功能。
QUIC协议使用64位id
对http请求进行标识,充分利用了UDP无连接的特性,只要是同一个id那么就是同一个HTTP流,这样就做到了在传输层识别不同HTTP流,不同的流不会互相阻塞。
但是在HTTP2中,使用二进制帧,将HTTP报文拆分为多个帧,每个帧属于特定的HTTP流,利用了TCP的全双工特性,可以同时传输多个HTTP流,在传输层,仍然利用的是同一个TCP,某个http流发生丢包,就会阻塞所有http。
关于队头阻塞(Head-of-Line blocking),看这一篇就足够了 - 知乎 (zhihu.com)
0-RTT
是利用了类似TCP快速打开
的技术,缓存了之前会话的认证信息,在下次连接的时候携带上认证信息和数据,通过认证信息建立连接。
HTTPS
HTTP没有对数据进行加密,安全性差;没有对通信方进行身份验证,可能会遭遇伪装(客户端或服务端都可能不是预料之内的真正的客户端或服务端);无法验证报文的完整性,报文可能会被篡改。
HTTPS由HTTP负责通信,SSL/TLS进行数据的加密。其主要目的是对web服务器的身份认证,保护交换数据的隐私和完整性。
HTTP 与 HTTPS 的区别
- HTTP是
明文传输
,没有对数据进行加密,安全性较差,HTTPS对数据进行了加密
,安全性好; - HTTP没有对通信双方进行
身份验证
- HTTP的页面响应速度比HTTPS的响应速度快一点,主要是因为HTTPS比HTTP多了一个证书验证、协商交换会话密钥阶段,所以在首次建立连接时消耗多一点时间。
安装SSL证书(https)后会使网站速度变慢吗?_ONE PIECE-CSDN博客 - HTTP和HTTPS的
连接方式不同
,使用的端口也不同
,HTTP默认使用80端口,HTTPS使用443端口
混合加密+数字证书(美团到家事业群外卖部一面)(百度一面)
这里最重要的就是这两个点:混合加密
、数字证书
,一定要主动答到。今天美团一面就被问到了,我以为只问的数字证书,结果她说我没提到混合加密,人晕晕。
单独使用非对称加密有什么问题?
非对称加密使用公钥进行加密,使用私钥进行解密。私钥只有服务器拥有,客户通过判断对方是否拥有私钥来判断是不是服务器。这样的方式存在两个问题
。首先是服务器公钥的真实性
问题,因为任何人都可以生成公钥私钥,攻击者可以冒充服务器发送自己生成的公钥,客户端就会认为这是真的服务端;其次是公钥的公开性带来的数据保密性
问题,服务端用私钥加密的内容,用公开的公钥就可以解密,很不安全。
也许有的项目会用两把非对称密钥的公钥进行加密,但是这样会导致HTTP请求变慢,每次的加解密计算会消耗更多的cpu资源、内存资源。
使用混合加密解决通信过程的数据加密问题
在tls握手阶段,第一、二次握手,客户端和服务端首先进行加密组件交换,客户端发送自己支持的对称加密算法和会话密钥生成算法
,以及其他参数,例如密钥长度、第一个随机数
,服务端选择之后,返回后面要用到的加密组件和第二个随机数
给客户端,还有数字证书。
客户端验证证书有效后,生成第三个随机数
,并用服务端公钥进行加密,然后发送给服务端,还会发送一个提示报文和finshed报文,服务端收到后用公钥解密加密字符串,得到第三个随机数,这是第三次握手。通过第三次握手,服务端和客户端通过得到了三个随机数,就可以用dh算法生成会话密钥。服务端也会返回一个提示报文和finished报文给客户端。
这样客户端和服务端就可以用相同的参数和会话密钥生成算法生成相同的会话密钥,同时密钥又不会暴露在网络中。
客户端是如何获取服务端公钥的?
首先我们知道HTTPS中使用了数字证书
来验证服务端的真实性。那么数字证书中包含哪些内容呢。数字证书包含证书有效期、域名、服务端公钥、证书所有者、签名算法、签名、证书机构信息。客户端验证证书合法后就从服务端证书中获取服务端公钥。
客户端如何确认公钥是真正的服务端发布的?
客户端会预先内置
数字证书,绝大部分客户端只会内置根证书、中间证书。当收到服务端证书后,首先验证证书的内容信息
,是否过期,域名是否与请求的服务端一致,然后在客户端内置的各个证书链中进行验证。
找到后再用CA公钥对服务端证书中的签名
进行解密,拿到摘要(指纹),再用哈希算法对服务端证书内容进行哈希计算,生成新摘要,对比两个摘要是否一致。哈希值不同则证明证书被人篡改。
数字签名对证书进行完整性保护
。并且在这个过程中用CA私钥
保证了数字签名的真实性,就算证书被篡改,没有CA私钥,攻击者自己搞个私钥来签名也没意义,没有配对的CA公钥就没法正确解开签名。
如果一致,还会访问CA服务器
,验证证书的合法性和有效性。如果CA私钥泄露就会很严重了,虽然会去CA服务器查询证书信息,如果CA私钥泄露发现的迟,CA服务器的信息更新就越滞后。
什么是指纹?指纹算法?
指纹算法是对签名后的证书计算散列值,检测证书是否被篡改。
如何防止数字证书被篡改?
中间人攻击可以将证书中的公钥改成自己的。证书机构使用数字签名防止中间人攻击
,确保证书内容的真实性。
证书机构将数字证书包含的信息生成摘要,将摘要用CA私钥加密,生成数字签名。服务器将数字证书和数字签名一同发给客户端。客户端拿到服务端返回的数字证书后,用本地内置的CA公钥解密签名得到摘要,再用hash算法对服务端证书内容进行哈希计算,比较两个摘要是否一致。
哈希值不同则证明证书被人篡改。并且在这个过程中用CA私钥保证了数字签名的真实性,就算证书被篡改,没有CA私钥,攻击者自己搞个私钥来签名也没意义,没有配对的CA公钥就没法正确解开签名。
如何防止伪造的数字证书?
有3点。第一用户要有安全意识
,不要安装非权威机构的根证书。其二,权威机构的私钥
一定要保管好。这两个条件都满足的话,攻击者就没法仿造权威机构的证书。证书合法性验证时就查出来这个证书有问题。如果机构私钥泄露的话,那就后果严重了,攻击者确实可以仿造证书。其三,再验证证书时还要通过访问CA服务器查询证书是否被吊销,查询吊销列表和证书状态
。但是吊销列表存在滞后性,在短时间内也可能造成巨大损失,只有等待新的吊销列表才可能发现伪造的证书。
客户端内置的根证书如何更新?
通过接口请求更新,要过期了就请求机构续期证书或者申请新的证书。也有的会一次性预置多个公钥,延长使用时间。
根证书、服务器证书、用户证书的区别?
首先说明一下证书链
,证书链的顺序是根证书、中间证书、ssl证书。根证书签发中间证书,中间证书签发ssl证书。服务器申请的数字证书就是ssl证书。服务器证书包含ssl证书,还可以包含中间证书。
服务器证书需要通过整个证书链上的证书的验证,才合法。例如下面图中情况:你从证书2处购买了证书3,但是证书2不是默认包含在浏览器种的可信证书,中间证书2是根证书1签发的证书,如果服务器端只发送证书3不包含证书2,那么浏览器找不到证书3的签发者证书2,这样整个证书的验证链条就断裂了,你的证书就会被浏览器标识为无效证书“Invalid certificate” 或 “certificate not trusted” 。
根证书、服务器证书、用户证书的区别roddger1的博客-CSDN博客根证书和用户证书有什么作用
HTTPS连接建立过程
HTTPS默认工作在443端口
。HTTPS建立连接的过程是先进行TCP三次握手,再进行TLS四次握手。TLS是SSL3.0标准化后的产物,SSL有1.0/2.0/3.0版本,TLS有1.0/1.1/1.2/1.3(只需要两次握手)。
HTTPS连接建立过程分为:
- TCP三次握手;
- TLS四次握手;
- 会话密钥验证无误后,就可以发送HTTP请求交换数据。
TLS四次握手细节如下:
- 第一次握手,客户端向服务端发送加密组件信息。也就是Client Hello报文中包含客户端支持的SSL版本、加密算法、密钥长度、随机数、session id等。
- 第二次握手,服务端返回后续要使用的加密组件信息,以及证书,还有server hello done报文,握手协商结束。也就是Server Hello报文,报文中包含客户端加密组件信息中服务端支持的加密组件信息,Certificate报文,报文中包含数字证书。
- 客户端验证服务端数字证书
- 首先检查证书有效期、验证证书域名与访问的服务端域名是否一致;
- 然后检查证书机构可信度,沿着上一级证书机构找,直到找到客户端信任的证书机构,然后用CA公钥解密签名,比较摘要;
- 与CA服务器交互,判断CA证书是否被篡改,并查询证书是否被吊销。
- 第三次握手,客户端向服务端发送用公钥加密后的第三个随机数,以及Change cipher spec报文,提示服务端,后续的客户端报文会采用会话密钥进行加密,还有Finished报文,其中包含连接至今的全部报文的整体校验值(也就是会话密码加密了之前握手的所有信息)
- Client Key Exchange报文,其中包含用服务器公钥进行了加密的Pre-master secret随机密码串(后续客户端和服务端通过三个随机数和相同的对称密钥生成算法计算出会话密钥,master secret)
- SSL握手是否成功取决于服务端能否正确解密Finshed报文。
- 第四次握手,服务端向客户端发送Change Cipher Spec报文,提示客户端,后续服务端报文会采用会话密钥加密,以及Finshed报文。
- 客户端与服务端的Finished报文交换完毕,没有问题的话,SSL连接建立完成。
(1条消息) HTTPS协议详解(四):TLS/SSL握手过程_郭晓东的专栏-CSDN博客_ssl握手
数据以加密的方式传输,对称加密算法和密钥加密保证数据机密性;hash算法进行数据完整性保护。
Https:TLS 握手协议 - 简书 (jianshu.com)
TCP三次握手在前还是TLS四次握手在前
HTTPS是基于TCP传输协议实现的,一般情况下都得先建立可靠的TCP连接才能进行TLS握手。特殊场景
HTTPS可以在SSL四次握手过程中同时进行TCP三次握手,但是必须满足两个条件
:客户端和服务端都开启TCP Fast Open功能,且TLS版本是1.3;客户端和服务端已经通信过一次。
给面试官上一课:HTTPS是先进行TCP三次握手,再进行TLS四次握手 - 知乎 (zhihu.com)
TCP Fast Open功能在Linux3.7内核版本后才加入。 第一次连接时:
客户端发送syn报文,报文中包含Fast Open选项
,该选项中cookie
为空。服务端收到syn报文,生成cookie,放置在ack报文的Fast Open选项中。客户端收到后,缓存Fast Open选项中的cookie。
后续连接时,客户端发送syn报文,报文中携带了数据,Fast Open中携带上cookie。服务端收到报文中,对cookie进行校验,然后对数据进行处理。如果cookie无效,服务端返回的ack报文中只包含syn的序列号。cookie有效的话,就在ack报文中带响应数据。
@todo这里的cookie是用什么表示连接双方的。
HTTPS与HTTP
ssl 握手之后,会先请求服务器是否支持 http2.0,如果不支持就用 http1.1。@todo待考证。
http2是基于https实现的。
WebSocket
WebSocket与Socket不同。Socket是应用层与TCP/IP协议通信的中间软件抽象层,是对tcp/ip协议的封装,是一组接口。而WebSocket则不同,它是一个完整的应用层协议,包含一套标准的API。
WebSocket是一种基于TCP
的全双工
通信协议,位于应用层,与HTTP使用相同的端口
。例如,ws://www.chrono.com、wss://www.chrono.com:8080/srv这样的请求就用的是WebSocket协议。ws默认占用80端口,wss默认占用443端口。
客户端发起get请求,设置Upgrade字段与后端进行握手连接
,一旦客户端与后端建立连接后,就可以在两者之间建立持久性连接,并进行双向数据传输。WebSocket采用帧作为基本传输单元
,分为控制帧和数据帧,每个帧分为头部和负载。
帧
帧分为控制帧和数据帧,控制帧不能分片,数据帧可以分片。
FIN,标记是否分片
,没有分片的帧的FIN为1,分片帧的第一个分片的FIN为0,最后一个分片帧FIN为1。
opcode,指示帧类型
,其中0x1 (Text)/0x2 (Binary)为数据帧,0x8 (Close)/0x9 (Ping)/0xA (Pong)为控制帧。
MASK,用在客户端发送的帧中,为1使用Masking-key掩码对帧的数据进行掩码计算
,规范规定必须为1。服务端帧不做要求。对客户端帧做掩码计算的主要目的是防御代理缓存污染攻击。
Payload len表示数据的实际长度,以字节为单位。
WebSocket 协议完整解析 - 知乎 (zhihu.com)
WebSocket对象的readyState属性
表示实例的当前状态,CONNECTING
,正在连接,值为0;OPEN
,连接成功,值为1;CLOSING
,连接正在关闭,值为2;CLOSED
,连接已经关闭,或者打开连接失败,值为3。
握手连接
// 前端发起请求,h5 websocket对象。项目里用的ws模块。
var url = "ws://xxx.xx/x/y/z?p1&p2";
var ws = new WebSocket(url);
ws.on('open', (e) => {
}
ws.on('close', (e) => {
console.log(e.wasClean, e.reason, e.code);
})
ws.on('error', (e) => {
console.log(e.data);
})
ws.on('message', (data) => {
var res = JSON.parse(data);
})
建立WebSocket连接的第一步是进行握手,访问后端是否支持websocket协议
。客户端向后端发送一个get请求,设置请求头Upgrade字段为websocket,connection字段为upgrade,此外还需要设置Sec-WebScoket-Key/Sec-WebSocket-Version。
Sec-WebSocket-Version表示WebSocket的版本号
,如果同时支持多个版本,那么应该降序排列,后端会从中选择一个支持的版本号,如果都没有,那么会返回426状态码。
Sec-WebSocket-Key是用于握手的密钥
,由客户端生成的一个128位随机值,再通过base64编码的字符串,这是用来进行掩码运算的。这两个字段必传,否则握手失败。
## Request ##
GET /chat HTTP/1.1 /*main code*/
Host: server.example.com
Upgrade: websocket /*main code*/
Connection: Upgrade /*main code*/
Origin: http://example.com
Sec-WebSocket-Key: mupA9l2rXciZKoMNQ9LphA== /*main code*/
Sec-WebSocket-Version: 13 /*main code*/
Sec-WebSocket-Protocol: char, superchat
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
服务端如果支持websocket协议,并同意连接,设置Upgrade为websocket,并将connection设置为upgrade,返回101状态码
。
除此之外,还必须传递两个字段,sec-websocket-accept/ sec-websocket-version。
sec-websocket-accept 字段的值是,用客户端传递来的 sec-websocket-key 值与websocket魔数
(258EAFA5-E914-47DA- 95CA-C5AB0DC85B11)进行字符串连接,对拼接的字符串进行哈希(SHA-1哈希),再将哈希值进行base64编码,最后编码值就是sec-websocket-accept的值。
sec-websocket-version 的值是后端支持的websocket版本号
。
## Response ##
HTTP/1.1 101 Switching Protocols
Upgrade: webSocket
Connection: Upgrade
Sec-WebSocket-Accept: s4VAqh7eedG0a11ziQlwTzJUY3s=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
// python代码生成sec-websocket-accept值,也就是掩码运算后的值。
magic_number = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
key = 'mupA9l2rXciZKoMNQ9LphA=='
accept = base64.b64encode(hashlib.sha1(key + magic_number).digest())
assert(accept == 's4VAqh7eedG0a11ziQlwTzJUY3s=')
客户端要校验后端是否真的支持 websocket 协议
服务端收到客户端发来的 sec-websocket-key,用魔数进行拼接、哈希、再base64编码。当客户端收到响应报文后,用 sec-websocket-key 做同样的操作,先用魔数进行拼接,再哈希、base64编码。验证是否与 sec-websocket-accept 一致。
因为websocket协议使用和http相同的connection/upgrade字段,可能有的后端并不支持websocket协议,但是还是可能返回预期的upgrade。
客户端收到响应报文后,还会进行很多校验,101状态码、upgrade字段与字段值、sec-websocket-accept是否合法。(其实还会检测另外两个字段,sec-websocket-extensions/sec-websocket-protocol)。
@todo 为什么需要客户端发送一次http请求?
掩码覆盖
为了防御代理缓存污染攻击,规范规定客户端帧
一定要进行掩码覆盖。
使用4KB的掩码(Masking-key)对帧进行掩码计算,以字节为步长遍历帧的数据部分,用帧的字节索引i取模4,得j,取出Masking-key的第j个字节,然后将帧的第i个字节和Masking-key
的第j
个字节进行异或,就得到了掩码覆盖后的值。
服务端收到后首先检查帧是否有Mask
字段,如果没有则终止连接,如果携带了这个字段,就进行相同的运算就得到了客户端帧原来的数据。
// original-octet-i 表示帧的第i个字节。masking-key-octet-j 表示masking-key的第j个字节。
// transformed-octet-i表示帧的第i个字节掩码覆盖后的值。
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
什么是代理缓存污染攻击?
为什么要分片。websocket 分片有两个原因
,第一个是因为为了满足实时性
,消息过长,或者消息是实时产生的话,不能预测数据的最终长度,所以只要有消息产出就可以组成一个帧发送出去,让消息更快的到达客户端。另一个原因是为了复用TCP连接
,具体如何复用的还不知道。消息分片主要用fin/ opcode字段实现。未分片的消息,一个帧就携带了完整的消息,此时fin为1,opcode根据消息是文本还是二进制选择0x1或0x2。而分片的消息,例如文本消息,第一个帧fin:1; opcode: 0x1,中间帧的fin为0,opcode为0x0,表示这个帧是continuation frame,最后一个帧fin: 1; opcode: 0x1。分片的消息按序发送,由TCP保证消息可靠有序的交付给客户端。
@todo websocket会粘包、半包吗?什么是粘包、半包?如何解决?(websocket)协议中Ping Pong,Socket通讯ping pong(长连接),心跳包_邓文(desaco)的博客-CSDN博客_ping pong socket
心跳检测的实现
首先我们要知道客户端为什么需要心跳检测。在信号弱、网络异常的时候,客户端的ws连接会异常关闭
,这时候就需要对服务端进行心跳检测,而像在线聊天这种场景,需要客户端与服务端保持持久连接,并且出现连接错误应该给出及时的提示。而且我们先了解下websocket能监听的事件,open / error / close / message,error用来处理异常,close处理关闭,断开的原因有很多种,closeEvent有三个属性来描述这些原因,错误状态码 code 表示断开连接的类型,reason字符串,wasClean
表示连接是不是正常断开的,是一个布尔值,一般异常断开的时候是fasle。
WebSocket断开原因、心跳机制防止自动断开连接 - Jade_g - 博客园 (cnblogs.com)
WebSocket :用WebSocket实现推送你必须考虑的几个问题_M1lo的博客-CSDN博客_websocket的弊端
实现一个了心跳检测方法,设定一个定时器,到期后调用send方法向服务端发送一个约定好的消息,服务端收到后回复约定好的消息,这样就能确定客户端和服务端的ws连接正常。
在 onmessage 中收到消息就重置定时器
,如果长时间没有收到消息,定时器一到,就会调用 send
方法发送探测消息。这样做可以减少心跳包对传输性能的影响,保证了只有在定时时间没有数据传输的情况下才进行探测。
如果连接异常或连接已经断开,那么就在onerror / onclose回调中进行重连
,在onclose中根据了wasClean来区分连接是正常关闭还是异常关闭。第一次重连设定在5秒自会后,依次增加重连间隔时间,重连5次都失败就判定为失去连接。一般error发生后close也会发生,但是并不能保证error发生后close一定会执行,所以onerror里面最好用来做重连逻辑和websocket实例异常,在onclose里有三个属性很重要,特别是wasClean属性,表示是不是异常断开。
同样的,服务端也应该做心跳检测,因为客户端异常关闭ws连接时,服务端依然建立着和客户端的TCP连接,这会导致浪费服务端的资源并且影响这个客户端下一次的ws连接。控制帧有三种,close frame/ping frame/pong frame。close frame用来关闭websocket连接,当某一方需要关闭连接时,就发送一个close frame。ping/pong
目前只能服务端向客户端发送,客户端回应pong帧。
heart beat 与 keep-alive
keep-alive 是 tcp 内建的机制,需要在创建 tcp 连接的时候就设置参数启动,而 heart-beat 是在 tcp 之上的应用层实现的。
keep-alive 适用于清除死亡时间比较长的连接。比如说一个用户访问某个网页,向服务器发起 http 请求,然后网线被别人拔了,这种情况 tcp 连接会断开(内核会进行处理),但是服务器并不知道,还会维护
它与用户的这个 tcp 连接,如果服务器设置了 keep-alive 机制,那么就能够在用户断网超过定时时间后,丢弃这个 tcp 连接。一般定时时间为2小时。
然而,在实际应用中,更希望得到快速的反馈。如果用户不知道网线断了,这个页面有聊天窗口,那一直收不到信息,也没有断开连接的提示反馈。所以 heart-beat 就有一点优势,heart-beat 是在应用层实现的,可以用自行决定当前连接断开之后采取什么行动,而不仅仅是丢弃。heart-beat 的原理和 keep-alive 类似,都是发送探测包。
tcp 服务端如何判断客户端断开连接 - youxin - 博客园 (cnblogs.com)
Http与WebSocket的区别
有3点区别,连接方式不同,消息传输方式不同。
http协议是一种请求应答式的通信协议,一般只能由客户端发起请求,服务端返回应答,客户端收到应答后才能发送下一个请求。http2 实现了多路复用,但是仍不能实现全双工通信。WebSocket协议是一种全双工通信协议,实现了点对点的长连接,建立连接时需要客户端发送一次 http 请求,ws 连接成功后,后续所有的数据传输都通过这个 tcp 连接进行通信.
ws 中服务端可以主动向客户端发送数据。http2也有服务端推送,有什么区别?
ws 基本传输单位采用的是二进制帧方式,http1.1 采用文本格式。ws 首部分为控制帧和消息帧,控制帧有 fin / mask / masking-key / payload-len 等字段,http1.1 报文由报文首部、空行、报文主体组成,报文首部有请求行、状态行、请求首部、响应首部、通用首部、实体首部,http2.0 二进制有点复杂。
WebSocket的优缺点
数据包头部小,开销低;实现了持久连接;支持二进制或文本传输。
维护持久连接,需要处理断线重连;服务器维护持久连接增加一定的成本。
应用场景
多人游戏这种上下行数据量都大的场景,或者弹幕、实时股票等应用。
服务端推送,项目中用到的是ws模块,浏览器提供了原生的WebSocket对象。ws模块实现原理?与WebSocket对象的区别?
DNS 协议
什么是DNS协议?DNS有什么用?
DNS协议就是一套系统,可以将域名转换为对应的IP地址,IP是一串数字或数字的组合,不方便人记忆,所以就用域名来表示IP便于人记忆,可以反向查询,通过IP地址查询对应的DNS。
DNS还有什么功能?DNS负载均衡如何做
主机别名,邮件服务器别名,负载均衡。
DNS查询过程了解吗?
DNS查询有两种方式,递归方式和迭代方式。递归方式就是终端会逐个向上级DNS服务器查询IP,直到找到IP地址。DNS服务器有4种,本地DNS服务器,权威DNS
服务器、顶级DNS
服务器、根DNS
服务器。迭代查询类似于for循环操作,依次查询各个DNS
服务器,直到找到IP地址。
实际应用中查询过程主要为查询缓存、递归查询与迭代查询混合使用。使用缓存的主要目的是为了减少服务器压力,一般操作系统和浏览器都会对DNS
查询记录做缓存。访问URL
时,操作系统会先查询本地缓存,没有查到的话就去浏览器缓存中查询;如果还没查到,终端发起请求查询本地DNS
服务器,本地DNS
服务器没找到的话就迭代查询其他DNS
服务器。
@todo 运营商网络,权威dns服务器可以是公司在运营商注册的域名服务器。待证实。
TCP 协议
TCP报文结构
TCP是基于字节流的传输协议。会将应用层交付的报文拆分成报文段segment
,在每个报文段前加上TCP首部,共20个字节。
四元组就是源地址、目标地址、源端口、目标端口。
序列号是建立连接时生成的随机数,通过SYN
报文发送给接收端。
确认号表示下一次期望收到的数据序列号,发送端可以认为确认号以前的数据均被正常接收。
ACK
为1时,表示确认号字段有效,只有第一次握手包ACK
字段为0。
RST
为1,表示TCP连接出现异常,强制断开连接。
SYN
为1,表示建立连接,在序列号中设置初始值。
FIN
为1,表示请求断开连接。
TCP的特点
- 实现了复杂的控制机制,保证了数据可以有序、可靠的交付;
- 需要拆分报文,而且TCP头部有20个字节,拆分后的每个报文段都要加上头部,额外开销大;
- 还有流量控制、拥塞控制,可以减缓接收方和网络的压力。
三次握手过程
客户端和服务端绑定好端口地址,进入Listen
状态。客户端向服务端发送连接请求报文,包含客户端报文的初始序列号
,进入syn_sent
状态。
服务端收到客户端的连接请求后,同意连接就返回一个应答报文,其中包含了服务端报文初始序列号
,发送完进入syn_recived
状态。
客户端收到服务端的应答报文后,进入ESTABLISTED
状态,还要发送一个确认报文ack
给服务端,服务端收到后,也进入ESTABLISHED
状态。
为什么不能是两次?为什么不能是4次?
2次不可以。首先需要保证通信双方都具有
发送和接收能力;防止二次握手可能导致历史的连接请求SYN被服务端接收处理
的情况,比如:C
发送连接请求后由于网络原因超时迟迟没有收到服务端的应答报文,重新发送新的连接请求。服务端收到连接请求后进入Establisted
状态,当数据传输完后就释放连接。旧请求又到达了S
,S
收到后立即建立新的连接,返回一个应答报文并进入ESTABLISHED
状态,然而C
处于CLOSED
状态,并不知道S
在等他发送数据。
TCP3次握手的话,服务端收到历史连接请求进入syn_recived
状态,不会直接进入Established
状态,发送应答报文给客户端,客户端此时处于关闭状态,收到后直接丢弃。服务端重传5次后,还没收到客户端应答,就关闭这个连接。
4次就没必要了。因为经过三次握手,C、S
都知道了对方具备发送和接收能力,可以开始传输数据了。
第三次握手包可以携带数据吗?在什么时候能发送数据呢?
可以
。因为在客户端在收到服务端返回的ACK包后,就会进入Established
状态。在发送给服务端的ACK报文
中就可以携带数据。客户端在进入Established
之后,就能立即发送数据。
如果第一次、第二次、第三次握手包迟到了或丢包了怎么办?
第一、第二次丢包都会触发重传
机制。
TCP 三次握手背的滚瓜乱熟,那意外情况呢?丢包了呢?故意不回复 ACK 呢?_承香墨影的博客-CSDN博客
服务端迟迟没有收到第三次握手包或者数据包的话,就会触发等待重传
机制,重传SYN/ACK
包,重传多次后还是未收到,就会自动关闭这个连接。但是如果有客户端发送来的数据包,那么服务端会将这个数据包作为第三个握手包。
What if a TCP handshake segment is lost? - Stack Overflow
如何避免历史报文被接收?
使用随机生成的初始序列号ISN
,确切来说应该是序列号,虽然序列号是基于ISN递增变化的,以及计时器
。
初始序列号ISN
与序列号SEQ
的区别,作用是什么?
序列号
可以保证接收数据的有序。序列号表示数据报文段中数据的开始序号,会根据发送数据的字节数增加。初始序列号就是客户端和服务端发起连接请求的SYN报文中的序列号
,初始序列号的产生是随机的。这样做的目的主要是防止历史报文被重复使用和安全考虑。从安全角度考虑,如果使用固定的生成方式,可能会被攻击者伪造报文,干扰正常的TCP
连接。
20-1-tcp连接——初始化序列号(ISN)_网络安全-CSDN博客_isn是什么意思
确认序列号为对方报文的序列号+报文数据长度+1
。
什么是半连接队列,全连接队列?
服务端第一次收到客户端的syn
报文段,进入syn_rcvd
状态,这种状态的连接会被放在一个队列里。全连接队列
就是已经完成三次握手的请求存放的队列。如果队列满了就可能发生丢包现象。
什么是syn攻击,如何防御?
SYN
攻击针对TCP
第一次握手,客户端向服务器发送SYN
报文。服务端收到后,将这些连接存放在半连接队列
中,并返回SYN
和ACK
报文,等待客户端的回应。客户端伪造大量随机IP,发送大量SYN
报文,占据服务端的半连接队列,消耗服务端资源。
服务端一般会重试5次,可以调低服务端的重试次数,减少半连接队列中连接的存活时间。设置防火墙,网关过滤。
TCP四次挥手过程
发送端与接收端一开始处于ESTABLISHED
状态,发送端没有数据需要发送了,向接收端发送关闭连接请求报文,进入fin_wait_1
状态。
接收端收到后,返回一个ack
报文段,进入close_wait
状态,此时发送端到接收端的连接已经断开,而接收端还可以继续发送数据。发送端收到ack
报文段后,进入fin_wait_2
状态,继续处理接收端发来的数据。
接收端发送完数据后发送一个关闭连接请求报文给发送端,进入last_ack
状态。
发送端收到fin
报文后,发送一个ack
报文段给接收端,进入time_wait
状态,等待两个msl
时间后进入closed
状态。接收端收到确认报文段后,进入closed
状态。
为什么挥手需要四次,握手只需要三次?
TCP
建立连接第二次握手时,接收端将ack
报文段和syn
报文段合并在一起发送的。挥手时,由于接收方可能还有些数据需要返回给发送方,所以先发送ack
确认报文段,待数据发送完了,再发送fin
报文段。所以没有合并在一起发送。
TCP四次挥手可以减少一次吗?
可以,TCP
有一个延迟ACK
机制,为了减少一次请求就返回一次ACK
带来的时间消耗,通过累积到一定量的请求再返回一个ACK
,所以可以将接收端的ACK
、FIN
、要传输的数据
合并在一起发送。
(1 封私信 / 28 条消息) TCP四次挥手中间两次会合并成一次吗? - 知乎 (zhihu.com)
time_wait 状态
msl
是报文在网络中的最大存活时间。等待2*msl时间是为了确保接收端能收到发送端的应答,成功关闭
。如果发送端最后一次发送给接收端的ack
确认报文段没有被收到的话,接收端会重新返回一个fin
报文段,发送端接收到这个新的fin
报文段就知道因为某种原因接收端没有收到ack报文。
还可以确保
本次连接产生的所有报文都过期消失。
tcp keepalive机制
tcp keepalive与http keep-alive的区别
tcp keepalive 机制是用来维持 tcp 的持久连接,因为 tcp 可靠连接其实就是一种状态,需要双方维护,并不是真实的物理上的直接长久连接。而 http keep-alive 机制是用来复用 tcp 连接的,也是用来维持 tcp 的,但是时间相对要短,大概几十秒。而tcp长连接一般设置2小时的超时时间。
HTTP keep-alive和TCP keepalive的区别,你了解吗? - 知乎 (zhihu.com)
TCP连接也会遇到什么很多异常,例如网络问题、异常断开。
TCP假死就是一种很复杂的失活现象。
深入原理学习之–TCP长连接与心跳保活 - 知乎 (zhihu.com)
关于TCP/IOCP构架中出现的假死连接解决方案 - 风之枫 - 博客园 (cnblogs.com)
浏览器刷新的时候,未完成的HTTP请求会断开吗?TCP会断开吗?如果断开,是哪一方先断开的?浏览器这边如何处理?服务器如何处理?
超时重传
超时重传机制就是重传请求协议,有停等ARQ协议、连续ARQ协议
。
快速确认机制中,发送方向接受方发送一个数据包,会开启一个定时器,如果在定时时间内接收到应答就取消定时器并发送下一个报文。如果报文丢失了,超过定时时间,发送方就会再次发送丢失的报文直到对端响应。如果是接收方返回的应答丢失或超时,超过定时时间,发送方也会发送报文给接收方,接收方收到报文后发现序列号相同,会丢掉报文,重传应答报文。
延迟确认机制中。发送端维护了一个发送窗口
,发送端不用等待应答,可以连续发送数据。相比于快速确认机制减少了等待时间,提高了效率。接收端可以连续接收报文,通过累计确认
,收到多个报文之后统一回复一个应答报文,告诉发送端,这个序列号以及之前的报文都成功接受了,可以继续发送下一个序列号的报文。
累计确认有2个重要的缺陷
第一个缺陷是就算收到接收端发送到的缺失的报文序列号,也不会立即发送丢失的报文,而是等待定时时间到了才发送接收端缺失的报文。
第二个缺陷又叫回退n步。在连续接收到报文时,可能会先收到序号为5的报文,然后收到序号7的报文,这时候接收方只回复了序列号为6的应答,选择死等序号6的报文。而此时发送方还以为序号6之后的报文都丢了,将序号从6开始的报文都重传了一遍。这种方式又叫回退N步
。
为了解决这2种缺陷,提出了快速重传、SACK选择重传、DSACK重复选择重传
。
快速重传
,当接收方收到后续乱序的报文时,会返回重复的ack
,当发送端收到三次相同的ack
之后就重传接收方期望的报文,不再等待定时器。这种方式解决了发送方超时重传需要等待定时器的问题。但是仍没解决回退n步的问题。
SACK选择重传
,建立在快速重传
的基础上,返回收到的后续乱序的报文,这样发送方就知道哪些报文被接收,在重传的时候,就不会重传这些报文,避免了重传接收端已经接受的报文。
在SACK
的基础上,又提出了DSACK
,可以携带额外信息,告诉发送方哪些数据包重复接收了,可以有效帮助发送方进行判断,是发生了包失序
、ack丢失
,还是包重复
,更好的进行流量控制。(这里还需要捋捋)
TCP重传机制 - -零 - 博客园 (cnblogs.com)
滑动窗口机制
滑动窗口主要用来控制发送端的发送速率
,当接收方处理数据包的速率来不及处理发送端发送来的数据包时,接收方的缓冲区很快就会被数据包占满,如果发送方接着发送,就会导致丢包。通过滑动窗口这种机制,应答报文中包含了接收窗口的剩余大小
,可以控制发送端的数据包发送速率。
滑动窗口的大小如何确定?(待补充…)
发送方发送窗口会为每个已发送数据包设置一个定时器,如果定时时间内没有收到ack
,那么就会进行重传。
如何解决接收窗口为0引发的死锁问题?
(2条消息) TCP传输可靠性-排序、丢弃、重发_nicolelili1的博客-CSDN博客_tcp重排序
接收方接收窗口为0时,发送方停止发送。但是过了一段时间,接受方缓冲区中的数据处理完了,又发送了一个报文告诉发送方接收窗口有空间了,但是这个报文丢失了。接受方就一直等待发送方,发送方也在等接收方。如何解决呢?
TCP
引入了定时器
,当发送方收到接收方返回的0窗口大小应答后,开始计时,时间到了会发送一个探测报文,接收方返回一个探测应答,包含接收窗口大小。如果还为0,那么加倍定时时间,继续探测,知道定时时间超过60s。如果接收方没有返回探测应答,那么会断开连接。
TCP如何保证数据可靠传输?
序列号+确认应答、超时重传、流量控制、拥塞控制、校验和。
流量控制和拥塞控制都是发生于发送端,流量控制是作用于接收方,拥塞控制是作用于网络。
对字节流进行分段并编号,并通过确认回复和超时重传机制进行保证。
TCP层数据称为segment
报文段或段,每个报文段包含TCP协议头,该头部包含seq
字段,表示segment
的第一个字节在整个字节流的位置,还包含len
字段,表示该报文段的字节数。通过这两个字段可以推断出报文段在整个字节流中的位置,报文段符合要求,那么就回复ack
确认报文段,如果不符合就丢弃。如果发送方一段时间内没有收到ack
报文段,会重发丢失的报文段。
(1条消息) TCP/IP / 如何保证数据包传输的有序可靠?_布袋和尚-CSDN博客_tcp如何保证数据有序
流量控制
流量控制就是让发送放发送速率不要过快,让接受方来得及接受处理。利用滑动窗口机制,在应答报文中携带上接收窗口的大小,发送方根据这个字段和网络的拥塞程度来控制发送窗口的大小。
tcp传输的数据有用于控制的。这些报文的数据量小,但是头部大,tcp头部加ip头,有40字节。因此,出现了Nagle算法,规定一个tcp连接最多只能有一个未被确认的未完成的小分组。@todo nagle算法。
拥塞控制
与流量控制不同,流量控制是为了让接收方来得及处理数据。拥塞控制是作用于网络,为了防止过多的数据进入网络,增加网络的负载。
如果对网络中某个资源的需求超过了这个资源所能提供的可用部分,那么网络性能就会变坏,出现网络拥塞。针对socket接收方来说,就是接收缓冲区满了,出现丢包了。@todo 还要细化。
除了接收方接受缓冲区,还有带宽、交换节点的缓存等,都是网络资源。
发送方会维护一个cwnd
变量,动态变化,取决于网路的拥塞程度,只要没有出现拥塞,cwnd就增大,否则就减少。网络拥塞出现的依据
,就是发生重传,没有按时收到应当到达的确认报文。需要维护一个门限值 ssthresh 变量,通过这个变量来动态调整拥塞控制算法,以及cwnd。当 cwnd < ssthresh 时,采用慢开始,等于的时候,可以使用慢开始或者拥塞避免,大于时改用拥塞避免。
发放方发送窗口如何确定
发送方发送窗口取拥塞窗口和应答报文中接受窗口剩余大小的最小值。@todo 细化。
拥塞控制有4种算法,慢开始、拥塞避免、快重传、快恢复。
慢开始算法,初始化发送窗口和 cwnd 为1,当发送端收到多少确认报文,以报文个数为单位,就增到多少个大小。呈现一种指数增长的趋势。
拥塞避免,拥塞窗口只会线性加1,保持线性增长。
慢开始的窗口增长速度并不慢,但是初始化小,这样做的目的是要减少一开始向网络中注入的报文。拥塞避免并非可以完全避免,只是在发送速率较大的时候,尽量保持发送速率缓速增长,避免发送拥塞。
tcp tahoe 版本仅使用这两种算法,当发生超时重传时,断定为网络拥塞,将 ssthresh 设定为 cwnd 的一半,将 cwnd 减 1,并从新开始执行慢开始算法,也就是发送窗口又设置为1,并且会更早的达到门限,发送窗口增速变换。这样做会导致1个问题
。第一个是个别报文在网络中丢失,网络没有发生拥塞,这也会导致发送方超时重传,并误认为是网络拥塞,降低了传输速率。
快重传,应对了超时重传机制里面的快重传,解决了不必等待超时定时器,立即发送丢失的报文。快重传有3点要求,第一是要求接收方不能在发送自己的数据时才捎带确认,而是立即发送确认。第二是接收方收到了失序的报文,就要立即发出丢失报文的确认报文。第三是发送方一旦收到3个连续的重复确认,就要将相应的报文立即发出,而不是等待定时器到了再发。
快重传在收到3个连续的重复确认后,就执行快恢复算法
。快恢复算法的话,有的是发送方将 ssthresh 和 cwnd 调整为发送窗口的一半,再开始执行拥塞避免算法。而有的是将拥塞窗口 cwnd 增大,ssthresh + 3。
(2条消息) TCP的拥塞控制(详解)程序媛婷的博客-CSDN博客_tcp拥塞控制
UDP 协议
UDP报文段结构
UDP是基于数据包的传输协议,不会对应用层交付的报文进行合并、拆分。只会在数据包前加上8个字节
的UDP首部。
特点
- 自身没有保证可靠、有序的控制机制;
- 报文段额外开销少,只有8个字节;
- 没有流量控制、拥塞控制机制。
UDP与TCP的区别
主要有两方面的差别,报文结构方面来看,UDP
拥有更小的头部。从传输过程来看,UDP
协议是面向无连接的,也就是说传输数据没有建立连接的过程,不用建立连接就可以传输数据;UDP
协议只负责传输报文,不能保证有序可靠的数据传输;没有任何流量控制、拥塞控制算法,总的来说比TCP
更加轻便。
为什么不可靠
首先UDP
协议是无连接的,双方通信不需要建立连接,数据随时都能发送,这种情况肯定不稳定?
并且UDP
没有重传机制,丢包也不会管。而且没有流量控制、拥塞控制,在网络条件不好的时候丢包可能更严重。
高效的原因
UDP比TCP,主要有两方面的原因
,从报文结构
方面来看,UDP不会对应用层报文进行分割,只添加一个8字节
的头部,而TCP需要将应用层报文拆分为报文段,并且给每个报文段加上20字节
的头部;TCP
还需要对报文段的序号进行校验,确保报文段有序。从数据传输过程
来看,UDP不需要建立连接,没有丢包重传机制,这就少了两部分的时间开销,并且UDP没有TCP那样的限速控制
。
应用场景
UDP
适用于实时性要求高,连接数比较大的场景。
直播
如果直播业务用TCP
的话。为了保证数据传输的正确性,如果有数据没有收到的话,就会等待接收丢包的数据。在网络差的时候,画面卡住后会继续播放卡住前的老旧画面。那指定不行,用户想看的肯定是最新的画面。所以直播业务理论上用UDP
比较高效。
游戏
如果是玩家人数过多的PVP游戏,服务器的用户连接数大,如果使用TCP连接,那么会出现服务器不够用的请求,因为每台服务器可以支撑的TCP连接数有限。并且由于TCP重传机制,不能实时更新最新画面,那就没法接着玩下去了。
服务器支持的TCP连接上限是多少?为什么会有上限?(待补充…)
TCP
每个连接需要一个socket
,理论上,TCP
连接有65535个,实际上TCP
连接数受到机器资源、操作系统的限制,上限是10140个,而UDP
只需要socket
就可以创建多个连接。
这个问题太深了,最好不要扯到这上面。其实通过多路复用,可以端口复用。
(小林coding有讲)
还有一个理由,每个TCP如果占用4KB,那么内存为16G的服务器可以管理的TCP就是16x1024x1024/4个。
ICMP
RTT
以毫秒为单位,网络请求从发起到达目的地,再返回到起点所需的时间,是一个变化的值。ping 命令采用一种估算往返时间的方法,round trip time 等于 min/avg/max/stddev。stddev 是一个平均偏差值,值越大说明网速越不稳定。但是 ping 一般用于网络连通性测试,还有 tracert、telnet、nslookup 等命令。评估网络质量,除了时延、连通性,还有吞吐量、发包收包率、丢包率等等。