powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
260 сообщений из 260, показаны все 11 страниц
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38754161
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Типа, просто и понятно, но чем плохи сокеты Windows или чем не устраивает Indy или ICS или IPWorks! или ещё что...
И - где лучше не использовать.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38754166
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вот что пишут.

Предположим, мы проектируем систему и используем "чистый" tcp.

Вопросы:
- используем блокирующие сокеты? Или асинхроные? Блокировка => просто кодить, но считается, что приложение "плохо масштабируется". Если же используем асинхронный i/o, то запариваемся с кодингом.

- как решить вопрос с изменением нагрузки? Какой компонент будет считаться сервером, какой - клиентом? Что делать, чтобы подключить сервер к серверу? Что делать, если связь часто обрывается?

- что делать, когда сообщение частично? Что делать, если сообщения поступают слишком часто? Если сообщение не помещаются во входной буфер?

- где и как хранить очередь сообщений? Нужна ли очередь на приеме? ... на передаче?

- что делать с потерянными сообщениями? Игнорировать? , отправить запрос повторно, выбросить исключение, ...?

- что делать, если нам нужно использовать другой сетевой транспорт? Скажите, мультикаст вместо уникаст TCP ? Или IPv6?

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

- как обработать ошибки сети? Игнорировать, попробовать еще раз, завершить работу?

- ...?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38754172
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Есть вариант использовать готовые системы, ориентированные на обмен сообщения.
Они отлажены, стандартизированы, решают проблемы с балансировкой нагрузки, с обработкой ошибок, с динамической маршрутизацией, с кодировками и т.п.
К сожалению, эти системы - брокер - ориентированные. То есть, обычно предполагают наличие специального вычислительного процесса, который работает на выделенном железе и обслуживается специально выделенными людьми.
Следовательно, это стоит делать только для больших распределенных приложений с множеством компонентов, построенных большими командами.
...
PS: мы, например, для одной распределенной задачи использовали существующую сеть jabber - серверов, "паразитируя" на них.
Кодить, используя сообщения, а не tcp - поток, намного приятнее. :)
Позднее мы пытались отойти от использования публичных jabber -серверов и запустили на своем железе собственный сервер, но надежность системы в итоге упала: наш админ не смог обеспечить доступ к ресурсу в обещанном режиме 24х7...

...
Таким образом, небогатым разработчикам небольших и средних приложений приходится либо избегать сетевого программирования и делать "монолитны" приложения, которые плохо развиваются.
Либо "нырять в сокеты" и создавать сложные в разработки и сопровождении приложения.

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

ZeroMQ была создана для того, чтобы сделать возможной работу с сообщениями простым и дешевым образом, чтобы она могла работать в любом приложении, и была бесплатной (или со стоимостью, близкой к нулевой).
Она была разработана как библиотека, которую просто можно использовать без каких-либо зависимостей.
Она запускается на любой ОС и работает с любым языком программирования.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38754243
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Поехали!

Сокеты.

Жизнь сокетов ZeroMQ состоит из четырех частей.

1. Создание и уничтожение, которые должны идти парами: см zmq_socket (), zmq_close () .
2. Настройка сокета: установить параметры сокета: zmq_setsockopt () , проверить настройки сокета: zmq_getsockopt () .
3. Подключение сокета в топологию сети путем создания сходящего или исходящего ZeroMQ соединения: zmq_bind ( ), zmq_connect () .
4. Использование сокета для передачи данных путем записи и приема сообщений в/из них: zmq_msg_send ()/ zmq_msg_recv () .

Сокеты в Delphi представлены просто указателями (Pointer).
А сообщения zmq_msg_t - структурой:

Код: pascal
1.
2.
3.
zmq_msg_t = record
    _: Array[0..32-1] of Byte;
  end; 




Подключение сокетов

Для создания соединения между двумя узлами на одном узле использует zmq_bind() , а на втором zmq_connect() .
Принято считать, что узел, где zmq_bind() - это "сервер", который располагается в заранее известной точке сети (т.е., имеет фиксированный сетевой адрес). А узел, где используется zmq_connect() - "клиент", его сетевой адрес заранее неизвестен. Говорят "привязка" сокета ("биндинг") и подключение ("коннектинг"). То есть "привязываем" сокет к конкретной точке, и "подключаем" сокет к конкретной точке, "конкретная точка" - это известный сетевой адрес.

Соединения ZeroMQ отличаются от привычных соединений TCP:

Работают по разным транспортным протоколам (inproc, ipc, tcp, pgm, epgm).
Один сокет может иметь много исходящих и много входящих соединений.
Метода Accept() /zmq_accept()/ нет! Когда сокет привязывается к конретной точке, Accept() стартует автоматически.
Сотевые соединения выполняются в фоне, а ZeroMQ автоматом реконнектится, если сеть обрывается и восстанавливается.
Приложение не будет работать с соединением напрямую, только через сокет ZeroMQ.


В ранее рассмотренных примерах мы запускали клиента до запуска сервера, безо всяких проблем.
В обычных сетях мы бы уже получили сообщение, что сервер не готов и т.д.
Но ZeroMQ позволяет запускать и останавливать разные сетевые компоненты в произвольной последовательности. Как только узел - клиент вызывает zmq_connect () , соединение уже существует, и что узел может начать писать сообщения в сокет.
На определенном этапе (хорошо бы, до момента переполнения очереди сообщений ), сервер оживает, выполняет zmq_bind () , и ZeroMQ начинает доставлять сообщения.

Узел-сервер может биндиться к нескольким конкретным точкам сети. И даже к разным протоколам.

Код: pascal
1.
2.
3.
zmq_bind (socket, 'tcp://*:5555');
zmq_bind (socket, 'tcp://*:9999');
zmq_bind (socket, 'inproc://somename');



К конкретной точке сети нельзя биндиться дважды. Если будет запущено два сервера, выполняющие такой код:
Код: pascal
1.
zmq_bind (socket, 'tcp://*:9999');

- то работать будет первый, второй будет ждать.

Пишут, однако, что для ipc транспорта допускается множественный биндинг, но нас это пока не касается: ipc пока реализован только на всяких линуксах.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38754256
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Добавление:

Протокол inter-thread (inproc) - это транспорт, основанный на сигналах. Очень быстрый. Но с ограничениями: сервер должен выполнить биндинг до того, как клиент попытается законнектиться. Пишут, что в будущем, возможно, это поведение будет исправлено (приведено к стандартному). А пока рекомендованная схема: главный поток создает сокет, биндит его к уникальному имени, потом создает дочерний поток, в котором так же создается сокет, который коннектится к inproc с тем же именем.

В остальном с inproc точно так же:

Биндинг в одном потоке:
Код: pascal
1.
2.
3.
4.
5.
//  Имя - "#1"
zmq_bind(socket, 'inproc://#1');
...
//  Имя - 'my-endpoint'
zmq_bind(socket, 'inproc://my-endpoint');



Конектинг в другом:

Код: pascal
1.
2.
3.
zmq_connect(socket, 'inproc://#1');
...
zmq_connect(socket, 'inproc://my-endpoint'); 



То есть: 'имя протокола://уникальное имя коннекта'
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756923
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Дальше я буду писать ZMQ вместо ZeroMQ.
Или даже 0MQ.

С целью экономии ресурса клавиатуры.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756927
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Шаблоны сетевых топологий ZeroMQ.

В первой части
были представлены и подробно описаны приложения, реализующих два простых шаблона сетевой топологии вроде "Запрос - Ответ" и "Издатель - Подписчик.
Во 2й части был представлен шаблон решения задачи методом "Разделяй и Властвуй" (когда задачу разделяем на кусочки и разадаем исполнителям). При этом использовался шаблон "Параллельный трубопровод" (Parallel Pipeline).



Считается, что ZMQ "искаропки" реализует четыре шаблона:

1. Запрос-Ответ (Request-Reply), в котором соединяются множество клиентов к множеству сервисов ("серверов"). Этот шаблон предназначен для реализации удаленного выполнения каких-либо задач, вроде.
2. Издатель - Подписчик (Pub-sub), в котором соединяются множество издателей с множеством подписчиков. Это - шаблон для реализации задачи предоставления данных по подписке.
3. Трубопровод (Pipeline), в котором соединяются узлы для "проталкивания" (в т.ч. параллельного) сообщений, для задач, требующих в процессе решения множество этапов перемещения (в том числе циклического) данных. Это - шаблон для решения задач вроде разделения большого задания на несколько меньших с последующим сбором результатов.
4. Эксклюзивная пара (Exclusive pair), в которой соединяются только два сокета. Это - шаблон для решения задач вроде связи двух потоков в процессе.

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

В каждом из представленных приложениях сокеты настраивались в соответствии с задачей.
Для пар сокетов ZMQ есть следующие допустимые комбинации настроек:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
     PUB    - SUB
    REQ    - REP
    REQ    - ROUTER
    DEALER - REP
    DEALER - ROUTER
    DEALER - DEALER
    ROUTER - ROUTER
    PUSH   - PULL
    PAIR   - PAIR
 

Половину из этих комбинаций мы уже знаем. :)
Остальные я еще только собираюсь пощупать.

Из документации:
Использование пар сокетов ZMQ в других комбинациях повлечет появление недокументированных эффектов и, возможно, чудовищные разрушения оборудования.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756935
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Поговорим о СООБЩЕНИЯХ ZMQ

В рассмотренных ранее тестовых приложениях мы пользовались методами zmq_send () и zmq_recv () . В их параметрах мы указывали сокет, адрес буфера с данными и длину данных. Все незатейливо. Беда в том, что zmq_recv() нельзя использовать, если не знаешь заранее размер принимаемого сообщения: если сообщение не поместится, хвост отсечётся.

Так вот, в API ZMQ есть чудесные методы для работами со структурой zmq_msg_t.
Здесь уже возможностей больше (но и кодить придется больше):


Инициализация сообщения: zmq_msg_init(), zmq_msg_init_size(), zmq_msg_init_data() .
Отправка и получение соощения: zmq_msg_send(), zmq_msg_recv() .
Освобождение (ресурсов) сообщения: zmq_msg_close() .
Доступ к данным сообщения: zmq_msg_data(), zmq_msg_size(), zmq_msg_more() .
Работа со свойствами сообщения: zmq_msg_get(), zmq_msg_set() .
Манипуляции с сообщениями: zmq_msg_copy(), zmq_msg_move() .

В памяти сообщение представляется собой структуру:

Код: pascal
1.
2.
3.
4.
type
  zmq_msg_t = record
    _: Array[0..32-1] of Byte;
  end;



Особенности работы с сообщениями ZMQ:
- вы создаете и передаете в работу только объекты zmq_msg_t, а не блоки данных;
- для чтения сообщения, сначала вызываем zmq_msg_init() (создается пустое сообщение), а затем передаем его в zmq_msg_recv() ;
- для инициализации сообщения вашими данными, необходимо вызвать zmq_msg_init_size() - будет создано сообщение и выделен блок данных указанного размера. Затем вы "руками" заполняете данные и передаете сообщение в zmq_msg_send() .
- для освобождения (для очистки, а не для удаления объекта zmq_msg_t), вызываем zmq_msg_close() . Это действие сбросит ссылку в ZMQ на структуру и для ZMQ соощение перестанет существовать.
- для доступа к содержимому сообщения, используем zmq_msg_data() . Для получения длины блока данных в байтах используем zmq_msg_size() .

Внимание! Операции zmq_msg_move(), zmq_msg_copy(), zmq_msg_init_data() опасны разрушением контекста, если вы не знаете, для чего и как их использовать.

После того, как сообщение было передано в zmq_msg_send(), ZMQ очистит сообщение, то есть, например, заполнит структуру нулями.

PS: ХЗ, что там делается реально - сообщение то зануляется, то нет. Ну, раз сказано, значит, так и есть.

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

Для zmq_send() и zmq_recv(), эти правила не работают, тут вы пересылаете свой массив байт, а не структуру zmq_msg_t.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756937
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Если все же нужно отправить одно и то же сообщение более, чем один раз, и оно довольно большое, создаем еще одно сообщение, инициализируем его с помощью zmq_msg_init() , а потом используем zmq_msg_copy() . Будет создана копия исходного сообщения. Такой способ не создает копию сообщения, а просто создает копию ссылки на сообщение.
Теперь это сообщение можно отправить дважды (если нужно больше - создаем больше копий).

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
  zmq_msg_init_size(fMsg1, 9999 * SizeOf(Char)); // Инициализация
  FillChar(zmq_msg_data(fMsg2)^, 9999, 'ё'); // Запонение буквами ё

  zmq_msg_init(fMsg2); // Создание копии
  zmq_msg_copy(fMsg2, fMsg1);

  zmq_msg_send(fMsg1, fSocketSender, 0);
  zmq_msg_send(fMsg2, fSocketSender, 0);


Сообщение будет полностью удалено только после того, как будет отправлена последняя копия.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756944
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQ также поддерживает сообщения, состоящие из нескольких частей (фрагментов, фреймов).

В этом случае в одном сообщении содержится несколько кадров.
Для однокадровых сообщений структура пересылаемых данных очень проста:
[Длина блока данных][...блок данных ...]
Составные (многокадровые) сообщения состоят из нескольких таких кадров, например

Код: javascript
1.
2.
3.
3 'ABC'    // Длина: 3 байта + строка из трех однобайтных символов
0          // Длина: 0 байт (пустой кадр)
6 'Привет' // Длина: 6 байт + строка из шести однобайтных символов



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

Фреймы представляют собой базовый для ZMQ формат, в котором данные передаются по выбранному транспорту.
Длина - от нуля и дальше.

PS: фреймы - это несомненная польза с т.зр. программистов, использовавших TCP, для которых всегда был актуален вопрос: "а сколько мне нужно прочитать данных из данного сокета в данных момент?".


Такой покадровый обмен данными называется ZMTP протоколом .

Обычно сообщения ZeroMQ состоит из одного кадра, наподобие датаграммы UDP.
Для превращения сообщения в мультикадровое ZMQ просто устанавливает в кадре бит "есть ещё!" . Затем читаем следующие кадры, пока не получим последний кадр со сброшенным битом.

И так.

Сообщение может состоять из одного и более частей.
Части сообщения называются кадры/фреймы (frames).
Каждая часть есть представляется объектом zmq_msg_t .
Программист принимает и отправляет каждую часть отдельно (используя API нижнего уровня).
API высших уровней обеспечивают обертками для отправки составных сообщений целиком.

Кроме того:


Можно посылать сообщения нулевой длины. Например, для отправки сигнала из одного потока в другой. Или - как мы делали в приложении 16607836 :
Код: pascal
1.
    zmq_send(fSocketSender, PChar(fDummy)^, 0, 0); // Отправка сигнала сборщику результата



ZeroMQ гарантирует доставку либо всех частей сообщения, либо ни одной части.

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

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

После использования принятого сообщения, его нужно закрыть: zmq_msg_close() .
При отправке сообщения этот метод вызывать не нужно. То есть, чисткой занимается получатель.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756947
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ах, да.

Есть удивительный метод zmq_msg_init_data() . Этот метод позволяет инициализировать сообщение без копирования данных пользователя. Для скорости, с целью экономии памяти и т.д.
Так вот, пока не используем его, если не хотим проблем (например: для использования метода нужно определить деаллокирующую данные функцию, да не простую, а thread-safe).

В документации сказано то же самое.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756959
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Работа с несколькими сокетами ZMQ.

Во всех примерах, что мы рассмотрели ранее, схема работы была примерно одинаковой - в цикле повторялись одни те же действия:
Код: plaintext
1.
2.
3.
4.
5.
    

    Ожидание сообщение из сокета
    Обработка сообщения.
    "Смыть, повторить."

Что делать, если нам нужно читать сообщения из нескольких конечных точек одновременно?
Самое простое - коннектить один сокет ко всем нужным конечным точкам и позволть, чтобы ZeroMQ втягивала нужные нам данные. Это допустимо, если все конечные точки настроены для работы по одному и тому же шаблоны, но может быть неправильным, если, например, коннектить PULL - сокеты к конечной точке типа PUB.

Так вот, чтобы иметь возможность читать из множества сокетов сразу, следует использовать метод zmq_poll() .
Более того, рекомендуется обернуть zmq_poll() в удобный для ситуации собственный фреймворк.

Создадим простое приложение, которое покажет, как читать данные из неблокирующего сокета.
Будем читать данные из двух сокетов с использованием неблокирующего чтения. Приложение будет работать и как подписчик, и как работник в системе обработки параллельных задач.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38756975
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вот исходник ридера, читающего данные и от нашей старой задачи "Ventilator" и от сервера метеостанции:


Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
program MS_Reader;

{$APPTYPE CONSOLE}

uses
  SysUtils, ZMQ;

var

  fConext: Pointer;
  fSocketReceiver: Pointer;
  fSocketSubscriber: Pointer;
  fMsgBuff: array[0..255] of Char;
  fSize: Integer;
begin

// Подключение к задаче "ventilator"
  fConext := zmq_ctx_new();
  fSocketReceiver := zmq_socket(fConext, ZMQ_PULL);
  zmq_connect(fSocketReceiver, 'tcp://localhost:5557');

// Подклчение к серверу метеостанции
  fSocketSubscriber := zmq_socket(fConext, ZMQ_SUB);
  zmq_connect(fSocketSubscriber, 'tcp://localhost:5556');
  zmq_setsockopt(fSocketSubscriber, ZMQ_SUBSCRIBE, PChar('10001 '), 6);

// Процесс обработки для обоих сокетов
// Считаем, что трафик от  задачи "ventilator" для нас важнее
  while true do begin
    while true do begin
      fSize := zmq_recv(fSocketReceiver, fMsgBuff, SizeOf(fMsgBuff), ZMQ_DONTWAIT);
      if fSize >= 0 then
        // Выполнение задания
      else
        break;
    end;
    while true do begin
      fSize := zmq_recv(fSocketSubscriber, fMsgBuff, SizeOf(fMsgBuff), ZMQ_DONTWAIT);
      if fSize >= 0 then
    // Обработка данных о погоде
      else
        break;
    end;
    // Активности нет, спим 1 ms
    Sleep(1);
  end;
  zmq_close(fSocketReceiver);
  zmq_close(fSocketSubscriber);
  zmq_ctx_destroy(fConext);

  Readln;

end.




Недостатком такого способа является некоторая дополнительная задержка на обработке первого сообщения (sleep(1) в конце цикла, когда нет никаких сообщений в процессе). Это было бы проблемой в приложениях, где задержка в миллисекунды имеет жизненно важное значение. Кроме того, необходимо быть уверенным, что sleep () или любая другая функция задержки не жрет процессорное время.

А теперь рассмотрим такое же приложение, но уже с использованием zmq_poll() .
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38757012
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Теперь: "правильный" мульиридер:



Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
program MS_Reader_Poll;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ZMQ;

var

  fConext: Pointer;
  fSocketReceiver: Pointer;
  fSocketSubscriber: Pointer;
  fMsgBuff: array[0..255] of Char;
  fSize: Integer;
  fItems: array[0..1] of pollitem_t;
begin

// Подключение к задаче "ventilator"
  fConext := zmq_ctx_new();
  fSocketReceiver := zmq_socket(fConext, ZMQ_PULL);
  zmq_connect(fSocketReceiver, 'tcp://localhost:5557');

// Подклчение к серверу метеостанции
  fSocketSubscriber := zmq_socket(fConext, ZMQ_SUB);
  zmq_connect(fSocketSubscriber, 'tcp://localhost:5556');
  zmq_setsockopt(fSocketSubscriber, ZMQ_SUBSCRIBE, PChar('Temperature '), 12);

  fItems[0].socket := fSocketReceiver;
  fItems[0].fd := 0;
  fItems[0].events := ZMQ_POLLIN;
  fItems[0].revents := 0;

  fItems[1].socket := fSocketSubscriber;
  fItems[1].fd := 0;
  fItems[1].events := ZMQ_POLLIN;
  fItems[1].revents := 0;
// Процесс обработки для обоих сокетов
  while true do begin

    zmq_poll(fItems[0], Length(fItems), -1);
    if ((fItems[0].revents and ZMQ_POLLIN) <> 0) then begin
      fSize := zmq_recv(fSocketReceiver, fMsgBuff, SizeOf(fMsgBuff), 0);
      if fSize >= 0 then
        // Выполнение задания
      else
        break;
      if ((fItems[1].revents and ZMQ_POLLIN) <> 0) then begin
        fSize := zmq_recv(fSocketSubscriber, fMsgBuff, SizeOf(fMsgBuff), 0);
        if fSize >= 0 then
    // Обработка данных о погоде
        else
          break;
      end;
    end;
  end;

  zmq_close(fSocketReceiver);
  zmq_close(fSocketSubscriber);
  zmq_ctx_destroy(fConext);

  Readln;

end.



Видим, что появилась интересная структура:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
type
  pollitem_t = record
    socket: Pointer; // Сокет ZeroMQ
    fd: Integer; // Стандартный сокет, представленный дескриптором файла
    events: Word; // Маска событий 
    revents: Word; // Маска событий 
  end;



Массив таких структур

Код: pascal
1.
  fItems: array[0..1] of pollitem_t;


инициализируется следующим образом:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
  fItems[0].socket := fSocketReceiver;
  fItems[0].fd := 0;
  fItems[0].events := ZMQ_POLLIN;
  fItems[0].revents := 0;

  fItems[1].socket := fSocketSubscriber;
  fItems[1].fd := 0;
  fItems[1].events := ZMQ_POLLIN;
  fItems[1].revents := 0;


В каждом элементе указан свой сокет и задана маска ожидаемых событий (ZMQ_POLLIN).

Далее вызывается метод

zmq_poll(fItems[0], Length(fItems), -1);
Последний параметр - время ожидания события в ms. Если -1 - то ожидание будет выполняться бесконечно долго.

Событие, произошедшее при выполнении zmq_poll(), заносится в битовую маску revents.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38757015
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД...

Видим, что появилась интересная структура:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
type
  pollitem_t = record
    socket: Pointer; // Сокет ZeroMQ
    fd: Integer; // Стандартный сокет, представленный дескриптором файла
    events: Word; // Маска событий 
    revents: Word; // Маска событий 
  end;



...

Честно говоря, не въехал, как использовать поле fd. Что "за стандартный сокет"?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758185
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Составные сообщения.

Процесс доставки сообщения атомарен .

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

Так вот, как было сказано ранее ( 16618997 ), сообщение может быть составным (состоять из нескольких кадров).
В реальных приложениях это полезно - например, для реализации собственных структур и алгоритмов (например, для простых способов сериализации объектов).

Так вот, насчет составных сообщений. Для каждой части (кадра) составного сообщения нужен свой экземпляр структуры zmq_msg_t.
Необязательно, чтобы все части (zmq_msg_t) были объявлены одновременно, можно инициализировать очередной кадр и отправлять его с признаком ( "будет ещё!" ), используя единственный zmq_msg_t. Однако, все кадры сообщения будут накапливаться в памяти передающей стороны до момента отправки.

Например, отправляем составное сообщение из 5 кадров. Можно объявить, инициализировать, заполнить данными, отправить и очистить пять разных zmq_msg_t. А можно работать с одним zmq_msg_t для всех пяти кадров последовательно.

Пример (сообщение из 5 кадров), 5 экземпляров zmq_msg_t:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
var
...
  fMsg_1: zmq_msg_t;
  fMsg_2: zmq_msg_t;
  fMsg_3: zmq_msg_t;
  fMsg_4: zmq_msg_t;
  fMsg_5: zmq_msg_t;


begin
...
  zmq_msg_send(fMsg_1, fSocketSender, ZMQ_SNDMORE); // Кадры с признаком "будет ещё!"
  zmq_msg_send(fMsg_2, fSocketSender, ZMQ_SNDMORE);
  zmq_msg_send(fMsg_3, fSocketSender, ZMQ_SNDMORE);
  zmq_msg_send(fMsg_4, fSocketSender, ZMQ_SNDMORE);
  zmq_msg_send(fMsg_5, fSocketSender, 0); // Завершающий кадр



Пример приема сообщения из 5 кадров, 1 экземпляр zmq_msg_t:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
var
...
  fMsg: zmq_msg_t;
...
begin
...
  while True do begin
    zmq_msg_init(fMsg);
    zmq_msg_recv(fMsg, fSocketReceiver, 0);
// Обработка кадра сообщения
    zmq_msg_close(fMsg);
    if not zmq_msg_more(fMsg) then
      break; // Это был последний кадр
  end;
...



Вещи, которые [b]важны при работе с составными сообщениями:[/b]

- При отправки составного сообщения и первый, и все последующие кадры начнут отправляться "физически" только тогда, когда был отправлен финальный кадр.
- Если на приме используется zmq_poll(), то следует иметь в виду, что в момент, когда вы получили первую часть сообщения, все остальные части были уже получены.
- Вы физически получаете либо все части сообщения, либо ни одного.
- Каждая часть сообщения есть отдельный элемент zmq_msg.
- Вы получите всегда ВСЕ части сообщения, вне зависимости от того, проверяете ли вы свойство "ещё!".
- На передающей стороне ZeroMQ последовательно помещает кадры сообщения в очередь, до кадра с признаком "последний" (вернее, без признака "будет ещё!" ), а только потом выполняется пересылка всех кадров сообщения.
- не существует иного способа отказаться от частично отправленного сообщения, кроме как закрыть сокет: zmq_close() .
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758187
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
чччД, читаю, очень интересно...Но всё равно возникает вопрос из заголовка - а для чего ?
Всё равно ведь ниже ZeroMQ лежат виндовые сокеты, а их не перепрыгнуть?
Можно примеры каких-нить настолько высоконагруженных приложений, в которых без стероидов ну никак ?...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758199
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorчччД, читаю, очень интересно...Но всё равно возникает вопрос из заголовка - а для чего ?
Всё равно ведь ниже ZeroMQ лежат виндовые сокеты, а их не перепрыгнуть?
Можно примеры каких-нить настолько высоконагруженных приложений, в которых без стероидов ну никак ?...
У меня там была рекламная страничка, но Рустам её грохнул.
Но я её помню наизусть:


Особенности ZeroMQ:

Асинхронный ввод-вывод, в фоновых потоках. Последние взаимодействуют с потоками приложения, используя неблокирующие структуры данных; таким образом, многопоточные приложения ZeroMQ не требуют блокировок, семафоров и либо других методов перевода в состояние ожидания.

Сетевые компоненты могут подключаться и отключаться динамически, при этом ZeroMQ будет реконнектиться автоматически. Имеется в виду, что вы можете запускать компоненты в любой последовательности. Можно создать "сервис - ориентированную архитектуру" (SOAs), в которой сервисы могут подключаться и отключаться к сети в любой момент.

Сообщения могут автоматически ставиться в очередь, по необходимости. Это делается логично, с проталкиванием сообщения как можно ближе к приемнику перед их постановкой в очередь.

ZeroMQ умеет бороться с переполнением очередей (называется "high water mark"). Когда очередь полна, ZeroMQ автоматически блокирует отправителя, или отбрасывает сообщения в зависимости от режима обмена сообщениями (см. ранее о "шаблонах").

Она позволяет вашему приложению общаться в через произвольный транспорт: TCP, multicast, in-process, inter-process. Вам не нужно менять код для использования другого транспорта.

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

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

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

Она доставляет сообщения полностью в том виде, в котором оно было отправлено, используя простую упаковку. Если вы отправили сообщение длиной 10к, вы и получите сообщение длиной 10к.

Она не налагает ограничений на формат сообщений. Сообщения представляют собой блобы длиной от нуля до нескольких гигабайт. Когда вам нужно специальное представление данных, вы выберете другой продукт, типа msgpack, Google's protocol buffers, и т.д.

Она разумно обрабатывает сетевые ошибки, выполняя повторные запросы когда это имеет смысл.

Она уменьшает затраты на железо.

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


Ах да, твой вопрос... имхо, проще и легковеснее, чем ZMQ, я ничего не видел.
Прочие ориентированные на сообщения системы реализуются в виде службы, а ZMQ - просто библиотека.

Типа, FireBird Embedded: для больший задач лучше настоящий сервер, а для приложения "на флешке" - FB Embedded - самое оно. :)

...хотя, есть примеры "больших" приложений: Веб-сайту Second Life удалось получить 13,4 микросекунды непрерывного времени ожидания и обслуживать до 4 100 000 сообщений в секунду.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758202
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
чччД, а то, что асинхронные сокеты изобрёл Мелкософт, никого уже не смущает ?
4 ляма сообщений в секунду - это железо умалчивается ?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758205
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
не наезжаю, ни боже мой.
Я просто пытаюсь понять, чем эта либа отличается от другой прослойки типа RealThinClient - тм тоже изначально синхронные сокеты
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758208
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorчччД, читаю, очень интересно...Но всё равно возникает вопрос из заголовка - а для чего ?
Всё равно ведь ниже ZeroMQ лежат виндовые сокеты, а их не перепрыгнуть?
...
Кроме того, ZMQ использует не только TCP, но и, например, inproc протокол для связи между потоками (а в UNIX-системах - между процессами).
...
У меня лично от Windows сокетов послевкусие. Особенно после асинхронных. "Ах, что-то пришло в буфер, посмотрим... Заголовок уже загружен? Нет еще, ну ладно... Опять сообщение - ура, заголовок загружен, и даже часть тела... аккуратно анализируем заголовок, и снова цикл ожидания загрузки остатка сообщения..."
А сообщения ZMQ - атомарны.
Конечно, атомарность сообщений ZMQ - не всегда благо. Например, при получении данных большого объема: сколько тебе послали, столько ты и получишь, все сразу. А с винсокетами ты можешь послать всех, кто (например) неправильно отправил заголовок, еще "в процессе", на принимая сообщения целиком.
По этой причине, если выставить ZMQ сокет "в интернет", его сразу могут завалить всяким мусором. Ну, если не принимать никаких мер. Но в локальной сети ведь злоумышленников быть не должно? :)

Т.е., это - не "серебряная пуля". А такой ничего себе ножичек.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758210
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorчччД, а то, что асинхронные сокеты изобрёл Мелкософт, никого уже не смущает ?
...
А они как раз и используются в ZMQ, вовсю.

defecator...
4 ляма сообщений в секунду - это железо умалчивается ?
Там используется распределенная архитектура, там МНОГО железа. Для организации его в сеть там как раз используется ZMQ.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758212
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecator...
Я просто пытаюсь понять, чем эта либа отличается от другой прослойки типа RealThinClient - тм тоже изначально синхронные сокеты
А в ZMQ - асинхронные...
...а про RealThinClient я вообще ничего не знаю.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758227
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Проблема динамического обнаружения

Краткое содержаниеВопрос сродни вот этому: Как получить список доступных MS SQL серверов?




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

Особенно трудно в случае, когда разные элементы подключаются и отключаются, из разных узлов сети. Поэтому это называется "проблемой динамического обнаружения".

Эта проблема решается везде по-разному: используется служба DNS, или широковещательные сообщения (UDP) и т.п.

Есть несколько простых решений проблемы динамического обнаружения. Можно жестко закодировать адрес(ip + порт) (конечную точку). Вообще никаких проблем - и никакой гибкости. Впрочем, некоторой гибкости для tcp транспорта можно добавить с помощью службы DNS.
Можно конечную току задавать с помощью конфигурационных файлов (и т.д.). Главное - не забывать их вовремя обновлять .
Можно использовать специальный брокер адресации, который передаст вам нужные данные. Только вот адрес этого брокера должен быть известен...
Можно построить средство, исследующее окружение, сканирующее известные диапазоны адресов и порты. Или рассылающее (получающее) udp - пакеты об адресной информации.
...
...
...
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Можно при запуске нового элемента сети вручную задавать конечную точку ("адрес сервера"). То есть, при подключении вы просто знаете конфигурацию сети и сообщаете нужные данные новому элементу сети. Так часто делают, но в реальности это приводит к громоздким и хрупким топологиям.
Вспомним пример "Издатель-Подписчик": 16583825 .
Пусть есть один издатель и тысяча подписчиков. Каждый подписчик коннектится к одному издателю. Можно руками конфигурировать подключение каждого подписчика, это просто. Сделали, работает. Подписчик - динамичный (может подключаться из любого места, и включаться когда угодно), а издатель - статичный. Сделали, работает.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758243
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Неожиданно набежали янычары. А вот теперь представим, что появился еще одни издатель. Нужно еще раз настроить каждого подписчика. Всех, 1000 шт. И каждый раз появление нового издателя повлечет за собой увеличение стоимости настройки.

Нужно динамическое обнаружение.

Добавим брокер сообщений. ZMQ не поставляется с брокерами, но позволяет его легко построить.
Теперь Издатель-Подписчик схема будет выглядеть вот так:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758247
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Таким образом, со статичным борокером сообщений задача решена.

Можно даже запустить общий брокер для всех типов сообщений, и строить сетевую архитектуру вокруг него.

Т.е., топология "Звезда" работает. Какое-то время.
...пока не возникнут вопросы вроде пропускной способности и расширения/усложнения логики брокера.

АДЪ неминуем.

ZMQ придет, порядок наведет!
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758260
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Куда лучше реализовать брокер не в качестве транспорта сообщений, а в качестве поставщика адресной информации.

Для этого следует использовать сокеты ZMQ типа XPUB и XSUB, так как с ними ZMQ не пересылает сообщения от издателя к подписчику напрямую.
Сокеты XSUB и XPUB - точно такие же, как и сокеты типа SUB и PUB, за исключением того, что они обрабатывают подписки в форме специальных сообщений. А при подключении SUB и PUB сокетов к XPUB и XSUB сокетам первые связываются друг с другом уже по известным адресам.


То есть, основной поток данных идет, минуя брокер:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758261
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД...
То есть, основной поток данных идет, минуя брокер:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758262
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А ты еще спрашиваешь
defecator...а для чего ?
...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758398
vavan
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДс винсокетами ты можешь послать всех, кто (например) неправильно отправил заголовок, еще "в процессе", на принимая сообщения целиком"послать" ты можешь только закрыв сокет. а так пока тебе в него будут вваливать придется читать
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758427
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vavanчччДс винсокетами ты можешь послать всех, кто (например) неправильно отправил заголовок, еще "в процессе", на принимая сообщения целиком"послать" ты можешь только закрыв сокет. а так пока тебе в него будут вваливать придется читать
Вот именно - c WinSockets ты можешь закрыть сокет в процессе чтения, не дочитав...

А в ZMQ - ты начинаешь читать, когда "все уже здеcь".
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758443
vavan
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДв ZMQ - ты начинаешь читать, когда "все уже здеcь"при условии что оно поместилось
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38758615
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vavanчччДв ZMQ - ты начинаешь читать, когда "все уже здеcь"при условии что оно поместилось
При всех условиях.
Если ты начал читать, значит - сообщение здесь. Они либо приходит, либо нет.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759698
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Шаблон "Разделяемая очередь".

Потихоньку перейдем к сокетам DEALER и ROUTER .
...

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

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


Например, коннектим клинтский сокет к трем конечным точкам ("сервисам") :A, B, и C.
Вспомним этот ( 16562041 ) пример, поясняющий схему "Запрос - Ответ" (REQ - REP). Чтобы клиент ( 16562066 ) подключился к трем сервисам (например, по tpc на localhost, на трех разных портах), нужно

Код: pascal
1.
2.
3.
  zmq_connect(requester, 'tcp://localhost:5555');
  zmq_connect(requester, 'tcp://localhost:5556');
  zmq_connect(requester, 'tcp://localhost:5557');


Клиент последовательно выполняет запросы R1, R2, R3, R4. В результате запросы R1 и R4 отправляются к сервису A, R2 - к B, R3 - к C.
Циклически распределяя запросы (наш любимый roud-robin).

Такая конструкция позволяет добавлять без проблем добавлять сколько угодно клиентов.
Как показано выше, с помощью zmq_connect() можно добавлять сколько угодно сервисов. Беда в том, что клиент должен знать, где находится новый сервис. Если клиентов - 100, и в течении скажем, суток, добавляется всего три новых сервиса, то нужно в итоге триста раз переконфигурировать всех клиентов.
Что грустно.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759713
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В идеале, мы должны быть в состоянии добавлять и удалять сервисы или клиентов в любое время, не касаясь любой другой части топологии.
...
авторДля реализации коннектов "много:много" есть два пути:
- каждый клиентский сокет может коннектиться ко множеству сервисных конечных точек. То есть, один клиентский сокет типа (REQ) коннектится к сервисным сокетам с известными адресами. После этого запросы будут распределяться между сервисами.

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

Напишем крошечный брокер запросов, реализующий желаемую гибкость топологии.
Брокер соединит две конечные точки - фронтенд (сокет стороны клиентов) и бэкэнд (сокет стороны сервисов).
Затем брокер, используя zmq_poll(), будет отслеживать активность этих сокетов, и перебрасывать сообщения от одного сокета к другому. При чем, в ручном управлении очередности использования сервисов нет необходимости, т.к. ZeroMQ делает это автоматически для каждого сокета.
Когда мы построили приложение ( 16562041 ) по схеме "Запрос - Ответ (Req_Rep)", система получилась с синхронным диалогом обмена. Клиент шлет запрос. Сервис читает запрос и шлет ответ. Клиент читает ответ. Если клиент или сервис будут выполнять что-то другое (например, клиент пошлет два запроса подряд без ожидания ответа), система просто перестанет работать.

...

Конечно, раз теперь мы умеем пользоваться zmq_poll() ( 16619247 ), сделаем можно сделать брокер неблокирующим.
Но мы пойдем другим путем, и не станем использовать сокеты типа REP и REQ.

