关于网络的知识的总结,我以前的文章也有总结过
现在希望对TCP/UPD/HTTP/HTTPS/跨域等知识做一个总结
计算机网络体系结构
计算机网络体系结构分为3种:OSI体系结构、TCP/IP体系结构、五层体系结构
TCP/IP 体系结构:含了一系列构成互联网基础的网络协议,是Internet的核心协议 &,被广泛应用于局域网和广域网
TCP协议
定义
Transmission Control Protocol
传输控制协议
- 属于传输层通信协议
- 基于
TCP
的应用层协议有HTTP
、SMTP
、FTP
、Telnet
和POP3
特点
- 面向连接:使用TCP传输数据前,必须先建立TCP连接;传输完成后再释放连接
- 全双工通信:建立TCP连接后,通信双方都能发送数据
- 通过TCP连接发送的数据:不丢失、无差错、不重复、按序到达
- 面向字节流:数据以流的形式进行传输
优缺点
- 优点:数据传输可靠
- 缺点:效率慢(因需建立连接、发送确认包等)
应用场景(对应应用的应用层协议)
要求通信数据可靠时,数据要准确无误地传递给对方时是用TCP协议 如:传输文件:HTTP、HTTPS、FTP等协议;传输邮件:POP、SMTP等协议
- 万维网:
HTTP
协议 - 文件传输:
FTP
协议 - 电子邮件:
SMTP
协议 - 远程终端接入:
TELNET
协议
报文格式
- TCP虽面向字节流,但传送的数据单元 = 报文段
- 报文段 = 首部 + 数据2部分
- TCP的全部功能体现再它首部中每个字段的作用,下面对报文的首部分析:首部前20个字符固定、后面有4n个字节是根据所需而增加的选项,故TCP首部最小长度是20字节
对应的字段的解释:
- 序号(报文段序号):本报文所发送的数据的第一个字节的序号(4字节)
- 确认号(ACK): 期望收到对方下一个报文字段的第一个数据字节的序号,四字节,若确认为N,则表明到序号N-1为止的所有数据都已正确收到
- SYN(同步位):连接建立时用于同步序号,SYN = 1 ,ACK = 0 ,表明这是一个连接请求文段,SYN = 1 ,ACK = 1 表明这是1个连接请求响应报文段
- FIN(终止控制位):释放连接,FIN =1时,表明此报文的发送方已发送数据完毕,要求释放连接
TCP 建立连接的过程
TCP需要同过三次握手建立连接
-
客户端向服务端发送连接请求,请求报文:客户端向服务端发送一个syn的数据包,这个时候客户端处于SYN_SENT的状态,等待服务器确认
报文信息:
- 同步标志位 设为1: SYN=1
- 随机选择以一个起始序号:seq = x
- 不携带数据
-
服务器收到请求连接报文段后,若同意建立连接,则向客户端发回连接确认的报文段。为该TCP连接分配TCP缓、变量,服务端进入同步已接收状态(SYN_RCVD)
- 同步标志位 设为1:SYN = 1
- 确认标记位 设为1:ACK = 1
- 随机选择一个起始序号:seq = y
- 确认号字段 设为:ack = x+1
- 不携带数据
-
客户端接收到确认报文后,向服务器再次发出连接确认报文段。为该TCP连接分配TCP缓、变量。客户端、服务端都进入已创建状态(ESTABLISHED),可以开始发送数据
- 确认标记位 设为1:ACK = 1
- 序号:seq = x +1
- 确认号字段 设为: ack = y + 1
- 可携带数据(因SYN无设为1;若不携带数据则不消耗序号)
注意:
- TCP提供的是全双工通信,故通信双方的应用进程在任何时候都能发送数据
- 三次握手期间,任何一次未收到对面的回复,则都会重发 为什么需要三次握手?
防止服务器因接收了早已失效的连接请求报文,从而一直等待客户端请求,最终导致形成死锁和资源浪费 分析:
在已失效的请求报文出现的情况,客户端发出的第1个连接请求报文字段无丢失,而是在某个网络节点长时间滞留了,导致延误到连接释放后的某个时间才到达服务器,这是一个早已失效的报文段,但服务器不知道,其收到此失效的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求,于是向客户端发出了确认报文段
如果没有三次握手,只要服务端发送了确认报文段就建立了连接,但由于客户端并无发出建立连接的请求,不会向服务端发送数据,但是服务端却认为新的TCP连接已建立,于是一直在等待客户端发送数据,造成了死锁状态 解决采用三次握手,对于上面的情况客户段不会向服务器的确认,服务器由于收不到客户端的确认信息,就知道客户端并没有要求建立TCP连接
释放连接的过程
- 在通信结束后,双方都可以释放连接,共需四次挥手
- 客户端发起中断连接请求,发送FIN报文,服务端接收到FIN报文(理解:客户端没有数据要发给你了,但是服务端还有数据没有发送完成的话不必着急关闭可以继续发送数据)
- 终止控制位 设为1:FIN = 1
- 报文序号 设为前端传送数据最后一个字节序号加1:seq = u
- 可携带数据 (FIN = 1 的报文即使不携带数据也携带1个序号)
- 服务端发送ACK告诉客户端:你的请求我收到了,但是我没有准备好,请继续等我的消息,这个时候客户端进入FIN_WAIT状态,继续等待Server端的FIN的报文
- 确认标记位 设为1:ACK = 1
- 报文序列号设为前端传送数据最后一个字节的序号加1:seq = v
- 确认号字段 设为: ack = u + 1
- 当server端确定数据已发送完成,则向客户端发送FIN报文,告诉客户端:服务端这边的数据发送完成,准备好关闭连接了
- 终止控制位 设为1 :FIN = 1
- 确认标记位 设为1 :ACK = 1
- 报文段序号 ; seq = w
- 重复上次已发送的确认号字段;设为: ack = u + 1
- 可携带数据 (FIN = 1的报文即使不携带数据也消耗一个序号)
- 客户端收到FIN报文后,但是他不相信网络,怕服务端不知道要关闭,所以发送ACK后进去TIME_WAIT状态,如果服务端没有收到ACK则可以重传, 服务端收到ACK后就知道可以断开连接了,客户端等待2MSL之后没有收到回复则说明服务端已正常关闭,客户端就可以关闭连接了
- 确认标记位 设为1: ACK = 1
- 报文段序号: seq = u + 1
- 确认号字段 设为: ack = w + 1
- 可携带数据 (FIN = 1的报文即使不携带数据也消耗一个序号)
为什么需要四次挥手?
为了保证通信双方都能通知对方 需释放断开连接
分析:
因为TCP是全双工双方都可以发送接受消息断开连接及时双方都无法接收发送消息
当主机1发出断开连接的请求时,主机2反回确认断开连接信息时,只表示主机1已无数据要发送到主机2,但是主机2还是可以发送数据给主机1,主机1还可以接收主机2的数据,这个情况就是单向断开TCP处于连接半关闭的状态
解决这半关闭的状态就需要进行四次挥手,当主机2也发送断开连接请求,主机1反回确认释放连接信息时吧表示主机2已经没有数据发送到主机1,当双方都没有办法通信的时候TCP才算真正的断开
为什么客户端关闭连接前要等待2MSL时间?
TIME - WAIT状态的作用是什么 MSL = 最长报文段寿命 原因:
-
为了保证客户端发送的最后一个连接释放去人报文能到达服务器,从而使得服务器能正常释放连接
分析:
客户端发送的最后一个连接释放确认报文可能会丢失,当服务器收不到最后一个连接释放确认报文时,则不会进入关闭状态,但会超时重发,连接释放报文
假设客户端不等2MSL时间就去直接关闭,当最后一个连接释放确认报文丢失,服务器重发连接释放报文,客户端则无法接收到服务器重新发送的连接释放报文,因此也不会发送;连接释放确认报文段,最终导致服务器无法进入关闭状态
-
防止早已失效的连接请求报文出现在本连接中
客户端发送最后一个连接释放请求释放报文后,再经过2MSL时间,则可使本连接持续时间内所产生的所有报文段都从网络中消失,即:在下一个新的连接中就不会出现早已失效的连接请求报文
UDP协议
User Datagram Protocol
,即 用户数据报协议
- 属于传输层协议
- 基于UDP的应用层协议有
TFTP
、SNMP
与DNS
特点
- 无连接 :使用UDP传输数据前,不需要建立UDP连接
- 不可靠: UDP的数据包发送后,不管其是否会到达接收方
- 面向报文: 数据以数据报文(包)的形式传输
- 无拥塞控制: 由于是不可靠的传输,即不管是否到达接收方,故不需阻塞控制
优缺点
- 优点: 速度快
- 缺点: 消息易消失,特别是在网络较差时
应用场景(对应应用层协议)
要求通信速度高
- 域名转换: DNS协议
- 文件传输: FTP协议
- 网络管理: SNMP协议
- 远程文件服务器: NFS协议
报文格式
共有2个字段:数据字段和首部字段
TCP 和 UDP对比
HTTP协议
属于应用层的超文本传输协议,规定了应用进程间通信的准则
特点:
- 传输效率高:
- 无连接: 交换HTTP报文前,不需要建立HTTP连接
- 无状态: 数据传输过程中不保存任何历史和状态信息,该特性简化了服务器的设计,使服务器更容易支持大量并发的HTTP请求
- 传输格式简单:请求是只需传送请求方法和路径
- 传输可靠性高:
- 采用TCP作为传输层协议
- TCP协议是面向连接的可靠性传输,交换报文时需要先建立TCP连接 工作方式采用请求/响应的工作方式
HTTP报文详解
- HTTP在应用层交互数据的方式 = 报文
- 报文分为请求报文和响应报文
请求报文
- 结构分为 请求行,请求头,请求体组成
- 请求行
- 作用: 声明请求方法,主机域名,资源路径,协议版本
- 结构: 组成 = 请求方法 + 请求路径 + 协议版本
- 注意: 结构组成中的空格不能少
结构介绍:
- 请求方法: 对请求对象的操作:GET POST DELET PUT
- 请求路径: URL中的请求地址部分,定义为统一的资源定位符
- 协议版本: 定义HTTP的版本号,HTTP1.0 HTTP2.0
补充: GET,POST 方法的区别
从技术上来说:
- GET请求能缓存,POST请求不能
- POST相对GET请求相对安全一点,因为GET请求都包含在URL里,且会被浏览器保存历史记录,POST不会,但在抓包的情况下都是一样的
- POST可以通过request body来传输比GET更多的数据
- URL有长度限制,会影响GET请求,但是这个长度限制是浏览器规定的,不是RFC规定的
- POST请求支持更多的编码类型且不对数据类型限制
从本质来说:
-
GET与POST都是TCP链接,但GET产生一个TCP数据包,POST产生两个TCP数据包
- 对于GET方法的请求,浏览器会把http header和data一并发出去,服务器响应200(返回数据)
- 对于POST,浏览器先发送header,服务器响应100,浏览器再发送data,服务器响应200ok(返回数据)
-
示例
设:请求报文采用
GET
方法、URL
地址 = www.tsinghua.edu.cn/chn/yxsz/in…;、HTTP1.1
版本
则 请求行是:GET /chn/yxsz/index.htm HTTP/1.1
- 请求头
- 作用: 声明客户端,服务器/报文的部分信息
- 使用方式:采用"header(字段名):value(值)"的方式
- 请求体
- 作用: 存放需发给服务器的数据信息
- 使用方式: 共3种
- 数据交换
- 键值对
- 分部分心性质 对比请求报文和响应报文
HTTP2.0
- 多路复用
相对HTTP1.0,大大的提升了web的性能在 在1.0中会造成对头阻塞会导致最大的请求数量, 当页面中需要请求很多资源的时候,队头阻塞会导致在达到最大的请求数量的时候,剩余资源需要等其他的资源请求完成后才能发起请求,在2.0中引入了多路复用
在2.0中有帧(代表最小的数据单位,每个帧会标识出该帧属于哪个流)和流(多个帧组成的数据流) 多路复用就是在一个TCP连接中可以存在多条流,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求,通过这个技术可以避免HTTP1.0中队头阻塞的问题,极大的提高传输性能
-
二进制传输 HTTP2.0中所有加强的性能核心点在二进制传输,在此之前的HTTP版本中,都是通过文本的方式传输数据,在HTTP2.0中引入新的编码机制,所有的传输数据都会被分割,并采用二进制格式编码
-
Header压缩 在1.0中,我们使用文本的形式传输header,在header携带cookie的情况下,可能每次都需要好重复传输几百到几千的字节 在2.0中,使用了HPACK压缩格式对传输的header进行编码,减少了header大小,并在两维护了索引表,用于记录出现过的header,后面再传输过程中就可以传输已经记录过的header,,对端收到数据后就可以通过键名找到对应的值
-
服务端Push 在HTTP2.0中,服务端可以在客户端某个请求后,主动推送其他资源,某些资源客户端是一定会请求的,这时就可以采用服务端push技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间,当然在浏览器兼容的情况下可以使用prefetch
HTTPS
HTTPS在HTTP上建立SSL加密层,并对传输数据进行加密,是HTTP协议的安全版本,现在被广泛用于万维网上安全敏感的通讯容易交易支付方向的 主要作用:
- 对数据进行加密,并建立一个想你想安全通道,来保证数据传输过程中的数据安全
- 对网站服务器进行真实身份认证 对比:
- HTTPS是超文本传输协议,信息是明文传输,存在安全风险的问题,https则解决HTTP不安去哪的缺陷,在TCP和HTTP网络层之间加入了SSL/TLS安全协议,使得报文可以加密传输
- HTTP连接建立相对简单,TCP三次握手后便可进行HTTP的报文传输,而HTTPS在TCP三次握手之后要进行SSL/TLS的握手过程,才可以进入加密报文传输
HTTP是如何解决上面的三个风险的
- 混合加密的方式实现信息的机密性,解决了窃听的风险
- 摘要算法的方式来实现完整性,它能够为数据生成独一无二的标识,标识用于校验数据的完整性,解决了篡改的风险
- 将服务器公钥放入到数字证书中,解决了冒充的风险
混合加密
HTTPS采用的是堆成加密和非堆成加密结合的混合加密的方式
- 在通信建立前采用非对称的加密方式交换会话密钥,后续就不再使用非对称加密
- 在通信过程中全部使用对称加密的会话密钥的方式加密明文数据
采用混合加密的原因:
- 对称加密只使用过一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换
- 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢
摘要算法
摘要算用来实现完整性,能够为数据生成独一无二的指纹,用于校验数据的完整性,解决了篡改的风险
客户端在发送明文之前会通过摘要算法算出明文的指纹,发送出来的时候把指纹+明文一起加密成密文后发送给服务器,服务器解密后,用相同的摘要算法算出发送过来的明文,通过比较客户端携带的指纹和当前算出的指纹作比较,若指纹相同,说明数据是完整的
数字证书
客户端先向服务端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己私钥解密,但是如何保证公钥不被篡改和信任度,这就需要借助第三方权威机构CA(数字证书认证机构),将服务器公钥放在数字证书(由数字证书认证机构颁发)中,只要数字证书是可信的,公钥就是可信
加密过程
大致可以理解为分为六个阶段
- 客户端申请https通信
- 服务器响应并向客户端传递证书
- 客户端验证证书,获取公钥,生成对称加密密钥,用公钥加密后传给服务器
- 服务器收到消息,用私钥解密,拿出对称密钥,并通知客户端,SSL通道建立完成后,HTTPS通信也就建立完成
- 共享密钥交换成功,https通信建立后,客户端和服务器利用共享密钥加密通信
- 客户端断开连接
浏览器跨域
因为浏览器处于安全考虑,浏览器有同源策略,如果协议,域名,端口有一不同就是跨域了,Ajax请求就会失败
解决跨域的方法:
1.CORS
跨域资源共享 cors需要浏览器和后端同时支持,浏览器会自动进CORS通信,实现cors通信的关键是后端,只要后端实现了cors就实现了跨域
服务端设置Access-Control-Allow-Origin 就可以开启CORS,该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源
浏览器会将CORS
请求分成两类,简单请求和非简单请求,浏览器对这两种的请求的处理是不会一样的
简单请求
- 请求的方法是HEAD POST GET 三种方法之一
- HTTP的头信息不超过以下几种字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type(只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
如果同时满足这两个条件就是一个简单请求,
对一个简单请求来说,浏览器会发出CORS请求,就是在这个请求的头信息中,自动添加一个Origin
字段,来说明本次请求的来源(协议+域名+端口),而后端服务器会根据这个值来决定是否同意这次请求,如果同意,返回的响应会多出以下响应头信息
Access-Control-Allow-Origin: http://juejin.com // 和 Orign 一致 这个字段是必须的
Access-Control-Allow-Credentials: true // 表示是否允许发送 Cookie 这个字段是可选的
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值 这个字段是可选的
Content-Type: text/html; charset=utf-8 // 表示文档类型
在简单请求中服务器至少需要设置:Access-Contol-Allow-Origin
字段
非简单请求
比如PUT或者DELETE请求,或者Content-Type为application/json,就是非简单请求
非简单CORS请求,正式请求前会发送一次OPTIONS类型的查询请求,称为预检请求
,询问服务器是否支持网页所在域名的请求,以及可以使用哪些头信息字段,只有收到肯定的答复,才会发起XMLHttpRequest
请求,否则报错
预检请求的方法是OPTIONS,它的头信息中有几个字段
- Origin: 表示请求来自哪个域(必须)
- Access-Control-Requset-Method: 列出CORS请求会用到哪些HTTP方法(必须)
- Access-Control-Request-Headers: 指定CORS请求会额外发送的头信息字段,用逗号隔开 OPTIONS请求次数过多也会损耗性能,所以要尽量减少OPTIONS请求,就可以让服务器在请求头部添加
Access-Control-Max-Age: Number // 数字 单位是秒
表示预检请求的返回结果可以被缓存多久,在这个时间范围内再请求就不需要预检了,但是这个缓存只对一样的URL才会生效
服务端CORS跨域配置
在后端语言中不同的语言肯定配置项语法伤可能有差异,但是内容是一样的 配置允许跨域的来源
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: http://juejin.com
CORS
跨域请求中,最关键的就是Access-Control-Allow-Origin
字段,是必须项,*
指所有的来源,浏览器将不会发送Cookie
,
配置允许跨域请求的方法
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT...
该字段也是必须项,值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法 配置允许的请求头字段
Access-Control-Allow-Headers: x-requested-with,content-type...
如果在请求头中有自定义的请求头字段,那么此项也是必须的,他是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器字啊预检中的请求字段 配置是否允许发送Cookie
Access-Control-Allow-Credentials: true
该字段可选,它的值是一个布尔值,表示是否允许发送Cookie,默认情况下,Cookie不包括在CORS请求之中 设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器 该字段只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可 配置本次预检请求的有效期
Access-Control-Max-Age: 1728000
该字段可选,用来指定本次预检请求的有效期,单位为秒,上面结果中,有效期是20天(1728000秒),即允许缓存该条回应20天,在此期间如果你再次发出了这个接口请求,就不用发预检请求了,节省服务端资源
2.document.domain
该方式只能由于二级域名相同的情况下,比如a.test.com和b.test.com 适用于该方式,只需要给页面添加document.domain = 'test.com'表示二级域名都相同就可以实现跨域
3.JSONP
JSON就是利用<script>
标签没有跨域限制的漏洞,通过``标签指向一个需要访问的地址并提供一个回调函数来接收数据,
<script src="http://domain/api?param1=a">
</script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
JSONP使用简单且兼容性不错,但是只限于get请求
4.postMessage
这种方式可通常用于获取嵌入页面中的第三方页面数据,一个页面发送消息,另一个页面判断来源并接收消息
// 发送消息
window.parent.postMessage('message', 'http://test.com');
// 接收消息端
cosnt mc = new MessageChannel()
mc.addEventListener('message', (event)=>{
const arigin = event.origin || event.originalEvent.origin
if(origin === 'http://test.com') {
console.log('验证通过')
}
})
5.Node中间件代理
原理: 同源策略是罗拉你去需要遵循的标准,而如果是服务器向服务器请求就无需遵守同源策略 所以我们需求启动一个代理服务器,这个代理服需要做以下工作
- 接收客户端请求
- 将请求转发给服务器
- 拿到服务器响应数据
- 将响应转发给客户端
cli工具中的代理
- webpack
在webpack中可以设置
proxy
来快速获得接口代理能力
- Vue-cli 使用一:
module.export = {
// ...
devServer: {
proxy: {
'/api': 'http//www.hahaha.com'
}
}
}
如上所示,当你请求/api/abc
接口时就会被代理到http://www.hahaha.com/api/abc
使用二:
当需要将多个路径代理到同一个target
下,那就可以使用
module.export = {
deServer: {
proxy:[{
context:['/api1','/api2','/api3'],
target:'http://.hahaha.com',
}]
}
}
使用三:
在使用第一种方式代理时,代理了/api
,最终的代理结果是http://www.hahaha.com/api/abc
,但是有时我们并不想代理时传递/api
,那就可以通过pathRewrite
属性来进行路径重写
module.export = {
deServer: {
proxy:{
'/api': {
target:'http//www.hahaha.com',
pathRewrite:{'^/api':''}
}
}
}
}
这个时候/api/abc
接口就会被代理到http://www.hahaha.com/abc
使用四:
默认情况下,代理一般是不接受运行在HTTPS
上,且使用了无效证书的后端服务器,如果想要接受,需要设置secure: fasle
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'https://www.hahaha.com',
secure: false
}
}
}
}
使用五:
配置一个字段changeOrigin
,当它为true
时,本地就会虚拟一个服务器接受你的请求并且代理发送的请求,如果要代理跨域,这个字段就是必选项
module.exports = {
// ...
devServer: {
proxy: {
"/api": {
target: 'http://www.hahaha.com',
changeOrigin: true,
}
}
}
}
使用六: 配置多个不同的代理
module.exports = {
// ...
devServer: {
proxy: {
"/api": {
target: 'http://www.hahaha.com',
changeOrigin: true,
},
"/api2":{
target: 'http://hahaha2.com',
changeOrigin: true,
pathRewrite: {'^/api3' : ''}
}
}
}
}
在本地配置的代理跨域,知识解决开发是的跨域问题,当项目上线时,如果存在跨域的情况需要后端配置跨域
6.Nginx反向代理
Nginx是通过反向代理的方式,这里需要自定义一个域名为了能够保证我当前域能获取到静态资源和接口,
通过Nginx
配置一个代理服务器,反向代理访问跨域接口,并且可以修改Cookie
中的domain
信息,方便当前域Cookie
写入,
Nginx
的使用就是进行配置,举例: 页面a在http://www.hahaha.com
域下,接口在http://hahaha1.com:9999
域下
那么我们就可以通过 Nginx 配置一个代理服务器,域名和页面 a 相同,都是 www.hahaha.com ,用它来充当一个跳板的角色,反向代理访问 www.hahaha1.com 接口
# Nginx代理服务器
server {
listen 80;
server_name www.hahaha.com;
location / {
# 反向代理地址
proxy_pass http://www.hahaha1.com:9999;
# 修改Cookie中域名
proxy_cookie_domain www.hahaha1.com www.hahaha.com;
index index.html index.htm;
# 前端跨域携带了Cookie,所以Allow-Origin配置不可为*
add_header Access-Control-Allow-Origin http://www.hahaha.com;
add_header Access-Control-Allow-Credentials true;
}
}
参考文章: