Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
Топик располагается на нескольких страницах: 1 2 3 4 5 6 7 8 9 10 11      [все]
 ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Типа, просто и понятно, но чем плохи сокеты Windows или чем не устраивает Indy или ICS или IPWorks! или ещё что...
И - где лучше не использовать.
23 сен 14, 06:52    [16607877]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вот что пишут.

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

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

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

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

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

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

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

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

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

- ...?
23 сен 14, 07:33    [16607904]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Есть вариант использовать готовые системы, ориентированные на обмен сообщения.
Они отлажены, стандартизированы, решают проблемы с балансировкой нагрузки, с обработкой ошибок, с динамической маршрутизацией, с кодировками и т.п.
К сожалению, эти системы - брокер - ориентированные. То есть, обычно предполагают наличие специального вычислительного процесса, который работает на выделенном железе и обслуживается специально выделенными людьми.
Следовательно, это стоит делать только для больших распределенных приложений с множеством компонентов, построенных большими командами.
...
PS: мы, например, для одной распределенной задачи использовали существующую сеть jabber - серверов, "паразитируя" на них.
Кодить, используя сообщения, а не tcp - поток, намного приятнее. :)
Позднее мы пытались отойти от использования публичных jabber -серверов и запустили на своем железе собственный сервер, но надежность системы в итоге упала: наш админ не смог обеспечить доступ к ресурсу в обещанном режиме 24х7...

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

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

ZeroMQ была создана для того, чтобы сделать возможной работу с сообщениями простым и дешевым образом, чтобы она могла работать в любом приложении, и была бесплатной (или со стоимостью, близкой к нулевой).
Она была разработана как библиотека, которую просто можно использовать без каких-либо зависимостей.
Она запускается на любой ОС и работает с любым языком программирования.
23 сен 14, 07:51    [16607918]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Поехали!

Сокеты.

Жизнь сокетов 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 - структурой:

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 начинает доставлять сообщения.

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

zmq_bind (socket, 'tcp://*:5555');
zmq_bind (socket, 'tcp://*:9999');
zmq_bind (socket, 'inproc://somename');


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

Пишут, однако, что для ipc транспорта допускается множественный биндинг, но нас это пока не касается: ipc пока реализован только на всяких линуксах.
23 сен 14, 09:34    [16608170]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Добавление:

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

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

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


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

zmq_connect(socket, 'inproc://#1');
...
zmq_connect(socket, 'inproc://my-endpoint'); 


То есть: 'имя протокола://уникальное имя коннекта'
23 сен 14, 09:56    [16608271]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Дальше я буду писать ZMQ вместо ZeroMQ.
Или даже 0MQ.

С целью экономии ресурса клавиатуры.
25 сен 14, 05:57    [16618938]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Шаблоны сетевых топологий ZeroMQ.

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



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

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

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

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


PUB - SUB
REQ - REP
REQ - ROUTER
DEALER - REP
DEALER - ROUTER
DEALER - DEALER
ROUTER - ROUTER
PUSH - PULL
PAIR - PAIR

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

Из документации:
Использование пар сокетов ZMQ в других комбинациях повлечет появление недокументированных эффектов и, возможно, чудовищные разрушения оборудования.
25 сен 14, 06:28    [16618950]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Поговорим о СООБЩЕНИЯХ 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().

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

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.
25 сен 14, 07:09    [16618967]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Если все же нужно отправить одно и то же сообщение более, чем один раз, и оно довольно большое, создаем еще одно сообщение, инициализируем его с помощью zmq_msg_init(), а потом используем zmq_msg_copy(). Будет создана копия исходного сообщения. Такой способ не создает копию сообщения, а просто создает копию ссылки на сообщение.
Теперь это сообщение можно отправить дважды (если нужно больше - создаем больше копий).

  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);

Сообщение будет полностью удалено только после того, как будет отправлена последняя копия.
25 сен 14, 07:22    [16618972]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
ZeroMQ также поддерживает сообщения, состоящие из нескольких частей (фрагментов, фреймов).

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

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


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

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

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


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

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

И так.

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

Кроме того:


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


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

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

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

После использования принятого сообщения, его нужно закрыть: zmq_msg_close().
При отправке сообщения этот метод вызывать не нужно. То есть, чисткой занимается получатель.
25 сен 14, 07:54    [16618997]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Ах, да.

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

В документации сказано то же самое.
25 сен 14, 08:02    [16619007]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Работа с несколькими сокетами ZMQ.

Во всех примерах, что мы рассмотрели ранее, схема работы была примерно одинаковой - в цикле повторялись одни те же действия:
    

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


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

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

Создадим простое приложение, которое покажет, как читать данные из неблокирующего сокета.
Будем читать данные из двух сокетов с использованием неблокирующего чтения. Приложение будет работать и как подписчик, и как работник в системе обработки параллельных задач.
25 сен 14, 08:18    [16619033]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вот исходник ридера, читающего данные и от нашей старой задачи "Ventilator" и от сервера метеостанции:

+

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().
25 сен 14, 08:43    [16619081]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Теперь: "правильный" мульиридер:

+


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.


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

type
  pollitem_t = record
    socket: Pointer; // Сокет ZeroMQ
    fd: Integer; // Стандартный сокет, представленный дескриптором файла
    events: Word; // Маска событий 
    revents: Word; // Маска событий 
  end;


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

  fItems: array[0..1] of pollitem_t;

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

  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.
25 сен 14, 09:32    [16619247]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
...

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

type
  pollitem_t = record
    socket: Pointer; // Сокет ZeroMQ
    fd: Integer; // Стандартный сокет, представленный дескриптором файла
    events: Word; // Маска событий 
    revents: Word; // Маска событий 
  end;


...


Честно говоря, не въехал, как использовать поле fd. Что "за стандартный сокет"?
25 сен 14, 09:35    [16619257]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Составные сообщения.

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

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

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

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

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

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

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:

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().
25 сен 14, 22:12    [16623222]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
чччД, читаю, очень интересно...Но всё равно возникает вопрос из заголовка - а для чего ?
Всё равно ведь ниже ZeroMQ лежат виндовые сокеты, а их не перепрыгнуть?
Можно примеры каких-нить настолько высоконагруженных приложений, в которых без стероидов ну никак ?...
25 сен 14, 22:17    [16623228]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
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 сообщений в секунду.
25 сен 14, 22:43    [16623279]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
чччД, а то, что асинхронные сокеты изобрёл Мелкософт, никого уже не смущает ?
4 ляма сообщений в секунду - это железо умалчивается ?
25 сен 14, 22:48    [16623293]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
не наезжаю, ни боже мой.
Я просто пытаюсь понять, чем эта либа отличается от другой прослойки типа RealThinClient - тм тоже изначально синхронные сокеты
25 сен 14, 22:50    [16623298]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
defecator
чччД, читаю, очень интересно...Но всё равно возникает вопрос из заголовка - а для чего ?
Всё равно ведь ниже ZeroMQ лежат виндовые сокеты, а их не перепрыгнуть?
...

Кроме того, ZMQ использует не только TCP, но и, например, inproc протокол для связи между потоками (а в UNIX-системах - между процессами).
...
У меня лично от Windows сокетов послевкусие. Особенно после асинхронных. "Ах, что-то пришло в буфер, посмотрим... Заголовок уже загружен? Нет еще, ну ладно... Опять сообщение - ура, заголовок загружен, и даже часть тела... аккуратно анализируем заголовок, и снова цикл ожидания загрузки остатка сообщения..."
А сообщения ZMQ - атомарны.
Конечно, атомарность сообщений ZMQ - не всегда благо. Например, при получении данных большого объема: сколько тебе послали, столько ты и получишь, все сразу. А с винсокетами ты можешь послать всех, кто (например) неправильно отправил заголовок, еще "в процессе", на принимая сообщения целиком.
По этой причине, если выставить ZMQ сокет "в интернет", его сразу могут завалить всяким мусором. Ну, если не принимать никаких мер. Но в локальной сети ведь злоумышленников быть не должно? :)

Т.е., это - не "серебряная пуля". А такой ничего себе ножичек.
25 сен 14, 22:54    [16623306]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
defecator
чччД, а то, что асинхронные сокеты изобрёл Мелкософт, никого уже не смущает ?
...

А они как раз и используются в ZMQ, вовсю.

defecator
...
4 ляма сообщений в секунду - это железо умалчивается ?

Там используется распределенная архитектура, там МНОГО железа. Для организации его в сеть там как раз используется ZMQ.
25 сен 14, 22:56    [16623310]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
defecator
...
Я просто пытаюсь понять, чем эта либа отличается от другой прослойки типа RealThinClient - тм тоже изначально синхронные сокеты

А в ZMQ - асинхронные...
...а про RealThinClient я вообще ничего не знаю.
25 сен 14, 22:58    [16623316]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Проблема динамического обнаружения

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




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

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

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

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

К сообщению приложен файл. Размер - 8Kb
25 сен 14, 23:33    [16623417]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Неожиданно набежали янычары. А вот теперь представим, что появился еще одни издатель. Нужно еще раз настроить каждого подписчика. Всех, 1000 шт. И каждый раз появление нового издателя повлечет за собой увеличение стоимости настройки.

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

Добавим брокер сообщений. ZMQ не поставляется с брокерами, но позволяет его легко построить.
Теперь Издатель-Подписчик схема будет выглядеть вот так:

К сообщению приложен файл. Размер - 11Kb
26 сен 14, 00:00    [16623492]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Таким образом, со статичным борокером сообщений задача решена.

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

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

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

ZMQ придет, порядок наведет!
26 сен 14, 00:12    [16623516]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Куда лучше реализовать брокер не в качестве транспорта сообщений, а в качестве поставщика адресной информации.

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


То есть, основной поток данных идет, минуя брокер:
26 сен 14, 00:27    [16623545]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
...
То есть, основной поток данных идет, минуя брокер:


К сообщению приложен файл. Размер - 3Kb
26 сен 14, 00:27    [16623546]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
А ты еще спрашиваешь
defecator
...а для чего ?
...

26 сен 14, 00:30    [16623550]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
vavan
Member

Откуда: Казань
Сообщений: 3106
чччД
с винсокетами ты можешь послать всех, кто (например) неправильно отправил заголовок, еще "в процессе", на принимая сообщения целиком
"послать" ты можешь только закрыв сокет. а так пока тебе в него будут вваливать придется читать
26 сен 14, 09:08    [16623952]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
vavan
чччД
с винсокетами ты можешь послать всех, кто (например) неправильно отправил заголовок, еще "в процессе", на принимая сообщения целиком
"послать" ты можешь только закрыв сокет. а так пока тебе в него будут вваливать придется читать

Вот именно - c WinSockets ты можешь закрыть сокет в процессе чтения, не дочитав...

А в ZMQ - ты начинаешь читать, когда "все уже здеcь".
26 сен 14, 09:40    [16624062]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
vavan
Member

Откуда: Казань
Сообщений: 3106
чччД
в ZMQ - ты начинаешь читать, когда "все уже здеcь"
при условии что оно поместилось
26 сен 14, 09:48    [16624109]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
vavan
чччД
в ZMQ - ты начинаешь читать, когда "все уже здеcь"
при условии что оно поместилось

При всех условиях.
Если ты начал читать, значит - сообщение здесь. Они либо приходит, либо нет.
26 сен 14, 11:36    [16624826]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Шаблон "Разделяемая очередь".

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

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

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


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

  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, и в течении скажем, суток, добавляется всего три новых сервиса, то нужно в итоге триста раз переконфигурировать всех клиентов.
Что грустно.

К сообщению приложен файл. Размер - 7Kb
28 сен 14, 03:15    [16630265]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
В идеале, мы должны быть в состоянии добавлять и удалять сервисы или клиентов в любое время, не касаясь любой другой части топологии.
...
автор
Для реализации коннектов "много:много" есть два пути:
- каждый клиентский сокет может коннектиться ко множеству сервисных конечных точек. То есть, один клиентский сокет типа (REQ) коннектится к сервисным сокетам с известными адресами. После этого запросы будут распределяться между сервисами.

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

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

...

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

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

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

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

Вот что у нас было:
+ Клиент

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.



+ Сервис (сервер)
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.

Будем улучшать мир. Воткнем между ними брокер.
28 сен 14, 05:04    [16630300]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Брокер.

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

+ Брокер

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 заменить биндинг на коннект:

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


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

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

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


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

Такой брокер для схемы "Запрос- Ответ" существенно облегчает обслуживание сети , так как клиентам нет нужды знать, где размещены сервисы, а сервисам нет нужны знать, где размещены клиенты. Единственной статической точкой является брокер.
Жизнь налаживается. :)
28 сен 14, 07:08    [16630307]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
...
Теперь можно запускать сколько хочешь сервисов и сколько хочешь клиентов - все они будут общаться через брокер.

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


К сообщению приложен файл. Размер - 10Kb
28 сен 14, 07:09    [16630308]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Приведенный выше код трансляции сообщений представляется очень полезным для многих случаев для схемы "Запрос - Ответ".

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


