С ++ неблокирующая выборка сокета посылает слишком медленно? - PullRequest
1 голос
/ 07 марта 2012

У меня есть программа, которая поддерживает список «потоковых» сокетов. Эти сокеты настроены на неблокирующие сокеты.

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

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

Следовательно, я не уверен, что моя функция send_TCP_NB работает эффективно или правильно?

int send_TCP_NB(int cs, char data[], int data_length) {

    bool sent = false;
    FD_ZERO(&write_flags);      // initialize the writer socket set
    FD_SET(cs, &write_flags);   // set the write notification for the socket based on the current state of the buffer
    int status;
    int err;

    struct timeval waitd;       // set the time limit for waiting
    waitd.tv_sec = 0;
    waitd.tv_usec = 1000;

    err = select(cs+1, NULL, &write_flags, NULL, &waitd);
    if(err==0)
    {
        // time limit expired
        printf("Time limit expired!\n");
        return 0;   // send failed
    }
    else
    {
        while(!sent)
        {
                if(FD_ISSET(cs, &write_flags))
                {
                     FD_CLR(cs, &write_flags);
                     status = send(cs, data, data_length, 0);
                     sent = true;
                }
         }

         int nError = WSAGetLastError();
         if(nError != WSAEWOULDBLOCK && nError != 0)
         {      
              printf("Error sending non blocking data\n");
              return 0;
         }
         else
         {
              if(nError == WSAEWOULDBLOCK)
              {
                    printf("%d\n", nError);
              }
              return 1;
          }
       }
}

Ответы [ 4 ]

3 голосов
/ 07 марта 2012

Одна вещь, которая могла бы помочь, это если бы вы продумали точно , что должна делать эта функция.То, что он на самом деле делает, вероятно, не то, что вы хотели, и имеет некоторые плохие функции.

Основные функции того, что он делает, что я заметил, это:

  1. Изменение некоторого глобального состояния
  2. Дождаться (до 1 миллисекунды), пока в буфере записи не будет свободного места
  3. Прервать, если буфер еще заполнен
  4. Отправить 1 или более байтов в сокет (игнорируя, сколько было отправлено)
  5. Если произошла ошибка (включая отправку, решившую, что она заблокирована, несмотря на более раннюю проверку), получите ее значение.В противном случае получите случайное значение ошибки
  6. Возможно, выведите что-то на экран в зависимости от полученного значения
  7. Верните 0 или 1, в зависимости от значения ошибки.

Комментарии к этим пунктам:

  1. Почему write_flags глобальный?
  2. Вы действительно намеревались заблокировать эту функцию?
  3. Thisвероятно, все в порядке
  4. Конечно, вам небезразлично сколько данных было отправлено?
  5. Я не вижу ничего в документации, которая предполагает, что это будет ноль, если send преуспевает

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

При этом

У меня есть некоторые данные, которые мне нужно отправить на все эти потоковые сокеты

Что именно точно вам нужно?

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

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

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

РЕДАКТИРОВАТЬ: как подразумевается в другом ответе, это то, что операционная система хороша в любом случае.Вместо того, чтобы пытаться написать собственный код для управления этим, если вы обнаружите, что буферы сокетов заполнены, то увеличьте системные буферы.И если они все еще заполняются, вам следует действительно серьезно подумать о том, что ваша программа должна блокироваться так или иначе, чтобы она перестала отправлять данные быстрее, чем другой конец можетсправиться.т.е. просто используйте обычную блокировку send s для всех ваших данных.

1 голос
/ 08 марта 2012

Вы звоните select() до вызова send(). Сделай это наоборот. Звоните select() только если send() сообщает WSAEWOULDBLOCK, например:

