Выбрать и принять задержку в Linux

Я создал простое приложение для приема TCP-соединений IPv4 с помощью select() и accept().

Я использую python script для проверки этого. Он последовательно открывает 100 соединений. то есть:

for i in range(100):
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 print s.connect((IP, PORT))
 s.send("Test\r\n")

Я наблюдаю, что мое приложение застревает в select() в течение 2 секунд после первых X-соединений. Выход из strace:

1344391414.452208 select(30, [3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29], NULL, NULL, NULL) = 1 (in [3])
1344391416.742843 accept(3, 0, NULL) = 30

Мой код следующий. Любая идея, что я делаю неправильно?

#include <assert.h>
#include <errno.h>
#include <netinet in.h="">
#include <sys socket.h="">
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys select.h="">
int
fd_create (void)
{
 int fd;
 int set = true;
 struct sockaddr_in addr;
 fd = socket(AF_INET, SOCK_STREAM, 0);
 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
 memset(&addr, 0, sizeof(addr));
 addr.sin_family = AF_INET;
 addr.sin_port = htons(1999);
 addr.sin_addr.s_addr = INADDR_ANY;
 bind(fd, (struct sockaddr *)&addr, sizeof(addr));
 listen(fd, 1024);
 return (fd);
}
int
fd_echo (int fd)
{
 int n;
 char buffer[128 + 1];
 while ((n = recv(fd, buffer, 128, 0)) > 0);
 return (n);
}
int
main (void)
{
 int listen_fd;
 fd_set working;
 fd_set master;
 int max_fd;
 int i;
 int new_fd;
 int rc;
 int con;
 FD_ZERO(&master);
 listen_fd = fd_create();
 fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL) | O_NONBLOCK);
 max_fd = listen_fd;
 printf("%d\n", listen_fd);
 FD_SET(listen_fd, &master);
 con = 0;
 for (;;) {
 memcpy(&working, &master, sizeof(fd_set));
 select(max_fd + 1, &working, NULL, NULL, NULL);
 for (i = 0; i <= max_fd; i++) {
 if (FD_ISSET(i, &working)) {
 if (i == listen_fd) {
 while ((new_fd = accept(i, NULL, NULL)) >= 0) {
 fcntl(new_fd, F_SETFL, fcntl(new_fd, F_GETFL) | O_NONBLOCK);
 FD_SET(new_fd, &master);
 if (max_fd < new_fd) {
 max_fd = new_fd;
 }
 printf("New connection %d (%d)\n", new_fd, ++con);
 }
 if ((new_fd == -1) && (errno != EAGAIN && errno != EWOULDBLOCK)) {
 return(0);
 }
 } else {
 rc = fd_echo(i);
 if ((rc == 0) ||
 ((rc == -1) && ((errno != EAGAIN && errno != EWOULDBLOCK)))) {
 close(i);
 FD_CLR(i, &master);
 }
 }
 }
 }
 }
 return (0);
}
</sys></fcntl.h></unistd.h></syslog.h></string.h></stdlib.h></stdio.h></stdbool.h></sys></netinet></errno.h></assert.h>
2 ответа

ОБНОВЛЕНИЕ/ПРЕДУПРЕЖДЕНИЕ:, пытаясь доказать этот ответ, я обнаружил, что, возможно, это не так. Я проверил тест и получил задержки без max_fd, когда-либо получавших больше 300. И у меня также были задержки с опросом(). Поэтому я попробовал tcpdump и были повторные передачи. Похоже, что даже 127.0.0.1 может отбрасывать пакеты, когда вы быстро их бросаете. Оставляя ответ здесь, потому что это реальная проблема, даже если она не самая насущная.

Таким образом, это связано с большим количеством файловых дескрипторов, и оно работает с опросом, но не выбирает. С этими подсказками я вижу объяснение: вы преодолели предел FD_SETSIZE.

Официальное выражение от POSIX (см. FD_ZERO/FD_SET/FD_CLR/FD_ISSET):

Поведение этих макросов undefined, если аргумент fd меньше 0 или больше или равен FD_SETSIZE, или если fd не является допустимым файловым дескриптором или если какой-либо из аргументов представляет собой выражения с побочными эффектами.

(от http://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html)

Чтобы понять, что произошло, вам нужно глубже, чем официальная спецификация, изучить фактическую реализацию типа FD_SET. У него раздвоение личности. В ядре, где select реализовано, он рассматривается как массив бит переменной длины. Первый аргумент select используется для определения того, где заканчивается массив. Если вы вызываете select(2048, ...), ядро ​​ожидает, что каждый не-NULL fd_set * укажет на массив из 256 байтов (2048 бит).

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

int array[1024];
array[1024] = 1;

Другими словами, вы сбивали все, что было в памяти после FD_SET, в результате чего произошли более странные вещи.

Есть способы обойти это. Я видел старый код, который делает #define FD_SETSIZE somebignumber, прежде чем включать заголовок, который определяет FD_SET. Я не знаю, на каких ОС работало; Я просто попробовал, и glibc, кажется, игнорирует это.

Лучшая возможность - сделать что-то вроде старого "struct hack", где вы выделили бы структуру с большим объемом памяти, чем ее sizeof, а дополнительная память могла бы использоваться в качестве дополнительных элементов в массиве, который был последним член структуры.

fd_set *rfds = malloc(128+sizeof *foo); /* can hold fds up to FD_SETSIZE+128*8-1 */

Теперь, конечно, вам нужно не забудьте освободить его, когда вы закончите с ним, и передайте rfds вместо &rfds в select и макросы FD_* и сделайте свой собственный memset вместо этого из FD_ZERO, и надеемся, что реализация ядра не изменится, так как вы теперь являетесь настоящим чудом с ним. Но он работает... пока.

Использование опроса - это, вероятно, правильный ответ.


Итак, дальнейшая отладка ядра...

Пакет удаляется в "tcp_v4_syn_recv_sock()", потому что "sk_acceptq_is_full (sk)" возвращает true.

"sk- > sk_ack_backlog" равно 11, а сконфигурированный "sk- > sk_max_ack_backlog" равен 10. (Мы установили это в команде listen().)

(Обновление на основе заметок EJP.) Поэтому я предполагаю, что происходит:

Клиентские блоки при подключении(). SYN отправляется на сервер. Ядро получает SYN посылает SYN/ACK. Клиент возвращает SYN/ACK и разблокирует и a) отправляет ACK и b) новый SYN/ACK.

Сервер получает ACK и помещает соединение в отставание.

Сделайте это 10 раз, и мы застряли.

Хорошие парни. Не понял бы это без вашей помощи. Спасибо!!

licensed under cc by-sa 3.0 with attribution.