长连接和短连接是什么?为什么是必要的?
如何避免队头阻塞?
根据此图谈谈网络优化
说说抓包? ⭐
[原创]某直播APP逆向TCP协议分析 Android抓包总结 扯一扯HTTPS单向认证、双向认证、抓包原理、反抓包策略
心跳包作用、格式的定义及处理
作用是保持长连接避免不断创建tcp连接
报文头的定义,就是你发送数据的时候需要先发送报文头,报文里面能解析出你将要发送的数据长度 你发送数据包的格式,是json的还是其他序列化的方式
Socket连接池干嘛的
Socket连接池,就是维护着一定数量Socket长连接的集合。它能自动检测Socket长连接的有效性,剔除无效的连接,补充连接池的长连接的数量
http的各个版本缺陷
三次握手在高延迟的场景下影响较明显,慢启动则对大量小文件请求影响较大(没有达到最大窗口请求就被终止) HTTP/1 有连接无法复用、队头阻塞、协议开销大和安全因素等多个缺陷 HTTP/2 通过多路复用、二进制流与 Header 压缩等技术,极大地提高了性能,但是还是存在一些问题 HTTP/3 抛弃 TCP 协议,以全新的视角重新设计 HTTP。其底层支撑是 QUIC 协议,该协议基于 UDP,有 UDP 特有的优势,同时它又取了 TCP 中的精华,实现了即快又可靠的协议
http rfc规范
HTTP/2 由两个规范组成: Hypertext Transfer Protocol version 2 - RFC7540 HPACK - Header Compression for HTTP/2 - RFC7541
Http加密过程是怎样的
TLS需要2个RTT,TLS 1.3 需要1个RTT
自己书写的https连接全过程
另外这篇文章有IP和TCP包头格式
让第三方CA来确保server公钥正确
1)server向CA提供基本信息、公钥等,CA用CA私钥对server公钥和信息进行加密形成数字证书(防止server被伪造)。
2)client向server发出https请求和随机数c,server返回数字证书和随机数s。
3)client收到数字证书,用CA公钥对数字证书进行解密,得到server的公钥。
得到server公钥后,client可以用server公钥解密server签名得到摘要。 client也把原文hash一遍得到的摘要比对,确认内容没有经过更改
生成会话密钥
client用server公钥加密client生成的随机数premaster secret,发送给server。 server收到加密的随机数premaster secret,用server私钥解密随机数,然后和随机数c、随机数s一起生成会话密钥。此时client也拥有三个随机数生成相同的会话密钥(Session Key)。双方用会话密钥进行加密通信(防止内容被窃听进行加密)
用会话密钥(Session Key)进行加密通信是对称加密的。如DES,AES等对称加密,只有一把钥匙 而获得会话密钥的过程是不对称加密的。最常见的非对称加密是RSA,有公钥和私钥,公钥加密只有私钥解密,私钥加密只有对应公钥解密
如何防止数据被篡改
- 数字签名是server对通信内容进行hash,得到信息摘要。
- 然后用server私钥对摘要进行加密得到数字签名。server把明文连同数字签名一起发送给client
- client用server公钥对数字签名解密得到信息摘要,然后用相同的hash对数据进行计算得到信息摘要,比对两个信息摘要是否一致。
为什么客户端在TIME_WAIT状态时还需要等2MSL后才能返回到CLOSED状态?
TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
报文最大生存时间(Maximum Segment Lifetime)
- 1 个
MSL确保四次挥手中主动关闭方最后的ACK报文最终能达到对端 - 1 个
MSL确保对端没有收到ACK后重传的FIN报文可以到达
虽然双方都同意关闭连接了,但网络是不可靠的,你无法保证你最后发送的
ACK报文会一定被对方收到。 因此对方处于LAST_ACK状态下的Socket可能会因为超时未收到ACK报文,而重发FIN报文
TIME_WAIT状态所导致的服务器问题
一台机器上端口号数量的上限是65536个,TCP服务器最大并发连接数是多少?
- 如果在同一台机器上进行压力测试模拟上万的客户请求,并且循环与服务端进行短连接通信,那么这台机器将产生4000个左右的TIME_WAIT Socket,后续的短连接就会产生address already in use : connect的异常
- 如果使用Nginx作为方向代理也需要考虑TIME_WAIT状态,发现系统存在大量TIME_WAIT状态的连接,通过调整内核参数解决。
tcp为什么是四次挥手而不是三次?
因为服务端收到客户端发送的FIN后,可能还有需要发送给客户端的内容,所以只先回一个ACK 如果服务端的ACK延迟到服务端发完数据后ACK和FIN一起发,会导致超过一个MSL后客户端会以为第一个客户端发起的FIN丢了,不断地重发FIN
tcp为什么发起握手连接是三次,而不是两次?
根本原因: 无法确认客户端的接收能力。
三次握手的目的是确认双方发送和接收的能力 如果第一次客户端发起的SYN A丢包,等到客户端和服务器重新建立且正常关闭了连接后,这个丢包了的SYN A被服务器收到了,由于只需要两次,服务器会建立新连接,而客户端不知道
tcp三次握手过程中可以携带数据么?
第三次握手的时候,可以携带。前两次握手不能携带数据。
如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。
SYN Flood 攻击是什么,如何解决?
典型的 DoS/DDoS 攻击
-
原因:客户端短时间内伪造大量不存在的IP,疯狂向服务端发送第一次握手SYN
- 服务端处理到大量的SYN,需要返回第二次握手的ACK和SYN,半连接队列很快被占满无法接受正常的请求
半连接队列:服务端收到以后回复第二次握手的ACK和SYN,状态由
LISTEN变为SYN_RCVD,并连接挂入SYN半连接队列- 由于是不存在的IP,服务端发送第二次握手的ACK和SYN后,长期收不到第三次握手,会导致服务端不断重发第二次握手
-
解决方法:
- 增加服务端SYN半连接队列容量
- 减少服务端重试第二次握手的次数,避免短期遇到大量的超时重发
- 利用
SYN Cookie验证客户端存在:服务端在接收到第一次握手的SYN时不立即分配资源,根据这个SYN计算出一个Cookie,连同第二次握手回复给客户端,客户端在回复第三次握手时带上Cookie,服务端在验证Cookie合法后才会分配连接资源
介绍一下 TCP 报文头部的字段
- 两个端口,各2字节:传输层TCP 报文怎么没有源 IP 和目标 IP 呢?这是因为在网络层 IP 层就已经处理了 IP。
- 序列号(
Sequence number)本报文段第一个字节的序号。4字节 - [0,2^32-1],可以循环到0- 保证数据包按正确的顺序组装。
- 在 SYN 报文中交换彼此的初始序列号
-
ISN(
Initial Sequence Number)三次握手时的初始序列号。ISN动态增长每4ms加一,溢出则从0重启,使得无法被攻击者猜测ISN。
- ACK(
Acknowledgment number),告知对方下一个期望收到的序列号,小于ACK的所有字节都已经全部收到 - SYN/ACK/FIN
- RST:
Reset,强制断开连接 - PSH:
Push,对方这些数据包收到后应该马上交给上层的应用,不能缓存 - 窗口大小,2字节(16位)但不够用。可以有窗口缩放作为比例因子,将窗口值扩大为2^n
- 校验和(
check sum) 2字节 - 可选部分:
- TimeStamp: TCP 时间戳。计算往返时延 RTT(
Round-Trip Time),防止序列号的回绕问题- kind = 8, length = 10, info 有两部分构成:
timestamp和timestamp echo,各占 4 个字节。 - 计算RTT时遇到的问题:是以第一次报文为开始还是重发的为开始?所以有
timestamp和timestamp echo分别记录本报文时间,和透传上一条对方发的报文的时间。- a 向 b 发送的时候,timestamp 中存放的内容就是 a 主机发送时的内核时刻 ta1。
- b 向 a 回复 s2 报文的时候,timestamp 中存放的是 b 主机的时刻 tb, timestamp echo字段为从 s1 报文中解析出来的 ta1。
- a 收到 b 的 s2 报文之后,此时 a 主机的内核时刻是 ta2, 而在 s2 报文中的 timestamp echo 选项中可以得到 ta1, 也就是 s2 对应的报文最初的发送时刻。然后直接采用 ta2 - ta1 就得到了 RTT 的值。
- 防止序列号回绕问题:网络中如果存在Seq相同报文,需要用时间戳做区分。
-
由于Seq溢出后会返回0,此时溢出前的Seq序列号,在溢出后从0重启后又到了相同Seq序列号。
- kind = 8, length = 10, info 有两部分构成:
- MSS: 指的是 TCP 允许的从对方接收的最大报文段。
- SACK: 选择确认选项。
- Window Scale:窗口缩放选项。
- TimeStamp: TCP 时间戳。计算往返时延 RTT(
TCP 的超时重传时间是如何计算的?
重传间隔也叫做超时重传时间(Retransmission TimeOut, 简称RTO),根据RTT密切变化
每次传输会根据RTT更新SRTT(Smoothed round trip time,即平滑往返时间),然后利用|RTT - SRTT|再和SRTT占不同权重求得
TCP Fast Open (TFO) 是什么?
优化后的 TCP 握手流程,快速打开一个TCP连接。
- 在首次三次握手的后续握手,服务端验证
Cookie后可以直接返回 HTTP 响应.充分利用了1 个RTT(Round-Trip Time,往返时延)的时间提前进行数据传输 - 可以通过上文提到的
Cookie达成,后续的TCP请求客户端直接发送缓存的Cookie、SYN和HTTP请求。 - 服务器可以把第二次握手改为,仅验证了
Cookie是否有效,就可以返回HTTP响应。客户端再第三次握手ACK
注意: 客户端最后握手的 ACK 不一定要等到服务端的 HTTP 响应到达才发送,两个过程没有任何关系。
TCP 的流量控制?
接收端接受能力低,对接收端来说:
SND 即send, WND 即window, UNA 即unacknowledged, 表示未被确认(已经发送未被确认),NXT 即next, 表示下一个发送的位置(未发送可以发送) 上图是发送方的发送窗口。 由于接收端处理能力低:大量负载来不及处理的情况,只能处理一部分,剩下的一部分留在缓冲队列里,这样下次能接受的窗口就小了。
- 需要发送端少发送,接收端的ACK报文首部会带上能被接受窗口大小。
- 此时发送端收到ACK,UNA往右移动,同时发送窗口会整个缩小
TCP 的拥塞控制?
发送端发送能力低,容易丢包,对发送端来说: TCP 每条连接都需要维护两个核心状态:
- 拥塞窗口(
Congestion Window,cwnd) -
接收窗口(rwnd)是接收端给的限制;拥塞窗口(cwnd)是发送端的限制
-
发送窗口大小 = min(rwnd, cwnd)
- 慢启动阈值(
Slow Start Threshold,ssthresh) 涉及到的算法有这几个: - 慢启动(三次握手后每一个
RTT窗口大小翻倍) - 拥塞避免(慢启动阈值后开始转为缓慢线性增长,避免堵塞)
- 快速重传和快速恢复(如果丢包,发现多个重复ACK后不用等待一个
RTO,直接进行重传)- 快速重传:接收端选择性重传(
SACK,Selective Acknowledgment)丢掉的部分 - 快速恢复:(防止加重网络堵塞)
cwnd和拥塞阈值同时降低为cwnd的一半cwnd变为线性增长
- 快速重传:接收端选择性重传(
延迟发送和延迟接收
如果延迟发送和延迟接收同时一起使用,可能会造成更大的延迟,产生性能问题
如何避免小包的频繁发送? Nagle 算法
- 当第一次发送数据时不用等待,就算是 1byte 的小包也立即发送
- 后面发送满足下面条件之一就可以发了:
- 数据包大小达到最大段大小(Max Segment Size, 即 MSS)
- 之前所有包的 ACK 都已接收到
延迟确认
短时间内连续收到包,可以合并ACK,除非需要立即回复ACK的场景:
- 接收到了大于一个 frame 的报文,且需要调整窗口大小
- TCP 处于 quickack 模式(通过tcp_in_quickack_mode设置)
- 发现了乱序包
HTTPDNS
Android Webview + HttpDns最佳实践 DNS优化 - 百度的HTTPDNS大部分标准DNS都是基于UDP与DNS服务器交互的,HTTPDNS则是利用HTTP协议与DNS服务器交互
摘要一则:对于客户端,在PC上要学会使用tcpdump和Wireshark等工具,适当使用Fiddler和Charles等工具,很多时候电脑和手机的网络环境不见得一致,所以要在手机上使用iNetTools,Ping&DNS或终端工具。学会使用工具后,要学着创造不同的网络环境,有很多工具能帮助你完成这点,比如苹果的Network Link Conditioner,FaceBook的ATC(Augmented Traffic Control)等
负载均衡
nginx负载均衡调度算法默认是 round robin,也就是轮询调度算法
j = i;
do
{
j = (j + 1) mod n;
if (W(Sj) > 0){
i = j;
return Si;
}
} while (j != i);
return NULL;
压缩算法用的是什么?
客户端发送Accept-Encoding,服务端回应
Accept-Encoding: gzip, compress, br
Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
(除了 identity 之外,都是无损压缩)
gzip:表明实体采用 GNU zip 编码。(gzip 对媒体文件的压缩效果相对较差)
compress:表明实体采用 Unix 的文件压缩程序。
deflate:表明使用是用 zlib 的格式压缩的。
br:表明实体使用 Brotli 算法的压缩格式。
identity:表明没有对实体进行编码,为默认值。
具体说明见Accept-Encoding