Почему после ~ 16370 сокетных соединений происходит долгая пауза? - PullRequest
0 голосов
/ 30 октября 2018

Я немного поигрался с API сокетов, чтобы понять, как он работает.

Я написал две маленькие программы:

  1. Сервер прослушивает 8080 для потокового соединения. Каждому, кто подключается к нему, отправляется простое сообщение.
  2. Клиент подключается к 127.0.0.1:8080 и выгружает то, что он получает, в стандартный вывод. Это повторяется 20000 раз подряд.

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

Я повторил этот эксперимент несколько раз и поймал его в 16370, 16371 и 16372. Он удивительно последовательн в повторных экспериментах.

Мой вопрос: зачем делать паузу после ~ 16370 итераций? Что за узкое место здесь?

FWIW, я на MacOS Sierra.

Я запускаю код сервера следующим образом:

clang -Wall -Werror -Wpedantic server.c -o server.out && ./server.out

и код клиента вот так:

clang -Wall -Werror -Wpedantic client.c -o client.out && time ./client.out

Вот две программы:

server.c

#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define PORT 8080
#define MAXMSG 512

int make_socket(int port) {
  int sock;
  struct sockaddr_in name;

  sock = socket(PF_INET, SOCK_STREAM, 0);
  if (sock < 0) {
    perror("socket");
    exit(1);
  }

  name.sin_family = AF_INET;
  name.sin_port = htons(port);
  name.sin_addr.s_addr = htonl(INADDR_ANY);
  if (bind(sock, (struct sockaddr*) &name, sizeof(name)) < 0) {
    perror("bind");
    exit(1);
  }
  return sock;
}

int main(int argc, char** argv) {
  const char hello[] = "Hello visitor ";
  char buffer[MAXMSG];
  int sk;
  unsigned long count = 0;
  strcpy(buffer, hello);

  sk = make_socket(PORT);
  listen(sk, 10);

  printf("ready\n");
  for (;;) {
    count++;
    sprintf(buffer + strlen(hello), "%lu", count);
    int s = accept(sk, NULL, NULL);
    if (send(s, buffer, strlen(buffer) + 1, 0) < 0) {
      perror("send");
      exit(1);
    }
    close(s);
    printf("data socket (%d) message sent (%s)\n", s, buffer);
  }
}

client.c

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define PORT 8080
#define MAXMSG 512

int make_socket() {
  int sock;

  sock = socket(PF_INET, SOCK_STREAM, 0);
  if (sock < 0) {
    perror("socket");
    exit(1);
  }

  return sock;
}

int main(int argc, char** argv) {
  char buffer[MAXMSG];
  int sk;
  size_t i;
  struct sockaddr_in addr;
  strcpy(buffer, "Hello world!");

  for (i = 0; i < 20000; i++) {
    sk = make_socket();
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    connect(sk, (struct sockaddr*) &addr, sizeof(addr));
    recv(sk, buffer, strlen(buffer) + 1, 0);
    close(sk);
    printf("socket (%d) message = %s\n", sk, buffer);
  }
}

Это последний вывод, который я получаю на стороне клиента:

socket (3) message = Hello visitor 16369
socket (3) message = Hello visitor 16370
socket (3) message = Hello visitor 16371
socket (3) message = Hello visitor 16372

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

Скорее всего, вы достигли предела, называемого Ephemeral Port Range в вашей операционной системе. Тот же принцип применим ко всем операционным системам на основе IP.

Всякий раз, когда устанавливается соединение с сокетом, порт выделяется для запроса и связывается с интерфейсом, на котором установлено соединение. Как только этот сокет закрыт, порт переводится в состояние с именем TIME_WAIT. Эффективное размещение порта на скамейке в течение определенного периода времени, чтобы избежать его повторного использования. Это сделано для того, чтобы скрытые пакеты в Интернете не приходили поздно и не вызывали проблем.

Эфермальный диапазон портов указан в Linux как /proc/sys/net/ipv4/ip_local_port_range.

Вы можете отобразить их на MacOS, используя:

sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last

net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.last: 65535

То есть 16,383 доступных портов в эфемерном диапазоне.

Чтобы увидеть все параметры сети, вы можете выполнить:

sysctl net.inet.tcp

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

Вы можете увидеть количество открытых соединений, используя netstat -an. Сокеты могут застрять в состоянии TIME_WAIT, если вы открываете и закрываете множество соединений. В некоторых местах это неизбежно, но вам, возможно, придется подумать, нужен ли вам пул соединений, если это так.

Если проблема связана с TIME_WAIT, то вы можете настроить системные параметры. Вы можете установить net.ipv4.tcp_tw_reuse / net.ipv4.tcp_tw_recycle для ускорения оборота соединения.

Одним из быстрых тестов было бы переключиться на другой интерфейс и повторить попытку. Если вы использовали localhost, а затем испытали замедление, вы могли бы затем переключиться на внешний IP-адрес на другом интерфейсе, и вы должны работать как gangbusters, пока не достигнете предела снова.

Это не проблема с языком, который вы используете, а проблема использования сети на основе сокетов.

0 голосов
/ 30 октября 2018

Возможно, вы имеете дело с состоянием TIME_WAIT сокетов. После активного закрытия каждого сокета подключения на вашем сервере сокет остается в этом состоянии в течение длительного (десятки секунд) периода. Итак, как подсказывает @Blaze, ваша программа достигла лимита ресурсов и должна дождаться окончательного закрытия этих сокетов.

Эта функция предназначена для предотвращения двух случаев, во-первых, это вероятность того, что пакет с задержкой, отправленный в одном соединении, будет интерпретирован как часть более позднего соединения. Второй - это вероятность того, что последний ACK закрытия соединения не будет получен пассивной стороной закрытия соединения, что приведет к повторной передаче FIN / ACK. Если это происходит, и активная сторона закрытия уже закрыла сокет, он ответит RST, в результате чего другая сторона получит ошибку, несмотря на то, что вся информация была отправлена ​​правильно.

Взгляните на это: http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications-for-protocols-and-scalable-servers.html

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

struct linger immediate_linger;
immediate_linger.l_onoff = 1; /* Do linger on closing */
immediate_linger.l_linger = 0;  /* Wait 0 seconds to linger after closing */
if (setsockopt(comm_socket, SOL_SOCKET, SO_LINGER, &immediate_linger, sizeof (immediate_linger))) {
  err = errno;
  printf("Error setting immediate linger for socket %d: %s.", comm_socket, strerror(err));
}
else {
  printf("Set immediate linger after close for the socket %d.", comm_socket);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...