function zmq_proxy( frontend, backend, capture: Pointer ): Integer; 


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

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


+ Та-да-мммм!
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.


+

Дальше будет еще интереснее. Картинка с другого сайта.
28 сен 14, 07:26    [16630309]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Важно.
Видов сокетов, которые практически можно использовать в брокере:

ROUTER - DEALER
XSUB - XPUB
PULL - PUSH.
28 сен 14, 07:32    [16630312]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Мосты.

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

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

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

В качестве примера напишем крошечный прокси, который, находясь между издателем и множеством подписчиков, соединяет две разные сети.
Вернемся к примеру с метеостанцией. 16583825
Предположим, что сервер-издатель (которые измеряет температуру, давление и проч) работает во внутренней сети, часть подписчиков - тоже во внутренней. А еще часть - во внешней.
Создаем прокси, в которой фронтэнд сокет (SUB) будет общаться с внутренней сетью, а бэкэнд сокет (PUB) - с внешней.
Прокси будет подписываться фронтэнд-сокетом на события сервиса погоды и пере-публиковывать их на бэкэнд-сокете.
+ Прокси для метео: "мост в интернет"

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.
28 сен 14, 20:33    [16631345]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
...прокси, который, находясь между издателем и множеством подписчиков, соединяет две разные сети...


К сообщению приложен файл. Размер - 16Kb
28 сен 14, 20:34    [16631347]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Кино нашел, на русском:

http://wiki.4intra.net/0MQ_—_Сокеты_на_стероидах_(Сергей_Гулько,_OSDN-UA-2012)
28 сен 14, 20:46    [16631355]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
Кино нашел, на русском:

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


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

Поискал - "каким же образом?"
Нашел кое-какое описание, вроде специальной упаковки сообщений ZMQ в пакеты tcp.
28 сен 14, 20:57    [16631362]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Обработка ошибок. ETERM.

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

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

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

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


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

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

Пример:
  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") обнаруживает сообщение "умри!", он завершается.

Сборщик почти не меняется, добавится еще один сокет и отправка сообщения "умри!":
var
...
  fSocketController: Pointer;
...
begin
...
  // Сокет для отправки сигнала "умри!"
  fSocketController := zmq_socket(fContext, ZMQ_PUB);
  zmq_bind(fSocketController, 'tcp://*:5559');
...
  // Отправка сигнала "умри!" рабочим процессам
  zmq_send(fSocketController, PChar('KILL')^, 4, 0);
...


Вот полный код сборщика:
+ Сборщик "Sink"

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 сокет должен быть настроен:
  zmq_setsockopt(fSocketController, ZMQ_SUBSCRIBE, nil, 0);

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

+ Код рабочего процесса "Worker"
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").
29 сен 14, 07:53    [16632282]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Топология только что описанной сетевой задачи:

К сообщению приложен файл. Размер - 14Kb
29 сен 14, 07:54    [16632283]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Обработка Crtl+C для консольных приложений Windows.
Казалось бы, нажали и хрясь - приложение убито. Если бы.

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

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

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

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


uses
  ...Windows, ...;
...
begin
...
  Windows.SetConsoleCtrlHandler(CtrlCHandler, True);


Шаблон для CtrlCHandler:

function console_handler( dwCtrlType: DWORD ): BOOL;
var
  i: Integer;
begin
  if CTRL_C_EVENT = dwCtrlType then
  begin
    // Выполняем "мягкое" завершение
...
  end else 
    result := False;
end;

Как выполнить "мягкое" завершение - зависит от текущей задачи.
30 сен 14, 00:27    [16636514]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Мультипоточность с 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 клиентов. Сервер из реальной жизни будет запускать несколько рабочих потоков. После чего он будет принимать запросы так быстро, как это возможно и раздавать их своим своих рабочим потокам.
Рабочие потоки будут "перемалывать цифирь" и отправлять результаты обратно.

Конечно, это можно сделать с помощью прокси-брокера и внешних рабочих процессов, но часто легче начать один процесс, который займет все шестнадцать ядер, чем шестнадцать процессов, каждый из которых жрет одно ядро. Кроме того, рабочие процессы будут занимать линии связи и поглощать сетевой трафик и просто тормозить.
30 сен 14, 03:25    [16636652]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Многопоточный сервис:
+ Сервис

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.



Клиент - все тот же:
+ Клиент

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 протоколу.
Сокет рабочего потока:
// Сокет для общения с диспетчером
  fSocketReceiver := zmq_socket(aContext, ZMQ_REP);
  zmq_connect(fSocketReceiver, 'inproc://worker');


Сокет основного потока:
  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.
30 сен 14, 06:03    [16636680]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Получилась вот такая структура:

К сообщению приложен файл. Размер - 7Kb
30 сен 14, 07:54    [16636743]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
В выводе сервиса видно, что номера итераций повторяются по три раза - по числу запущенных потоков.

К сообщению приложен файл. Размер - 103Kb
30 сен 14, 08:07    [16636759]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Для удобства в дальнейшей работе создадим пару функций, пересылающих и принимающих строки:
function s_recv(aZMQSocket: Pointer; aFlags: integer = 0): string;
function s_send(aZMQSocket: Pointer; const aSrcString: string;

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;
1 окт 14, 20:44    [16647068]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Обмен сигналами между потоками.

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

Пример:

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

Отправка - прием сигналов будет выполняться с помощью сокетов типа PAIR по inproc протоколу.

К сообщению приложен файл. Размер - 5Kb
1 окт 14, 21:04    [16647139]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
"Сигнал" в данном случае - это просто сообщение, строка "READY". На приемной стороне содержамое строки игнорируется, важен сам факт передачи-приема.

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

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

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

+

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.



К сообщению приложен файл. Размер - 6Kb
1 окт 14, 21:45    [16647304]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Итак, это был образец классического многопоточного приложения 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 - лучший выбор для пересылки сигналов координации между парами потоков в приложении.
1 окт 14, 22:42    [16647476]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Согласование работы между узлами сети.

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

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

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

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

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

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

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

К сообщению приложен файл. Размер - 3Kb
2 окт 14, 00:04    [16647731]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Поехали. Кодируем проект "Метео" (исходная задача - 16583825). Сервис начинает публиковать данные только после коннекта 10 клиентов. Пакеты данных публикуются 10 000 раз, после чего публикуется сообщение 'END'.
Клиенты при запуске сообщают сервису о своем появлении и подписываются на данные сервиса.
Полученные по подписке данные выводятся в консоль клиента, число полученных пакетов подсчитывается, . Рабочий цикл клиента прерывается при получении сообщения 'END'.

Код сервиса:
+ Сервис "метео" с синхронизацией запуска.
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.

Код клиента:
+ Клиент "метео" с синхронизацией запуска.
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.
- Когда издатель получит необходимое число подтверждений от коннекте от подписчиков, он начинает публиковать реальные данные.
2 окт 14, 01:57    [16647859]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Нуль - копия (Zero-Copy).

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

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

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

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

procedure my_free (aData : Pointer; aHint : Pointer); cdecl;
begin
  Freemem(aData);
end;


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

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

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

 free_fn = procedure(data, hint: Pointer); cdecl;



Пример применения:
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:
 free_fn = procedure(data, hint: Pointer); cdecl;

Значение его просто дублируется из параметра hint функции
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.
2 окт 14, 03:58    [16647908]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
Нуль - копия (Zero-Copy).


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

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

При записи составных сообщений ZeroMQ отлично работает с нуль-копией. Для обычных сообщений вам понадобилось бы слить несколько сообщений в один буфер, а только потом отправлять. То есть, понадобилось бы выполнить копирование данных. А с ZMQ можно отправить несколько разных буферов, пришедших от разных источников, как отдельные кадры сообщения. Каждое поле отправляется как кадр, отделенный значением длины (префиксом). В приложении это выглядит как последовательность вызовов отправлений или приема. Однако, внутри ядра ZMQ, составное сообщение отправляется и принимается одним системным вызовом, что очень эффективно.
2 окт 14, 04:12    [16647914]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Упаковка сообщений для схемы "Издатель-Подписчик"

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

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

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

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

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


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

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)); // Настройка сокета

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

Сервис.
+ Код сервиса "Метео" с составным пакетом

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.



Клиент:

+ Клиент "Метео", составной пакет
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.


Разбиение сообщения на части удобно, в том числе, например, для логического разделения составных данных.Например: Ключ - Адрес - Основное сообщение.

К сообщению приложен файл. Размер - 24Kb
2 окт 14, 05:16    [16647934]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
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 реализует еще и собственные очереди.
2 окт 14, 06:11    [16647969]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Потери сообщений.

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

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

- Для сокетов 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.

К сообщению приложен файл. Размер - 50Kb
2 окт 14, 20:11    [16652644]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
...
- Если используется транспорт inproc....Также, сначала делайте bind, а лишь затем - connect.
...


PS: В ZeroMQ версии 4.* эта проблема решена, bind и connect для inproc может быть выполнен в любой последовательности.
2 окт 14, 20:14    [16652653]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Шаблон "Запрос - Ответ" - подробности.

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

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

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

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

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

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

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


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

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


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

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

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


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

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

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


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

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


+ Просто код (не всевдо).

  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" и находит коннект, связанный с ним. Ели коннект найден, наружу перекачивается следующие два кадра - уже знакомый минимальный конверт

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


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

Все просто.

Ну и что?

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

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

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

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

- Каждый раз сокет ROUTER принимает сообщение, оно с помощью идентификатора сообщает вам, какой корреспондент это сообщение прислал.
- Вы можете воспользоваться хэш- таблицей (когда идентификатор - ключевая строка), чтобы отследить вновь подключенного абонента.
- Сокет ROUTER будет асинхронно, циклически обрабатывать всех корреспондентов, подключившихся к нему, если префикс - идентификатор идентичен первому кадру сообщения.
Сокеты ROUTER не обрабатывают конверт полностью. Они ничего не знают о пустых разделителях. Все ,что они делают - это работа с кадром идентификации, который позволяет выяснить, в какой из коннектов следует дальше отправить сообщение.
5 окт 14, 02:40    [16662285]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Что мы знаем про сокеты из схемы "Запрос - Ответ"


- Сокеты 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
5 окт 14, 03:13    [16662307]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
"Запрос - Ответ", рабочие комбинации сокетов.
Некоторые особенности.

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.
5 окт 14, 04:28    [16662330]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

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

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

procedure s_dump(aSocket: Pointer);


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

+ ZMQ_utils.pas
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.


Пригодится, когда начнем разбирать "конверты".
6 окт 14, 01:03    [16664115]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Сокеты ROUTER, более подробно.
Концепция идентификаторов и адресов

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

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

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

+ Пример идентификации
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'.
Смотрим, что же получилось на выходе приемника.

Видно, что каждое сообщение состоит из трех кадров: идентификатор, пустой кадр - разделитель, и кадр с данными:

К сообщению приложен файл. Размер - 37Kb
6 окт 14, 01:29    [16664174]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Обработка ошибок сокета ROUTER.

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

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

Начиная с ZeroMQ v3.2, у сокетов появилась опция для перехвата ошибок: ZMQ_ROUTER_MANDATORY. Устанавливаем ей для сокета ROUTER получаем возможность отловить ситуацию, когда индентификации недостаточно для работы сокета ROUTER, сокет будет сигнализировать ошибкой EHOSTUNREACH.
6 окт 14, 02:34    [16664229]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Раз уж выяснилось (16666035), что Write/Writeln небезопасны в потоках, добавим безопасную функцию вывода строки в консоль. Снова расширим ZMQ_Utils.pas.

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


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

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

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


+ ZMQ_Utils.pas

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.

6 окт 14, 17:07    [16667318]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Шаблон Балансировка Нагрузки

Рассмотрим простой код (см. ниже). Видно, как сокет 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!".

+ Код приложения, поясняющего идею балансировки нагрузки

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 - асинхронный.

ИМХО, сообщения лучше читать кадр за кадром, логически отделяя процесс приема от процесса передачи. Вот так, например:
  // Следующее сообщение возвращает поток, первый выполнивший задание
    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 секунд
Вывод программы:

К сообщению приложен файл. Размер - 42Kb
6 окт 14, 17:36    [16667531]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Брокер на сокете ROUTER, рабочие процессы - на сокете DEALER

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

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

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

+ Брокер на сокете ROUTER, рабочий процесс - на сокете Dealer

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, то для простоты мы можем с обоих сторон просто отбросить пустой кадр - разделитель. Что, в общем, часто и делается.
6 окт 14, 18:44    [16667823]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Брокер сообщений с балансировкой нагрузки

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

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

Исходник получается длинноват, но в нем стоит разобраться.
6 окт 14, 21:22    [16668293]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Вот он:
+ Брокер с балансировкой нагрузки
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():

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

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

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

2. Основным источником задач в примере является процедура потока клиентов client_thread_proc(). Следовательно, при завершении приложения есть смысл дождаться завершения потоков клиентского слоя. Для этого при создании потоков слоя клиентов запоминаются дескрипторы потоков, а при завершении процедуры выполняется ожидание завершения всех потоков клиентов:
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);
9 окт 14, 13:01    [16681363]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Немного поясню.

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


