Pull to refresh

Comments 40

В чем смысл делать сокеты неблокирующими — а потом вызывать методы в цикле?
В чем смысл делать сокеты неблокирующими — а потом вызывать методы в цикле?

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

Активное ожидание, пусть и с вызовом sleep — суть костыль.
В реальных задачах используется хотя бы select

Приведите аргумент в пользу select и
более продвинутых способов
для слушающего сокета?

Для «рабочих» клиентских сокетов смысл есть, согласен. Только в приведенном примере этого смысла нет, потому как тут обрабатывается одно единственное подключение.
Есди сервер обрабатывает одно-единственное подключение, то зачем вообще неблокирующие сокеты?
Это туториал!
Если перестанете кидать минусы — напишу, как из этого примера сделать реально работающий сервер для любого количества подключений ))
Даже если закидают, пост всё равно нужен, чтобы его закидали плюсами.

PS: На самом деле магии в использовании select,epoll,kqueue довольно много, простые примеры они простые, но если речь идёт о работе на разных платформах под большими нагрузками, то начинают всплывать различны мелкие проблемки.
Самый простой вариант использовать готовые билиотеки для обработки событий, просто использовать и работают стабильно.
Можно использовать код из моей Астры: bitbucket.org/cesbo/astra/src/astra-4/core/event.c (поддерживает select,poll,epoll,queue) гоняет гигабиты ТВ.
Гы, с отрицательной кармой это теперь будет проблематично )))
Спасибо за совет. Я знаю про boost::asio.
Из вашего поста следует, что мой код подвержен
проблеме c10k
и является «неправильным способом», я правильно понимаю?
Было бы любопытно почитать ваши доводы на эту тему.
Сколько соединений сможет одновременно «держать» ваш сервер? Я не вижу полного кода, поэтому тут либо 1, либо 1000. Это и есть проблема.

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

Неблокирующие сокеты — это способ повысить лимит числа соединений дальше. Классическая схема с select поддерживает до 10 000 соединений, дальше начинаются проблемы в силу квадратичной природы алгоритма. Однако, и большее число соединений держится, пусть и с лагами.

Дальше идут такие вещи, как poll, epoll, /dev/poll, kevent, асинхронные сокеты и куча других страшных слов, с которыми лично я никогда не работал. Там можно обрабатывать одновременно столько соединений, сколько способен держать сетевой стек ОС — своих накладных расходов эти способы накладывают крайне мало.
Сколько соединений сможет одновременно «держать» ваш сервер?

Хоть сколько.
Сервер в моей статье не использует ни select ни epoll ничего!
Да, это не правильно, но так тоже можно.

В принципе, если меня не закидают минусами, я могу показать, как пишется кроссплатформенный сервер с select и epoll. Он будет поддерживать тоже любое количество соединений в Linux, а в Windows — сколько позволит select.
Ничего там сверхъестественного нет.
Хоть сколько.
Неверно. Тот код, который я вижу, вообще не сможет обработать более одного соединения — это еще хуже, чем многопоточный сервер.

Но даже если в ваш сервер добавить многопоточность — то он все равно будет хуже, чем такой же сервер на блокирующих сокетах.
Дальше идут такие вещи, как poll, epoll, /dev/poll, kevent, асинхронные сокеты и куча других страшных слов, с которыми лично я никогда не работал.

Но даже если в ваш сервер добавить многопоточность — то он все равно будет хуже, чем такой же сервер на блокирующих сокетах.

Ну вам то оно конечно виднее, но по моему вы меня тролите.
Может кто опровергнет или подтвердит. Помоему при установке соединения (handshake) openssl блокирует всё и вся прелесть неблокирующих сокетов теряется.
Опровергаю: nginx использует openssl — но в использовании блокирующих сокетов никогда замечен не был.
Тут и я не нужен, достаточно на код автора посмотреть, он хоть очень странный, написан просто ужасно, да и выедает процессор в бесконечном цикле так, что лучше бы уж блокировался, но тем не менее, из него видно, что SSL_do_handshake/SSL_read/SSL_write возвращают управление в функцию, сигнализируя SSL_ERROR_WANT_READ и SSL_ERROR_WANT_WRITE, после чего остается только взвести таймер и положить дескриптор на соответствующее событие в epoll/kqueue/eventports/по_вкусу.
If the underlying BIO is non-blocking, SSL_do_handshake() will also return when the underlying BIO could not satisfy the needs of SSL_do_handshake() to continue the handshake. In this case a call to SSL_get_error() with the return value of SSL_do_handshake() will yield SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. The calling process then must repeat the call after taking appropriate action to satisfy the needs of SSL_do_handshake(). The action depends on the underlying BIO. When using a non-blocking socket, nothing is to be done, but select() can be used to check for the required condition. When using a buffering BIO, like a BIO pair, data must be written into or retrieved out of the BIO before being able to continue.
www.openssl.org/docs/ssl/SSL_do_handshake.html
Поправочка. Автор и accept'ит соединение с помощью OpenSSL, поэтому не пользуется SSL_do_handshake(), но сути дела это не меняет, она работает также: www.openssl.org/docs/ssl/SSL_accept.html
Вместо дескриптора сокета accept теперь вернет значение (-1).

