在Linux上通过套接字减小TCP最大分段大小(MSS)

3
在我们的服务器需要更新低资源传感器/跟踪设备固件的特殊应用中,我们遇到了一个问题,即在接收新固件数据包的远程设备(客户端)中有时会丢失数据。连接是通过GPRS网络的TCP/IP连接。该设备使用SIM900 GSM芯片作为网络接口。
这些问题可能是由于设备接收太多数据引起的。我们尝试通过更少发送数据包来减少流量,但有时仍然出现错误。
我们联系了SIM900芯片的当地零售商,他们负责提供技术支持并可能联系芯片的中国制造商(simcom)。他们说,首先我们应该尝试减少连接的TCP MSS(最大分段大小)。
在我们的服务器上,我做了以下操作:
static int
create_master_socket(unsigned short master_port) {

    static struct sockaddr_in master_address;
    int master_socket = socket(AF_INET,SOCK_STREAM,0);
    if(!master_socket) {
            perror("socket");
            throw runtime_error("Failed to create master socket.");
    }

    int tr=1;
    if(setsockopt(master_socket,SOL_SOCKET,SO_REUSEADDR,&tr,sizeof(int))==-1) {
            perror("setsockopt");
            throw runtime_error("Failed to set SO_REUSEADDR on master socket");
    }

    master_address.sin_family = AF_INET;
    master_address.sin_addr.s_addr = INADDR_ANY;
    master_address.sin_port = htons(master_port);
    uint16_t tcp_maxseg;
    socklen_t tcp_maxseg_len = sizeof(tcp_maxseg);
    if(getsockopt(master_socket, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, &tcp_maxseg_len)) {
            log_error << "Failed to get TCP_MAXSEG for master socket. Reason: " << errno;
            perror("getsockopt");
    } else {
            log_info << "TCP_MAXSEG: " << tcp_maxseg;
    }
    tcp_maxseg = 256;
    if(setsockopt(master_socket, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, tcp_maxseg_len)) {
            log_error << "Failed to set TCP_MAXSEG for master socket. Reason: " << errno;
            perror("setsockopt");
    } else {
            log_info << "TCP_MAXSEG: " << tcp_maxseg;
    }
    if(getsockopt(master_socket, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, &tcp_maxseg_len)) {
            log_error << "Failed to get TCP_MAXSEG for master socket. Reason: " << errno;
            perror("getsockopt");
    } else {
            log_info << "TCP_MAXSEG: " << tcp_maxseg;
    }
    if(bind(master_socket, (struct sockaddr*)&master_address,
                            sizeof(master_address))) {
            perror("bind");
            close(master_socket);
            throw runtime_error("Failed to bind master_socket to port");

    }

    return master_socket;
}

运行上述代码会得到以下结果:
I0807 ... main.cpp:267] TCP_MAXSEG: 536
E0807 ... main.cpp:271] Failed to set TCP_MAXSEG for master socket. Reason: 22 setsockopt: Invalid argument
I0807 ... main.cpp:280] TCP_MAXSEG: 536

正如您所看到的,输出的第二行中存在问题:setsockopt返回“无效参数”。

为什么会这样?我了解到在设置TCP_MAXSEG时存在一些限制,但我没有遇到过这种行为的报告。

谢谢, Dennis


听起来好像大家都在猜测。设备应该正确处理标准MTU和MSS:如果它不能,或者更可能是它有其他TCP错误,他们应该修复它。 - user207421
2个回答

7
除了xaxxon的回答,我想指出我尝试强制我的Linux仅发送最大TCP段大小的经验(比它们通常的大小要小):
最简单的方法就是使用iptables: sudo iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN --destination 1.1.1.1 -j TCPMSS --set-mss 200
这将覆盖出站连接上的远程传入SYN / ACK数据包,并将MSS强制为特定值。
注意1:你在wireshark中看不到这个过程,因为wireshark会在此之前捕获。 注意2:iptables不允许你增加MSS,只能降低它。
另外,我还尝试了像Dennis所做的设置套接字选项TCP_MAXSEG。在从xaxxon那里得到修复后,这也起作用了。
注意:应该在连接建立后读取MSS值。否则,它会返回默认值,这让我(和Dennis)走了弯路。
最后,我还遇到了其他一些问题: 我遇到了TCP离线处理问题,尽管我的MSS已经正确设置,但wireshark仍显示发送的帧太大了。你可以通过以下方式禁用此功能: sudo ethtool -K eth0 tx off sg off tso off 这花费了我很长时间才弄清楚。
TCP有很多高级功能,如MTU路径发现,实际上会尝试动态增加MSS。有趣而酷炫,但显然很令人困惑。不过,在我的测试中没有出现问题。
希望这能帮助将来有人尝试做同样的事情。

1

这就是它,谢谢!不幸的是问题的核心仍然存在,只是以另一种形式出现:现在setsockopt返回0,但值没有改变。现在我要进行更多的研究! - dennis90
@dennis90 你尝试过禁用 Nagle 算法并使用较小的发送调用吗?我猜这并不能完全保证,但可能会起作用。 - xaxxon
禁用 Nagle 算法是否意味着只需在服务器的 accept() 调用返回的套接字上设置 TCP_NODELAY?然后我确实尝试过,但它没有起作用。不过我现在会再试一次,也许之前做错了什么。 - dennis90
是的,基本上操作系统会尝试在你提供数据后立即发送它,而不是等待一段时间看看你是否会提供更多。因此,如果你发送小块数据,它很可能会立即发送它。所以,请发送小块数据。 - xaxxon
1
@dennis90 做得很好.. 但我的意思是把细节放在你的源代码中。 - xaxxon
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接