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 重传(对我来说似乎没问题)。问题是,每次失败后重传之间的时间越来越长,似乎永远不会放弃并关闭套接字。
有没有办法设置最大重传次数或类似的东西?谢谢
不确定其他人是否会给你更好的选择,但在我参与的几个项目中,我们遇到了非常相似的情况。
对于我们来说,解决方案就是简单地将控制权掌握在自己手中,而不是依赖底层操作系统/驱动程序来告诉您连接何时终止。如果您同时控制客户端和服务器端,则可以引入自己的 ping 消息,这些消息会在客户端和服务器之间反弹。通过这种方式,您可以 a) 控制自己的连接超时 b) 轻松保存指示连接运行状况的记录。
在最新的应用程序中,我们将这些 ping 作为带内控制消息隐藏在通信库本身内,因此就实际的客户端/服务器应用程序代码而言,连接超时就起作用了。