Пишу сервер приложение на с++ через libuv для работы с большим количеством TCP клиентов, поэтому ресурсы важны.
Я в libuv пока не силён, но на сколько я понял, для отправки по TCP нужно использовать uv__write и если сокет пока не может принять данные, то внутри libuv появляется очередь на запись.
И если 100 раз я вызвал uv__write пока сокет недоступен для записи, то будет очередь из 100 запросов за запись и потом вызовется 100 раз функции send().
Это не оптимальное решение. Было был лучше так: Я сам строю очередь на запись, а libuv вызывает мою callback функцию когда сокет доступен на запись и я вызываю send() и сам уменьшаю очередь на запись.
Можно ли в libuv сделать так ?
M>>Пишу сервер приложение на с++ через libuv для работы с большим количеством TCP клиентов, поэтому ресурсы важны.
Pzz>А большое количество — это сколько? И какова совокупная нагрузка/нагрузка по клиенту?
около 300 тыс соединений. Нагрузка пока не известна, но нужно написать максимально хорошо, чем больше выдержит тем лучше.
Пока такая задача.
Здравствуйте, maks1180, Вы писали:
M>около 300 тыс соединений. Нагрузка пока не известна, но нужно написать максимально хорошо, чем больше выдержит тем лучше. M>Пока такая задача.
Pzz>300K — это много.
95% будут в режиме ожидания находиться и только 1 раз в минуту отправлять маленькие пакеты.
Pzz>Ну в принципе, у тебя есть такой API: https://github.com/libuv/libuv/blob/v1.x/docs/src/poll.rst
Спасибо, попробую.
Pzz>Я только не очень понимаю, зачем тебе libuv, если почти всю ее работу ты собираешься делать сам, и низкоуровнего сокетного API вроде как не боишься.
Но не всю, а только отправку по сигналу от libuv хочу сделать. Конечно можно самому разобраться с epoll (на Linux) и completion port (на Windows). Но зачем если libuv хорошо протестирована ?
Pzz>>Я только не очень понимаю, зачем тебе libuv, если почти всю ее работу ты собираешься делать сам, и низкоуровнего сокетного API вроде как не боишься. M>Но не всю, а только отправку по сигналу от libuv хочу сделать. Конечно можно самому разобраться с epoll (на Linux) и completion port (на Windows). Но зачем если libuv хорошо протестирована ?
Подебажил я по исходникам libuv — очень много всего нагорожено. Что-бы сделать UPDATE-ON-FLY с libuv нужно будет долго возиться. Действительно проще самому реализовать работу с epoll и completion port.
UPDATE-ON-FLY — это когда приложение передаёт все дескрипторы и всю информацию своей новой версии и та начинает работать не разрывая соединения. Клиент вообще не замечает такого обновления. Мы делали эксперимент и с 300 тыс TCP клиентов, апдей занял около 500мс, т.е. клиенты максимум получали доп задержку в 500мс и не замечали обновления.
Здравствуйте, maks1180, Вы писали:
M>UPDATE-ON-FLY — это когда приложение передаёт все дескрипторы и всю информацию своей новой версии и та начинает работать не разрывая соединения.
Насколько я помню, это в винде нельзя было сделать — передать сокет другому процессу.
Здравствуйте, maks1180, Вы писали:
Pzz>>300K — это много. M>95% будут в режиме ожидания находиться и только 1 раз в минуту отправлять маленькие пакеты.
А протокол самодельный?
Pzz>>Я только не очень понимаю, зачем тебе libuv, если почти всю ее работу ты собираешься делать сам, и низкоуровнего сокетного API вроде как не боишься. M>Но не всю, а только отправку по сигналу от libuv хочу сделать. Конечно можно самому разобраться с epoll (на Linux) и completion port (на Windows). Но зачем если libuv хорошо протестирована ?
А тебе так уж важно, чтобы работало и на linux и в венде?
Дело в том, что у линуха с вендой модель асинхронного ввода-вывода очень разная. На линухе мы дожидаемся готовности сокета (т.е., наличия у него места в буфере), потом туда делаем send(), сокет копирует данные в свой буфер и отправляет в сеть по мере возможности, а нас уведомляет, когда в него можно подпихнуть следущую порцию данных.
Венда же, наоборот, мы ей сразу даем порцию данных, а она сообщает, когда они отправлены. Это называется overlapped i/o.
На венде трудно сэмулировать линуксное поведение достаточно хорошо, чтобы массштабировалось на 300К сокетов. Проще наоборот, эмулировать вендовое поведение на линуксе. Отсюда очереди, которые в венде просто превратятся в overlapped i/o с completion port (т.е., очередью будет управлять система), а на линуксе будут управляться програмно. Но зато высокоуровневый API один и тот же.
Если учесть, что основная серверная платформа телерь линукс, может вообще, ну ее нафиг, эту венду?
M>>UPDATE-ON-FLY — это когда приложение передаёт все дескрипторы и всю информацию своей новой версии и та начинает работать не разрывая соединения.
F>Насколько я помню, это в винде нельзя было сделать — передать сокет другому процессу.
Я это делал в Linux. В Windows тоже вроже можно через DuplicateHandle
M>И если 100 раз я вызвал uv__write пока сокет недоступен для записи, то будет очередь из 100 запросов за запись и потом вызовется 100 раз функции send().
Подебажил я исходники libuv в Линуксе она вызывает не send, а write/writev. Возможно она сможет объединить много запросов на запись через 1 вызов writev, но для этого ей нужно выделить память, что-бы собрать все запросы для 1 вызова writev — не уверен что она так будет делать.
Почему write, а не send ? Кто из них быстрее будет работать ?
Pzz>>>300K — это много. M>>95% будут в режиме ожидания находиться и только 1 раз в минуту отправлять маленькие пакеты.
Pzz>А протокол самодельный?
Да
Pzz>А тебе так уж важно, чтобы работало и на linux и в венде?
Для Windows не особо важно, но удобнее тестировать общий код на Windows и может пригодиться кому-нибудь такое решение.
Pzz>Дело в том, что у линуха с вендой модель асинхронного ввода-вывода очень разная. На линухе мы дожидаемся готовности сокета (т.е., наличия у него места в буфере), потом туда делаем send(), сокет копирует данные в свой буфер и отправляет в сеть по мере возможности, а нас уведомляет, когда в него можно подпихнуть следущую порцию данных. Pzz>Венда же, наоборот, мы ей сразу даем порцию данных, а она сообщает, когда они отправлены. Это называется overlapped i/o.
В любом случаи я должен хранить буффер для отправки сам (для libuv тоже), и поэтому для отправки я не вижу проблем.
Вызываю send и жду, в Linux когда статус сокета измениться, в Windows когда операция завершиться.
Для чтения, в Windows нужно заранее выделить буфер. Вот тут отличия, так как зараняя выделить существенный буфер для 300К клиентов — это накладно.
А общий буфер для всех клиентов в Windows так наверно нельзя делать ? Или можно если только 1 поток будет работать с IoCompletionPort ?
Если нельзя, попробую выделить 1 байт и потом уже дочитать вторым вызовом recv().
Есть ещё какие-то варианты в Windows, без существенного буфера для каждого клиента ?
Pzz>Если учесть, что основная серверная платформа телерь линукс, может вообще, ну ее нафиг, эту венду?
Может и откажусь. Но пока вроде для моих задач, можно на обоих сделать.
Здравствуйте, maks1180, Вы писали:
Pzz>>А протокол самодельный? M>Да
С самодельными протоколами в реальной обстановке хлопот не оберешься...
M>Для чтения, в Windows нужно заранее выделить буфер. Вот тут отличия, так как зараняя выделить существенный буфер для 300К клиентов — это накладно. M>А общий буфер для всех клиентов в Windows так наверно нельзя делать ? Или можно если только 1 поток будет работать с IoCompletionPort ?
Нет, нельзя. Completion port — это очередь статусов отработанных запросов. Сами запросы работают независимо от потока, который выгребает completion port.
M>Если нельзя, попробую выделить 1 байт и потом уже дочитать вторым вызовом recv().
Не очень хорошая идея. Тебе придется делать два вызова на каждую порцию данных.
M>Есть ещё какие-то варианты в Windows, без существенного буфера для каждого клиента ?
Насколько я помню, таких, которые умеют работать с completion port, других больше нет.
M>>Есть ещё какие-то варианты в Windows, без существенного буфера для каждого клиента ?
Pzz>Насколько я помню, таких, которые умеют работать с completion port, других больше нет.
Если сделать через message-based notification (WSAAsyncSelect) и отдельное окно для этих целей.
Насколько это решение будет выдерживать большие нагрузки ?
Это решение будет аналогично epoll ? т.е.информировать когда у сокета есть данные для чтения, или он готов принять данные для отправки.
Здравствуйте, maks1180, Вы писали:
M>Пишу сервер приложение на с++ через libuv для работы с большим количеством TCP клиентов, поэтому ресурсы важны.
Собери буст.асио либо вообще gRPC возьми. У тебя не такие серьёзные нагрузки чтобы прямо по хардкору развлекаться с сокетами, uv и epool.
Здравствуйте, maks1180, Вы писали:
M>И если 100 раз я вызвал uv__write пока сокет недоступен для записи, то будет очередь из 100 запросов за запись и потом вызовется 100 раз функции send(). M>Это не оптимальное решение. Было был лучше так: Я сам строю очередь на запись, а libuv вызывает мою callback функцию когда сокет доступен на запись и я вызываю send() и сам уменьшаю очередь на запись.
Вроде бы Nagle-алгоритм включен по умолчанию, поэтому переживать насчёт рукопашного уменьшения количества send не нужно?
M>>Пишу сервер приложение на с++ через libuv для работы с большим количеством TCP клиентов, поэтому ресурсы важны. K>Собери буст.асио либо вообще gRPC возьми. У тебя не такие серьёзные нагрузки чтобы прямо по хардкору развлекаться с сокетами, uv и epool.
серьёзные нагрузки, это какие ? Какой предел у обычного компа с 4-х ядерный процессором и с 1 Гбит сетевой картой ?