问题场景
在使用 QT 开发一个客户端 App 的时候,我们通过 QTcpSocket 与后台服务器进程通信。 后台程序使用其他语言编写。 问题: 在客户端启用之后尝试建立与后台程序的 TCP 连接的时候,发现连接速度非常慢(肉眼可见的慢)。
我的客户端代码:
...
m_Socket = new QTcpSocket();
m_RemoteHost = "0.0.0.0";
m_RemotePort = 20021;m_Socket->connectToHost(m_RemoteHost, m_RemotePort);
if (!m_Socket->waitForConnected(5000)) {// error
}
...
问题分析
根据本人从事网络开发的经验来看,对于建立一个 TCP 连接,即使服务器在公网,在服务器压力正常的情况下,连接时间应处于 50ms-100ms。 此时我们的服务器还是处于同一局域网中,且当前并没有其他连接, 因此怀疑问题出在 QTcpSocket 上。(服务器做过单独的测试,连接速度没有问题)
没有尝试获取 QTcpSocket 的日志,直接使用 Wireshark 抓包,看看导致是慢在了哪里?
抓的包如下图:
嗬,就淡看这么多的红色记录,不慢才怪了,下来我们仔细看看问题的原因:
10 条红色记录,分别是 5 次我们发送的 TCP SYN 被拒绝,服务器响应 TCP RST的记录。
服务器为什么拒绝我们的连接请求?
点开我们的第一个 SYN 包,详细结构如下:
这里看到,TCP 包没有任何问题。 问题在于 IP 包,我们客户端使用的 IPv6., 额,好吧, QTcpSocket 默认竟然使用的 IPv6. 而我们的服务器只监听在了 IPv4 的端口, 因此连接无法建立,服务器选择 reset 我们的连接。
在看 log, 发现 QTcpSocket 在初次连接失败之后,没有直接回退到 IPv4 协议,而是选择了重试。从图中我们也能看到,它重试了五次. 再进一步,我们可以看出每次重试的间隔为 500ms. 额滴天,就重试这五次,花费了 2s 时间。
在 5 次重试之后,QTcpSocket 选择回退到 IPv4, 之后连接成功.
由 wireshark log 可以看到,我们当前环境下一个正常的 TCP 连接需要时间是 14.714993 - 14.714753 = 0.000240s, 也就是说在 1ms 级别(我的测试服务器和客户端在一台机器), 硬生生的被拖延到 2s.
如何修改代码?
...
m_Socket = new QTcpSocket();
m_RemoteHost = "0.0.0.0";
m_RemotePort = 20021;// 指定使用 IPv4
m_Socket->connectToHost(m_RemoteHost, m_RemotePort, QAbstractSocket::ReadWrite, QAbstractSocket::IPv4Protocol);
if (!m_Socket->waitForConnected(5000)) {// error
}
...
问题非常简单,却让人比较经验,竟然默认使用的 IPv6。 IPv6 已经这么流行了吗? 竟然至于默认使用 IPv6 协议。 佩服佩服啊