zoukankan      html  css  js  c++  java
  • TCP KEEP-ALIVE和TCP_USER_TIMEOUT机制

    正常通信的情况下,send函数发送成功会返回发送数据的字节数。当有错误发生时,send返回-1,全局变量errno被设置。很多情况下,send返回-1是由于连接被对端关闭(对端发送了RST或者FIN包),这种情况errno会被设置为ECONNRESET(Connection reset by peer)。

    可是在对端的网线被拔、网卡被卸载或者禁用的时候,对端没有机会向本地操作系统发送TCP RST或者FIN包来关闭连接。这时候操作系统不会认为对端已经挂了。所以在调用send函数的时候,返回的仍然是我们指定发送的数据字节数。当我们无法通过send的返回值来判断对端是否存活的情况下,就要使用TCP Keep-alive机制了。

    在《Unix网络编程(卷一)》中提到,使用SO_KEEPALIVE套接字选项启用对套接字的保活(Keep-alive)机制。

    给一个TCP套接口设置保持存活(keepalive)选项后,如果在2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方发一个保持存活探测分节(keepalive probe)。

    TCP提供了这种机制帮我们判断对端是否存活,如果对端没有对KeepAlive包进行正常的响应,则会导致下一次对套接字的send或者recv出错。应用程序就可以检测到这个异常。

    设置KeepAlive机制的选项如下代码所示:

    int keep_alive = 1;
    int keep_idle = 5, keep_interval = 1, keep_count = 3;
    int ret = 0;
     
    if (-1 == (ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive,
        sizeof(keep_alive)))) {
        fprintf(stderr, "[%s %d] set socket to keep alive error: %s", __FILE__,
            __LINE__, ERRSTR);
    }
    if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle,
        sizeof(keep_idle)))) {
        fprintf(stderr, "[%s %d] set socket keep alive idle error: %s", __FILE__,
            __LINE__, ERRSTR);
    }
    if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_interval,
        sizeof(keep_interval)))) {
        fprintf(stderr, "[%s %d] set socket keep alive interval error: %s", __FILE__,
            __LINE__, ERRSTR);
    }
    if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_count,
        sizeof(keep_count)))) {
        fprintf(stderr, "[%s %d] set socket keep alive count error: %s", __FILE__,
            __LINE__, ERRSTR);
    }
     
    1. 设置SO_KEEPALIVE选项,将这个选项设置为1,代表打开KeepAlive机制。
    2. 设置TCP_KEEPIDLE选项,值为5秒,代表如果TCP连接上有五秒钟没有任何数据包传输,则启动保活机制,发送TCP Keep-alive机制。默认为2小时。
    3. 设置TCP_KEEPINTVL选项,值为1秒,代表如果启动保活机制,则每隔1秒发送一个Keep-alive包。默认为75秒。
    4. 设置TCP_KEEPCNT选项,值为3,代表如果对端对3次Keep-alive数据包都没有正常响应,则判断对端已经崩溃。默认为9。

    这样断网的判断问题就解决了。

    但是如果发送方发送的数据包没有收到接收方回复的ACK数据包,则TCP Keep-alive机制就不会被启动,而TCP会启动超时重传机制,这样就使得TCP Keep-alive机制在未收到ACK包时失效。在查阅这个问题时找到了stackoverflow上面的资料:http://stackoverflow.com/questions/5907527/application-control-of-tcp-retransmission-on-linux

    根据排名第一的回答表示,Linux Kernel 2.6.37中增加了一个叫做TCP_USER_TIMEOUT的socket选项。答案大意是,TCP_USER_TIMEOUT选项是TCP层的socket选项,选项接受unsigned int类型的值。值为数据包被发送后未接收到ACK确认的最大时长,以毫秒为单位,例如设置为10000时,代表如果发送出去的数据包在十秒内未收到ACK确认,则下一次调用send或者recv,则函数会返回-1,errno设置为ETIMEOUT,代表connection timeout。

    实现的代码应该如下:

    unsigned int timeout = 10000;
    if (-1 == setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout))) {
        fprintf(stderror, "set TCP_USER_TIMEOUT option error: %s", strerror(errno));
    }

    如上所述,使用TCP Keep-alive加上TCP_USER_TIMEOUT机制,就可以完美解决通信对端异常断网、掉电的情况下,连接被长期挂起的问题了。

    转自http://blog.leeyiw.org/tcp-keep-alive/;

  • 相关阅读:
    如何让pc端网站在手机上可以等比缩放的整个显示
    CSS
    常见的IE布局兼容问题
    CSS : 使用 z-index 的前提
    CSS : object-fit 和 object-position实现 图片或视频自适应
    CSS
    vscode
    如何识别Form字段中一对多或者多对多字段
    window.open简单使用
    由一个模型拿它的名字、app的名字、字段对象以及字段对象中的属性
  • 原文地址:https://www.cnblogs.com/yanenquan/p/3866503.html
Copyright © 2011-2022 走看看