Linux查看网络状态
netstat 用于显示各种网络相关信息(网络连接,路由表,接口状态,无效连接,组播成员)
// 查看网络链接状况
netstat -tlunp
Recv-Q(网络接收队列):表示收到的数据已经在本地接收缓冲,但是还有多少没有被进程取走,recv()
Send-Q(网络发送队列):对方没有收到的数据或者说没有Ack的,还是本地缓冲区.
通过netstat的这两个值就可以简单判断程序收不到包到底是包没到还是包没有被进程recv。
这两个值通常应该为0,如果不为0可能是有问题的。packets在两个队列里都不应该有堆积状态。可接受短暂的非0情况。如文中的示例,短暂的Send-Q队列发送pakets非0是正常状态。
如果接收队列 Recv-Q 一直处于阻塞状态,可能是遭受了拒绝 服务 denial-of-service 攻击。
如果发送队列 Send-Q 不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快。
-a: 列出系统中所有网络连接,包括已经连接的网络服务、监听的网络服务和Socket套接字
-t: 列出TCP数据
-u: 列出UDP数据
-l: 列出正在监听的网络服务(不包含已经连接的网路服务)
-n: 用端口显示服务,而不用服务名
-p: 列出该服务的进程ID(PID)
tcpdump
tcpdump只能抓取流经本机的数据包。
lsof (列出被进程所打开的文件的信息)
- 列出谁在使用某个特定的udp端口 lsof -i udp:55
- 通过某个进程号显示该进行打开的文件 lsof -p 1
ss (获取socket统计信息)
可以显示和netstat类似的内容。但是ss的优势在于它能够显示更详细的有关网络连接的状态信息,而比netstat更快速、更高效。
查看当前服务器的网络连接数
ss -s
查看所有打开的网络端口
ss -l
ss -ta #查看TCP socket
TCP/IP
TCP/IP 概念层模型
应用层 (FTP、HTTP)以某种统一规定的协议格式解读数据。
应用层按既定协议打包数据,由传输层加上双方的端口,由网络层加上双方IP地址,由链路层加上双方MAC地址【数据拆成数据帧,经过多个路由器+网关后】。
传输层:提供端对端的接口(TCP、UDP)
- 网络层【IP】:根据IP定义网络地址。子网内根据地址解析协议ARP进行MAC寻址
- IP地址作用:在WLAN内进行路由寻址,选择最佳路由。
- IP报头中包含TTL(生存时间)数据包在传输过程中没经过一个路由器则减1,当其为0时,发送ICMP报文通知源主机,防止源主机无休止发送报文。ICMP就是检测网络通畅的协议,ping、tracert基于此。
传输层【端口】:定义逻辑端口(UDP、TCP)
- 数据链路层【MAC】: 以字节为单位把0与1进行分组,定义数据帧。写入源和目标机器的物理地址、数据、校验位来传输数据。传输有地址的数据帧+错误检测(ARP)
首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层。
其中以太网(Ethernet)的数据帧在链路层
IP包在网络层
TCP或UDP包在传输层
TCP或UDP中的数据(Data)在应用层
它们的关系是 数据帧{IP包{TCP或UDP包{Data}}}
ICMP协议
ICMP就是检测网络通畅的协议,ping、tracert基于此。
传输层【端口】:定义逻辑端口(UDP、TCP)
应用层:以某种统一规定的协议格式解读数据。
应用层按既定协议打包数据,由传输层加上双方的端口,由网络层加上双方IP地址,由链路层加上双方MAC地址【数据拆成数据帧,经过多个路由器+网关后】。
ping原理 - ICMP协议 【大部分服务器都会屏蔽ICMP,ping可以用来诊断局域网网络情况】
ICMP(Internet Control Message Protocol):IP通信中,某个IP包未能到达指定地址,原因将由ICMP负责通知。
ICMP报文封装在IP包里面,工作在网络层。ICMP类型字段大体分为两类:
- 查询报文类型(用于诊断的查询消息) 回送消息
- 差错报文类型(通知出错原因的错误消息)网络、主机、协议、端口不可达/需要分片但设置了不分片
ICMP目标不可达类型代码号:
0 网络不可达
1 主机不可达
2 协议不可达
3 端口不可达
4 需要进行分片但设置不了分片位
- ping命令执行后,源主机会构建一个ICMP回送请求消息数据包【类型+序号】。
- ICMP协议将数据包连同地址一起交给IP层,IP层将协议字段设置为1表示ICMP协议。
Traceroute 差错报文类型
故意设置特殊的TTL,来追踪来往目的地时沿途经过的路由器。【利用IP包的生存期限,从1开始按照顺序递增的同时发送UDP包,强制接受ICMP超时消息(有的路由器不会返回这个ICMP,有些公网地址看不到经过的路由)】它在发送UDP包时,填入一个不存在的端口( 端口不可达),返回ICMP消息。
故意设置不分片,为了路径MTU发现。发送端主机每次收到ICMP消息时就减少包的大小,定位一个合适的MTU值。
TCP概述
- TCP协议是面向连接的传输层协议
- 提供可靠交付
- 使用全双工通信
- 面向字节流
TCP数据报
-「窗口」:窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化着 窗口指的是发送本报文段的一方的接收窗口(而不是自己的发送窗口)。
-「确认ACK」:仅当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。
TCP重传机制
TCP 实现可靠传输的方式之一,是通过序列号(SYN)与确认应答(ACK)
- 超时重传
超时重传时间 RTO 的值应该略大于报文往返 RTT 的值。如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍
。两次超时,就说明网络环境差,不宜频繁反复发送。 - 快速重传
它不以时间为驱动,而是以数据驱动重传。快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。
- SACK (选择性确认)
在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据
。 - D-SACK
使用了 SACK 来告诉「发送方」有哪些数据被重复接收
了。
滑动窗口
tcp使用滑动窗口做流量控制
与乱序重排
。tcp传输可靠性来源于确认重传机制,tcp滑动窗口的可靠性也是建立在确认重传机制上的。
接收端会给发送端一个负反馈,通过这个负反馈可以控制发送端的滑动窗口的大小。
流量控制
TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。
拥塞控制
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大….拥塞控制的目的就是避免「发送方」的数据填满整个网络。
拥塞窗口 cwnd是发送方维护的一个 的状态变量,它会根据网络的拥塞程度动态变化的。
- 慢启动
一点一点的提高发送数据包的数量。当发送方每收到一个 ACK,就拥塞窗口 cwnd 的大小就会加 1。
当达到慢启动门限
时,切换拥塞避免
慢启动值得就是一条TCP链接刚建立时不要一下发送大量数据导致网络拥塞激增,而是由小到大根据反馈逐渐增大拥塞窗口。
- 拥塞避免
拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长。每当收到一个 ACK 时,cwnd 增加 1/cwnd。
就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。当触发了重传机制
,也就进入了「拥塞发生算法」。
拥塞避免就是让滑动窗口缓慢增大,而不是像慢开始那样成倍增长。
- 拥塞发生
当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
- 超时重传 (ssthresh慢启动门限 设为 cwnd/2,cwnd 重置为 1) 后,就重新开始慢启动,慢启动是会突然减少数据流的。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。
- 快重传(cwnd = cwnd/2 ,也就是设置为原来的一半;ssthresh慢启动门限 = cwnd;) 进入快速恢复算法
发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器到期。
- 快恢复
- 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了)
- 重传丢失的数据包
- 如果再收到重复的 ACK,那么 cwnd 增加 1
- 如果收到新数据的 ACK 后,设置 cwnd 为 ssthresh,接着就进入了拥塞避免算法
当发送方连续收到三个重复确认时,就执行 “乘法减小” 算法,把慢开始门限减半。这是为了预防网络发生拥塞。注意,接下去不执行慢开始算法。
执行快恢复算法时,改变滑动窗口的值,然后开始执行拥塞避免算法,使得拥塞窗口缓慢性增大。
三次握手
TCP第三次握手失败 (不会重传ack报文)
当失败时服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN洪泛攻击。
那么如果 第三次握手中的ACK包丢失的情况下,Client 向 server端发送数据,Server端将以 RST包响应,方能感知到Server的错误。
syn洪泛攻击:当第三次握手没有发送确认信息时,等待一段时间后,主机就会断开之前的半开连接并回收资源,这为dos攻击埋下隐患,当主动方主动发送大量的syn数据包,但并不做出第三次握手响应,server就会为这些syn包分配资源(但并未使用),就会使server占用大量内存,使server连接环境耗尽,这就是syn洪泛攻击
优化三次握手
- 客户端的优化
当客户端发起 SYN 包时,可以通过 tcp_syn_retries 控制其重传的次数。 - 服务端的优化
- 当服务端 SYN 半连接队列溢出后,会导致后续连接被丢弃。(调整 SYN 半连接队列的大小)
- 如果遭受 SYN 攻击,,应把 tcp_syncookies 参数设置为 1,表示仅在 SYN 队列满后开启
syncookie
功能,可以保证正常的连接成功建立。 - 服务端收到客户端返回的 ACK,会把连接移入
accpet 队列
,等待进行调用 accpet() 函数取出连接。如果 accept 队列溢出,系统默认丢弃 ACK,如果可以把 tcp_abort_on_overflow 设置为 1 ,表示用 RST 通知客户端连接建立失败。 TCP Fast Open 功能可以绕过三次握手
,使得 HTTP 请求减少了 1 个 RTT 的时间,Linux 下可以通过 tcp_fastopen 开启该功能,同时必须保证服务端和客户端同时支持。(在客户端首次建立连接时的过程,SYN 报文包含 Fast Open 选项正常三次握手。客户端再次向服务器建立连接,如果服务器接受了 SYN 报文中的「数据」,服务器可在握手完成之前发送「数据」,这就减少了握手带来的 1 个 RTT 的时间消耗
)
如何防御 SYN 攻击?
- 增大半连接队列;
- 开启 tcp_syncookies 功能
- 减少 SYN+ACK 重传次数
优化四次挥手
主动关闭连接的,才有 TIME_WAIT 状态。
- 主动方的优化
- 主动发起 FIN 报文断开连接的一方,如果迟迟没收到对方的 ACK 回复,则会重传 FIN 报文,重传的次数由 tcp_orphan_retries 参数决定。
- 当主动方收到 ACK 报文后,连接就进入 FIN_WAIT2 状态,根据关闭的方式不同,优化的方式也不同:
如果这是 close 函数关闭的连接,那么它就是孤儿连接。如果 tcp_fin_timeout 秒内没有收到对方的 FIN 报文,连接就直接关闭。同时,为了应对孤儿连接占用太多的资源,tcp_max_orphans 定义了最大孤儿连接的数量,超过时连接就会直接释放。
反之是 shutdown 函数关闭的连接,则不受此参数限制; - 当主动方接收到 FIN 报文,并返回 ACK 后,主动方的连接进入 TIME_WAIT 状态。这一状态会持续 1 分钟,为了防止 TIME_WAIT 状态占用太多的资源,tcp_max_tw_buckets 定义了最大数量,超过时连接也会直接释放。
- 当 TIME_WAIT 状态过多时,还可以通过设置 tcp_tw_reuse 和 tcp_timestamps 为 1 ,将 TIME_WAIT 状态的端口复用于作为客户端的新连接,注意该参数只适用于客户端。
- 被动方的优化
被动关闭的连接方应对非常简单,它在回复 ACK 后就进入了CLOSE_WAIT
状态,等待进程调用 close 函数
关闭连接。因此,出现大量 CLOSE_WAIT 状态的连接时,应当从应用程序中找问题
。
当被动方发送 FIN 报文后,连接就进入LAST_ACK
状态,在未等到 ACK 时,会在 tcp_orphan_retries 参数的控制下重发 FIN 报文。
TCP 优化数据传输
TCP 报文发出去后,并不会立马从内存中删除,因为未收到ACK重传时还需要用到它。所以并行批量发送报文,再批量确认报文即刻。
但我们还得考虑接收方的处理能力。TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是滑动窗口的由来。
接收方会把当前可接收的大小放在 TCP 报文头部中的窗口字段,这样就可以起到窗口大小通知的作用。
网络时延(RTT)
由于发送缓冲区大小决定了发送窗口的上限,而发送窗口又决定了「已发送未确认」的飞行报文的上限。因此,发送缓冲区不能超过「带宽时延积」
。
在高并发
服务器中,为了兼顾网速与大量的并发连接,我们应当保证缓冲区的动态调整的最大值达到带宽时延积
,而最小值保持默认的 4K 不变
即可。而对于内存紧张的服务而言,调低默认值是提高并发的有效手段。
同时,如果这是网络 IO 型服务器,那么,调大 tcp_mem 的上限
可以让 TCP 连接使用更多的系统内存,这有利于提升并发能力。
TCP粘包,拆包
TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议。那么UDP是否会发生粘包或拆包的现象呢?答案是不会。UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。而TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。
粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界
在哪。
粘包、拆包发生原因
- 滑动窗口
粘包:假设发送方的每256 bytes表示一个完整的报文,接收方由于数据处理不及时,这256个字节的数据都会被缓存到SO_RCVBUF(接收缓存区)中。如果接收方的SO_RCVBUF中缓存了多个报文,那么对于接收方而言,这就是粘包。
拆包:考虑另外一种情况,假设接收方的窗口只剩了128,意味着发送方最多还可以发送128字节,而由于发送方的数据大小是256字节,因此只能发送前128字节,等到接收方ack后,才能发送剩余字节。这就造成了拆包。
- MSS/MTU限制
MSS: 是Maximum Segement Size缩写,表示TCP报文中data部分的最大长度,是TCP协议在OSI五层网络模型中传输层对一次可以发送的最大数据的限制。
MTU: 最大传输单元是Maxitum Transmission Unit的简写,是OSI五层网络模型中链路层(datalink layer)对一次可以发送的最大数据的限制。
MSS长度=MTU长度-IP Header-TCP Header
MSS表示的一次可以发送的DATA的最大长度,而不是DATA的真实长度。发送方发送数据时,当SO_SNDBUF中的数据量大于MSS时,操作系统会将数据进行拆分,使得每一部分都小于MSS,这就是拆包,然后每一部分都加上TCP Header,构成多个完整的TCP报文进行发送,当然经过网络层和数据链路层的时候,还会分别加上相应的内容。
- Nagle算法
为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
粘包、拆包解决办法
- 发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。
- 包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。
包头加上包体长度
。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。
TCP 、UDP、IP包的最大长度
- TCP
整个包的最大长度是由最大传输大小(MSS,Maxitum Segment Size)决定,MSS就是TCP数据包每次能够传 输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需 要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。通讯双方会根据双方提供的MSS值得最小值 确定为这次连接的最大MSS值。 - UDP:64K
因为udp包头有2个byte用于记录包体长度. 2个byte可表示最大值为: 2^16-1=64K-1=65535
udp包头占8字节, ip包头占20字节, 65535-28 = 65507 - IP
IP包的大小由MTU决定(IP数据包长度就是MTU-28(包头长度)。 MTU值越大,封包就越大,理论上可增加传送速率,但 MTU值又不能设得太大,因为封包太大,传送时出现错误的机会大增。一般默认的设置,PPPoE连接的最高MTU值是1492, 而以太网 (Ethernet)的最高MTU值则是1500,而在Internet上,默认的MTU大小是576字节。
ip报头最小长度20字节,icmp报头最小长度8字节,而1472是指icmp报文中的数据长度。
最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。
HTTP + HTTPS
HTTP 是一个计算机世界里专门在【两点】之间【传输】文字、图片、音频等【超文本】数据的【约定和规范】。(简单、灵活和易于扩展、应用广泛和跨平台)
HTTP 是一种超文本 传输 协议,HTTPS 也就是在 HTTP 与 TCP 层之间增加了 SSL安全套接字层/TLS传输层安全
(对称加密算法)。
Http协议中的Header与Body
Header的每行最后要加\r\n
Header与Body之间要用\r\n隔开
- http工作在应用层(OSI第七层),下层可以随意变换。HTTPS就是在HTTP与TCP层之间增加了SSL/TLS安全传输层。
- http1.1默认keep-alive,使得管道(pipeline)网络传输成为可能。管道机制允许浏览器同时发出A请求+B请求。服务器会按顺序回应。但是【请求-应答】模式加剧了HTTP性能问题,【队头阻塞】会导致客户端请求不到数据。HTTP2/3优化了性能问题。
- https混合加密(机密性) + 摘要算法(完整性)+数字证书(冒充问题)
http常见字段
- Host 指定服务器的域名
- Content-Length 本次回应的数据长度
- Connection http1.1默认keep-alive 复用TCP链接
- Content-Type 服务器回应时,展示数据是什么格式的
- Content-Encoding 服务器返回的数据使用了什么压缩格式
SSL/TLS 的握手阶段
- 在进行通信前,首先会进行 HTTP 的三次握手,握手完成后,再进行 TLS 的握手过程。
SSL/TLS 协议基本流程:
- 客户端向服务器索要并验证服务器的公钥。
- 双方协商生产「会话秘钥」。
- 双方采用「会话秘钥」进行加密通信。
「握手阶段」涉及四次通信
1. ClientHello
客户端向服务器发起加密通信请求。
(1)客户端支持的 SSL/TLS 协议版本,如 TLS 1.2 版本。
(2)客户端生产的随机数(Client Random),后面用于生产「会话秘钥」。
(3)客户端支持的密码套件列表,如 RSA 加密算法。
2. SeverHello
服务器收到客户端请求后,向客户端发出响应。
(1)确认 SSL/ TLS 协议版本,如果浏览器不支持,则关闭加密通信。
(2)服务器生产的随机数(Server Random),后面用于生产「会话秘钥」。
(3)确认的密码套件列表,如 RSA 加密算法。
(4)服务器的数字证书。
3. 客户端回应
首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:
(1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。
(2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
(3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。
上面第一项的随机数是整个握手阶段的第三个随机数,这样服务器和客户端就同时有三个随机数,接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」。
4. 服务器的最后回应
服务器收到客户端的第三个随机数
(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。
(1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
至此,整个 SSL/TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。
认证服务器:
客户端发起 HTTPS 请求,服务端返回证书,客户端对证书进行验证。
协商会话密钥:
1.验证通过后本地生成客户端RSA公私钥与它的会话密钥,通过证书中的公钥对随机数进行加密传输到服务端
2.服务端接收后通过私钥解密得到公私钥与会话密钥,并且生成服务器会话密钥
加密通讯:
此时客户端服务器双方都有了本次通讯的会话密钥,之后传输的所有Http数据,都通过会话密钥加密(对称加密算法)。
这样网路上的其它用户,将很难窃取和篡改客户端和服务端之间传输的数据,从而保证了数据的私密性和完整性。(HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段
。)
HTTPS 解决了 HTTP 的哪些问题?
- 窃听风险,比如通信链路上可以获取通信内容,用户号容易没。(
混合加密
的方式实现信息的机密性,解决了窃听的风险) - 篡改风险,比如强制入垃圾广告,视觉污染,用户眼容易瞎。(
摘要算法
的方式来实现完整性
,它能够为数据生成独一无二的「指纹」,指纹用于校验数据的完整性,解决了篡改的风险。) - 冒充风险,比如冒充淘宝网站,用户钱容易没。(将
服务器公钥放入到数字证书
中,解决了冒充的风险。)
- 混合加密 (信息的机密性)
HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式:
- 在通信建立前采用
非对称加密
的方式交换「会话秘钥」,后续就不再使用非对称加密。 - 在通信过程中全部使用
对称加密
的「会话秘钥」的方式加密明文数据。
采用「混合加密」的方式的原因: - 对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。
- 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。
- 摘要算法 (完整性,解决篡改)
客户端在发送明文之前会通过摘要算法算出明文的「指纹」,发送的时候把「指纹 + 明文」一同
加密成密文后,发送给服务器,服务器解密后,用相同的摘要算法算出发送过来的明文,通过比较客户端携带的「指纹」和当前算出的「指纹」做比较,若「指纹」相同,说明数据是完整的。 - 数字证书 (解决公钥冒充,防止中间人)
将服务器公钥放在数字证书
(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的。
HTTP/2 做了什么优化
- 头部压缩
如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复
的部分。 - 二进制格式
头信息和数据体都是二进制
,并且统称为帧(frame):头信息帧和数据帧。 - 数据流
HTTP/2 的数据包不是按顺序发送
的,同一个连接里面连续的数据包,可能属于不同的回应。每个请求或回应的所有数据包,称为一个数据流。 - 多路复用
HTTP/2 是可以在一个连接中并发多个请求或回应,而不用按照顺序一一对应。
移除了 HTTP/1.1 中的串行请求,不需要排队等待,也就不会再出现「队头阻塞」问题,降低了延迟,大幅度提高了连接的利用率。 - 服务器推送
服务不再是被动地响应,也可以主动向客户端发送消息
。在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS 文件等静态资源主动发给客户端,减少延时的等待。
HTTP/3 (基于UDP协议的QUIC协议)
HTTP3.0又称为HTTP Over QUIC,其弃用TCP协议,改为使用基于UDP协议的QUIC协议来实现。
QUIC协议的核心思想是将TCP协议在内核实现的诸如可靠传输、流量控制、拥塞控制等功能转移到用户态来实现,同时在加密传输方向的尝试也推动了TLS1.3的发展。
解决的问题
- 队头阻塞
一个数据包影响了一堆数据包,它不来大家都走不了。
队头阻塞问题可能存在于HTTP层和TCP层,在HTTP1.x时两个层次都存在该问题。
HTTP2.0协议的多路复用机制解决了HTTP层的队头阻塞问题,但是在TCP层仍然存在队头阻塞问题。
TCP协议在收到数据包之后,这部分数据可能是乱序到达的,但是TCP必须将所有数据收集排序整合后给上层使用,如果其中某个包丢失了,就必须等待重传,从而出现某个丢包数据阻塞整个连接的数据使用。
QUIC协议是基于UDP协议实现的,在一条链接上可以有多个流,流与流之间是互不影响的,当一个流出现丢包影响范围非常小,从而解决队头阻塞问题。
- 0 RTT 建链
RTT(数据包一来一回的时间消耗)包括三部分:往返传播时延、网络设备内排队时延、应用程序数据处理时延。
一般来说HTTPS协议要建立完整链接包括:TCP握手和TLS握手,总计需要至少2-3个RTT,普通的HTTP协议也需要至少1个RTT才可以完成握手。
QUIC协议可以实现在第一个包就可以包含有效的应用数据。
首次连接时需要1 RTT,使用DH算法完成客户端和服务端的密钥协商和数据传输过程。
非首次连接,客户端会将config存储下来,后续再连接时可以直接使用,从而跳过这个1RTT,实现0RTT的业务数据交互。
- 连接迁移
TCP协议使用五元组来表示一条唯一的连接,当我们从4G环境切换到wifi环境时,手机的IP地址就会发生变化,这时必须创建新的TCP连接才能继续传输数据。
QUIC协议基于UDP实现摒弃了五元组的概念,使用64位的随机数作为连接的ID,并使用该ID表示连接。
基于QUIC协议之下,我们在日常wifi和4G切换时,或者不同基站之间切换都不会重连,从而提高业务层的体验。
地址栏输入 URL 发生了什么
- 浏览器会根据你输入的 URL 地址,去查找域名是否被本地
DNS 缓存
。如果没有缓存你的 URL 地址,浏览器就会发起系统调用来查询本机 hosts 文件
是否有配置 ip 地址。如果找不到,就向网络中发起一个 DNS 查询。(在由根域名服务器 -> 顶级域名服务器 -> 权威 DNS 服务器后,由权威服务器告诉本地服务器目标 IP 地址,再有本地 DNS 服务器告诉用户需要访问的 IP 地址。)
DNS报文基础部分为DNS首部。其中包含了事务ID,标志,问题计数,回答资源计数,回答计数,权威名称服务器计数和附加资源记录数。
标志字段细分如下:
- QR(Response):查询请求,值为0;响应为1
- Opcode:操作码。0表示标准查询;1表示反向查询;2服务器状态请求
- AA(Authoritative):授权应答,该字段在响应报文中有效。通过0,1区分是否为权威服务器。如果值为 1 时,表示名称服务器是权威服务器;值为 0 时,表示不是权威服务器。
- TC(Truncated):表示是否被截断。当值为1的时候时,说明响应超过了 512字节并已被截断,此时只返回前512个字节。
- RD(Recursion Desired):期望递归。该字段能在一个查询中设置,并在响应中返回。该标志告诉名称服务器必须处理这个查询,这种方式被称为一个递归查询。如果该位为
0,且被请求的名称服务器没有一个授权回答,它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询。- RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为 1 时,表示服务器支持递归查询。
- Z:保留字段,在所有的请求和应答报文中,它的值必须为 0。
- rcode(Reply code):通过返回只判断相应的状态
- 浏览器需要和目标服务器建立 TCP 连接,需要经过三次握手的过程。
数据在链路层的传输是需要MAC地址的,仅仅知道B的IP地址是无法进行通信的。ARP全称是地址解析协议(Address Resolution Protocol),其基本功能为透过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行,它是IPv4中网络层必不可少的协议。
IP协议是不可靠的传输协议,网络中进行可靠传输的是TCP协议。如果在消息没有送达的情况下,需要用到ICMP协议。它的作用:更加有效地转发IP数据报作为IP数据报的数据部分,可以分为ICMP差错报文,和ICMP查询报文。
- 建立连接后,浏览器会向目标服务器发起 HTTP-GET 请求,包括其中的 URL,HTTP 1.1 后默认使用
长连接
,只需要一次握手即可多次传输数据。 - 如果目标服务器只是一个简单的页面,就会直接返回。但是对于某些大型网站的站点,往往不会直接返回主机名所在的页面,而会直接重定向。返回的状态码就不是 200 ,而是 301,302
以 3 开头的重定向码
,浏览器在获取了重定向响应后,在响应报文中 Location 项找到重定向地址,浏览器重新第一步访问即可。
数据包传输流程
- 用户上传数据,TCP小朋友开始拆分TCP数据包(分成多段,里面有
序号、源端口、目的端口
),打包成IP数据包(原IP地址、目的IP地址)。 - IP数据包交给链路层,从ARP表中找到网关路由器的MAC地址(源地址、目标地址-网关路由器)。
- 链路层包经过交换机路由到相应端口。
- 路由器A收到链路层包,看到IP数据包。修改IP数据包源IP地址、目标地址和TCP数据包源端口,在NAT表中记录修改的IP地址和端口的对应关系。将新的IP数据包放到链路层包中。
- 路由器D从ARP表中找到了目标ip地址的MAC地址,重新封装新的数据链路层箱子发出。
- 交互机得到链路层包,发送至对应端口,来到最终的服务器。
应用层(对应应用)、传输层(对应TCP/UDP)、网络层(对应IP,路由操作)、数据链路层(对应MAC,交换机操作)
BIO、NIO、AIO
阻塞就是 CPU 停下来等待一个慢的操作完成
CPU 才接着完成其它的事。
非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。
IO操作的两个阶段
- 查看数据是否就绪;
- 进行数据拷贝(内核将数据拷贝到用户线程)。
同步IO和异步IO是针对用户线程和内核的交互来说的:
同步IO:当用户线程发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程;
异步IO:只有IO请求操作
的发出是由用户线程来进行的,IO操作的两个阶段都是由内核自动完成,然后发送通知告知用户线程IO操作已经完成。也就是说在异步IO中,不会对用户线程产生任何阻塞
。
阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息。
阻塞IO:当用户线程发起一个IO请求操作,内核会去查看要读取的数据是否就绪,如果数据没有就绪,则会一直在那等待,直到数据就绪,当数据就绪之后,便将数据拷贝到用户线程。
非阻塞IO:当用户线程发起一个IO请求操作,内核会去查看要读取的数据是否就绪,如果数据没有就绪,如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪。
BIO (同步阻塞式IO)
BIO会产生两次阻塞,第一次在等待连接时accept阻塞,第二次在等待数据read时阻塞
请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求
。
多线程BIO会为这些不发送消息的请求创建一个单独的线程(但线程的上线文切换开销会在高并发的时候体现的很明显)。
public static void main(String[] args) {
byte[] buffer = new byte[1024];
try {
ServerSocket serverSocket = new ServerSocket(8079);
while (true) {
System.out.println();
System.out.println("服务器正在等待连接...");
Socket socket = serverSocket.accept();
System.out.println("服务器已接收到连接请求...");
System.out.println();
System.out.println("服务器正在等待数据...");
socket.getInputStream().read(buffer);
System.out.println("服务器已经接收到数据");
System.out.println();
String content = new String(buffer);
System.out.println("接收到的数据:" + content);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
NIO多路复用 (同步非阻塞式IO)
服务端的一个线程可以处理多个请求,客户端发送的连接请求注册在多路复用器Selector上,服务端线程通过轮询多路复用器查看是否有IO请求,有则进行处理。
- 缓冲区(Buffer)
主要用于与NIO通道进行交互,数据是从通道读入到缓冲区,从缓冲区写入通道中的。(提高字节的读取效率)
Buffer就像一个数组,可以保存多个相同类型的数据。 - 通道(Channel)
用于进行数据传输,面向缓冲区进行操作,支持双向传输。Channel 负责传输, Buffer 负责存储。
- 选择器(Selector)
一个NIO的选择器,可以检测(监视)多个NIO channel
,看看读或者写事件是否就绪。
多个Channel以事件的方式可以注册到同一个Selector,从而达到一个Selector线程通过IO多路复用
的方式(比如epoll()、poll()、select())来处理多个IO请求。
public static void main(String[] args) throws InterruptedException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
List<SocketChannel> socketList = new ArrayList<SocketChannel>();
try {
//Java为非阻塞设置的类
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8079));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
while(true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel==null) {
//表示没人连接
System.out.println("正在等待客户端请求连接...");
Thread.sleep(5000);
}else {
System.out.println("当前接收到客户端请求连接...");
socketList.add(socketChannel);
}
for(SocketChannel socket:socketList) {
socket.configureBlocking(false);
int effective = socket.read(byteBuffer);
if(effective!=0) {
byteBuffer.flip();//切换模式 写-->读
String content = Charset.forName("UTF-8").decode(byteBuffer).toString();
System.out.println("接收到消息:"+content);
byteBuffer.clear();
}else {
System.out.println("当前未收到客户端消息");
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
在真实NIO中,并不会在Java层上来进行一个轮询,而是将轮询的这个步骤交给我们的操作系统来进行,他将轮询的那部分代码改为操作系统级别的系统调用(win:select函数,在linux:epoll
),在操作系统级别上调用select函数,主动地去感知有数据的socket。
Java对BIO、NIO的支持:
Java BIO (blocking I/O)
: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。Java NIO (non-blocking I/O)
: 同步非阻塞,服务器实现模式为一个请求一个线程
,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。1.4后支持
AIO (异步非阻塞IO)
一般用于连接数较多且连接时间较长的应用,在读写事件完成后由回调服务
去通知程序启动线程进行处理。
与NIO不同,当进行读写操作时,只需直接调用read或write方法
即可。这两种方法均为异步的
select、poll、epoll之间的区别
- select==>时间复杂度O(n)
有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
select的缺点:
- 底层存储依赖bitmap,处理的请求是有上限的,为1024。
- 文件描述符是会置位的,所以如果当被置位的文件描述符需要重新使用时,是需要重新赋空值的。
- fd(文件描述符)从用户态拷贝到内核态仍然有一笔开销。
- select返回后还要再次遍历,来获知是哪一个请求有数据。
- poll==>时间复杂度O(n)
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。 - epoll==>时间复杂度O(1)
它能显著提高程序在大量并发连接中只有少量活跃
的情况下的系统CPU利用率,获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列
的描述符集合就行了。
epoll的fd是共享在用户态和内核态之间的,所以可以不必进行从用户态到内核态的一个拷贝
,这样可以节约系统资源;如果某个请求的数据已经准备好,epoll只会返回存在数据的请求
,这是因为epoll在发现某个请求存在数据时,首先会进行一个重排操作,将所有有数据的fd放到最前面的位置,然后返回(返回值为存在数据请求的个数N)。
进程间通信的方式
每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。
1.管道
令行里的「|」竖线就是一个匿名管道
,它的功能是将前一个命令(ps auxf)的输出,作为后一个命令(grep mysql)的输入.管道传输数据是单向
的,如果想相互通信,需要创建两个管道
才行。(管道还有另外一个类型是命名管道
)
ps auxf | grep mysql
管道这种通信方式效率低,不适合进程间频繁
地交换数据。对于匿名管道,它的通信范围是存在父子关系
的进程。对于命名管道,它可以在不相关的进程间也能相互通信。不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核
中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出
原则,不支持 lseek 之类的文件定位操作。
2.消息队列
消息队列是保存在内核中的消息链表
,解决进程间频繁交换数据的问题。
缺点:一是通信不及时,二是附件也有大小限制。
消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销
。进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。
3.共享内存
共享内存解决用户态与内核态之间的数据拷贝开销问题。
拿出一块虚拟地址空间来,映射到相同的物理内存中。
4.信号量
信号量可以防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。
信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步
,而不是用于缓存进程间通信的数据。这就是P(-1)V(+1)原语。(P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。)
- 信号初始化为 1,就代表着是
互斥
信号量 - 信号初始化为 0,就代表着是
同步
信号量
5.信号
以上说的进程间通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。
信号是进程间通信机制中唯一的异步通信机制
,因为可以在任何时候发送信号给某一进程。
- 执行默认操作
- 捕捉信号
- 忽略信号
6.Socket
Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信
。
本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件
,这也就是它们之间的最大区别。
DHCP
动态主机设置协议(DHCP)是一种使网络管理员能够集中管理和自动分配IP网络地址的通信协议