Так вот, есть схема использования пар сокетов, которые реализуют схему "Посредник - Маршрутизатор" , режимы сокетов соответственно называются DEALER и ROUTER . Они позволяют получить неблокирующий режим для схемы "Запрос - Ответ".
В нашей схеме "Запрос - Ответ" сокет REQ будет "говорить" с сокетом ROUTER, а сокет DEALER - с сокетом REP.
Сокеты DEALER ROUTER будет как раз размещаться на нашем брокере, передачу сообщений между ними мы обеспечим с помощью кода. Будем извлекать сообщение из одного и передавать его другому сокету.

Наш брокер схемы "Запрос - Ответ" привязывается к двум конечным точкам: одна для коннектов к ней клиентов(фронтэнд сокет), вторая - для коннектов сервисов(бэкэнд сокет).

Задача та же, что и в первом примере: возведение в квадрат целых чисел. Клиент отсылает запросы, сервис (сервер) - выполняет.
Запрос - беззнаковое x32 целое число (UInt32) , ответ - квадрат беззнаковое x64 целого (UInt64).

Вот что у нас было:
Клиент
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
program BrRR_Client;

{$APPTYPE CONSOLE}
// Клиент
// Коннектится сокетом REQ к tcp://localhost:5559
// Шлет целое число сервису (серверу), обратно получает квадрат числа

uses
  SysUtils, ZMQ;
const
  c_ReqCnt = 100;
var
  fContext: Pointer;
  fSocketRequester: Pointer;
  fSrcVal: Cardinal;
  fResultVal: UInt64;
  i: integer;
begin

  fContext := zmq_ctx_new(); // Инициализация
  fSocketRequester := zmq_socket(fContext, ZMQ_REQ);
  zmq_connect(fSocketRequester, 'tcp://localhost:5559'); // Коннект к сервису
  Randomize();
  for i := 0 to Pred(c_ReqCnt) do begin
    fSrcVal := Cardinal(Random(-1)); // Генерация случайного целого
    zmq_send(fSocketRequester, fSrcVal, SizeOf(fSrcVal), 0); // Запрос
    zmq_recv(fSocketRequester, fResultVal, SizeOf(fResultVal), 0); // Ответ
    Writeln('Iter: ', i, ' src=', fSrcVal, ' result=', fResultVal);
  end;
  zmq_close(fSocketRequester);
  zmq_ctx_destroy(fContext);
  Readln;

end.




Сервис (сервер)
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
program BrRR_Service;

{$APPTYPE CONSOLE}
// Сервис (сервер)
// Находится в известной клиентам конечной точке, связывает сокет REP с tcp:*:5560,
// Получает целое число, возводит в квадрат и отправляет обратно результат

uses
  SysUtils, ZMQ;
var
  fContext: Pointer;
  fSocketResponder: Pointer;
  fSrcVal: Cardinal;
  fResultVal: UInt64;
  i: integer;
begin

  fContext := zmq_ctx_new(); // Инициализация
  fSocketResponder := zmq_socket(fContext, ZMQ_REP);
  zmq_bind(fSocketResponder, 'tcp://*:5559'); // Привязка к конечной точке
  Writeln('Starting service...');
  i := 0;
  while True do begin
    zmq_recv(fSocketResponder, fSrcVal, SizeOf(fSrcVal), 0); // Запрос
    fResultVal := UInt64(fSrcVal) * UInt64(fSrcVal); // "Полезная работа"
    zmq_send(fSocketResponder, fResultVal, SizeOf(fResultVal), 0); // Ответ
    Writeln('Iter: ', i, ' src=', fSrcVal, ' result=', fResultVal);
    Inc(i);
  end;
  zmq_close(fSocketResponder);
  zmq_ctx_destroy(fContext);
  Readln;

end.



Пока ничего нового не видим: продублирована функциональность схемы "Вопрос - Ответ". Клиент - коннектится к конечной точке - к сервису, сервис находится в этой конечной точке и ждет запросов клиентов. "Конечная точка" - это известный адрес (Например, "tcp://localhost:5560").
То есть, реализована вот эта схема: 16630265 .

Будем улучшать мир. Воткнем между ними брокер.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759717
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Брокер.

Что-то новенькое: сокеты DIALER и ROUTER.

Брокер
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
program BrRR_Broker;

{$APPTYPE CONSOLE}
// Брокер
// Находится в известной клиентам и сервисам конечной точке,
// Находится в известной клиентам конечной точке,
// связывает сокет ZMQ_ROUTER с tcp:*:5559,
// связывает сокет ZMQ_DEALER с tcp:*:5560,
// Перекидывает сообщения между сокетами от FrontEnd к BackEnd
// и обратно

uses
  SysUtils, ZMQ, Math;
var
  fContext: Pointer;
  fSocketFrontEnd: Pointer;
  fSocketBackEnd: Pointer;
  fZMQPoll: array[0..1] of pollitem_t;
  fMsg: zmq_msg_t;
  fDoMore: Boolean;
begin

  fContext := zmq_ctx_new(); // Инициализация
  fSocketFrontEnd := zmq_socket(fContext, ZMQ_ROUTER);
  fSocketBackEnd := zmq_socket(fContext, ZMQ_DEALER);
  zmq_bind(fSocketFrontEnd, 'tcp://*:5559');
    // Конечная точка для клиентов
  zmq_bind(fSocketBackEnd, 'tcp://*:5560');
    // Кончная точка для сервисов
  fZMQPoll[0].socket := fSocketFrontEnd;
    // Инициализация пула сокетов
  fZMQPoll[0].fd := 0;
  fZMQPoll[0].events := ZMQ_POLLIN;
  fZMQPoll[0].revents := 0;
  fZMQPoll[1].socket := fSocketBackEnd;
  fZMQPoll[1].fd := 0;
  fZMQPoll[1].events := ZMQ_POLLIN;
  fZMQPoll[1].revents := 0;
  while true do begin
    zmq_poll(fZMQPoll[0], Length(fZMQPoll), -1);
      // Проверка состояния сокетов из пула
    if (fZMQPoll[0].revents and ZMQ_POLLIN) <> 0 then
      while True do
      begin // Трансляция сообщний от клиента к сервису
      // Обработка всх частей сообщения
        zmq_msg_init(fMsg);
        zmq_msg_recv(fMsg, fSocketFrontEnd, 0);
        fDoMore := zmq_msg_more(fMSG) <> 0;
        zmq_msg_send(fMsg, fSocketBackEnd, IfThen(fDoMore, ZMQ_SNDMORE, 0));
        zmq_msg_close(fMsg);
        if not fDoMore then
          Break; // Это была последняя часть сообщения
      end;
    if (fZMQPoll[1].revents and ZMQ_POLLIN) <> 0 then
      while True do
        // Трансляция сообщний от сервиса к клиенту
      begin // Обработка всх частей сообщения
        zmq_msg_init(fMsg);
        zmq_msg_recv(fMsg, fSocketBackEnd, 0);
        fDoMore := zmq_msg_more(fMSG) <> 0;
        zmq_msg_send(fMsg, fSocketFrontEnd, IfThen(fDoMore, ZMQ_SNDMORE, 0));
        zmq_msg_close(fMsg);
        if not fDoMore then
          Break; // Это была последняя часть сообщения
      end;
  end;
  zmq_close(fSocketFrontEnd);
  zmq_close(fSocketBackEnd);
  zmq_ctx_destroy(fContext);
  Readln;

end.




Чтобы все заработало, нужно у сервиса из 16630300 заменить биндинг на коннект:

Код: pascal
1.
2.
//  zmq_bind(fSocketResponder, 'tcp://*:5559'); // Привязка к конечной точке
  zmq_connect (fSocketResponder, 'tcp://localhost:5560'); // Коннект к конкретной точке



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

Чтение по частям реализовано "для общности". Чтобы работало с любыми сообщениями.
Интересно, что наши крошечные сообщения
Код: pascal
1.
2.
3.
     zmq_send(fSocketRequester, fSrcVal, SizeOf(fSrcVal), 0); // Запрос
...
     zmq_recv(fSocketRequester, fResultVal, SizeOf(fResultVal), 0); // Ответ

и
Код: pascal
1.
2.
3.
    zmq_recv(fSocketResponder, fSrcVal, SizeOf(fSrcVal), 0); // Запрос
...
    zmq_send(fSocketResponder, fResultVal, SizeOf(fResultVal), 0); // Ответ


порождают на стороне брокера каскад из нескольких составных сообщений из структур zmq_msg_t, что хорошо видно в отладчике.


Теперь можно запускать сколько хочешь сервисов и сколько хочешь клиентов - все они будут общаться через брокер.

Такой брокер для схемы "Запрос- Ответ" существенно облегчает обслуживание сети , так как клиентам нет нужды знать, где размещены сервисы, а сервисам нет нужны знать, где размещены клиенты. Единственной статической точкой является брокер.
Жизнь налаживается. :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759718
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД...
Теперь можно запускать сколько хочешь сервисов и сколько хочешь клиентов - все они будут общаться через брокер.

Такой брокер для схемы "Запрос- Ответ" существенно облегчает обслуживание сети , так как клиентам нет нужды знать, где размещены сервисы, а сервисам нет нужны знать, где размещены клиенты. Единственной статической точкой является брокер.
...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759719
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Приведенный выше код трансляции сообщений представляется очень полезным для многих случаев для схемы "Запрос - Ответ".

Так вот, ZeroMQ есть даже специальный метод, реализующий все, что мы нако'дли в брокере.
См. ниже: "Та-да-мммм!"


Код: pascal
1.
function zmq_proxy( frontend, backend, capture: Pointer ): Integer; 



frontend - сокет фронтэнд
backend - сокет бэкэнд
capture - сокет для перехвата сообщений (nil, если не используется)

Технически нет никакой разницы между фронтэнд и бэкэнд сокетами.


Та-да-мммм!
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
program BrRR_Broker;

{$APPTYPE CONSOLE}
// Брокер
// Находится в известной клиентам и сервисам конечной точке,
// Находится в известной клиентам конечной точке,
// связывает сокет ZMQ_ROUTER с tcp:*:5559,
// связывает сокет ZMQ_DEALER с tcp:*:5560,
// Перекидывает сообщения между сокетами от FrontEnd к BackEnd
// и обратно

uses
  SysUtils, ZMQ, Math;
var
  fContext: Pointer;
  fSocketFrontEnd: Pointer;
  fSocketBackEnd: Pointer;
begin

  fContext := zmq_ctx_new(); // Инициализация
  fSocketFrontEnd := zmq_socket(fContext, ZMQ_ROUTER);
  fSocketBackEnd := zmq_socket(fContext, ZMQ_DEALER);
  zmq_bind(fSocketFrontEnd, 'tcp://*:5559'); // Конечная точка для клиентов
  zmq_bind(fSocketBackEnd, 'tcp://*:5560'); // Кончная точка для сервисов

  zmq_proxy (fSocketFrontEnd, fSocketBackEnd, nil); // Старт прокси!

  zmq_close(fSocketFrontEnd);
  zmq_close(fSocketBackEnd);
  zmq_ctx_destroy(fContext);
  Readln;

end.




Дальше будет еще интереснее.

...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759721
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Важно.
Видов сокетов, которые практически можно использовать в брокере:

Код: plaintext
1.
2.
ROUTER - DEALER
XSUB   - XPUB
PULL   - PUSH.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759954
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Мосты.

Вопрос: "как передать сообщения из одной подсети в другую?"
Или даже - из сети с протоколом tcp в сеть pgm .

Вариант решения - с помощью моста .

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

В качестве примера напишем крошечный прокси, который, находясь между издателем и множеством подписчиков, соединяет две разные сети.
Вернемся к примеру с метеостанцией. 16583825
Предположим, что сервер-издатель (которые измеряет температуру, давление и проч) работает во внутренней сети, часть подписчиков - тоже во внутренней. А еще часть - во внешней.
Создаем прокси, в которой фронтэнд сокет (SUB) будет общаться с внутренней сетью, а бэкэнд сокет (PUB) - с внешней.
Прокси будет подписываться фронтэнд-сокетом на события сервиса погоды и пере-публиковывать их на бэкэнд-сокете.
Прокси для метео: "мост в интернет"
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
program BrRR_BrokerMeteoInterNet;

{$APPTYPE CONSOLE}
// Брокер для проекта "Метео"
// Реализует мост между интрасаетью метеосервера
// и внешней сетью. Размещается во внутренней сети,
// для брокера открыт "наружу" tcp порт 8100.
// "Внешние" клиенты коннектятся к известной конечной точке tcp://10.1.1.0:8100
// Коннектит сокет ZMQ_XSUB с известной конечной точкой метеосервера tcp://192.168.55.210:5556
// Связывает сокет ZMQ_XPUB с tcp://10.1.1.0:8100,
// Перекидывает сообщения подписки между сокетами от FrontEnd к BackEnd
// и обратно

uses
  SysUtils, ZMQ;
var
  fContext: Pointer;
  fSocketFrontEnd: Pointer;
  fSocketBackEnd: Pointer;
begin

  fContext := zmq_ctx_new(); // Инициализация
  fSocketFrontEnd := zmq_socket(fContext, ZMQ_XSUB);
  fSocketBackEnd := zmq_socket(fContext, ZMQ_XPUB);
  zmq_connect(fSocketFrontEnd, 'tcp://192.168.55.210:5556'); // Конечная точка метеосервиса
  zmq_bind(fSocketBackEnd, 'tcp://10.1.1.0:8100'); // Конечная точка для "внешних" подписчиков
//  zmq_bind(fSocketBackEnd, 'tcp://*:8100'); // Можно и так, главное - чтобы внешние клиенты знали адрес

  zmq_proxy (fSocketFrontEnd, fSocketBackEnd, nil); // Старт прокси

  zmq_close(fSocketFrontEnd);
  zmq_close(fSocketBackEnd);
  zmq_ctx_destroy(fContext);
  Readln;

end.




Очень похоже на код предыдущего прокси, но используется для трансляции сообщений из одной подсети в другую. Точно так же подобный прокси - мост можно использовать, например, для подключения подписчиков в мультикаст PGM сети с издателем в TCP.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759956
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД...прокси, который, находясь между издателем и множеством подписчиков, соединяет две разные сети...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759961
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38759965
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДКино нашел, на русском:

http://wiki.4intra.net/0MQ_—_Сокеты_на_стероидах_(Сергей_Гулько,_OSDN-UA-2012)

...У ZeroMQ пропускная способность выше, чем у TCP/IP, хотя ZeroMQ работает over TCP/IP...

Поискал - "каким же образом?"
Нашел кое-какое описание, вроде специальной упаковки сообщений ZMQ в пакеты tcp.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38760163
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Обработка ошибок. ETERM.

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

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

Надежность кода должна обеспечиваться использованием Assert(). Срабатывание Assert() вызывает либо завершение приложения, либо исключительную ситуацию.
Когда ZeroMQ обнаруживает внешнюю проблему, она возвращает соответствующий код завершения. В редких случаях, ZMQ показывает сообщения "молча", если нет очевидной стратегии, позволяющей восстановиться после ошибки.

В рассмотренных ранее примерах обработки ошибок не было.
В реальном коде необходимо анализировать код завершения вызова каждого метода ZMQ.


Существует несколько простых правил, ноги которых растут еще с соглашений POSIX:

- Методы, которые создают объекты, в случае ошибки возвращают nil;
- Методы, которые обрабатывают данные, могут вернуть число обработанных байт, или -1 в случае ошибки;
- Другие методы возвращают 0, когда все ОК, и, в случае ошибки - ненулевой код ошибке;
- Код ошибки доступен через errno (для ОС POSIX) или через метод zmq_errno();
- Описание ошибки (например, для логирования) можно получить с помощью метода zmq_strerror().

Пример:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
  fContext := zmq_ctx_new(); // Инициализация
  Assert(fContext <> nil);

  fSocketFrontEnd := zmq_socket(fContext, ZMQ_ROUTER);
  Assert(fSocketFrontEnd <> nil);

  fSocketBackEnd := zmq_socket(fContext, ZMQ_DEALER);
  Assert(fSocketBackEnd <> nil);

  fRC := zmq_bind(fSocketFrontEnd, 'tcp://*:5559'); // Конечная точка для клиентов
  Assert(fRC <> -1, 'Bind failed: tcp://*:5559');

  fRC := zmq_bind(fSocketBackEnd, 'tcp://*:5560'); // Кончная точка для сервисов
  Assert(fRC <> -1, 'Bind failed: tcp://*:5560');




Есть две исключительные ситуации, которые могут обрабатываться как некритические:
- когда ваш код принимает сообщение с ZMQ_NOWAIT ("асинхронно"), но данных не ожидается. ZMQ вернет -1 и установит код ошибка равным EAGAIN ;
- когда один поток вызывает zmq_ctx_destroy() , а второй все еще выполняет работу в блокирующем режиме, то вызов zmq_ctx_destroy() вызывает закрытие контекста и всех блокирующих вызовов с кодом завершения -1, а код ошибки устанавливается равным ETERM .

После того, как код будет отлажен и "вылизан", Asserts() могут быть отключены опциями компиляции. Однако, не стоит компилировать саму библиотеку ZMQ с отключенными assert() - запросто можно прозевать проблему в самом неожиданном месте.


Рассмотрим способы "чистого" завершения процессов. В качестве примера возьмем параллельный трубопровод из примера: 16607833 .

Мы возьмем пример параллельного газопровода из предыдущего раздела. Если мы в фоне стартовали множество рабочих процессов "Worker", то теперь мы хотим уничтожить их всех, когда все пакетное задание будет выполнено. Будем делать это, отправляя рабочим процессам сообщение "умри!". Этим будет заниматься процесс - сборщик "Synk" (так как он знает, когда завершается пакетное задание).

Каким же образом подключить сборщик "Synk" к рабочим процессам? Сокеты PUSH/PULL допускают передачу только в одну сторону. Можно использовать сокеты другого типа, или смешать несколько потоков. Попробуем следующее: используем схему "Издатель-Подписчик" (pub-sub) для отправки рабочим процессам сообщения "умри!".

Описание:
Сборщик ("Sink") создает сокет - издатель (PUB) в новой конечной точке.
Рабочие процессы ("Worker") связывают свои входные сокеты с этой конечной точкой.
Когда сборщик ("Sink") определяет, что задание выполнено, он посылает сигнал "умри!" в свой сокет - издатель (PUB).
Когда рабочий процесс ("Worker") обнаруживает сообщение "умри!", он завершается.

Сборщик почти не меняется, добавится еще один сокет и отправка сообщения "умри!":
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
var
...
  fSocketController: Pointer;
...
begin
...
  // Сокет для отправки сигнала "умри!"
  fSocketController := zmq_socket(fContext, ZMQ_PUB);
  zmq_bind(fSocketController, 'tcp://*:5559');
...
  // Отправка сигнала "умри!" рабочим процессам
  zmq_send(fSocketController, PChar('KILL')^, 4, 0);
...



Вот полный код сборщика:
Сборщик "Sink"
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
program PL_Sink;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, ZMQ;

const
  c_task_count = 100;

var
  fContext: Pointer;
  fSocketReceiver: Pointer;
  fSocketController: Pointer;
  fStart: Cardinal;
  fTaskCount: Integer;
  fDummy: string;
begin
 // Инициализация
  fContext := zmq_ctx_new();

  // Сокет для приема сигналов
  fSocketReceiver := zmq_socket(fContext, ZMQ_PULL);
  zmq_bind(fSocketReceiver, 'tcp://*:5558');

  // Сокет для отправки сигнала "умри!"
  fSocketController := zmq_socket(fContext, ZMQ_PUB);
  zmq_bind(fSocketController, 'tcp://*:5559');

  // Ожидание первого сигнала о старте пакета задач
  zmq_recv(fSocketReceiver, PChar(nil)^, 0, 0);

  // Фиксация времени начала выполнения пакета
  fStart := GetTickCount();
  // Ждем подтверждения выполнения от 100 рабочих процессов
  for fTaskCount := 0 to Pred(c_task_count) do begin
    zmq_recv(fSocketReceiver, PChar(nil)^, 0, 0);

    if (fTaskCount mod 10 = 0) then // "Статус - бар" :)
      Write(':')
    else
      Write('.')
  end;
  // Вычисление и показ времени выполнения пакета задач
  Writeln('Total elapsed time: ', GetTickCount() - fStart, 'ms');

  // Отправка сигнала "умри!" рабочим процессам
  zmq_send(fSocketController, PChar('KILL')^, 4, 0);

  zmq_close(fSocketReceiver);
  zmq_ctx_destroy(fContext);
  Readln(fDummy);

end.



Естественно, рабочий процесс "Worker" тоже придется переделать.
Теперь "Worker" управляется двумя сокетами: PULL - для получения задачи, и SUB - для получения команд управления.
Не забываем, что SUB сокет должен быть настроен:
Код: pascal
1.
  zmq_setsockopt(fSocketController, ZMQ_SUBSCRIBE, nil, 0);


Используем технику с zmq_poll (), которую уже применяли ранее.

Код рабочего процесса "Worker"
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
program PL_Worker;

{$APPTYPE CONSOLE}

uses
  SysUtils, ZMQ;

var
  fContext: Pointer;
  fSocketReceiver: Pointer;
  fSocketSender: Pointer;
  fSocketController: Pointer;
  fStrMsg: string;
  fLen: Integer;
  fMsg: zmq_msg_t;
  fDummy: string;
  fPollItems: array[0..1] of pollitem_t;
begin
  fContext := zmq_ctx_new();

 // Сокет для приема сообщений
  fSocketReceiver := zmq_socket(fContext, ZMQ_PULL);
  zmq_connect(fSocketReceiver, 'tcp://localhost:5557');

// Сокет для отправки сообщений
  fSocketSender := zmq_socket(fContext, ZMQ_PUSH);
  zmq_connect(fSocketSender, 'tcp://localhost:5558');

// Сокет для приема управляющх сигналов
  fSocketController := zmq_socket(fContext, ZMQ_SUB);
  zmq_connect(fSocketController, 'tcp://localhost:5559');
  zmq_setsockopt(fSocketController, ZMQ_SUBSCRIBE, nil, 0);

  fPollItems[0].socket := fSocketReceiver;
  fPollItems[0].fd := 0;
  fPollItems[0].events := ZMQ_POLLIN;
  fPollItems[0].revents := 0;
  fPollItems[1].socket := fSocketController;
  fPollItems[1].fd := 0;
  fPollItems[1].events := ZMQ_POLLIN;
  fPollItems[1].revents := 0;

// Бесконечный цикл выполнения заданий
  while True do begin
    zmq_poll(fPollItems[0], 2, -1);
    if (fPollItems[0].revents and ZMQ_POLLIN) <> 0 then begin
      zmq_msg_init(fMsg);
      fLen := zmq_msg_recv(fMsg, fSocketReceiver, 0); // Получение задания
      SetLength(fStrMsg, fLen div SizeOf(Char)); // Перевод буфера сообщения в строку
      Move(zmq_msg_data(fMsg)^, PChar(fStrMsg)^, fLen);
      Writeln(fStrMsg); // Отображение в консоли процесса "работы"
      Sleep(StrToInt(fStrMsg)); // "Полезная" работа...
      zmq_send(fSocketSender, PChar(fDummy)^, 0, 0);   // Отправка сигнала сборщику результата
    end;
   // Все, что получаем из контроллера, считаем командой 'KILL'
    if (fPollItems[1].revents and ZMQ_POLLIN) <> 0 then
      Break; // Выход из цикла обработки
  end;
  zmq_close(fSocketReceiver);
  zmq_close(fSocketSender);
  zmq_close(fSocketController);
  zmq_ctx_destroy(fContext);
//  Readln(fDummy);

end.




Теперь запускаем: процесс "Sink", один или несколько процессов "Worker", процесс "Ventilator". В консоли процесса "Ventilator" жмем Enter и наблюдаем примерно такую картину: 16607848
За исключением того, что процессы "Worker" по завершению пакетного задания будут завершены: по завершению работы процесс "Sink" посылает сигнал "умри!" всем подписчикам (процессам "Worker").
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38760164
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Топология только что описанной сетевой задачи:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38761368
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Обработка Crtl+C для консольных приложений Windows.
Казалось бы, нажали и хрясь - приложение убито. Если бы.

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

В общем, "все не так однозначно".

Все, что написано в документации - касается всяческих Linux.

Нам, дельфистам, придется использовать специальные обрабочики: подключаем в uses модуль Windows и настраиваем:


Код: pascal
1.
2.
3.
4.
5.
6.
uses
  ...Windows, ...;
...
begin
...
  Windows.SetConsoleCtrlHandler(CtrlCHandler, True);



Шаблон для CtrlCHandler:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
function console_handler( dwCtrlType: DWORD ): BOOL;
var
  i: Integer;
begin
  if CTRL_C_EVENT = dwCtrlType then
  begin
    // Выполняем "мягкое" завершение
...
  end else 
    result := False;
end;


Как выполнить "мягкое" завершение - зависит от текущей задачи.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38761403
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Мультипоточность с ZeroMQ.

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

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

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

Для беспроблемного написания многопоточного кода с помощью ZeroMQ следует руководствоваться следующими правилами:

- Изолируйте данные внутри потока и никогда не разделяйте их между потоками. Единственным исключением являются контексты ZeroMQ, которые являются threadsafe.
- Держитесь подальше от классических механизмов параллелизма, таких как мьютексы, критические секции, семафоры и т.д. Это анти-паттерны в приложениях ZeroMQ.
- Создайте один ZeroMQ контекст в начале вашего процесса и передавайте его всем создаваемым потокам, с которыми будете взаимодействовать через InProc сокеты ZMQ.
- Для создания структуры вашего приложения используйте подключаемые (attach) потоки, и соедините их с их родительскими потоками через сокеты PAIR по протоколу InProc. Порядок работы: привязываем (zmq_bind()) сокет, а затем создаем дочерний поток, который коннектится к сокету родительского потока.
- Используйте отключаемые (detach) для имитации работы самостоятельных задач, с учетом своих условий. Подключите их по протоколу TCP. Позже вы можете переместить их в автономные процессы без значительного изменения кода.
- Все взаимодействия между потоками происходит как ZeroMQ сообщения, которые вы можете определить более или менее формально.
- Не разделять сокеты ZeroMQ между потоками. Сокеты ZeroMQ не потокобезопасны. Технически есть возможность передачи сокета от одного потока к другому, но это требует навыка. Единственное место, где разумно и оправдано разделение сокетов между потоками - это удаление сокетов в деструкторах ваших классовых оберток над ZMQ.


Например.

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

Если следовать перечисленным правилам, можно легко создавать надежные многопоточные приложения. Логика приложения может размещаться потоках, процессах, или узлах сети, в соответствии с текущими планами по захвату мира.

ZeroMQ использует нативные потоки ОС (Windows), а не виртуальные "зеленые" потоки. Для отладки можно использовать стандартные средства, вроде ThreadChecker от Intel, чтобы увидеть, что ваше приложение делает. К недостаткам использования нативных потоков можно отнести, что API-интерфейсы "родной" многопоточности конкретной ОС не всегда портируются, и, к примеру, если вы используете огромное количество потоков (тысячи), то некоторые ОС просто не потянут такой нагрузки.
...
Перейдем к практике. Превратим наш старый сервер 16562041 , в нечто более работоспособное.
Старый сервер был однопоточным. Если обслуживание каждого запроса было легким, то это нормально: один ZMQ поток может работать на полной скорости ядра процессора без ожидания и выполнять очень много работы.
Но серверы из реальной жизни на каждый запрос делают более сложную работу. Одноядерного сервера может оказаться недостаточно, когда по серверу жахнет сразу 10000 клиентов. Сервер из реальной жизни будет запускать несколько рабочих потоков. После чего он будет принимать запросы так быстро, как это возможно и раздавать их своим своих рабочим потокам.
Рабочие потоки будут "перемалывать цифирь" и отправлять результаты обратно.

Конечно, это можно сделать с помощью прокси-брокера и внешних рабочих процессов, но часто легче начать один процесс, который займет все шестнадцать ядер, чем шестнадцать процессов, каждый из которых жрет одно ядро. Кроме того, рабочие процессы будут занимать линии связи и поглощать сетевой трафик и просто тормозить.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38761427
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Многопоточный сервис:
Сервис
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
program RR_Service;

{$APPTYPE CONSOLE}
// Многопоточный сервис (сервер)
// Находится в известной клиентам конечной точке, "слушает" tcp порт 5555,
// к которому привязан сокет ZMQ_ROUTER
// Получает целое число, возводит в квадрат и отправляет обратно результат

uses
  SysUtils, ZMQ;

procedure Worker_Routine(aContext: Pointer); // Процедура потока
var
  fSocketReceiver: Pointer;
  fSrcVal: Cardinal;
  fResultVal: UInt64;
  i: integer;
begin
// Сокет для общения с диспетчером
  fSocketReceiver := zmq_socket(aContext, ZMQ_REP);
  zmq_connect(fSocketReceiver, 'inproc://worker');
  i := 0;
  while True do
  begin
    zmq_recv(fSocketReceiver, fSrcVal, SizeOf(fSrcVal), 0); // Запрос
    fResultVal := UInt64(fSrcVal) * UInt64(fSrcVal); // "Полезная работа"
    zmq_send(fSocketReceiver, fResultVal, SizeOf(fResultVal), 0); // Ответ
    Writeln('In thread, Iter: ', i, ' src=', fSrcVal, ' result=', fResultVal);
    Inc(i);
  end;
  zmq_close(fSocketReceiver);
end;

var
  fContext: Pointer;
  fSocketClients: Pointer;
  fSocketWorkers: Pointer;
  i: integer;
  fThreadId : Cardinal;
  fRez : Integer;
begin

  fContext := zmq_ctx_new(); // Инициализация

  fSocketClients := zmq_socket(fContext, ZMQ_ROUTER);
  fRez := zmq_bind(fSocketClients, 'tcp://*:5555'); // Привязка к конечной точке

  fSocketWorkers := zmq_socket(fContext, ZMQ_DEALER);
  fRez := zmq_bind(fSocketWorkers, 'inproc://worker');// Привязка к конечной точке

  Writeln('Starting service...');
  for i := 0 to 2 do  // Запуск пула рабочих потоков
    BeginThread(nil, 0, @Worker_Routine, fContext, 0, fThreadId);

// Соединение рабочих потоков с потоками клиентов через очередь
  zmq_proxy(fSocketClients, fSocketWorkers, nil);

  zmq_close(fSocketClients);
  zmq_close(fSocketWorkers);
  zmq_ctx_destroy(fContext);
  Readln;

end.




Клиент - все тот же:
Клиент
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
program RR_Client;

{$APPTYPE CONSOLE}
// Клиент
// Коннектится сокетом REQ к tcp://localhost:5555
// Шлет целое число сервису (серверу), обратно получает квадрат числа

uses
  SysUtils, ZMQ;
const
  c_ReqCnt = 100;
var
  fContext: Pointer;
  fSocketRequester: Pointer;
  fSrcVal: Cardinal;
  fResultVal: UInt64;
  i: integer;
begin

  fContext := zmq_ctx_new(); // Инициализация
  fSocketRequester := zmq_socket(fContext, ZMQ_REQ);
  zmq_connect(fSocketRequester, 'tcp://localhost:5555');
    // Коннект к сервису
  Randomize();
  for i := 0 to Pred(c_ReqCnt) do begin
    fSrcVal := Cardinal(Random(-1));
      // Генерация случайного целого
    zmq_send(fSocketRequester, fSrcVal, SizeOf(fSrcVal), 0); // Запрос
    Write('Iter: ', i, ' src=', fSrcVal);
    zmq_recv(fSocketRequester, fResultVal, SizeOf(fResultVal), 0); // Ответ
    Writeln(' result=', fResultVal);
  end;
  zmq_close(fSocketRequester);
  zmq_ctx_destroy(fContext);
  Readln;

end.




Прежде всего интересен сервис ("сервер"). Основной поток приложения поочередно транслирует запросы от сокета fSocketClients (сокет типа ZMQ_ROUTER) к сокету fSocketWorkers (сокет типа ZMQ_DEALER) и обратно, используя знакомый метод zmq_proxy().
То есть, основной поток является брокером ("прокси") сообщений.
Для обработки данных запущено три потока. Процедура потока - Worker_Routine(). В процедуре потока создается сокет fSocketReceiver типа (ZMQ_REP). Сокет fSocketReceiver рабочего потока связан с сокетом fSocketWorkers основного потока по inproc протоколу.
Сокет рабочего потока:
Код: pascal
1.
2.
3.
// Сокет для общения с диспетчером
  fSocketReceiver := zmq_socket(aContext, ZMQ_REP);
  zmq_connect(fSocketReceiver, 'inproc://worker');



Сокет основного потока:
Код: pascal
1.
2.
  fSocketWorkers := zmq_socket(fContext, ZMQ_DEALER);
  fRez := zmq_bind(fSocketWorkers, 'inproc://worker');// Привязка к конечной точке


Естественно, вместо inproc - протокола можно было бы использовать tcp, но пришлось бы занять порт и, кроме того, inproc гораздо быстрее.

Еще раз: работа сервиса.

Сервис запускает несколько рабочих потоков. Каждый рабочих поток создает сокет типа REP и затем в цикле обрабатывает запросы к сокету. Рабочие потоки представляют собой обычные однопоточные сервисы, разница - в транспорте (inproc вместо tcp), и в направлении операции "привязать - подключить" (bind-connect).

Сервер создает сокет типа ROUTER, чтобы общаться с клиентами и связывает его (сокет) с внешним интерфейсом по tcp.

Сервис создает сокет типа DEALER, чтобы общаться с рабочими процессами и связывает этот сокет с внутренним интерфейсом (с помощью inproc).

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

