Я разрабатываю приложение для сокетов, которое должно быть устойчивым к сбоям в сети.
Приложение имеет 2 работающих потока, один ожидает сообщения из сокета (цикл read ()), а другой отправляет сообщения в сокет (цикл write ()).
В настоящее время я пытаюсь использовать SO_KEEPALIVE для обработки сетевых сбоев.
Это работает нормально, если я заблокирован только на чтение (). Через несколько секунд после того, как соединение потеряно (сетевой кабель отключен), read () завершится ошибкой с сообщением «Время соединения истекло».
Но если я попытаюсь выполнить wrte () после отключения сети (и до истечения времени ожидания), 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);
}
Чтобы воспроизвести ошибку, используйте telnet 5656.
If будет выходить через пару секунд после потери соединения, если я не попытаюсь что-то написать в терминале. В этом случае он будет заблокирован навсегда.
Итак, вопросы: что не так? как это исправить? Есть ли другие альтернативы?
Спасибо!
Я пытался использовать Wireshark для проверки сетевого подключения. Если я не вызываю write (), я вижу отправку пакетов поддержки активности TCP, и через несколько секунд соединение закрывается.
Если вместо этого я пытаюсь написать (), он прекращает отправку пакетов Keep-Alive и вместо этого начинает отправлять повторные передачи TCP (мне кажется, это нормально). Проблема в том, что время между повторными передачами увеличивается и увеличивается после каждого сбоя, и кажется, что он никогда не сдается и не закрывает сокет.
Есть ли способ установить максимальное количество повторных передач или что-то подобное?
Спасибо