Каков наилучший способ реализации кросс-платформенного многопоточного сервера в C/С++?

В состав группы разработчиков, с которой я работаю, была поставлена ​​задача написать сервер для интеграции с нашим продуктом. У нас есть некоторые низкоуровневые сенсорные устройства, которые обеспечивают SDK SD, и мы хотим поделиться ими по сети для использования людьми, собирающими данные. Звучит просто, не так ли? Кто-то подключит сенсорное устройство к своей машине в одной части здания и запустит наш сервер, таким образом, совместно используем устройства (-и) с остальной частью сети. Затем клиент будет подключаться к этому серверу через наше приложение и собирать показания датчика с устройства.

Я создал простой, язык-агностический сетевой протокол и ссылку на Java. Проблема заключается в создании реализации, которая будет работать с нашими устройствами, которые предоставляют только SDK, написанный на C. Мы думали сделать следующее:

  • Создайте опросные потоки, которые собирают и сохраняют самые последние показания с каждого подключенного устройства.
  • Используйте многопоточный сервер, чтобы открутить каждое входящее соединение к рабочему потоку.
  • Когда рабочий поток получает запрос на считывание датчика, последнее сообщение, полученное потоком опроса, отправляется обратно клиенту.

Это много потоков, особенно в C. Итак, чтобы рассмотреть, общие требования:

  • Работает на компьютерах под управлением Windows XP/Vista, Linux и OS X.
  • Написанный на C или С++, для взаимодействия с SDK SD мы имеем
  • Принимает переменное количество одновременных подключений (рабочие потоки)
  • Необходимо использовать потоки, а не разветвлять (не хотят иметь дело с другим уровнем IPC)

Может ли кто-нибудь предложить библиотеку и желательно какой-то пример кода для начала использования?

Ответ 1

Я использовал Boost.Thread и Boost.Asio для создания многопоточного сервера в системах Windows и Linux. Учебники упростили работу.

Ответ 2

Лучший способ написать такой сервер - не писать и перестраивать систему, чтобы она не была необходима, и/или повторно использовать компоненты, которые уже существуют. Потому что:

Кто-то подключит сенсорное устройство к их машине в одной части создания и запуска нашего сервера, таким образом разделение устройства (ов) с остальной частью сети.

Это также может поделиться всей машиной с остальной частью сети, если ваш код имеет уязвимость (что, вероятно, будет, поскольку вы пишете его на С++ с нуля и изобретаете новый протокол).

Итак, сделайте это наоборот. Установите простой клиент на машине с аппаратным обеспечением датчика, затем запустите ее либо все время, либо периодически, и попросите (отправить) результаты на центральный сервер. Центральный сервер может быть стандартным веб-сервером. Или это может быть база данных. (Обратите внимание, что оба они уже написаны - нет необходимости изобретать колесо, -)

Ваше приложение работает так же, как вы сейчас это понимаете, однако оно собирает данные из базы данных, а не из датчиков. Тем не менее, часть, работающая на машине с датчиком, сократилась от многопоточного пользовательского кошмара на сервере, до небольшого однопоточного клиента командной строки, который только делает исходящие соединения и который может запускаться из cron (или эквивалент в окнах).

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

edit: ceretullis и ответы pukku предлагают хороший вариант для этого, используя multicast - см. этот ответ и комментарии

Ответ 3

Дуглас Шмидт ACE (адаптивная среда связи) является зрелым, высоконагруженная версия с открытым исходным кодом для создания высокопроизводительных многопоточных серверов. В основном это касается телекоммуникационных приложений, но используется для различных проектов. Он также поставляется с броузером запроса объектов под названием TAO (если вы находитесь в CORBA)

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

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

О, и, кстати, он бесплатный, как в речи, - и как в пиве. Если вы хотите коммерческий маршрут, есть и экосистема консультантов, которая будет оказывать поддержку и наставничество для него. Учебник с некоторыми фрагментами кода можно найти здесь.

Ответ 4

Я бы использовал QT. Он имеет кросс-платформенную поддержку резьбы. Хорошая документация:

QT Threading Documentation

Механизм обмена сообщениями/слотами работает без проблем между потоками.

Ответ 5

Не совсем ответ на ваш вопрос, но поскольку кажется, что вы на самом деле более знакомы с Java, чем с C/С++, почему бы не продолжить свою ссылочную реализацию и подключиться к SDK, используя Java Native Interface. (Я никогда не использовал его, но я решил, что он будет полезен именно для таких ситуаций.)

В качестве альтернативы вы можете легко написать простую программу на языке С, которая использует SDK, а затем отправляет данные в вашу программу Java, например, используя потоки сокетов. Таким образом, вы могли бы снова обрабатывать более сложные вещи в Java.