То есть, цепочка запрос - ответ выглядит так: REQ-ROUTER-очередь-DEALER-REP.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38761452
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Получилась вот такая структура:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38761456
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В выводе сервиса видно, что номера итераций повторяются по три раза - по числу запущенных потоков.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38763975
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Для удобства в дальнейшей работе создадим пару функций, пересылающих и принимающих строки:
Код: pascal
1.
2.
function s_recv(aZMQSocket: Pointer; aFlags: integer = 0): string;
function s_send(aZMQSocket: Pointer; const aSrcString: string;


Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
function s_recv(aZMQSocket: Pointer; aFlags: integer = 0): string;
var
  fLen: Integer;
  fZMQMsg: zmq_msg_t;
begin

  Result := '';
  try
    zmq_msg_init(fZMQMsg);
    fLen := zmq_msg_recv(fZMQMsg, aZMQSocket, aFlags);
    if fLen <= 0 then
      Exit;
    SetLength(Result, fLen div SizeOf(Char));
    Move(zmq_msg_data(fZMQMsg)^, Result[1], fLen div SizeOf(Char));
  finally
    zmq_msg_close(fZMQMsg);
  end;
end;

function s_send(aZMQSocket: Pointer; const aSrcString: string;
  aFlags: integer = 0): integer;
var
  fLen: Integer;
  fZMQMsg: zmq_msg_t;
begin
  Result := 0;
  zmq_msg_init(fZMQMsg);
  if Length(aSrcString) > 0 then begin
    zmq_msg_init_size(fZMQMsg, Length(aSrcString) * SizeOf(Char));
    Move(PChar(aSrcString)^, zmq_msg_data(fZMQMsg)^, Length(aSrcString) *
      SizeOf(Char));
  end;
  Result := zmq_msg_send(fZMQMsg, aZMQSocket, aFlags);
end;
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38763992
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Обмен сигналами между потоками.

Пора рассмотреть сокеты типа PAIR .

Пример:

Приложение в "основном потоке" создает поток 2 и ждет сигнала о выполнении какой-то полезной работы.
Поток 2 создает поток 3 и ждет сигнала о выполнении какой-то полезной его работы, затем посылает сигнал "основному" потоку.
Поток 3 выполняет некоторую полезную работу и посылает сигнал потоку 2.

Отправка - прием сигналов будет выполняться с помощью сокетов типа PAIR по inproc протоколу.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764033
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
"Сигнал" в данном случае - это просто сообщение, строка "READY". На приемной стороне содержамое строки игнорируется, важен сам факт передачи-приема.

... Что-то я с названиями напутал. :(

Step 3 - это "основной" поток, который создает Step 2. Последний создает Step 1.
Ну и Step 3 ждет сигнала от Step 2, а Step 2 ждет сигнала от Step 1.

Итак, исходник:


Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
program SgnlBtwThrds;

{$APPTYPE CONSOLE}

uses
  SysUtils, ZMQ,
  ZMQ_Utils, // Тут вспомогательные вещи, вроде s_send() и s_recv()
  Windows;


function Thread_Step_1(aContext: Pointer): integer;
var
  fSocketXmitter: Pointer;
begin
  Result := 0;
 // Подключается к Thread_Step_2 и сообщает о готовности
  fSocketXmitter := zmq_socket(aContext, ZMQ_PAIR);
  zmq_connect(fSocketXmitter, 'inproc://step2');
  Writeln('Step 1 ready, signaling step 2');
  s_send(fSocketXmitter, 'READY');
  zmq_close(fSocketXmitter);
end;


function Thread_Step_2(aContext: Pointer): integer;
var
  fDummy: string;
  fSocketReceiver: Pointer;
  fSocketXmitter: Pointer;
  fThreadId: Cardinal;
begin
  Result := 0;
 // Связыывает inproc сокет перед запуском
  fSocketReceiver := zmq_socket(aContext, ZMQ_PAIR);
  zmq_bind(fSocketReceiver, 'inproc://step2');
  BeginThread(nil, 0, @Thread_Step_1, aContext, 0, fThreadId);

// Ожидание сигнала
  fDummy := s_recv(fSocketReceiver);
  zmq_close(fSocketReceiver);

// Коннект к step3 сообщение о готовности
  fSocketXmitter := zmq_socket(aContext, ZMQ_PAIR);
  zmq_connect(fSocketXmitter, 'inproc://step3');
  Writeln('Step 2 ready, signaling step 3');
  s_send(fSocketXmitter, 'READY');
  zmq_close(fSocketXmitter);
end;
var
  fContext: Pointer;
  fSocketReceiver: Pointer;
  fThreadId: Cardinal;
  fDummy: string;
begin
  fContext := zmq_ctx_new();

  fSocketReceiver := zmq_socket(fContext, ZMQ_PAIR);
    // Сокет для приема сигнала
  zmq_bind(fSocketReceiver, 'inproc://step3');
  BeginThread(nil, 0, @Thread_Step_2, fContext, 0, fThreadId);

  fDummy := s_recv(fSocketReceiver);
  zmq_close(fSocketReceiver);

  Writeln('Step 3 ready!');
  Writeln;
  Writeln('Test successful!');
  zmq_ctx_destroy(fContext);
  Readln;

end.


...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764056
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Итак, это был образец классического многопоточного приложения ZeroMQ:
- Два потока взаимодействуют через InProc, используя общий контекст.
- Родительский поток создает один сокет, связывает его с конкретной конечной точкой по InProc, а затем запускает дочерний поток, передавая ему контекст.
- Дочерний поток создает второй сокет, соединяет его с той же конкретной конечной точкой по InProc и по готовности сигнализирует родительскому потоку.

Обращаем внимание, что многопоточный код, используемый в данной схеме, не масштабируется за пределы процесса: если используется протокол InProc и сокеты типа PAIR, значит, строится сильносвязная система, в которой есть взаимозависымые структуры. Такие вещи следует делать, когда нужна высокая скорость взаимодействия между компонентами системы.
Если использовать схему с протоколом TCP и использовать собственный контекст в каждом потоке, система будет менее связной и позволит в будущем легко масштабироваться методом вычленения узлов в отдельные процессы.
~~~~~~~~~~~~~

Почему были использованы сокеты типа PAIR? -Потому, что использование сокетов других типов имеет нежелательные побочные эффекты:
- Можно использовать рассмотренные ранее PUSH для отправителя и PULL для приемника. Это выглядит просто и будет работать, однако следует помнить, что PUSH будет распределять сообщения по всем доступным приемникам. Если вы случайно запустили два приемника (например, создали еще один поток с такой же процедурой потока), то вы будете "потеряете" половину ваших сигналов. Преимущество сокетов PAIR в том, что они не позволят создать больше одного соединения; пара сокетов типа PAIR - является эксклюзивной.
- Вы можете использовать DEALER для отправителя и ROUTER для приемника. ROUTER, однако, упаковывает сообщение в "конверт", т.обр. ваш сигнал нулевого размера превращается в составное сообщение. Это несущественно, если вы не заботитесь о самих данных, а посылаете только сигнал. Однако, если понадобится отправить реальные данные, то обнаружится, что ROUTER прислал вам "неправильные" сообщения. Кроме того, DEALER точно так же как и PUSH, распределяет исходящие сообщения между всеми приемниками , т.е. тут такой же риск потери сообщений, как и при использовании PUSH.
- Вы можете использовать PUB для отправителя и SUB для приемника. Эта схема будет правильно доставлять сообщения, и PUB не разбросает их по приемникам, как DEALER или PUSH. Тем неменее, вам придется все время настривать приемник на подписку, что утомительно.

По этим причинам, пара сокетов типа PAIR - лучший выбор для пересылки сигналов координации между парами потоков в приложении.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764099
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Согласование работы между узлами сети.

Если нужно согласовывать работы набора узлов в сети, то сокеты типа PAIR уже не так хороши.

Это как раз те области, где стратегии использования потоков и узлов различаются. В большинстве случаев узлы приходят и уходят "сами по себе", а потоки обычно статичны. Сокеты типа PAIR не выполнят автоматическое переподключение, если удаленный узел сети уйдет, а потом появится снова. Другим существенным различием в применении узлов и применением потоков является то, что обычно мы имеем фиксированное число потоков, в то время как число рабочих узлов сети меняется.
...
~~~~~~~~~~~~~
Рассмотрим рассмотреть предыдущий сценарий (с метостанцией-издателем и кучей клиентов-подписчиков) и попробуем координировать узлы так, чтобы быть уверенными в том, что при запуске подписчики-клиенты не потеряют данные.

Схема работы приложения:

- Издатель (сервис метеостанции) заранее знает, сколько будет подписчиков. То есть, это просто волшебное число, которое он откуда-то получает.

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

- Когда к издателю подключатся все подписчики, он начинает публиковать данные.

В данном случае для согласования действий между подписчиком и издателем мы будем использовать пары сокетов REQ-REP.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764148
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Поехали. Кодируем проект "Метео" (исходная задача - 16583825 ). Сервис начинает публиковать данные только после коннекта 10 клиентов. Пакеты данных публикуются 10 000 раз, после чего публикуется сообщение 'END'.
Клиенты при запуске сообщают сервису о своем появлении и подписываются на данные сервиса.
Полученные по подписке данные выводятся в консоль клиента, число полученных пакетов подсчитывается, . Рабочий цикл клиента прерывается при получении сообщения 'END'.

Код сервиса:
Сервис "метео" с синхронизацией запуска.
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
program SynPS_Service;
{$APPTYPE CONSOLE}
// Меостанция
// Сервис - издатель с синхронизацией запуска

uses
  SysUtils, ZMQ, ZMQ_Utils;

const
  c_SUBSCRIBERS_EXPECTED = 10; // Ждем 10 подписчиков!
var
  fContext: Pointer;
  fSocketPublisher: Pointer;
  fSocketSyncService: Pointer;
  fDummy: string;
  fMsgStr: string;
  fSubscribers: Integer = 0;
  fSndhwm: Integer = 1100000;
  i: Integer;
begin
  fContext := zmq_ctx_new();
  // Сокет для общения с клиенатами
  fSocketPublisher := zmq_socket(fContext, ZMQ_PUB);
  zmq_setsockopt(fSocketPublisher, ZMQ_SNDHWM, @fSndhwm, SizeOf(fSndhwm));
  zmq_bind(fSocketPublisher, 'tcp://*:5561');

  // Сокет для приема сигналов
  fSocketSyncService := zmq_socket(fContext, ZMQ_REP);
  zmq_bind(fSocketSyncService, 'tcp://*:5562');

 // Получение синхросигналов от подписчиков
  Writeln('Waiting for subscribers...');
  while fSubscribers < c_SUBSCRIBERS_EXPECTED do begin
  // - ожидание синхрозапроса
    s_recv(fSocketSyncService);
  // - отправка синхроответа
    s_send(fSocketSyncService, '');
    Inc(fSubscribers);
  end;

  // Теперь раздача 10 000 оповещений, а затем - отправка 'END'

  Randomize;

  for i := 0 to 9999 do begin
    Sleep(1); // Типа измеряет что-то
    // Температура
    fMsgStr := Format('Temperature : %d C', [20 - Random(40)]);
    s_send(fSocketPublisher, fMsgStr);

    // Атм. давление
    fMsgStr := Format('Pressure : %d Pa', [101375 - Random(100)]);
    s_send(fSocketPublisher, fMsgStr);

    // Скорость ветра
    fMsgStr := Format('Wind : %d m/s', [Random(10)]);
    s_send(fSocketPublisher, fMsgStr);
  end;
  s_send(fSocketPublisher, 'END');
  Writeln('Publisher stopped...');
  zmq_close(fSocketPublisher);
  zmq_close(fSocketSyncService);
  zmq_ctx_destroy(fContext);
  Readln(fDummy);
end.


Код клиента:
Клиент "метео" с синхронизацией запуска.
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
program SynPS_Client;

{$APPTYPE CONSOLE}
// Меостанция
// Клиент - подписчик с синхронизацией запуска
uses
  SysUtils, ZMQ, ZMQ_Utils;

var
  fContext: Pointer;
  fSocketSubscriber: Pointer;
  fSocketSyncClient: Pointer;
  fMsgStr: string;
  fCnt: Integer = 0;

const
  cFilter1 = 'Temperature';
  cFilter2 = 'Pressure';
  cFilter3 = 'Wind';
begin
  fContext := zmq_ctx_new(); // Инициализация
  // Сначала подключаем сокет подписчика
  fSocketSubscriber := zmq_socket(fContext, ZMQ_SUB);
  zmq_connect(fSocketSubscriber, 'tcp://localhost:5561');
  zmq_setsockopt(fSocketSubscriber, ZMQ_SUBSCRIBE, nil, 0);  // Настройка сокета
  Sleep(1);// ZMQ настолько шустрый, что нужно подождать...

  // Теперь синхронизируемся с издателем
  fSocketSyncClient := zmq_socket(fContext, ZMQ_REQ);
  zmq_connect(fSocketSyncClient, 'tcp://localhost:5562');
  s_send(fSocketSyncClient, ''); // Отправка сообщения о готовности
  s_recv(fSocketSyncClient); // Ожидание подтверждения

  Writeln('Subscriber started...');

  while True do begin
    fMsgStr := s_recv(fSocketSubscriber); // Прием данных
    if fMsgStr = 'END' then
      Break;
    Writeln(fMsgStr);
    Inc(fCnt)
  end;
  Writeln('Received ', fCnt, ' updates');

  zmq_close(fSocketSubscriber);
  zmq_close(fSocketSyncClient);
  zmq_ctx_destroy(fContext);
  Readln;
end.



Мы не можем быть уверены, что коннект SUB будет завершен к тому времени, когда завершится диалог REQ/REP. Вообще нет никакой гарантии того, что исходящие соединения завершатся в том или ином порядке, если вы используете любой транспорт за исключением InProc.
Ну, в примере мы воткнули ожидание ( sleep(1) ) после подпиской и синхропосылками REQ/REP.
Что как бы работает, но тоже в общем случае не гарантирует.

Более надежная схема могла бы выглядеть так:

- Издатель открывает PUB - сокет и начинает передавать сообщения "Hello"(без данных).
- Подписчик подключает SUB - сокет и, когда тот принимает сообщение "Hello", то сообщает об этом издателю через пару сокетов REQ/REP.
- Когда издатель получит необходимое число подтверждений от коннекте от подписчиков, он начинает публиковать реальные данные.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764169
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Нуль - копия (Zero-Copy ).

API ZeroMQ позволяет отправлять и принимать сообщения напрямую, используя буфера данных приложения, без копирования данных. Эта технология называется нуль - копия , и её применение позволяет увеличить производительность в некоторых приложениях.

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

Делаем нуль-копию. С помощью zmq_msg_init_data() создается сообщение, которое ссылается на уже существующий блок данных вашего приложения, которое затем передается в zmq_msg_send() . Когда создается сообщение, вы также передаете параметр - функцию, чтобы ZeroMQ смогла вызвать её для освобождения блока данных после завершения передачи сообщения.

Пример такой функции, предполагающий, что буфер представляет собой блок длиной, скажем, в 1000 байт, выделенный в куче:

Код: pascal
1.
2.
3.
4.
procedure my_free (aData : Pointer; aHint : Pointer); cdecl;
begin
  Freemem(aData);
end;



PS: в модуле ZMQ.PAS

Тип zmq_free_fn определен как
Код: pascal
1.
2.
3.
type
...
  free_fn = procedure(data, hint: Pointer); 


без модификатор cdecl. Конечно, следует исправить:

Код: pascal
1.
 free_fn = procedure(data, hint: Pointer); cdecl;




Пример применения:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
procedure my_free(aBuf, aHint : Pointer); cdecl;
begin
  FreeMem(aBuf);
end;


var
  fMsg : zmq_msg_t;
  fData : Pointer;

begin
...
    GetMem(fData, 1000);
    FillChar(fData^, 1000, 'z');
    zmq_msg_init_data(fMsg, fData, 1000, my_free, nil);
    zmq_msg_send(fMsg,fSocketSyncService, 0);



PPS: насчет параметра hint:
Код: pascal
1.
 free_fn = procedure(data, hint: Pointer); cdecl;


Значение его просто дублируется из параметра hint функции
Код: pascal
1.
2.
function zmq_msg_init_data( var msg: zmq_msg_t; data: Pointer; size: size_t;
  ffn: free_fn; hint: Pointer ): Integer; cdecl; external libzmq;


Судя по всему, он введен для особых случаев - например, когда требуется передать блок дополнительных данных в процедуру ffn: free_fn.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764172
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД Нуль - копия (Zero-Copy ).


Следует еще раз отметить, что вызывать zmq_msg_close() после отправки сообщения не нужно - libzmq выполнит вызов автоматически, когда сообщение будет отправлено.

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

При записи составных сообщений ZeroMQ отлично работает с нуль-копией. Для обычных сообщений вам понадобилось бы слить несколько сообщений в один буфер, а только потом отправлять. То есть, понадобилось бы выполнить копирование данных. А с ZMQ можно отправить несколько разных буферов, пришедших от разных источников, как отдельные кадры сообщения. Каждое поле отправляется как кадр, отделенный значением длины (префиксом). В приложении это выглядит как последовательность вызовов отправлений или приема. Однако, внутри ядра ZMQ, составное сообщение отправляется и принимается одним системным вызовом, что очень эффективно.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764182
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Упаковка сообщений для схемы "Издатель-Подписчик"

Вернемся к схеме "Издатель - Подписчик". (Приложение "Метостанция").
Вспомним, что при подписке данные можно фильтровать: 16590914 .
Однако, фильтрация по самим данным не всегда удобно. Куда удобнее фильтровать по значению ключевого поля, связанному с данными.
Например:

T - Температура
P - Давление
W - Скорость ветра

Это гораздо удобнее. Например, ключу W можно сопоставить не только скорость ветра, но и направление ветра.
Или данные о скорости ветра разбить на две подгруппы:
WS - ветер до 5 м/с;
WF - ветер 5 м/с и более.

Это реализуется очень просто: на сервере нужно формировать составное сообщение:

Код: pascal
1.
2.
3.
4.
    // Температура
    fMsgStr := Format('Temperature : %d C', [20 - Random(40)]);
    s_send(fSocketPublisher, 'T', ZMQ_SNDMORE); // 1я часть - ключ
    s_send(fSocketPublisher, fMsgStr);// 2я часть - тело сообщения



А клиент, при оформлении подписки, должен указать фильтр:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
const
  cFilter1: string = 'T';
  cFilter2: string = 'P';
  cFilter3: string = 'W';
begin
  fContext := zmq_ctx_new(); // Инициализация
  // Подключаем сокет подписчика
  fSocketSubscriber := zmq_socket(fContext, ZMQ_SUB);
  zmq_connect(fSocketSubscriber, 'tcp://localhost:5561');
  zmq_setsockopt(fSocketSubscriber, ZMQ_SUBSCRIBE, PChar(cFilter1), Length(cFilter1) * SizeOf(Char)); // Настройка сокета


При этом сообщение будет фильтроваться по первому кадру, а приходить отфильтрованное сообщение будет полностью.

Сервис.
Код сервиса "Метео" с составным пакетом
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
program EnvPS_Service;
{$APPTYPE CONSOLE}
// Метеостанция

uses
  FastMM4, SysUtils, ZMQ, ZMQ_Utils;

var
  fContext: Pointer;
  fSocketPublisher: Pointer;
  fDummy: string;
  fMsgStr: string;
  i: Integer;
begin
  fContext := zmq_ctx_new();
  // Сокет для общения с клиенатами
  fSocketPublisher := zmq_socket(fContext, ZMQ_PUB);
  zmq_bind(fSocketPublisher, 'tcp://*:5561');

  Writeln('Press Enter,  please, when all subscribers will be ready...');
  Readln;

  // Теперь раздача 100 собщений
  Randomize;

  for i := 0 to 99 do begin
    Sleep(1); // Типа измеряет что-то
    // Температура
    fMsgStr := Format('Temperature : %d C', [20 - Random(40)]);
    s_send(fSocketPublisher, 'T', ZMQ_SNDMORE); // 1я часть - ключ
    s_send(fSocketPublisher, fMsgStr);// 2я часть - тело сообщения

    // Атм. давление
    fMsgStr := Format('Pressure : %d Pa', [101375 - Random(100)]);
    s_send(fSocketPublisher, 'P', ZMQ_SNDMORE);
    s_send(fSocketPublisher, fMsgStr);

    // Скорость ветра
    fMsgStr := Format('Wind : %d m/s', [Random(10)]);
    s_send(fSocketPublisher, 'W', ZMQ_SNDMORE);
    s_send(fSocketPublisher, fMsgStr);
  end;
  Writeln('Publisher stopped...');
  zmq_close(fSocketPublisher);
  zmq_ctx_destroy(fContext);
  Readln(fDummy);
end.




Клиент:

Клиент "Метео", составной пакет
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
program EnvPS_Client;

{$APPTYPE CONSOLE}
// Метеостанция
uses
  SysUtils, ZMQ, ZMQ_Utils;

var
  fContext: Pointer;
  fSocketSubscriber: Pointer;
  fPrefix: string;
  fBody: string;
  fCnt: Integer = 0;

const
  cFilter1: string = 'T';
  cFilter2: string = 'P';
  cFilter3: string = 'W';
begin
  fContext := zmq_ctx_new(); // Инициализация
  // Подключаем сокет подписчика
  fSocketSubscriber := zmq_socket(fContext, ZMQ_SUB);
  zmq_connect(fSocketSubscriber, 'tcp://localhost:5561');
  zmq_setsockopt(fSocketSubscriber, ZMQ_SUBSCRIBE, PChar(cFilter1), Length(cFilter1) * SizeOf(Char)); // Настройка сокета
  Writeln('Subscriber started...');

  while True do begin
    fPrefix := s_recv(fSocketSubscriber); // Прием данных, префикс
    fBody := s_recv(fSocketSubscriber); // Прием данных, тело
    Writeln(fPrefix, ' ', fBody);
    Inc(fCnt);
    if fCnt > 10 then
      break
  end;
  Writeln('Received ', fCnt, ' updates');

  zmq_close(fSocketSubscriber);
  zmq_ctx_destroy(fContext);
  Readln;
end.



Разбиение сообщения на части удобно, в том числе, например, для логического разделения составных данных.Например: Ключ - Адрес - Основное сообщение.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38764197
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
High-Water Marks (Высокая вода).

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

Проблема заключается в следующем: вообразите, что есть процесс A, с высокой частотой отправляющий сообщения процессу B, который их обрабатывает. Иногда процесс B оказывается недоступен (сборка мусора, перегрузка CPU, что угодно), и не может обработать сообщения за короткий период. Если такие задержки составляют несколько секунд или даже больше - это может стать серьезной проблемой. Что произойдет с сообщениями, которые процесс A все еще старается отправлять? Некоторые из них попадут в сетевые буферы процесса B. Некоторые будут все еще в процессе передачи по Ethernet. Некоторые будут в буферах сети процесса A. А остальные будут накапливаться в памяти процесса A с той скоростью, с которой процесс A их отправляет. Если не принять мер предосторожности, можно легко получить out of memory и крах.

Это - классическая проблема систем обмена сообщениями. Причем, чаще всего, процесс В - это приложение, написанное пользователем, и процесс А его никак не контролирует.

Что делать? Один из вариантов решения - управлять входным потоком. Процесс A получает сообщение откуда-то еще. Говорим ему "Stop!". И так далее - тормозим всех, кто меня торопит. Это называется управление потоком (flow control - обмен сигналами, при котором каждое устройство оповещает о готовности послать или принять данные). Такое решение выглядит правдоподобно, но что если вам пришло сообщение из Твиттера? Вы скажете всему миру подождать, пока вы в процессе B делается что-то важное?

Flow control работает в некоторых случаях, но не работает в других. Транспортный уровень не может сообщить уровню приложения "stop". Это как если метрополитен скажет большему бизнесу: "пожалуйста, держите работников еще полчаса, я слишком занят". Решением для системы обмена сообщениями является назначение пределов размеров буферов, а по достижению этих границ - выполнение некоторых разумных действий. В некоторых случаях (ОК, не для метрополитена), будет отказ в обслуживании (сообщения отбрасываются), в других лучшей стратегией будет ожидание.

ZeroMQ использует концепцию HWM (high-water mark) для определения емкости своих внутренних трубопроводов. Каждое соединение от сокета к сокету имеет собственный трубопровод, и значение HWM для отправки и/или для приема , в зависимости от типа сокета. Некоторые сокеты (PUB, PUSH) имеют только буферы для отправления сообщений. Некоторые (SUB, PULL, REQ, REP) - только для приема. Некоторые (DEALER, ROUTER, PAIR) имеют оба типа буферов.

В ZeroMQ v2.x значение HWM было бесконечным по умолчанию. Это было легко для использования, но и, как правило, оказывалось смертельным для сокетов - издателей с большим объемом сообщений. В ZeroMQ v3.x оно установлено в 1000 по умолчанию, которое является более разумным. Если вы все еще используете ZeroMQ v2.x, вы всегда должны установить HWM на ваших сокетах, например - 1000, чтобы соответствовать ZeroMQ v3.x или другое значение, которое посчитаете правильным.

Когда сокет достигает своего HWM, он либо блокирует данные, либо отбрасывает сообщения, в зависимости от типа скета. Сокеты типа PUB и ROUTER будут отбрасывать данные, в то время как другие будут блокировать. Для транспорта inproc передающий и принимающий сокеты используют общие буферы, поэтому реальное значение HWM будет суммой HWM, установленных для обоих сторон.

Наконец, последнее. Значения HWMs не являются точными величинами. Если вы установите 1,000 сообщений (по умолчанию), то реальная величина буфера будет меньше, чем половина, так как libzmq реализует еще и собственные очереди.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38765367
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Потери сообщений.

Сообщения атомарны, это хорошо. То есть, если вы что-то получаете - то получаете это полностью.
Плохо то, что если что-то теряется, то вы не получаете вообще ничего.

Далее - универсальный решатель проблем потерь сообщений.

- Для сокетов SUB, всегда устанавливать подписку с помощью zmq_setsockopt() и ZMQ_SUBSCRIBE , или не получите ни одного сообщения. Так как вы подписываетесь на сообщения с заданным префиксом, для приема всех сообщений следует указать префикс "" (пустая строка).

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

- Даже если сокеты SUB и PUB синхронизированы, сообщения могут все равно теряться . Это из-за того, что внутренние очереди могут быть еще не созданы до момента фактического коннекта. Если вы можете переключить направление операций bind/connect так, чтобы для сокета SUB выполнялся bind, а для сокета PUB - connect, возможно, вы получите более рабочий вариант.

- Если используются сокеты REP и REQ, но при этом не соблюдается синхронный порядок операций запрос/ответ/запрос/ответ, ZeroMQ будет сообщать об ошибках, которые вы, возможно, будете игнорировать. Это также будет выглядеть как потеря сообщений. При использовании сокетов REQ или REP следует строго соблюдать последовательность запросов и ответов на обеих сторонах соединения. И всегда в реальном коде следует проверять коды ошибок после вызовов ZeroMQ.

- Если используются сокеты PUSH, может получиться, что первые подключившиеся сокеты PULL получат несоразмерно большое количество сообщений . Равномерное распределение сообщений возможно лишь в случае, когда все сокеты PULL успешкно подключились, что может занять несколько миллисекунд. В качестве альтернативы PUSH/PULL, для если нагрузка невелика, следует рассмотреть пару ROUTER/DEALER и шаблон балансировки нагрузки.

- Разделение сокетов между потоками приведет к неустойчивому поведению и авариям .

- Если используется транспорт inproc, убедитесь, что оба сокета имеют общий контекст . Иначе будет отказ на стороне коннекта. Также, сначала делайте bind, а лишь затем - connect. Транаспорт inproc не отключаемый, в отличии от tcp.

- При использовании сокетов ROUTER очень легко потерять сообщения, если случайно отправлены неверные кадры идентификации (или если вообще забыть про кадры идентификации). Отличной идеей будет установка для таких сокетов ROUTER отции ZMQ_ROUTER_MANDATORY, но все равно не следует забывать проверять код завершения после каждого вызова ZMQ .

Последнее. Если вы все сделали правильно, но ничего не работает, сделайте крошечное тестовое приложение, воспроизводящее проблему и запросите помощь у сообщества ZeroMQ.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38765369
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД...
- Если используется транспорт inproc... .Также, сначала делайте bind, а лишь затем - connect.
...

PS: В ZeroMQ версии 4.* эта проблема решена, bind и connect для inproc может быть выполнен в любой последовательности.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38767110
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Шаблон "Запрос - Ответ" - подробности.

Ранее были немного упомянуты составные сообщения.
Так вот, использование составных сообщений дает возможность оформлять сообщения в форме "конвертов", когда адрес отделен от тела сообщения. Так вот, сообщения в SUB/PUB сокетах как раз пересылаются в таких конвертах. Наличие адресов позволяет легко организовать двусторонний обмен с помощью средств общего назначения - таких, как ZPI ZMQ и прокси, которые на лету создают, читают и удаляют адреса, не затрагивая "полезные" данные.

Так вот, в шаблоне "Запрос - Ответ" конверт содержит обратный адрес для ответа. Наличие обратного адреса позволяет получить ответ на запрос.

При использовании сокетов REQ и REP нет никакой нужды создавать конверты самостоятельно; это автоматически делают сами сокеты.

Для понимания интересно разобрать, как такие конверты используются с сокетом ROUTER.

Простая форма конверта для ответа REPLY .

Обмен данными "запрос-ответ" состоит из сообщения-запроса и, в конечном итоге, из сообщении-ответа.
В простой схеме "запрос-ответ" на один запроса приходится один ответ.
В более сложных схемах запросы и ответы могут пересылаться асинхронно. Однако, ответный "конверт" всегда одинаковый.

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


Например, в сокет REQ отправляем запрос "Hello". Сокет REQ создает простейший конверт для ответа без адреса, просто пустой кадр и кадр сообщения, содержащий строку "Hello". Т.обр., сообщение из двух кадров:

№ кадраДлинаСодержание1025Hello

Сокет REP, получив конверт, "вскрывает" его: удаляет разделитель конвертов (первый кадр) и передает сообщение приложению.

Если перехватить сетевые данные приложения, мы увидим, что каждый запрос и каждый ответ состоит из двух кадров: пустой кадр, а затем кадр с полезными данными. Для простого случая "REQ-REP" это выглядит не очень полезным.

Однако, это важно для сокетов типа ROUTER и DEALER.


Расширенная форма конвертов для ответа

Рассмотрим, как работают пары сокетов REQ-REP через прокси (который использует сокеты ROUTER-DEALER), и как это влияет на форма конвертов для ответа. См. приложение из 16630307

В общем, совершенно никакой разницы, сколько прокси - ноль, один, два или больше:
Код: plaintext
1.
2.
3.
 [REQ] <-> [REP]
[REQ] <-[ROUTER|DEALER]-> [REP]
[REQ] <-[ROUTER|DEALER]<-[ROUTER|DEALER]-> [REP] 

Вот псевдокод того, что делает прокси:

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
prepare context, frontend and backend sockets
while true:
    poll on both sockets
    if frontend had input:
        read all frames from frontend
        send to backend
    if backend had input:
        read all frames from backend
        send to frontend



Просто код (не всевдо).
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
  while true do begin
    zmq_poll(fZMQPoll[0], Length(fZMQPoll), -1);
      // Проверка состояния сокетов из пула
    if (fZMQPoll[0].revents and ZMQ_POLLIN) <> 0 then
      while True do
      begin // Трансляция сообщний от клиента к сервису
      // Обработка всх частей сообщения
        zmq_msg_init(fMsg);
        zmq_msg_recv(fMsg, fSocketFrontEnd, 0);
        fDoMore := zmq_msg_more(fMSG) <> 0;
        zmq_msg_send(fMsg, fSocketBackEnd, IfThen(fDoMore, ZMQ_SNDMORE, 0));
        zmq_msg_close(fMsg);
        if not fDoMore then
          Break; // Это была последняя часть сообщения
      end;
    if (fZMQPoll[1].revents and ZMQ_POLLIN) <> 0 then
      while True do
        // Трансляция сообщний от сервиса к клиенту
      begin // Обработка всх частей сообщения
        zmq_msg_init(fMsg);
        zmq_msg_recv(fMsg, fSocketBackEnd, 0);
        fDoMore := zmq_msg_more(fMSG) <> 0;
        zmq_msg_send(fMsg, fSocketFrontEnd, IfThen(fDoMore, ZMQ_SNDMORE, 0));
        zmq_msg_close(fMsg);
        if not fDoMore then
          Break; // Это была последняя часть сообщения
      end;
  end;





Так вот. Сокет ROUTER, в отличии от всех остальных, отслеживает каждое входящее соединение и сообщает об этом вызывающей стороне. Вызывающая сторона получает уведомление, что она должна проверять идентификатор каждого входящего сообщения, который будет следовать перед сообщением. Идентификатор, который иногда называют адресом, представляет собой всего-навсего двоичную строку, несущую единственную нагрузку: "Это - уникальный дескриптор связи". После этого, когда приложение отправляет сообщение через сокет ROUTER, первым отправляется кадр идентификации.

Вот что сказано об этом в документации по zmq_socket() :
...из документации по zmq_socket() Перед передачей в приложение все сообщения, принимаемые сокетом ZMQ_ROUTER , должны предваряться частью собщения, идентифицирующей вызывающего корреспондента. Сообщения принимаются от всех подключенных корреспондентов в соответствии с алгоритмом справедливой очереди (fair-queued) . При отправке сообщения, сокет ZMQ_ROUTER должен удалить первую часть сообщения и использовать её для идентификации корреспондента, которому должно быть доставлено сообщение.

В качестве идентификаторов ZMQ v2.* использовала UUID, а начиная с V3.0 используются короткие целые. Изменения улучшили производительность сети, правда, только в случае использования множества прокси - ретрансляторов (что бывает крайне редко).

Сокет ROUTER для каждого соединения генерирует случайное число. То есть, когда к сокету ROUTER в прокси подключается три клиента сокетами REQ, генерируется три случайных числа, по одному на каждый сокет REQ.


Итак, начнем разрабатывать поясняющий пример. Предположим, у сокета REQ трехбатовый идентификатор "ABC". Это подразумевает, что внутренние механизмы сокета ROUTER содержат хэш-таблицу, в которой ищется строка "ABC" и соответствующее данному сокету REQ соединение TCP.
При получении сообщения от сокета ROUTER socket, мы получаем три кадра:
Запрос с одним адресомНомер кадра Длина Содержание Описание13ABCИдентификатор соединения20Пустой разделяющий кадр35HelloКадр с данными


Ядро прокси в цикле просто читает данные из одного сокета и передает в другой, то есть буквально эти три кадра и попадают на вход сокета DEALER. Прослушка сетевого трафика показала бы, что эти три фрейма пролетают из сокета DEALER в REP. Сокет REP делает все так же, как было описано ранее: срезает с конверта все, включая новый обратный адрес и передает "Hello" абоненту - получателю.

Следует отметить, что сокет REP одновременно может работать только одним циклом запрос - ответ. Поэтому, если вы попытаетесь прочитать несколько запросов или оправить несколько ответов, строго не придерживаясь цикла "Запрос-Ответ", сокет вернет ошибку.

Теперь вполне понятен и обратный путь сообщения. Когда сервис возвращает ответ, сокет REP заворачивает его в "сохраненный" (при "вскрытии") конверт, и отправляет ответ из трех кадров через сокет DEALER.

Ответ с одним адресомНомер кадра Длина Содержание Описание13ABCИдентификатор соединения20Пустой разделяющий кадр35WorldКадр с данными

Далее сокет DEALER читает эти три фрейма и отправляет через сокет ROUTER. Сокет ROUTER берет первый кадр сообщения с идентификатором "ABC" и находит коннект, связанный с ним. Ели коннект найден, наружу перекачивается следующие два кадра - уже знакомый минимальный конверт

№ кадраДлинаСодержание1025World

Сокет REQ принимает сообщение, проверяет, что первым кадром идет пустой разделитель, отбрасывает кадр и передает в приложение "World"...

Все просто.

Ну и что?

Следует признать, что вот такие простые схемы "Запрос - Ответ" или даже "Запрос - Ответ с брокером" не так уж часто применяются. Кроме того, нет простого способа восстановления системы после таких вещей как падение сервера (например, из-за бажного кода).

Методы построения надежных схем Запрос -Ответ будут рассмотрены позже.

Сейчас разберемся, как отважная четверка сокетов (REQ-REP-ROUTER-DEALER) борется с конвертами. Это позволит делать всякие полезные вещи.

Итак, мы поняли, что сокет ROUTER использует конверты для обратной пересылки, чтобы определить, какому из клиентских сокетов REQ следует направить обратный ответ. Или, иными словами:

- Каждый раз сокет ROUTER принимает сообщение, оно с помощью идентификатора сообщает вам, какой корреспондент это сообщение прислал.
- Вы можете воспользоваться хэш- таблицей (когда идентификатор - ключевая строка), чтобы отследить вновь подключенного абонента.
- Сокет ROUTER будет асинхронно, циклически обрабатывать всех корреспондентов, подключившихся к нему, если префикс - идентификатор идентичен первому кадру сообщения.
Сокеты ROUTER не обрабатывают конверт полностью. Они ничего не знают о пустых разделителях. Все ,что они делают - это работа с кадром идентификации, который позволяет выяснить, в какой из коннектов следует дальше отправить сообщение.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38767113
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Что мы знаем про сокеты из схемы "Запрос - Ответ"


- Сокеты REQ перед сообщением отправляют в сеть пустой фрагмент-разделитель. Сокеты REQ - синхронные. Сокеты REQ всегда посылают один запрос и всегда ждут одного ответа. Сокеты REQ одновременно общаются только с одним корреспондентом. Если вы подключаете сокет REQ к некольким абонентам, запросы будет отправлены, а ответы получены по одному корреспонденту в каждом цикле запрос - ответ.

- Сокет REP читает и сохраняет все кадры идентификации до пустого разделительного кадра включительно, затем передает следующий кадр или кадры абоненту. Сокеты REP - синхронные и одновременно общаются только с одним абонентом. Если вы подключаете сокет REP ко множеству абонентов, запросы читаются от корреспондентов последовательно по кругу (in fair fashion), а ответы всегда будут отправляться тому же самому корреспонденту, от которого был последний запрос.

- Сокет DEALER не обращает на конверт возврата никакого внимания и обрабатывает его как любое составное сообщение. Сокеты DEALER - асинхронные и похожи на комбинацию сокетов PUSH и PULL. Они распределяют отправляемые сообщения по всем соединениям, и принимают сообщения от всех соединений по алгоритму справедливой очереди.

- Сокет ROUTER не обращает внимания на конверт возврата, как и сокет DEALER. Он создает идентификаторы своих соединений, и передают эти идентификаторы корреспондентам в виде первого кадра каждого входящего соединения. И наоборот, когда корреспондент отправляет сообщение, он использует первый кадр как идентификатор, чтобы найти соединение для отправки. Сокет ROUTERS - асинхронный.


Допустимые комбинации сокетов

От (connect) Направление К(bind) REQ-->REP DEALER--> REP REQ-->ROUTER DEALER-->ROUTER DEALER-->DEALER ROUTER-->ROUTER

Недопустимые комбинации:

От (connect) Направление К(bind) REQ-->REQ REQ-->DEALER REP-->REP REP-->ROUTER
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38767118
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
"Запрос - Ответ", рабочие комбинации сокетов.
Некоторые особенности.

REQ->REP
Мы уже рассматривали, как клиент REQ общается с сервером REP. Одно замечание: клиент REQ должен быть инициатором потока сообщений. Сервер REP не может начать общение с клиентом REQ, пока тот не пришлет запрос.

DEALER->REP

Далее. Меняем в клиенте сокет REQ на сокет DEALER. Это дает асинхронного клиента, который может общаться с множеством серверов REP. Если перепишем клиента "Hello World", используя сокет DEALER, мы сможет отправлять любое число запросов "Hello" без ожидания ответов.

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

- Отправляем пустой кадр с установленным флагом "MORE".
- Затем отправляем тело сообщения.

Ну, а когда принимаем сообщение, то:

- принимаем первый кадр и, если он не пуст - отбрасываем все сообщение;
- принимаем следующий кадр и передаем его в приложение.

REQ -> ROUTER

Можем заменить не только REQ на DEALER, но и REP на ROUTER. Это дает нам асинхронный сервер, который может общаться с множеством REQ клиентов одновременно. Если мы сервер перепишем "Hello World", используя сокет ROUTER, мы сможем обрабатывать параллельно любое число запросом "Hello". Мы это уже делали.

Есть два различных способа использования сокетов ROUTER::

- как прокси, который переключает сообщения между сокетами frontend и backend.
- как приложение, которое читает сообщения и реагирует на них.

В первом случае сокет ROUTER просто читает все кадры, включая искусственный кадр с идентификатором, а затем вслепую передает их
Во втором случае сокет ROUTER должен знать формат конверта возврата, который он отправляет... Так как второй сокет в паре типа REQ то сокет ROUTER получает кадр идентификации, пустой кадр-разделитель и затем - кадр данных.

DEALER -> ROUTER

Теперь заменим сразу оба сокета - и REQ, и REP на DEALER и ROUTER. Получам самую мощную комбинацию сокетов, в которой DEALER общается с ROUTER. Это дает нам асинхронного клиента, общающегося с асинхронным сервером, когда обе стороны имеют полный контроль над форматом сообщений.

Так как и DEALER и ROUTER могут работать с сообщениями любого фората, вам придется "немного" поработать в качестве проектировщика протокола. Как минимум, вы должны решить - будете ли вы эмулировать конверты возврата REQ/REP. Все зависит от того, нужно ли вам в действительности отправлять ответы или нет.

DEALER -> DEALER

Вы можете поменять не только REP на ROUTER, но и REP на DEALER, если DEALER общается с одним и только одним корреспондентом.

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

ROUTER -> ROUTER

Название пары звучит так, как будто она идеальна для организации соединений N-to-N, но в действительности это наиболее сложная для использования комбинация. В руководстве рекомендуют избегать её, пока не станете докой в ZeroMQ.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38767431
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Для дальнейшей работы понадобится инструмент для более детального просмотра сообщений, поступающих в сокет.

Создадим процедуру s_dump(), которая будет читать все части сообщения из сокета и выводить их в консоль:

Код: pascal
1.
procedure s_dump(aSocket: Pointer);



...и добавим её в наш вспомогательны модуль ZMQ_utils:

ZMQ_utils.pas
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
unit ZMQ_Utils;
interface
// Возвращает длину отправленного сообщения в байтах
function s_recv(aZMQSocket: Pointer; aFlags: integer = 0): string;

// Отправляет строку Delphi в сокет. Возвращает число отправленных байт.
function s_send(aZMQSocket: Pointer; const aSrcString: string;
  aFlags: integer = 0): integer;

// Читает из сокета сообщение и показывает его в консоли с разбивкой по кадрам
procedure s_dump(aSocket: Pointer);

implementation
uses SysUtils, ZMQ;

//  Читает строку ZMQ из сокета и преобразует её в строку Delphi
//  В сокете должна быть именно строка Delphi. Возвращает пустую строку
//  если контекст ZMQ был завершен.

function s_recv(aZMQSocket: Pointer; aFlags: integer = 0): string;
var
  fLen: Integer;
  fZMQMsg: zmq_msg_t;
begin

  Result := '';
  try
    zmq_msg_init(fZMQMsg);
    fLen := zmq_msg_recv(fZMQMsg, aZMQSocket, aFlags);
    if fLen <= 0 then
      Exit;
    SetLength(Result, fLen div SizeOf(Char));
    Move(zmq_msg_data(fZMQMsg)^, Result[1], fLen div SizeOf(Char));
  finally
    zmq_msg_close(fZMQMsg);
  end;
end;

function s_send(aZMQSocket: Pointer; const aSrcString: string;
  aFlags: integer = 0): integer;
    // Возвращает длину отправленного сообщения в байтах
var
  fZMQMsg: zmq_msg_t;
begin
  zmq_msg_init(fZMQMsg);
  if Length(aSrcString) > 0 then begin
    zmq_msg_init_size(fZMQMsg, Length(aSrcString) * SizeOf(Char));
    Move(PChar(aSrcString)^, zmq_msg_data(fZMQMsg)^, Length(aSrcString) *
      SizeOf(Char));
  end;
  Result := zmq_msg_send(fZMQMsg, aZMQSocket, aFlags);
end;

procedure s_dump(aSocket: Pointer);
var
  fZMQMsg: zmq_msg_t;
  fSize: Integer;
  fIsText: Boolean;
  i: Integer;
  fData: PChar;
  fMore: UInt64;
  fMoreSize: size_t;
begin
  Writeln('----------------------------------------');
  while true do begin
        //  Обработка всех частей сообщения fZMQMsg
    zmq_msg_init(fZMQMsg);
    fSize := zmq_msg_recv(fZMQMsg, aSocket, 0);

        //  Вывод сообщения fZMQMsg в виде текста или hex
    fData := zmq_msg_data(fZMQMsg);
    fIsText := True;
    for i := 0 to Pred(fSize) do
      if not (fData[i] in [#32..#127]) then begin
        fIsText := False;
        break
      end;
    Write(Format('[%3u] ', [fSize]));
    for i := 0 to Pred(fSize) do begin
      if (fIsText) then
        Write(fData[i])
      else
        Write(Format('%.2x ', [Integer(fData[i])]))
    end;
    Writeln;
    fMore := 0; //  Сообщение составное?
    fMoreSize := sizeof(fMore);
    zmq_getsockopt(aSocket, ZMQ_RCVMORE, @fMore, fMoreSize);
    zmq_msg_close(fZMQMsg);
    if fMore = 0 then
      break; //  Последняя часть сообщения
  end;
end;

end.



Пригодится, когда начнем разбирать "конверты".
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38767439
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сокеты ROUTER , более подробно.
Концепция идентификаторов и адресов

- Идентификация сообщений в ZMQ касается только сокетов ROUTER.
- В широком смысле идентификатор представляет собой обратный адрес для ответа в составе конверта сообщений. В большинстве случаев, идентификаторы являются случайным значением, и они локальны в рамках одного сокета ROUTER. Идентификатор - ключевое значение для поиска в хэш-таблице.
- Независимо от значений идентификаторов, соединения могут иметь физические адреса ("tcp://192.168.55.117:5670"), или логические (UUID или email адрес или любое уникальное ключевое значение).

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

Пример крошечного приложения, в котором сообщения пересылаются по inproc протоколу от сокетов REQ к сокету ROUTER:

Пример идентификации
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
program IC;

{$APPTYPE CONSOLE}

uses
  SysUtils, ZMQ, ZMQ_Utils;
var
  fContext: Pointer;
  fSocketSink: Pointer;
  fSocketAnonymous: Pointer;
  fSocketIdentifier: Pointer;
begin
  fContext := zmq_ctx_new();
  fSocketSink := zmq_socket(fContext, ZMQ_ROUTER);
    // Настройка приемника
  zmq_bind(fSocketSink, 'inproc://example');

// 1. Разрешаем 0MQ установить идентификатор
  fSocketAnonymous := zmq_socket(fContext, ZMQ_REQ);
  zmq_connect(fSocketAnonymous, 'inproc://example');
  s_send(fSocketAnonymous, 'ROUTER uses a generated UUID');
  s_dump(fSocketSink); // Смотрим, что на выходе приемника

// 2. Устанавливаем идентификатор самостоятельно
  fSocketIdentifier := zmq_socket(fContext, ZMQ_REQ);
  zmq_setsockopt(fSocketIdentifier, ZMQ_IDENTITY, PChar('PEER2'), 5); //!
  zmq_connect(fSocketIdentifier, 'inproc://example');
  s_send(fSocketIdentifier, 'ROUTER socket uses REQ''s socket identity');
  s_dump(fSocketSink); // Смотрим, что на выходе приемника

  zmq_close(fSocketSink);
  zmq_close(fSocketAnonymous);
  zmq_close(fSocketIdentifier);
  zmq_ctx_destroy(fContext);
  Readln;
end.



Создается сокет - приемник ROUTER, к которому коннектятся два сокета REQ. Второму сокету перед коннектом назначется идентификатор 'PEER2'.
Смотрим, что же получилось на выходе приемника.

Видно, что каждое сообщение состоит из трех кадров: идентификатор, пустой кадр - разделитель, и кадр с данными:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38767446
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Обработка ошибок сокета ROUTER .

Ну, не то чтобы ошибок.

Если сокет ROUTER не может определить, куда отправить сообщения, он просто удаляет их. Для реальных приложений это, наверное, правильно (клиент отвалился - что тут поделаешь?), но это затрудняет отладку - особенно если обратный конверт для сокета ROUTER формируется "ручками".

Начиная с ZeroMQ v3.2, у сокетов появилась опция для перехвата ошибок: ZMQ_ROUTER_MANDATORY. Устанавливаем ей для сокета ROUTER получаем возможность отловить ситуацию, когда индентификации недостаточно для работы сокета ROUTER, сокет будет сигнализировать ошибкой EHOSTUNREACH .
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38768218
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Раз уж выяснилось ( 16666035 ), что Write/Writeln небезопасны в потоках, добавим безопасную функцию вывода строки в консоль. Снова расширим ZMQ_Utils.pas.

Код: pascal
1.
2.
// "Потокобезопасно" выводит строку в консоль
procedure z_Log(const aStr : string);



Ну и для работы с текущей темой добавим еще пару функций:

Код: pascal
1.
2.
3.
4.
5.
// Формирует строку указанной длины со случайным заполнением
function s_random(aLen: Integer): string;

//  Устанавливает случайный текстовый идентификатор для сокета
procedure s_set_id(aSocket: Pointer);



ZMQ_Utils.pas
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
unit ZMQ_Utils;
interface
// Возвращает длину отправленного сообщения в байтах
function s_recv(aZMQSocket: Pointer; aFlags: integer = 0): string;

// Отправляет строку Delphi в сокет. Возвращает число отправленных байт.
function s_send(aZMQSocket: Pointer; const aSrcString: string;
  aFlags: integer = 0): integer;

// Читает из сокета сообщение и показывает его в консоли с разбивкой по кадрам
procedure s_dump(aSocket: Pointer);

// Формирует строку указанной длины со случайным заполнением
function s_random(aLen: Integer): string;

//  Устанавливает случайный текстовый идентификатор для сокета
procedure s_set_id(aSocket: Pointer);

// "Потокобезопасно" выводит строку в консоль
procedure z_Log(const aStr : string);

implementation
uses SysUtils, ZMQ, Windows;

var
  cs: TRTLCriticalSection;

procedure z_Log(const aStr : string);
begin
  EnterCriticalSection(cs);
    Writeln(aStr);
  LeaveCriticalSection(cs);
end;

procedure s_set_id(aSocket: Pointer);
//  Устанавливает случайный текстовый идентификатор для сокета
begin
  zmq_setsockopt( aSocket, ZMQ_IDENTITY,  PChar(s_random(10)), 10 * SizeOf(Char));
end;

function s_random(aLen: Integer): string;
// Формирует случайную текстовую строку
const
  Chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
var
  s: String;
  i: integer;
begin
  Randomize;
  result := '';
  for i := 1 to aLen do
    result := result + Chars[Random(Length(Chars)) + 1];
end;



function s_recv(aZMQSocket: Pointer; aFlags: integer = 0): string;
//  Читает строку ZMQ из сокета и преобразует её в строку Delphi
//  В сокете должна быть именно строка Delphi. Возвращает пустую строку
//  если контекст ZMQ был завершен.
var
  fLen: Integer;
  fZMQMsg: zmq_msg_t;
begin

  Result := '';
  try
    zmq_msg_init(fZMQMsg);
    fLen := zmq_msg_recv(fZMQMsg, aZMQSocket, aFlags);
    if fLen <= 0 then
      Exit;
    SetLength(Result, fLen div SizeOf(Char));
    Move(zmq_msg_data(fZMQMsg)^, Result[1], fLen div SizeOf(Char));
  finally
    zmq_msg_close(fZMQMsg);
  end;
end;

function s_send(aZMQSocket: Pointer; const aSrcString: string;
  aFlags: integer = 0): integer;
    // Возвращает длину отправленного сообщения в байтах
var
  fZMQMsg: zmq_msg_t;
begin
  zmq_msg_init(fZMQMsg);
  if Length(aSrcString) > 0 then begin
    zmq_msg_init_size(fZMQMsg, Length(aSrcString) * SizeOf(Char));
    Move(PChar(aSrcString)^, zmq_msg_data(fZMQMsg)^, Length(aSrcString) *
      SizeOf(Char));
  end;
  Result := zmq_msg_send(fZMQMsg, aZMQSocket, aFlags);
end;

procedure s_dump(aSocket: Pointer);
var
  fZMQMsg: zmq_msg_t;
  fSize: Integer;
  fIsText: Boolean;
  i: Integer;
  fData: PChar;
  fMore: UInt64;
  fMoreSize: size_t;
begin
  Writeln('----------------------------------------');
  while true do begin
        //  Обработка всех частей сообщения fZMQMsg
    zmq_msg_init(fZMQMsg);
    fSize := zmq_msg_recv(fZMQMsg, aSocket, 0);

        //  Вывод сообщения fZMQMsg в виде текста или hex
    fData := zmq_msg_data(fZMQMsg);
    fIsText := True;
    for i := 0 to Pred(fSize) do
      if not (fData[i] in [#32..#127]) then begin
        fIsText := False;
        break
      end;
    Write(Format('[%3u] ', [fSize]));
    for i := 0 to Pred(fSize) do begin
      if (fIsText) then
        Write(fData[i])
      else
        Write(Format('%.2x ', [Integer(fData[i])]))
    end;
    Writeln;
    fMore := 0; //  Сообщение составное?
    fMoreSize := sizeof(fMore);
    zmq_getsockopt(aSocket, ZMQ_RCVMORE, @fMore, fMoreSize);
    zmq_msg_close(fZMQMsg);
    if fMore = 0 then
      break; //  Последняя часть сообщения
  end;
end;

initialization
  InitializeCriticalSection( cs );

finalization
  DeleteCriticalSection( cs );

end.


...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38768260
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Шаблон Балансировка Нагрузки

Рассмотрим простой код (см. ниже). Видно, как сокет ROUTER коннектится к сокету REQ, а затем к сокету DEALER. Эти два примера следуют одной и той же логике, соотвествующей шаблону "Балансировка Нагрузки" . Этот шаблон был использован, когда мы впервые применили сокет ROUTER для маршрутизации сообщений, а не просто как средство для ответа на запрос.

Шаблон "Балансировка Нагрузки" чрезвычайно популярен. Он решает главную проблему, когда простые алгоритмы последовательной круговой(round robin) маршртизации (которые обеспечивают PUSH и DEALER) становятся неэффективными. Такое случается, когда для выполнения задач, решаемых рабочими процессами, требуется разное время.

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

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

Одной из причин использования упрощенного подхода в сокетах PUSH и DEALER является производительность. Если вы прибываете в любой из главных аэропортов США, вы увидите несколько длинных очередей людей, ожидающих оформления процедуры иммиграции. Пограничники заранее отправляют людей к очереди конкретного служащего, а не заставляют всех стоять в одной общей очереди. Заставив людей заранее пройти полсотни метров, реально экономится 1-2 минут на обслуживание каждого пассажира. А так как проверка каждого паспорт занимает примерно одинаковое время, это обеспечивает что-то вроде справедливого обслуживания. Это и есть стратегия для сокетов PUSH и DEALER: распределить нагрузку заранее так, чтобы уменьшить расстояние для перемещения сообщения.

То есть, метод обслуживания в аэропорту отличается от метода в почтовом офисе.

Рассмотрим сценарий, когда рабочий процесс (сокетом DEALER или REQ) подключается к брокеру (к сокету ROUTER). Брокер знает, когда рабочий процесс готов к обслуживанию, и хранит список рабочих процессов. Поэтому он может всегда определить, какой из рабочих процессов наиболее редко использовался.

Решение задачи очень просто: рабочий посылает сообщение "готов" после того, как стартует, а также всякий раз после выполнения очередной задачи. Брокер читает сообщения последовательно, одно за другим. Понятно, что всякий раз, когда сообщение прочитано, оно принято от рабочего процесса, использованного последним . И так как используется сокет ROUTER, в начале конверта сообщения содержится идентификатор, который позволяет оправить задачу рабочему процессу обратно.

Цикл запрос - ответ необходим, так как задание отправляется с ответом, а любой ответ на задание отправляется как новый запрос.

Следующий пример понятно демонстрирует описанное:

Основной поток приложения создает "слушающий" сокет ROUTER. Затем создается 10 рабочих потоков, каждый поток создает сокет REQ и коннектится к сокету ROUTER основного потока. Каждый рабочий поток в цикле посылает сообщение "Я готов!", потом получает задание и выполняет его, пока не получит сообщение "Свободен!".
Основной поток в цикле читает конверт сообщения из сокета ROUTER. Первый кадр сообщения - идентификатор потока, который его прислал. Значит, этот поток готов к выполнению работы, формируется обратный конверт и сообщение отправляется обратно.
Если задача выполнялась больше 5 секунд, основной поток отправляет сообщение "Fire!".

Код приложения, поясняющего идею балансировки нагрузки
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
program RouterToReq;

{$APPTYPE CONSOLE}

uses
  SysUtils, ZMQ, ZMQ_Utils, Windows;

const
  C_NMBR_WORKERS = 10;

procedure worker_task(args: Pointer);
var
  fContext: Pointer;
  fSocketWorker: Pointer;
  fTotal: Integer;
  fWorkload: Utf8String;
begin
  fContext := zmq_ctx_new();
  fSocketWorker := zmq_socket(fContext, ZMQ_REQ);
  // Устанавливает случайное текстовое значение идентификатора для сокета
  s_set_id(fSocketWorker);
  zmq_connect(fSocketWorker, 'tcp://localhost:5671');

  fTotal := 0;
  while true do
  begin
// Сообщаем брокеру, что поток готов к работе
    s_send(fSocketWorker, 'Hi Boss');

// Получаем рабочее задание от брокера, пока не будет команды на прекращение
    fWorkload := s_recv(fSocketWorker);
    if fWorkload = 'Fired!' then
    begin
      z_Log(Format('Completed: %d tasks', [fTotal]));
      break;
    end;

    sleep(random(500) + 1); // Выполнение какой-то "полезной" работы
    Inc(fTotal);
  end;
  zmq_close(fSocketWorker);
  zmq_ctx_destroy(fContext);
end;

var
  fContext: Pointer;
  fSocketBroker: Pointer;
  i: Integer;
  fThrId: Cardinal;
  fFrequency: Int64;
  fStart: Int64;
  fStop: Int64;
  fDT: Int64;
  fWrkThreadsFired: Integer;
  fStrIdentity: string;
  fDummy: string;
begin
  fContext := zmq_ctx_new();
  fSocketBroker := zmq_socket(fContext, ZMQ_ROUTER);

  zmq_bind(fSocketBroker, 'tcp://*:5671');
  Randomize;

  // Запускаем пять рабочих потоков
  for i := 0 to Pred(C_NMBR_WORKERS) do
    BeginThread(nil, 0, @worker_task, nil, 0, fThrId);

 // Засекаем время
  QueryPerformanceFrequency(fFrequency);
  QueryPerformanceCounter(fStart);

// В течении пяти секунд шлем задания, а затем посылаем сообщение, чтобы остановились
  fWrkThreadsFired := 0;
  while true do
  begin

  // Следующее сообщение возвращает поток, первый выполнивший задание
    fStrIdentity := s_recv(fSocketBroker); // Кадр с идентификатором (фактически - обратный адрес)

    // Формируем конверт для ответа
    s_send(fSocketBroker, fStrIdentity, ZMQ_SNDMORE); // Первый кадр - обратный адрес

    fDummy :=  s_recv(fSocketBroker); // Пустой кадр - разделитель конверта
    s_send(fSocketBroker, '', ZMQ_SNDMORE); // Пустой кадр - разделитель конверта

    fDummy :=  s_recv(fSocketBroker); // Ответ от рабочего потока, тоже игнорируем


    QueryPerformanceCounter(fStop);
    fDT := (MSecsPerSec * (fStop - fStart)) div fFrequency;

    if fDT < 5000 then
      s_send(fSocketBroker, 'Work harder') // Шлем задание
    else begin
      s_send(fSocketBroker, 'Fired!'); // Команда на остановку
      Inc(fWrkThreadsFired);
      if fWrkThreadsFired = C_NMBR_WORKERS then
        break;
    end;
  end;
  zmq_close(fSocketBroker);
  zmq_ctx_destroy(fContext);
  readln;
end.





На выходе сокета ROUTER формируется конверт со следующей структурой:

№ кадраДлинаСодержаниеОписание11002947ws5fw Идентификатор отправителя20Пустой кадр - разделитель37Hi Boss Сигнал готовности рабочего потока

Видно, что кадры ответного сообщения формируется и отправляются параллельно со считыванием кадров входящего сообщения.
Это сделано, чтобы лишний раз напомнить: сокет ROUTER - асинхронный.

ИМХО, сообщения лучше читать кадр за кадром, логически отделяя процесс приема от процесса передачи. Вот так, например:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
  // Следующее сообщение возвращает поток, первый выполнивший задание
    fStrIdentity := s_recv(fSocketBroker); // Кадр с идентификатором (фактически - обратный адрес)
    fDummy :=  s_recv(fSocketBroker); // Пустой кадр - разделитель конверта
    fDummy :=  s_recv(fSocketBroker); // Ответ от рабочего потока, тоже игнорируем

    // Формируем конверт для ответа
    s_send(fSocketBroker, fStrIdentity, ZMQ_SNDMORE); // Первый кадр - обратный адрес
    s_send(fSocketBroker, '', ZMQ_SNDMORE); // Пустой кадр - разделитель конверта

    QueryPerformanceCounter(fStop);
    fDT := (MSecsPerSec * (fStop - fStart)) div fFrequency;

    if fDT < 5000 then
      s_send(fSocketBroker, 'Work harder') // Шлем задание
    else begin
      s_send(fSocketBroker, 'Fired!'); // Команда на остановку
      Inc(fWrkThreadsFired);
      if fWrkThreadsFired = C_NMBR_WORKERS then
        break;
    end;


...

После запуска приложение усиленно что-то делает 5 секунд, потом каждый поток отчитывается о количестве задач, которые успел выполнит за эти 5 секунд
Вывод программы:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38768339
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Брокер на сокете ROUTER , рабочие процессы - на сокете DEALER

Везде, где можно использовать сокет REQ, можно использовать DEALER. При этом следует учесть отличия:
- Сокет REQ всегда отправляет пустой кадр - разделитель конверта перед любыми кадрами с данными. Сокет DEALER так не делает.
- Сокет REQ всегда отправляет одно сообщение перед тем, как принимает ответ. Сокет DEALER - полностью асинхронный.

В нашем примере нет никакой разницы - синхронная работа или асинхронная, так как придерживаемся строгой последовательности "запрос - ответ". Это станет важно позднее, когда мы займемся вопросами восстановления после сбоев.

Рассмотрим такой же пример, как и предыдущий, но заменим сокет REQ на сокет DEALER:

Брокер на сокете ROUTER, рабочий процесс - на сокете Dealer
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
program RouterToDealer;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ZMQ,
  ZMQ_Utils,
  Windows;

const
  C_NMBR_WORKERS = 10;

procedure worker_task(args: Pointer);
var
  fContext: Pointer;
  fDummy: string;
  fSocketWorker: Pointer;
  fTotal: Integer;
  fWorkload: Utf8String;
begin
  fContext := zmq_ctx_new();
  fSocketWorker := zmq_socket(fContext, ZMQ_DEALER);
  // Устанавливает случайное текстовое значение идентификатора для сокета
  s_set_id(fSocketWorker);
  zmq_connect(fSocketWorker, 'tcp://localhost:5671');

  fTotal := 0;
  while true do
  begin
// Сообщаем брокеру, что поток готов к работе
    s_send(fSocketWorker, '', ZMQ_SNDMORE); // Отправляем пустой кадр-разделитель конверта
    s_send(fSocketWorker, 'Hi Boss'); // Отправляем сообщение о готовности

// Получаем рабочее задание от брокера, пока не будет команды на прекращение
    fDummy := s_recv(fSocketWorker); // Пропускаем кадр - разделитель
    fWorkload := s_recv(fSocketWorker); // Получаем задание
    if fWorkload = 'Fired!' then
    begin
      z_Log(Format('Completed: %d tasks', [fTotal]));
      break;
    end;

    sleep(random(500) + 1); // Выполнение какой-то "полезной" работы
    Inc(fTotal);
  end;
  zmq_close(fSocketWorker);
  zmq_ctx_destroy(fContext);
end;

var
  fContext: Pointer;
  fSocketBroker: Pointer;
  i: Integer;
  fThrId: Cardinal;
  fFrequency: Int64;
  fStart: Int64;
  fStop: Int64;
  fDT: Int64;
  fWrkThreadsFired: Integer;
  fStrIdentity: string;
  fDummy: string;
begin
  fContext := zmq_ctx_new();
  fSocketBroker := zmq_socket(fContext, ZMQ_ROUTER);

  zmq_bind(fSocketBroker, 'tcp://*:5671');
  Randomize;

  // Запускаем десять рабочих потоков
  for i := 0 to Pred(C_NMBR_WORKERS) do
    BeginThread(nil, 0, @worker_task, nil, 0, fThrId);

 // Засекаем время
  QueryPerformanceFrequency(fFrequency);
  QueryPerformanceCounter(fStart);

// В течении пяти секунд шлем задания, а затем посылаем сообщение, чтобы остановились
  fWrkThreadsFired := 0;
  while true do
  begin

  // Следующее сообщение возвращает поток, первый выполнивший задание
    fStrIdentity := s_recv(fSocketBroker); // Кадр с идентификатором (фактически - обратный адрес)
    // Формируем конверт для ответа
    s_send(fSocketBroker, fStrIdentity, ZMQ_SNDMORE); // Первый кадр - обратный адрес

    fDummy :=  s_recv(fSocketBroker); // Пустой кадр - разделитель конверта
    fDummy :=  s_recv(fSocketBroker); // Ответ от рабочего потока, тоже игнорируем

    s_send(fSocketBroker, '', ZMQ_SNDMORE); // Пустой кадр - разделитель конверта

    QueryPerformanceCounter(fStop);
    fDT := (MSecsPerSec * (fStop - fStart)) div fFrequency;

    if fDT < 5000 then
      s_send(fSocketBroker, 'Work harder') // Шлем задание
    else begin
      s_send(fSocketBroker, 'Fired!'); // Команда на остановку
      Inc(fWrkThreadsFired);
      if fWrkThreadsFired = C_NMBR_WORKERS then
        break;
    end;
  end;
  zmq_close(fSocketBroker);
  zmq_ctx_destroy(fContext);
  readln;
end.




Этот код почти такой же, как предыдущий, за исключением того, что рабочие процессы используют сокет DEALER и читают и пишут пустой кадр перед кадром данных. Таким образом, сохранена совместимость с вариантом рабочих процессов на сокетах REQ.

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

Если сообщение никогда не будет передано к сокету REP, то для простоты мы можем с обоих сторон просто отбросить пустой кадр - разделитель. Что, в общем, часто и делается.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38768452
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Брокер сообщений с балансировкой нагрузки

Предыдущий пример был завершен лишь наполовину. Брокер может управлять множеством рабочих процессов с фиктивными запросами и ответами, но не может общаться с клиентами. Если мы добавим еще один frontend ROUTER сокет, который принимает запросы клиентов, и превратим наш пример в настоящий прокси, который может передавать сообщения от frontend к backend, мы получим полезный и готовый для практического многократного использования крошечный брокер сообщений с балансировкой нагрузки.

Этот брокер делает следующее:
- принимает соединения от множества клиентов;
- принимает соединения от множества рабочих процессов;
- принимает запросы от клиентов и хранит их в общей очереди;
- отсылает эти запросы рабочим процессам в соответствии со схемой «Балансировка нагрузки»;
- обратно принимает ответы от рабочих процессов;

Исходник получается длинноват, но в нем стоит разобраться.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38771575
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вот он:
Брокер с балансировкой нагрузки
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
program LoadBalancingBroker;

{$APPTYPE CONSOLE}
// Брокер с балансировкой нагрузки.
//=================================
// Для упрощения запуска пример реализована как многопоточное приложение,
// с использованием протокола inproc
//=================================
// Имеется клиенты (c_NBR_CLIENTS шт) и рабочие (c_NBR_WORKERS шт).
// Каждый рабочий при запуске сообщает брокеру о своей готовности.
// Рабочий ставится в очередь.
//
// Клиент при запуске обращается к брокеру с заданием. Брокер отправляет
// задание первому свободному рабочему. Рабочий, выполнив задание, возвращает
// результат брокеру, брокер пересылает задание исходному клиенту



uses
  SysUtils, ZMQ, ZMQ_Utils, Windows;
const
  c_NBR_CLIENTS = 5; // Число клиентов
  c_NBR_WORKERS = 3; // // Число рабочих
  c_NMBR_REQ = 2; // // Число запросов от каждого клиента

  // Конечные точки подключения
  c_url_clients = 'inproc://clients';
  c_url_workers = 'inproc://workers';

procedure client_thread_proc(aContext: Pointer);
// Процедура потока клиента
var
  fSocketClient: Pointer;
  fReply: string;
  i: integer;
begin
  fSocketClient := zmq_socket(aContext, ZMQ_REQ);
  // Назначение идентификатора соединения (строка случайных символов)
  s_set_id(fSocketClient);
  zmq_connect(fSocketClient, c_url_clients); // Коннект к брокеру

  for i := 0 to Pred(c_NMBR_REQ) do begin
  // Отправка запроса, получение ответа
    s_send(fSocketClient, 'HELLO');
    fReply := s_recv(fSocketClient);
    z_Log(Format('Client : %s', [fReply]));
  end;
  zmq_close(fSocketClient);
end;


procedure worker_thread_proc(aContext: Pointer);
// Процедура рабочего потока
var
  fSocketWorker: Pointer;
  fIdentity: string;
  fEmpty: string;
  fRequest: string;
begin
  fSocketWorker := zmq_socket(aContext, ZMQ_REQ);
   // Назначение идентификатора соединения
  s_set_id(fSocketWorker);
  zmq_connect(fSocketWorker, c_url_workers); // Коннект к брокеру

  // Соощаем брокеру, что рабочий поток запущен и готов к работе
  s_send(fSocketWorker, 'READY');

  while true do // Рабочий цикл
  begin
    // Читаем и запоминаем все кадры вплоть пустого (fEmpty)
    // В данном примере кадров всего один, но реально их может быть больше
    fIdentity := s_recv(fSocketWorker); // Идентификатор клиента
    if zmq_errno() = ETERM then
      Break; // Уходим, если контекст в процессе завершения

    fEmpty := s_recv(fSocketWorker); // Кадр - разделитель
    Assert(fEmpty = '');
    // Получение запроса, отправка ответа
    fRequest := s_recv(fSocketWorker);
    z_Log(Format('Worker : %s', [fRequest]));

    Sleep(50); // Имитируем выполнение полезной работы

    // Формирование конверта составного сообщения:
    s_send(fSocketWorker, fIdentity, ZMQ_SNDMORE);
      // Идентификатор клиента
    s_send(fSocketWorker, '', ZMQ_SNDMORE); // Разделитель
    s_send(fSocketWorker, 'OK'); // Результат работы
  end;
  zmq_close(fSocketWorker);
end;


var
  fContext: Pointer;
  fSocketClients: Pointer;
  fSocketWorkers: Pointer;
  i: Integer;
  fThrId: Cardinal;
  fAvailableWorkers: Integer;
  fZMQPoll: array[0..1] of pollitem_t;
  fWrkrs_Que: array[0..Pred(c_NBR_WORKERS)] of string; // Очередь рабочих
  fRC: Integer;
  fWorker_id: string;
  fClient_id: string;
  fEmpty: string;
  fReplay: string;
  fRequest: string;
  fCliReqNmbr: Integer; // Номер клиентского запроса
  fThrWIds: array[0..Pred(c_NBR_WORKERS)] of Cardinal;
  fThrCIds: array[0..Pred(c_NBR_CLIENTS)] of Cardinal;
begin
 // Подготовка контекста и сокетов
  fContext := zmq_ctx_new();
  fSocketClients := zmq_socket(fContext, ZMQ_ROUTER);
  fSocketWorkers := zmq_socket(fContext, ZMQ_ROUTER);
  zmq_bind(fSocketClients, c_url_clients);
  zmq_bind(fSocketWorkers, c_url_workers);

  for i := 0 to Pred(c_NBR_WORKERS) do // Создаем рабочих
    fThrWIds[i] := BeginThread(nil, 0, @worker_thread_proc, fContext, 0, fThrId);

  for i := 0 to Pred(c_NBR_CLIENTS) do // Создаем клиентов
    fThrCIds[i] := BeginThread(nil, 0, @client_thread_proc, fContext, 0, fThrId);


// Главный цикл для LRU очереди. Используется два сокета: fSocketClients для
// клиентов и fSocketWorkers для рабочих. Опрос fSocketWorkers
// выполняется всегда, а fSocketClients - только тогда, когда есть один или
// больше готовых рабочих.
// Сообщения, которые еще не готовы к обработке, в ZMQ хранятся
// во встроенной очередей сообщений.
// Когда мы получаем запрос клиента, мы берем рабочего из начала
// очереди (fWrkrs_Que[0]) и посылаем ему запрос, которых включаеи исходный
// идентификатор клиента.
// Когда же приходит запрос от рабочего, этого рабочего ставим в конец очереди,
// а ответ переправляем исходному клиенту (используя идинтификатор в конверте).

  fAvailableWorkers := 0; // Число доступных рабочих

  fCliReqNmbr := 0;

  // Подготовка пула сокетов
  fZMQPoll[0].socket := fSocketWorkers;
  fZMQPoll[0].fd := 0;
  fZMQPoll[0].events := ZMQ_POLLIN;
  fZMQPoll[1].socket := fSocketClients;
  fZMQPoll[1].fd := 0;
  fZMQPoll[1].events := ZMQ_POLLIN;

  while fCliReqNmbr < c_NBR_CLIENTS * c_NMBR_REQ do begin

    fZMQPoll[0].revents := 0; // Сброс результатов опроса
    fZMQPoll[1].revents := 0;


    // Читать из fSocketClients только тогда, когда есть свободные рабочие
    // Если нет свободных - читать только из fSocketWorkers
    if fAvailableWorkers = 0 then
      fRC := zmq_poll(fZMQPoll[0], 1, 11)
        // Только ждем готовности от рабочих
    else
      fRC := zmq_poll(fZMQPoll[0], 2, 11); // Читаем оба сокета

    if fRC = -1 then
      Break; // Цикл прерван

    // Обрабока действий рабочих на fSocketWorkers
    if (fZMQPoll[0].revents and ZMQ_POLLIN) <> 0 then begin
      fWorker_id := s_recv(fSocketWorkers);
      Assert(fAvailableWorkers < c_NBR_WORKERS);
      // Помещаем рабочего в конец очереди
      fWrkrs_Que[fAvailableWorkers] := fWorker_id;
      Inc(fAvailableWorkers);

      // Второй кадр - пустой
      fEmpty := s_recv(fSocketWorkers);
      assert(fEmpty = '');

      // Третий кадр - готовность ("READY"), иначе это Id клиента в ответе
      fClient_id := s_recv(fSocketWorkers);

      // Если это ответ клиенту, отправить ответ в fSocketClients
      if fClient_id <> 'READY' then begin
        fEmpty := s_recv(fSocketWorkers);
        Assert(fEmpty = '');
        fReplay := s_recv(fSocketWorkers);
        s_send(fSocketClients, fClient_id, ZMQ_SNDMORE);
        s_send(fSocketClients, '', ZMQ_SNDMORE);
        s_send(fSocketClients, fReplay);
        Inc(fCliReqNmbr); // Число обслуженных запросов

      end;
    end;

    // Обработка запросов клиентов:
    if (fZMQPoll[1].revents and ZMQ_POLLIN) <> 0 then
    begin
      // Получаем очередной клиентский запрос, отправляем его рабочему из
      // начала очереди
      // Конверт запроса клиента: [identity][empty][request]
      fClient_id := s_recv(fSocketClients);
      fEmpty := s_recv(fSocketClients);
      Assert(fEmpty = '');
      fRequest := s_recv(fSocketClients);

      s_send(fSocketWorkers, fWrkrs_Que[0], ZMQ_SNDMORE);
      s_send(fSocketWorkers, '', ZMQ_SNDMORE);
      s_send(fSocketWorkers, fClient_id, ZMQ_SNDMORE);
      s_send(fSocketWorkers, '', ZMQ_SNDMORE);
      s_send(fSocketWorkers, fRequest);

      // Извлечение из очереди
      Dec(fAvailableWorkers);
      for i := 0 to Pred(fAvailableWorkers) do
        fWrkrs_Que[i] := fWrkrs_Que[i + 1];
    end;
  end;

  for I := 0 to High(fThrCIds) do // Ждем завершения клиентов
    WaitForSingleObject(fThrCIds[i], INFINITE);

  zmq_close(fSocketClients);
  zmq_close(fSocketWorkers);
  zmq_ctx_destroy(fContext);

//  for I := 0 to High(fThrWIds) do
//    WaitForSingleObject(fThrWIds[i], INFINITE);

  Readln;

end.



Исходник избыточно комментирован, поэтому добавлю только пару замечаний.

1. Обратить внимание на цикл рабочего потока worker_thread_proc():

Код: pascal
1.
2.
3.
4.
5.
6.
7.
  while true do // Рабочий цикл
  begin
    // Читаем и запоминаем все кадры вплоть пустого (fEmpty)
    // В данном примере кадров всего один, но реально их может быть больше
    fIdentity := s_recv(fSocketWorker); // Идентификатор клиента
    if zmq_errno() = ETERM then
      Break; // Уходим, если контекст в процессе завершения


Так как для связи между потоками используется протокол inproc, необходимо использовать общий контекст, который передается как параметр в процедуру потока.
Так вот, когда основной поток завершается, выполняется закрытие контекста ZMQ:
Код: pascal
1.
  zmq_ctx_destroy(fContext);


Следовательно, обращение к сокетам контекста вызовет ошибку ETERM, что в данном случае считается признаком необходимости завершения основного цикла процедуры потока.

2. Основным источником задач в примере является процедура потока клиентов client_thread_proc(). Следовательно, при завершении приложения есть смысл дождаться завершения потоков клиентского слоя. Для этого при создании потоков слоя клиентов запоминаются дескрипторы потоков, а при завершении процедуры выполняется ожидание завершения всех потоков клиентов:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
var
...
  fThrCIds: array[0..Pred(c_NBR_CLIENTS)] of Cardinal;
...
begin
...
  for i := 0 to Pred(c_NBR_CLIENTS) do // Создаем клиентов
    fThrCIds[i] := BeginThread(nil, 0, @client_thread_proc, fContext, 0, fThrId);
...
...
...
  for I := 0 to High(fThrCIds) do // Ждем завершения клиентов
    WaitForSingleObject(fThrCIds[i], INFINITE);

  zmq_close(fSocketClients);
  zmq_close(fSocketWorkers);
  zmq_ctx_destroy(fContext);
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38771740
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Немного поясню.

В алгоритме две сложные вещи:
а) упаковка сообщений в конверты при каждом чтении и записи;
б) сам алгоритм балансировки нагрузки


Рассмотрим весь путь сообщения схемы "Запрос - от клиента до рабочего процесса и обратно. В коде есть вызовы
Код: pascal
1.
2.
3.
4.
  
s_set_id(fSocketClient);
и
s_set_id(fSocketWorker);


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

Предположим, что идентификатором клиента будет строка "CLIENT", а рабочего - "WORKER". Предположим, что клиентское приложение посылает один кадр, содержащий "Hello".

Сообщение, отсылаемое клиентом:
№ кадраДлинаСодержаниеОписание15Hello Кадр с данными

Сокет REQ сам добавляет в начало пустой кадр - разделитель. Далее, сокет ROUTER добавляет идентификатор соединения. В итоге прокси читает уже адрес (идентификатор) клиента, пустой кадр и данные:

Сообщение, которое читает прокси с фронтэнд сокета ROUTER:
№ кадраДлинаСодержаниеОписание15ClientИдентификатор клиента20Пустой кадр - разделитель35Hello Кадр с данными
Брокер отправляет все это рабочему, предварив двумя кадрами: идентификаторо рабочего и разделителем.

Сообщение, которое брокер отправляет в бэкэнд сокета ROUTER:
№ кадраДлинаСодержаниеОписание16WorkerИдентификатор рабочего20Пустой кадр - разделитель35ClientИдентификатор клиента40Пустой кадр - разделитель55Hello Кадр с данными

Этот конверт распаковывается сначала бэкенд - сокетом ROUTER, который удаляет первый кадр и отправляет все сокету REQ рабочего. Потом сокет REQ рабочего удаляет пустой кадр, а остаток передает в приложение рабочего потока:

Сообщение, которое получает рабочий:
№ кадраДлинаСодержаниеОписание15ClientИдентификатор клиента20Пустой кадр - разделитель35Hello Кадр с данными

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

~~~~~~~~~~~~~~~~~

Теперь по поводу алгоритма балансировки нагрузки.

Со стороны и клиента, и рабочего используются сокеты REQ. Рабочий должен корректно получить, запомнить и правильно вернуть получаемые конверты . Алгоритм:

- создается пул сокетов. Пул всегда содержит бэкэнд сокет (сторона рабочего), и, если есть хоть один свободный рабочий - фронтэнд сокет (сторона клиента).

- пул активируется с бесконечным таймаутом;

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

- если есть активность со стороны фронтэнда (клиент), мы берем запрос клиента, извлекаем из очереди адрес очередного рабочего (который использовался последним), и отправляем запрос в бэкэнд (рабочему). В конверте теперь первый кадр - адрес рабочего, потом разделитель, потом все три части клиентского запроса.

Данная схема легко расширяется. Например, в процессе работы работники могли бы запускать счетчики производительности для оценки собственного быстродействия и отчитываться брокеру о результатах. А брокер мог бы выбирать самого быстрого из работников, использованных последними.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38775726
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQ, переходим на последнюю версию: 4.0.4

Здесь: 16562018 было сказано, что биндинг реализован для версий 2.* и 3.*.

Хотя уже год как есть 4я версия. В которой реализованы такие интересные вещи, как новый протокол уровня передачи, могут использоваться криптографические библиотеки и аутентификация коннекта а также добавлен новый тип сокета - ZMQ_STREAM (для работы в качестве TCP клиента или сервера).

Вот файлик, реализующий интерфейсы библиотеки libzmq.dll (используем его в uses вместо старого zmq.pas) :
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38775728
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
libzmq.dll была откомпилирована с помощью MS VS 2012 Update 4, с указанием, что целевая платформа - Windows XP (v110_XP).
То есть, работать будет на Windows XP и более новых.

Приложенные к библиотеке тесты отработали без проблем, и все примеры, рассмотренные ранее, также работают.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38775729
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сама библиотека. Распаковать и положить возле приложения. Больше для работы ничего не нужно.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38786201
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вернется к брокеру с балансировкой нагрузки (с учетом перехода на версию ZeroMQ 4.0.4): 16681363
Брокер с балансировкой нагрузки
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
program LoadBalancingBroker;

{$APPTYPE CONSOLE}
// Брокер с балансировкой нагрузки.
//=================================
// Для упрощения запуска пример реализована как многопоточное приложение,
// с использованием протокола inproc
//=================================
// Имеется клиенты (c_NBR_CLIENTS шт) и рабочие (c_NBR_WORKERS шт).
// Каждый рабочий при запуске сообщает брокеру о своей готовности.
// Рабочий ставится в очередь.
//
// Клиент при запуске обращается к брокеру с заданием. Брокер отправляет
// задание первому свободному рабочему. Рабочий, выполнив задание, возвращает
// результат брокеру, брокер пересылает задание исходному клиенту

uses
//  Fastmm4,
  Windows,
  SysUtils,
  zmq_h,
  ZMQ_Utils;

const
  c_NBR_CLIENTS = 5; // Число клиентов
  c_NBR_WORKERS = 3; // // Число рабочих
  c_NMBR_REQ = 2; // // Число запросов от каждого клиента

  // Конечные точки подключения
  c_url_clients = 'inproc://clients';
  c_url_workers = 'inproc://workers';

procedure client_thread_proc(aContext: Pointer);
// Процедура потока клиента
var
  fSocketClient: Pointer;
  fReply: string;
  i: integer;
begin
  fSocketClient := zmq_socket(aContext, ZMQ_REQ);
  // Назначение идентификатора соединения (строка случайных символов)
  s_set_id(fSocketClient);
  zmq_connect(fSocketClient, c_url_clients); // Коннект к брокеру

  for i := 0 to Pred(c_NMBR_REQ) do begin
  // Отправка запроса, получение ответа
    s_send(fSocketClient, 'HELLO');
    fReply := s_recv(fSocketClient);
    z_Log(Format('Client : %s', [fReply]));
  end;
  zmq_close(fSocketClient);
end;


procedure worker_thread_proc(aContext: Pointer);
// Процедура рабочего потока
var
  fSocketWorker: Pointer;
  fIdentity: string;
  fEmpty: string;
  fRequest: string;

begin
  fSocketWorker := zmq_socket(aContext, ZMQ_REQ);
   // Назначение идентификатора соединения
  s_set_id(fSocketWorker);
  zmq_connect(fSocketWorker, c_url_workers); // Коннект к брокеру

  // Соощаем брокеру, что рабочий поток запущен и готов к работе
  s_send(fSocketWorker, 'READY');

  while true do // Рабочий цикл
  begin
    // Читаем и запоминаем все кадры вплоть пустого (fEmpty)
    // В данном примере кадров всего один, но реально их может быть больше
    fIdentity := s_recv(fSocketWorker); // Идентификатор клиента
    if zmq_errno() = ETERM then
      Break; // Уходим, если контекст в процессе завершения

    fEmpty := s_recv(fSocketWorker); // Кадр - разделитель
    Assert(fEmpty = '');
    // Получение запроса, отправка ответа
    fRequest := s_recv(fSocketWorker);
    z_Log(Format('Worker : %s', [fRequest]));

    Sleep(5); // Имитируем выполнение полезной работы

    // Формирование конверта составного сообщения:
    s_send(fSocketWorker, fIdentity, ZMQ_SNDMORE);
      // Идентификатор клиента
    s_send(fSocketWorker, '', ZMQ_SNDMORE); // Разделитель
    s_send(fSocketWorker, 'OK'); // Результат работы
  end;
  zmq_close(fSocketWorker);
end;

procedure thread_proc(aContext: Pointer); cdecl;
var
  fVal: Integer;
begin
  fVal := Integer(aContext^);
  z_Log(IntToStr(fVal));
end;


var
  fContext: Pointer;
  fSocketClients: Pointer;
  fSocketWorkers: Pointer;
  i: Integer;
  fThrId: Cardinal;
  fAvailableWorkers: Integer;
  fZMQPoll: array[0..1] of zmq_pollitem_t;
  fWrkrs_Que: array[0..Pred(c_NBR_WORKERS)] of string; // Очередь рабочих
  fRC: Integer;
  fWorker_id: string;
  fClient_id: string;
  fEmpty: string;
  fReplay: string;
  fRequest: string;
  fCliReqNmbr: Integer; // Номер клиентского запроса
  fThrWIds: array[0..Pred(c_NBR_WORKERS)] of Cardinal;
  fThrCIds: array[0..Pred(c_NBR_CLIENTS)] of Cardinal;
  fP: Pointer;
begin
 // Подготовка контекста и сокетов
  fContext := zmq_ctx_new();
  fSocketClients := zmq_socket(fContext, ZMQ_ROUTER);
  fSocketWorkers := zmq_socket(fContext, ZMQ_ROUTER);
  zmq_bind(fSocketClients, c_url_clients);
  zmq_bind(fSocketWorkers, c_url_workers);

  for i := 0 to Pred(c_NBR_WORKERS) do // Создаем рабочих
    fThrWIds[i] := BeginThread(nil, 0, @worker_thread_proc, fContext, 0, fThrId);

  for i := 0 to Pred(c_NBR_CLIENTS) do // Создаем клиентов
    fThrCIds[i] := BeginThread(nil, 0, @client_thread_proc, fContext, 0, fThrId);


// Главный цикл для LRU очереди. Используется два сокета: fSocketClients для
// клиентов и fSocketWorkers для рабочих. Опрос fSocketWorkers
// выполняется всегда, а fSocketClients - только тогда, когда есть один или
// больше готовых рабочих.
// Сообщения, которые еще не готовы к обработке, в ZMQ хранятся
// во встроенной очередей сообщений.
// Когда мы получаем запрос клиента, мы берем рабочего из начала
// очереди (fWrkrs_Que[0]) и посылаем ему запрос, которых включаеи исходный
// идентификатор клиента.
// Когда же приходит запрос от рабочего, этого рабочего ставим в конец очереди,
// а ответ переправляем исходному клиенту (используя идинтификатор в конверте).

  fAvailableWorkers := 0; // Число доступных рабочих

  fCliReqNmbr := 0;

  // Подготовка пула сокетов
  fZMQPoll[0].socket := fSocketWorkers;
  fZMQPoll[0].fd := 0;
  fZMQPoll[0].events := ZMQ_POLLIN;
  fZMQPoll[1].socket := fSocketClients;
  fZMQPoll[1].fd := 0;
  fZMQPoll[1].events := ZMQ_POLLIN;

  while fCliReqNmbr < c_NBR_CLIENTS * c_NMBR_REQ do begin

    fZMQPoll[0].revents := 0; // Сброс результатов опроса
    fZMQPoll[1].revents := 0;


    // Читать из fSocketClients только тогда, когда есть свободные рабочие
    // Если нет свободных - читать только из fSocketWorkers
    if fAvailableWorkers = 0 then
      fRC := zmq_poll(@fZMQPoll, 1, 11)
        // Только ждем готовности от рабочих
    else
      fRC := zmq_poll(@fZMQPoll, 2, 11); // Читаем оба сокета

    if fRC = -1 then
      Break; // Цикл прерван

    // Обработка действий рабочих на fSocketWorkers
    if (fZMQPoll[0].revents and ZMQ_POLLIN) <> 0 then begin
      fWorker_id := s_recv(fSocketWorkers);
      Assert(fAvailableWorkers < c_NBR_WORKERS);
      // Помещаем рабочего в конец очереди
      fWrkrs_Que[fAvailableWorkers] := fWorker_id;
      Inc(fAvailableWorkers);

      // Второй кадр - пустой
      fEmpty := s_recv(fSocketWorkers);
      assert(fEmpty = '');

      // Третий кадр - готовность ("READY"), иначе это Id клиента в ответе
      fClient_id := s_recv(fSocketWorkers);

      // Если это ответ клиенту, отправить ответ в fSocketClients
      if fClient_id <> 'READY' then begin
        fEmpty := s_recv(fSocketWorkers);
        Assert(fEmpty = '');
        fReplay := s_recv(fSocketWorkers);
        s_send(fSocketClients, fClient_id, ZMQ_SNDMORE);
        s_send(fSocketClients, '', ZMQ_SNDMORE);
        s_send(fSocketClients, fReplay);
        Inc(fCliReqNmbr); // Число обслуженных запросов

      end;
    end;

    // Обработка запросов клиентов:
    if (fZMQPoll[1].revents and ZMQ_POLLIN) <> 0 then
    begin
      // Получаем очередной клиентский запрос, отправляем его рабочему из
      // начала очереди
      // Конверт запроса клиента: [identity][empty][request]
      fClient_id := s_recv(fSocketClients);
      fEmpty := s_recv(fSocketClients);
      Assert(fEmpty = '');
      fRequest := s_recv(fSocketClients);

      s_send(fSocketWorkers, fWrkrs_Que[0], ZMQ_SNDMORE);
      s_send(fSocketWorkers, '', ZMQ_SNDMORE);
      s_send(fSocketWorkers, fClient_id, ZMQ_SNDMORE);
      s_send(fSocketWorkers, '', ZMQ_SNDMORE);
      s_send(fSocketWorkers, fRequest);

      // Извлечение из очереди
      Dec(fAvailableWorkers);
      for i := 0 to Pred(fAvailableWorkers) do
        fWrkrs_Que[i] := fWrkrs_Que[i + 1];
    end;
  end;

  for I := 0 to High(fThrCIds) do // Ждем завершения клиентов
    WaitForSingleObject(fThrCIds[i], INFINITE);
  zmq_threadstart(thread_proc, @i);
  Sleep(1000);
  zmq_close(fSocketClients);
  zmq_close(fSocketWorkers);
  zmq_ctx_destroy(fContext);

//  for I := 0 to High(fThrWIds) do
//    WaitForSingleObject(fThrWIds[i], INFINITE);

  Readln;

end.



Как видим, кода довольно много, чтобы быстро во всем разобраться. Это связано с тем, что использовались вызовы API ZMQ низкого уровня.

Вот основной рабочий цикл:
Основной рабочий цикл брокера

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
  while fCliReqNmbr < c_NBR_CLIENTS * c_NMBR_REQ do begin

    fZMQPoll[0].revents := 0; // Сброс результатов опроса
    fZMQPoll[1].revents := 0;


    // Читать из fSocketClients только тогда, когда есть свободные рабочие
    // Если нет свободных - читать только из fSocketWorkers
    if fAvailableWorkers = 0 then
      fRC := zmq_poll(@fZMQPoll, 1, 11)
        // Только ждем готовности от рабочих
    else
      fRC := zmq_poll(@fZMQPoll, 2, 11); // Читаем оба сокета

    if fRC = -1 then
      Break; // Цикл прерван

    // Обрабока действий рабочих на fSocketWorkers
    if (fZMQPoll[0].revents and ZMQ_POLLIN) <> 0 then begin
      fWorker_id := s_recv(fSocketWorkers);
      Assert(fAvailableWorkers < c_NBR_WORKERS);
      // Помещаем рабочего в конец очереди
      fWrkrs_Que[fAvailableWorkers] := fWorker_id;
      Inc(fAvailableWorkers);

      // Второй кадр - пустой
      fEmpty := s_recv(fSocketWorkers);
      assert(fEmpty = '');

      // Третий кадр - готовность ("READY"), иначе это Id клиента в ответе
      fClient_id := s_recv(fSocketWorkers);

      // Если это ответ клиенту, отправить ответ в fSocketClients
      if fClient_id <> 'READY' then begin
        fEmpty := s_recv(fSocketWorkers);
        Assert(fEmpty = '');
        fReplay := s_recv(fSocketWorkers);
        s_send(fSocketClients, fClient_id, ZMQ_SNDMORE);
        s_send(fSocketClients, '', ZMQ_SNDMORE);
        s_send(fSocketClients, fReplay);
        Inc(fCliReqNmbr); // Число обслуженных запросов

      end;
    end;

    // Обработка запросов клиентов:
    if (fZMQPoll[1].revents and ZMQ_POLLIN) <> 0 then
    begin
      // Получаем очередной клиентский запрос, отправляем его рабочему из
      // начала очереди
      // Конверт запроса клиента: [identity][empty][request]
      fClient_id := s_recv(fSocketClients);
      fEmpty := s_recv(fSocketClients);
      Assert(fEmpty = '');
      fRequest := s_recv(fSocketClients);

      s_send(fSocketWorkers, fWrkrs_Que[0], ZMQ_SNDMORE);
      s_send(fSocketWorkers, '', ZMQ_SNDMORE);
      s_send(fSocketWorkers, fClient_id, ZMQ_SNDMORE);
      s_send(fSocketWorkers, '', ZMQ_SNDMORE);
      s_send(fSocketWorkers, fRequest);

      // Извлечение из очереди
      Dec(fAvailableWorkers);
      for i := 0 to Pred(fAvailableWorkers) do
        fWrkrs_Que[i] := fWrkrs_Que[i + 1];
    end;
  end;



Громоздко, да. И это мы еще использовали наши вспомогательные процедурки вроде s_recv()/s_send(), а без них бы пришлось пересылать сообщения ZMQ и заниматься упаковкой-распоковкой данных.

В общем, если использовать низкуровневое API в сложных задачах, то и кодировать долго, и потом разбираться непросто.

Нужно переходить к более высоким уровням абстракции.

Ранее упоминалась библиотека Delphi: https://github.com/bvarga/delphizmq

Из этой библиотеки мы использовали только модуль zmq.pas, который и обеспечивает API низкого уровня. Модуль zmqapi.pas предоставляет объектный интерфейс более высокого уровня в соответствии видением прекрвсного с создателя библиотеки и, надо полагать, в соответствии с задачами, которые стояли перед ним в момент написания.

К сожалению, больше года библиотека почти не обновляется, и зависла на поддержке ZeroMQ версий 2.* и 3.*.
~~~~~~~~~~~~

К счастью, выход есть: iMatrix (контора, которая и разрабатывает ZeroMQ) создала и развивает библиотеку API высокого уровня: http://czmq.zeromq.org/
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38786214
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
High Level API: http://czmq.zeromq.org/

Вот так на Delphi будет выглядеть "Hello, Worrld!"
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
program hl_HelloWorld;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  czmq_h;

var
  push: p_zsock_t;
  pull: p_zsock_t;
  fpStr: PChar;
begin

  push := zsock_new_push('inproc://example');
  pull := zsock_new_pull('inproc://example');

  zstr_send(push, 'Hello, World!');

  fpStr := zstr_recv(pull);
  Writeln(fpStr);

  zstr_free(fpStr);

  zsock_destroy(pull);
  zsock_destroy(push);

  Readln;
end.


Здесь пара сокетов: PUSH и PULL, взаимодействующих между собой по inproc - протоколу.
В сокет push посылается строка 'Hello, World!', которая принимается из сокета pull.

Как видим, строка здесь - "Сишная", то есть наш привычный PChar:
Код: pascal
1.
2.
3.
var
...
  fpStr: PChar;



Так сделано из-за того, что и эта библиотека (как и libzmq.dll) реализована в виде dll.
Более того, чтобы избежать возможных проблем с менеджером памяти, освобождение памяти, выделенной для строки, также выполняется специальным методом:

Код: pascal
1.
  zstr_free(fpStr);



Т.обр, не требуется создание/освобождение контекста, настройки сокетов, биндингов, коннектов и всей возни с упаковкой/распаковкой строк.

К сообщению приложены файлы библиотеки czmq.dll, libzmq.dll, скомпилированные с помощью MS VS 2012 U4 (должны работать на Win XP и более новых).

О том, как использовать эту красоту, расскажу чуть позже.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38786222
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Использование.

Для использования CZMQ следует подключить файл czmq.h, приложенный к сообщению.

При импорте .h файлов я старался избегать использования устаревших (deprecated) элементов, однако впоследствии, в процессе изучения ZMQ по не очень новым руководствам, пришлось кое-что добавить.

Возможно, позднее файл биндинга будет изменен.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38786280
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQ High Level API: http://czmq.zeromq.org/
К сообщению приложены файлы библиотеки czmq.dll, libzmq.dll, скомпилированные с помощью MS VS 2012 U4 (должны работать на Win XP и более новых).
О том, как использовать эту красоту, расскажу чуть позже.

zbeacon использовал? про нее будет рассказ?

http://czmq.zeromq.org/manual:zbeacon
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38786719
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPA,

обязательно будет.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791187
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Особенности High-level API CZMQ :

Автоматизация обслуживания сокетов. К примеру, при закрытии контекста все его сокеты обработка будут также закрываться. При этом в некоторых случаях для сокетов можно назначить таймайт.

Кроссплатформенное управление тредами.

Передача потоков сообщений от родительских тредов к дочерним. (При этом автоматически будут использоваться сокеты PAIR по протоколу inproc).

Кроссплатформенный доступ к системным часам.

Специальный реактор для замены zmq_poll(). Цикл опроса прост, громоздок. Каждый раз приходится писать один и тот же код: обсчет таймеров и вызов процедур обработки (ридеров) по мере готовности сокетов. Простой реактор с ридерами сокетов и таймерами позволит сократить время написания цикла обработки.

Правильная обработка нажатия Ctrl-C для консольных приложений.
...
Кроме того, в CZMQ версии 3.* добавлены:

Класс zsock, который работает вообще без контекста и с конструкторами, совмещающими операции создания и коннекта/биндинга.
Класс zactor для multithreaded - разработки.
Класс zgossip для исследования конфигурации сети.
Класс zrex для регулярок.
Функций управления процессами - zsys.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791214
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вариант брокера с балансировкой нагрузки, реализованного с помощью High-level API CZMQ:

Брокер с балансировкой нагрузки & CZMQ
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
program hl_LoadBalancingBroker;

{$APPTYPE CONSOLE}
// Брокер с балансировкой нагрузки.
// Демонстрируется использование CZMQ и реактора
//==============================================
// Имеется клиенты (c_NBR_CLIENTS шт) и рабочие (c_NBR_WORKERS шт).
// Каждый рабочий при запуске сообщает брокеру о своей готовности (c_WORKER_READY).
// Рабочий ставится в очередь.
//
// Клиент при запуске обращается к брокеру с заданием. Брокер отправляет
// задание первому свободному рабочему. Рабочий, выполнив задание, возвращает
// результат брокеру, брокер пересылает результат клиенту - заказчику.

uses
  SysUtils
  , zmq_h
  , czmq_h
  , ZMQ_Utils
  ;

const
  c_NBR_CLIENTS = 10; // Число клиентов
  c_NBR_WORKERS = 3; // // Число рабочих

  // Конечные точки подключения
  c_url_clients = 'tcp://%s:5555'; //'inproc://clients';
  c_url_workers = 'tcp://%s:5556'; //'inproc://workers';
  c_domain = 'localhost'; // Сетевой адрес брокера
  c_interf: string = '*'; // Адрес для биндинга

  c_WORKER_READY: byte = 1; // Сигнал готовности рабочего


function client_task(args: Pointer): Pointer; cdecl;
// Функция треда клиента
var
  client: Pointer;
  ctx: p_zctx_t;
  reply: PChar;
begin
  ctx := args; // zctx_new();
  client := zsocket_new(ctx, ZMQ_REQ);
  zsocket_connect(client, c_url_clients, c_domain);
// Запрос - ответ
  while true do begin
    zstr_send(client, 'HELLO');
    reply := zstr_recv(client);
    if (reply = nil) then
      break;
    z_Log('Client: ' + reply);
    zstr_free(reply);
    sleep(1);
  end;
//  zctx_destroy(ctx);
  Result := nil;
end;

function worker_task(args: Pointer): Pointer; cdecl;
// Функция треда рабочего
var
  ctx: p_zctx_t;
  frame: p_zframe_t;
  msg: p_zmsg_t;
  worker: Pointer;
begin
  ctx := args; // zctx_new();
  worker := zsocket_new(ctx, ZMQ_REQ);
  zsocket_connect(worker, c_url_workers, c_domain);

// Сообщаем брокеру о готовности работать
  frame := zframe_new(@c_WORKER_READY, 1);
  zframe_send(frame, worker, 0);

// ОБработка сообщений по мере их получения
  while True do begin
    msg := zmsg_recv(worker);
    if msg = nil then
      break; // Interrupted
    zframe_reset(zmsg_last(msg), PChar('OK'), 2);
    zmsg_send(msg, worker);
  end;
//  zctx_destroy(ctx);
  Result := nil;
end;


// Структура, передаваемая в реактор
type
  p_lbbroker_t = ^lbbroker_t;
  lbbroker_t = packed record
    frontend: Pointer; // Сокет - слушать клиентов
    backend: Pointer; // Сокет - слушать рабочих
    workers: p_zlist_t; // Список свободных рабочих
  end;

// Устройство реактора таково, что все сообщения, приходящие в сокет,
// передаются ректором в функцию обработки. У нас - два обработчика:
// для фронтэнда (клиенты) и для бэкэнда (рабочие)

// Обработка ввода от клиентjd (на фронтэнд)

function s_handle_frontend(loop: p_zloop_t; poller: p_zmq_pollitem_t; arg: Pointer):
  Integer; cdecl;
var
  msg: p_zmsg_t;
  self: p_lbbroker_t;
begin
  self := p_lbbroker_t(arg);
  msg := zmsg_recv(self.frontend); // Сообщение от клиента
  if msg <> nil then begin
    zmsg_wrap(msg, p_zframe_t(zlist_pop(self.workers)));
    zmsg_send(msg, self.backend);
// Завершение обработчика, если нет доступных рабочих
    if zlist_size(self.workers) = 0 then begin
      poller.socket := self.frontend;
      poller.fd := 0;
      poller.events := ZMQ_POLLIN;
      poller.revents := 0;
      zloop_poller_end(loop, poller);
    end;
  end;
  Result := 0;
end;

// Обработка ввода от рабочих (на бэкэнд)

function s_handle_backend(loop: p_zloop_t; poller: p_zmq_pollitem_t; arg: Pointer):
  Integer; cdecl;
var
  frame: p_zframe_t;
  identity: p_zframe_t;
  msg: p_zmsg_t;
  self: p_lbbroker_t;
begin
// Для балансировки нагрузки снова используем идентификацию
  self := p_lbbroker_t(arg);
  msg := zmsg_recv(self.backend);
  if msg <> nil then begin
    identity := zmsg_unwrap(msg);
    zlist_append(self.workers, identity);


    if zlist_size(self.workers) = 1 then begin
    // Разрешить ридер фронтэнда
      poller.socket := self.frontend;
      poller.fd := 0;
      poller.events := ZMQ_POLLIN;
      poller.revents := 0;
      zloop_poller(loop, poller, @s_handle_frontend, self);
    end;
// Переброска сообщения клиенту, если это не "ГОТОВ".
    frame := zmsg_first(msg);
    if CompareMem(zframe_data(frame), @c_WORKER_READY, 1) then
      zmsg_destroy(msg)
    else
      zmsg_send(msg, self.frontend);
  end;
  result := 0;
end;

// Основной тред, порождающий дочерние, запускающий затем реактор.
// Если нажать Ctrl-C, реактор завершится и главный тред тоже завершится.

procedure DoMain;
var
  ctx: p_zctx_t;
  frame: p_zframe_t;
  i: Integer;
  poller: zmq_pollitem_t;
  reactor: p_zloop_t;
  self: p_lbbroker_t;
begin
  ctx := zctx_new(); // Контекст
  New(self); // Данные реактора

  self.frontend := zsocket_new(ctx, ZMQ_ROUTER); // Сокеты реактора
  self.backend := zsocket_new(ctx, ZMQ_ROUTER);

  zsocket_bind(self.frontend, c_url_clients, c_interf);
    // Привязка к интерфейсу
  zsocket_bind(self.backend, c_url_workers, c_interf);

  for i := 0 to pred(c_NBR_CLIENTS) do // Запуск тредов клиентов
    zthread_new(@client_task, ctx);
  for i := 0 to Pred(c_NBR_WORKERS) do // Запуск тредов рабочих
    zthread_new(@worker_task, ctx);

// Очередь доступных рабочих
  self.workers := zlist_new();

// Подготовка и запуск реактора
  reactor := zloop_new();

  poller.socket := self.backend;
  poller.fd := 0;
  poller.events := ZMQ_POLLIN;
  poller.revents := 0;

  zloop_poller(reactor, @poller, @s_handle_backend, self);
  zloop_start(reactor);
  zloop_destroy(reactor);

// Аккуратно завершаем все при выходе
  while zlist_size(self.workers) > 0 do begin
    frame := zlist_pop(self.workers);
    zframe_destroy(frame);
  end;
  zlist_destroy(self.workers);
  zctx_destroy(ctx);
  Dispose(self);
end;

begin
  DoMain;
  Readln;
end.




"Чистый" ZeroMQ в случае прерывания (по Ctrl+C) вернет код завершения операции -1 и установит код ошибка EINTR.
Здесь же (в CZMQ) операция просто вернет nil:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
  while true do begin
    zstr_send(client, 'HELLO');
    reply := zstr_recv(client);
    if (reply = nil) then
      break;
    z_Log('Client: ' + reply);
    zstr_free(reply);
    sleep(1);
  end;



Если используем zmq_poll() ( 16619033 ), то следим за кодом завершения:

Код: pascal
1.
2.
if  zmq_poll (items, 2, 1000 * 1000) = -1 then
  break; // Прерывание



Но в данном примере мы используем РЕАКТОР CZMQ.

Вот что он позволяет.

Любому сокету может быть назначить ридер: код, который вызывается всякий раз, когда в сокете появляются данные, готовые для чтения.
Отключить ридер для сокета.
Назначить таймер, который будет запускаться один или несколько раз через указанные промежутки времени.
Отключить таймер.

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

Когда мы используем шаблон "Реактор", код как бы выворачивается наизнанку. Код внешне выглядит примерно так:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
var
  reactor :=  p_zloop_t;
...
begin
...
  reactor := zloop_new ();
  zloop_reader(reactor, self.backend, @s_handle_backend, self);
  zloop_start (reactor);
  zloop_destroy (reactor);



Таким образом, обработка сообщений ZMQ размещена в коде специальных методов - обработчиков. При этом один и тот же обработчик может обрабатывать как активность сокета (попступление данных), так и таймеров сокета.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791217
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Что-то у меня обработка Ctrl+C работает неправильно. В "чистом" ZMQ все ОК, а с CZMQ - крутые глюки какие-то.

И не то чтобы это меня особо напрягает, но все же.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791262
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Что-то не очень вразумительно про реактор написал.

Ладно, полшага назад.

Вот версия брокера, использующего CZMQ, но без реактора.

Брокер с балансировкой нагрузки & CZMQ без реактора
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
program hl_LoadBalancingBrokerSimple;

{$APPTYPE CONSOLE}
// Брокер с балансировкой нагрузки.
// Демонстрируется использование CZMQ
//======================================================
// Имеется клиенты (c_NBR_CLIENTS шт) и рабочие (c_NBR_WORKERS шт).
// Каждый рабочий при запуске сообщает брокеру о своей готовности (c_WORKER_READY).
// Рабочий ставится в очередь.
//
// Клиент при запуске обращается к брокеру с заданием. Брокер отправляет
// задание первому свободному рабочему. Рабочий, выполнив задание, возвращает
// результат брокеру, брокер пересылает результат клиенту - заказчику.


uses
  SysUtils,
  zmq_h,
  czmq_h,
  ZMQ_Utils;

const
  c_NBR_CLIENTS = 1; // Число клиентов
  c_NBR_WORKERS = 1; // // Число рабочих

  // Конечные точки подключения
  c_url_clients = 'tcp://%s:5555'; //'inproc://clients';
  c_url_workers = 'tcp://%s:5556'; //'inproc://workers';
  c_domain = 'localhost'; // Сетевой адрес брокера
  c_interf: string = '*'; // Адрес для биндинга

  c_WORKER_READY: byte = 1;
    // Сигнал готовности рабочего         ё


function client_task(args: Pointer): Pointer; cdecl;
// Функция треда клиента
var
  client: Pointer;
  ctx: p_zctx_t;
  reply: PChar;
begin
  ctx := args; // zctx_new();
  client := zsocket_new(ctx, ZMQ_REQ);
  zsocket_connect(client, c_url_clients, c_domain);
// Запрос - ответ
  while true do begin
    zstr_send(client, 'HELLO');
    reply := zstr_recv(client);
    if (reply = nil) then
      break;
    z_Log('Client: ' + reply);
    zstr_free(reply);
    sleep(1);
  end;
//  zctx_destroy(ctx);
  Result := nil;
end;

function worker_task(args: Pointer): Pointer; cdecl;
// Функция треда рабочего
var
  ctx: p_zctx_t;
  frame: p_zframe_t;
  msg: p_zmsg_t;
  worker: Pointer;
begin
  ctx := args; // zctx_new();
  worker := zsocket_new(ctx, ZMQ_REQ);
  zsocket_connect(worker, c_url_workers, c_domain);

// Сообщаем брокеру о готовности работать
  frame := zframe_new(@c_WORKER_READY, 1);
  zframe_send(frame, worker, 0);

// ОБработка сообщений по мере их получения
  while True do begin
    msg := zmsg_recv(worker);
    if msg = nil then
      break; // Interrupted
    zframe_reset(zmsg_last(msg), PChar('OK'), 2);
    zmsg_send(msg, worker);
  end;
//  zctx_destroy(ctx);
  Result := nil;
end;




// Основной тред, порождающий дочерние, запускающий затем реактор.
// Если нажать Ctrl-C, реактор завершится и главный тред тоже завершится.

procedure DoMain;
var
  backend: Pointer;
  ctx: p_zctx_t;
  frame: p_zframe_t;
  frontend: Pointer;
  i: Integer;
  identity: p_zframe_t;
  poller: zmq_pollitem_t;
  reactor: p_zloop_t;
  items: array[0..1] of zmq_pollitem_t;
  msg: p_zmsg_t;
  rc: Integer;
  workers: p_zlist_t;
begin
  ctx := zctx_new(); // Контекст

  frontend := zsocket_new(ctx, ZMQ_ROUTER); // Сокеты реактора
  backend := zsocket_new(ctx, ZMQ_ROUTER);

  zsocket_bind(frontend, c_url_clients, c_interf);
    // Привязка к интерфейсу
  zsocket_bind(backend, c_url_workers, c_interf);

  for i := 0 to pred(c_NBR_CLIENTS) do // Запуск тредов клиентов
    zthread_new(@client_task, ctx);
  for i := 0 to Pred(c_NBR_WORKERS) do // Запуск тредов рабочих
    zthread_new(@worker_task, ctx);

// Очередь доступных рабочих
  workers := zlist_new();

 // Главный цикл балансировщика нагрузок.
 // Длинне, чем с реактором, но короче, чем на "чистом" ZMQ
  while True do begin
    items[0].socket := backend;
    items[0].fd := 0;
    items[0].events := ZMQ_POLLIN;
    items[0].revents := 0;
    items[1].socket := frontend;
    items[1].fd := 0;
    items[1].events := ZMQ_POLLIN;
    items[1].revents := 0;

// Опрашиваем клиентов только если есть незанятые рабочие
    if zlist_size(workers) > 0 then
      rc := zmq_poll(@items[0], 2, -1)
    else
      rc := zmq_poll(@items[0], 1, -1);
    if rc = -1 then
      break; // прерывание

// Обработка данных рабочих (от backend)
    if (items[0].revents and ZMQ_POLLIN) <> 0 then begin
  // Используем идентификацию
      msg := zmsg_recv(backend);
      if msg = nil then
        break; // Interrupted
      identity := zmsg_unwrap(msg);
      zlist_append(workers, identity);

// Если это не сообщение о готовности, переслать сообщение клиенту
      frame := zmsg_first(msg);
      if CompareMem(zframe_data(frame), @c_WORKER_READY, 1) then
        zmsg_destroy(msg)
      else
        zmsg_send(msg, frontend);
    end;
    if (items[1].revents and ZMQ_POLLIN) <> 0 then begin
// Получение запроса от клиента, передача первому незанятому рабочему
      msg := zmsg_recv(frontend);
      if msg <> nil then begin
        zmsg_wrap(msg, zlist_pop(workers));
        zmsg_send(msg, backend);
      end;
    end;
  end;
// Аккуратно завершаем все при выходе
  while zlist_size(workers) > 0 do begin
    frame := zlist_pop(workers);
    zframe_destroy(frame);
  end;
  zlist_destroy(workers);
  zctx_destroy(ctx);
end;

begin
  DoMain;
  Readln;
end.



Отличие от версии 16775038 - в главном цикле:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
// Очередь доступных рабочих
  workers := zlist_new();

 // Главный цикл балансировщика нагрузок.
 // Длинне, чем с реактором, но короче, чем на "чистом" ZMQ
  while True do begin
    items[0].socket := backend;
    items[0].fd := 0;
    items[0].events := ZMQ_POLLIN;
    items[0].revents := 0;
    items[1].socket := frontend;
    items[1].fd := 0;
    items[1].events := ZMQ_POLLIN;
    items[1].revents := 0;

// Опрашиваем клиентов только если есть незанятые рабочие
    if zlist_size(workers) > 0 then
      rc := zmq_poll(@items[0], 2, -1)
    else
      rc := zmq_poll(@items[0], 1, -1);
    if rc = -1 then
      break; // Прерывание

// Обработка данных рабочих (от backend)
    if (items[0].revents and ZMQ_POLLIN) <> 0 then begin
  // Используем идентификацию
      msg := zmsg_recv(backend);
      if msg = nil then
        break; // Прерывание
      identity := zmsg_unwrap(msg);
      zlist_append(workers, identity);

// Если это не сообщение о готовности, переслать сообщение клиенту
      frame := zmsg_first(msg);
      if CompareMem(zframe_data(frame), @c_WORKER_READY, 1) then
        zmsg_destroy(msg)
      else
        zmsg_send(msg, frontend);
    end;
    if (items[1].revents and ZMQ_POLLIN) <> 0 then begin
// Получение запроса от клиента, передача первому незанятому рабочему
      msg := zmsg_recv(frontend);
      if msg <> nil then begin
        zmsg_wrap(msg, zlist_pop(workers));
        zmsg_send(msg, backend);
      end;
    end;
  end;
// Аккуратно завершаем все при выходе
  while zlist_size(workers) > 0 do begin
    frame := zlist_pop(workers);
    zframe_destroy(frame);
  end;
  zlist_destroy(workers);
  zctx_destroy(ctx);
end;


Объект workers теперь - не массив, а список типа p_zlist_t.
Видна работа с фреймами:

Код: pascal
1.
2.
 identity := zmsg_unwrap(msg);
 zlist_append(workers, identity);


Составные сообщения теперь принимаются целиком, а не частями:
Код: pascal
1.
 msg := zmsg_recv(frontend);



Ну и забавные вещи вроде строк с форматированием ("Си"шный format()):

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
const
  // Конечные точки подключения
  c_url_clients = 'tcp://%s:5555'; //'inproc://clients';
  c_url_workers = 'tcp://%s:5556'; //'inproc://workers';
  c_domain = 'localhost'; // Сетевой адрес брокера
  c_interf: string = '*'; // Адрес для биндинга.

begin

...
  zsocket_connect(client, c_url_clients, c_domain);
...
  zsocket_bind(frontend, c_url_clients, c_interf);


Отмечу, что zsocket_connect() / zsocket_bind() - процедуры с переменным числом параметров:

Код: pascal
1.
2.
3.
//  Connect a socket to a formatted endpoint
//  Returns 0 if OK, -1 if the endpoint was invalid.
function zsocket_connect(self: pointer; format: PChar): Integer; varargs; cdecl; external   cZMQ_DllName;


А так как процедуры сишные - не забываем, в случае использования констант в параметрах, указывать модификатор типа для односимвольных строк:

Код: pascal
1.
2.
const
  c_interf: string = '*'; // Адрес для биндинга.


То же самое:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
const
  c_WORKER_READY: byte = 1;
...

begin
...
    frame := zframe_new(@c_WORKER_READY, 1);
...
    if CompareMem(zframe_data(frame), @c_WORKER_READY, 1) then
...



То есть там, где требуется "настоящий" адрес данных - в объявлении константы указываем модификатор типа.
Или просто используем переменную.
...
...
...
Теперь, имхо, разобраться с моим предыдущим сообщением ( 16775038 ) гораздо проще.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791266
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Шаблон "Асинхронный клиент/сервер" .

Будем создавать архитектуру сети N-1, когда несколько разных клиентов асинхронно общаются с одним сервером.

Работать это будет вот так:

- клиенты коннектятся к серверу и отправляют запросы;
- на каждый запрос сервер отправляет 0 или больше ответов;
- клиенты могут отправлять множество запросов без ожидания ответов;
- серверы могут отправлять множество ответов без ожидания новых запросов.

Топология:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791283
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В целях упрощения тестирования, кодировать будем в рамках одного процесса, как мы уже делали несколько раз.
Т.е., множество тредов будет имитировать многопроцессную архитектуру.
При запуске примера видно, что три клиента (каждый со случайным id) выводят на консоль ответы от сервера.
Если присмотреться, то можно заметить, что каждая задача клиента порождает ноль или больше ответов на каждый запрос.

Модель асинхронного клиент-сервера
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
program hl_AsynchClientServer;

{$APPTYPE CONSOLE}

uses
  SysUtils
  , zmq_h
  , czmq_h
  ;


// Асинхронные клиент - сервер ( от DEALER к ROUTER)
//
// Так как это пример, для облегчения запуска все работает в рамках одного процесса.
// В реальности каждая задача должна быть отдельным процессом.


// Задача клиента
// Клиент коннектится к серверу и шлет ему запросы по одному в секунду.
// Собирает ответы в порядке поступления а потом распечатывает.
// Несколько клиентов работаеют параллельно, каждый со своим Id.

function client_task(args: Pointer): Pointer; cdecl;
var
  centitick: Integer;
  client: Pointer;
  ctx: p_zctx_t;
  identity: string;
  items: zmq_pollitem_t;
  msg: p_zmsg_t;
  request_nbr: Integer;
begin
  ctx := zctx_new();
  client := zsocket_new(ctx, ZMQ_DEALER);

// Случайный идентификатор (текст: для облегчения трассировки)
  identity := Format('%4x - %4x', [Random($10000), Random($10000)]);
  zsocket_set_identity(client, PChar(identity));
  zsocket_connect(client, 'tcp://localhost:5570');

  items.socket := client;
  items.fd := 0;
  items.events := ZMQ_POLLIN;
  items.revents := 0;
  request_nbr := 0;
  while true do begin
// Тики по одному в секунду: получаем приходящие сообщения
    for centitick := 0 to 99 do begin
      zmq_poll(@items, 1, 10 * ZMQ_POLL_MSEC);
        // Опрос с таймаутом 0,01 сек
      if (items.revents and ZMQ_POLLIN) <> 0 then begin // Что-то есть
        msg := zmsg_recv(client);
        zframe_print(zmsg_last(msg), PChar(identity));
        zmsg_destroy(&msg);
      end;
    end;
    Inc(request_nbr);
    zstr_sendf(client, PChar('request # %d'), request_nbr);
  end;
  zctx_destroy(ctx);
  result := nil;
end;

// Задача сервера
// Используется многонитевая модель. Для вытягивания запросов в пул
// рабочих и распределения ответов обратно клиентам. Один рабочий может
// одновременно обработать один запрос, но один клиент может общаться с
// множеством рабочих одновременно.

procedure server_worker(args: Pointer; ctx: p_zctx_t; pipe: Pointer); cdecl; forward;

function server_task(args: Pointer): Pointer;
var
  backend: Pointer;
  ctx: p_zctx_t;
  frontend: Pointer;
  thread_nbr: Integer;
begin
// Фронтенд сокет общается с клиентами по tcp
  ctx := zctx_new();
  frontend := zsocket_new(ctx, ZMQ_ROUTER);
  zsocket_bind(frontend, 'tcp://*:5570');

// Бэкенд сокет общается с рабочими по inproc
  backend := zsocket_new(ctx, ZMQ_DEALER);
  zsocket_bind(backend, 'inproc://backend');

// Запуск пула рабочих нитей, точное количество не важно

  for thread_nbr := 0 to 4 do
    zthread_fork(ctx, @server_worker, nil);

// Коннект бэкэнда к фронтэнду через прокси
  zmq_proxy(frontend, backend, nil);

  zctx_destroy(ctx);
  result := nil;

end;

// Каждая задача рабочего работает одновременно над одним запросом и
// отправляет случайное число ответов со случайными паузами между ответами:

procedure server_worker(args: Pointer; ctx: p_zctx_t; pipe: Pointer); cdecl;
var
  content: p_zframe_t;
  identity: p_zframe_t;
  msg: p_zmsg_t;
  replies: Integer;
  reply: Integer;
  worker: Pointer;
begin
  worker := zsocket_new(ctx, ZMQ_DEALER);
  zsocket_connect(worker, 'inproc://backend');

  while true do begin
// Сокет DEALER дает нам конверт ответа и мообщение
    msg := zmsg_recv(worker);
    identity := zmsg_pop(msg);
    content := zmsg_pop(msg);
    assert(content <> nil);
    zmsg_destroy(msg);

// Отправка обратно 0..4 ответов
    replies := Random(5);
    for reply := 0 to pred(replies) do begin
// Sleep какое-то случайное время
      zclock_sleep(Random(1000) + 1);
      zframe_send(identity, worker, c_ZFRAME_REUSE + c_ZFRAME_MORE);
      zframe_send(&content, worker, c_ZFRAME_REUSE);
    end;
    zframe_destroy(&identity);
    zframe_destroy(&content);
  end;
end;

// Главный тред просто запускает 3 клиента и 1 сервер и ждет; затем сервер завершается.

procedure DoMain;
begin
  Randomize;
  zthread_new(@client_task, nil);
  zthread_new(@client_task, nil);
  zthread_new(@client_task, nil);
  zthread_new(@server_task, nil);
  zclock_sleep(5 * 1000);
    // Работаем 5 секунд, потом завершение
end;

begin
  DoMain;
  Readln; // Для отладки
end.




Некоторые замечания к коду примера.

Клиенты шлют запросы раз в секунду и получают обратно ноль или несколько ответов. Чтобы такое сделать с помощью zmq_poll(), мы не может просто опрашивать с 1-секундным таймаутом, или мы завершим отправку нового запроса только через 1 секунду после того, как мы примем последний ответ. Поэтому мы опрашиваем с высокой частотой (100 раз в секунду, по 1/100 секунде на опрос ), что можно считать достаточно точным.

Сервер использует пул рабочих нитей, и каждая из них обрабатывает один запрос синхронно. Он соединяет их со своими фронтэнд сокетами с помощью внутренней очереди. Соединение фронтэнд и бэкэнд сокетов выполняется с помощью вызова zmq_proxy().

Таким образом, более детальная структура асинхронного сервера такова:
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791297
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Стоит подчеркнуть, что для диалога между клиентами и сервером используются сокеты DEALER -> ROUTER, в то время как внутри сервера, для общения между главной нитью сервера и рабочими используются сокеты DEALER -> DEALER. Если бы сокеты были строго синхронными, то мы бы использовали сокет REPORT. Однако, так как мы хоти отправлять множество ответов, нам нужен асинхронный сокет. Мы не хотим маршрутизировать ответы, так как они всегда идут к одной серверной нити, который слал нам запрос.


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

Схема примерно такая:
Client Server frontendWorker[ DEALER ]<----->[ ROUTER <-----> DEALER <-----> DEALER ]1 часть2 части2 части


Теперь насчет сокетов. Для реализации балансировщика нагрузуки для общения с рабочими мы могли бы использовать схему ROUTER -> DEALER, но это повлекло бы много дополнительной работы. В данном случае схема DEALER -> DEALER очевидно предпочтительнее, обеспечивая компромисс между низкой латентностью для каждого запроса и повышенным риском разбалансировки нагрузки рабочих. В данном случае было сделано "как проще".

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

Для правильного управления состоянием клиента в асинхронном сервере, необходимо:

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

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

Пример: Система "Периметр".

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

- Следите и реагируйте на остановку хартбита. Если от клиента не приходо запроса, скажем, в течении двух секунд, сервер может это обнаружить и уничтожить все состояния, связанные с данным клиентом.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38791300
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Рабочий пример. Межброкерная маршрутизация.

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

Для нас такая задача - как два байта переслать. Мы ж дельфисты.

Смоделирует эту задачу, используя ZeroMQ .
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38792614
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQ,

А почему эту тему не ведешь в виде отдельного профильного ресурса?
заведи свой сайт-блог - контент ведь качественный.
а тут ведь затеряется в "мусорных вопросах" если не пинать каждый раз в топ :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38793065
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPAZeroMQ,

А почему эту тему не ведешь в виде отдельного профильного ресурса?
заведи свой сайт-блог - контент ведь качественный.
а тут ведь затеряется в "мусорных вопросах" если не пинать каждый раз в топ :)
Пожалуй, ты прав.

Я вот тут : http://www.sql.ru/blogs/blogs.aspx - оставлял заявку, но воз и ныне там.

Ну и изначально я не предполагал, что материала будет так много.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38793987
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД...
Сокеты в Delphi представлены просто указателями (Pointer).
А сообщения zmq_msg_t - структурой:

Код: pascal
1.
2.
3.
zmq_msg_t = record
    _: Array[0..32-1] of Byte;
  end; 



...

Ты-дыщь! Начиная с версии 4.1.0, структура zmq_msg_t имеет длину 48 байт!

Код: pascal
1.
2.
3.
4.
type
  zmq_msg_t = packed record
    _: array[0..47] of Byte;
  end;



На уровне протоколов обмена данными проблем совместимости "снизу вверх" нет, а на уровне софта - еще как может быть.

Вот тут можно взять свежие .dll и .pas.

Или работать со старыми (.dll и .pas): см. аттачи к 16751865 и 16751887 соответственно.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38794972
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вычитал про форки этой библиотеки: "Crossroad I/O" (уже вроде умер) и NanoMSG .

Форкнул библиотеку Мартин Сюстрик (Martin Su'strik) - один из создателей, с целью:
- изменить лицензию использования;
- сделать сокеты thread-safe;
- расширить режимы работы;
- упростить использование;
- изменить механизмы использования;
- поднять нагрузочную способность;
- добавить несколько полезных утилит


Хотя одной из задач была сделать библиотеку частью ядра Linux, на страничке загрузки есть версия и для Windows.
В составе библиотеки, кроме самой .dll, есть разные утилиты, которые позволяю прямо из командной строки организовать общение с сетью, тестирование, опрос и т.п.

API библиотеки существенно похудел. Это просто супер.
Больше нет работы с контекстом, а сокеты теперь могут общаться в дополнительных режимах. Например, схема "Издатель - Подписчик" расширена до уровня не просто "Слушайте меня все, кто хочет", а еще: "А ну-ка ответьте мне, мои подписчики...".

Забавно, что одним из недостатков ZeroMQ (которое решено в NanoMSG) названа возможность без проблем работать всего () с 10 000 клиентов, а дальше, типа, нужно вызывать процедуры настройки, а еще дальше - глядишь, и ядро придется перекомпилировать... :)
...
К сожалению, библиотека NanoMSG пока только в стадии beta.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38794973
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38795739
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
За выходные разобрался с непонятками, исправил ошибки и собрал все здесь: http://delphi-and-zeromq.blogspot.ru/2014/10/zeromq.html
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38796962
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQКраткий обзор NanoMSG: https://hguemar.fedorapeople.org/slides/nanomsg/presentation.html

NanoMSG под XP не пашет.
CancelIoEx используется
https://github.com/nanomsg/nanomsg/issues/102
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38797045
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPAZeroMQКраткий обзор NanoMSG: https://hguemar.fedorapeople.org/slides/nanomsg/presentation.html

NanoMSG под XP не пашет.
CancelIoEx используется
https://github.com/nanomsg/nanomsg/issues/102
Мда. Под Win7 все ОК.

libzmq.dll и czmq.dll я старательно под MS VS 2008 компилю, чтобы и на Win 2000 запускалось. Хотя, не пробовал запускать. Надо будет в виртмашине потестить.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38798085
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQPPAпропущено...


NanoMSG под XP не пашет.
CancelIoEx используется
https://github.com/nanomsg/nanomsg/issues/102
Мда. Под Win7 все ОК.

libzmq.dll и czmq.dll я старательно под MS VS 2008 компилю, чтобы и на Win 2000 запускалось. Хотя, не пробовал запускать. Надо будет в виртмашине потестить.

Попробовал ZeroMQ на Win2K: фиквам.

ZeroMQ:
автор---------------------------
Точка входа в процедуру getaddrinfo не найдена в библиотеке DLL WS2_32.dll.
---------------------------

Это еще лечится, добавлением в проект одной строчкой

Код: plaintext
1.
#include <Wspiapi.h> // For W2K: поможет, только если czmq  не использовать.



А если использовать czmq.dll, то этого мало, там еще

автор---------------------------
Точка входа в процедуру GetAdaptersAddresses не найдена в библиотеке DLL IPHLPAPI.DLL.
---------------------------


До WinXP вместо GetAdaptersAddresses использовалась GetAdaptersInfo, функциональности которого, к сожалению, недостаточно.
...
Ну и ладно. Пора на XP.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38824384
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQ Рабочий пример. Межброкерная маршрутизация.

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

Для нас такая задача - как два байта переслать. Мы ж дельфисты.

Смоделирует эту задачу, используя ZeroMQ .

Долго разбирался и портировал примеры на Delphi.

Здесь подробный рабочий пример, имитирующий сетевые "кластеры".
...
Дальше буду разбираться с обеспечением надежности доставки сообщений.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38962519
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Как-то не очень внимательно рассмотрел схему "Запрос - Ответ" (REQ - REP).

А тут отладке приложения обнаружил, что "сервер" (там, где сокет REP) получает сообщение от клиента (там, где сокет REQ) не после отправки клиентом сообщения, а только после того, как клиент обратится к процедуре считывания ответа: receive или polling (zmq_msg_recv/zmq_poll).

То есть, клиент отправляет сообщение (zmq_msg_send), что-то делает дальше, но сервер ничего не видит. Пока клиент не захочет считать ответ от сервера (zmq_msg_recv или zmq_poll). В момент обращения клиента к zmq_msg_recv() или к zmq_poll() сервер фиксирует входное сообщение (zmq_msg_recv() или zmq_poll()) и может начать формировать ответ (zmq_msg_send).
Обращение же сервера к zmq_msg_send отправляет сообщение клиенту сразу.

Что, в общем, логично, если вспомнить работу по схеме "Запрос - Ответ" (REQ - REP). Но почему-то сие ускользнуло от моего понимания.
...
...полезно, например, если в режиме REQ-REP нужно серверу отправить служебный сигнал, не требующий подтверждения.
Для этого нужно не просто отправить сообщение, а еще и обратиться к zmq_msg_recv() с минимальным временем ожидания.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #38962640
ZeroMQ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQКак-то не очень внимательно рассмотрел схему "Запрос - Ответ" (REQ - REP).

А тут отладке приложения обнаружил, что "сервер" (там, где сокет REP) получает сообщение от клиента (там, где сокет REQ) не после отправки клиентом сообщения, а только после того, как клиент обратится к процедуре считывания ответа: receive или polling (zmq_msg_recv/zmq_poll).

То есть, клиент отправляет сообщение (zmq_msg_send), что-то делает дальше, но сервер ничего не видит. Пока клиент не захочет считать ответ от сервера (zmq_msg_recv или zmq_poll). В момент обращения клиента к zmq_msg_recv() или к zmq_poll() сервер фиксирует входное сообщение (zmq_msg_recv() или zmq_poll()) и может начать формировать ответ (zmq_msg_send).
Обращение же сервера к zmq_msg_send отправляет сообщение клиенту сразу.

Что, в общем, логично, если вспомнить работу по схеме "Запрос - Ответ" (REQ - REP). Но почему-то сие ускользнуло от моего понимания.
...
...полезно, например, если в режиме REQ-REP нужно серверу отправить служебный сигнал, не требующий подтверждения.
Для этого нужно не просто отправить сообщение, а еще и обратиться к zmq_msg_recv() с минимальным временем ожидания.

А, не. Вовсе необязательно, что отправка сообщения будет отложена до receive или polling. Но - вполне возможно.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39122783
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPAZeroMQКраткий обзор NanoMSG: https://hguemar.fedorapeople.org/slides/nanomsg/presentation.html

NanoMSG под XP не пашет.
CancelIoEx используется
https://github.com/nanomsg/nanomsg/issues/102
Есть две хорошие новости по поводу nanomsg:

1. Один из пользователей форкнул код, реализовав замену CancelIoEx под WinXP. Пишет, что работает у него в продакшне.

2. Для nanomsg будет реализован IPC протокол под Windows, на основе Named Pipes. Что радует: так как как минимум один из компонентов ZMQ обычно "слушает" tcp порт. Это вызывает вопросы при инсталляция приложений, основанных на ZeroMQ со стороны антивирусов и фаерволов.

... а может, и в ZeroMQ реализуют IPC под Windows, так как ZMQ теперь успешно развивается без главного идеолога (который "все бросил" и стал делать nanomsg).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39124074
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
[quot чччД]PPAпропущено...
1. Один из пользователей форкнул код, реализовав замену CancelIoEx под WinXP. Пишет, что работает у него в продакшне.
.

адрес?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39124238
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPA,

что-то результат потерял... :(

Вот отсюда можно за хвост потянуть: https://github.com/nanomsg/nanomsg/pull/349
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39195896
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В ветку ZMQ 4.1 вернули поддержку WindowsXP (которую в декабре 2015го лихо убрали из 4.1.4). Брать здесь: https://github.com/zeromq/zeromq4-1

В ветку ZMQ 4.2 тоже вернули поддержку WindowsXP, но не полностью: новые сокеты (ZMQ_SERVER/ZMQ_CLIENT) будут доступны в Windows Vista и новее. Брать здесь: https://github.com/zeromq/libzmq

И то хорошо.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39199757
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД,

А ipv6 пробовал под виндой?
у меня простой клиент не пашет.

WindowsXP + IP6 Teredo

под linux все хорошо.

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
int zmq_test_client()
{
	void* context = zmq_ctx_new();
	printf("Client Starting!\n");
	int major = 0, minor = 0, patch = 0;
	zmq_version(&major, &minor, &patch);
	printf("ZeroMQ version: %d.%d.%d\n", major, minor, patch);
	
	void* request = zmq_socket(context, ZMQ_REQ);
	int ipv6 = 1;
	const auto l_result_opt = zmq_setsockopt(request, ZMQ_IPV6, &ipv6, 4);
	if (l_result_opt != 0)
		printf("zmq_setsockopt = %d\n", errno);
	auto l_result_connect = zmq_connect(request, "tcp://[2001:41D0:000A:1A3B:0000:0000:0000:0012]:4040");
	if (l_result_connect != 0)
		printf("zmq_connect = %d\n", errno);



E:\>test-console.exe
Client Starting!
ZeroMQ version: 4.2.0
Sending: hello - 0
Assertion failed: Bad protocol option (q:\vc15\r5xx\zmq\src\ip.cpp:116)

может сталкивался?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39199765
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
А разве в Windows XP было IP v6 ?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39199838
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecator
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39199857
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecator,

+1.

ipv6 в Win XP требуется включать. PPA, попробуй, набери в командной строке:

Код: c#
1.
netsh int ipv6 install





PPA,
"Просто так" ipv6 - в Vista и новее.

Там даже при выходе релиза 4.1.4 лихо отменили поддержку WinXP из-за этого: заюзали функцию if_nametoindex()
...
Ну и просто
Код: pascal
1.
// Set target version to Windows Server 2008, Windows Vista or higher.




Потом, в 4.1.5 вернули ( https://github.com/zeromq/zeromq4-1 ).
Код: pascal
1.
//  Set target version to Windows Server 2003, Windows XP/SP1 or higher.


И в 4.2.0 - тоже, за исключением блока, обеспечивающего новые сокеты ZMQ_CLIENT/ZMQ_SERVER.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39199953
Фотография Feg16
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А движка для мультиплеера на них нету?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200113
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Feg16А движка для мультиплеера на них нету?

Что за "движок мультиплеера"?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200115
Фотография Feg16
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДЧто за "движок мультиплеера"?Логика над сокетами - коннект, дисконнект, отправка подписанного пакета и т.п. Т.е. вся та обвязка которая необходима для подключения нескольких клиентов к серверу, и сервер который может обрабатывать данные от нескольких клиентов
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200131
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Feg16чччДЧто за "движок мультиплеера"?Логика над сокетами - коннект, дисконнект, отправка подписанного пакета и т.п. Т.е. вся та обвязка которая необходима для подключения нескольких клиентов к серверу, и сервер который может обрабатывать данные от нескольких клиентов
Ну так да. Выбираешь нужный тип сокета, и вперед.

К примеру, режим "запрос-ответ": клиент отправляет запрос и ждет ответ. Сервер, получи запрос, отвечает только на него и только этому клиенту.
Реализация.
Используются сокет типа REQ и сокет типа REP.
К серверу (там сокет REP) коннектится куча клиентов (используя сокет REQ). Все запроси к серверу выстраиваются в очередь, сервер их по очереди разгребает и возвращает ответ именно тому клиенту, от которого получил запрос.
...
Если нужно общаться одновременно несколькими клиентами не по очереди, а одновременно (например, получить запрос от одного, обработать его и отправить результат другому) - используется пара сокетов Dialer - Router. При этом состав клиентского сообщения автоматически добавляется идентификатор клиента (назначается автоматически или ручками на клиенте). То есть, к примеру, получаешь сообщение, меняешь в нем адрес и отправляешь обратно. А оно приходит не к отправителю, а к тому, чей адрес ты подставил. Вот тебе и прокси в три строки кода.

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

Есть всякие там шифрации и подписи.
Даже есть средства наблюдения за каталогами файловой системы в онлайне.

Недостаток: основное назначение - для работы в локальных сетях по причинам архитектуры (можно заDDOSить, например). Ну и еще, до недавнего времени нельзя было сокеты передавать между вычислительными нитями. Особые правила работы для построения мультинитевых приложений.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200132
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
так он хочет без низкоуровневых обёрток.
Нужно что-то типа DirectPlay
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200138
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecator...типа DirectPlay
Ну, тут кодировать - всего ничего (главное - придумать). Например, один из компов назначается "сервером", где клиенты регистрируются и где хранится состояние клиентов. А потом ты, запросив у сервера данные, сможешь напрямую общаться с другим, нужным тебе логическим клиентом.

Тут ты сам выстраиваешь нужную архитектуру, какая твоей левой пятке приглянется, а не пытаешься натянуть существующую на собственное понимание прекрасного.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200265
Фотография Feg16
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorтак он хочет без низкоуровневых обёрток.
Нужно что-то типа DirectPlayДа :)
чччДТут ты сам выстраиваешь нужную архитектуруУвы рук всего две, и они уже заняты остальным, поэтому приходится для тех или иных моментов натягивать уже готовое решение.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200271
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
Feg16defecatorтак он хочет без низкоуровневых обёрток.
Нужно что-то типа DirectPlayДа :)
чччДТут ты сам выстраиваешь нужную архитектуруУвы рук всего две, и они уже заняты остальным, поэтому приходится для тех или иных моментов натягивать уже готовое решение.
Так натяни готовое решение - тот же DirectPlay, тем более в винду он встроен
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200447
GunSmoker
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecator, DirectPlay начал устаревать ещё в Vista, и потихоньку из винды выпиливается. Что вместо него - я бы сам не прочь узнать.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200482
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
GunSmokerdefecator, DirectPlay начал устаревать ещё в Vista, и потихоньку из винды выпиливается. Что вместо него - я бы сам не прочь узнать.
так это же составная часть DirectX, как его могут выпилить-то ?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200491
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДdefecator,

+1.

ipv6 в Win XP требуется включать. PPA, попробуй, набери в командной строке:

Код: c#
1.
netsh int ipv6 install





Я естественно включил ip6 в XP руками
но проблема в самой либе оказалась в методе

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
void zmq::enable_ipv4_mapping (fd_t s_)
{
  (void) s_;

#ifdef IPV6_V6ONLY
#ifdef ZMQ_HAVE_WINDOWS
    DWORD flag = 0;
#else
    int flag = 0;
#endif
    int rc = setsockopt (s_, IPPROTO_IPV6, IPV6_V6ONLY, (const char*) &flag,
        sizeof (flag));
#ifdef ZMQ_HAVE_WINDOWS
    wsa_assert (rc != SOCKET_ERROR);
#else
    errno_assert (rc == 0);
#endif
#endif
}



IPV6_V6ONLY - This socket option is supported on Windows Vista or later.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms738574(v=vs.85).aspx

Странно что в самой zmq не детектят версию винды и зовут этот метод
как починить правльнее пока не знаю - убрал экраном IPV6_V6ONLY
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200508
GunSmoker
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecator,
авторWarning: Microsoft DirectPlay has been deprecated. Deprecated components of Microsoft DirectX 9.0 for Managed Code are considered obsolete. While these components are still supported in this release of DirectX 9.0 for Managed Code, they may be removed in the future. When writing new applications, you should avoid using these deprecated components. When modifying existing applications, you are strongly encouraged to remove any dependency on these components.
авторDirectPlay will be supported in DirectX DLLs for the lifetime of Microsoft Windows XP, but from the autumn of 2007 the headers and libraries — vital components if developers wanted to develop new programs that utilize the technology — were absent from the DirectX SDK.

In Windows Vista, DirectPlay has been deprecated and DirectPlay Voice and DirectPlay's NAT Helper have been removed.

В DirectX 10 и выше DirectPlay нет. В Vista и выше нет необходимых сетевых компонентов. Выпилили по аналогии с .hlp - при запуске программы в первый раз выводится запрос на скачку/установку пакета поддержки старья.

Также выпилили DirectSound и DirectMusic.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200516
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
GunSmoker, сурово попилили движок, не знал
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200526
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
Между тем, только что проверил - в Windows 1 (DirectX 11) есть интерфейсы DirectPlay.
Запустил приложение, собранное аж 18.12.1999, и спокойно подключился

Вроде ничего не ставил дополнительного
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200544
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
Вот исходники примитивного чата через DirectPlay, внутри есть EXE, откомпилёный ещё на Delphi 2 в 1999 году:
http://rghost.ru/7q4zrQSGv

Всё работает в Window 7....
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200580
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
В Windows 8.1 "искаропки" интерфейса DirectPlay не оказалось.
А в Windows 7 "искаропки" он ещё есть
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200600
Фотография Feg16
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorА в Windows 7 "искаропки" он ещё есть
Код: plaintext
Warning: Microsoft DirectPlay has been deprecated. 
Этого достаточно чтобы сморщив нос закрыть страницу :(
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200602
Фотография Feg16
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А то будет потом как с RedAlert - поставьте тот компонент, настройте протокол IPX
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200603
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
Feg16defecatorА в Windows 7 "искаропки" он ещё есть
Код: plaintext
Warning: Microsoft DirectPlay has been deprecated. 
Этого достаточно чтобы сморщив нос закрыть страницу :(
какие тонкие нюхательные ощущения, я поражаюсь.
А с древним кодом нет, не работал никогда ? Тошнило тебя ?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39200795
Фотография Feg16
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorА с древним кодом нет, не работал никогда ? Тошнило тебя ?Одно дело пилить на дядю говнокод на практике, а второе заменять один движок на другой. Смысл мне ставить раком проект, если он у пользователей попросту не станет работать?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39205141
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ZeroMQ Особенности High-level API CZMQ :

Автоматизация обслуживания сокетов. К примеру, при закрытии контекста все его сокеты обработка будут также закрываться. При этом в некоторых случаях для сокетов можно назначить таймайт.
...

Интересно:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
//  Create an attached thread. An attached thread gets a ctx and a PAIR
//  pipe back to its parent. It must monitor its pipe, and exit if the
//  pipe becomes unreadable. Do not destroy the ctx, the thread does this
//  automatically when it ends.
type 
  p_zthread_attached_fn = ^zthread_attached_fn;
  zthread_attached_fn = procedure(args: Pointer; ctx: p_zctx_t; pipe: Pointer); cdecl;
...
interface

  function zthread_fork(ctx: p_zctx_t; thread_fn: p_zthread_attached_fn; args: Pointer):
    Pointer; cdecl; external cZMQ_DllName;



zthread_fork создает и запускает "прикрепленную нить", фактически запуская cdecl - функцию типа zthread_attached_fn и автоматически создавая пару сокетов для общения с нитью, как и сказано в описании выше.

Так вот, контекст ZMQ (параметр ctx: p_zctx_t) не просто передается, а создается его копия в блоке данных нити.
В рамках нового контекста в нити и будет создаваться новый пул сокетов и связанных с ними коннектов, очередей и т.п.
Т.е., по завершении нити произойдет корректная финализация копии контекста и связанных с ним сокетов и проч.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39331834
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД, с аутентификацией не разбирался?

В тестах есть пример , но слишком простой.

С сервером понятно, непонятно как узнать клиенту что у него "Invalid username or password"?
На клиенте libzmq.dll получает "400", рвет соединение, больше его не пытается установить, но наружу никак это не сообщает. Снаружи мой код работает как будто все хорошо. zmq_send() отправляет, zmq_recv() висит в ожидании приема.

Нагуглил это , вроде тот же вопрос, но мало что там понял, у меня с английским не очень хорошо.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39332932
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Может кому пригодиться. Все что нарыл - это включить мониторинг ( zmq_socket_monitor () ) и анализировать что будет после ZMQ_EVENT_DISCONNECTED, если после попытки установить соединение прекратятся, то считаем что сервер прислал команду отключиться.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39332970
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T,

выстраивая свой протокол, я сделал так, что в момент handshake клиент передает серверу свои текстовые идентификационные данные (имя компа, юзернэйм windows и т.п.) в отдельном фрейме. А после handshake, обменявшись открытыми ключами для шифрования (алг. Диффи-Хеллмана), в целях экономии трафика использую собственный открытый Д-Х ключ заодно и как 8-байтный UID для identity коннекта.
Сервер кэширует UID'ы и хранит их в течении заданного времени (5 сек без активности -> "свободен!).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39332975
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Т.е., аутентификацией на сервере занимаюсь я сам.
Что удобно и позволяет выстраивать различные, удобные для моей левой пятки схемы.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333002
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Схема примерно вот такая.

Клиент получает от сервера сообщения:
- хартбит - посылается периодически, чтобы клиент знал: сервер жив;
- запрос выполнен - успешный ответ сервера на любой запрос клиента (кроме запроса "гуд бай, сервер!").
- запрос об идентификации - ответ сервера на любой запрос, когда сервер не знает о данном identity ничего, клиент должен представиться;

Сервер получает запросы от клиента:
- хартбит - посылается периодически, чтобы сервер знал: клиент жив.
- запрос - посылается клиентом по мере надобности.
- запрос с блоком идентификации (высылается на запрос сервера).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333515
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД,

я вот одного не понял, если что бы получить сообщение нужно сперва выделить под него память где тогда асинхронность?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333546
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)чччД,

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

Не обязательно. Используй zmq_msg_recv() :

длина_сообщения := zmq_msg_recv(сокет, сообщение, флаги);

Если длина_сообщения >= 0, значит, все ОК.
А данные после приема можно получить вот так: zmq_msg_data (fMessage).

А асинхронность - в поллинге: http://delphi-and-zeromq.blogspot.ru/2014/11/05-zeromq-zmqpoll.html
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333622
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДТ.е., аутентификацией на сервере занимаюсь я сам.
Что удобно и позволяет выстраивать различные, удобные для моей левой пятки схемы.
Тоже склоняюсь к самодельной аутентификации.
Свой протокол это хорошо, возможно даже лучше для безопасности чем встроенный, т.к. можно всунуть перехват zmq_setsockopt() установки логина/пароля и готово. Или трафик послушать. В PLAIN режиме оно открытым текстом идет.

Единственный плюс встроенной что сервер рвет соединение если зацепились вовсе не не через ZMQ, если встроеная отключена - не рвет, просто не отвечает.
Еще встроенная показывает IP откуда коннект, без нее можно через монитор попробовать сопоставить. Я не определился, нужен ли вообще мне этот IP или без него проживу.

Хочу задействовать новые сокеты ZMQ_CLIENT, ZMQ_SERVER. Управление идентификацией гибче чем с DILLER-ROUTER. И побыстрее должно работать теоретически, т.к. маршрутизация по ID сессии (int32), а не по имени клиента (строка произвольной длины)
Смущает что они подключаются по
Код: plaintext
1.
#define ZMQ_BUILD_DRAFT_API


т.е. вроде как еще в варианте черновика. Есть опыт использования? Не глючат?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333648
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T...
Хочу задействовать новые сокеты ZMQ_CLIENT, ZMQ_SERVER...
[/src]
т.е. вроде как еще в варианте черновика. Есть опыт использования? Не глючат?
Не пользовался пока, чуть-чуть почитал только.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333959
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД,

нда, не густо - нулевая обёртка над апи практически
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333970
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД,
всё больше склоняюсь, что вот такой подход более практичен и в плане результата и в плане лёгкости разработки.
ещё бы для асинхронных операций чтения память выделять когда она требуется, а не заранее - тогда наверное можно и с классическим пулингом посостязаться по затратам ресурсов
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39333998
Товарищ младший сержант
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)чччД,

нда, не густо - нулевая обёртка над апи практически
Где нулевая обертка над апи?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39334035
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Товарищ младший сержантkealon(Ruslan)чччД,

нда, не густо - нулевая обёртка над апи практически
Где нулевая обертка над апи?

чччДА асинхронность - в поллинге: http://delphi-and-zeromq.blogspot.ru/2014/11/05-zeromq-zmqpoll.html
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
// Процесс обработки для обоих сокетов
  while true do begin
 
    zmq_poll(@fItems[0], Length(fItems), -1);
    if ((fItems[0].revents and ZMQ_POLLIN) <> 0) then begin
      fSize := zmq_recv(fSocketReceiver, @fMsgBuff, SizeOf(fMsgBuff), 0);
      if fSize >= 0 then
        // Выполнение задания
      else
        break;
      if ((fItems[1].revents and ZMQ_POLLIN) <> 0) then begin
        fSize := zmq_recv(fSocketSubscriber, @fMsgBuff, SizeOf(fMsgBuff), 0);
        if fSize >= 0 then
    // Обработка данных о погоде
        else
          break;
      end;
    end;
  end;
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39334087
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)чччД,
всё больше склоняюсь, что вот такой подход более практичен и в плане результата и в плане лёгкости разработки.
ХЗ что проще. Ниже код сервера с многопоточной обработкой, букав поменьше и нет необходимости синхронизации доступа к очереди на обработку.
kealon(Ruslan)ещё бы для асинхронных операций чтения память выделять когда она требуется, а не заранее - тогда наверное можно и с классическим пулингом посостязаться по затратам ресурсов
zmq_msg_recv() выделяет ровно столько сколько надо по приходу сообщения.

Многопоточный эхо-серверСообщения принимаются и раздаются трем потокам на обработку.
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
#define ZMQ_BUILD_DRAFT_API
#include "zmq.h"

#include <assert.h>
#include <stdlib.h>
#include <thread>

void *ctx = NULL;
void *sock = NULL;

// поток обработки сообщений
void worker(int num) {
	printf("worker %d start\n", num);
	void* queue = zmq_socket(ctx, ZMQ_PULL);	assert(queue);
	int rc = zmq_connect(queue, "inproc://echo-queue"); assert(rc == 0);
	while(1) {
		// получение указателя из очереди
		zmq_msg_t p;
		rc = zmq_msg_init(&p); assert(rc == 0);
		rc = zmq_msg_recv(&p, queue, 0); assert(rc == sizeof(zmq_msg_t*));
		zmq_msg_t* msg = NULL;
		memcpy(&msg, zmq_msg_data(&p), sizeof(zmq_msg_t*));
		rc = zmq_msg_close(&p); assert(rc == 0);
		// обработка сообщения
		//printf("worker %d recv %d bytes from %X\n", num, zmq_msg_size(msg), zmq_msg_routing_id(msg));
		rc = zmq_msg_send(msg, sock, 0); assert(rc != -1); // отправка msg и освобождение памяти
		free(msg);
	}
	zmq_close(queue);
}

int main (void) {
	ctx = zmq_ctx_new (); assert (ctx);
	// сокет для сообщений от клиентов
	sock = zmq_socket(ctx, ZMQ_SERVER);	assert(sock);
	int rc = zmq_bind(sock, "tcp://*:12345"); assert(rc == 0); 
	// сокет для очереди обработки
	void* queue = zmq_socket(ctx, ZMQ_PUSH);	assert(queue);
	rc = zmq_bind(queue, "inproc://echo-queue"); assert(rc == 0);
	// Запуск потоков обработки
	std::thread w1(worker, 1), w2(worker, 2), w3(worker, 3);
	// прием сообщений от клиентов
	while (1) {
		zmq_msg_t* msg = (zmq_msg_t*)malloc(sizeof(zmq_msg_t)); assert(msg); // инициализация пустого сообщения
		rc = zmq_msg_init(msg); assert(rc == 0);// инициализация пустого сообщения
		rc = zmq_msg_recv(msg, sock, 0); assert(rc > 0); // ожидание приема, прием с выделением памяти
		// Постановка в очередь на обработку
		zmq_msg_t p;
		rc = zmq_msg_init_size(&p, sizeof(zmq_msg_t*)); assert(rc == 0);
		memcpy(zmq_msg_data(&p), &msg, sizeof(zmq_msg_t*));
		rc = zmq_msg_send(&p, queue, 0); assert(rc == sizeof(zmq_msg_t*));
	}
	zmq_close(sock);
	zmq_ctx_destroy(ctx);
	return 0;
}


Для переделки в полноценный сервер просто дописать поток обработки worker().

Провел небольшой тест на скорость. Клиент открывает 1000 соединений к серверу и в каждое шлет 100 запросов.
Соединение через 127.0.0.1. Проц i7 4 ядра. Win10
СоединенийВремя мсСкорость запрос/секПамять сервера Мб10004 82820 70038 Мб300023 57012 700122 Мбв состоянии простоя (без соединений) память, используемая сервером, 5-6 Мб.
код клиента
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
#define COUNT 1000 // предел 1023, для 3000 запускал три client.exe
int main (void)
{
	printf("Connecting to ECHO server\n");
	void *ctx = zmq_ctx_new(); assert(ctx);

	void *sock[COUNT];
	int rc;
	for(int i = 0; i < COUNT; i++) {
		sock[i] = zmq_socket(ctx, ZMQ_CLIENT); assert(sock[i]);
		rc = zmq_connect(sock[i], "tcp://127.0.0.1:12345"); assert(rc == 0);
	}


	int t = GetTickCount();

	for (int i = 0; i < 100; i++) {
		for(int j = 0; j < COUNT; j++) {
			zmq_msg_t msg;
			rc = zmq_msg_init_size(&msg, 5); assert(rc == 0);
			memcpy(zmq_msg_data(&msg), "Hello", 5);
			rc = zmq_msg_send(&msg, sock[j], 0); assert(rc == 5);
		}
		for (int j = 0; j < COUNT; j++) {
			zmq_msg_t msg;
			rc = zmq_msg_init(&msg); assert(rc == 0);
			rc = zmq_msg_recv(&msg, sock[j], 0); assert(rc == 5);
		}
	}
	printf("Time: %d\n", GetTickCount() - t);
	zmq_close(sock);
	zmq_ctx_destroy(ctx);
	return 0;
}

...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39334138
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Перемудрил я в коде сервера с указателями и похоже утечка памяти есть.
Надо передавать содержимое zmq_msg_t, это как понимаю структура-заголовок (64 байта) где все ссылки на данные сообщения.
Переделал, немного побыстрее стало. Сервер при простое занимает 2-3 Мб памяти.
исходник
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
#define ZMQ_BUILD_DRAFT_API
#include "zmq.h"

#include <assert.h>
#include <stdlib.h>
#include <thread>

void *ctx = NULL;
void *sock = NULL;

// рабочий поток обработки сообщений
void worker(int num) {
	printf("worker %d start\n", num);
	void* queue = zmq_socket(ctx, ZMQ_PULL);	assert(queue);
	int rc = zmq_connect(queue, "inproc://echo-queue"); assert(rc == 0);
	while(1) {
		// получение указателя из очереди
		zmq_msg_t p;
		rc = zmq_msg_init(&p); assert(rc == 0);
		rc = zmq_msg_recv(&p, queue, 0); assert(rc == sizeof(zmq_msg_t));
		zmq_msg_t* msg = (zmq_msg_t*)zmq_msg_data(&p);
		// обработка сообщения
		//printf("worker %d recv %d bytes from %X\n", num, zmq_msg_size(msg), zmq_msg_routing_id(msg));
		rc = zmq_msg_send(msg, sock, 0); assert(rc != -1); // отправка msg и освобождение памяти
		rc = zmq_msg_close(&p); assert(rc == 0); // освобождение памяти сообщения с указателем
	}
	zmq_close(queue);
}

int main (void) {
	ctx = zmq_ctx_new (); assert (ctx);
	// сокет для сообщений от клиентов
	sock = zmq_socket(ctx, ZMQ_SERVER);	assert(sock);
	int rc = zmq_bind(sock, "tcp://*:12345"); assert(rc == 0); 
	// сокет для очереди обработки
	void* queue = zmq_socket(ctx, ZMQ_PUSH);	assert(queue);
	rc = zmq_bind(queue, "inproc://echo-queue"); assert(rc == 0);
	// Запуск потоков обработки
	std::thread w1(worker, 1), w2(worker, 2), w3(worker, 3);
	// прием сообщений от клиентов
	while (1) {
		zmq_msg_t msg;
		rc = zmq_msg_init(&msg); assert(rc == 0);// инициализация пустого сообщения
		rc = zmq_msg_recv(&msg, sock, 0); assert(rc > 0); // ожидание приема, прием с выделением памяти
		// Постановка в очередь на обработку
		zmq_msg_t p;
		rc = zmq_msg_init_size(&p, sizeof(zmq_msg_t)); assert(rc == 0);
		memcpy(zmq_msg_data(&p), &msg, sizeof(zmq_msg_t));
		rc = zmq_msg_send(&p, queue, 0); assert(rc == sizeof(zmq_msg_t));
	}
	zmq_close(sock);
	zmq_ctx_destroy(ctx);
	return 0;
}

...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39334777
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)я вот одного не понял, если что бы получить сообщение нужно сперва выделить под него память где тогда асинхронность?
ИМХУ ты тут теплое с мягким в кучу замешал.
Асинхронность это алгоритмический подход, несинхронно, т.е. не ждать выполнения каждого шага, т.е. дал команду сделать то-то до тогда-то и забыл. Получил ответ "то-то сделано", запустил следующее, истек таймаут "тогда-то наступило и ничего не сделано", повторил команду "сделать то-то" или еще какие действия. А когда выделилась память - пофиг. Мысли ширше или ширее )))
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39334842
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima Tkealon(Ruslan)я вот одного не понял, если что бы получить сообщение нужно сперва выделить под него память где тогда асинхронность?
ИМХУ ты тут теплое с мягким в кучу замешал.
Асинхронность это алгоритмический подход, несинхронно, т.е. не ждать выполнения каждого шага, т.е. дал команду сделать то-то до тогда-то и забыл. Получил ответ "то-то сделано", запустил следующее, истек таймаут "тогда-то наступило и ничего не сделано", повторил команду "сделать то-то" или еще какие действия. А когда выделилась память - пофиг. Мысли ширше или ширее )))
ага, коревато, но ты же понял, что я хотел спросить :-)

