SO_KEEPALIVE 在调用 write() 期间不起作用?

Pau*_*sta 5 c sockets keep-alive

我正在开发一个套接字应用程序,它必须对网络故障具有健壮性。

该应用程序有 2 个正在运行的线程,一个等待来自套接字的消息(一个 read() 循环),另一个向套接字发送消息(一个 write() 循环)。

我目前正在尝试使用 SO_KEEPALIVE 来处理网络故障。如果我只在 read() 上被阻止,它就可以工作。连接丢失(移除网络电缆)几秒钟后,read() 将失败并显示“连接超时”消息。

但是,如果我在网络断开连接后(并且在超时结束之前)尝试写入(),则 write() 和 read() 都将永远阻塞,不会出错。

这是一个剥离的示例代码,它将 stdin/stdout 定向到套接字。它侦听端口 5656:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

int socket_fd;

void error(const char *msg) {
    perror(msg);
    exit(1);
}

//Read from stdin and write to socket
void* write_daemon (void* _arg) {
    while (1) {
        char c;
        int ret = scanf("%c", &c);
        if (ret <= 0) error("read from stdin");
        int ret2 = write(socket_fd, &c, sizeof(c));
        if (ret2 <= 0) error("write to socket");
    }
    return NULL;
}

//Read from socket and write to stdout
void* read_daemon (void* _arg) {
    while (1) {
        char c;
        int ret = read(socket_fd, &c, sizeof(c));
        if (ret <= 0) error("read from socket");
        int ret2 = printf("%c", c);
        if (ret2 <= 0) error("write to stdout");
    }
    return NULL;
}


//Enable and configure KEEPALIVE - To detect network problems quickly
void config_socket() {
    int enable_no_delay   = 1;
    int enable_keep_alive = 1;
    int keepalive_idle     =1; //Very short interval. Just for testing
    int keepalive_count    =1;
    int keepalive_interval =1;
    int result;

    //=> http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/#setsockopt
    result = setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE, &enable_keep_alive, sizeof(int));
    if (result < 0)
        error("SO_KEEPALIVE");

    result = setsockopt(socket_fd, SOL_TCP, TCP_KEEPIDLE, &keepalive_idle, sizeof(int));
    if (result < 0) 
        error("TCP_KEEPIDLE");

    result = setsockopt(socket_fd, SOL_TCP, TCP_KEEPINTVL, &keepalive_interval, sizeof(int));
    if (result < 0) 
        error("TCP_KEEPINTVL");

    result = setsockopt(socket_fd, SOL_TCP, TCP_KEEPCNT, &keepalive_count, sizeof(int));
    if (result < 0) 
        error("TCP_KEEPCNT");
}

int main(int argc, char *argv[]) {
    //Create Server socket, bound to port 5656
    int listen_socket_fd;
    int tr=1;
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t clilen = sizeof(cli_addr);
    pthread_t write_thread, read_thread;

    listen_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_socket_fd < 0)
        error("socket()");

    if (setsockopt(listen_socket_fd,SOL_SOCKET,SO_REUSEADDR,&tr,sizeof(int)) < 0)
        error("SO_REUSEADDR");

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(5656);
    if (bind(listen_socket_fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("bind()");

    //Wait for client socket
    listen(listen_socket_fd,5);
    socket_fd = accept(listen_socket_fd, (struct sockaddr *) &cli_addr, &clilen);
    config_socket();
    pthread_create(&write_thread, NULL, write_daemon, NULL);
    pthread_create(&read_thread , NULL, read_daemon , NULL);
    close(listen_socket_fd);
    pthread_exit(NULL);
}
Run Code Online (Sandbox Code Playgroud)

要重现该错误,请使用 telnet 5656。如果将在连接丢失后几秒后退出,除非我尝试在终端中写入一些内容。在这种情况下,它将永远阻塞。

所以,问题是:出了什么问题?如何解决?还有其他选择吗?

谢谢!


我尝试使用 Wireshark 检查网络连接。如果我不调用 write(),我可以看到 TCP keep-alive 包正在发送,并且连接在几秒钟后关闭。

相反,如果我尝试 write(),它会停止发送 Keep-Alive 数据包,而是开始发送 TCP 重传(对我来说似乎没问题)。问题是,每次失败后重传之间的时间越来越长,似乎永远不会放弃并关闭套接字。

有没有办法设置最大重传次数或类似的东西?谢谢

DXM*_*DXM 2

不确定其他人是否会给你更好的选择,但在我参与的几个项目中,我们遇到了非常相似的情况。

对于我们来说,解决方案就是简单地将控制权掌握在自己手中,而不是依赖底层操作系统/驱动程序来告诉您连接何时终止。如果您同时控制客户端和服务器端,则可以引入自己的 ping 消息,这些消息会在客户端和服务器之间反弹。通过这种方式,您可以 a) 控制自己的连接超时 b) 轻松保存指示连接运行状况的记录。

在最新的应用程序中,我们将这些 ping 作为带内控制消息隐藏在通信库本身内,因此就实际的客户端/服务器应用程序代码而言,连接超时就起作用了。