Рассмотрим весь путь сообщения схемы "Запрос - от клиента до рабочего процесса и обратно. В коде есть вызовы
  
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", либо идентификатор клиента, для которого рабочий отправил сообщение. В обоих случаях мы помещаем адрес рабочего (первый кадр конверта) в конец очереди свободных рабочих, а оставшуюся часть (если это ответ клиенту) отправляем через фронтенд сокет клиенту.

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

Данная схема легко расширяется. Например, в процессе работы работники могли бы запускать счетчики производительности для оценки собственного быстродействия и отчитываться брокеру о результатах. А брокер мог бы выбирать самого быстрого из работников, использованных последними.
9 окт 14, 14:20    [16682003]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
ZeroMQ, переходим на последнюю версию: 4.0.4

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

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

Вот файлик, реализующий интерфейсы библиотеки libzmq.dll (используем его в uses вместо старого zmq.pas) :

К сообщению приложен файл (zmq_h.7z - 3Kb) cкачать
13 окт 14, 23:45    [16699729]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
libzmq.dll была откомпилирована с помощью MS VS 2012 Update 4, с указанием, что целевая платформа - Windows XP (v110_XP).
То есть, работать будет на Windows XP и более новых.

Приложенные к библиотеке тесты отработали без проблем, и все примеры, рассмотренные ранее, также работают.

К сообщению приложен файл. Размер - 55Kb
13 окт 14, 23:52    [16699737]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Сама библиотека. Распаковать и положить возле приложения. Больше для работы ничего не нужно.

К сообщению приложен файл (libzmq.7z - 72Kb) cкачать
13 окт 14, 23:54    [16699740]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Вернется к брокеру с балансировкой нагрузки (с учетом перехода на версию ZeroMQ 4.0.4): 16681363
+ Брокер с балансировкой нагрузки
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 низкого уровня.

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


  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/
24 окт 14, 02:11    [16751832]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
High Level API: http://czmq.zeromq.org/

Вот так на Delphi будет выглядеть "Hello, Worrld!"
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:
var
...
  fpStr: PChar;


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

  zstr_free(fpStr);


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

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

О том, как использовать эту красоту, расскажу чуть позже.

К сообщению приложен файл (bin.7z - 125Kb) cкачать
24 окт 14, 03:03    [16751865]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Использование.

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

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

Возможно, позднее файл биндинга будет изменен.

К сообщению приложен файл (ZMQ_4.7z - 33Kb) cкачать
24 окт 14, 03:27    [16751887]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
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
24 окт 14, 08:51    [16752072]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
PPA,

обязательно будет.
24 окт 14, 15:08    [16754360]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Особенности High-level API CZMQ:

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

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

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

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

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

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

Класс zsock, который работает вообще без контекста и с конструкторами, совмещающими операции создания и коннекта/биндинга.
Класс zactor для multithreaded - разработки.
Класс zgossip для исследования конфигурации сети.
Класс zrex для регулярок.
Функций управления процессами - zsys.
29 окт 14, 20:37    [16774869]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Вариант брокера с балансировкой нагрузки, реализованного с помощью High-level API CZMQ:

+ Брокер с балансировкой нагрузки & CZMQ

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:

  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), то следим за кодом завершения:

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


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

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

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

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

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

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 размещена в коде специальных методов - обработчиков. При этом один и тот же обработчик может обрабатывать как активность сокета (попступление данных), так и таймеров сокета.
29 окт 14, 21:41    [16775038]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Что-то у меня обработка Ctrl+C работает неправильно. В "чистом" ZMQ все ОК, а с CZMQ - крутые глюки какие-то.

И не то чтобы это меня особо напрягает, но все же.
29 окт 14, 21:43    [16775046]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Что-то не очень вразумительно про реактор написал.

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

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

+ Брокер с балансировкой нагрузки & CZMQ без реактора
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 - в главном цикле:
// Очередь доступных рабочих
  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.
Видна работа с фреймами:

 identity := zmsg_unwrap(msg);
 zlist_append(workers, identity);

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


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

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() - процедуры с переменным числом параметров:

//  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;

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

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

То же самое:

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) гораздо проще.
30 окт 14, 00:22    [16775473]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Шаблон "Асинхронный клиент/сервер".

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

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

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

Топология:

К сообщению приложен файл. Размер - 5Kb
30 окт 14, 00:42    [16775525]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
В целях упрощения тестирования, кодировать будем в рамках одного процесса, как мы уже делали несколько раз.
Т.е., множество тредов будет имитировать многопроцессную архитектуру.
При запуске примера видно, что три клиента (каждый со случайным id) выводят на консоль ответы от сервера.
Если присмотреться, то можно заметить, что каждая задача клиента порождает ноль или больше ответов на каждый запрос.

+ Модель асинхронного клиент-сервера

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().

Таким образом, более детальная структура асинхронного сервера такова:

К сообщению приложен файл. Размер - 13Kb
30 окт 14, 02:15    [16775691]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Стоит подчеркнуть, что для диалога между клиентами и сервером используются сокеты DEALER -> ROUTER, в то время как внутри сервера, для общения между главной нитью сервера и рабочими используются сокеты DEALER -> DEALER. Если бы сокеты были строго синхронными, то мы бы использовали сокет REPORT. Однако, так как мы хоти отправлять множество ответов, нам нужен асинхронный сокет. Мы не хотим маршрутизировать ответы, так как они всегда идут к одной серверной нити, который слал нам запрос.


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

Схема примерно такая:
Client Server frontendWorker
[ DEALER ]<----->[ ROUTER <-----> DEALER <-----> DEALER ]
1 часть2 части2 части



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

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

Для правильного управления состоянием клиента в асинхронном сервере, необходимо:

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

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

Пример: Система "Периметр".

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

- Следите и реагируйте на остановку хартбита. Если от клиента не приходо запроса, скажем, в течении двух секунд, сервер может это обнаружить и уничтожить все состояния, связанные с данным клиентом.
30 окт 14, 03:36    [16775781]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Рабочий пример. Межброкерная маршрутизация.

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

Для нас такая задача - как два байта переслать. Мы ж дельфисты.

Смоделирует эту задачу, используя ZeroMQ.
30 окт 14, 03:56    [16775787]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
ZeroMQ,

А почему эту тему не ведешь в виде отдельного профильного ресурса?
заведи свой сайт-блог - контент ведь качественный.
а тут ведь затеряется в "мусорных вопросах" если не пинать каждый раз в топ :)
31 окт 14, 10:23    [16781203]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
PPA
ZeroMQ,

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

Пожалуй, ты прав.

Я вот тут :http://www.sql.ru/blogs/blogs.aspx - оставлял заявку, но воз и ныне там.

Ну и изначально я не предполагал, что материала будет так много.
31 окт 14, 15:10    [16783978]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

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

zmq_msg_t = record
    _: Array[0..32-1] of Byte;
  end; 


...


Ты-дыщь! Начиная с версии 4.1.0, структура zmq_msg_t имеет длину 48 байт!

type
  zmq_msg_t = packed record
    _: array[0..47] of Byte;
  end;


На уровне протоколов обмена данными проблем совместимости "снизу вверх" нет, а на уровне софта - еще как может быть.

Вот тут можно взять свежие .dll и .pas.

Или работать со старыми (.dll и .pas): см. аттачи к 16751865 и 16751887 соответственно.
1 ноя 14, 22:34    [16788831]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Вычитал про форки этой библиотеки: "Crossroad I/O" (уже вроде умер) и NanoMSG .

Форкнул библиотеку Мартин Сюстрик (Martin Su'strik) - один из создателей, с целью:
- изменить лицензию использования;
- сделать сокеты thread-safe;
- расширить режимы работы;
- упростить использование;
- изменить механизмы использования;
- поднять нагрузочную способность;
- добавить несколько полезных утилит


Хотя одной из задач была сделать библиотеку частью ядра Linux, на страничке загрузки есть версия и для Windows.
В составе библиотеки, кроме самой .dll, есть разные утилиты, которые позволяю прямо из командной строки организовать общение с сетью, тестирование, опрос и т.п.

API библиотеки существенно похудел. Это просто супер.
Больше нет работы с контекстом, а сокеты теперь могут общаться в дополнительных режимах. Например, схема "Издатель - Подписчик" расширена до уровня не просто "Слушайте меня все, кто хочет", а еще: "А ну-ка ответьте мне, мои подписчики...".

Забавно, что одним из недостатков ZeroMQ (которое решено в NanoMSG) названа возможность без проблем работать всего () с 10 000 клиентов, а дальше, типа, нужно вызывать процедуры настройки, а еще дальше - глядишь, и ядро придется перекомпилировать... :)
...
К сожалению, библиотека NanoMSG пока только в стадии beta.
4 ноя 14, 07:09    [16795137]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Краткий обзор NanoMSG: https://hguemar.fedorapeople.org/slides/nanomsg/presentation.html
4 ноя 14, 07:09    [16795138]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
За выходные разобрался с непонятками, исправил ошибки и собрал все здесь: http://delphi-and-zeromq.blogspot.ru/2014/10/zeromq.html
5 ноя 14, 07:21    [16798628]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
ZeroMQ
Краткий обзор NanoMSG: https://hguemar.fedorapeople.org/slides/nanomsg/presentation.html


NanoMSG под XP не пашет.
CancelIoEx используется
https://github.com/nanomsg/nanomsg/issues/102
6 ноя 14, 09:33    [16804437]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
PPA
ZeroMQ
Краткий обзор 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 запускалось. Хотя, не пробовал запускать. Надо будет в виртмашине потестить.
6 ноя 14, 10:43    [16804806]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
ZeroMQ
PPA
пропущено...


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.
---------------------------

Это еще лечится, добавлением в проект одной строчкой

#include <Wspiapi.h> // For W2K: поможет, только если czmq  не использовать.


А если использовать czmq.dll, то этого мало, там еще

автор
---------------------------
Точка входа в процедуру GetAdaptersAddresses не найдена в библиотеке DLL IPHLPAPI.DLL.
---------------------------


До WinXP вместо GetAdaptersAddresses использовалась GetAdaptersInfo, функциональности которого, к сожалению, недостаточно.
...
Ну и ладно. Пора на XP.
7 ноя 14, 06:19    [16809823]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
ZeroMQ
Рабочий пример. Межброкерная маршрутизация.

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

Для нас такая задача - как два байта переслать. Мы ж дельфисты.

Смоделирует эту задачу, используя ZeroMQ.


Долго разбирался и портировал примеры на Delphi.

Здесь подробный рабочий пример, имитирующий сетевые "кластеры".
...
Дальше буду разбираться с обеспечением надежности доставки сообщений.
4 дек 14, 04:22    [16947200]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
Как-то не очень внимательно рассмотрел схему "Запрос - Ответ" (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() с минимальным временем ожидания.
19 май 15, 12:16    [17660409]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
ZeroMQ
Member

Откуда: Оттуда.
Сообщений: 10125
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. Но - вполне возможно.
19 май 15, 13:30    [17660979]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
PPA
ZeroMQ
Краткий обзор 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).
8 дек 15, 01:15    [18527110]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
[quot чччД]
PPA
пропущено...
1. Один из пользователей форкнул код, реализовав замену CancelIoEx под WinXP. Пишет, что работает у него в продакшне.
.


адрес?
9 дек 15, 13:24    [18534931]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
PPA,

что-то результат потерял... :(

Вот отсюда можно за хвост потянуть: https://github.com/nanomsg/nanomsg/pull/349
9 дек 15, 15:38    [18536051]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
В ветку 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

И то хорошо.
19 мар 16, 05:22    [18952107]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
чччД,

А ipv6 пробовал под виндой?
у меня простой клиент не пашет.

WindowsXP + IP6 Teredo

под linux все хорошо.

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)

может сталкивался?
24 мар 16, 14:25    [18973338]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
А разве в Windows XP было IP v6 ?
24 мар 16, 14:31    [18973380]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 2927
defecator

К сообщению приложен файл. Размер - 82Kb
24 мар 16, 15:24    [18973759]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
defecator,

+1.

ipv6 в Win XP требуется включать. PPA, попробуй, набери в командной строке:

netsh int ipv6 install




PPA,
"Просто так" ipv6 - в Vista и новее.

Там даже при выходе релиза 4.1.4 лихо отменили поддержку WinXP из-за этого: заюзали функцию if_nametoindex()
...
Ну и просто
// Set target version to Windows Server 2008, Windows Vista or higher.