Dima Tkealon(Ruslan)чччД,
всё больше склоняюсь, что вот такой подход более практичен и в плане результата и в плане лёгкости разработки.
ХЗ что проще. Ниже код сервера с многопоточной обработкой, букав поменьше и нет необходимости синхронизации доступа к очереди на обработку.
kealon(Ruslan)ещё бы для асинхронных операций чтения память выделять когда она требуется, а не заранее - тогда наверное можно и с классическим пулингом посостязаться по затратам ресурсов
zmq_msg_recv() выделяет ровно столько сколько надо по приходу сообщения.

пример зачётный, именно то, что хотел
к твоему примеру очень неплохо должно ложиться, то что описано в статье
PS: мда, что-то слишком много вокруг этих корутин последнее время крутится, случайности неслучайны...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39334926
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)PS: мда, что-то слишком много вокруг этих корутин последнее время крутится, случайности неслучайны...
ИМХУ это просто удобный синтаксис. Адаптированный к асинхронным алгоритмам.
Нынче повышение производительности железа де факто остановилось для синхронных алгоритмов, т.е. производительность одного ядра не меняется, а дополнительные ядра пользы не дают. Поэтому все направились на смену подходов, чтобы хоть как-то эти ядра хоть чем-то загрузить. Плюс сетевого взаимодействия стало больше.

