如何优化TCP 协议,来达到好的效果
左面主机想和右面主机进行通讯 在应用层:进程主体,申请端口,创建套接字(一个四元组),两端的套接字和套接字进程通讯。 在运输层:端到端:防火墙 在网络层:主机和主机之间的(IP 和 IP 之间)
运输层协议概述: TCP :传输控制协议,面向连接,可靠的数据传输协议。 UDP :用户报协议,无连接,不可靠的数据传输协议。 交付和差错检验是TCP / UDP 最低责任。 基本责任,将两个运行在端上的进程之间负责通信。
UDP 为什么要进行差错检验? 对于可靠还是不可靠,讲的是,包到的顺序,有没有丢包,UDP 并不管理丢包。 不过一个包经过一系列过程中,到达对端,还是要进行差错检验的。 差错检验:奇偶校验。。。 有问题的数据丢掉,没有问题的数据交付。 有差错的包是否可以恢复?只要加的校验码足够多,就可以。
所谓的连接,并不是真的有一个线,将两个对象连在一起,而且两端上各自有一个数据,来存储对方的数据,知道对方的变化。
对于UDP ,不需要知道对方的状态。发送的时候不知道对方是否还在线,如果对方不在线的话,UDP 依然会发送数据,这就给网络带来了一定的压力,所以使用UDP 的时候,需要在应用层,做一个封装。所以对于网络来说,UDP 是不友好的。
多路复用,多路分解: 当一个地址收到很多消息,到了之后,需要分发信息(多路分解)。 每个端口发送的信息,都经过这一个地址(多路复用)。
多路复用和多路分解 在运输层,只关注端口。 在网络层,只关注从哪里来的,到哪里去。
一个网站服务端,两个客户。 客户A, 发送一个报文,四元组 客户C,发送两个报文,开了两个客户端,在C 中也有一个26145 的端口,但是对于服务端来说,这还是3个客户,虽然房间号一致,但是楼号不一致。
为什么会选择无连接的运输服务 UDP
- 关于何时、发送什么数据的应用层控制更为精细(使用TCP ,发送数据,不受自己控制,即使自己发送太快,TCP 也会根据自己流量控制,拥塞控制,发送数据。UDP, 没有这个限制)
- 无需建立连接(代价小,效率高)
- 无状态连接(负担小)
- 分组开销小(每一个包的真实信息承载量更大)
SNMP 简单网络管理协议 所有网络提供商,管理光猫。 在网络不太顺畅的时候,TCP 会限制发送,使用UDP 就像是一个法外狂徒,可以全力使用网速,虽然可能会对网络造成更大的阻塞(TCP 更加抑制),这个时候,需要管理网络,限制发送端的流量,尽可能限制UDP 发送,虽然也限制了TCP。 在网络中发生了网络风暴(形成死循环的情况,可能是路由表出现问题,或者是DNS 层面发生问题,导致信息一直在内部循环,数据会越来越多,导致TCP 连接无法建立),这个时候管理网络,只能使用UDP,发送很多的包,有一个到达一个路由器,就会组织风暴的发生。 UDP 采用的是循环冗余校验。
UDP 校验和 先对所有的相加,看最后一组,发生了溢出,溢出的1与原本相加得到的后两位01 相加,得到了10,每次相加都这样循环,最终得到校验码。
如何构建一个可靠的连接。
方法1 : 使用一个可靠的信道。(运输层传输为可靠,TCP) 当使用一个不可靠的信道,我们需要做哪些事情,让不可靠的信道,变得可靠。
在写一个大型应用的时候,我们应该需要考虑,我们需要使用什么。
RDT 1.0: 完全可靠的信道 发送方:拥有完全可靠的信道,创建一个数据包,使用UDP 发送包就可以 接收方:可靠的接收信息,解析包,上传数据即可。
RDT 2.0 : 具有比特差错信道。传递的信息不会丢失。 客户端要发送数据,下层先将数据和产生的校验和封装成一个包,然后将这个包用UDP 发送出去,接收方下层,收到这个包之后,进行冲突检测,如果检测发成冲突,接收方封装一个NAK 的包,然后发送过去,如果检测之后,没有发生冲突,那么解析这个包,然后将包中的信息传给上层,并且封装一个ACK 的包,用UDP 发送给客户端。 返回客户端,如果这个时候收到了发送方发送的包,并且检测到有NAK 的包,那么UDP 重发数据,如果检测到有ACK 的包,那么进行下一次的数据传输。
这都基于发送的信息不会丢失,但是效率过于慢,等待的过程有点长。每一次往返周期只有一个包,信息量很大的时候,会很慢。
RDT 2.1 因为rdt 2.1 一次只能发一个包,为了提高效率,我们使一次可以发多个包。 这个先假定为两个包(0包, 1包) 发送方想要发送0包,下层先将0这个序号和数据以及校验码 进行封装,发送数据 发送方想要发送1包,下层先将1这个序号和数据以及校验码 进行封装,发送数据
接收方,需要的不仅仅是包的正确性,还要保证包的序列是正确的。 所以等待0 / 1的接收方如果收到冲突的包,封装NAK 以及校验和 如果收到的包没有冲突。 当等待0的接收方收到0的包,会解析包,然后向上层发送数据,并且,封装ACK, 和 校验码 包,然后向发送方发出,然后等待1包 当等待0的接收方收到1的包,封装ACK, 和 校验码 包,然后向发送方发出,并继续等待0包。
当等待1的接收方收到1的包,会解析包,然后向上层发送数据,并且,封装ACK, 和 校验码 包,然后向发送方发出,然后等待0包。 当等待1的接收方收到0的包,封装ACK, 和 校验码 包,然后向发送方发出,并继续等待1包。
接着回到发送方, 等待ACK或NAK0 的一端,如果收到包,但是包有冲突,或者包是 NAK ,那么重新发包 等待ACK或NAK0 的一端,如果收到包,但是包没有冲突,并且包是 ACK ,那么等待1的调用
等待ACK或NAK1 的一端,如果收到包,但是包有冲突,或者包是 NAK ,那么重新发包 等待ACK或NAK1 的一端,如果收到包,但是包没有冲突,并且包是 ACK ,那么等待1的调用
RDT 2.2 RDT 2.2 中去除了NAK 只有ACK, 只确认我收到的东西,没有收到的东西,不去确认。
发送方: 等待发送0的发送方,想要发送消息,下层封装 ** 0 **, 数据, 校验和, 然后通过udp 发送数据。 等待发送1的发送方,想要发送消息,下层封装 ** 1 **, 数据, 校验和, 然后通过udp 发送数据。
接收方: 等待来自0的信息,如果收到了包,并且没有错误,并且是序号0,那么会先解析包,然后上传数据,然后封装 ACK, 0, 校验和,发送包回去。 等待来自0的信息,如果收到了包,并且没有错误,但是序号是1,那么会封装 ACK, 1, 校验和,发送包回去。
等待来自1的信息,如果收到了包,并且没有错误,并且是序号1,那么会先解析包,然后上传数据,然后封装 ACK, 1, 校验和,发送包回去。 等待来自1的信息,如果收到了包,并且没有错误,但是序号是0,那么会封装 ACK, 0, 校验和,发送包回去。
回到发送方: 等待ACK0 的,如果收到了包,但是,包错误,或者ACK 序号是1,那么重新发包 等待ACK0 的,如果收到了包,包没有错误,并且ACK 序号是0,那么等待1
等待ACK1 的,如果收到了包,但是,包错误,或者ACK 序号是0,那么重新发包 等待ACK1 的,如果收到了包,包没有错误,并且ACK 序号是1,那么等待0
所以包出错,发送上一次包的 ACK
RDT 3.0 在rdt 2.2 上增加一个一个定时器。
发送方想要发送一个0号信息,会先封装一个0, 数据, 校验码 ,然后发送这个包,并且开始一个计时器。 如果收到一个包,包有错误,或者收到的ack 是 1,那么什么也不做。 如果时间结束,那么重新发送消息,并且重新开始计时器。 如果收到包,并且包没有错误,并且收到的ack 是0, 那么停止计时器。 等待发送的过程中,如果收到包,那么什么也不做(交给超时处理) 1 方同理。 当前这个协议,依然存在的问题,序号太少,如果收到上一次的0 / 1 的消息,那么会出现bug
TCP 中,第一次发送的序号是随机生成了,然后后面的序号跟随前一个序号增加1,序号都不是从0开始的,所以获得冲突的信号可能性很小。 TCP 中:
- Go-Back-N 协议,返回的ack 值是最后收到的包的ack ,说明我要收的包是这个ack 下面的那个包。如果一次发出很多的包,如果有一个包被劫持了,那么序号后面的包都会被退回来。
- 选择重传法:发送很多包,然后有一个包丢失,那么会返回丢失包的序列,并且其他的包不会回退。
- 流量控制:每次发送包之后,返回的信息,会包含接收端的空闲窗口的大小,下一次发送空间窗口大小的数据。如果空间窗口大小为0,那么下一次会发送一个空包。(通过两个缓冲区的窗口的交互)
send 函数做的事情,其实是将信息放到发送缓冲区中。
TCP 协议
应用层和运输层接口:套接字 通过套接字可以使用底层提供的网络服务。
TCP 报文结构
宽 32 bytes 所以最大端口号为16字节为 65535 序号是这次发的TCP 字节流序号,假如发1000,下次就是2000, 不是连续的 确认号是期望得到的下一次得到的包号,这次发1000,会的序列号是2000 序号最大32位 首部长度4 为 4bytes, 单位是32字 接收窗口:目标的空余窗口大小,当接收窗口为0,下一次会发一个空包过去。 特殊标志位: URG : 紧急数据 PSH :立即上交(收到这个数据,直接上交给应用层) ! ACK :确认码,如果ACK 为 1 ,说明,包中不包含数据,是对方给我的一个确认,是ACK 包 RST :连接远程服务器,发生ConnectionReused 就是 回应一个 RST,端口没有监听。 !SYN :三次握手,第一次握手发送,第二次握手收到,是用来建立连接的 !FIN : 四次挥手的 因特网校验和:确保数据是正确的。
TCP 在远程主机上,按一个x,到 x 显示会发生什么事情? 从本地到键盘感知,驱动程序,内核拿到,内核查被谁打开了,然后给响应的应用,然后把x 封装成数据包,发送到远程的云主机,本地收到数据包和ACK ,显示x,再显现出来。 ACK 是对上一次服务器发送的确认。 第一次发送序列号 731, ACK 是 1024,所以回下次期望收到732(ACK 是 732),序列号是前一次的1024, 然后本地回Seq 732, 期望下一次发送1025(ACK期望下一次发送) 往返时间估算: A 包 发送到 B 服务器,然后再传回来? SampleRTT : 样本往返时间,单次发送包到对面,收到ACK EstimatedRTT : 估算往返时间 公式 EstimatedRTT = (1 - α)
?
*
? EstimatedRTT + α
?
*
?SampleRTT α = 0.125 Dev = (1 - β) * DevRTT + β * |SampleRTT - EstimatedRTT| ? β = 0.25 TimeoutInterval = EstimsedRTT + 4 * DevRTT 第一个RTT 根据估算时间和往返时间的差值来计算 网络应该处于一个比较稳定的,出现一个短暂的波动,不应该算异常 但是一直时间不好,时间状态就会被刷上去,回拿到一个客观的超时时间,有一次出现波动,不会影响计算超时时间(引申到网络监测变化) 但是持续时间很长,应该可以作为参考。 所以网络超时时间是动态的,当超时的时候,会把超时时间加倍,避免因为网络突变,导致时间边长。 如果不调整,回导致很多包在网络上跑
流量控制 整个TCP 有一个发送缓冲区,和接收缓冲区 recvbuff 值是一定的 接收窗口的值,是上一次收的 减去 上层应用读走的,得到的也就是接收缓冲区。 上次发走的数据,减去上次ACK 的数据,(数据还在网络中的数据)应该小于等于 接收窗口的大小。 才能保证数据不被丢失,如果接收窗口满了,还发,会导致数据丢失。 流量控制针对发送双方,如果网速不好,流量控制机制给网络有优化有限。但也会抑制发送速率
网络不好,也就是现在在网络中的数据增多,未交付的数据增多。
流量控制更多针对的是发送方
三次握手
需要记住的是: 三次握手每次发送的都是什么? TCP 三次握手,由客户端发起,服务端堵塞在accept 那里, 第一次:客户端发送SYN = 1 信息 第二次:服务端发送SYN + ACK = 1 的信息 第三次:客户端回SYN = 0, ACK = 1; 为什么是三次握手,而不是两次握手? 在发起方,知道,我发的消息,服务方可以知道,服务方发的消息,我也可以收到 在服务方,知道,我能收到客户方的消息,我的消息客户方也知道 如果变成两次握手,也就是不要最后一次握手。 这样对客户方没有影响,服务方不知道我发的消息能否被收到。是不可以少的 四次握手是可以的,但是三次握手就已经可以了,但是发的消息出现冗余的情况。 三次握手的目的就是为了通讯。
四次挥手
发起方先说再见,被动方回复。 被动方说再见,发起方回复。
四次挥手,为什么要4次? 只挥1次是否可以?
- 主机A 发起结束,本地调用close, 发送一个FIN 包
- 被动方收到SYN 知道要结束,然后回复一个ACK 说知道停止。
- 至于能不能分手,还是需要看被动方。
- 服务端可以选择不分手,但是这个时候,会出现异常情况,出现的情况是半连接情况。这个时候连接,实际上还在,发起方等待SYN ,这种情况会发生一个有害的套接字,这样会出现,一个进程维持很多个已经断开的连接,所以需要回复一个SYN
- 发起方实际上需要等待20s~30s 这一个周期。
(为什么要等待?)
- 对方的ACK 会丢失,服务端会多次发SYN ,等待会避免SYN 丢失。
- 等待这么多时间,是避免刚断开连接,另外一个进程占用端口,收到上一个进程中遗留的包。
(假如上一个服务端发送多个FIN有一个很久才过来,会有结果导致,刚建立的连接,就关闭。)
TCP 状态转换序列
客户端: 默认状态为Close 序列, 调用connect 发送 SYN ,接收SYN ACK 发送ACK 进行建立连接。 关闭发送FIN 到 FIN_WAIT_1 接收到ACK 到 FIN_WAIT_2 接收FIN 发送 ACK TIME_WAIT 等待2ms
服务端: 默认Close 然后bind 开始监听 Listen 接收SYN 发送SYN ,ACK 到SYN_RCVD 接收ack ,这个时候可能会有数据 到 ESTABLSHE 接收FIN 发送 ack (!)CLOSE_WAIT (这个时候是要关闭,等着自己关闭连接) 发送FIN (!)LAST_ACK 接收ACK ,不发送
- 洪泛攻击,只进行两次连接
- 全部都在close wait, 导致很多半连接
这两种情况都会使程序崩溃。
拥塞控制方法: 如何通过协议方式,减少当网络延迟过高,波动,链路断开之后,数据暴增。 拥塞控制:
- 端到端的控制
TCP 使用超时,三次冗余ACK ,然后减少接收窗口 发生超时,超时时间加倍 - 网络辅助的拥塞控制
ATM网络 ABR 拥塞控制
把数据包分为数据信元,和资源管理信元。 在数据信元中夹杂着资源管理信元,看路上是否发生了拥塞,资源管理信元过去之后,还会回来,来上告路上的情况。 资源管理信元:
- 显示拥塞指示:EFCI (1bit)如果网络发生拥塞,这个置为1,当发送方收到这个,就会减少发送。
这个比特,可以被任何一个交换机将其拥塞指示设为1。当收到的信元多数为1,则认为链路拥塞,改变CI - 无增长比特NI ,显示轻度拥塞,经过每一个交换机,会更新er,目的是找到网络的吞吐量。
找到最低的,就是吞吐量。
多个微服务并发,可以使用这个策略,来看哪个吞吐量最低。
TCP 拥塞控制 rcwd 用来做流量控制的 cwnd 拥塞窗口 上次发送的 减去 上次接收到的 <= {cwnd, rwnd} TCP 拥塞控制的知道思想
- 一个丢失的报文段意味着拥塞,丢包就应该减小发送端的发送速率。(有一个丢了,就说明有问题)
- 一个确认的报文段表示网络正在向接收方交付报文段,因此,当收到对之前的报文段的ACK 后,应该增加发送方的速率。
一个正确的确认,表示正在交付报文,收到ACK 就是正向的激励,让发送速率稳定,健康 - 带宽探测
试探网络能容纳多少,每次都去试探,尽可能多发,收到确认继续尝试,发现丢包之后收敛,直至一个稳定值
拥塞控制之慢启动 MSS 最大报文长度。 最开始是一个MSS, 每收到一个确认,增加一个。 1 -> 2 -> 4 -> 8 … 当发生超时的时候,假设8超时,cwnd 设为 4MSS 然后新的cwdn 大于等于 4MSS 的时候,进入平缓模式
拥塞避免模式 慢启动,指数级增长,拥塞避免线性增加。 出现超时。重新开始慢启动,阈值为当前一半。
出现快速恢复,说明有冗余ACK , 冗余ACK说明只是这一个包的问题 超时说明后面的包都没有到 ssthresh 阈值需要设置。
超时:慢启动。 冗余ACK 迅速恢复。 大于等于阈值:拥塞避免。
初始状态,初始状态为1 ,阈值设为64kb,冗余ACK = 0, 当收到一个冗余ACK ,冗余ACK ++,收到一个新的ACK 的时候,cwnd += MSS,冗余ACK 清0,发送新的报文段 当cwnd >= 阈值,进入拥塞避免模式 每收到一个ACK, cwnd = cwnd + MSS * (MSS / cwnd) 线性增长,冗余ACK 清0, 发送新报文 收到冗余ACK ,冗余ACK ++ 如果冗余ACK == 3, 那么阈值设为cwnd一半, cwnd 设为阈值 + 3 * MSS(有三个冗余ACK) 进入快速恢复 没收到一个冗余ACK ,cwnd += MSS 收到新的ACK cwnd = 阈值, 冗余ACK = 0到拥塞避免 所有的模式下发生超时都会进入到慢启动 慢启动下如果冗余ACK == 3 ,进入到快速恢复状态
|