Потом, в 4.1.5 вернули ( https://github.com/zeromq/zeromq4-1 ).
//  Set target version to Windows Server 2003, Windows XP/SP1 or higher.

И в 4.2.0 - тоже, за исключением блока, обеспечивающего новые сокеты ZMQ_CLIENT/ZMQ_SERVER.
24 мар 16, 15:31    [18973822]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Feg16
Member [заблокирован]

Откуда: دولة اسرا
Сообщений: 5414
Блог
А движка для мультиплеера на них нету?
24 мар 16, 16:58    [18974401]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Feg16
А движка для мультиплеера на них нету?


Что за "движок мультиплеера"?
24 мар 16, 20:57    [18975249]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Feg16
Member [заблокирован]

Откуда: دولة اسرا
Сообщений: 5414
Блог
чччД
Что за "движок мультиплеера"?
Логика над сокетами - коннект, дисконнект, отправка подписанного пакета и т.п. Т.е. вся та обвязка которая необходима для подключения нескольких клиентов к серверу, и сервер который может обрабатывать данные от нескольких клиентов
24 мар 16, 21:04    [18975268]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Feg16
чччД
Что за "движок мультиплеера"?
Логика над сокетами - коннект, дисконнект, отправка подписанного пакета и т.п. Т.е. вся та обвязка которая необходима для подключения нескольких клиентов к серверу, и сервер который может обрабатывать данные от нескольких клиентов

Ну так да. Выбираешь нужный тип сокета, и вперед.

К примеру, режим "запрос-ответ": клиент отправляет запрос и ждет ответ. Сервер, получи запрос, отвечает только на него и только этому клиенту.
Реализация.
Используются сокет типа REQ и сокет типа REP.
К серверу (там сокет REP) коннектится куча клиентов (используя сокет REQ). Все запроси к серверу выстраиваются в очередь, сервер их по очереди разгребает и возвращает ответ именно тому клиенту, от которого получил запрос.
...
Если нужно общаться одновременно несколькими клиентами не по очереди, а одновременно (например, получить запрос от одного, обработать его и отправить результат другому) - используется пара сокетов Dialer - Router. При этом состав клиентского сообщения автоматически добавляется идентификатор клиента (назначается автоматически или ручками на клиенте). То есть, к примеру, получаешь сообщение, меняешь в нем адрес и отправляешь обратно. А оно приходит не к отправителю, а к тому, чей адрес ты подставил. Вот тебе и прокси в три строки кода.

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

Есть всякие там шифрации и подписи.
Даже есть средства наблюдения за каталогами файловой системы в онлайне.

Недостаток: основное назначение - для работы в локальных сетях по причинам архитектуры (можно заDDOSить, например). Ну и еще, до недавнего времени нельзя было сокеты передавать между вычислительными нитями. Особые правила работы для построения мультинитевых приложений.
24 мар 16, 21:30    [18975346]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
так он хочет без низкоуровневых обёрток.
Нужно что-то типа DirectPlay
24 мар 16, 21:32    [18975349]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
defecator
...типа DirectPlay

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

Тут ты сам выстраиваешь нужную архитектуру, какая твоей левой пятке приглянется, а не пытаешься натянуть существующую на собственное понимание прекрасного.
24 мар 16, 21:48    [18975394]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Feg16
Member [заблокирован]

Откуда: دولة اسرا
Сообщений: 5414
Блог
defecator
так он хочет без низкоуровневых обёрток.
Нужно что-то типа DirectPlay
Да :)
чччД
Тут ты сам выстраиваешь нужную архитектуру
Увы рук всего две, и они уже заняты остальным, поэтому приходится для тех или иных моментов натягивать уже готовое решение.
25 мар 16, 08:36    [18976067]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
Feg16
defecator
так он хочет без низкоуровневых обёрток.
Нужно что-то типа DirectPlay
Да :)
чччД
Тут ты сам выстраиваешь нужную архитектуру
Увы рук всего две, и они уже заняты остальным, поэтому приходится для тех или иных моментов натягивать уже готовое решение.

Так натяни готовое решение - тот же DirectPlay, тем более в винду он встроен
25 мар 16, 08:42    [18976084]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
GunSmoker
Member

Откуда:
Сообщений: 3068
defecator, DirectPlay начал устаревать ещё в Vista, и потихоньку из винды выпиливается. Что вместо него - я бы сам не прочь узнать.
25 мар 16, 11:50    [18977070]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
GunSmoker
defecator, DirectPlay начал устаревать ещё в Vista, и потихоньку из винды выпиливается. Что вместо него - я бы сам не прочь узнать.

так это же составная часть DirectX, как его могут выпилить-то ?
25 мар 16, 12:28    [18977303]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
чччД
defecator,

+1.

ipv6 в Win XP требуется включать. PPA, попробуй, набери в командной строке:

netsh int ipv6 install




Я естественно включил ip6 в XP руками
но проблема в самой либе оказалась в методе

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
25 мар 16, 12:33    [18977354]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
GunSmoker
Member

Откуда:
Сообщений: 3068
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.
25 мар 16, 12:54    [18977478]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
GunSmoker, сурово попилили движок, не знал
25 мар 16, 13:05    [18977532]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
Между тем, только что проверил - в Windows 1 (DirectX 11) есть интерфейсы DirectPlay.
Запустил приложение, собранное аж 18.12.1999, и спокойно подключился

Вроде ничего не ставил дополнительного
25 мар 16, 13:13    [18977564]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
Вот исходники примитивного чата через DirectPlay, внутри есть EXE, откомпилёный ещё на Delphi 2 в 1999 году:
http://rghost.ru/7q4zrQSGv

Всё работает в Window 7....
25 мар 16, 13:21    [18977613]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
В Windows 8.1 "искаропки" интерфейса DirectPlay не оказалось.
А в Windows 7 "искаропки" он ещё есть
25 мар 16, 13:50    [18977739]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Feg16
Member [заблокирован]

Откуда: دولة اسرا
Сообщений: 5414
Блог
defecator
А в Windows 7 "искаропки" он ещё есть
Warning: Microsoft DirectPlay has been deprecated. 
Этого достаточно чтобы сморщив нос закрыть страницу :(
25 мар 16, 13:59    [18977792]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Feg16
Member [заблокирован]

Откуда: دولة اسرا
Сообщений: 5414
Блог
А то будет потом как с RedAlert - поставьте тот компонент, настройте протокол IPX
25 мар 16, 14:00    [18977800]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
defecator
Member

Откуда:
Сообщений: 38600
Feg16
defecator
А в Windows 7 "искаропки" он ещё есть
Warning: Microsoft DirectPlay has been deprecated. 
Этого достаточно чтобы сморщив нос закрыть страницу :(

какие тонкие нюхательные ощущения, я поражаюсь.
А с древним кодом нет, не работал никогда ? Тошнило тебя ?
25 мар 16, 14:01    [18977804]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Feg16
Member [заблокирован]

Откуда: دولة اسرا
Сообщений: 5414
Блог
defecator
А с древним кодом нет, не работал никогда ? Тошнило тебя ?
Одно дело пилить на дядю говнокод на практике, а второе заменять один движок на другой. Смысл мне ставить раком проект, если он у пользователей попросту не станет работать?
25 мар 16, 16:28    [18978797]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
ZeroMQ
Особенности High-level API CZMQ:

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


Интересно:

//  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) не просто передается, а создается его копия в блоке данных нити.
В рамках нового контекста в нити и будет создаваться новый пул сокетов и связанных с ними коннектов, очередей и т.п.
Т.е., по завершении нити произойдет корректная финализация копии контекста и связанных с ним сокетов и проч.
31 мар 16, 14:22    [19000503]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
чччД, с аутентификацией не разбирался?

В тестах есть пример, но слишком простой.

С сервером понятно, непонятно как узнать клиенту что у него "Invalid username or password"?
На клиенте libzmq.dll получает "400", рвет соединение, больше его не пытается установить, но наружу никак это не сообщает. Снаружи мой код работает как будто все хорошо. zmq_send() отправляет, zmq_recv() висит в ожидании приема.

Нагуглил это, вроде тот же вопрос, но мало что там понял, у меня с английским не очень хорошо.
21 окт 16, 17:37    [19810451]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Может кому пригодиться. Все что нарыл - это включить мониторинг (zmq_socket_monitor ()) и анализировать что будет после ZMQ_EVENT_DISCONNECTED, если после попытки установить соединение прекратятся, то считаем что сервер прислал команду отключиться.
24 окт 16, 16:17    [19816581]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Dima T,

выстраивая свой протокол, я сделал так, что в момент handshake клиент передает серверу свои текстовые идентификационные данные (имя компа, юзернэйм windows и т.п.) в отдельном фрейме. А после handshake, обменявшись открытыми ключами для шифрования (алг. Диффи-Хеллмана), в целях экономии трафика использую собственный открытый Д-Х ключ заодно и как 8-байтный UID для identity коннекта.
Сервер кэширует UID'ы и хранит их в течении заданного времени (5 сек без активности -> "свободен!).
24 окт 16, 16:46    [19816728]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Т.е., аутентификацией на сервере занимаюсь я сам.
Что удобно и позволяет выстраивать различные, удобные для моей левой пятки схемы.
24 окт 16, 16:49    [19816747]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Схема примерно вот такая.

Клиент получает от сервера сообщения:
- хартбит - посылается периодически, чтобы клиент знал: сервер жив;
- запрос выполнен - успешный ответ сервера на любой запрос клиента (кроме запроса "гуд бай, сервер!").
- запрос об идентификации - ответ сервера на любой запрос, когда сервер не знает о данном identity ничего, клиент должен представиться;

Сервер получает запросы от клиента:
- хартбит - посылается периодически, чтобы сервер знал: клиент жив.
- запрос - посылается клиентом по мере надобности.
- запрос с блоком идентификации (высылается на запрос сервера).
24 окт 16, 17:12    [19816857]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
чччД,

я вот одного не понял, если что бы получить сообщение нужно сперва выделить под него память где тогда асинхронность?
25 окт 16, 12:55    [19819465]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
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
25 окт 16, 13:35    [19819677]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
чччД
Т.е., аутентификацией на сервере занимаюсь я сам.
Что удобно и позволяет выстраивать различные, удобные для моей левой пятки схемы.

Тоже склоняюсь к самодельной аутентификации.
Свой протокол это хорошо, возможно даже лучше для безопасности чем встроенный, т.к. можно всунуть перехват zmq_setsockopt() установки логина/пароля и готово. Или трафик послушать. В PLAIN режиме оно открытым текстом идет.

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

Хочу задействовать новые сокеты ZMQ_CLIENT, ZMQ_SERVER. Управление идентификацией гибче чем с DILLER-ROUTER. И побыстрее должно работать теоретически, т.к. маршрутизация по ID сессии (int32), а не по имени клиента (строка произвольной длины)
Смущает что они подключаются по
#define ZMQ_BUILD_DRAFT_API

т.е. вроде как еще в варианте черновика. Есть опыт использования? Не глючат?
25 окт 16, 14:40    [19820075]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Dima T
...
Хочу задействовать новые сокеты ZMQ_CLIENT, ZMQ_SERVER...
[/src]
т.е. вроде как еще в варианте черновика. Есть опыт использования? Не глючат?

Не пользовался пока, чуть-чуть почитал только.
25 окт 16, 15:12    [19820207]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
чччД,

нда, не густо - нулевая обёртка над апи практически
25 окт 16, 21:45    [19821745]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
чччД,
всё больше склоняюсь, что вот такой подход более практичен и в плане результата и в плане лёгкости разработки.
ещё бы для асинхронных операций чтения память выделять когда она требуется, а не заранее - тогда наверное можно и с классическим пулингом посостязаться по затратам ресурсов
25 окт 16, 22:11    [19821801]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Товарищ младший сержант
Member [заблокирован]

Откуда:
Сообщений: 5122
kealon(Ruslan)
чччД,

нда, не густо - нулевая обёртка над апи практически

Где нулевая обертка над апи?
25 окт 16, 23:59    [19822012]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
Товарищ младший сержант
kealon(Ruslan)
чччД,

нда, не густо - нулевая обёртка над апи практически

Где нулевая обертка над апи?


чччД
А асинхронность - в поллинге: http://delphi-and-zeromq.blogspot.ru/2014/11/05-zeromq-zmqpoll.html

// Процесс обработки для обоих сокетов
  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;
26 окт 16, 07:02    [19822161]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
kealon(Ruslan)
чччД,
всё больше склоняюсь, что вот такой подход более практичен и в плане результата и в плане лёгкости разработки.

ХЗ что проще. Ниже код сервера с многопоточной обработкой, букав поменьше и нет необходимости синхронизации доступа к очереди на обработку.
kealon(Ruslan)
ещё бы для асинхронных операций чтения память выделять когда она требуется, а не заранее - тогда наверное можно и с классическим пулингом посостязаться по затратам ресурсов

zmq_msg_recv() выделяет ровно столько сколько надо по приходу сообщения.

+ Многопоточный эхо-сервер
Сообщения принимаются и раздаются трем потокам на обработку.
#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 Мб.
+ код клиента
#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;
}
26 окт 16, 09:10    [19822312]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Перемудрил я в коде сервера с указателями и похоже утечка памяти есть.
Надо передавать содержимое zmq_msg_t, это как понимаю структура-заголовок (64 байта) где все ссылки на данные сообщения.
Переделал, немного побыстрее стало. Сервер при простое занимает 2-3 Мб памяти.
+ исходник
#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;
}
26 окт 16, 10:08    [19822557]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
kealon(Ruslan)
я вот одного не понял, если что бы получить сообщение нужно сперва выделить под него память где тогда асинхронность?

