问题
问题简述
如下图. server docker restart 后, client 端写入的日志丢失, 并且无报错. 因为不支持时序图, 把时序图代码嵌入在代码里.
“`sequence
client->server: log_data
client->server: log_data
server->server: docker restart
server->client: fin
client->server: log_data loss without error
“`
tcp state diagram
问题定位过程
为什么卡在 CLOSE_WAIT.
看 tcp 状态转换图, 可以看到 client 收到了 fin, 一直没有 recv, 一直卡在 CLOSE_WAIT. 和实际的代码是吻合的. 那么, 为什么在 server docker restart 引发 CLOSE_WAIT 后, client 发消息仍然不报错呢? 因为:
tcp 协议允许 client 在收到 fin 后, 继续发送消息.
server 在 docker restart 后 ip 改变, client 还是往原来的 ip 发送消息, 没有主机通知 client rst, 导致消息在系统 buffer 里积压.
积压信息如下:
root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0
tcp 1 402 10.0.0.186:62281 10.0.0.16:27017 CLOSE_WAIT 4308/server
root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0
tcp 1 70125 10.0.0.186:62281 10.0.0.16:27017 CLOSE_WAIT 4308/server
此时, 在 elixir socket 接口层面来看, 不管 socket 的状态, 还是发送, 都是 ok 的.
iex(client@client.)25> socket |> :inet.port
{:ok, 57395}
iex(client@client.)26> socket |> :gen_tcp.send(“aaa”)
:ok
如果主动 close, 则会进入 LAST_ACK 状态
iex(client@client.)27> socket |> :gen_tcp.close()
:ok
root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0
tcp 1 70126 10.0.0.186:62281 10.0.0.16:27017 LAST_ACK –
CLOSE_WAIT 的恢复
如果代码还是只发不收. 是检测不到 CLOSE_WAIT 的. 显然, 应用层心跳是一个解决方案. 那么, 不使用心跳, 只发不收的情况下, 什么时候才能检测到错误呢?
send buffer 满
todo 深究 tcp keepalive, 不使用 keepalive 情况下的 tcp 最大链接空闲时间.