А дальше возникает проблема удобства разработки и отладки, т.к. асинхронные алгоритмы в разы сложнее чем синхронные аналоги.
Например в C# много в эту сторону наработано: await/async, встроенный пул потоков, асинхронная модель на основе задач (TAP) и т.д. Все это создано в т.ч. чтобы убрать асинхронные сложности от глаз разработчика.
Там где подобного нет: один из вариантов - очереди сообщений, ZeroMQ тут очень даже в тему, особенно если требуется работа по сети.

PS Во втором варианте с копированием zmq_msg_t я тоже накосячил. Разработчики не советуют туда лезть напрямую.
Во всех описаниях функций zmq_msg_*() пишутNever access zmq_msg_t members directly, instead always use the zmq_msg family of functions.

Поправил так
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
#define ZMQ_BUILD_DRAFT_API
#include "zmq.h"

#include <assert.h>
#include <stdlib.h>
#include <thread>

void *ctx = NULL;
void *sock = NULL;

// рабочий поток обработки сообщений
void worker(int num) {
	printf("worker %d start\n", num);
	void* queue = zmq_socket(ctx, ZMQ_PULL);	assert(queue);
	int rc = zmq_connect(queue, "inproc://echo-queue"); assert(rc == 0);
	while(1) {
		// получение указателя из очереди
		zmq_msg_t p;
		rc = zmq_msg_init(&p); assert(rc == 0);
		rc = zmq_msg_recv(&p, queue, 0); assert(rc == sizeof(zmq_msg_t));
		zmq_msg_t* msg = (zmq_msg_t*)zmq_msg_data(&p);
		// обработка сообщения
		//printf("worker %d recv %d bytes from %X\n", num, zmq_msg_size(msg), zmq_msg_routing_id(msg));
		rc = zmq_msg_send(msg, sock, 0); assert(rc != -1); // отправка msg и освобождение памяти
		rc = zmq_msg_close(&p); assert(rc == 0); // освобождение памяти сообщения с указателем
	}
	zmq_close(queue);
}

