TCP协议摘要(二) 常见问题

来源:互联网 发布:广东省大数据局级别 编辑:程序博客网 时间:2024/06/14 23:28

Q: TCP协议栈sync包的重传策略.
A: SYNC包的重传并未区别与数据包的重传, 但是SYNC包的重传没有办法根据rtt来算RTO, 因为根本接收不到任何ACK包, 所以TCP协议栈需要配置具有退避性质的重传间隔, Linux TCP协议栈SYNC包的头一次重传时间是1Sec, 之前是指数退避, 总共重发5个SYNC包(可以通过socket option来配置).

Q:ACK包的发送策略.
A: ACK发送时机分为以下几种.

* 收到乱序的报文段的时候,  TCP会发送一个ACK包, 但是不是选择性的确认这个收到的乱序的报文段, 而是重发了前一个收到的顺序的报文包的ACK,  告诉发送端期待的下一个序号的报文段. * Delay ACK的机制: 收到一个报文包后,一般等待200ms之后发送ACK. * 连续收到2.3个报文包后, 立刻发送ACK确认包(当然是最大确认原则, 把接收窗口尽量往右移动). * ACK包没有确认机制, 没有超时重发. 

Q: 如何确定一个ACK包是重复的ACK包?
A: 符合一下所有条件的, 则认为是重复的ACK包. (Stevens TCP/IP Illustrated Vol II p970).

* 没有ack确认新的数据* ack包中没有报文数据(payload为空)* 接收窗口没有变化* 本地存在unack的发送数据* ACK包中的ack序号为本地接收到的上一次最新的确认数据的ack序号(不是小于本地的最新ack序号).  

Q: 接收窗口大小是否就是等于socket接收缓冲大小?
A: TCP的接收窗口总的大小是固定的, 也就是socket的接收缓冲大小.
接收窗口的实际有效大小 = 接收窗口的总大小 - 已经确认收到未被应用程序读取的内容大小.

本端通知对端接收窗口的大小不一定等于本端接收窗口的实际有效长度, 具体可以参考上面Silly Window中所述.

接收窗口的大小具有一个范围, 在不同的平台限制稍有不同.

Q: TCP的定时器的精度:
A: TCP协议有4个定时器: 重传定时器, 坚持定时器, 保活定时器, 2MSL定时器. 定时器的精度与TCP的实现有关, 有一些是200ms, 一些是500ms.

Q: 网络质量不好时, TCP内部何时发送RST复位信号.
A: 首次分组传输与复位信号传输之间的时间差约为9分钟(中间伴随重传),该时间在目前的TCP实现中是不可变的. 其实这个时间与具体的实现有关系, solaris是2min钟.

Q: 何为TCP的半开连接?
A: TCP两端中的1端系统如果shutdown了, 另外一端是不知道对端的情况的, 会继续认为连接是OK的, 避免这种情况, 需要做TCP的连接保活.

Q: Linux中TCP协议栈中一些关键参数值?
A:
MAX_TCP_WINDOW : 32767U - 不开window scaling的情况下的最大TCP接收窗口.
TCP_BASE_MSS : 512 - 默认的MTU大小.
TCP_MIN_MSS: 88 - MTU probe机制下允许的最小MTU.
TCP_SYN_RETRIES :5 - SYNC包最大的重试次数.
TCP_SYNACK_RETRIES: 5 - 三次握手中发送SYN/ACK包的最大重试次数.
TCP_TIMEWAIT_LEN: 60Hz(sec) - TIMEWAIT持续最大时间.
TCP_FIN_TIMEOUT : 60Hz(sec) - FIN_WAIT2 deadlock breaker.
TCP_RTO_MAX : 120Hz(sec)
TCP_RTO_MIN: 200ms
TCP_TIMEOUT_INIT: 1Hz(sec)- SYNC包重传间隔, RFC6298 2.1 initial RTO value.
TCP_TIMEOUT_FALLBACK: 3Hz(sec)-没有任何RTT数据情况下, 第一次重传后的退避重传间隔.
TCP_KEEPALIVE_TIME: 120*60Hz.
TCP_KEEPALIVE_PROBES: 9 - 保活探测最多发送的探测包.
TCP_KEEPALIVE_INTVL: 75Hz. -保活探测包发送的间隔.
MAX_TCP_SYNCNT: 127. - SYN包重发可以通过socket option设置, 此参数为最大值.
TCP_INIT_CWND : 10 - 初始拥赛窗口大小.

Q: 连接关闭阶段, 如果网络质量不好, 丢包或者后发先至的情况下, TCP协议栈是如何处理的?
A:

* 被动关闭的一端, 处于LAST-ACK状态下, 在未收到最后一个ACK的情况下, 会重发FIN-ACK包, 此时接收到对端的报文, 只处理控制信息, 数据信息直接丢掉. * 主动关闭的一方, 处于FIN-WAIT-1或者FIN-WAIT-2状态, 如果对方没有ACK本端报文包, 都会启动重发机制, 此时既处理对端报文中的控制信息, 也处理数据信息. * TIME-WAIT状态下, 不处理对方发过来的报文包, 直接丢掉, 必要时发送RESET包. * LAST-ACK和FIN-WAIT(非应用层使用shutdown来维持一个半开连接)状态下, 存在一直收不到对端的控制报文包这样情况, 这种残留的无效的连接会占用系统的资源, BSD系统会启动一个10min的timer来关闭这中连接.* 如果出现FIN后发先至等情况下, 收到FIN包后, 会根据状态机规则更改本地状态, 如果是本地关闭进入LAST-ACK状态后, 后至的数据包都将不处理. 

