Существует ли неблокирующий метод для проверки данных быстрее, чем select() и poll()?

У меня есть программа C, отправляющая данные так быстро, как может использовать метод sendto() от отправителя к получателю, который получает с помощью метода recvfrom(). Данные инкапсулируются в Ethernet-фреймы уровня 2, и приложение записывает Ethernet-кадры непосредственно на провод (без TCP или UDP или даже IP). Это на x86_64 Linux (dev - это всего лишь запас Ubuntu 14.04). Я не намерен портировать какую-либо другую ОС, область разработки приложений для Linux, поэтому другие ОС не имеют значения.

Отправитель:

while (true)
 {
 sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0,
 (struct sockaddr*)&socket_address, sizeof socket_address);
 }

Получатель:

while (true)
 {
 recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
 }

Я хочу, чтобы отправитель мог проверить полученные пакеты; если приложение-получатель завершает работу, он отправляет данные обратно отправителю, говоря "Я ухожу", чтобы отправитель прекратил отправку данных. Я использовал poll() для отправителя, чтобы проверить полученные сообщения, как показано ниже, но это значительно снижает скорость передачи данных только от 1 Гбит/с (968 Мбит/с) до 10 Мбит/с. Я тестирую кросс-кабель между двумя ПК с сетевыми адаптерами 1 Гбит/с. Отправитель подсчитывает отправленные кадры и размер фрейма, а получатель подсчитывает принятые кадры и размер кадра, поэтому для подтверждения приложение фактически получает скорость проводки достаточно близко, я не просто смотрю на использование NIC или подобное.

Метод опроса():

int rv;
struct pollfd ufds[1];
ufds[0].fd = sockFD;
ufds[0].events = POLLIN
while (true)
{
 sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
 // wait for events on the sockets, 1ms timeout
 rv = poll(ufds, 1, 1);
 if (rv > 0) {
 if (ufds[0].revents & POLLIN) {
 recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
 }
 }
}

1 миллисекунда - это самый низкий тайм-аут, который может быть установлен для метода poll(). Вот почему моя программа передачи может передавать только 10 Мбит/с. Приложение может легко насытить связь 1 Гбит/с, хотя при минимальном использовании ЦП, я получал 968 Мбит/с, как я уже говорил (я не имею в виду пик, кстати, это устойчивая пропускная способность).

Я удалил вызов poll() и переключился на select(), используя приведенный ниже пример, но опять же, используя самую маленькую задержку, я могу здесь, мое приложение передачи может получить только 175 Мбит/с. Не близко к исходным 968 Мбит/с;

Выбрать() Метод:

fd_set readfds;
struct timeval tv;
int rv, n;
FD_ZERO(&readfds);
FD_SET(sockFD, &readfds);
n = sockFD + 1;
while (true)
{
 sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
 tv.tv_sec = 0;
 tv.tv_usec = 000001;
 rv = select(n, &readfds, NULL, NULL, &tv);
 if (rv > 0) {
 if (FD_ISSET(sockFD, &readfds)) {
 recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
 }
}

Похоже, что оба метода слишком медленны для сегодняшних систем (мое использование процессора было около 2% для всех тестов выше). Я хочу скоро перенести это приложение на некоторые машины 10GigE и начать тестирование там, но я, очевидно, не могу использовать любой из этих двух методов. Нет ли более быстрого способа проверки?

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

Как сторона node, я еще не прочитал метод recvfrom(), чтобы узнать, где задержка еще до публикации, но я попробовал следующее, потому что для изменения кода потребовалось всего 30 секунд, это привели к худшему результату, который был меньше 1 Мбит/с;

Отправитель:

while (true)
 {
 // Continually send a frame then check for a frame, send a frame then check for a frame...
 sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
 recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
 }
5 ответов

Нет необходимости блокировать poll или select в течение любого периода времени. Если параметры timeout для poll или select установлены на ноль, оба вызова немедленно возвращаются с указанием доступности i/o. Это устраняет включение таймера и последующее округление.

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


Я бы не использовал режим без блокировки. Просто выделите поток в режиме блокировки. Таким образом, вы выполняете только один системный вызов: recvfrom(), поэтому вы сохраняете коммутаторы контекста в ядре.


Как вы поняли, причиной вашей работы является то, что вы ограничились отправкой не более 1000 пакетов в секунду.

Если вы хотите использовать два потока, лучше выбрать EJP answer. Если вы действительно хотите использовать только один поток, лучшим вариантом является использование select() или poll(), чтобы вы знали, есть ли что-то в той точке, когда ваша очередь передачи насыщена. Таким образом, вы можете установить ваш сокет в неблокирующий режим, или вы можете использовать флаг MSG_DONTWAIT при выполнении ввода/вывода. Прекратите выполнение этого ввода-вывода, когда вы получите уведомление EAGAIN/EWOULDBLOCK, а затем снова заблокируйте ожидание соответствующего события в select() или poll() (не устанавливайте тайм-аут). В псевдокоде с упрощенной обработкой ошибок:

writeable = true;
readable = false;
make_nonblock(s);
for (;;) {
 if (readable) {
 while (recvfrom(s,...) > 0) {
 done = done_check();
 }
 if (done) break;
 assert(errno == EAGAIN);
 readable = false;
 }
 if (writeable) {
 while (sendto(s,...) > 0) {}
 assert(errno == EAGAIN);
 writeable = false;
 }
 poll_socket(s, &readable, &writeable);
}


Вы можете попытаться найти информацию о проблеме C10k для получения некоторой информации.

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


Может быть полезно поставить select в другой поток.

Тема 1:

while (gRunning)
{
 sendResult = sendto(...)
}

Тема 2:

rv = select(n, &readfds, NULL, NULL, &tv);
if (rv > 0) {
 if (FD_ISSET(sockFD, &readfds)) {
 recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
 if (!strcmp(rxBuffer,"I QUIT")) {
 gRunning = FALSE;
 }
 }
}

Выбор потока должен уступать процессору во время ожидания, что, вероятно, даст вашему отправителю больше циклов.

licensed under cc by-sa 3.0 with attribution.