int main (void) {
	ctx = zmq_ctx_new (); assert (ctx);
	// сокет для сообщений от клиентов
	sock = zmq_socket(ctx, ZMQ_SERVER);	assert(sock);
	int rc = zmq_bind(sock, "tcp://*:12345"); assert(rc == 0); 
	// сокет для очереди обработки
	void* queue = zmq_socket(ctx, ZMQ_PUSH);	assert(queue);
	rc = zmq_bind(queue, "inproc://echo-queue"); assert(rc == 0);
	// Запуск потоков обработки
	std::thread w1(worker, 1), w2(worker, 2), w3(worker, 3);
	// прием сообщений от клиентов
	while (1) {
		// Сообщение для отправки в очередь
		zmq_msg_t p;
		rc = zmq_msg_init_size(&p, sizeof(zmq_msg_t)); assert(rc == 0);
		zmq_msg_t* msg = (zmq_msg_t*)zmq_msg_data(&p);

		// Ожидание сообщений от клиентов
		rc = zmq_msg_init(msg); assert(rc == 0);// инициализация пустого сообщения
		rc = zmq_msg_recv(msg, sock, 0); assert(rc > 0); // ожидание приема, прием с выделением памяти

		// Постановка в очередь на обработку
		rc = zmq_msg_send(&p, queue, 0); assert(rc == sizeof(zmq_msg_t));
	}
	zmq_close(sock);
	zmq_ctx_destroy(ctx);
	return 0;
}
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39335878
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Поизучал что реально происходит в inproc - пришел к выводу что не надо никаких указателей для inproc соединений, там и так указатели передаются.
Точнее если сообщение маленькое (5 байт например), то само сообщение, если большое (100 байт пробовал), то указатель, а содержимое сообщения остается в памяти там же где и было. Т.е. ZMQ сама отлично решает как быстрее доставить.

