Пишу клиент-серверное приложение с использованием библиотеки Poco.
В пределах одного ПК всё работает нормально, так же, как работало бы через пайпы, вместо сокетов. Проблемы начинаются, когда клиент и сервер находятся на разных ПК (не важно, в локальной сети или через интернет).
Клиент должен подключаться к серверу по сокету, проброшенному в роутэре и фаерволле, после чего на сервере запускается (третья) программа, выполняющая долгие сложные вычисления, передавая в процессе данные клиенту. Т.е. запущенная программа передаёт данные клиенту напрямую, а не через серверный модуль, потому что данных много и часто. Но, ведь для неё выделяется отдельный порт, который не проброшен в роутэре... И клиентов одноврменно может быть больше одного, даже на одном ПК может быть сразу несколько активных клиентов, обращающихся к серверу по одному и тому же проброшенному порту...
Я не понимаю, как мне быть с портами? Один конкретный порт сисадмин может настроить, но для взаимодействия мне нужна куча портов, получается. И куча клиентов будут стучаться на сервер по одному и тому же порту и я плохо представляю, что мне со всем этим делать.
Это моё первое клиент-серверное приложение, я даже не могу толком сформулировать проблему и не знаю, куда мне идти и что читать? Именно С++, именно Poco.
Скажу спасибо за любой совет по существу :)
Армянское Радио, Нет, распределёных вычислений нет. Есть офисные ПК за 10 тысяч и мощный ПК за 200 тысяч. На слабеньком подготавливаются данные и отправляются для обработки на мощный. По кусочкам данные обработать нельзя, поэтому их даже распаралелить не всегда удаётся, не то что распределить.
Передавать нужно от 4 байт (не считая любого дополнительного объёма служебных данных) до любого количества гигабайт (условно, скопировать файл с сервера на клиент, пока что больше сотни гигабайт не было).
maaGames, ну, возмите любой софт для создания очередей, тот же RabbitMQ, или просто складывайте задания в СУБД и из нее же забирайте - на кой черт связываться с низкоуровневым написанием кучи багов на сокетах?
Армянское Радио, Я не могу писать в БД десятки тысяч мелких сообщений в течении нескольких минут и десятки гигабайт тоже писать не могу.
Нужна двусторонняя интерактивная связь (не в реальном времени, задержки в несколько секунд допустимы). А вот использование сторонних библиотек не желательно. Хотя бы Poco можно использовать, уже повезло, что не нужно самому под Вин и Линукс обёртки писать.
maaGames, не можете, потому что не умеете, или у вас чокнутый заказчик?
Сторонние библиотеки не хотите использовать. Опять же - у постгреса есть версия, сертифицированная под Астра Линукс и гостайну.
То есть тут варианты такие - либо вашему заказчику не жалко денег на оплату велосипеда и покрытие рисков, связанных с багами, виноваты в возникновении которых будете лично вы.
Либо вам облом изучать технологии и вы думаете, что сможете быстро и качественно написать надежный конвейер на уровне сокетов сами. Что маловероятно, к сожалению.
Промышленный софт тестируется и эксплуатируется миллионы часов, а сколько времени будет тестироваться ваше поделие до выхода в эксплуатацию? 10 часов от силы, вашими же руками.
Армянское Радио, Встраиваемся в имеющуюся инфраструктуру и можем использовать только то, что в ней есть. И это моё первое клиент-серверное приложение, поэтому и не умею тоже (с реляционными БД умею, но в виде standalone).
Я не вижу, как использование СУБД(любой) решает проблему передачи прогресса вычислений от 0 до 100, например.
maaGames, банально, вы обновляете в СУБД ячейку с прогрессом, а в триггере на это обновление пишите команду отсылки сообщения "прогресс задачи такой-то теперь %d процентов". В клиенте у вас при этом будет вызываться коллбэк, которому будет передаваться текст сообщения.
Передавая по сети гигабайты данных вы столкнетесь с такими интересными штуками, как
-сокет отвалился на середине - что делать?!
-клиент, гад, перестал слать данные - как это ловить и что с этим делать?
-оказывается, отсылка большой кучи данных в сокет приводит к залипанию отсылающей стороны, так что при передаче большого куска в одну сторону нужно реализовывать собственный двусторонний протокол.
И другие интересные истории. Про них есть полная увлекательного чтения книжка Снейдера "Эффективное программирование TCP/IP". Только оно здесь давром не нужно - положили задачку в постгрес - взяли задачку, результат - обратно в постгрес.
Потом еще окажется, что данные там надо как-то тупо агрегировать, поэтому постгрес справится сам, без "считающего" бэкэнда.
Армянское Радио, данные тупо агрегируются в файл :) На локальном ПК файл сразу в нужном месте сохраняется и его не приходится даже передавать, а вот при работе на разных ПК этот файл с результатами расчётов придётся передавать. Как Постгрэс относится к бинарному блоб-объекту размером в десятки гигабайт?
Вроде как Poco часть проблем с сокетами решает, но обработки разрыва соединения там нет...
maaGames, распиши, каким сетевым протоколом пользуешься, какой у тебя протокол соединения между клиентом и сервером с открытия сокета и до перехода в рабочий режим.
Использование базы данных в целом ряде случаев является непрофессиональным оверинженерингом. Для обращения к услугам баз данных нужно техническое обоснование и условия неизбежности использования.
Условно, каждый клиент имеет какой-то идентификатор (например UUID) и вначале каждого блока данных я должен этот идентификатор цеплять, чтобы сервер знал, от кого пакет прилетел? Это решит проблему с получением сервером данных от кучи клиентов (у каждого экземпляра свой UUID). Как я понимаю, после каждого чтения придётся закрывать сокет и заново ждать соединения от следующего клиента, чтобы после соединения запросить адрес клиента? Т.е. ка кобратно с сервера конкретному клиенту запрос посылать?
И что делать с "третьей" программой, которая выполняет работу и должна клиенту посылать данные? Или всё это можно провернуть через один порт при помощи 'Type'?
Сейчас, в варианте "один клиент - один сервер" я держу сокет открытым на протяжении всего времени работы. Как я понимаю, этот подход не подходит для озвученных выше требований и нужно после чтения каждого сообщения сокет закрывать?
> условно держите соединение то ваш сервер блокируется
Да, сейчас именно так и работает - блокируется. Чтобы совсе мне блокировать, я вызываю poll с таймером и в цикле. Вроде как если заменить poll на select с массивом сокетов, то можно слушать сразу несколько сокетов.
Пока только начал читать книжки и ничего не понятно, но, похоже, мне нужно устанавливать логическое соединение с каждым клиентом, потому что нужно двустороннее взаимодейсткие (как минимум, чтобы пользователь мог отправить запрос на прерывание расчётов). Видимо, открывать-закрывать сокет мне не подойдёт. Или подойдёт, но я ещё не дочитал до того, как именно сделать, чтобы подходило :)
Прежде чем писать сетевое приложение вы бы немного почитали про то как там все работает, какие могут быть трудности и как их можно обходить.
Если вы до сих пор этого не знаете, то сервер на одном слушающем сокете может работать с кучей клиентов. Если вы используете TCP для обмена, то на каждое соединение с новым клиентом создается новый сокет полноценный сокет. Вы можете этот сокет передать процессу обработчику. Тогда вам не понадобится куча слушающих сокетов.
Не стоит запускать на каждого клиента отдельный вычислительный процесс, если вычисления достаточно ресурсоемкие вы парой десятков клиентов обеспечите DDoS на свой сервер, т.е. ваш сервер может просто умрететь под нагрузкой.
Нехватка ресурсов ПК будет решаться покупкой ещё одного ПК и переносом на него части клиентов. Т.е. в этом плане можно проблему игнорировать. Уже сейчас 16 поточный ПК с 64ГБ памяти одним вычислительным процессом забивается на 100% и хочет ещё... Видимо, потом будет какое-то ограничение ресурсов на каждый процесс, но сейчас об этом можно не думать.
Я немного почитал, всё непонятно, но очень интересно :) Полноценная работа через сокеты на одном ПК с одним клиентом у меня заработала, теперь пытаюсь разобраться, как это всё переделать под работу с несколькими клиентами и сервером на отдельном ПК. Понятно, что всё на порядок сложнее и то, что аработало в простейшем случае теперь и не должно работать. TCP сокеты для двустороннего обмена, на сервер передаются несколько крупных блоков данных (обычно не более гигабайта в сумме) и потом с сервера на клиент передаётся очень много мелких данных в процессе работы и крупные объёмы с результатам ирасчётов, там объём ограничен только объёмом жёсткого диска.
Вот с полноценным сокетом у меня проблема, что его же надо в роутере пробрасывать, а админ только порт для сервера пробрасывать должен, а не для каждого запущенного экземпляра. Или я ещё чего-то совсем не понимаю, но без проброса портов не получается соединиться.
maaGames, Проще всего сразу после установки соединения стартовать вычислительный процесс и ему передавать новый клиентский сокет. А серверный процесс продолжает дальше принимать соединения.
res2001, Да, я примерно так это себе и представляю. Серверный процесс нужен только для запуска вычислительного процесса и передачи клиенту порта этого процесса. Но не ясно, как они друг друга увидят, ведь порт "случайный" и в роутере не открыт. Т.е. если в вычислительном процессе порт будет пассивным и случайным, то на клиенте нужно делать активный и открывать в роутере, но тогда только один Клиентский процесс может быть запущен. Т.е. пока идут вычисления, клиент может работать с другими расчётными моделями и запустить второй расчёт параллельно, ведь его ПК при этом не загружен расчётами.
res2001, Сейчас Виндоус7-х64 (и старше) и Линуксы не знаю пока какой версии минимум. Велика вероятность что один из "российских" вариантов из-за санкций. Я серьёзно :(
maaGames, Астра линукс - вполне годный вариант. Есть сертифицированный вариант с ядром 4 версии можно считать более-менее актуальным. Если сертификат не нужен - то доступны все новые ядра.
В линукс передать сокет можно если новый процесс создавать с помощью fork(), тогда все открытые дескрипторы наследуются дочерним процессом. Есть аттрибут дескриптора с помощью которого можно регулировать, какие дескрипторы должны быть унаследованы порожденным процессом. Это работает с любыми дескрипторами (файлами, сокетами и т.п.)
В винде сходу не скажу как передавать дескриптор сокета, но наверняка механизм есть.
maaGames, Для винды можете начать отсюда: www.codenet.ru/progr/inet/socket_proc.php
Вы должны будете у poco получить "сырой" дескриптор сокета соединения, а затем его передавать.
После того как дескриптор передан, на слушающем процессе его можно закрыть.
maaGames, парой IP:PORT определяется UDP сокет (и слушающий TCP сокет), а TCP сокет соединения определяется 2 парами для обеих сторон соединения. Поэтому TCP сокет установленного соединения называют полным.
Хорошая книга по программированию сокетов: Unix. Разработка сетевых приложений У.Стивенс
Она хоть и давно не переиздавалась (автор уже умер к сожалению), но по моему является лучшей книгой по сетевому программированию и до сих пор не утратила актуальность. В свое время покупал ее на alib.ru.
С сырым дескриптором кроссплатформенно нельзя работать.
res2001, Пока что читаю Снейдера, всего 300 страниц, может не запутаюсь и что-нибудь пойму... Пока точно понял, что я путаюсь в терминологии и не могу корректно сформулировать диаграму взаимодействия процессов :)
Хранить сырые сокеты мне в любом случае не подходит, т.к. после запуска Вычислительного процесса у пользователя должна быть возможность не просто закрыть соединение, но и выключить свой ПК. Т.е. результаты сохраняются на сервере до тех пор, пока пользователь не будет готов их получить. Получается, что в процессе расчёта данные о прогрессе могут как отсылатсья, так и не отсылаться и результаты могут отсылаться как сразу, так и через какое-то время.
Полноценную СУБД решили не подключать, т.к. это сильно усложнит работу в локальном режиме, когда и клиент и сервер на одном ПК, а две разных реализации делать мы не будем. И основным режимом именно работа на одном ПК является, клиент-сервер это менее востребованный функционал на сегодня.
Хранить сырые сокеты мне в любом случае не подходит
Вам и не нужно их хранить. Вам нужно сырой дескриптор только передать порожденному процессу, порожденный процесс получает дескриптор и на его основе создает соответствующий класс poco (я надеюсь poco продоставляет такой функционал), т.е. poco должен не открыть сокет, а присоединиться к нему (attach).
А дальше работаете как обычно через poco.
Если пользователь закрыл соединение, то когда он откроет его снова, порожденный процесс вместо того чтобы что-то вычислять просто прочитает сохраненные данные и передаст клиенту.
На счет не использования СУБД - вопрос спорный что тут больше усложнит, но вам, конечно, виднее.
res2001, СУБД усложнит в том смысле, что сейчас данные сразу сериализуются в файл в нужном формате и в качестве результата просто возвращается имя этого файла. С СУБД придётся делать лишние записи и чтение из неё, что при локальной работе не нужно. Делать отдельную версию для сетевого варианта не хочется, поэтому пока что думаем, что сумеем обойтись без СУБД.
Спасибо. Пока что буду читать и пытаться разобраться в происходящем.