ИМХУ ты тут теплое с мягким в кучу замешал.
Асинхронность это алгоритмический подход, несинхронно, т.е. не ждать выполнения каждого шага, т.е. дал команду сделать то-то до тогда-то и забыл. Получил ответ "то-то сделано", запустил следующее, истек таймаут "тогда-то наступило и ничего не сделано", повторил команду "сделать то-то" или еще какие действия. А когда выделилась память - пофиг. Мысли ширше или ширее )))
26 окт 16, 20:07    [19825613]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
Dima T
kealon(Ruslan)
я вот одного не понял, если что бы получить сообщение нужно сперва выделить под него память где тогда асинхронность?

ИМХУ ты тут теплое с мягким в кучу замешал.
Асинхронность это алгоритмический подход, несинхронно, т.е. не ждать выполнения каждого шага, т.е. дал команду сделать то-то до тогда-то и забыл. Получил ответ "то-то сделано", запустил следующее, истек таймаут "тогда-то наступило и ничего не сделано", повторил команду "сделать то-то" или еще какие действия. А когда выделилась память - пофиг. Мысли ширше или ширее )))

ага, коревато, но ты же понял, что я хотел спросить :-)

Dima T
kealon(Ruslan)
чччД,
всё больше склоняюсь, что вот такой подход более практичен и в плане результата и в плане лёгкости разработки.

ХЗ что проще. Ниже код сервера с многопоточной обработкой, букав поменьше и нет необходимости синхронизации доступа к очереди на обработку.
kealon(Ruslan)
ещё бы для асинхронных операций чтения память выделять когда она требуется, а не заранее - тогда наверное можно и с классическим пулингом посостязаться по затратам ресурсов

zmq_msg_recv() выделяет ровно столько сколько надо по приходу сообщения.

пример зачётный, именно то, что хотел
к твоему примеру очень неплохо должно ложиться, то что описано в статье
PS: мда, что-то слишком много вокруг этих корутин последнее время крутится, случайности неслучайны...
26 окт 16, 22:28    [19825981]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
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.

Поправил так
#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;
}
27 окт 16, 08:04    [19826377]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Поизучал что реально происходит в inproc - пришел к выводу что не надо никаких указателей для inproc соединений, там и так указатели передаются.
Точнее если сообщение маленькое (5 байт например), то само сообщение, если большое (100 байт пробовал), то указатель, а содержимое сообщения остается в памяти там же где и было. Т.е. ZMQ сама отлично решает как быстрее доставить.

Итого: сообщение можно принимать с TCP соединения и сразу отправлять в inproc очередь на обработку без какого-либо шаманства.
+ Код сервера стал меньше, понятнее и быстрее на 5%
#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;
}
28 окт 16, 07:59    [19831560]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
Dima T,

А у вас ZMQ_CLIENT будет на винде?
он ведь XP не поддерживает - это на всегда или временно?
28 окт 16, 17:22    [19834979]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
PPA
Dima T,

А у вас ZMQ_CLIENT будет на винде?
он ведь XP не поддерживает - это на всегда или временно?

Откуда инфа? На XP пока не тестил, но надо будет, без XP оно вообще не надо.
Есть проблемы c компиляцией libzmq.dll в режиме совместимости с XP. Просто не компилируется с ключем /MT. Пока не разбирался, но думаю решаемо.
28 окт 16, 18:49    [19835265]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Есть там засады. Связанное с IPv6 как понял отключается дефайном ZMQ_HAVE_WINDOWS_TARGET_XP.
Но не все отключается. Они там местами используют WinAPI от Windows Vista+, например SleepConditionVariableCS() это лечить надо переписыванием кода, ConditionVariable есть в С++ 11, попробую заменить на них, может получится.
28 окт 16, 19:30    [19835333]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Неприятно, но не смертельно. В крайнем случае можно вернуться к DILLER-ROUTER и более старой версии работающей в XP, все равно на верхнем уровне я закладываюсь на возможные потери важных сообщений.

А так да, главная засада опенсорца - игнорирование старых ОС, да и железа тоже. Пытался ставить линукс на не очень старенький ноут с Pentium M, получал ответ "процессор не поддерживает PAE", может путаю, не PAE а что-то похожее, но суть в том что интел конкретно из этого проца убрал то что было и в более ранних мобильных и в десктоп аналогах.
28 окт 16, 19:48    [19835364]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
Dima T,

Иногда это по ошибке делается - разработчики сидят на i7 :)
я вот у себя в опенсорс-проекте добавил libtorrent и не знал, что если VC++ 2015 не указан тип оптимизации
она автоматом собирается с SSE2!

и через день после обновления версии получил письмо:

Флай 19969 падает при загрузке, SSE2 команды...
Откатился на 19935.
AMD Duron. 1.2 ГГц. 512 Мб.


а поддержка XP для пользователей пока еще очень актуальна - особенно в отдаленных уголках России.
28 окт 16, 23:08    [19835947]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
PPA
я вот у себя в опенсорс-проекте добавил libtorrent и не знал, что если VC++ 2015 не указан тип оптимизации
она автоматом собирается с SSE2!

Наступал на эти грабли недавно 18816450. Хуже всего что с запретом SSE2 невозможно скомпилировать в 2015.
Пришлось тогда вернуться на MSVC 6. В данном случае можно пожертвовать владельцами антикварного железа.
PPA
а поддержка XP для пользователей пока еще очень актуальна - особенно в отдаленных уголках России.

Кроме того есть еще продвинутые линуксоиды, Wine эмулирует XP, а как там с более поздним WinAPI - большой вопрос.

Попробую поизучать исходники. Из нового кроме сокетов ZMQ_CLIENT еще много чего добавилось, например zmq_atomic_*()
Может несовместимые нововведения не касаются ZMQ_CLIENT, тогда есть шанс просто исключить вызовы WinAPI отсутствующего в XP.
Попробую тупо позаменять несовместимое на MessageBox(). Что получится - напишу.
29 окт 16, 09:39    [19836352]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Оказалось не все так просто. Переписал. Даже быстрее стало работать. Под XP запускается, но иногда затыкается при больших нагрузках. Тут топик поднял по этому поводу.

Есть еще вторая проблема - не компилируется как static. Надо рантайм VS2015. Думаю решаемо, но сначала надо с зависаниями разобраться.
29 окт 16, 17:18    [19837189]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Переделал код под 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
	if_name_result = if_indextoname(index, buffer);

заменить на
#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
	if_name_result = if_indextoname(index, buffer);
#endif

+ condition_variable.hpp
Заменить класс condition_variable_t на этот
#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&);
    };

или отдельно прописать каждую замену. Структура такая:
#ifndef ZMQ_HAVE_WINDOWS_TARGET_XP
		родной код
#else
		код для XP (копировать из кода выше)
#endif

Остался вопрос со static сборкой, чтобы рантайм MSVC2015 не ставить.
30 окт 16, 16:28    [19839048]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Собрал static. Не надо никаких рантаймов. Под XP работает.

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

Хотел готовую DLL выложить, но сюда не лезет, она 0,5 Мб (0,3 в архиве)

PS Там стандартное ограничение на 64 соединения, т.к. используется select(). Думаю этого достаточно для клиента.

К сообщению приложен файл (libzmq-42-xp.zip - 14Kb) cкачать
30 окт 16, 20:59    [19839464]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Товарищ младший сержант
Member [заблокирован]

Откуда:
Сообщений: 5122
Dima T
Поизучал что реально происходит в inproc - пришел к выводу что не надо никаких указателей для inproc соединений, там и так указатели передаются.
Точнее если сообщение маленькое (5 байт например), то само сообщение, если большое (100 байт пробовал), то указатель, а содержимое сообщения остается в памяти там же где и было. Т.е. ZMQ сама отлично решает как быстрее доставить.

Итого: сообщение можно принимать с TCP соединения и сразу отправлять в inproc очередь на обработку без какого-либо шаманства.
...

Ага, тоже вовсю inproc использую, даже для простых случаев многонитевого приложения: очень удобно и быстро.
31 окт 16, 01:34    [19839931]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Товарищ младший сержант
Member [заблокирован]

Откуда:
Сообщений: 5122
Dima T
Есть там засады. Связанное с IPv6 как понял отключается дефайном ZMQ_HAVE_WINDOWS_TARGET_XP.
Но не все отключается. Они там местами используют WinAPI от Windows Vista+, например SleepConditionVariableCS() это лечить надо переписыванием кода, ConditionVariable есть в С++ 11, попробую заменить на них, может получится.

А я просто не самую новую версию libzmq использую. Ибо поддержка WinXP еще очень важна.
31 окт 16, 01:36    [19839932]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Товарищ младший сержант
Member [заблокирован]

Откуда:
Сообщений: 5122
Dima T
Собрал static. Не надо никаких рантаймов. Под XP работает.

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

Хотел готовую DLL выложить, но сюда не лезет, она 0,5 Мб (0,3 в архиве)

PS Там стандартное ограничение на 64 соединения, т.к. используется select(). Думаю этого достаточно для клиента.

Ух ты, класс!
Надо на всякий случай перешерстить интерфейсы, для Delphi - биндинга...
31 окт 16, 01:39    [19839934]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
Dima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls
31 окт 16, 08:12    [19840054]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
Товарищ младший сержант
Надо на всякий случай перешерстить интерфейсы, для 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
#define FD_SETSIZE 1024
#include <winsock2.h>
...


Добавили группу функций zmq_atomic_counter_*() как понимаю это потокобезопасные счетчики. Не знаю нужны ли они в дельфи, а мне не актуально, в С/С++ есть std::atomic<>

В zmq.h есть какие-то zmq_msg_group() и zmq_msg_set_group(), но описаловки по ним никакой не нашел. Может в будущем напишут зачем они.
31 окт 16, 08:14    [19840056]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
PPA
Dima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls

Попробую. С английским у меня все плохо. Читать немного получается, а написать - засада. Поймут ли автоперевод?
Если найдутся желающие написать за меня - я не против. Надо им заслать только то что добавить в код, написано тут 19839048.

Мой проект для МС2015 наверно не стоит туда слать, т.к. делал его лишь бы скомпилировалось. По хорошему сборку под XP впилить отдельным пунктом в имеющийся у них проект. В тюнинге проектов MSVC я не спец.
31 окт 16, 08:31    [19840073]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
PPA
Dima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls

Отправил. Приняли. Можете пользоваться. Проект для сборки.
2 ноя 16, 15:17    [19851379]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
[quot Dima T]
PPA
Dima T,
Отправил. Приняли. Можете пользоваться.


Круто. спасибо! на выходных протестирую.
3 ноя 16, 18:28    [19857031]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Dima T
PPA
Dima T,

А PR в upstream не планируешь послать?
https://github.com/zeromq/libzmq/pulls

Отправил. Приняли. Можете пользоваться. Проект для сборки.

А ты czmq - используешь?
22 ноя 16, 00:00    [19918622]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
чччД
А ты czmq - используешь?

Нет, свою обертку написал.
22 ноя 16, 13:20    [19920369]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Dima T
чччД
А ты czmq - используешь?

Нет, свою обертку написал.

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

Ядро (libzmq) обычно собирается без проблем, или с минимальным стуком в бубен, а в совокупности с czmq (и с libsodium) часто уже приходится париться: то под новую версию MS VS что-то не собирается, то что-то исключили из поддержки, то еще что...
Плюс поддерживать согласованный биндинг в актуальном состоянии для двух библиотек напряжней, чем для одной.
22 ноя 16, 23:13    [19923080]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
чччД
Куда быстрее было сделать то, что нужно именно тебе на основе ядра, чем разбираться с тем, что кто-то делал "под себя".

Я так понимаю в czmq кроме самого ZMQ дополнительно собран попутно используемый функционал, который приведен к кроссплатформенному виду. Работа с потоками, файловой системой, временем и т.д. Лично мне было интересно все что связано с многопоточностью, т.к. в виндовсе и линуксе все по разному.
Но сегодня это не актуально, т.к. в С++11 добавили все связанное с многопоточностью.
23 ноя 16, 09:28    [19923609]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
С libsodium тоже не стал разбираться. Не настолько важная инфа у меня чтобы усложнять работу стойким шифрованием.
Сделал самодельный Диффи-Хеллман для обмена ключами (64 бита) и CBC шифрование. Плюс CRC32 в начало сообщения, чтобы данные окончательно перемешались после шифрования.
23 ноя 16, 10:03    [19923688]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Dima T
С libsodium тоже не стал разбираться. Не настолько важная инфа у меня чтобы усложнять работу стойким шифрованием.
Сделал самодельный Диффи-Хеллман для обмена ключами (64 бита) и CBC шифрование. Плюс CRC32 в начало сообщения, чтобы данные окончательно перемешались после шифрования.