Итого: сообщение можно принимать с TCP соединения и сразу отправлять в inproc очередь на обработку без какого-либо шаманства.
Код сервера стал меньше, понятнее и быстрее на 5%
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
#define ZMQ_BUILD_DRAFT_API
#include "zmq.h"

#include <assert.h>
#include <stdlib.h>
#include <thread>

void *ctx = NULL;
void *sock = NULL;

// поток обработки сообщений
void worker(int num) {
	printf("worker %d start\n", num);
	void* queue = zmq_socket(ctx, ZMQ_PULL); assert(queue);
	int rc = zmq_connect(queue, "inproc://echo-queue"); assert(rc == 0);

	while (1) {
		// получение сообщения из очереди
		zmq_msg_t msg;
		rc = zmq_msg_init(&msg); assert(rc == 0);
		rc = zmq_msg_recv(&msg, queue, 0); assert(rc > 0);
		// обработка сообщения
		printf("worker %d recv %d bytes at %X from %X\n", num, zmq_msg_size(&msg), zmq_msg_data(&msg), zmq_msg_routing_id(&msg));
		rc = zmq_msg_send(&msg, sock, 0); assert(rc != -1); // отправка msg и освобождение памяти
	}
	zmq_close(queue);
}

int main (void) {
	ctx = zmq_ctx_new (); assert (ctx);
	// сокет для сообщений от клиентов
	sock = zmq_socket(ctx, ZMQ_SERVER); assert(sock);
	int rc = zmq_bind(sock, "tcp://*:12345"); assert(rc == 0); 
	// сокет для очереди обработки
	void* queue = zmq_socket(ctx, ZMQ_PUSH);	assert(queue);
	rc = zmq_bind(queue, "inproc://echo-queue"); assert(rc == 0);
	// запуск потоков обработки
	std::thread w1(worker, 1), w2(worker, 2), w3(worker, 3);
	// прием сообщений от клиентов
	while (1) {
		// Ожидание сообщений от клиентов
		zmq_msg_t msg;
		rc = zmq_msg_init(&msg); assert(rc == 0);// инициализация пустого сообщения
		rc = zmq_msg_recv(&msg, sock, 0); assert(rc > 0); // ожидание приема, прием с выделением памяти
		printf("recv %d bytes at %X from %X\n", zmq_msg_size(&msg), zmq_msg_data(&msg), zmq_msg_routing_id(&msg));
		// Постановка в очередь на обработку
		rc = zmq_msg_send(&msg, queue, 0); assert(rc > 0);
	}
	zmq_close(sock);
	zmq_ctx_destroy(ctx);
	return 0;
}

...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39336536
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T,

А у вас ZMQ_CLIENT будет на винде?
он ведь XP не поддерживает - это на всегда или временно?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39336600
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPADima T,

А у вас ZMQ_CLIENT будет на винде?
он ведь XP не поддерживает - это на всегда или временно?
Откуда инфа? На XP пока не тестил, но надо будет, без XP оно вообще не надо.
Есть проблемы c компиляцией libzmq.dll в режиме совместимости с XP. Просто не компилируется с ключем /MT. Пока не разбирался, но думаю решаемо.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39336623
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Есть там засады. Связанное с IPv6 как понял отключается дефайном ZMQ_HAVE_WINDOWS_TARGET_XP.
Но не все отключается. Они там местами используют WinAPI от Windows Vista+, например SleepConditionVariableCS() это лечить надо переписыванием кода, ConditionVariable есть в С++ 11, попробую заменить на них, может получится.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39336633
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Неприятно, но не смертельно. В крайнем случае можно вернуться к DILLER-ROUTER и более старой версии работающей в XP, все равно на верхнем уровне я закладываюсь на возможные потери важных сообщений.

А так да, главная засада опенсорца - игнорирование старых ОС, да и железа тоже. Пытался ставить линукс на не очень старенький ноут с Pentium M, получал ответ "процессор не поддерживает PAE", может путаю, не PAE а что-то похожее, но суть в том что интел конкретно из этого проца убрал то что было и в более ранних мобильных и в десктоп аналогах.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39336709
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T,

Иногда это по ошибке делается - разработчики сидят на i7 :)
я вот у себя в опенсорс-проекте добавил libtorrent и не знал, что если VC++ 2015 не указан тип оптимизации
она автоматом собирается с SSE2!

и через день после обновления версии получил письмо:

Флай 19969 падает при загрузке, SSE2 команды...
Откатился на 19935.
AMD Duron. 1.2 ГГц. 512 Мб.


а поддержка XP для пользователей пока еще очень актуальна - особенно в отдаленных уголках России.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39336783
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPAя вот у себя в опенсорс-проекте добавил libtorrent и не знал, что если VC++ 2015 не указан тип оптимизации
она автоматом собирается с SSE2!
Наступал на эти грабли недавно 18816450 . Хуже всего что с запретом SSE2 невозможно скомпилировать в 2015.
Пришлось тогда вернуться на MSVC 6. В данном случае можно пожертвовать владельцами антикварного железа.
PPAа поддержка XP для пользователей пока еще очень актуальна - особенно в отдаленных уголках России.
Кроме того есть еще продвинутые линуксоиды, Wine эмулирует XP, а как там с более поздним WinAPI - большой вопрос.

Попробую поизучать исходники. Из нового кроме сокетов ZMQ_CLIENT еще много чего добавилось, например zmq_atomic_*()
Может несовместимые нововведения не касаются ZMQ_CLIENT, тогда есть шанс просто исключить вызовы WinAPI отсутствующего в XP.
Попробую тупо позаменять несовместимое на MessageBox(). Что получится - напишу.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39336964
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Оказалось не все так просто. Переписал. Даже быстрее стало работать. Под XP запускается, но иногда затыкается при больших нагрузках. Тут топик поднял по этому поводу.

Есть еще вторая проблема - не компилируется как static. Надо рантайм VS2015. Думаю решаемо, но сначала надо с зависаниями разобраться.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337348
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Переделал код под XP. Мой тест стал быстрее: 2.4 сек. против 4.3 сек с родным кодом.

Инструкция по правке проекта для MSVC2015:

В свойствах проекта libzmq поменять:
Platform toolset: v140_xp
C/C++ - Preprocessor – Preprocessor definition добавить ZMQ_HAVE_WINDOWS_TARGET_XP; {то что было}

Правки в коде:
tcp_address.cpp
строка 248
Код: plaintext
1.
	if_name_result = if_indextoname(index, buffer);


заменить на
Код: plaintext
1.
2.
3.
#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
	if_name_result = if_indextoname(index, buffer);
#endif


condition_variable.hppЗаменить класс condition_variable_t на этот
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
#ifdef ZMQ_HAVE_WINDOWS_TARGET_XP
#include <condition_variable> 
#include <mutex> 
#endif

    class condition_variable_t
    {
    public:
        inline condition_variable_t ()
        {
#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
			InitializeConditionVariable (&cv);
#endif
        }

        inline ~condition_variable_t ()
        {

        }

        inline int wait (mutex_t* mutex_, int timeout_ )
        {
#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
			// Исходный код
			int rc = SleepConditionVariableCS(&cv, mutex_->get_cs(), timeout_);

			if (rc != 0)
			return 0;

			rc = GetLastError();

			if (rc != ERROR_TIMEOUT)
			win_assert(rc);

			errno = EAGAIN;
			return -1;
#else
			std::unique_lock<std::mutex> lck(mtx); // захватываем mtx
			mutex_->unlock(); //  // освобождаем mutex_ 
			int res;
			switch (timeout_) {
			case INFINITE: // -1 ждем без указания времени
				cv.wait(lck); // освобождаем mtx и висим на cv
				break;        // захватываем mtx при выходе из cv.wait()

			case 0: // эмулируем истечение таймаута
				errno = EAGAIN;
				res = -1;
				break;

			default: // Сюда никогда не заходит. Все вызовы с -1 и 0
				if (cv.wait_for(lck, std::chrono::milliseconds(timeout_)) == std::cv_status::timeout) {
					// По истечению таймаута SleepConditionVariableCS() == 0
					errno = EAGAIN;
					res = -1;
				} else {
					res = 0;
				}
			}
			lck.unlock(); // освобождаем mtx
			mutex_->lock(); // захватываем mutex_
			return res;
#endif
		}

        inline void broadcast ()
        {
#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
			WakeAllConditionVariable(&cv);
#else
			std::unique_lock<std::mutex> lck(mtx);
			cv.notify_all();
#endif
        }

    private:

#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
		CONDITION_VARIABLE cv;
#else
		std::condition_variable cv;
		std::mutex mtx;
#endif

        //  Disable copy construction and assignment.
        condition_variable_t (const condition_variable_t&);
        void operator = (const condition_variable_t&);
    };


или отдельно прописать каждую замену. Структура такая:
Код: plaintext
1.
2.
3.
4.
5.
#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
		родной код
#else
		код для XP (копировать из кода выше)
#endif


Остался вопрос со static сборкой, чтобы рантайм MSVC2015 не ставить.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337418
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Собрал static. Не надо никаких рантаймов. Под XP работает.

Для желающих повторить: скачать исходники , просто распаковать архив в корневую папку (не уверен что вечно будет работать замена файлов исходников) и скомпилировать в VC2015 проект из папки libzmq_VC2015.

Хотел готовую DLL выложить, но сюда не лезет, она 0,5 Мб (0,3 в архиве)

PS Там стандартное ограничение на 64 соединения, т.к. используется select(). Думаю этого достаточно для клиента.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337455
Товарищ младший сержант
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TПоизучал что реально происходит в inproc - пришел к выводу что не надо никаких указателей для inproc соединений, там и так указатели передаются.
Точнее если сообщение маленькое (5 байт например), то само сообщение, если большое (100 байт пробовал), то указатель, а содержимое сообщения остается в памяти там же где и было. Т.е. ZMQ сама отлично решает как быстрее доставить.

Итого: сообщение можно принимать с TCP соединения и сразу отправлять в inproc очередь на обработку без какого-либо шаманства.
...
Ага, тоже вовсю inproc использую, даже для простых случаев многонитевого приложения: очень удобно и быстро.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337456
Товарищ младший сержант
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TЕсть там засады. Связанное с IPv6 как понял отключается дефайном ZMQ_HAVE_WINDOWS_TARGET_XP.
Но не все отключается. Они там местами используют WinAPI от Windows Vista+, например SleepConditionVariableCS() это лечить надо переписыванием кода, ConditionVariable есть в С++ 11, попробую заменить на них, может получится.
А я просто не самую новую версию libzmq использую. Ибо поддержка WinXP еще очень важна.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337457
Товарищ младший сержант
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TСобрал static. Не надо никаких рантаймов. Под XP работает.

Для желающих повторить: скачать исходники , просто распаковать архив в корневую папку (не уверен что вечно будет работать замена файлов исходников) и скомпилировать в VC2015 проект из папки libzmq_VC2015.

Хотел готовую DLL выложить, но сюда не лезет, она 0,5 Мб (0,3 в архиве)

PS Там стандартное ограничение на 64 соединения, т.к. используется select(). Думаю этого достаточно для клиента.
Ух ты, класс!
Надо на всякий случай перешерстить интерфейсы, для Delphi - биндинга...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337495
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337496
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Товарищ младший сержантНадо на всякий случай перешерстить интерфейсы, для Delphi - биндинга...
Как минимум добавить вызовы zmq_msg_routing_id() и zmq_msg_set_routing_id()

У сокета ZMQ_SERVER другая логика работы (по сравнению с ZMQ_ROUTER), он каждому клиентскому соединению присваивает routing_id (по сути ID соединения) и его надо явно указывать если с нуля генеришь сообщение. В моих примерах этого не видно, т.к. я обратно отправляю исходное сообщение, а там уже изначально стоит routing_id.
Пока не нашел ответа на вопрос: Надо ли на клиенте явно подставлять routing_id? Пока не разбирался, надо будет потестить что происходит при обрыве TCP-соединения и реконнекте.

Еще большой плюс ZMQ_CLIENT-ZMQ_SERVER в том что сообщения не теряются. Например если ZMQ_DEALER установил соединение, затем отправил, затем стартанул ZMQ_ROUTER, то ZMQ_ROUTER ничего не получит, а ZMQ_SERVER - получит. ZMQ_CLIENT блокируется при отправке если сообщение не влазит в буфер отправки, ZMQ_SERVER в таком случае получает ошибку при отправке.

И про 64 соединения уточню: это 64 TCP соединения , можно увеличить, задав нужный FD_SETSIZE перед #include Winsock2.h в файле windows.hpp
Код: plaintext
1.
2.
3.
#define FD_SETSIZE 1024
#include <winsock2.h>
...



Добавили группу функций zmq_atomic_counter_*() как понимаю это потокобезопасные счетчики. Не знаю нужны ли они в дельфи, а мне не актуально, в С/С++ есть std::atomic<>

В zmq.h есть какие-то zmq_msg_group() и zmq_msg_set_group(), но описаловки по ним никакой не нашел. Может в будущем напишут зачем они.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39337501
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPADima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls
Попробую. С английским у меня все плохо. Читать немного получается, а написать - засада. Поймут ли автоперевод?
Если найдутся желающие написать за меня - я не против. Надо им заслать только то что добавить в код, написано тут 19839048 .

Мой проект для МС2015 наверно не стоит туда слать, т.к. делал его лишь бы скомпилировалось. По хорошему сборку под XP впилить отдельным пунктом в имеющийся у них проект. В тюнинге проектов MSVC я не спец.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39340046
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPADima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls
Отправил. Приняли. Можете пользоваться. Проект для сборки .
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39341094
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
[quot Dima T]PPADima T,
Отправил. Приняли. Можете пользоваться.

Круто. спасибо! на выходных протестирую.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39351519
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TPPADima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls
Отправил. Приняли. Можете пользоваться. Проект для сборки .
А ты czmq - используешь?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39351805
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДА ты czmq - используешь?
Нет, свою обертку написал.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39352305
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TчччДА ты czmq - используешь?
Нет, свою обертку написал.
Пожалуй, мудрое решение.
Куда быстрее было сделать то, что нужно именно тебе на основе ядра, чем разбираться с тем, что кто-то делал "под себя".
...
Я довольно долго разбирался с функционалом czmq, в итоге в реальных приложениях использую очень малое подмножество методов czmq, думаю, что их можно было достаточно быстро реализовать средствами Delphi.

Ядро (libzmq) обычно собирается без проблем, или с минимальным стуком в бубен, а в совокупности с czmq (и с libsodium) часто уже приходится париться: то под новую версию MS VS что-то не собирается, то что-то исключили из поддержки, то еще что...
Плюс поддерживать согласованный биндинг в актуальном состоянии для двух библиотек напряжней, чем для одной.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39352410
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДКуда быстрее было сделать то, что нужно именно тебе на основе ядра, чем разбираться с тем, что кто-то делал "под себя".
Я так понимаю в czmq кроме самого ZMQ дополнительно собран попутно используемый функционал, который приведен к кроссплатформенному виду. Работа с потоками, файловой системой, временем и т.д. Лично мне было интересно все что связано с многопоточностью, т.к. в виндовсе и линуксе все по разному.
Но сегодня это не актуально, т.к. в С++11 добавили все связанное с многопоточностью.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39352420
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
С libsodium тоже не стал разбираться. Не настолько важная инфа у меня чтобы усложнять работу стойким шифрованием.
Сделал самодельный Диффи-Хеллман для обмена ключами (64 бита) и CBC шифрование. Плюс CRC32 в начало сообщения, чтобы данные окончательно перемешались после шифрования.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39352685
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TС libsodium тоже не стал разбираться. Не настолько важная инфа у меня чтобы усложнять работу стойким шифрованием.
Сделал самодельный Диффи-Хеллман для обмена ключами (64 бита) и CBC шифрование. Плюс CRC32 в начало сообщения, чтобы данные окончательно перемешались после шифрования.

Мда... "фантазией они не отличались" - (с). В смысле - все как у меня, почти. :) Плюс я еще подписываю блок с помощью аппаратно-реализованного ECC (с использованием Guardant Sign)
...
Насчет CRC32 в качестве "соли" - не очень хорошо. Ибо значение CRC32 постоянно для неизменного блока данных.
Таким образом, один и тот же блок данных после зашифрования одним и тем же ключом будет выглядеть одинаково, что снижают стойкость криптосистемы.
...
В начале обычно втыкают блок случайных данных.
Например, размером, равным ключу шифрования. Этим блоком шифруют сообщение, а сам блок шифруют сеансовым ключом, сгенерированным по алгоритму Д-Х. Таким образом, зашифрованные данные получаются всегда разными, даже если шифруются одинаковые блоки (даже в случае не-ECB режима шифрования).
...
Впрочем, не госсекреты ведь мы защищаем, а с хакерами боремся.
...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39352772
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДНасчет CRC32 в качестве "соли" - не очень хорошо. Ибо значение CRC32 постоянно для неизменного блока данных.
Таким образом, один и тот же блок данных после зашифрования одним и тем же ключом будет выглядеть одинаково, что снижают стойкость криптосистемы.
Повторений у меня нет, т.к. внутри еще номер сообщения идет (счетчик на стороне отправителя), для ответов используется.
CRC32 удобно, т.к. одновременно и соль и подпись/контрольная сумма для проверки корректности принятого. Считается быстро и места немного занимает.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39352777
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TчччДНасчет CRC32 в качестве "соли" - не очень хорошо. Ибо значение CRC32 постоянно для неизменного блока данных.
Таким образом, один и тот же блок данных после зашифрования одним и тем же ключом будет выглядеть одинаково, что снижают стойкость криптосистемы.
Повторений у меня нет, т.к. внутри еще номер сообщения идет (счетчик на стороне отправителя), для ответов используется.
CRC32 удобно, т.к. одновременно и соль и подпись/контрольная сумма для проверки корректности принятого. Считается быстро и места немного занимает.
ОК, все зависит от задачи.
К примеру, у меня на основе zmq реализовано общение с сервером защиты. Трафик невелик. В данном случае обеспечение неповторяемости данных в трафике - вещь обязательная, для защиты от табличной эмуляции.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39391236
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TPPADima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls
Отправил. Приняли. Можете пользоваться. Проект для сборки .
Опять сломали. Проект libzmq "для WinXP" не собирается:

ЭГГОГОшибка LNK2019 ссылка на неразрешенный внешний символ _zmq_z85_decode в функции "public: int __thiscall zmq::options_t::set_curve_key(unsigned char *,void const *,unsigned int)" (?set_curve_key@options_t@zmq@@QAEHPAEPBXI@Z) libzmq D:\libzmq\builds\msvc\vs2015_xp\options.obj 1

Решение: в список файлов проекта libzmq добавить файл zmq_utils.cpp.

Теперь собирается.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39450211
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Похоже, что nanomsg таки сдулся, автор несколько месяцев не модифицирует исходники... "сообщество" что-то изредка правит, и все...

А zeromq - напротив, цветет и пахнет, несмотря на то, что умер один из основных разработчиков.
Вот что значит поддержка со стороны большой конторы.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39478667
Коллеги, приветствую!
Стою на пороге тотального переписывания сетевого кода в клиент-серверном приложении.
Половину дня просидел изучая что русскоязычные люди пишут про ZeroMQ.
Очень нравится, ясно что надо изучать гайд с офф сайта.
Но не хочется терять время, т.к. кое-что насторожило.
Может вы мне поможите, просветите:
1) на ОС Windows от WinXP (самая ходовая по регионам необъятной родины) нужен и сервер и клиент, ZeroMQ умеет XP или поддержку отключили и больше не будет? Ну т.е. я вижу что на гитхабе есть проект _xp, но вот к примеру сокеты ZMQ_SERVER и ZMQ_CLIENT будут на ней работать? И по вашим ощущениям, не выпилят с очередным релизом (и не останусь я потом сидеть с необновляемой библиотекой?)
2) С Vista и т.д. сервер сможет держать больше сотни коннектов? Т.е. ZeroMQ реально юзает только select в винде, APC не умеет?
3) На linux ZeroMQ умеет МНОГА коннектов? Мне надо 14 тыс постоянных (но слабоактивных) для мониторинга и вялого обмена сообщениями.
5) Где-то прочёл что опасно ZeroMQ смотреть в эти ваши интернеты, заDDOSят мол. Есть у кого опыт, соображения на этот счёт, правда защиты от мусора нет совсем?
6) Технический момент: в итоге-то решение найдено по отключению "мёртвых" клиентов, да? Т.е. я беру любой протокол с поддержкой хардбитинга (или пинг-понга, если есть) и использую, верно?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479311
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон АксёновКоллеги, приветствую!
Стою на пороге тотального переписывания сетевого кода в клиент-серверном приложении.
Половину дня просидел изучая что русскоязычные люди пишут про ZeroMQ.
Очень нравится, ясно что надо изучать гайд с офф сайта.
Но не хочется терять время, т.к. кое-что насторожило.
Может вы мне поможите, просветите:
1) на ОС Windows от WinXP (самая ходовая по регионам необъятной родины) нужен и сервер и клиент, ZeroMQ умеет XP или поддержку отключили и больше не будет? Ну т.е. я вижу что на гитхабе есть проект _xp, но вот к примеру сокеты ZMQ_SERVER и ZMQ_CLIENT будут на ней работать? И по вашим ощущениям, не выпилят с очередным релизом (и не останусь я потом сидеть с необновляемой библиотекой?)
2) С Vista и т.д. сервер сможет держать больше сотни коннектов? Т.е. ZeroMQ реально юзает только select в винде, APC не умеет?
3) На linux ZeroMQ умеет МНОГА коннектов? Мне надо 14 тыс постоянных (но слабоактивных) для мониторинга и вялого обмена сообщениями.
5) Где-то прочёл что опасно ZeroMQ смотреть в эти ваши интернеты, заDDOSят мол. Есть у кого опыт, соображения на этот счёт, правда защиты от мусора нет совсем?
6) Технический момент: в итоге-то решение найдено по отключению "мёртвых" клиентов, да? Т.е. я беру любой протокол с поддержкой хардбитинга (или пинг-понга, если есть) и использую, верно?

1. ХР использовать можно, см. первое сообщение на этой страничке. Мы-используем (вернее, у нас есть покупатели, использующие WinXP). Сокеты типа ZMQ_SERVER и ZMQ_CLIENT, и также протокол TCP iPV6 будут недоступны.
И да, обязательно рано или поздно поддержку XP выпилят: 2017 год на дворе. И что? Люди до сих пор zmq версий 2.* используют, "чтобы работало на всём". В данный момент мы используем последнюю (опубликованную 2016/12/32) версию v4.2.1, собранную именно для WinXP (с "вкомпиленным" рантаймом), если надо - могу прислать.

2. Вопрос не вполне понятен. Если интересна внутренняя реализация zmq, можно посмотреть исходники. Например, метод zmq_Poll() (см. файл soсket_poller.cpp)в винде использует select() (из winsock2.h). Есть ограничение на число сокетов, используемых в zmq_poll() -> не более 1024 (можно обойти и увеличить, многократно писали как, примерно в 50 раз (фактически ограничение связано с числом доступных tcp портов в системе).

Но, к примеру, я тестировал сервер всего с одним (не считая INPROC сокета) TCP сокетом типа ROUTER с парой тысяч входящих (активных!) коннектов, в качестве сервера была обычная офисная машинка с Win7x64, сервер загружал процессор на 5-7%. Естественно, можно и больше, но конкретно с моим софтом каждый отдельный клиент создает в ОЗУ довольно большой внутренний контекст, и в 32-битном приложении сие вызовет скорый затык, но для меня вполне достаточно.

3. Что такое linux - не знаю, и пока не планирую восполнять сей пробел. Я бы на вашем месте написал тест, приближенный к требованиям пятки левой ноги, работы совсем немного.

4. Да. :)

5. Пишут, что это было раньше, до какой-то из версий библиотеки 2.*. Дальше я не читал, поэтому ничего более сказать не могу, ибо использовал zmq только в локальных сетях. Возможно, скоро ситуацию изменится, и мы вылезем в интернет, вот тогда и будем разбираться.

6. Мертвых клиентов рекомендуют "стряхивать". Или периодически(раз в сутки - в час-в минуту, по ситуации) , или когда их (мертвых) становится слишком (по мнению серверного софта) много. Делается тупым образом: сервер разрушает входящий сокет и создает новый, живые клиенты реконнектятся, мертвые - нет.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479316
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон Аксёнов 5) Где-то прочёл что опасно ZeroMQ смотреть в эти ваши интернеты, заDDOSят мол. Есть у кого опыт, соображения на этот счёт, правда защиты от мусора нет совсем?
обычно это не актуально, nginx на внешку, как правило, решает такие проблемы - а что с ним делать, знает большинство админов
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479435
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)Антон Аксёнов 5) Где-то прочёл что опасно ZeroMQ смотреть в эти ваши интернеты, заDDOSят мол. Есть у кого опыт, соображения на этот счёт, правда защиты от мусора нет совсем?
обычно это не актуально, nginx на внешку, как правило, решает такие проблемы - а что с ним делать, знает большинство админов

