文章目录
一些很简单的问题。。
1)网络为什么会发生丢包 ?
接收到的分组校验出错、分组在网络中超出最大存活时间、路由器接收分组数量达到上限后或者TTL为0,会丢弃多余分组。
UDP
TCP可靠数据传输原理
- 校验和(TCP首部、TCP数据部分、伪首部(伪首部来自IP首部 如源目的IP地址、传输层协议) 的校验和)
- 确认超时重传:发送接受确认报文超时(RTO) 时重传,解决丢包问题和校验和出错问题。引出校验和出错了会直接丢弃不反悔ACK。(大多数TCP实现中会做下修改:超时时间间隔加倍,因为持续的超时重传会使拥塞更加严重。)
- 序列号:解决重传的时候 接收方看出这是重传的包还是重新发的包。以及滑动窗口多次传输多个报文段时要通过序列号排序和根据收到的序列号来决定确认号。确认号:发送方通过确认号来知道自己发出的哪些包被正确收到了,进而要不要移动滑动窗口和重传。
滑动窗口
发送方:
sendbase:最早未确认的序列号。
nextseqnum:下一个要发送但还没发送的序列号。
1) 从应用层接收到数据,假如此时定时器未启动,启动定时器。向网络层交付报文段,nextseqnum=nextseqnum+length(data);
2)定时器超时:重传sendbase对应的报文段,重启定时器。
3)收到ack,确认号为y:if(y>sendbase) {sendbase=y; 重启定时器。}
接收方:
rcv_base:最早期待还未收到(他后面有的被收到了)
1)接受到的包损坏则直接丢弃不做处理。
2)序号在rcv_base之前(就是收到重复的包),或者 在rcv_base之后,都返回 一个确认号为rcv_base的ack包。
3)序号是rcv_base,交付给应用层一组连续的报文端,并更新rcv_base.
MSS及TCP报文何时发送
MSS只有在syn时协商,处于TCP首部选项,就算你发给别人的首部带有MSS,别人可能也不按MSS发送给你。(我MSS看不懂。。。)
当接收端来不及取走Receive Windows里的数据,会导致发送端的发送窗口越来越小。到最后,如果接收端腾出几个字节并告诉发送端现在有几个字节的window,而我们的发送端会义无反顾地发送这几个字节。为此,TCP引入了Nagle算法。
- 如果包长度达到MSS,则允许发送;
- 如果该包含有FIN,则允许发送;
- 设置了TCP_NODELAY选项,则允许发送;•未设置TCP_CORK选项(加上这个选项的话所有的ack收到也不想发送tcp报文段,还想等待更多应用层的数据。)时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
- 上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
TCP流量控制
流量控制是一个速度匹配服务,即发送方的发送速率与接受方应用程序的读取速率相匹配。如果某应用程序读取数据时相对缓慢,而发送方发送的太多、太快,发送的数据就会很容易地使该连接的接受缓存溢出。
- TCP是全双工通信,在连接两端的发送方都各自维护一个接受窗口的变量,rwnd<=RcvBuffer-( LastByteRead(接收方应用进程从缓存读取数据流最后一个字节的编号)-LastByteRcvd(放入主机B接受缓存的数据流的最后一个字节的编号))。
- rwnd字段在TCP首部选项。
B告知A rwnd=0时,A无法向B发送数据,TPC规范要求只有有数据要发或者发确认包才会发送报文段,所以当B清空缓存后,无法将新的rwnd告诉A。那怎么办呢??
持续计时器 +零窗口探测报文段:为了解决这个问题,TCP为每一个连接设有一个持续计时器(persistence timer)。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。如果窗口仍然是零,那么收到这个报文段的一方就重新设置持续计时器。如果窗口不是零,那么死锁的僵局就可以打破了。
TCP拥塞控制
- 慢启动:从1个MSS开始指数增长。(1) 超时指示丢包了,cwnd=1,ssthresh=cwnd/2,进入慢启动模式。(2) cwnd到达ssthresh时,进入拥塞避免模式。
- 拥塞避免:每个RTT增加一个MSS。(1)超时指示丢包了,cwnd=1,ssthresh=cwnd/2,进入慢启动模式。(2)丢包事件也能由由三个冗余ACK事件触发,TCP对这种丢包事件的行为,相比于超时指示的丢包响应不那么剧烈:cwnd=1/2*(cwdn+3),ssthresh=1/2cwnd。进入快速恢复模式。
- 快速恢复:收到每个冗余的ACK,cwnd+=MSS,最终收到对于丢失报文的ACK时,TCP降低cwnd后进入拥塞避免模式。(1) 超时指示丢包了,cwnd=1,ssthresh=cwnd/2,进入慢启动模式。
几种TCP连接中出现RST的情况
- 端口未打开:服务器程序端口未打开而客户端来连接。这种情况很常见。特别是服务器程序core dump之后重启之前连续出现RST的情况会经常发生。
- 请求服务端超时:一个客户端连接服务器,connect返回-1并且error=EINPROGRESS。 直接telnet发现网络连接没有问题。ping没有出现丢包。用抓包工具查看,客户端是在收到服务器发出的SYN之后就莫名其妙的发送了RST。因为客户端设置了超时时间,超时后客户端会发送RST。
- 如果某个socket已经关闭,但依然收到数据也会产生RST。
TCP连接管理
说一下tcp三次握手过程
刚开始客户端处于CLOSED状态,服务端处于LISTEN状态。
- 第一步:客户端选择一个client_isn,并向服务端发送一个SYN报文段。(报文段不含应用层数据)
第一步完成后客户端处于SYN_SENT,服务端处于SYN_RECEIVED。 - 第二步:服务器通过这个SYN报文段。将确认号置为client_isn+1,服务端选择自己的初始序号server_isn,并向客户端发送一个SYNACK报文段。
第二步完成后客户端处于ESTABLISHED,服务端处于SYN_RECEIVED - 第三步:客户端收到SYNACK报文段后。将确认号置为server_isn+1,发送一个可以携带应用层数据的报文段。
第三步完成后客户端处于EATABLISHED,服务端处于ESTABLISHED。
tcp为什么是3次握手,而不是2或4次
- 可以让客户端和服务端都知道自己和对方收发信息功能正常。
- 可以让客户端和服务端都知道对方已经正确知道了自己的初始序列号。
假如没有第三次:
- 有可能 服务端到客户端的包含着初始化序列号的包丢了,所以需要第三次,这样的话没有第三次的ack,服务端就会超时重发。
- 服务端收到了客户端发的syn包之后,也收到了之前在链路滞留的syn包,服务端对第二个syn包作了ack,显然这是错误的。客户端收到这个ack之后被气死了,又重新发一个syn。
为什么不是四次:
虽然上面 假如没有第三次的第二种情况是客户端重新发一个syn,万一服务端没收到呢,,这样下去不久死循环了。三次已经保证很高的正确率了,没必要四次了因为变多一次肯定浪费资源呀。
ISN为什么要随机不变
- 从安全角度考虑:1)恶意机器伪造SYN包,ip地址乱填;服务器向乱填的ip地址回应的SYNACK包后;恶意机器猜出服务端的ISN,发送一个ACK包。这样服务端认为连接已经建立了。这样的话干一些坏事再也不用担心被封ip了。2)恶意机器得到客户端的序列号后,在第三次握手还没完成时发送一个伪造的RST包,服务端会以为这是客户端发的RST包,服务端就处于LISTEN状态。这样真正的客户端和服务端建立连接失败。
- 如果ISN固定,很可能新连接的服务端收到了以前连接的报文,且这个seq在服务端的接受窗口内,那就全乱套了,以后真的再来的话真的就被抛弃了。
SYN泛攻击解决
- 内核参数优化:
(1) 减少SYN重试次数:
net.ipv4.tcp_syn_retries = 6
sysctl -w net.ipv4.tcp_synack_retries=3
sysctl -w net.ipv4.tcp_syn_retries=3
(2) 超出处理能力时,对新来的SYN丢弃连接(影响用户体验):
net.ipv4.tcp_abort_on_overflow
(3) 适当增加accept queue队列长度 TCP之三:TCP/IP协议中backlog参数(队列参数)
另外,客户端connect()返回不代表TCP连接建立成功,有可能此时accept queue 已满,系统会直接丢弃后续ACK请求;客户端误以为连接已建立,开始调用等待至超时;服务器则等待ACK超时,会重传SYN+ACK 给客户端,重传次数受限 net.ipv4.tcp_synack_retries ,默认为5,表示重发5次,每次等待30~40秒,即半连接默认时间大约为180秒,该参数可以在tcp被洪水攻击是临时启用这个参数。
注:accept queue溢出,即便SYN queue没有溢出,新连接请求的SYN也可能被drop。
所以Backlog (全连接队列长度) 过小,accept queue溢出,握手第3步的ACK被丢弃,但client认为连接成功并发送数据,造成所谓慢请求(?不知道慢请求是什么玩意儿);
(4) SYN cookie
缺点:关于SYN Cookie和SYN Cache的再次讨论
1)由于无状态机制,如果客户端回应的ACK在传输过程中丢掉了,就会导致客户端和服务器端状态的不一致,客户端认为连接已经建立,但是服务器端没有建立,通常这个问题可以通过SYN-ACK的重传机制来解决,但是SYN Cookie中没有这部分,最后就是服务器发送RST中断连接。
2)SYN Cookie存在ACK Flood攻击缺陷 ACK Flood
3)如果攻击者攻破了算法,仅仅使用ACK就可以完成连接的建立。使得一些通过SYN包来进行过滤的防火墙失效。
- SYN Cache 我不会!
说下四次挥手的过程
- 收到一个FIN意味着对方不会向自己发送数据 了。一个TCP连接在收到一个FIN后仍能发送数据。
- CLOSE_WAIT:收到a的FIN后,B毫无疑问要发送ACK,此时进入CLOSE_WAIT状态。接下来,B可能还有数据要发给对方,直到没有了发送FIN报文给A。
说下TIME_WAIT
无语,这么小的图怎么一上传搞这么大?知道我的字好看,也不用这么放大给人看把,还好我这行云流水的字经得住任何考验。但是排版丑死了。
- A接受了FIN发送ACK并开始计时(记当前时刻为T),B没收到ACK,并在 T-0.5RTT(FIN发送的一瞬间)+RTO 这一时刻重传FIN包,然后 T-0.5RTT+RTO+0.5RTT=T+RTO 这一时刻A收到了重传的FIN包。A收到了重传的FIN包意味着要重传ACK包,也会重新计时。所以TIME_WAIT为RTO时保证了B一定会收到ACK。
- 还要保证在A关闭连接之前,这个连接的包从网络中消失。分为三种情况:
(1) B没收到ACK的时候,A没收到FIN(这个FIN一直在游荡,就是不去找A):记B发送FIN的时刻为T,A没收到FIN,自然不会发送ACK,B在T+RTO时就会重传FIN,A收到B重传的FIN的时间是T+RTO+0.5RTT,此时A就是TIME_WAIT状态,从A开始计时的时候,那个在网络游荡的FIN包已经存活了RTO+0.5RTT时长,所以TIME_WAIT的时间为MSL-(RTO+0.5RTT)就可以保证这种情况下的FIN包一定会从网络消失。
(2) A向B发送的ACK一直到达不了B(这个ACK一直在游荡,就是不去找B):记B发送FIN的时刻为T,A在T+0.5RTT时刻发送ACK,但是B一直没有收到,B在T+RTO时就会重传FIN,A收到B重传的FIN的时间是T+RTO+0.5RTT,此时A就是TIME_WAIT状态,从A开始计时的时候,那个在网络游荡的ACK包已经存活了RTO时长,所以TIME_WAIT的时间为MSL-RTO就可以保证这种情况下的ACK包一定会从网络消失。
(3) B收到ACK了,但是是超时后收到了ACK,因为超时了所以B也重发出去了FIN包,但是这个FIN包一直游荡在网络里,就是不去A 记A发送ACK的时刻为T(也就是刚开始进入TIME_WAIT状态的时刻),这种情况下的FIN包在T+0.5RTT(最大是0.5RTT,重发的FIN包在这里一定比ACK到的时间早)时刻产生并存活。所以TIME_WAIT的时间为MSL+0.5RTT就可以保证这种情况下的ACK包一定会从网络消失。
还有别的情况,比如A发给B的ACK,B接收不到,B又重发ACK,A又接受不到ACK,这样下去等多久也没用,所以我们不管这种情况。
综上所述:TIME_WAIT时间为max(RTO,MSL+0.5RTT)即可。
TIME_WAIT时期 sockt能被复用吗
假如A发起断开,但是A不想等待TIME_WAIT,可以采用SO_REUSEADDR选项,否则A在TIME_WAIT时期假如socket重新绑定会错误,采用这个选项便可以复用socket。假如B还是不变,意思就是下次要连接的对象还是这一次的,端口也是这一次的,这种情况下称为原来的连接化身。但SO_REUSEADDR选项禁止tcp这样化身之前的连接,可以复用socket只要不一模一样就好了。
而SO_REUSEPORT是一个与上面概念完全不同的选项。SO_REUSEPORT允许我们将任意数目的socket绑定到完全相同的源地址端口上。
SO_REUSEPORT解决惊群问题(解决的并不是epoll的惊群问题)🔗