运输层协议
-
运输层协议是在端系统中而不是在路由器中实现的
-
有了运输层的端到端差错控制,为什么还要有链路层的差错控制?
- 如果没有链路层重传,损坏的数据包必须沿整个链路重传,大大降低了性能
- 数据链路层有校验了,为什运输层仍需要校验?
- 路由器拆包可能错了
- 可能发错目标了
-
运输层需要有发送端缓冲区以支持重传
-
为什么TCP和IP协议要实现在一起?
- 避免多次缓冲区复制
- 网卡收到数据后先复制到内核缓冲区,在到网络层缓冲区,再到传输层缓冲区
- 也可以通过在复制时同时计算校验和来节约时间
- 避免多次缓冲区复制
-
为什么传输协议要在内核中实现?
- 如果实现在用户态,可能会多次进行上下文切换
- 输出缓冲区的建立,也能减少这种切换,因为应用程序在用户
-
TCP 协议有拥塞控制策略, 网络为什么还会发生拥塞崩溃?
- UDP流量
- 拥塞判断有延迟
TCP协议
-
TCP协议是流式的传输层通信协议
- TCP序列号的值针对的是字节而不是报文,用于确定接收端有序的接收到字节流
- TCP 没保证数据按照你输入的间隔发送,只是保证了顺序和输入的顺序一样
- TCP是不维护应用报文的边界,需要上层协议自己定义协议格式,然后按照协议去读取并解析
- 上层协议一般通过定长分割,或者特定分隔符来分割,或者增加消息头并表示消息长度
- 例如HTTP的
/r/n
切分
- 例如HTTP的
- 上层协议一般通过定长分割,或者特定分隔符来分割,或者增加消息头并表示消息长度
- TCP 并不能保证接收方应用程序和发送方应用程序所发出的数据块具有对应大小的关系
- “粘包问题”
- TCP是流协议,本身没有包的概念,因此没有粘包的说法
- TCP 底层并不了解上层业务数据的具体含义,它会根据 TCP 缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包进行发送,这就是所谓的 TCP 粘包和拆包问题
- 参考:
- TCP序列号的值针对的是字节而不是报文,用于确定接收端有序的接收到字节流
-
建立TCP连接和慢启动会对端到端时延产生负面影响
- 所以应用层最好保持长连接
-
超时间隔通过往返时间(RTT)估计
- 重传后如果还未收到回复,超时时间会逐渐加倍,成指数增长
-
超时重传存在超时间隔较长的问题,导致端到端时延过长
- 但是TCP没有否定确认机制,无法显示告知发送方数据丢失
- 快速重传通过冗余ACK隐式告知丢失
- 当发送方收到三次重复ACK时,便认为后一段的数据丢失,立即重传
-
为什么是三次重复ACK?
- 因为两次重复可能是乱序造成的
- 实际上是一个经验值,可以设置为更多次
-
SYN洪泛攻击
- 攻击者发送大量SYN包请求与服务器建立连接
- 此时服务器维持大量半连接状态
- accept函数可以指定半连接队列长度
- 攻击者不回复服务器的确认,直到服务器耗尽资源,无法为正常请求建立连接
- 攻击者可以通过设置防火墙,禁止除了SYN数据包外的的传出数据包通过防火墙实现拒绝回复
- 可能的解决方式
- IP黑名单
- 攻击者可能会篡改源IP地址,或者控制别人的主机
- 使用SYN cookie
- 收到SYN请求后不建立半连接,而是通过源IP和端口计算出一个cookie作为处理SYN序号
- 当客户端返回响应且cookie验证通过后才建立连接
- IP黑名单
- 攻击者发送大量SYN包请求与服务器建立连接
-
流量控制
- 针对于发送方发送速度过快
- 利用滑动窗口解决
-
滑动窗口的一个问题是如果接收方告知窗口为0后不再发送数据,发送方就一直堵塞,即使接收方已经处理完了缓存中的数据
- 解决方法是使用窗口探测
- 发送方继续发送只有一个字节数据的数据报,直到接收方返回非零窗口
-
拥塞控制
- 针对于网络状况不佳,与发送方速度无关,可能是路由器缓冲区溢出
- 感知拥塞方式
- 超时
- 冗余ACK
- 慢启动(SS)
- 从一个MSS开始发送
- 每次翻倍,以指数速度增长直到发生超时
- 重新从一个MSS开始增长,并将阈值设为最大值的一半
- 因为慢启动每次增长2倍,前一次是成功的
- 避免拥塞(CA)
- 当速度重新指数增长到阈值时,速度改为一次增长一个MSS
- 直到收到三次冗余ACK,此时速度降为最大值一半
- 如果超时依旧降为1个MSS
- 上述方式导致拥塞窗口(CWND)大小呈现锯齿
-
快速重传
-
单位时间内(一个 RTT)发送量不是 CWND,而是 min(CWND, RWND)
- 除了数据发送端有个 CWND 以外,数据接收端还有个 RWND(Receive Window,接收窗口),决定还能接收多少数据,并通过接收确认报文显性告知数据发送端
- 发送端要参考 CWND 和 RWND 信息决定最终发送的数据量(packet 个数)
- 管道中究竟能放多少数据,取决于 CWND 和 RWND
-
浏览器经常通过多个并行的TCP链接来传送WEB页面中的多个对象
- 这样做抢占了网络上的流量
-
nagle算法与延时确认
- 延时确认是接收方不再受到数据后立刻发送ACK,而是等待一段时间,以求能够被发送的数据捎带回去
- nagle算法是当发送方有许多小包时,暂存在缓冲区,直到前面的ACK返回后才一起发出
- 可能会导致端到端时延增加
- 可能和延时确认相互作用造成死锁
if 有数据要发送:
if 接受窗口 > mss && 可发送的数据 > mss:
立即发送
else:
if 有未确认的数据(等待ACK):
缓存数据
else:
立即发送
-
低能窗口综合征
- 发送端:数据生产过慢
- 接收端:数据消费过慢,导致可用窗口大小很小,发送方只能发送小包
-
如何避免老的数据包被当做新的处理?
- seq在一定时间内自增,该时间应该大于一个包在网络中的最大生存期,因此新的链接的包的seq应该都大于老的链接的
- 当传输速度过快时,可能会耗尽syn的区间,导致从0开始
- 这个时候就无法区分老的包和新的包了
- 需要在optional字段加上时间戳协助判断
-
如何避免老的数SYN被当做新的处理?
- 当旧的SYN到2,2也会发SYN给1,此时1返回reject,2便终止握手
- 若旧的第三次握手到来,2通过查看ACK判断是否是自己SYN的回应
-
为什么一定要三次握手?
- 三次握手目的是为了让双方都得知SYN
- 为什么不能两次的原因如上一条,如果老的请求到来,B发送回应后就认为建立连接了,但是实际上该请求无效,B浪费了大量时间等待一个无效链接的数据,直到超时
- 如果A还在工作,会返回一个reject,但是这个reject也可能会丢
- 如果A还在工作,会返回一个reject,但是这个reject也可能会丢
-
为什么不能在第一次握手的时候就携带数据,接受方先缓存住?
- 这样会 放大SYN洪泛攻击,接收方大量内存被浪费
- 第三次可以携带数据,因为伪造IP的主机是不会接收到第二次报文的。能够发出第三次握手报文的,应该是合法的用户
-
释放连接时为什么要设置time-out(time_wait)
- 客户端发送完最后一个ack确认包后,需要等待2msl的时间才能关闭连接。MSL是Maximum Segment Lifetime,译为“报文最大生存时间”,可为30s,1min或2min
- 原因1:为了保证B能收到A的确认应答。若A发完确认应答后直接进入CLOSED状态,那么如果该应答丢失,B等待超时后就会重新发送连接释放请求,但此时A已经关闭了,不会作出任何响应,因此B永远无法正常关闭(如果没有开keepalive的话)
- 实际上不会,一个是因为B进程可能会退出释放,另一个是大部分连接会经过NAT,而NAT会定期断开无效连接
- 原因2:在第四次挥手后,经过2msl的时间足以让本次连接产生的所有报文段都从网络中消失,这样下一次新的连接中就应该不会出现旧连接的报文段了
- 坏处:服务器大量time_wait会占用大量端口
-
为什么time_wait长度为2MSL?
- 这2个MSL中的第一个MSL是为了等自己发出去的最后一个ACK从网络中消失,而第二MSL是为了等在对端收到ACK之前的一刹那可能重传的FIN报文从网络中消失
-
服务器保持了大量CLOSE_WAIT状态的原因?
- 在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态
- 服务器忙于读写
-
RTT测量
-
一次HTTP请求在实际发送数据前已经经过了4个RTT
- DNS解析1个RTT
- 一般来说有缓存,可以不算时间
- TCP握手需要1个RTT
- 实际是SYN,SYN&ACK,ACK 三次握手共1.5RTT
- 不过最后一次ACK与ClientHello合并了,所以是1个RTT
- 如果是浏览刚刚已经访问过的站点的话,通过 TLS 的会话恢复机制,第三步 TLS 握手能够从 2 RTT 变为 1 RTT
- 如果使用了
TLS1.3
那么,第一次建连只需要1RTT,恢复则是0RTT
- 实际是SYN,SYN&ACK,ACK 三次握手共1.5RTT
- TLS握手和秘钥交换需要2个RTT
- DNS解析1个RTT
-
TCP连接的keepalive心跳机制
- 正常的TCP关闭流程会通知对方连接已经关闭(发送
FIN
包),但是如果是一些意外情况,例如拔掉网线、有一端设备宕机或重启,此时正在执行recv或者send操作的一方就会因为没有任何连接中断的通知而一直等待下去- 当我们 ssh 到自己的 VPS 上,然后不小心踢掉网线,此时的网络变化并不会被 TCP 检测出,当我们重新插回网线,仍旧可以正常使用 ssh,同时此时并没有发生任何 TCP 的重连
- 为了解决这个问题,引入了TCP的keepalive机制,当连接建立后,相关socket可以定期向对端发送心跳包来检查连接是否还有效
- 探测包是一个纯 ACK 包,不应该包含任何数据(也可以包含1个无意义的字节,比如0x0),其 Seq号 与上一个包是重复的
- TCP的keepalive机制是默认关闭的 ,TCP连接的任何一方都可打开此功能
- 因为可能会产生无意义的带宽浪费
- 内核会为每个开启了keepalive的套接字维护一个定时器,这会占用一定的资源
- 一般是由服务器发送探测包,而不是反过来
- 服务器侧需要不间断地提供服务,如果客户端不能主动和其保持连接,那么就应该主动回收与该客户端的连接,清理无效连接以减轻负载
- 心跳机制的用处:
- 对于长期闲置的TCP连接进行保活,避免防火墙或者NAT主动关闭连接
- 检测死链,避免路由器或交换机等出现状况,导致闲置的TCP连接实际已经无法工作,而客户端和服务端不知道
- 用户可以设置相关的参数:
tcp_keepalive_time
: 指定时间内此连接都不活跃,开启保活功能的一端会向对端发送一个保活探测报文- 默认:7200秒(2h)
tcp_keepalive_intvl
: 若由于网络原因或其他原因导致,发送端无法正常收到保活探测报文的响应,则指定时间后再次发送保活探测报文- 默认:75秒
tcp_keepalive_probes
:指定探测次数后仍未收到响应,则任务TCP连接已失效,需要将连接做中断处理- 默认:9次
- 也就是默认情况下一条TCP连接在2小时(7200秒)都没有报文交换后,会开始进行保活探测,若再经过9*75秒=11分钟15秒的循环探测都未收到探测响应,即共计:2小时11分钟15秒后会自动断开TCP连接
- 为什么有了TCP的keepalive,一般应用层还要实现心跳保活?
- TCP的keepalive只能检测连接是否存活,不能检测连接是否可用
- 例如某台服务器因为某些原因导致CPU 100%,无法响应任何业务请求,但是使用 TCP 探针则仍旧能够确定连接状态
- 应用层心跳有着更大的灵活性
- 可以控制检测时机,间隔和处理流程,甚至可以在心跳包上附带额外信息
- 虽然TCP也有参数可以调整,但那是系统级的变量。修改了以后影响的不止自己所需要的范围吧
- 额外信息例如服务器负载,网络拥堵情况
- 可以将心跳间隔根据程序状态进行调整,当程序在后台时,尽量拉长心跳间隔
- 避免了频繁心跳带来的耗电和耗流量的弊端
- 很多时候TCP的keepalive会终结到四层负载均衡上面,无法检测实际的server是否还连着
- 可以控制检测时机,间隔和处理流程,甚至可以在心跳包上附带额外信息
- 应用层心跳功能不依赖于传输层协议
- 如果有一天我们想将传输层协议由TCP改为UDP,那么传输层不提供心跳机制了
- 有些运营商会过滤掉keepalive包
- TCP的keepalive只能检测连接是否存活,不能检测连接是否可用
- 正常的TCP关闭流程会通知对方连接已经关闭(发送
- TCP协议有什么问题?
- TCP优化
- 拥塞检测
UDP协议
-
UDP协议适合实时性要求很高,并且几乎不能容忍重传的场景
- 流媒体应用、语音交流、视频会议等音视频通信
- 丢包了不需要也不能补帧,一补就越来越大的延时
- 如果采用TCP,那么在出现丢包的时候,就可能会出现比较大的延时
- NTP协议也使用UDP传输
- NTP包禁止重传
- 重传一个过期的时间包过来,还不如发一个新的UDP包同步新的时间过来
- TCP的Nagel算法可能导致时间不准
- 用户数据会在一定情况下会被内核缓存延后发送出去,这样时间同步就会出现比较大的偏差,协议将不可用
- NTP包禁止重传
- 流媒体应用、语音交流、视频会议等音视频通信
-
UDP是无连接的协议
- 一个 UDP 的 Endpoint1(IP,PORT),可以向多个 UDP 的 Endpoint ( IP , PORT )发送数据包,也可以接收来自多个 UDP 的 Endpoint(IP,PORT) 的数据包
-
劣势
- 无法调节流量
- 无控制的UDP流量可能会阻塞TCP流量
- 没有应答确认机制,不能解决丢包、重发、错序问题
- 处于安全考虑,防火墙可能会禁止UDP流量
- 避免洪泛攻击
- 运营商会对UDP包进行QoS整形
- 无法调节流量
-
优势
- 实时性高
- 采用 UDP 协议时,只要应用进程将数据传给 UDP,UDP 就会将此数据打包进 UDP 报文段并立刻传递给网络层
- 反观TCP有拥塞控制的功能,它会在发送前判断互联网的拥堵情况,如果互联网极度阻塞,那么就会抑制 TCP 的发送方
- UDP牺牲TCP的局部公平性来换取更好的实时性,或者更浪费点带宽,来实现更低的延迟
- 无需建立和维持连接
- 没有建连延迟
- 服务器不需要维持复杂的连接状态表
- 连接状态包括接收和发送缓存、拥塞控制参数以及序号和确认号的参数
- 专门用于某种特定应用的服务器当应用程序运行在 UDP 上,一般能支持更多的活跃用户
- 对于何时发送数据,应用层的控制更加精细
- 可以在应用层进行传输策略的优化,不必依赖内核的支持,优化更方便
- TCP协议在应用层传给协议栈后不一定会立即发送,而会暂存于缓冲区,所有无法控制数据发送端额速度与时间
- 首部开销少
- 每个 TCP 报文段都有 20 字节的首部开销,而 UDP 仅仅只有 8 字节的开销
- 比TCP更容易穿越NAT
- 使用ICE交互式建连
- 参考:https://zhuanlan.zhihu.com/p/350937199
- 抗弱网上UDP好于TCP
- 因为TCP有拥塞管理
- 实时性高
-
UDP包头的长度字段是冗余的
- 它等于IP total length 减去IP header,TCP就是这样计算的
- 和IP分包无关,因为IP分包应该对UDP是透明的,即送给UDP处理的一定是重组后的
- 而且IP分包后,除了第一个包,后续的包都不包含UDP包头,因此不重组的话都不知道应该发给哪个端口
- 可能的原因:
- 有可能是UDP和IP协议并行发展的结果,UDP设计者认为底层协议可能不是IP
- 为了便于硬件处理,报头长度需要是4字节的整数倍,因此就把空位填上了长度字段
- 参考:https://www.zhihu.com/question/25399270
-
为什么早年QQ使用UDP协议通信?
-
UDP洪水
- UDP 洪水是一种拒绝服务攻击,攻击者将大量用户数据报协议(UDP) 数据包发送到目标服务器,旨在让该设备的处理和响应能力无力承担
- UDP 洪水的工作原理主要是利用服务器检查并响应UDP流量时所采取的步骤
- 服务器首先检查是否有任何当前侦听指定端口请求的程序正在运行
- 如果该端口上没有程序正在接收数据包,则服务器将以 ICMP (ping) 数据包作为响应,以告知发送方目标不可达
- 由于目标服务器利用资源来检查并响应每个接收到的UDP 数据包,当收到大量UDP 数据包时,目标资源会很快耗尽,从而导致对正常流量拒绝服务
- 防范方式
- 限制ICMP 数据包的响应速率
- 缺点是在攻击期间合法数据包也可能在此过程中被过滤
QUIC协议
- QUIC协议主要是作用在弱网环境下,弥补TCP连接在弱网环境下表现较差的问题。
- 在正常网络环境下,通过TCP多年的优化,QUIC并没有特别明显的优势
- 而在弱网环境下,握手时间较短、可定制拥塞控制算法、连接迁移等特性就有很大的提升了