Ответ 6

Я также хотел бы рекомендовать The Spread Toolkit, который (согласно веб-сайту проекта) является "набором инструментов с открытым исходным кодом, который обеспечивает высокопроизводительную службу обмена сообщениями, которая устойчива к сбоям в локальных и глобальных сетях". Я использовал его несколько раз в ситуациях, которые очень похожи на ваши. По сути, он дает вам сервер, который предлагает frankodwyer.

Демон Spread (т.е. сервер) не является многопоточным, но он очень быстро и масштабируется, по крайней мере, до сотен или тысяч клиентов. Кроме того, протокол обслуживает надежную многоадресную IP-рассылку, которая (в среде с несколькими клиентом) может дать вам (по производительности) определенный предел против чего-либо, реализованного только с использованием соединений точка-точка TCP или UDP. (Но: не пытайтесь реализовать надежную IP-многоадресную рассылку самостоятельно... по-видимому, проект Spread выпустил ряд тезисов PhD/MSc как побочный продукт - или это инструментарий, который является побочным продуктом, в то время как основной упор всегда было академическим исследованием? Я действительно не знаю...).

Поскольку у Spread есть клиентские API C и Java (плюс Python), это похоже на очень хорошее совпадение с вашей проблемой. Они имеют две модели лицензирования; первая альтернатива близка к BSD. Конечно, это кросс-платформенный (как для клиента, так и для сервера).

Однако Spread (конечно) не сделает все для вас. Наиболее заметно, возможно, это не делает упорство (то есть, если ваш клиент находится в автономном режиме или иначе не может получать сообщения, Spread не будет их буферизовать, по крайней мере, за очень небольшим количеством msgs). Но, к счастью, на самом деле не слишком сложно выполнить собственную реализацию настойчивости поверх того, что дает Спред (я даже не знаю, важна ли для вас такая проблема или нет). Во-вторых, Spread ограничивает ваши сообщения до 100 килобайт каждый, но этот предел также довольно легко обойти, просто отправив отправителю большое сообщение на несколько меньших, а затем конкатенируя их в приемнике.

Ответ 7

Я использую libevent для мультиплексирования сетевых операций ввода-вывода. Вы должны рассматривать это как альтернативу Boost.Asio.

API libevent предоставляет механизм для выполнения функции обратного вызова, когда определенное событие происходит в файловом дескрипторе или после достижения тайм-аута. В настоящее время libevent поддерживает /dev/poll, kqueue, порты событий, select, poll и epoll.

Ответ 8

Я согласен с frankodwyer, переверните протокол от модели pull до модели push.

Если компьютер с подключенным датчиком передал показания через многоадресную рассылку UDP на частоте 100 Гц (или что бы то ни было, поскольку для ваших датчиков) всякий раз, когда работает служба совместного доступа. Затем напишите клиентам, которые читают данные многоадресной рассылки.

В качестве альтернативы вы можете использовать широковещательный UDP вместо многоадресной рассылки.

Кстати, это количество GPS, Lidar и других датчиков.

Ответ 9

Используйте API кросс-платформы или создайте свой собственный API, который вы изменяете для каждой архитектуры.

Также пересмотреть это: http://www.goingware.com/tips/getting-started/

Ответ 10

Если вы хотите использовать C (а не С++), библиотека NSPR может предоставить вам то, что вам нужно...

Ответ 11

Я настоятельно рекомендую вам рассмотреть образец шаблона прототипа.

Я использовал этот шаблон для написания агностического сервера протокола на С++, который я использовал для всего от HTTP Web Services до пользовательских собственных бинарных протоколов.

В принципе, идея такова:

Сервер заботится о принятии() входящих входящих соединений на конкретном порту и создании потоков (или процессов) для обработки этих соединений.

Когда вы пытаетесь создать общий сервер, вы понимаете, что не можете читать или писать какие-либо данные, не делая предположений о протоколе... Итак, фокус в том, чтобы использовать шаблон прототипа.

Создайте класс "ConnectionHandlerBase" с чистым методом "HandleConnection() = 0". Сделайте пользователей класса подкласса класса этого класса своей собственной реализацией. Кроме того, этот класс реализует метод "Clone()", который возвращает копию самого себя... Таким образом, сервер может создавать новые экземпляры, не требуя знать его тип... Затем, когда вы получаете соединение, вызовите "Clone()" в вашем прототипном экземпляре и обработать поток "HandleConnection()" на этом объекте.

При запуске приложения пользователь класса сервера должен вызвать что-то вроде этого:

"Server.AttachConnectionPrototype(& MyConnectionObject);"