int send_TCP_NB(int cs, char data[], int data_length)
{ 
    int status; 
    int err; 
    struct timeval waitd;

    char *data_ptr = data;
    while (data_length > 0)
    {
        status = send(cs, data_ptr, data_length, 0); 
        if (status > 0)
        {
            data_ptr += status;
            data_length -= status;
            continue;
        }

        err = WSAGetLastError();
        if (err != WSAEWOULDBLOCK)
        {
            printf("Error sending non blocking data\n"); 
            return 0;   // send failed 
        }

        FD_ZERO(&write_flags);
        FD_SET(cs, &write_flags);   // set the write notification for the socket based on the current state of the buffer 

        waitd.tv_sec = 0; 
        waitd.tv_usec = 1000; 

        status = select(cs+1, NULL, &write_flags, NULL, &waitd); 
        if (status > 0) 
            continue;

        if (status == 0)
            printf("Time limit expired!\n"); 
        else
            printf("Error waiting for time limit!\n"); 

        return 0;   // send failed 
    }

    return 1; 
} 
1 голос
/ 07 марта 2012

Это просто ужасный способ делать вещи.select не служит никакой цели, кроме как тратить время.Если send не является блокирующим, он может изменить данные при частичной отправке.Если он блокируется, вы все равно тратите много времени на ожидание одного получателя.

Вам нужно выбрать разумную стратегию ввода / вывода.Вот один из них: Установите все сокеты неблокирующими.Когда вам нужно отправить данные в сокет, просто позвоните write.Если все данные пишет, прекрасно.Если нет, сохраните часть данных, которые не были отправлены на потом, и добавьте сокет в свой набор записи.Если вам больше нечего делать, звоните select.Если вы получили удар по любому сокету в вашем наборе записи, запишите как можно больше байтов из того, что вы сохранили.Если вы запишите все из них, удалите этот сокет из набора записи.

(Если вам нужно записать данные, которые уже есть в вашем наборе записи, просто добавьте данные в сохраненные данные для отправки. Выможет потребоваться закрыть соединение, если буферизуется слишком много данных.)

Лучшей идеей может быть использование библиотеки, которая уже делает все это.Boost :: asio хороший.

1 голос
/ 07 марта 2012

Несколько общих советов:

  • Имейте в виду, что вы умножаете данные. Таким образом, если вы получаете 1 МБ / с, вы выводите N МБ / с с N клиентами. Вы уверены, что ваша сетевая карта может принять это? Хуже с меньшими пакетами, вы получаете более общие издержки. Вы можете рассмотреть вопрос о вещании.

  • Вы используете неблокирующие сокеты, но блокируете, пока они не свободны. Если вы хотите быть не блокирующим, лучше немедленно отбросить пакет, если сокет не готов.

  • Что было бы лучше, это «выбрать» более одного сокета одновременно. Делайте все, что вы делаете, но для всех доступных сокетов. Вы будете писать в каждый «готовый» сокет, а затем повторите его, пока есть сокеты, которые не готовы. Таким образом, вы будете сначала использовать доступные сокеты, а затем с некоторой вероятностью занятые сокеты станут сами по себе доступными.

  • цикл while (!sent) бесполезен и, вероятно, глючит. Поскольку вы проверяете только один сокет, FD_ISSET всегда будет верным. Неправильно проверять снова FD_ISSET после FD_CLR

  • Имейте в виду, что ваша ОС имеет некоторые внутренние буферы для сокетов и что есть способ их расширить (однако в Linux это не так просто, чтобы получить большие значения, вам нужно выполнить некоторые настройки от имени root).

  • Существуют некоторые библиотеки сокетов, которые, вероятно, будут работать лучше, чем те, которые вы можете реализовать в разумные сроки (boost::asio и zmq для тех, которые я знаю).

  • Если вам нужно реализовать это самостоятельно (например, потому что, например, zmq имеет свой собственный формат пакета), рассмотрите возможность использования библиотеки пула потоков.

EDIT:

  • Спать 1 миллисекунду, вероятно, плохая идея. Ваш поток, вероятно, выйдет из расписания и займет гораздо больше времени, прежде чем вы снова начнете использовать процессор.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...