Что такое "отставание" в TCP-соединениях?

Ниже вы видите программу на python, которая действует как сервер, прослушивающий запросы на подключение к порту 9999:

# server.py 
import socket                                         
import time

# create a socket object
serversocket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM) 

# get local machine name
host = socket.gethostname()                           

port = 9999                                           

# bind to the port
serversocket.bind((host, port))                                  

# queue up to 5 requests
serversocket.listen(5)                                           

while True:
    # establish a connection
    clientsocket,addr = serversocket.accept()      

    print("Got a connection from %s" % str(addr))
    currentTime = time.ctime(time.time()) + "\r\n"
    clientsocket.send(currentTime.encode('ascii'))
    clientsocket.close()

Вопрос в том, какова функция параметра метода socket.listen() (т. socket.listen() 5).

На основе учебных пособий по интернету:

Аргумент backlog указывает максимальное количество подключений в очереди и должен быть не менее 0; максимальное значение зависит от системы (обычно 5), минимальное значение равно 0.

Но:

  1. Что это за очереди?
  2. Имеет ли это значение для запросов клиентов? (Я имею в виду, отличается ли сервер, работающий с socket.listen(5) от сервера, работающего с socket.listen(1) при принятии запросов на подключение или при получении данных?)
  3. Почему минимальное значение равно нулю? Не должно ли быть хотя бы 1?
  4. Есть ли предпочтительное значение?
  5. Это backlog определено только для TCP-соединений или оно также применимо для UDP и других протоколов?

Ответ 1

ПРИМЕЧАНИЕ. Ответы оформляются без какого-либо опыта в Python, но вопросы не имеют отношения к языку, на который нужно отвечать.

Что это за очереди?

Проще говоря, параметр backlog указывает количество ожидающих соединений, которые будет удерживать очередь.

Когда к серверу подключается несколько клиентов, сервер удерживает входящие запросы в очереди. Клиенты располагаются в очереди, и сервер обрабатывает их запросы один за другим, как и когда участник очереди продолжает работу. Природа такого типа соединения называется соединением в очереди.

Имеет ли это значение для запросов клиентов? (Я имею в виду, отличается ли сервер, работающий с socket.listen(5) от сервера, работающего с socket.listen(1) при принятии запросов на подключение или при получении данных?)

Да, оба случая разные. В первом случае можно было бы разместить только 5 клиентов в очереди; тогда как в случае backlog = 1 в очереди может удерживаться только 1 соединение, что приводит к удалению дальнейшего запроса на соединение!

Почему минимальное значение равно нулю? Не должно ли быть хотя бы 1?

Я понятия не имею о Python, но, согласно этому источнику, в C аргумент backlog 0 может позволить сокету принимать соединения, и в этом случае длина очереди прослушивания может быть установлена на минимальное значение, определяемое реализацией.

Есть ли предпочтительное значение?

На этот вопрос нет четкого ответа. Я бы сказал, что это зависит от характера вашего приложения, а также от конфигурации оборудования и программного обеспечения. Опять же, согласно источнику, BackLog тихо ограничен от 1 до 5 включительно (опять же, согласно C).

Это отставание определено только для TCP-соединений или оно также применимо для UDP и других протоколов?

NO. Обратите внимание, что нет необходимости слушать() или принять() для неподключенных дейтаграммных сокетов (UDP). Это один из преимуществ использования неподключенных дейтаграмм сокетов!

Но имейте в виду, что существуют реализации сокетов дейтаграмм на основе TCP (называемые TCPDatagramSocket), которые имеют параметр backlog.

Ответ 2

Когда устанавливается соединение TCP, выполняется так называемое трехстороннее рукопожатие. Обе стороны обмениваются некоторыми пакетами, и, как только они это сделают, это соединение называется завершенным, и оно готово для использования приложением.

Однако это трехстороннее рукопожатие занимает некоторое время. И в течение этого времени соединение ставится в очередь, и это отставание. Таким образом, вы можете установить максимальное количество незавершенных параллельных соединений с помощью .listen(no) (обратите внимание, что в соответствии со стандартом posix значение является лишь подсказкой, оно может быть полностью проигнорировано). Если кто-то попытается установить соединение выше лимита невыполненных обязательств, другая сторона откажется от него.

Таким образом, предел невыполненных заданий относится к незавершенным соединениям, но не установлен.

Теперь более высокий лимит отставания будет лучше в большинстве случаев. Обратите внимание, что максимальный лимит зависит от ОС, например, cat/proc/sys/net/core/somaxconn дает мне 128 на моем Ubuntu.

Ответ 3

Похоже, что функция этого параметра заключается в ограничении количества входящих запросов на подключение, которые сервер будет хранить в очереди, предполагая, что он может обслуживать текущий запрос и небольшое количество ожидающих запросов в очереди в течение разумного периода времени при высокой нагрузке. Вот хороший параграф, с которым я столкнулся, который дает небольшой контекст вокруг этого аргумента...

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

https://docs.python.org/3/howto/sockets.html#creating-a-socket

Ранее в документе был текст, предлагающий клиентам подключаться и отключаться от сервера, чтобы вы не создавали длинную очередь запросов в первую очередь...

Когда connect завершается, сокет s можно использовать для отправки запроса на текст страницы. Тот же сокет будет читать ответ, а затем будет уничтожен. Это верно, уничтожено. Клиентские сокеты обычно используются только для одного обмена (или небольшого набора последовательных обменов).