Вы же кроссплатформенный код пишите, нельзя так делать. accept возвращает -1 в никсах, а в винде она возращает INVALID_SOCKET который ещё надо проверить и убедиться что там WSAEWOULDBLOCK, а не ошибка.

И немного не понял смысл замены блокирующего вызова на зацикленный неблокирующий.
Неблокирующий сокет нужен для обработки циклом клиентов и приёма новых конектов в одной ните, вместо разнесения этих операций по разным нитям.
accept возвращает -1 в никсах, а в винде она возращает INVALID_SOCKET

INVALID_SOCKET в винде равен -1.
Хотя согласен, можно добавить этот макрос для линукса. Про WSAEWOULDBLOCK — не вижу смысла его проверять для сервера: ну ошибка и ошибка, все равно будем в цикле слушать.

И немного не понял смысл замены блокирующего вызова на зацикленный неблокирующий.

Вы же сами ответили:
Неблокирующий сокет нужен для обработки циклом клиентов и приёма новых конектов в одной ните, вместо разнесения этих операций по разным нитям.
Тип SOCKET, в винде беззнаковый, а в никсах сокеты имеют знаковый тип int, а у Вас все присваивается типу int.
Уже ответили что в винде не -1. Товарищи, разрабатывающие pvs studio пишут про эту ошибку чуть ли не в каждой статье.
К тому же в MSDN вполне чёрным по белом что ошибка функции accept это именно INVALID_SOCKET, а не что-то другое. И именно на него надо проверять если нет желания когда-то потом мучаться с совместимостью и переносимостью кода.
Стоило хотя бы упомянуть что код ошибки стоит проверять что это действительно всё ок для неблокирующего вызова. Это всего пара лишних макросов чтобы свести к единому виду, можно даже было бы привести в статье.

Вы же сами ответили:

Я в примерах вижу в цикле вызывает accept без каких либо других действий внутри цикла. А обработка коннекта идёт после выхода из него.
Чем это отличается блокирующего вызова кроме ухудшения производительности из-за слипа?
Должно быть примерно так всё же:
while(1) {
  accept
  ...
  // делаем что-то там нам нужное со старыми открытыми коннектами или с новым
}
...
// закрваем сокеты
Да я вообще-то планировал сделать несколько статей, в каждой из которых постепенно добавлять куски кода, чтобы в конце концов выложить тут полноценный однопоточный кроссплатворменный сервер для высоконагруженных проектов.
Сомневаюсь, что кому-то была бы интересна взявшаяся из воздуха портянка кода из 100500 классов по 9000 строк. Любители таких развлечений ковыряют исходники nginx, а не на форумах сидят.
А вот сейчас, когда мне слили тут всю карму, как то не особенно и хочется уже продолжать.

ЗЫ. Не знаю как в винде, а в Visual Studio 2012 INVALID_SOCKET равен -1. Можете проверить, ссылка на готовый проект есть в статье.
WinSock.h
typedef UINT_PTR        SOCKET;

#define INVALID_SOCKET  (SOCKET)(~0)

Разницу между -1 и ~0 надеюсь объяснять не надо?
Надо, объясните если не затруднит
Значение ~0 зависит от типа. Двоичное представление у него в виде всех единиц: для знакового это будет соответствовать -1, для беззнакового максимальное допустимое значение.
ОК, продолжите свою мысль:
в Windows код
int sd = accept (listen_sd, (struct sockaddr*) &sa_cli, (int *)&client_len);
в случае ошибки вернет какое значение для переменной sd?

Или что то же самое:
в Windows код
int sd = INVALID_SOCKET
вернет какое значение для переменной sd?
Вообще не вижу смысла о чём-то говорить если в винде пишите int sd а не SOCKET sd
За этим заканчиваю обсуждение.

А что не так? Я в C/C++ новичок, но мне вот очевидно, что автор прав. В винде получится именно -1 при приведении к int именно из-за того, что в битовой форме все единицы.

Ну хотя бы то не так, что на винде дескриптор сокета - не число, а .указатель. И приведение его к int в лучшем случае просто не скомпилируется, а в худшем потеряет старшие биты (на 64х битных платформах) и не будет работать.

Все прекрасно компилируется и ничего не теряется. PS: в винде дескриптор сокета - никакой не указатель, а целое число от нуля до INVALID_SOCKET–1.

Не знаю как в винде, а в Visual Studio 2012 INVALID_SOCKET равен -1. Можете проверить, ссылка на готовый проект есть в статье.


Нельзя использовать магические константы! Разработчки, ну зачем вы сами себе собираете атомную бомбу, с таймером произвольного запуска.
а где вообще можно глянуть код целиком?
а где вообще можно глянуть код целиком?
Дочитайте статью до конца — может найдете ответ на свой вопрос.
2014 год, select. Где-то тут делят на 0. Есть же iocp, epoll, libevent.
Если бы проблема была в этом :)
Простите, но где вы тут select нашли?
Теоретически, вместо бесконечного цикла, можно с помощью функции select и ее более мощных аналогов, ждать пока сокет listen_sd станет доступен для чтения, а лишь потом один раз вызвать accept. Но лично я не вижу в моем способе с циклом никаких особых недостатков.


Как-то тоже был интерес к написанию http-сервера в качестве тестового задания. А получился пост :) В нем как раз можно и более «мощные средства» найти. В них бывает польза… Правда приведенный материал без ssl.
Sign up to leave a comment.

Articles