关键词: TCP/IP的四层模型
定义: HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范
请求大致流程图:
增加了 HEAD、POST 等新方法;
增加了响应状态码,标记可能的错误原因;
引入了协议版本号概念;
引入了 HTTP Header(头部)的概念,让 HTTP 处理请求和响应更加灵活;
传输的数据不再仅限于文本。
关键词: 短链接,队首阻塞,多TCP链接
增加了 PUT、DELETE 等新的方法;
增加了缓存管理和控制;
明确了连接管理,允许持久连接;
允许响应数据分块(chunked),利于传输大文件;
强制要求 Host 头,让互联网主机托管成为可能。
关键词: 长链接(connection:Keep-alive),可复用TCP链接(FIFO)
基于 Google 的 SPDY 协议,HTTP/2 虽然使用 “帧 ”“流” “多路复用”,没有了“队头阻塞”,但这些手段都是在应用层里,而在下层协议中,也就是 TCP 协议里,还是会发生 “队头阻塞”。
二进制协议,不再是纯文本;
可发起多个请求,废弃了 1.1 里的管道;
使用专用算法压缩头部,减少数据传输量;
允许服务器主动向客户端推送数据;
增强了安全性,“事实上”要求加密通信。
TLS 协议提供“ALPN(应用层协议协商)”扩展,让客户端和服务器协商使用的应用层协议,“发现”HTTP/2 服务。
特点
单个TCP链接(请求并行处理)
Stream多路复用,而不是有序和阻塞
二进制消息帧:二进制协议,头信息和数据包都是二进制,统称为帧,其中定义了10种不同类型的帧。
头部压缩:HPACK
服务端PUSH: 主动推送消息给客户端缓存
二进制协议格式:
帧开头是 3 个字节的长度(但不包括头的 9 个字节),默认上限是 2^14,最大是 2^24,也就是说 HTTP/2 的帧通常不超过 16K,最大是 16M。
长度后面的一个字节是帧类型,大致可以分成数据帧和控制帧两类,HEADERS 帧和 DATA 帧属于数据帧,存放的是 HTTP 报文,而 SETTINGS、PING、PRIORITY 等则是用来管理流的控制帧。
第 5 个字节是非常重要的帧标志信息,可以保存 8 个标志位,携带简单的控制信息。
常用的标志位有:
END_HEADERS表示头数据结束,相当于 HTTP/1 里头后的空行(“\r\n”),
END_STREAM表示单方向数据发送结束(即 EOS,End of Stream),相当于 HTTP/1 里 Chunked 分块结束标志(“0\r\n\r\n”)。
报文头里最后 4 个字节是流标识符,也就是帧所属的“流”,接收方使用它就可以从乱序的帧里识别出具有相同流 ID 的帧序列,按顺序组装起来就实现了虚拟的“流”。 虽然帧是乱序收发的,但只要它们都拥有相同的流 ID,就都属于一个流,而且在这个流里帧不是无序的,而是有着严格的先后顺序
扩展:10种帧的定义与其用途
DATA帧:
HEADERS帧:
PRIORITY帧:
RST_STREAM帧:
SETTING帧:
PUSH_PROMISE帧:
PING帧:
GOAWAY帧:
WINDOW_UPDATE帧:
CONTINUATION帧:
基于 Google 的 QUIC 协议,它在 HTTP/2 的基础上又实现了质的飞跃,真正“完美”地解决了“队头阻塞”问题。
QUIC 的基本数据传输单位是包(packet)和帧(frame),一个包由多个帧组成,包面向的是“连接”,帧面向的是“流”。
QUIC 是一个新的传输层协议(但不是由操作系统内核实现,不受其限制,而是运行在用户空间),建立在 UDP 之上,实现了可靠传输;
QUIC 内含了 TLS1.3,只能加密通信,支持 0-RTT 快速建连
QUIC 的连接使用“不透明”的连接 ID,不绑定在“IP 地址 + 端口”上,支持“连接迁移”
QUIC 的流与 HTTP/2 的流很相似,但分为双向流和单向流;
HTTP/3 没有指定默认端口号,需要用 HTTP/2 的扩展帧“Alt-Svc”来发现
QUIC : 在UDP上实现的TCP + TLS + HTTP/2的功能
帧结构:
由于流管理被“下放”到了 QUIC,所以 HTTP/3 里帧的结构也变简单了。
为啥没有了流标识字段,那怎么样区分是不是同一个流的数据呢?
HTTPS(HTTP over SSL/TLS)不仅能保证密文传输,重要的是还可以做到验证通信方的身份,保证报文的完整性,HTTPS在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段
TLS / SSL协议位于应用层和传输层TCP协议之间
1. 靠近应用层的握手协议(TLS Handshaking Protocols)
2. 靠近TCP的记录层协议(TLS Record Protocol)
传输大致流程: 客户端发起HTTPS请求,服务端返回证书,客户端对证书进行验证,验证通过后本地生成用于改造对称加密算法的随机数,通过证书中的公钥对随机数进行加密传输到服务端,服务端接收后通过私钥解密得到随机数,之后的数据交互通过对称加密算法(使用了双方交换好的随机数对称加密改造)进行加解密。
TLS 的密码套件命名: 密钥交换算法 + 签名算法 + 对称加密算法 + 分组模式 + 摘要算法。
例如: ECDHE-RSA-AES256-GCM-SHA384
对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换,常用的有 AES 和 ChaCha20;
非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢,常用的有 RSA 和 ECC。(注意:ECC不能直接实现密钥交换和身份验证,需要搭配DH,DSA等算法,形成专门的ECDHE,ECDSA算法,RSA比较特殊,本身即支持密钥交换也支持身份认证)
把对称加密和非对称加密结合起来就得到了“又好又快”的混合加密,也就是 TLS 里使用的加密方式
在机密性的基础上还必须加上完整性、身份认证等特性,才能实现真正的安全。
实现完整性的手段主要是:摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。
不过摘要算法不具有机密性,如果明文传输,那么黑客可以修改消息后把摘要也一起改了,网站还是鉴别不出完整性。
所以,真正的完整性必须要建立在机密性之上,在混合加密系统里用会话密钥加密消息和摘要,这样黑客无法得知明文,也就没有办法动手脚了。 这有个术语,叫哈希消息认证码(HMAC)
分析:加密算法结合摘要算法,我们的通信过程可以说是比较安全了。但这里还有漏洞,就是通信的两个端点(endpoint)。
就像一开始所说的,黑客可以伪装成网站来窃取信息。而反过来,他也可以伪装成你,向网站发送支付、转账等消息,网站没有办法确认你的身份,钱可能就这么被偷走了。
非对称加密里的“私钥”,使用私钥再加上摘要算法,就能够实现“数字签名”,同时实现“身份认证”和“不可否认”。
数字签名的原理其实很简单,就是把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。
刚才的这两个行为也有专用术语,叫做“签名”和“验签”。
只要你和网站互相交换公钥,就可以用“签名”和“验签”来确认消息的真实性,因为私钥保密,黑客不能伪造签名,就能够保证通信双方的身份。
比如,你用自己的私钥签名一个消息“我是小明”。网站收到后用你的公钥验签,确认身份没问题,于是也用它的私钥签名消息“我是某宝”。你收到后再用它的公钥验一下,也没问题,这样你和网站就都知道对方不是假冒的,后面就可以用混合加密进行安全通信了。
这里还有一个“公钥的信任”问题。因为谁都可以发布公钥,我们还缺少防止黑客伪造公钥的手段,也就是说,怎么来判断这个公钥就是你或者某宝的公钥呢?
这个“第三方”就是我们常说的CA(Certificate Authority,证书认证机构)。它就像网络世界里的公安局、教育部、公证中心,具有极高的可信度,由它来给各个公钥签名,用自身的信誉来保证公钥无法伪造,是可信的。
有了这个证书体系,操作系统和浏览器都内置了各大 CA 的根证书,上网的时候只要服务器发过来它的证书,就可以验证证书里的签名,顺着证书链(Certificate Chain)一层层地验证,直到找到根证书,就能够确定证书是可信的,从而里面的公钥也是可信的。
摘要算法用来实现完整性,能够为数据生成独一无二的“指纹”,常用的算法是 SHA-2;
数字签名是私钥对摘要的加密,可以由公钥解密后验证,实现身份认证和不可否认;
公钥的分发需要使用数字证书,必须由 CA 的信任链来验证,否则就是不可信的;
作为信任链的源头 CA 有时也会不可信,解决办法有 CRL(证书吊销列表,Certificate revocation list)、OCSP(在线证书状态协议,Online Certificate Status Protocol),还有终止信任
解释: 状态转换边的数据含义, 收到信号 / 发送信号 , 其中有些边是同步打开,或者同步关闭的时候建立的
状态:
CLOSED: 表示当前连接已经关闭
LISTEN:表示正在监听中,随时准备接受连接请求
SYN_SENT : 表示已经发送出建立TCP链接的数据包,等待对方回应
SYN_RECVD : 表示接受到了建立TCP链接的数据包,准备给对方发送SYN + ACK
ESTABLISHED : 表示已经建立TCP链接了
FIN WAIT - 1 : 表示主动关闭链接的一方已经发送了FIN包
FIN WAIT - 2 : 表示主动关闭的一方收到了FIN的ACK包, 等待对方发出FIN包
CLOSE WAIT : 表示被动关闭的一方收到了FIN包
LAST ACK : 表示被动关闭的一方发出了FIN包,开始等待对方发送ACK
TIMED WAIT : 表示主动关闭的一方已经发送了ACK,此时主动关闭的一方要等待2倍的max segment lifetime,在此期间,任何因为网络延迟或者拥堵未及时到达的包将会被丢弃,以防下一个链接收到了上一个链接的包
半打开状态出现在 三次握手阶段,主动建立链接方 和 被动建立链接方都会进入这个状态。
三次握手由 2个 SYN 和 2个 ACK组成,发送出SYN 还没 收到对端的 ACK的状态就是半打开状态,所以主动/被动都可能进入半打开状态。
主动:SYN_SEND, 被动: SYN-RECEIVED
半关闭状态出现在 四次挥手阶段,主动关闭链接方 和 被动关闭链接方 都会进入这个状态。
四次挥手由 2个 FIN 和 2个 ACK组成,发送出FIN 还没 收到对端的 ACK的状态就是半关闭状态,所以主动/被动都可能进入半打开状态。
主动:FIN-WAIT-1, 被动: LAST-ACK
TIME_WAIT 不是半关闭状态,此时已经走完四次挥手了;
CLOSE_WAIT也不是半关闭状态,因为此时作为被动关闭方,其已经收到对端的FIN,此时本端进入CLOSE_WAIT状态,需要应用程序调用close函数来触发对FIN的ACK,并清理资源后发送自己的FIN。只是应用程序没有主动关闭socket套接字(内核socket套接字里面维护了两个缓存区,分别为:读缓缓冲,写缓冲, 读socket发现读到文件结束符,然后调用close函数)。进入此状态,如果不调用close,则四次挥手永远只能停留在第一步,这也会导致主动关闭侧一直处于FIN_WAIT_1状态
第一次握手(SYN=1, seq=x),发送完毕后,客户端进入 SYN_SEND 状态
第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器端进入 SYN_RCVD 状态。
第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时, 也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输
流程示意图:
第三次握手是为了防止客户端A已失效的连接请求报文段(sync包)传送到B服务器端,因而产生错误的连接建立
具体分析:
正常请求情况
A发出连接请求,但因连接请求报文丢失而未收到确认。于是A再重传一次请求连接。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A共发送了两个连接请求报文段。其中第一个丢失,第二个到达了B。
异常请求情况
即A发出的第一个请求连接报文段并没有丢失,而是在某些网络结点长时间滞留了,以至到连接释放以后的某个时间才到达B。本来这是一个已失效的报文段。但B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求。于是就向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。
由于现在A并没有发出建立请求的连接,因此不会理睬B的确认,也不会向B发送数据,但B却以为新的运输连接已经建立了,并一直等待A发来的数据。B的许多资源就这样白白浪费了。
采用三次握手的话,A不会向B的确认发出确认。B由于收不到确认,就知道A并没有要求建立连接,所以就不会分配资源给这个连接。
第一次挥手(FIN=1,seq=u),发送完毕后,客户端进入FIN_WAIT_1 状态
第二次挥手(ACK=1,ack=u+1, seq =v),发送完毕后,服务器端进入CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态
第三次挥手(FIN=1,ACK=1, seq=w, ack=u+1),发送完毕后,服务器端进入LAST_ACK 状态,等待来自客户端的最后一个ACK。
第四次挥手(ACK=1,seq=u+1, ack=w+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
流程示意图:
1个 MSL 保证对端没有收到 ACK 那么进行重传的 FIN 报文能够到达
1个 MSL 保证四次挥手中主动关闭方最后的 ACK 报文能最终到达对端
客户端,服务端都需要通知对方,自己要发送的数据都已经发送完了
第一回合挥手: 客户端通知服务端,我没数据要发送了(一来一回,2次)
第二回合挥手: 服务端收到客户端的通知后,处理自己需要发送的数据和操作,等数据发送和操作都全部做完了,然后通知到客户端(一来一回,2次)
抓包命令:tcpdump -i any port 3310 -tttt -nn -X -s0
首先,TCP的连接是基于三次握手,而断开则是四次挥手。确保连接和断开的可靠性。
其次,TCP的可靠性,还体现在有状态; TCP会记录哪些数据发送了,哪些数据被接受了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。
再次,TCP的可靠性,还体现在可控制。它有报文校验、ACK应答、超时重传(发送方)、 失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口:接收端控制的缓存区)和 拥塞控制 (发送端控制的拥塞窗口)等机制。
TCP 提供一种机制(滑动窗口)可以让发送端根据接收端的实际接收能力控制发送的数据量,这就是流量控制
有个问题:ACK只向发送端告知最大的有序报文段,到底是哪个报文丢失了呢?并不确定!那到底该重传多少个包呢?
带选择确认的重传,Selective Acknowledgment
即Duplicate SACK(重复SACK),在SACK的基础上做了一些扩展,,主要用来告诉发送方,有哪些数据包自己重复接受了
发送方维护一个拥塞窗口cwnd(congestion window)的变量,它大小代表着网络的拥塞程度,并且是动态变化的
cwnd的值开始为1, 每收到一个ACK,就将拥塞窗口cwnd大小就加1(单位是MSS)。
每轮次发送窗口增加一倍,呈指数增长,只要网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,以便把更多的数据包发送出去,但只要网络出现拥塞,拥塞窗口的值就应该减小一些,以减少注入到网络中的数据包数。如果出现丢包,拥塞窗口就减半,进入拥塞避免阶段。
TCP连接完成,初始化cwnd = 1,表明可以传一个MSS单位大小的数据。
每当收到一个ACK,cwnd就加一 (每当过了一个RTT,cwnd就增加一倍 ,代表上一个cwnd窗口的所有数据ACK都收到了); 呈指数让升
cwnd不能无限增下去,使用了一个叫慢启动门限(ssthresh=65536),当cwnd超过该值后,停止慢启动算法,然后开始使用加法线性递增算法
每收到一个ACK时,cwnd = cwnd + 1 / cwnd (即每过一个RTT时,cwnd = cwnd + 1)
RTO超时重传
慢启动阀值sshthresh = cwnd / 2
cwnd 重置为 1
进入新的慢启动过程
发送方收到3个连续重复的ACK ( 此时是已出现丢包,接收方会连续三次发送重复的ACK) 时,就会快速重传,不必等待RTO超时再重传,快速重传和快速恢复算法一般同时使用。
快速恢复算法认为,还有3个重复ACK收到,说明网络也没那么糟糕,所以没有必要像RTO超时重传表现的那么强烈。
拥塞窗口大小 cwnd = cwnd / 2
慢启动阀值 ssthresh = cwnd
进入快速恢复算法
快速恢复算法
cwnd = sshthresh + 3
重传重复的那几个ACK(即丢失的那几个数据包)
如果再收到重复的 ACK,那么 cwnd = cwnd +1
如果收到新数据的 ACK 后,表明恢复过程已经结束,可以再次进入了拥塞避免的算法了。
TCP三次握手时,客户端发送SYN到服务端,服务端收到之后,便回复ACK和SYN,状态由LISTEN变为SYN_RCVD,此时这个连接就被推入了SYN队列,即半连接队列。
当客户端回复ACK, 服务端接收后,三次握手就完成了。这时连接会等待被具体的应用取走,在被取走之前,它被推入ACCEPT队列,即全连接队列。
SYN Flood是一种典型的DoS (Denial of Service,拒绝服务) 攻击,它在短时间内,伪造不存在的IP地址, 向服务器大量发起SYN报文。当服务器回复SYN+ACK报文后,不会收到ACK回应报文,导致服务器上建立大量的半连接,半连接队列满了,这就无法处理正常的TCP请求了。
END