Мда... "фантазией они не отличались" - (с). В смысле - все как у меня, почти. :) Плюс я еще подписываю блок с помощью аппаратно-реализованного ECC (с использованием Guardant Sign)
...
Насчет CRC32 в качестве "соли" - не очень хорошо. Ибо значение CRC32 постоянно для неизменного блока данных.
Таким образом, один и тот же блок данных после зашифрования одним и тем же ключом будет выглядеть одинаково, что снижают стойкость криптосистемы.
...
В начале обычно втыкают блок случайных данных.
Например, размером, равным ключу шифрования. Этим блоком шифруют сообщение, а сам блок шифруют сеансовым ключом, сгенерированным по алгоритму Д-Х. Таким образом, зашифрованные данные получаются всегда разными, даже если шифруются одинаковые блоки (даже в случае не-ECB режима шифрования).
...
Впрочем, не госсекреты ведь мы защищаем, а с хакерами боремся.
...
23 ноя 16, 13:53    [19924985]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Dima T
Member

Откуда:
Сообщений: 12479
чччД
Насчет CRC32 в качестве "соли" - не очень хорошо. Ибо значение CRC32 постоянно для неизменного блока данных.
Таким образом, один и тот же блок данных после зашифрования одним и тем же ключом будет выглядеть одинаково, что снижают стойкость криптосистемы.

Повторений у меня нет, т.к. внутри еще номер сообщения идет (счетчик на стороне отправителя), для ответов используется.
CRC32 удобно, т.к. одновременно и соль и подпись/контрольная сумма для проверки корректности принятого. Считается быстро и места немного занимает.
23 ноя 16, 14:45    [19925302]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Dima T
чччД
Насчет CRC32 в качестве "соли" - не очень хорошо. Ибо значение CRC32 постоянно для неизменного блока данных.
Таким образом, один и тот же блок данных после зашифрования одним и тем же ключом будет выглядеть одинаково, что снижают стойкость криптосистемы.

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

ОК, все зависит от задачи.
К примеру, у меня на основе zmq реализовано общение с сервером защиты. Трафик невелик. В данном случае обеспечение неповторяемости данных в трафике - вещь обязательная, для защиты от табличной эмуляции.
23 ноя 16, 14:51    [19925339]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Dima T
PPA
Dima 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.

Теперь собирается.
25 янв 17, 15:14    [20144974]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Похоже, что nanomsg таки сдулся, автор несколько месяцев не модифицирует исходники... "сообщество" что-то изредка правит, и все...

А zeromq - напротив, цветет и пахнет, несмотря на то, что умер один из основных разработчиков.
Вот что значит поддержка со стороны большой конторы.
9 май 17, 01:17    [20466022]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
Коллеги, приветствую!
Стою на пороге тотального переписывания сетевого кода в клиент-серверном приложении.
Половину дня просидел изучая что русскоязычные люди пишут про ZeroMQ.
Очень нравится, ясно что надо изучать гайд с офф сайта.
Но не хочется терять время, т.к. кое-что насторожило.
Может вы мне поможите, просветите:
1) на ОС Windows от WinXP (самая ходовая по регионам необъятной родины) нужен и сервер и клиент, ZeroMQ умеет XP или поддержку отключили и больше не будет? Ну т.е. я вижу что на гитхабе есть проект _xp, но вот к примеру сокеты ZMQ_SERVER и ZMQ_CLIENT будут на ней работать? И по вашим ощущениям, не выпилят с очередным релизом (и не останусь я потом сидеть с необновляемой библиотекой?)
2) С Vista и т.д. сервер сможет держать больше сотни коннектов? Т.е. ZeroMQ реально юзает только select в винде, APC не умеет?
3) На linux ZeroMQ умеет МНОГА коннектов? Мне надо 14 тыс постоянных (но слабоактивных) для мониторинга и вялого обмена сообщениями.
5) Где-то прочёл что опасно ZeroMQ смотреть в эти ваши интернеты, заDDOSят мол. Есть у кого опыт, соображения на этот счёт, правда защиты от мусора нет совсем?
6) Технический момент: в итоге-то решение найдено по отключению "мёртвых" клиентов, да? Т.е. я беру любой протокол с поддержкой хардбитинга (или пинг-понга, если есть) и использую, верно?
27 июн 17, 18:38    [20594806]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Антон Аксёнов
Коллеги, приветствую!
Стою на пороге тотального переписывания сетевого кода в клиент-серверном приложении.
Половину дня просидел изучая что русскоязычные люди пишут про 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. Мертвых клиентов рекомендуют "стряхивать". Или периодически(раз в сутки - в час-в минуту, по ситуации) , или когда их (мертвых) становится слишком (по мнению серверного софта) много. Делается тупым образом: сервер разрушает входящий сокет и создает новый, живые клиенты реконнектятся, мертвые - нет.
28 июн 17, 19:58    [20597954]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
Антон Аксёнов
5) Где-то прочёл что опасно ZeroMQ смотреть в эти ваши интернеты, заDDOSят мол. Есть у кого опыт, соображения на этот счёт, правда защиты от мусора нет совсем?

обычно это не актуально, nginx на внешку, как правило, решает такие проблемы - а что с ним делать, знает большинство админов
28 июн 17, 20:04    [20597964]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
kealon(Ruslan)
Антон Аксёнов
5) Где-то прочёл что опасно ZeroMQ смотреть в эти ваши интернеты, заDDOSят мол. Есть у кого опыт, соображения на этот счёт, правда защиты от мусора нет совсем?

обычно это не актуально, nginx на внешку, как правило, решает такие проблемы - а что с ним делать, знает большинство админов


подробнее - как nginx cможет прозрачно прокинуть коннект до zmq и тем более защитить от ddos?
29 июн 17, 08:17    [20598478]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
PPA
подробнее - как nginx cможет прозрачно прокинуть коннект до zmq и тем более защитить от ddos?

хотя бы вот так, хоть и experimental, но всё равно лучше чем напрямую. ещё где-то проекты были

ну а как от ddos защищаться, это уже к админам nginx
29 июн 17, 09:07    [20598549]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
энди
Member

Откуда: Киров, Россия
Сообщений: 892
А использовать очередь от Амазона? С одной стороны конечно стороннее решение, с другой вся головная боль о поддержке соотвествующей инфтраструкруты ложится на чужие плечи, а бесплатных сообщений в месяц там много на что хватить может. Мы использовали, правда в софте на Андроид, весьма неплохо работало.
29 июн 17, 09:26    [20598585]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
чччД,

Спасибо за столь обстоятельный ответ!
Что значит "вкомпиленый" - это не надо будет dll с собой таскать? Мы же про Delphi говорим, да?
В любом случае, высылайте :) Coriolis inbox ru Спасибо!
29 июн 17, 09:44    [20598648]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
Антон Аксёнов
Что значит "вкомпиленый" - это не надо будет dll с собой таскать? Мы же про Delphi говорим, да?

В случае с делфи таскать dll от zmq в любом случае придется, но она будет одна без dll-рантайма от микрософта.
29 июн 17, 12:59    [20599348]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Антон Аксёнов
чччД,

Спасибо за столь обстоятельный ответ!
Что значит "вкомпиленый" - это не надо будет dll с собой таскать? Мы же про Delphi говорим, да?
В любом случае, высылайте :) Coriolis inbox ru Спасибо!

"Вкомпиленный" == речь о рантайме SDK C++ MS VS 2015. Все теперь включено в libzmq.dll, больше не потребуются другие dll, типа msvcp120.dll и msvcr120.dll.
Т.е., одну dll (libzmq.dll) все же таскать с собой придется. :)
...
Выслал.
29 июн 17, 13:01    [20599354]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
чччД, PPA,

А, понял, я и забыл про рантаймы :) А то я уж было подумал что есть какой-то джедайский способ компилить файлы для инклуда в проект)))
А dll потаскать можно (хотя и не очень хочется конечно). Думаю хуки поставлю на CreateFile, ReadFile и буду грузить из памяти модуль, никто не пробовал так?

Файлы на почту пока не пришли, если что.
29 июн 17, 13:12    [20599400]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Антон Аксёнов,

К сообщению приложен файл. Размер - 2Kb
29 июн 17, 13:24    [20599455]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
чччД,

Аттач 2 кб?
вероятно гугл отрезал dll-ку
лучше такие вещи кидать через облако или в архив с паролем сувать :)
29 июн 17, 13:47    [20599539]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
Я так и не получил письмо, по скрину - адрес верный. Нигде нет, ни в папках со спамом, вообще нигде. Странно.
Может, правда, gdrive, ydisck... ?
29 июн 17, 13:48    [20599546]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Антон Аксёнов,

https://cloud.mail.ru/public/Mv7C/85z8Lt9E9
29 июн 17, 14:03    [20599592]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
чччД,

Здорово, тут еще и обёртка высокоуровневая, спасибо!
29 июн 17, 14:09    [20599616]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Антон Аксёнов
чччД,

Здорово, тут еще и обёртка высокоуровневая, спасибо!

Громко сказано: обёртка самая минимальная: работа с только с самыми базовыми вещами (я добавлял функционал по мере надобности): создать контекст, создать сокет, сформировать составное сообщение, принять/отправить составное сообщение, создать "прикрепленный" поток (для работы поллера).
29 июн 17, 14:21    [20599645]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
чччД,

На первых парах мне это и нужно, лишнее только будет отвлекать. В любом случае контроль целостности, очерёдности доставки, пакетирование логических каналов в один физический - делать буду сам. Мне от ZeroMQ нужы только сокеты которые умеют переподключаться в случае обрыва, ну т.е. все плюшки сокетов ZeroMQ.
29 июн 17, 14:31    [20599666]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
PPA
Member

Откуда: Караганда -> Липецк
Сообщений: 792
Антон Аксёнов,

Пишете тут об успехах и проблемах... тоже интересно направление zmq + xp
29 июн 17, 14:45    [20599713]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
Коллеги, прошу прощения что взбаламутил тему, понял что ZeroMQ мне мимо.

У меня по техзаданию есть некие Состояния. Эта такая штука которую протокол отправки данных должен считать (сам!) в самый послелдний момент перед реальной отправкой пакета данных в сеть (в буфер сетевого драйвера). Понятно для того чтобы второй стороне ушли как можно более свежие данные и чтобы небыло спама.
А у 0MQ концепция другая совсем, там к очереди в частности и к низкоуровневым сокетам в целом не пускают.

Всплакнул, ушёл курить ICS, он ближе к железу...
А под никсы придётся lNet курить, (на котоые автор забил).
30 июн 17, 14:30    [20603069]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Антон Аксёнов,

и что ж вы такое делаете-то? ...
...
Вчерась в одном из подразделений РЖД запустили систему на базе zmq, с перепугу заранее опций для тюнинга понавтыкали, а оно и так завелось, протестировали потенциально тяжелые случаи вроде кратковременных и долговременных обрывов связи, а также массовой заливки данных, и домой разъехались, сейчас в окошко на ураган смотрю...
30 июн 17, 14:50    [20603176]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
чччД,

Ничего адски-специфичного, просто интерфейс.
-много прогресс-баров (наша БД позволяет а мы этим вовсю пользуемся.)
-много таблиц "я покажу тебе ВСЕ строки" с большим бегунком, но грузящие только нужные строки с сервера, соотвтетсвенно при скроле надо считать только действительно те строки на которые юзер видит в гриде. Тут главная мера ввести задержку, так сейчас и работает, но если сам сетевой движок будет брать текущую позицию (опосредованно через состояние) то эту задержку можно сильно уменьшить, визуально выглядит очень круто.
...
Отдельная песня, стандартный аргумет "зачем показывать сразу всё, покажи по буквам алфавита/категориям пусть юзер начнёт фильтровать и результат уже показывай" и т.д., на самом деле наболело - кажыдй пытается придумать аргументы что такой подход неверн, но он востребован в реальной жизни, пользователи его любят (потому что удобный). Чем он удобен: пользователь ищет позицию в номенклатуре, он не помнит её название (либо как точно пишется) но хорошо знает что по алфавиту она следует за точно известной ему позицией (или перед ней). Профит, он ищет известную ему позицию и перемещает курсор вверх/вниз. Такая схема работает у нас годами (скоро десятилетиями), сломать её - это ну я не знаю с чем сравнимо, да ни с чем)))
Такой большой абзац потому что, повторюсь, наболело)
...

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

-В дальних планах передача видеопотока, тут то же самое - кадр это состояние: один поток обновляет состояние а другой считывает и передаёт в сеть - в итоге получаем потерю кадров вместо переполненного буфера при кратковременных затыках с сетью (понятно что тут тоже будет переполненный буфер, потому что отправляющая сторона только через heartbeat interval поймёт что что-то не так и перестанет отправлять, но тут буфер будет расти небольшой интервал а в zmq по WaterMark)

Кстати, а WaterMark не поможет ли мне опосредованно понять что сетевой буфер отправки переполнился?
30 июн 17, 15:14    [20603276]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
Т.е. если я поставлю WaterMark в 1 (что-то маленькое), поулчится 0mq не вернёт мне управления пока предыдущий пакет действительно не удёт?
30 июн 17, 15:18    [20603300]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Антон Аксёнов,