подробнее - как nginx cможет прозрачно прокинуть коннект до zmq и тем более защитить от ddos?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479455
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
PPAподробнее - как nginx cможет прозрачно прокинуть коннект до zmq и тем более защитить от ddos?
хотя бы вот так , хоть и experimental, но всё равно лучше чем напрямую. ещё где-то проекты были

ну а как от ddos защищаться, это уже к админам nginx
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479463
энди
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А использовать очередь от Амазона? С одной стороны конечно стороннее решение, с другой вся головная боль о поддержке соотвествующей инфтраструкруты ложится на чужие плечи, а бесплатных сообщений в месяц там много на что хватить может. Мы использовали, правда в софте на Андроид, весьма неплохо работало.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479476
чччД,

Спасибо за столь обстоятельный ответ!
Что значит "вкомпиленый" - это не надо будет dll с собой таскать? Мы же про Delphi говорим, да?
В любом случае, высылайте :) Coriolis inbox ru Спасибо!
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479623
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон АксёновЧто значит "вкомпиленый" - это не надо будет dll с собой таскать? Мы же про Delphi говорим, да?

В случае с делфи таскать dll от zmq в любом случае придется, но она будет одна без dll-рантайма от микрософта.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479625
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон АксёновчччД,

Спасибо за столь обстоятельный ответ!
Что значит "вкомпиленый" - это не надо будет dll с собой таскать? Мы же про Delphi говорим, да?
В любом случае, высылайте :) Coriolis inbox ru Спасибо!
"Вкомпиленный" == речь о рантайме SDK C++ MS VS 2015. Все теперь включено в libzmq.dll, больше не потребуются другие dll, типа msvcp120.dll и msvcr120.dll.
Т.е., одну dll (libzmq.dll) все же таскать с собой придется. :)
...
Выслал.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479633
чччД, PPA,

А, понял, я и забыл про рантаймы :) А то я уж было подумал что есть какой-то джедайский способ компилить файлы для инклуда в проект)))
А dll потаскать можно (хотя и не очень хочется конечно). Думаю хуки поставлю на CreateFile, ReadFile и буду грузить из памяти модуль, никто не пробовал так?

Файлы на почту пока не пришли, если что.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479645
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон Аксёнов,
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479658
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД,

Аттач 2 кб?
вероятно гугл отрезал dll-ку
лучше такие вещи кидать через облако или в архив с паролем сувать :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479662
Я так и не получил письмо, по скрину - адрес верный. Нигде нет, ни в папках со спамом, вообще нигде. Странно.
Может, правда, gdrive, ydisck... ?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479673
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479680
чччД,

Здорово, тут еще и обёртка высокоуровневая, спасибо!
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479689
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон АксёновчччД,

Здорово, тут еще и обёртка высокоуровневая, спасибо!
Громко сказано: обёртка самая минимальная: работа с только с самыми базовыми вещами (я добавлял функционал по мере надобности): создать контекст, создать сокет, сформировать составное сообщение, принять/отправить составное сообщение, создать "прикрепленный" поток (для работы поллера).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479697
чччД,

На первых парах мне это и нужно, лишнее только будет отвлекать. В любом случае контроль целостности, очерёдности доставки, пакетирование логических каналов в один физический - делать буду сам. Мне от ZeroMQ нужы только сокеты которые умеют переподключаться в случае обрыва, ну т.е. все плюшки сокетов ZeroMQ.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39479711
Фотография PPA
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон Аксёнов,

Пишете тут об успехах и проблемах... тоже интересно направление zmq + xp
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39480424
Коллеги, прошу прощения что взбаламутил тему, понял что ZeroMQ мне мимо.

У меня по техзаданию есть некие Состояния. Эта такая штука которую протокол отправки данных должен считать (сам!) в самый послелдний момент перед реальной отправкой пакета данных в сеть (в буфер сетевого драйвера). Понятно для того чтобы второй стороне ушли как можно более свежие данные и чтобы небыло спама.
А у 0MQ концепция другая совсем, там к очереди в частности и к низкоуровневым сокетам в целом не пускают.

Всплакнул, ушёл курить ICS, он ближе к железу...
А под никсы придётся lNet курить, (на котоые автор забил).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39480450
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон Аксёнов,

и что ж вы такое делаете-то? ...
...
Вчерась в одном из подразделений РЖД запустили систему на базе zmq, с перепугу заранее опций для тюнинга понавтыкали, а оно и так завелось, протестировали потенциально тяжелые случаи вроде кратковременных и долговременных обрывов связи, а также массовой заливки данных, и домой разъехались, сейчас в окошко на ураган смотрю...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39480467
чччД,

Ничего адски-специфичного, просто интерфейс.
-много прогресс-баров (наша БД позволяет а мы этим вовсю пользуемся.)
-много таблиц "я покажу тебе ВСЕ строки" с большим бегунком, но грузящие только нужные строки с сервера, соотвтетсвенно при скроле надо считать только действительно те строки на которые юзер видит в гриде. Тут главная мера ввести задержку, так сейчас и работает, но если сам сетевой движок будет брать текущую позицию (опосредованно через состояние) то эту задержку можно сильно уменьшить, визуально выглядит очень круто.
...
Отдельная песня, стандартный аргумет "зачем показывать сразу всё, покажи по буквам алфавита/категориям пусть юзер начнёт фильтровать и результат уже показывай" и т.д., на самом деле наболело - кажыдй пытается придумать аргументы что такой подход неверн, но он востребован в реальной жизни, пользователи его любят (потому что удобный). Чем он удобен: пользователь ищет позицию в номенклатуре, он не помнит её название (либо как точно пишется) но хорошо знает что по алфавиту она следует за точно известной ему позицией (или перед ней). Профит, он ищет известную ему позицию и перемещает курсор вверх/вниз. Такая схема работает у нас годами (скоро десятилетиями), сломать её - это ну я не знаю с чем сравнимо, да ни с чем)))
Такой большой абзац потому что, повторюсь, наболело)
...

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

-В дальних планах передача видеопотока, тут то же самое - кадр это состояние: один поток обновляет состояние а другой считывает и передаёт в сеть - в итоге получаем потерю кадров вместо переполненного буфера при кратковременных затыках с сетью (понятно что тут тоже будет переполненный буфер, потому что отправляющая сторона только через heartbeat interval поймёт что что-то не так и перестанет отправлять, но тут буфер будет расти небольшой интервал а в zmq по WaterMark)

Кстати, а WaterMark не поможет ли мне опосредованно понять что сетевой буфер отправки переполнился?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39480472
Т.е. если я поставлю WaterMark в 1 (что-то маленькое), поулчится 0mq не вернёт мне управления пока предыдущий пакет действительно не удёт?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39480497
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Антон Аксёнов,

у меня тоже все записи сразу клиент видит (вернее, он так думает, что видит), совсем просто: от сервера сперва получаешь списик id's - очень быстро (1млн id's - всего лишь 4 мегабайта), при отображении записей в окне я знаю (мне об этом сообщает компонент отображения) диапазон id's для показа - только записи с этими id's я и подгружаю в кэш. Никакого zmq, использую Firebird & fib+, в качестве средства отображегия - VTV и DevEx, они умеют работать в таком режиме практически "искаропки".
...хотя, если у тебя на клиенте показывается не миллион, а миллиард записей - такой способ уже не годится: 4Гб данных id's тянуть на клинта не гут.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39480535
чччД,

Ага, у меня и такой подход есть, но для дерева (тоже VTV использую).
Нет, записей меньше ))) до 20 тысяч, это если справочник номенклатуры, а обычно меньше. Но тут есть две проблемы.
Одна проблема - в натуральных индексах, тудыть их растудыть. Натуральный СОРТИРОВОЧНЫЙ индекс, представьте себе (например, наименование в верхнем регистре). Так повелось (nosql под названием MSM/GT.M, он сам толкает к такому дизайну). Я единственно как смог улучшить базу - это свести все места изменения ключевых данных в одно и там считать количество сущностей в каждой таблице при апдейтах, это ведёт ко второй Проблеме - при передаче всех индексов одним сжатым паком мне придётся читать всю таблицу, затягивая её в кэш (который, к слову, маленький, ибо сервера у меня - такие же рабочие станции, по железу). А так - отправил первые 25-40 строк и норм.

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

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

Чем глубже - тем страшнее, да? Так и живём :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39480778
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччДZeroMQ Особенности High-level API CZMQ :

Автоматизация обслуживания сокетов. К примеру, при закрытии контекста все его сокеты обработка будут также закрываться. При этом в некоторых случаях для сокетов можно назначить таймайт.
...

Интересно:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
//  Create an attached thread. An attached thread gets a ctx and a PAIR
//  pipe back to its parent. It must monitor its pipe, and exit if the
//  pipe becomes unreadable. Do not destroy the ctx, the thread does this
//  automatically when it ends.
type 
  p_zthread_attached_fn = ^zthread_attached_fn;
  zthread_attached_fn = procedure(args: Pointer; ctx: p_zctx_t; pipe: Pointer); cdecl;
...
interface

  function zthread_fork(ctx: p_zctx_t; thread_fn: p_zthread_attached_fn; args: Pointer):
    Pointer; cdecl; external cZMQ_DllName;



zthread_fork создает и запускает "прикрепленную нить", фактически запуская cdecl - функцию типа zthread_attached_fn и автоматически создавая пару сокетов для общения с нитью, как и сказано в описании выше.

Так вот, контекст ZMQ (параметр ctx: p_zctx_t) не просто передается, а создается его копия в блоке данных нити.
В рамках нового контекста в нити и будет создаваться новый пул сокетов и связанных с ними коннектов, очередей и т.п.
Т.е., по завершении нити произойдет корректная финализация копии контекста и связанных с ним сокетов и проч.
Вот ведь чушь написал-то. Контекст zmq (указатель) - естественно, копируется. Просто в czmq контекст представляет собой структуру, включающую в себя zmq контекст (в данном случае - скопированный Pointer из оригинального контекста), пул сокетов, пул вторичных контекстов и т.п. При разрушении czmq контекста сперва разрушаются все вложенные элементы, а потом, если контекст не-вторичный - разрушается и сам zmq контекст. Получается, что в czmq контексте общий zmq контекст для первичного контекста и для всех всех таких вот "прикрепленных", а вложенные объекты в каждом czmq контексте - свои.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39482210
Вот и мне понадобились ZeroMQ.
Есть некий OPC-сервер, подписанный (Advise) на получение изменений тэгов.
При изменении данных сервер вызывает Callback-процедуру, в которую передает массив изменившихся тэгов.
Эти данные мне надо разослать на ~100 (пока) компов.
Казалось все просто: используем топологию "Издатель-подписчик" (PUB-SUB), и дело в шляпе. НО.
НО она (Callback-процедура) выполняется в отдельной нити. Передавать ей заранее созданный сокет низзяааа,
патамушта здесь написано: "Запоминаем: не используем (и не закрываем) сокеты нигде, кроме как нитях, их создавших." ,
а каждый раз в начале процедуры создавать, а в конце убивать сокет как-то не айс (имхо).

Вопрос к уважаемому сообществу, в первую очередь к чччД:
Можно ли как-нибудь по другому решить, и если да - то как?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39482226
Вроде вырисовывается такая схема:
1. Поток - Издатель. 2 сокета:
- PULL. получает по inproc сообщения от Callback-a и передает их сокету
- PUB. Рассылает сообщения по TCP клиентам.
2. Callback. 1 сокет:
- PUSH. Создается в начале процедуры, посылает сообщения по inproc Издателю. В конце процедуры убивается.

А?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39482270
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вик Торович,

оба твоих сообщения я не понял, совсем.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39482874
чччД, как-то так:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
{ Псевдокод ON}
function OnDataChange(pvValues: POleVariantArray); stdcall;
begin
{    pvValues - массив изменившихся тэгов.                                    *
  Надо разослать его клиентам ~ 100 компов в локалке.                         *
  Здесь и хочу использовать ZMQ сокеты.                                       *
  Поскольку функция выполняется в контексте потока, созданного OPC-сервером,  *
  сокет надо создавать здесь, каждый раз при входе в процедуру,               *
  и убивать на выходе. Как-то не очень... Вот и прошу совета.                 }
  Socket := zmq_socket(Context, ZMQ_PUB);
  zmq_bind(Socket, 'tcp://*:5555');
  // zmq_send - Рассылка тэгов ...                                            *
  zmq_close(Socket);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Context := zmq_ctx_new;
  SrvIf := CreateComObject(ProgIDToClassID('SERVAK')) as IOPCServer;
  SrvIf.AddItems(Items);       // Говорим OPC, какие тэги нас интересуют,     *
                               // около 15000 штук.                           *
  SrvIf.Advise(@OnDataChange); // OPC-сервер создает поток, и при изменении   *
                               // интересующих нас тэгов в контексте этого    *
                               // потока вызывает функцию OnDataChange        *
end;
{ Псевдокод OFF}
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483060
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вик Торович,

Странное заявлениеПоскольку функция выполняется в контексте потока, созданного OPC-сервером, сокет надо создавать здесь, каждый раз при входе в процедуру и убивать на выходе.

Неужели поток каждый раз создается в момент вызова коллбэка?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483172
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Если нити для каждого вызова коллбэка разные, я бы не стал связываться с ZMQ, ибо повторный bind() к тому же tcp порту ничего не даст, кроме ожидания момента, когда порт освободится. Мало того, что bind выполняется не мгновенно, потом к нему должны выполниться connect()'s клиентов со стороны SUB, потом еще придется при зверешении коллбека ждать завершения рассылки (задавать ненулевое значение linger для сокета).

Если нить одна и тот же - сокет можно создать и настроить однократно, (например при первом коллбэке).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483484
чччДВик Торович,
Неужели поток каждый раз создается в момент вызова коллбэка?
Конечно же нет :) Поток создается OPC при подписке на данные (Advise).
Но о нем ничего неизвестно до первого изменения данных, читай - вызова коллбэка.

чччДЕсли нить одна и тот же - сокет можно создать и настроить однократно, (например при первом коллбэке).
Ага. Именно так и сделал.
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
{ Псевдокод ON}
function OnDataChange(pvValues: POleVariantArray); stdcall;
begin
  if not Assigned(Socket) then
  begin
    Socket := zmq_socket(Context, ZMQ_PUB);
    zmq_bind(Socket, 'tcp://*:5555');
  end;
  // zmq_send - Рассылка тэгов ...                                            *  
  // zmq_close(Socket); // Закрывать надо где-то не здесь. А где?

end;
{ Псевдокод OFF}


Осталось понять, где делать закрытие сокета. Закрыть-то его из другого потока тоже нельзя. Или если очень хочется - то можно? (с).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483488
чччД, при разрушении контекста сокеты закрываются?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483490
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вик Торович,

можно попробовать схитрить, вызвав деструктор самому: поменять хитрый тег, сервер вызовет коллбэк, а там этот тэг, вот и все, все разрушаешь и на всякий выставляешь флаг: "больше работать не хочу"...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483491
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вик ТоровиччччД, при разрушении контекста сокеты закрываются?

Да, закрываются, но операция разрушения контекста переходит в режим ожидания завершения ввода-вывода сокетами.
Время ожидания задается в параметре linger для сокета, по умолчанию = -1 ("ждать вечно").

Вот тут 20599592 , кроме всего прочего, есть простейшая объектная оболочка, где все сокеты я создаю с linger = 0 ("не ждать, завершаться сразу").

Кстати, Linger в 0 рекомендует устанавливать и автор (исходный создатель) библиотеки zmq.

Кстати, сам контекст - он вполне себе thread - safe, его можно передавать в треды, поэтому просто разрушь "главный" контекст, а в коллбэке анализируй коды завершения работы с сокетами (если = -1 - это разрушение, делаем Exit например)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483494
Да, нашел в описании функции zmq_ctx_term.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483495
чччД,
Ага, я уже скачал это. Спасибо за инфо о Linger.
пишу потихоньку. Вроде получается.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483496
чччДВик Торович,

можно попробовать схитрить, вызвав деструктор самому: поменять хитрый тег, сервер вызовет коллбэк, а там этот тэг, вот и все, все разрушаешь и на всякий выставляешь флаг: "больше работать не хочу"...
Хм. мысли сходятся не только у дураков. Как раз сижу и излагаю коллеге этот сценарий :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483531
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вик Торович,

к сожалению, об особенностях схемы pub-sub я знаю не больше, чем написано ранее про модель "метеостанция": попробовал, повертел и отложил.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483547
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Про XPUB и XSUB полный бред написал:
16623545
16623546
Даже не могу вспомнить, с чего это я вдруг решил, что пара XPUB-XSUB каким-то чудесным образом реализует прямую связь между издателем и подписчиком.
...
~~~~~~~~~~~~~~~~~~~~~~~~

В действительность, пара XPUB-XSUB всего лишь является концентратором сообщений для схемы PUB-SUB , когда издателей несколько, и их адреса могут достаточно часто меняться.

Предположим, есть не один издатель, а несколько. Используем схему PUB-SUB . Никаких проблем: каждый подписчик коннектится не одному, а к нескольким издателям. Одним и тем же сокетом.

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

Вот для этого случая и нужна схема XPUB-XSUB (см. картинку ниже). Есть один подписчик "proxy" с сокетами PUB , который знает адреса всех издателей, и коннектится к ним сокетом XSUB .
Он получает все входящие сообщения. Он их публикует, используя сокет XPUB . Все остальные подписчики подписываются лишь к одному издателю - "прокси", используя сокеты SUB .
Таким образом, при изменении списка издателей, нужно всего лишь изменить "прокси". Подписчики менять не нужно.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483554
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Возникает вопрос - почему бы не использовать в '"proxy" обычную пару PUB-SUB ?

Можно. Но XPUB-XSUB умеет делать интересный фокус с подписками. Издатель подписывается на определенные сообщения,
XPUB транслирует эти подписки в специальные сообщения и передает их в XSUB . То есть, "proxy" пересылает эти спец.сообщения со стороны абонентов на сторону издателя.

Вот для этого и нужны XSUB и XPUB .
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39483558
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вик Торович,

есть отдельная глава, посвященная особенностям схемы "издатель-подписчик": http://zguide.zeromq.org/page:all#Chapter-Advanced-Pub-Sub-Patterns

Прочитал с удовольствием, правда, пока не придумал - куда применить... :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39588841
_Читатель_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Здравия всем.
Почитал. Интересно. Мне наиболее подошел бы Radio-dish pattern.
Но печалька - Radio-dish is still in draft phase. .

Кто-нибудь в курсе - проект умер или будет таки развитие?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39588877
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Читатель_Кто-нибудь в курсе - проект умер или будет таки развитие?
Какой конкретно "проект"?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39588925
_Читатель_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
чччД, ZeroMQ.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39588957
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Читатель_,

А ты, про это. Не знаю, там на английском все, я не понимаю.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651927
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Почитал тут про ZMQ и возник один вопрос по безопасности: вот в обычном TCP, как было сказано в начале статьи, в случае если нам передали неверный заголовок сообщения мы можем либо сбросить подключение вообще, либо прочитать испорченные данные в /dev/null и приступить к обработке следующего сообщения.
А вот вопрос: какова будет реакция ZMQ, если злоумышленник, зная его протокол, попытается передать валидный пакет, объемом 100500 гигабайт (объёмом, гарантированно большим чем оперативная память + своп на сервере)?
Можно ли как-нибудь лимитировать допустимый размер сообщений?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651931
s62
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp,

так ZeroMQ это же обычная библиотека поверх TCP. Наверняка можно и размер лимитировать, и заголовок проверять и т.д.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651939
s62
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
s62,

то есть "поверх" сокетов видимо.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651946
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
s62,

Ну так размер лимитирует приложение, а не TCP. А в ZeroMQ между приложением и TCP - библиотека, отсюда и вопрос - как в ней это сделать, раз я поступлении данных я узнаю только когда сообщение прочитано целиком (т.е. оперативка закончится раньше).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651954
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
__alekcvpПочитал тут про ZMQ и возник один вопрос по безопасности: вот в обычном TCP, как было сказано в начале статьи, в случае если нам передали неверный заголовок сообщения мы можем либо сбросить подключение вообще, либо прочитать испорченные данные в /dev/null и приступить к обработке следующего сообщения.
А вот вопрос: какова будет реакция ZMQ, если злоумышленник, зная его протокол, попытается передать валидный пакет, объемом 100500 гигабайт (объёмом, гарантированно большим чем оперативная память + своп на сервере)?
Можно ли как-нибудь лимитировать допустимый размер сообщений?

1. Тебя не смущает неоднократное упоминание того, что ZMQ - для "внутреннего" использования? В рамках локальных сетей, или отдельного компа (для межпроцессного взаимодействия) или даже для внутрипроцессного межнитевого взаимодействия. Откуда возьмутся злоумышленники?
1.1. Про новые возможности ("работа в открытых сетях") я знаю совсем немного, но они появились, например, средства аутентификации.
2. "Сбросить" соединение нельзя, можно пересоздать сокет, что и рекомендуется для разных случаев, например, когда слишком много входящих соединений "зависло" (т.е., клиент подключился, поработал и у шел), сие один из паттернов использования zmq.
3. Да, можно установить опции сокета, ограничив размер сообщения и/или буфера под сообщения: http://api.zeromq.org/4-0:zmq-setsockopt.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651957
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
s62alekcvp,

так ZeroMQ это же обычная библиотека поверх TCP. Наверняка можно и размер лимитировать, и заголовок проверять и т.д.
Не, нельзя.
И tcp - лишь один из протоколов, которые может использовать zmq.

Ну да, есть там средства мониторинга ("это кто там подключился?" и т.п.), но zmq как раз для того, чтобы работать атомарными сообщениями: отправляешь сообщение (кадр или несколько кадров данных), и всё, на стороне приема они либо получены либо нет.
Кадр - всего лишь блок ТВОИХ данных, без каких-либо заголовков, длиной от 0 до 2^61-1 байт, дальше фантазируешь сам.

В общем, если тебе нужны сокеты Windows - не нужно искать их в zmq, это другой уровень.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651967
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__1. Тебя не смущает неоднократное упоминание того, что ZMQ - для "внутреннего" использования? В рамках локальных сетей, или отдельного компа (для межпроцессного взаимодействия) или даже для внутрипроцессного межнитевого взаимодействия. Откуда возьмутся злоумышленники?
У меня и есть внутренняя сеть. Внутренняя сеть учебного заведения. Сказать откуда тут с высокой вероятностью могут взяться злоумышленники с шилом в заднице?.. К тому же чем принципиально отличается работа в закрытой сети и в открытой, с точки зрения транспортного уровня, я не очень представляю.

чччД__2. "Сбросить" соединение нельзя, можно пересоздать сокет, что и рекомендуется для разных случаев, например, когда слишком много входящих соединений "зависло" (т.е., клиент подключился, поработал и у шел), сие один из паттернов использования zmq. Т.е. она не умеет даже определять "отвалившиеся" сокеты? Или имеется в виду что "ушел" он оставив запущенное приложение?
чччД__3. Да, можно установить опции сокета, ограничив размер сообщения и/или буфера под сообщения: http://api.zeromq.org/4-0:zmq-setsockopt. Ага, спасибо, про ZMQ_MAXMSGSIZE и был вопрос.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651969
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__Ну да, есть там средства мониторинга ("это кто там подключился?" и т.п.), но zmq как раз для того, чтобы работать атомарными сообщениями: отправляешь сообщение (кадр или несколько кадров данных), и всё, на стороне приема они либо получены либо нет.
Кадр - всего лишь блок ТВОИХ данных, без каких-либо заголовков, длиной от 0 до 2^61-1 байт, дальше фантазируешь сам.

В общем, если тебе нужны сокеты Windows - не нужно искать их в zmq, это другой уровень.
Мне нужно организовать взаимодействие между парой сотен клиентов и сервером на разных машинах в локальной сети (потом, возможно и в интернете), при этом сеть доступна посторонним лицам и ограничить доступ к серверу (по сети) я не могу. Взаимодействие как раз в виде передачи сообщений размером от сотен байт до единиц мегабайт.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651974
Позвольте, встряну.
alekcvpчччД__1. Тебя не смущает неоднократное упоминание того, что ZMQ - для "внутреннего" использования? В рамках локальных сетей, или отдельного компа (для межпроцессного взаимодействия) или даже для внутрипроцессного межнитевого взаимодействия. Откуда возьмутся злоумышленники?
У меня и есть внутренняя сеть. Внутренняя сеть учебного заведения. Сказать откуда тут с высокой вероятностью могут взяться злоумышленники с шилом в заднице?.. К тому же чем принципиально отличается работа в закрытой сети и в открытой, с точки зрения транспортного уровня, я не очень представляю.






В этом и проблема. Надо представлять. Тут речь о многосерверной архитектуре (микросервисы и т.д.), это когда ты строишь архитектуру чего-то большого и крутого, когда у тебя не один сервер и куча клиентов а много серверов и между ними надо общаться.
Т.е., не для клиент-сервер это, а для сервер-сервер. Хотя xxxД вот дал ссылку что в этом направлении таки что-то делают (лично я могу понять почему - ZMQ очень крутая).
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39651975
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpТ.е. она не умеет даже определять "отвалившиеся" сокеты? Или имеется в виду что "ушел" он оставив запущенное приложение?

Имеется в виду, что "она" даже не знает, приконнектился кто-то или нет.
Все, что есть - это работа с сообщениями.

Ждешь сообщение - получил - обработал. Всё. :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652021
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvpчччД__2. "Сбросить" соединение нельзя, можно пересоздать сокет, что и рекомендуется для разных случаев, например, когда слишком много входящих соединений "зависло" (т.е., клиент подключился, поработал и у шел), сие один из паттернов использования zmq. Т.е. она не умеет даже определять "отвалившиеся" сокеты? Или имеется в виду что "ушел" он оставив запущенное приложение?
а что вас удивляет? какая разница ушёл он или нет, не посылает - значит Так не надо
не обвязке же решать что делать

когда-то когда связь была "очень быстрая" и "очень надёжная", сокеты постоянно отваливались - вот такая физика нашего мира
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652022
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Да, я понял, походу для моей системы, где кроме самого сообщения необходимо знать ещё и от кого оно пришло, эта штука не подходит.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652024
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpkealon(Ruslan),

Да, я понял, походу для моей системы, где кроме самого сообщения необходимо знать ещё и от кого оно пришло, эта штука не подходит.
Ну так добавь в сообщение кадр с нужной инфой.
Ты думаешь, "чистые" tcp сокеты на приеме волшебным образом эту инфу получают? Нет, ее с передающей стороны присылают. :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652040
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__Ну так добавь в сообщение кадр с нужной инфой.
Ты думаешь, "чистые" tcp сокеты на приеме волшебным образом эту инфу получают? Нет, ее с передающей стороны присылают. :)
В "чистом" TCP адресат привязывается к соединению, а тут мало того что придётся передавать информацию в каждом сообщении, так ещё и придумывать какой-то механизм защиты от её подмены.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652056
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpчччД__Ну так добавь в сообщение кадр с нужной инфой.
Ты думаешь, "чистые" tcp сокеты на приеме волшебным образом эту инфу получают? Нет, ее с передающей стороны присылают. :)
В "чистом" TCP адресат привязывается к соединению, а тут мало того что придётся передавать информацию в каждом сообщении, так ещё и придумывать какой-то механизм защиты от её подмены.

Я фигею, дорогая редакция - (с).

Дело твое, конечно, но решение всех твои "проблем" отлично описаны в открытых источниках по ZMQ - и механизмы защиты, и методы идентификации, и обеспечения устойчивой связи.

А если ты хочешь от zmq получить функционал нижнего уровня - то вообще непонятно, нафига тебе ZMQ.

И, если ты думаешь, что tcp пакет сложно подменить пятью строчками кода - то заодно передавай от меня привет Деду Морозу.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652059
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__А если ты хочешь от zmq получить функционал нижнего уровня - то вообще непонятно, нафига тебе ZMQ.
И, если ты думаешь, что tcp пакет сложно подменить пятью строчками кода - то заодно передавай от меня привет Деду Морозу.
Нет, функционал мне как раз нравится, но не хватает однозначной идентификации от кого пришло сообщение. Надо будет почитать.

Да, расскажи как пятью строчками подменить обратный адрес в tcp-пакете, а потом получить на него ответ на свой адрес? :)
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652085
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpчччД__А если ты хочешь от zmq получить функционал нижнего уровня - то вообще непонятно, нафига тебе ZMQ.
И, если ты думаешь, что tcp пакет сложно подменить пятью строчками кода - то заодно передавай от меня привет Деду Морозу.
Нет, функционал мне как раз нравится, но не хватает однозначной идентификации от кого пришло сообщение. Надо будет почитать.

Да, расскажи как пятью строчками подменить обратный адрес в tcp-пакете, а потом получить на него ответ на свой адрес? :)


"Пятью строчками" не подменить, да, лишку сболтнул.

А что ты подразумеваешь под "однозначной идентификацией", конкретно?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652089
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__"Пятью строчками" не подменить, да, лишку сболтнул.
А что ты подразумеваешь под "однозначной идентификацией", конкретно?
Ну, грубо говоря, после accept() и прохождения идентификации клиента ты можешь полученный сокет использовать как идентификатор клиента, т.е. сервер всегда знает от какого клиента пришло сообщение и может у себя хранить некоторые "характеристики" этого клиента, ассоциированные с ним (через сокет). И "подменить пакет" довольно сложно, т.к. это придётся делать на уровне ip-пакетов и, не вдаваясь в подробности, это довольно геморно.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652091
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpчччД__"Пятью строчками" не подменить, да, лишку сболтнул.
А что ты подразумеваешь под "однозначной идентификацией", конкретно?
Ну, грубо говоря, после accept() и прохождения идентификации клиента ты можешь полученный сокет использовать как идентификатор клиента, т.е. сервер всегда знает от какого клиента пришло сообщение и может у себя хранить некоторые "характеристики" этого клиента, ассоциированные с ним (через сокет). И "подменить пакет" довольно сложно, т.к. это придётся делать на уровне ip-пакетов и, не вдаваясь в подробности, это довольно геморно.

Ну и тут сокеты идентифицируются, если нужно. Если используешь пару сокетов типа REQ-REP, то сервер ВСЕГДА отправляет ответ тому, кто послал запрос, со стороны программиста вообще никаких действий не нужно, просто возвращаешь ответ в тот же сокет, откуда пришел запрос. Тут (без твоего участия) сообщение автоматически дополняется кадром идентификации (по умолчанию - случайное число), этот кадр отсекается перед передачей в обрабатывающий код, а потом автоматически дополняется при отсылке ответа.

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

Для третьих типов (например, PUB-SUB) идентификация вообще не нужна, ибо логически бессмысленна.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652092
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvp,

ну вот я клиент, создал сокет, задал ему в качестве идентификатора, например случайное 64-битное число.
Сервер, получив от меня сообщение, будет всегда отправлять ответ мне же, используя этот же идентификатор (часть сообщения).
Что тут можно подменить и с какой целью, конкретно?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652097
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__,

Ну да, выглядит как то что надо. Короче, надо будет глубже почитать документацию.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652098
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvp,

я так понимаю, что ты желаешь получить ip адрес "той стороны"?

ZeroMQ создавался, чтобы в т.ч. и спрятать от тебя процесс управление соединениями, чтобы ты сосредоточился именно на обмене сообщениями. Это самое управление соединениями включает в себя в том числе и автоматическое переподключение, что запросто может привести к изменению IP-адреса, а сокет zmq, подключенного с другой стороны, будет тот же самый. Т.е., даже если ты сможешь из нутра ZeroMQ вытянут IP-адрес удаленного корреспондента, совершенно нет гарантии, что этот адрес не изменится в процессе работы.

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

Или тебе не ip-шник нужен?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652100
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__Что тут можно подменить и с какой целью, конкретно?
Например, после это клиент может попытаться отправить серверу сообщение с другим ID, прикидываясь другим клиентом. Как он узнает этот "другой ID" мы пока оставим за скобками.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652102
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__alekcvp,
Или тебе не ip-шник нужен?
Мне нужна двусторонняя связь, правда "наоборот": есть сервер, к нему подключаются клиенты, он им рассылает сообщения, они на них отвечают. Всё. Ключевой момент: нельзя допустить чтобы какой-то клиент имел возможность отвечая на сообщение прикинуться другим клиентом.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652104
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpчччД__Что тут можно подменить и с какой целью, конкретно?
Например, после это клиент может попытаться отправить серверу сообщение с другим ID, прикидываясь другим клиентом. Как он узнает этот "другой ID" мы пока оставим за скобками.

Не-не-не. Нужно определиться, что за вид атаку мы отражаем, а потом уже бороться со злоумышленниками.
...
Касаемо твоего случая - сервер будет "думать", что у него два клиента...и?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652107
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpчччД__alekcvp,
Или тебе не ip-шник нужен?
Мне нужна двусторонняя связь, правда "наоборот": есть сервер, к нему подключаются клиенты, он им рассылает сообщения, они на них отвечают. Всё. Ключевой момент: нельзя допустить чтобы какой-то клиент имел возможность отвечая на сообщение прикинуться другим клиентом.

Если один клиент прикинется другим клиентом (уже существующим, подключенным), то сервер будет слать сообщения им по очереди (такая логика ZMQ). Если я ничего не путаю. Т.е., скорее всего, получится фигня на стороне клиента.
Сие очень легко проверить.
...
Если студенты не "под присмотром" ментора - что им мешает не менять свой адрес, а просто ответить за другого? А если под присмотром - в чем проблема?
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652112
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__Если студенты не "под присмотром" ментора - что им мешает не менять свой адрес, а просто ответить за другого? А если под присмотром - в чем проблема?
То, что никто не видит - что он там у себя делает на экране, а беготня по залу пресекается.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652125
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvpчччД__Если студенты не "под присмотром" ментора - что им мешает не менять свой адрес, а просто ответить за другого? А если под присмотром - в чем проблема?
То, что никто не видит - что он там у себя делает на экране, а беготня по залу пресекается.

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

Хотя, если у тебя студент сможет разобраться в протоколе, дистанционно (без бегони по залу) на лету извлечь идентификатор сеанса соседа, реассемблировать свое приложение и подменить идентификатор сеанса - что ему мешает узнать соседские id-шники оборудования и подменить ими собственные?

Тут да, только чистые Win-сокеты, с ними студенты уж точно не совладают.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652154
Фотография Дегтярев Евгений
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
мне кажется у alekcvp и чччД__ расходится понимание что такое клиент.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652364
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__Ну, не знаю. Идентификатор оборудования на сервере храни, что ли.
На основе идентификатора оборудования подписывай контрольные сообщения, отправляемые сервером.

Тут вопрос что проще - заморачиваться с идентификаторами и подписями, или просто сделать примитивную обвязку над сокетами для моей задачи.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39652528
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Дегтярев Евгениймне кажется у alekcvp и чччД__ расходится понимание что такое клиент.
Да я вообще что-то не по делу влез, человеку явно нечто другое нужно.
Больше не буду, извинения.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39702732
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Ну вот, зоопарк форматов проектов MS VS больше поддерживаться не будет .

Используем cmake, instead.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39702786
Мимопроходящий
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
14.09.2018 15:36, чччД__ пишет:
> Используем cmake, instead.

давно пора!
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39702833
чччД__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Мимопроходящий14.09.2018 15:36, чччД__ пишет:
> Используем cmake, instead.

давно пора!

Это не путь дельфиста.
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39702839
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
чччД__,
От оно как, XE давно систему сборок ипользует, а дельфийцы оказывается нет!!!!!!!

моргнул и на кнопке прочитал надпись "Презрительный осмотр", всё, пятница удалась
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39754785
Фэйтл Эра
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В конце ноября вышел стабильный релиз 4.3.0.

- ZMQ_MSG_T_SIZE - новая опция метода zmq_ctx_get():
Код: pascal
1.
2.
zmq_ctx_get (context, ZMQ_MAX_SOCKETS)
// - возвращает размер структуры zmq_msg_t. Наконец-то, кто-то догадался ибо размер менялся два раза за три года;



- возможность закрепления ядер процессора потокам ввода-вывода;
- улучшен способ идентификации вычислительных нитей;
- создан и развивается механизм идентификации и защиты данных для клиент-сервеных коммуникация: Generic Security Service Application Program Interface (GSSAPI);
- расширен механизм обеспечения кроссплатформенных таймеров;
- расширены сервисные средства (например, статистика работы прокси);
- тонкая настройка асинхронного многоканального обмена;
- тестируется возможность идентификации коннекта/дисконнекта корреспондентов сокетов типа ROUTER;

И еще куча интересного, убрано много старых глюков и добавлено много новых...
...
Рейтинг: 0 / 0
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
    #39754787
Фэйтл Эра
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Довольно часто встречаются заявления, что ZeroMQ больше не развивается, т.к. главный разработчик и идеолог(Martin Sustrik) ушел из команды разработчиков ZeroMQ, и развивает собственную версию - NanoMSG .
Сие - странно, ZeroMQ очень мощно развивается, именно в последние годы.
Появляются возможности, которые ранее было обойдены по "религиозным" причинам: например, не было возможности отсечь ненужные коннекты и, как следствие, было весьма опасно работать в открытых сетях.
Число пользователей растет. Для Delphi есть как минимум 4 варианта биндинга.
И, самое главное - ZeroMQ все еще можно собрать для WinXP! :).
...
Рейтинг: 0 / 0
260 сообщений из 260, показаны все 11 страниц
Форумы / Delphi [игнор отключен] [закрыт для гостей] / ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]