Скільки часу локальна адреса сокета TCP, яка була обмежена недоступною після закриття?


13

У Linux (мої сервери в реальному часі знаходяться на RHEL 5.5 - нижче наведені посилання LXR на версію ядра) man 7 ip:

Адреса локальної розетки TCP, яка була зв'язана, недоступна протягом деякого часу після закриття, якщо не встановлено прапор SO_REUSEADDR.

Я не використовую SO_REUSEADDR. Як довго "деякий час"? Як я можу дізнатися, як довго це триває, і як я можу це змінити?

Я гуляв навколо цього і знайшов декілька інформаційних матеріалів, жодна з яких насправді не пояснює це з точки зору програміста програми. А саме:

  • TCP_TIMEWAIT_LEN в net/tcp.h"скільки часу чекати, щоб знищити стан TIME-WAIT", і фіксується на рівні "приблизно 60 секунд"
  • / proc / sys / net / ipv4 / tcp_fin_timeout є "Час утримувати сокет у стані FIN-WAIT-2, якщо він був закритий нашою стороною", і "Значення за замовчуванням - 60сек."

Де я спіткнувся в тому, щоб подолати розрив між моделлю ядра життєвого циклу TCP і моделлю портів програміста, недоступними, тобто в розумінні того, як ці стани ставляться до "деякого часу".


@Caleb: Щодо тегів, bind - це також системний виклик! Спробуйте, man 2 bindякщо ви мені не вірите. Справді, це, мабуть, не перше, про що думають люди, коли хтось каже "прив'яжи", так справедливо.
Том Андерсон

Я добре знав альтернативне використання bind, але тег тут спеціально застосовується до DNS-сервера. У нас немає тегів для кожного можливого системного дзвінка.
Калеб

Відповіді:


14

Я вважаю, що ідея недоступності програми для сокету полягає у тому, щоб дозволити приїзду будь-яких сегментів даних TCP, які ще перебувають у транзиті, та відмовитись від ядра. Тобто програма може викликати close(2)сокет, але затримка маршрутизації або помилки управління пакетами або що ви можете дозволити іншій стороні TCP-з'єднання на деякий час надсилати дані. У програмі вказано, що більше не хоче працювати з сегментами даних TCP, тому ядро ​​повинно просто відкинути їх, коли вони надходять.

Я зламав невелику програму на C, яку ви можете скласти та використовувати, щоб побачити, як триває час очікування:

#include <stdio.h>        /* fprintf() */
#include <string.h>       /* strerror() */
#include <errno.h>        /* errno */
#include <stdlib.h>       /* strtol() */
#include <signal.h>       /* signal() */
#include <sys/time.h>     /* struct timeval */
#include <unistd.h>       /* read(), write(), close(), gettimeofday() */
#include <sys/types.h>    /* socket() */
#include <sys/socket.h>   /* socket-related stuff */
#include <netinet/in.h>
#include <arpa/inet.h>    /* inet_ntoa() */
float elapsed_time(struct timeval before, struct timeval after);
int
main(int ac, char **av)
{
        int opt;
        int listen_fd = -1;
        unsigned short port = 0;
        struct sockaddr_in  serv_addr;
        struct timeval before_bind;
        struct timeval after_bind;

        while (-1 != (opt = getopt(ac, av, "p:"))) {
                switch (opt) {
                case 'p':
                        port = (unsigned short)atoi(optarg);
                        break;
                }
        }

        if (0 == port) {
                fprintf(stderr, "Need a port to listen on\n");
                return 2;
        }

        if (0 > (listen_fd = socket(AF_INET, SOCK_STREAM, 0))) {
                fprintf(stderr, "Opening socket: %s\n", strerror(errno));
                return 1;
        }

        memset(&serv_addr, '\0', sizeof(serv_addr));
        serv_addr.sin_family      = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port        = htons(port);

        gettimeofday(&before_bind, NULL);
        while (0 > bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
                fprintf(stderr, "binding socket to port %d: %s\n",
                        ntohs(serv_addr.sin_port),
                        strerror(errno));

                sleep(1);
        }
        gettimeofday(&after_bind, NULL);
        printf("bind took %.5f seconds\n", elapsed_time(before_bind, after_bind));

        printf("# Listening on port %d\n", ntohs(serv_addr.sin_port));
        if (0 > listen(listen_fd, 100)) {
                fprintf(stderr, "listen() on fd %d: %s\n",
                        listen_fd,
                        strerror(errno));
                return 1;
        }

        {
                struct sockaddr_in  cli_addr;
                struct timeval before;
                int newfd;
                socklen_t clilen;

                clilen = sizeof(cli_addr);

                if (0 > (newfd = accept(listen_fd, (struct sockaddr *)&cli_addr, &clilen))) {
                        fprintf(stderr, "accept() on fd %d: %s\n", listen_fd, strerror(errno));
                        exit(2);
                }
                gettimeofday(&before, NULL);
                printf("At %ld.%06ld\tconnected to: %s\n",
                        before.tv_sec, before.tv_usec,
                        inet_ntoa(cli_addr.sin_addr)
                );
                fflush(stdout);

                while (close(newfd) == EINTR) ;
        }

        if (0 > close(listen_fd))
                fprintf(stderr, "Closing socket: %s\n", strerror(errno));

        return 0;
}
float
elapsed_time(struct timeval before, struct timeval after)
{
        float r = 0.0;

        if (before.tv_usec > after.tv_usec) {
                after.tv_usec += 1000000;
                --after.tv_sec;
        }

        r = (float)(after.tv_sec - before.tv_sec)
                + (1.0E-6)*(float)(after.tv_usec - before.tv_usec);

        return r;
}

Я спробував цю програму на 3-х різних машинах, і я отримую змінний час, між 55 і 59 секундами, коли ядро ​​відмовляється дозволити користувачеві, котрий не має кореня, знову відкрити сокет. Я склав вищевказаний код до виконуваного файлу з назвою "відкривач" і запустив його так:

./opener -p 7896; ./opener -p 7896

Я відкрив ще одне вікно і зробив це:

telnet otherhost 7896

Це змушує перший екземпляр "відкривача" прийняти з'єднання, а потім закрити його. Другий екземпляр "відкривача" намагається перейти bind(2)до порту TCP 7896 щосекунди. "Відкривач" повідомляє про затримку від 55 до 59 секунд.

Гуляючи навколо, я вважаю, що люди рекомендують це робити:

echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout

щоб зменшити цей інтервал. Це не працювало для мене. З чотирьох машин Linux, до яких я мав доступ, дві мали 30, а дві мали 60. Я також встановив це значення як низьке 10. Немає різниці в програмі "відкривач".

Робити це:

echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle

щось змінило. Другому "відкривачу" знадобилося лише близько 3 секунд, щоб дістати свою нову розетку.


3
Я розумію (приблизно), яка мета періоду недоступності. Що я хотів би знати, це точно тривалість цього періоду в Linux і як його можна змінити. Проблема із номером зі сторінки Вікіпедії про TCP полягає в тому, що це обов'язково узагальнене значення, а не те, що безумовно стосується моєї конкретної платформи.
Том Андерсон

Ваші міркування були цікаві! просто позначте їх як такий із заголовком замість того, щоб видаляти їх, це дає способи пошуку причини!
Філіп Гачуд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.