у меня тоже все записи сразу клиент видит (вернее, он так думает, что видит), совсем просто: от сервера сперва получаешь списик id's - очень быстро (1млн id's - всего лишь 4 мегабайта), при отображении записей в окне я знаю (мне об этом сообщает компонент отображения) диапазон id's для показа - только записи с этими id's я и подгружаю в кэш. Никакого zmq, использую Firebird & fib+, в качестве средства отображегия - VTV и DevEx, они умеют работать в таком режиме практически "искаропки".
...хотя, если у тебя на клиенте показывается не миллион, а миллиард записей - такой способ уже не годится: 4Гб данных id's тянуть на клинта не гут.
30 июн 17, 15:47    [20603442]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
чччД,

Ага, у меня и такой подход есть, но для дерева (тоже VTV использую).
Нет, записей меньше ))) до 20 тысяч, это если справочник номенклатуры, а обычно меньше. Но тут есть две проблемы.
Одна проблема - в натуральных индексах, тудыть их растудыть. Натуральный СОРТИРОВОЧНЫЙ индекс, представьте себе (например, наименование в верхнем регистре). Так повелось (nosql под названием MSM/GT.M, он сам толкает к такому дизайну). Я единственно как смог улучшить базу - это свести все места изменения ключевых данных в одно и там считать количество сущностей в каждой таблице при апдейтах, это ведёт ко второй Проблеме - при передаче всех индексов одним сжатым паком мне придётся читать всю таблицу, затягивая её в кэш (который, к слову, маленький, ибо сервера у меня - такие же рабочие станции, по железу). А так - отправил первые 25-40 строк и норм.

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

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

Чем глубже - тем страшнее, да? Так и живём :)
30 июн 17, 16:25    [20603667]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
чччД
ZeroMQ
Особенности High-level API CZMQ:

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


Интересно:

//  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 контексте - свои.
1 июл 17, 04:35    [20604701]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
Вот и мне понадобились ZeroMQ.
Есть некий OPC-сервер, подписанный (Advise) на получение изменений тэгов.
При изменении данных сервер вызывает Callback-процедуру, в которую передает массив изменившихся тэгов.
Эти данные мне надо разослать на ~100 (пока) компов.
Казалось все просто: используем топологию "Издатель-подписчик" (PUB-SUB), и дело в шляпе. НО.
НО она (Callback-процедура) выполняется в отдельной нити. Передавать ей заранее созданный сокет низзяааа,
патамушта здесь написано: "Запоминаем: не используем (и не закрываем) сокеты нигде, кроме как нитях, их создавших.",
а каждый раз в начале процедуры создавать, а в конце убивать сокет как-то не айс (имхо).

Вопрос к уважаемому сообществу, в первую очередь к чччД:
Можно ли как-нибудь по другому решить, и если да - то как?
4 июл 17, 09:18    [20611125]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
Вроде вырисовывается такая схема:
1. Поток - Издатель. 2 сокета:
- PULL. получает по inproc сообщения от Callback-a и передает их сокету
- PUB. Рассылает сообщения по TCP клиентам.
2. Callback. 1 сокет:
- PUSH. Создается в начале процедуры, посылает сообщения по inproc Издателю. В конце процедуры убивается.

А?
4 июл 17, 10:01    [20611239]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вик Торович,

оба твоих сообщения я не понял, совсем.
4 июл 17, 11:13    [20611479]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
чччД, как-то так:
{ Псевдокод 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}
5 июл 17, 04:35    [20614196]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вик Торович,

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


Неужели поток каждый раз создается в момент вызова коллбэка?
5 июл 17, 11:50    [20615062]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Если нити для каждого вызова коллбэка разные, я бы не стал связываться с ZMQ, ибо повторный bind() к тому же tcp порту ничего не даст, кроме ожидания момента, когда порт освободится. Мало того, что bind выполняется не мгновенно, потом к нему должны выполниться connect()'s клиентов со стороны SUB, потом еще придется при зверешении коллбека ждать завершения рассылки (задавать ненулевое значение linger для сокета).

Если нить одна и тот же - сокет можно создать и настроить однократно, (например при первом коллбэке).
5 июл 17, 14:17    [20615839]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
чччД
Вик Торович,
Неужели поток каждый раз создается в момент вызова коллбэка?

Конечно же нет :) Поток создается OPC при подписке на данные (Advise).
Но о нем ничего неизвестно до первого изменения данных, читай - вызова коллбэка.

чччД
Если нить одна и тот же - сокет можно создать и настроить однократно, (например при первом коллбэке).

Ага. Именно так и сделал.
{ Псевдокод 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}

Осталось понять, где делать закрытие сокета. Закрыть-то его из другого потока тоже нельзя. Или если очень хочется - то можно? (с).
6 июл 17, 04:09    [20617588]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
чччД, при разрушении контекста сокеты закрываются?
6 июл 17, 05:17    [20617598]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вик Торович,

можно попробовать схитрить, вызвав деструктор самому: поменять хитрый тег, сервер вызовет коллбэк, а там этот тэг, вот и все, все разрушаешь и на всякий выставляешь флаг: "больше работать не хочу"...
6 июл 17, 05:18    [20617600]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вик Торович
чччД, при разрушении контекста сокеты закрываются?


Да, закрываются, но операция разрушения контекста переходит в режим ожидания завершения ввода-вывода сокетами.
Время ожидания задается в параметре linger для сокета, по умолчанию = -1 ("ждать вечно").

Вот тут 20599592, кроме всего прочего, есть простейшая объектная оболочка, где все сокеты я создаю с linger = 0 ("не ждать, завершаться сразу").

Кстати, Linger в 0 рекомендует устанавливать и автор (исходный создатель) библиотеки zmq.

Кстати, сам контекст - он вполне себе thread - safe, его можно передавать в треды, поэтому просто разрушь "главный" контекст, а в коллбэке анализируй коды завершения работы с сокетами (если = -1 - это разрушение, делаем Exit например)
6 июл 17, 05:24    [20617601]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
Да, нашел в описании функции zmq_ctx_term.
6 июл 17, 05:34    [20617604]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
чччД,
Ага, я уже скачал это. Спасибо за инфо о Linger.
пишу потихоньку. Вроде получается.
6 июл 17, 05:39    [20617606]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Вик Торович
Guest
чччД
Вик Торович,

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

Хм. мысли сходятся не только у дураков. Как раз сижу и излагаю коллеге этот сценарий :)
6 июл 17, 05:42    [20617608]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вик Торович,

к сожалению, об особенностях схемы pub-sub я знаю не больше, чем написано ранее про модель "метеостанция": попробовал, повертел и отложил.
6 июл 17, 08:52    [20617750]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Про XPUB и XSUB полный бред написал:
16623545
16623546
Даже не могу вспомнить, с чего это я вдруг решил, что пара XPUB-XSUB каким-то чудесным образом реализует прямую связь между издателем и подписчиком.
...
~~~~~~~~~~~~~~~~~~~~~~~~

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

Предположим, есть не один издатель, а несколько. Используем схему PUB-SUB. Никаких проблем: каждый подписчик коннектится не одному, а к нескольким издателям. Одним и тем же сокетом.

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

Вот для этого случая и нужна схема XPUB-XSUB (см. картинку ниже). Есть один подписчик "proxy" с сокетами PUB, который знает адреса всех издателей, и коннектится к ним сокетом XSUB.
Он получает все входящие сообщения. Он их публикует, используя сокет XPUB. Все остальные подписчики подписываются лишь к одному издателю - "прокси", используя сокеты SUB.
Таким образом, при изменении списка издателей, нужно всего лишь изменить "прокси". Подписчики менять не нужно.

К сообщению приложен файл. Размер - 11Kb
6 июл 17, 09:25    [20617793]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Возникает вопрос - почему бы не использовать в '"proxy" обычную пару PUB-SUB?

Можно. Но XPUB-XSUB умеет делать интересный фокус с подписками. Издатель подписывается на определенные сообщения,
XPUB транслирует эти подписки в специальные сообщения и передает их в XSUB. То есть, "proxy" пересылает эти спец.сообщения со стороны абонентов на сторону издателя.

Вот для этого и нужны XSUB и XPUB.
6 июл 17, 09:36    [20617823]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
Вик Торович,

есть отдельная глава, посвященная особенностям схемы "издатель-подписчик": http://zguide.zeromq.org/page:all#Chapter-Advanced-Pub-Sub-Patterns

Прочитал с удовольствием, правда, пока не придумал - куда применить... :)
6 июл 17, 09:44    [20617847]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
_Читатель_
Guest
Здравия всем.
Почитал. Интересно. Мне наиболее подошел бы Radio-dish pattern.
Но печалька - Radio-dish is still in draft phase..

Кто-нибудь в курсе - проект умер или будет таки развитие?
23 янв 18, 08:53    [21128962]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
_Читатель_
Кто-нибудь в курсе - проект умер или будет таки развитие?

Какой конкретно "проект"?
23 янв 18, 09:43    [21129090]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
_Читатель_
Guest
чччД, ZeroMQ.
23 янв 18, 11:00    [21129469]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД
Guest
_Читатель_,

А ты, про это. Не знаю, там на английском все, я не понимаю.
23 янв 18, 11:25    [21129586]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
Почитал тут про ZMQ и возник один вопрос по безопасности: вот в обычном TCP, как было сказано в начале статьи, в случае если нам передали неверный заголовок сообщения мы можем либо сбросить подключение вообще, либо прочитать испорченные данные в /dev/null и приступить к обработке следующего сообщения.
А вот вопрос: какова будет реакция ZMQ, если злоумышленник, зная его протокол, попытается передать валидный пакет, объемом 100500 гигабайт (объёмом, гарантированно большим чем оперативная память + своп на сервере)?
Можно ли как-нибудь лимитировать допустимый размер сообщений?
29 май 18, 16:43    [21451056]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
s62
Member

Откуда: Жуковский
Сообщений: 820
alekcvp,

так ZeroMQ это же обычная библиотека поверх TCP. Наверняка можно и размер лимитировать, и заголовок проверять и т.д.
29 май 18, 16:48    [21451090]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
s62
Member

Откуда: Жуковский
Сообщений: 820
s62,

то есть "поверх" сокетов видимо.
29 май 18, 16:56    [21451134]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
s62,

Ну так размер лимитирует приложение, а не TCP. А в ZeroMQ между приложением и TCP - библиотека, отсюда и вопрос - как в ней это сделать, раз я поступлении данных я узнаю только когда сообщение прочитано целиком (т.е. оперативка закончится раньше).
29 май 18, 17:01    [21451157]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
__
alekcvp
Почитал тут про ZMQ и возник один вопрос по безопасности: вот в обычном TCP, как было сказано в начале статьи, в случае если нам передали неверный заголовок сообщения мы можем либо сбросить подключение вообще, либо прочитать испорченные данные в /dev/null и приступить к обработке следующего сообщения.
А вот вопрос: какова будет реакция ZMQ, если злоумышленник, зная его протокол, попытается передать валидный пакет, объемом 100500 гигабайт (объёмом, гарантированно большим чем оперативная память + своп на сервере)?
Можно ли как-нибудь лимитировать допустимый размер сообщений?


1. Тебя не смущает неоднократное упоминание того, что ZMQ - для "внутреннего" использования? В рамках локальных сетей, или отдельного компа (для межпроцессного взаимодействия) или даже для внутрипроцессного межнитевого взаимодействия. Откуда возьмутся злоумышленники?
1.1. Про новые возможности ("работа в открытых сетях") я знаю совсем немного, но они появились, например, средства аутентификации.
2. "Сбросить" соединение нельзя, можно пересоздать сокет, что и рекомендуется для разных случаев, например, когда слишком много входящих соединений "зависло" (т.е., клиент подключился, поработал и у шел), сие один из паттернов использования zmq.
3. Да, можно установить опции сокета, ограничив размер сообщения и/или буфера под сообщения: http://api.zeromq.org/4-0:zmq-setsockopt.
29 май 18, 17:09    [21451194]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
s62
alekcvp,

так ZeroMQ это же обычная библиотека поверх TCP. Наверняка можно и размер лимитировать, и заголовок проверять и т.д.

Не, нельзя.
И tcp - лишь один из протоколов, которые может использовать zmq.

Ну да, есть там средства мониторинга ("это кто там подключился?" и т.п.), но zmq как раз для того, чтобы работать атомарными сообщениями: отправляешь сообщение (кадр или несколько кадров данных), и всё, на стороне приема они либо получены либо нет.
Кадр - всего лишь блок ТВОИХ данных, без каких-либо заголовков, длиной от 0 до 2^61-1 байт, дальше фантазируешь сам.

В общем, если тебе нужны сокеты Windows - не нужно искать их в zmq, это другой уровень.
29 май 18, 17:21    [21451238]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
1. Тебя не смущает неоднократное упоминание того, что ZMQ - для "внутреннего" использования? В рамках локальных сетей, или отдельного компа (для межпроцессного взаимодействия) или даже для внутрипроцессного межнитевого взаимодействия. Откуда возьмутся злоумышленники?

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

