Почему Listen() (до вызова Accept()) достаточно, чтобы приложение завершило трехстороннее рукопожатие?

Я отлаживаю очень простой TCP-сервер на C в Linux. Я остановил выполнение прямо перед строкой, в которой вызывается метод Accept(). К моему удивлению, когда клиент отправляет SYN, tcpdump показывает, что сервер отвечает SYN-ACK (на который сразу же приходит окончательный ответ ACK от клиента).

Команда ss действительно показывает, что приложение уже прослушивает связанный порт.

Я понимаю, что я уже вызвал функцию Listen(), поэтому приложение будет прослушивать связанный порт. Но тогда, по той же семантике, метод Accept() должен быть вызван до того, как сервер сможет принять соединение.

На страницах руководства Listen() написано (курсив мой):

Listen() помечает сокет, на который ссылается sockfd, как пассивный сокет, то есть сокет, который будет использоваться для приема входящих запросов на соединение с помощью Accept(2).

Хотя на страницах руководства Accept() написано:

Он извлекает первый запрос на соединение из очереди ожидающих соединений для прослушивающего сокета.

Из этого можно понять, что Accept() следует вызывать до того, как будет установлено соединение.

Что мне здесь не хватает? Если это стандартное поведение, можно ли мне указать на первоисточник? Или это просто специфика реализации?

Ниже приведен код, который я использую. Если я остановлю его выполнение прямо перед вызовом Listen(), использование netcat покажет, что на отправленный SYN отвечает RST. Но если я сделаю то же самое после выполнения Listen(), tcpdump покажет, что сервер отвечает SYN-ACK.

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

void error(const char* message) {
        printf("%s %s\n", message, strerror(errno));
}

int main(int argc, char** argv) {
        const int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if ( sockfd == -1 ) {
                error("Socket error:");
                return 1;
        }

        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(12345);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

        if ( bind(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) == - 1 ) {
                error("Bind error:");
                return 1;
        }
        if ( listen(sockfd, 5) == -1 ) {
                error("Listen error: ");
                return 1;
        }

        printf("Ready.\n");

        struct sockaddr_in cliaddr;
        socklen_t cliaddrlen = sizeof(cliaddr);
        char response[512];
        while (1) {
                const int connfd = accept(sockfd, (struct sockaddr*) &cliaddr, &cliaddrlen);
                if ( connfd == -1 ) {
                        printf("Accept error: %s\n", strerror(errno));
                        return 1;
                }
                const pid_t pid = fork();
                if ( pid == -1 ) {
                        printf("Fork error: %s\n", strerror(errno));
                        continue;
                }
                if ( pid == 0 ) {
                        close(sockfd);
                        char buffer[16];
                        inet_ntop(AF_INET, &cliaddr.sin_addr, buffer, 16);
                        printf("Connection from %s accepted.\n", buffer);
                        while ( 1 ) {
                                int nread = read(connfd, response, 512);
                                if ( nread == -1 ) {
                                        printf("%s\n", strerror(errno));
                                }
                                if (nread == 1 && response[0] == '\n') {
                                        break;
                                }
                                write(connfd, response, nread);
                                //write(STDIN_FILENO, response, nread);
                        }
                        printf("Good bye!\n");
                        close(connfd);
                        return 0;
                }
                close(connfd);
                wait(NULL);
        }
        return 0;
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
60
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Целочисленный параметр 'backlog' для Listen() – тот, где вы указываете 5 – определяет, сколько соединений может быть принято таким образом; после достижения этого предела новые SYN будут игнорироваться или отклоняться.

Это позволяет ставить эти соединения в очередь, например. на однопоточном сервере, который, скорее всего, обнаружил бы, когда изначально был разработан API сокетов BSD (который, если я правильно понимаю историю, предшествует многопоточности). Серверу, возможно, придется выполнить fork(), прежде чем он сможет принять() следующего клиента, или он может даже решить обслужить запрос немедленно в том же потоке, если это простой протокол (например, базовый сервер Whois или QotD), прежде чем перейти к следующий клиент.

Если бы сервер вообще не отвечал, у клиента быстро истечет тайм-аут соединения; но как только соединение установлено, тайм-аут может быть намного дольше (например, SMTP определяет тайм-аут в пять минут до приветствия). При этом я не знаю, какие задержки были бы «нормальными» во время создания этого API, поэтому по этому поводу есть некоторые предположения.

Вы не различаете работу пользовательского приложения и поддерживающую работу ядра, что в данном случае также является разделением уровня приложения и транспортного уровня (и ниже). Именно ядро ​​отвечает за TCP-квитирование и, в более общем смысле, за установление соединений на уровне TCP. Вызов приложения listen() просит его начать делать это для входящих запросов на соединение транспортного уровня. Со стороны пользовательского пространства подключенные сокеты, в которых находится приложение accepts(), обслуживают уровень приложения, по крайней мере, насколько это необходимо приложению.

Хотя на страницах руководства Accept() написано:

Он извлекает первый запрос на соединение из очереди ожидающих соединений для прослушивающего сокета.

Из этого можно понять, что Accept() следует вызывать перед соединение должно быть установлено.

Нет, не в той степени, в которой вы имеете в виду «до установки TCP-соединения». accept() обеспечивает обработку запросов на подключение на уровне приложения. Пользовательское пространство обычно не видит ничего ниже этого.

Кроме того, какой цели будет служить отсрочка завершения рукопожатия?

Другие вопросы по теме

Похожие вопросы

Передача входных данных в запуск скрипта Python
Запуск сценария Python с VirtualEnv в качестве демона с использованием файла .system
Попытка передать «указатель на структуру данных» обработчику сигнала с помощью функции sigqueue из одного процесса в другой, используя структуру siginfo_t
Awk: попробуйте преобразовать строку временной метки во время эпохи Unix
Как я могу выполнить поиск значения с помощью подстроки, извлеченной из имени хоста в bash?
Чтение и запись более 4096 байт в/из STDIN
Могу ли я получить информацию о файловом дескрипторе из основного файла с помощью GDB?
Ошибка анализатора ржавчины: не удалось загрузить связанный проект: корень проекта должен указывать на Cargo.toml илиrust-project.json (корневой каталог имеет формат пути Windows)
Как решить или подавить Gtk-предупреждения при открытии веб-сайта на Java?
Разбудить все потоки, спящие на фьютексе