TCP

TCP

TCP 头

三次握手

四次挥手

起始序列号 ISN

ISN 为什么不从 1 开始

client 第一次尝试建立连接: client 发送 1,2,3 数据包给 server,我们假设这个时候 3 这个数据包丢失了。紧接着 client 第二次尝试建立连接: client 发送 1,2 给 server,主观上并没有想发送 3 这个数据包,但是第一次的 3 数据包可能又会回来发送给 server,因此造成数据错误)。

起始序列号 ISN 如何计算

起始 ISN 是基于时钟的,每 4 毫秒加一,转一圈要 4.55 个小时。

TCP 初始化序列号不能设置为一个固定值,因为这样容易被攻击者猜出后续序列号,从而遭到攻击。 RFC1948 中提出了一个较好的初始化序列号 ISN 随机生成算法。

ISN = M + F (localhost, localport, remotehost, remoteport)

M 是一个计时器,这个计时器每隔 4 毫秒加 1。F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。

异常情况如何响应

出现如下异常情况,TCP 会如何响应。

Server 端口没有打开

内核给 Client 响应一个 RST

Server 所在的机器都没有开机

Client 等待 6s 重发一个 SYN,无响应则等待 24s 再发一个,若总共等待 75s 之后,则触发 ETIMEDOUT。通过 setConnectTimeout() 来改善这个等待时间,使其可以在合理的毫秒以内返回。

Server 宕机

客户端持续重传,并且 read 调用会阻塞,一段时间后 TIMEOUT,使用 setReadTimeout() 来改善这个阻塞时间。

Server 宕机又重启

收到了一个这个连接上不该有的,所以响应一个 RST

TCP close() vs shutdown()

  • 调用 close() 函数会把读和写两个方向的,同时关闭
  • 调用 shutdown() 可以只关闭一半的 TCP 连接。

大量 TIME_WAIT 状态

如果存在大量的 TIME_WAIT,往往是因为短连接太多,不断的创建连接,然后释放连接,从而导致很多连接在这个状态,可能会导致无法发起新的连接。解决的方式往往是:

  • 打开 tcp_tw_recycletcp_timestamps 选项;
  • 打开 tcp_tw_reusetcp_timestamps 选项;
  • 程序中使用 SO_LINGER,应用强制使用 rst 关闭。

什么时候发送 RST 响应

当客户端收到 Connection Reset,往往是收到了 TCP 的 RST 消息,RST 消息一般在下面的情况下发送:

  • 试图连接一个未被监听的服务端;
  • 对方处于 TIME_WAIT 状态,或者连接已经关闭处于 CLOSED 状态,或者重新监听 seq num 不匹配;
  • 发起连接时超时,重传超时,keepalive 超时
  • 在程序中使用 SO_LINGER,关闭连接时,放弃缓存中的数据,给对方发送 RST