чччД__
2. "Сбросить" соединение нельзя, можно пересоздать сокет, что и рекомендуется для разных случаев, например, когда слишком много входящих соединений "зависло" (т.е., клиент подключился, поработал и у шел), сие один из паттернов использования zmq.
Т.е. она не умеет даже определять "отвалившиеся" сокеты? Или имеется в виду что "ушел" он оставив запущенное приложение?
чччД__
3. Да, можно установить опции сокета, ограничив размер сообщения и/или буфера под сообщения: http://api.zeromq.org/4-0:zmq-setsockopt.
Ага, спасибо, про ZMQ_MAXMSGSIZE и был вопрос.
29 май 18, 17:37    [21451301]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
Ну да, есть там средства мониторинга ("это кто там подключился?" и т.п.), но zmq как раз для того, чтобы работать атомарными сообщениями: отправляешь сообщение (кадр или несколько кадров данных), и всё, на стороне приема они либо получены либо нет.
Кадр - всего лишь блок ТВОИХ данных, без каких-либо заголовков, длиной от 0 до 2^61-1 байт, дальше фантазируешь сам.

В общем, если тебе нужны сокеты Windows - не нужно искать их в zmq, это другой уровень.

Мне нужно организовать взаимодействие между парой сотен клиентов и сервером на разных машинах в локальной сети (потом, возможно и в интернете), при этом сеть доступна посторонним лицам и ограничить доступ к серверу (по сети) я не могу. Взаимодействие как раз в виде передачи сообщений размером от сотен байт до единиц мегабайт.
29 май 18, 17:41    [21451314]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Антон Аксёнов
Member

Откуда:
Сообщений: 33
Позвольте, встряну.
alekcvp
чччД__
1. Тебя не смущает неоднократное упоминание того, что ZMQ - для "внутреннего" использования? В рамках локальных сетей, или отдельного компа (для межпроцессного взаимодействия) или даже для внутрипроцессного межнитевого взаимодействия. Откуда возьмутся злоумышленники?

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






В этом и проблема. Надо представлять. Тут речь о многосерверной архитектуре (микросервисы и т.д.), это когда ты строишь архитектуру чего-то большого и крутого, когда у тебя не один сервер и куча клиентов а много серверов и между ними надо общаться.
Т.е., не для клиент-сервер это, а для сервер-сервер. Хотя xxxД вот дал ссылку что в этом направлении таки что-то делают (лично я могу понять почему - ZMQ очень крутая).
29 май 18, 17:50    [21451359]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
Т.е. она не умеет даже определять "отвалившиеся" сокеты? Или имеется в виду что "ушел" он оставив запущенное приложение?

Имеется в виду, что "она" даже не знает, приконнектился кто-то или нет.
Все, что есть - это работа с сообщениями.

Ждешь сообщение - получил - обработал. Всё. :)
29 май 18, 17:50    [21451360]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
alekcvp
чччД__
2. "Сбросить" соединение нельзя, можно пересоздать сокет, что и рекомендуется для разных случаев, например, когда слишком много входящих соединений "зависло" (т.е., клиент подключился, поработал и у шел), сие один из паттернов использования zmq.
Т.е. она не умеет даже определять "отвалившиеся" сокеты? Или имеется в виду что "ушел" он оставив запущенное приложение?

а что вас удивляет? какая разница ушёл он или нет, не посылает - значит Так не надо
не обвязке же решать что делать

когда-то когда связь была "очень быстрая" и "очень надёжная", сокеты постоянно отваливались - вот такая физика нашего мира
29 май 18, 19:17    [21451618]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
kealon(Ruslan),

Да, я понял, походу для моей системы, где кроме самого сообщения необходимо знать ещё и от кого оно пришло, эта штука не подходит.
29 май 18, 19:29    [21451625]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
kealon(Ruslan),

Да, я понял, походу для моей системы, где кроме самого сообщения необходимо знать ещё и от кого оно пришло, эта штука не подходит.

Ну так добавь в сообщение кадр с нужной инфой.
Ты думаешь, "чистые" tcp сокеты на приеме волшебным образом эту инфу получают? Нет, ее с передающей стороны присылают. :)
29 май 18, 19:35    [21451631]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
Ну так добавь в сообщение кадр с нужной инфой.
Ты думаешь, "чистые" tcp сокеты на приеме волшебным образом эту инфу получают? Нет, ее с передающей стороны присылают. :)

В "чистом" TCP адресат привязывается к соединению, а тут мало того что придётся передавать информацию в каждом сообщении, так ещё и придумывать какой-то механизм защиты от её подмены.
29 май 18, 20:11    [21451695]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
чччД__
Ну так добавь в сообщение кадр с нужной инфой.
Ты думаешь, "чистые" tcp сокеты на приеме волшебным образом эту инфу получают? Нет, ее с передающей стороны присылают. :)

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


Я фигею, дорогая редакция - (с).

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

А если ты хочешь от zmq получить функционал нижнего уровня - то вообще непонятно, нафига тебе ZMQ.

И, если ты думаешь, что tcp пакет сложно подменить пятью строчками кода - то заодно передавай от меня привет Деду Морозу.
29 май 18, 20:41    [21451787]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
А если ты хочешь от zmq получить функционал нижнего уровня - то вообще непонятно, нафига тебе ZMQ.
И, если ты думаешь, что tcp пакет сложно подменить пятью строчками кода - то заодно передавай от меня привет Деду Морозу.

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

Да, расскажи как пятью строчками подменить обратный адрес в tcp-пакете, а потом получить на него ответ на свой адрес? :)
29 май 18, 20:48    [21451797]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
чччД__
А если ты хочешь от zmq получить функционал нижнего уровня - то вообще непонятно, нафига тебе ZMQ.
И, если ты думаешь, что tcp пакет сложно подменить пятью строчками кода - то заодно передавай от меня привет Деду Морозу.

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

Да, расскажи как пятью строчками подменить обратный адрес в tcp-пакете, а потом получить на него ответ на свой адрес? :)



"Пятью строчками" не подменить, да, лишку сболтнул.

А что ты подразумеваешь под "однозначной идентификацией", конкретно?
29 май 18, 22:12    [21451921]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
"Пятью строчками" не подменить, да, лишку сболтнул.
А что ты подразумеваешь под "однозначной идентификацией", конкретно?

Ну, грубо говоря, после accept() и прохождения идентификации клиента ты можешь полученный сокет использовать как идентификатор клиента, т.е. сервер всегда знает от какого клиента пришло сообщение и может у себя хранить некоторые "характеристики" этого клиента, ассоциированные с ним (через сокет). И "подменить пакет" довольно сложно, т.к. это придётся делать на уровне ip-пакетов и, не вдаваясь в подробности, это довольно геморно.
29 май 18, 22:19    [21451938]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
чччД__
"Пятью строчками" не подменить, да, лишку сболтнул.
А что ты подразумеваешь под "однозначной идентификацией", конкретно?

Ну, грубо говоря, после accept() и прохождения идентификации клиента ты можешь полученный сокет использовать как идентификатор клиента, т.е. сервер всегда знает от какого клиента пришло сообщение и может у себя хранить некоторые "характеристики" этого клиента, ассоциированные с ним (через сокет). И "подменить пакет" довольно сложно, т.к. это придётся делать на уровне ip-пакетов и, не вдаваясь в подробности, это довольно геморно.


Ну и тут сокеты идентифицируются, если нужно. Если используешь пару сокетов типа REQ-REP, то сервер ВСЕГДА отправляет ответ тому, кто послал запрос, со стороны программиста вообще никаких действий не нужно, просто возвращаешь ответ в тот же сокет, откуда пришел запрос. Тут (без твоего участия) сообщение автоматически дополняется кадром идентификации (по умолчанию - случайное число), этот кадр отсекается перед передачей в обрабатывающий код, а потом автоматически дополняется при отсылке ответа.

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

Для третьих типов (например, PUB-SUB) идентификация вообще не нужна, ибо логически бессмысленна.
29 май 18, 22:37    [21451967]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp,

ну вот я клиент, создал сокет, задал ему в качестве идентификатора, например случайное 64-битное число.
Сервер, получив от меня сообщение, будет всегда отправлять ответ мне же, используя этот же идентификатор (часть сообщения).
Что тут можно подменить и с какой целью, конкретно?
29 май 18, 22:44    [21451970]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__,

Ну да, выглядит как то что надо. Короче, надо будет глубже почитать документацию.
29 май 18, 23:02    [21451996]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp,

я так понимаю, что ты желаешь получить ip адрес "той стороны"?

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

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

Или тебе не ip-шник нужен?
29 май 18, 23:02    [21451997]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
Что тут можно подменить и с какой целью, конкретно?

Например, после это клиент может попытаться отправить серверу сообщение с другим ID, прикидываясь другим клиентом. Как он узнает этот "другой ID" мы пока оставим за скобками.
29 май 18, 23:04    [21452002]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
alekcvp,
Или тебе не ip-шник нужен?

Мне нужна двусторонняя связь, правда "наоборот": есть сервер, к нему подключаются клиенты, он им рассылает сообщения, они на них отвечают. Всё. Ключевой момент: нельзя допустить чтобы какой-то клиент имел возможность отвечая на сообщение прикинуться другим клиентом.
29 май 18, 23:06    [21452008]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
чччД__
Что тут можно подменить и с какой целью, конкретно?

Например, после это клиент может попытаться отправить серверу сообщение с другим ID, прикидываясь другим клиентом. Как он узнает этот "другой ID" мы пока оставим за скобками.


Не-не-не. Нужно определиться, что за вид атаку мы отражаем, а потом уже бороться со злоумышленниками.
...
Касаемо твоего случая - сервер будет "думать", что у него два клиента...и?
29 май 18, 23:09    [21452011]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
чччД__
alekcvp,
Или тебе не ip-шник нужен?

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


Если один клиент прикинется другим клиентом (уже существующим, подключенным), то сервер будет слать сообщения им по очереди (такая логика ZMQ). Если я ничего не путаю. Т.е., скорее всего, получится фигня на стороне клиента.
Сие очень легко проверить.
...
Если студенты не "под присмотром" ментора - что им мешает не менять свой адрес, а просто ответить за другого? А если под присмотром - в чем проблема?
29 май 18, 23:15    [21452026]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
Если студенты не "под присмотром" ментора - что им мешает не менять свой адрес, а просто ответить за другого? А если под присмотром - в чем проблема?

То, что никто не видит - что он там у себя делает на экране, а беготня по залу пресекается.
29 май 18, 23:25    [21452049]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
alekcvp
чччД__
Если студенты не "под присмотром" ментора - что им мешает не менять свой адрес, а просто ответить за другого? А если под присмотром - в чем проблема?

То, что никто не видит - что он там у себя делает на экране, а беготня по залу пресекается.


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

Хотя, если у тебя студент сможет разобраться в протоколе, дистанционно (без бегони по залу) на лету извлечь идентификатор сеанса соседа, реассемблировать свое приложение и подменить идентификатор сеанса - что ему мешает узнать соседские id-шники оборудования и подменить ими собственные?

Тут да, только чистые Win-сокеты, с ними студенты уж точно не совладают.
30 май 18, 00:20    [21452147]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Дегтярев Евгений
Member

Откуда: Барнаул
Сообщений: 1369
мне кажется у alekcvp и чччД__ расходится понимание что такое клиент.
30 май 18, 05:32    [21452251]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
alekcvp
Member

Откуда:
Сообщений: 1091
чччД__
Ну, не знаю. Идентификатор оборудования на сервере храни, что ли.
На основе идентификатора оборудования подписывай контрольные сообщения, отправляемые сервером.

Тут вопрос что проще - заморачиваться с идентификаторами и подписями, или просто сделать примитивную обвязку над сокетами для моей задачи.
30 май 18, 11:56    [21453177]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
Дегтярев Евгений
мне кажется у alekcvp и чччД__ расходится понимание что такое клиент.

Да я вообще что-то не по делу влез, человеку явно нечто другое нужно.
Больше не буду, извинения.
30 май 18, 14:29    [21454031]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
Ну вот, зоопарк форматов проектов MS VS больше поддерживаться не будет.

Используем cmake, instead.
14 сен 18, 15:36    [21674848]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
Мимопроходящий
Member

Откуда: бурятский тундрюк, эсквайр
Сообщений: 29045

14.09.2018 15:36, чччД__ пишет:
> Используем cmake, instead.

давно пора!

Posted via ActualForum NNTP Server 1.5

14 сен 18, 16:58    [21674939]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
чччД__
Guest
Мимопроходящий
14.09.2018 15:36, чччД__ пишет:
> Используем cmake, instead.

давно пора!

Это не путь дельфиста.
14 сен 18, 19:27    [21675057]     Ответить | Цитировать Сообщить модератору
 Re: ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 3267
чччД__,
От оно как, XE давно систему сборок ипользует, а дельфийцы оказывается нет!!!!!!!

моргнул и на кнопке прочитал надпись "Презрительный осмотр", всё, пятница удалась
14 сен 18, 19:51    [21675073]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: 1 2 3 4 5 6 7 8 9 10 11      [все]
Все форумы / Delphi Ответить