Q: 何种情况下发送RST包? 接收端如何处理RST包?
A: RST大多在TCP目前的状态下, 收到了错误的报文的情况下发送, 例如:

* 对方断开未打开或者刚刚关闭了, 发送任何TCP包都会收到RESET包. * 连接处在LISTEN状态, 从没有发送过数据到对方, 却收到对方的ACK包了(包括数据包中设置了ACK标识). * 已经收到了对方FIN包, 这时候又收到了对方的新序号的数据包.* 本端进程是CLOSE-WAIT状态了, 然后ctrl+c退出程序, 会发送RESET包, 如果是ESTABLSH状态, ctrl+c退出程序会发送FIN包. * 当然还有其他很多TCP RESET的发送情况. * SO_LINGER TCP选项可以设置在close一个连接的时候发送Fin还是RESET包. * 多次重传对方还未收到数据(9min钟的重传数据包). 

接收端收到RESET包后,需要检测RESET包的合法性(如果本端的SYNC包被ACK过, 则检测RESET包的序号包是否在接收WIN范围内, 如果本端的SYNC未被ACK过,也即没有发送过其他的包,则检测RESET包的ackno是否SYNC包的发送序列号, 此规则是没有支持FastOpen等情况下). 如果合法, 会直接关闭本地的TCP连接以及回收TCP连接的所有资源, 不会走Graceful的关闭流程(发FIN包到对方等策略), 所以谨慎使用S0_LINGER选项.

Q: FIN包的发送策略?
A:
* FIN包是占有一个字节的标识位的.
* 在发送FIN包的时候,如果有发送区buf中还有未发送的数据包(当然也是允许发送的), 会组成一个数据包带FIN标志发送出去, 顾名思义, 发了FIN包后, 是不会再发送新的数据包了, 但是旧序号数据包的重传机制仍然启作用.
* FIN包的发送与拥塞窗口的关系? Linux协议栈的实现是如果FIN报文包不携带有数据(发生队列无数据), 则不受拥塞窗口的限制, 可以直接发送, 否则受拥塞窗口的限制.
* FIN包后发先至情况下, 主要根据接收端收到FIN包后的状态机取值, 来决定是否接收后至的报文数据段信息.
* FIN包发送与重传机制没有必然关系, 发送了FIN后, 重传机制仍然启作用.
* 极端情况下存在FIN包卡在发送buf中一直没有机会发送出去, 例如对端0接收窗口的case, 或者上网出口路由down机了. 这种情况下如何处理TCP的连接关闭呢? 我想应该会有timer机制来保证从协议之卸载掉这个连接.

Q: TCP的何种状态下不能接收数据包?
A: RFC 793, chapeter 3.9, “SEGMENT ARRIVES” 详细的说明的了TCP哪些状态不能处理接收数据包, 强烈推荐读一下这个RFC文档.
TCP收到报文包时, 首先做有效性校验, 然后检查是否更新状态机, 再把报文丢到数据处理函数中.
以下状态, 不会把数据包丢给RecvBuf中的.

  • CLOSE-WAIT STATE
  • CLOSING STATE
  • LAST-ACK STATE
  • TIME-WAIT STATE

实验
1. TCP的SendBuf设置的很大, 一下子发送很多的数据(注意要设置nonblock模式), 而TCP的RecvBuf设置的很小且接收进程读取数据的频率很小, 关闭tcp的发送端连接, 观察TCP的相关数据.
结果是发送进程close掉socket, 且进程退出, 内核层的tcp协议栈并未马上移除这个连接, 连接上仍然有数据流动, 且把写入到TCP连接的发送buf中的数据全部发送完毕, 才发送Fin包(发送FIN包之前连接仍然是ESTABLISHED状态), 连接走的正常的连接关闭流程. 服务器端的连接则变成了CLOSE_WAIT状态了(如果服务器不主动关闭连接).

当然接收进程在发送进程close掉socket后不能发送数据到发送端, 不然就收到RESET包.

2.在上述的试验条件下, 加入接收端与也往发送端发数据, 但是发送端不读取数据.
结果会发现, 当发送端close socket的时候, 会直接主动发送RESET包(As outlined in RFC 2525, section 2.17, we send a RST here because data was lost), 发送缓冲中的数据不会发送了. 在Linux协议栈的实现中, 当close(或者shutdown)掉一个socket时, 如果发现socket上的接收buf中有数据, 则会主动发送RESET包, 然后这条连接直接被close了.

3.基于1的试验条件下,加入丢包的条件会如何? (TODO).

Reference

http://www.ietf.org/rfc/rfc793.txt
http://tools.ietf.org/html/rfc2525
LWIP开源项目

0 0