Связанное руководство HowTo является обязательным для прочтения при ознакомлении с сетевым программированием с использованием сокетов. Это действительно фокусирует внимание на некоторых крупных картинных темах. Теперь, как серверный сокет управляет этой очередью, насколько подробности реализации - это другая история, возможно, интересная. Я полагаю, что мотивация для этого дизайна более показательна, без этого барьер для атаки типа "отказ в обслуживании" был бы очень и очень низким.

Что касается причины минимального значения 0 против 1, мы должны помнить, что 0 по-прежнему является допустимым значением, то есть ничего не ставить в очередь. По сути, это означает, что очереди запросов не будет, просто отклоните соединения, если в данный момент серверный сокет обслуживает соединение. В этом контексте всегда следует помнить о точке обслуживания активного соединения, единственной причиной, по которой очередь будет интересна в первую очередь.

Это подводит нас к следующему вопросу о предпочтительной стоимости. Это все дизайнерское решение, хотите ли вы ставить запросы в очередь или нет? Если это так, вы можете выбрать значение, которое, по вашему мнению, оправдано, исходя из ожидаемого трафика и известных аппаратных ресурсов, я полагаю. Я сомневаюсь, что есть что-то формальное в выборе значения. Это заставляет меня задуматься о том, насколько легким является запрос в первую очередь, когда вы будете подвергнуты штрафу за размещение чего-либо на сервере.


ОБНОВИТЬ

Я хотел обосновать комментарии от user207421 и пошел искать источник на python. К сожалению, этот уровень детализации находится не в источнике sockets.py, а в socketmodule.С# L3351-L3382 с хешем 530f506.

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

Мы стараемся выбрать достаточно высокий уровень невыполненных заданий по умолчанию, чтобы избежать обрывов соединений для обычных рабочих нагрузок, но не слишком высокий, чтобы ограничить использование ресурсов.

а также

Если задано отставание, оно должно быть не менее 0 (если оно меньше, оно равно 0); в нем указывается количество неприемлемых соединений, которые система разрешит перед отказом в новых соединениях. Если не указан, выбирается разумное значение по умолчанию.

/* s.listen(n) method */

static PyObject *
sock_listen(PySocketSockObject *s, PyObject *args)
{
    /* We try to choose a default backlog high enough to avoid connection drops
     * for common workloads, yet not too high to limit resource usage. */
    int backlog = Py_MIN(SOMAXCONN, 128);
    int res;

    if (!PyArg_ParseTuple(args, "|i:listen", &backlog))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    /* To avoid problems on systems that don't allow a negative backlog
     * (which doesn't make sense anyway) we force a minimum value of 0. */
    if (backlog < 0)
        backlog = 0;
    res = listen(s->sock_fd, backlog);
    Py_END_ALLOW_THREADS
    if (res < 0)
        return s->errorhandler();
    Py_RETURN_NONE;
}

PyDoc_STRVAR(listen_doc,
"listen([backlog])\n\
\n\
Enable a server to accept connections.  If backlog is specified, it must be\n\
at least 0 (if it is lower, it is set to 0); it specifies the number of\n\
unaccepted connections that the system will allow before refusing new\n\
connections. If not specified, a default reasonable value is chosen.");

Пройдя дальше по кроличьей норе во внешнее пространство, я обнаружил следующий источник из модуля сокетов...

 res = listen(s->sock_fd, backlog);

Этот источник заканчивается в socket.h и укорочен, используя Linux в качестве конкретной платформы фона для целей обсуждения.

/* Maximum queue length specifiable by listen.  */
#define SOMAXCONN   128
extern int __sys_listen(int fd, int backlog);

Там больше информации можно найти в справочной странице

http://man7.org/linux/man-pages/man2/listen.2.html

int listen(int sockfd, int backlog);

И соответствующая документация

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

Аргумент sockfd является дескриптором файла, который ссылается на сокет типа SOCK_STREAM или SOCK_SEQPACKET.

Аргумент backlog определяет максимальную длину, до которой может увеличиваться очередь ожидающих соединений для sockfd. Если запрос соединения приходит, когда очередь заполнена, клиент может получить ошибку с указанием ECONNREFUSED или, если базовый протокол поддерживает повторную передачу, запрос может быть проигнорирован, так что последующая ECONNREFUSED соединения будет ECONNREFUSED.

Один дополнительный источник определяет ядро как ответственное за очередь невыполненных работ.

Второй аргумент backlog этой функции указывает максимальное количество соединений, которое ядро должно поставить в очередь для этого сокета.

Далее они кратко расскажут о том, как недопустимые/поставленные в очередь соединения распределяются в заделе (полезный рисунок включен в связанный источник).

Чтобы понять аргумент backlog, мы должны понимать, что для данного сокета прослушивания ядро поддерживает две очереди:

Неполная очередь соединения, которая содержит запись для каждого SYN, полученного от клиента, для которого сервер ожидает завершения трехстороннего рукопожатия TCP. Эти сокеты находятся в состоянии SYN_RCVD (рисунок 2.4).

Завершенная очередь соединений, которая содержит запись для каждого клиента, с которым завершено трехстороннее рукопожатие TCP. Эти сокеты находятся в состоянии ESTABLISHED (рисунок 2.4). Эти две очереди изображены на рисунке ниже:

Когда в неполной очереди создается запись, параметры из сокета прослушивания копируются во вновь созданное соединение. Механизм создания соединения полностью автоматический; серверный процесс не задействован.