Диагностика сети для ZeroMQ Пример - PullRequest
2 голосов
/ 19 сентября 2019

Я пытаюсь реализовать ZeroMQ для получения приложения на Raspberry Pi 3 (Raspbian Stretch) для связи с приложением на отдельной машине (в данном случае Windows 7 64-битная ОС), связанной проводным или WLAN-соединением.

Я скомпилировал ZeroMQ с интерфейсом библиотеки C на обеих машинах (используя Cygwin в Windows) и пример Hello World (который я немного изменил, чтобы вывести значения указателя, чтобы убедиться, что функции «работают»).Обе машины подключены (в данном случае через проводной канал Ethernet и маршрутизатор), и соединение хорошее (я связываюсь с RPi с ПК через Xrdp или SSH OK).

У меня проблема в том, что клиент/ server Программы ZeroMQ, похоже, не «видят» друг друга, даже если они действительно работают, и мой вопрос: какие первые шаги я должен предпринять, чтобы выяснить, почему это происходит?Существуют ли какие-либо инструменты командной строки или GUI, которые могут помочь мне выяснить, что является причиной блокировки?(например, мониторы активности портов или что-то в этом роде?).

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

// ZeroMQ Test Server
// Compile with
// gcc -o zserver zserver.c -lzmq

#include <zmq.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main (void)
{
    void *context=NULL,*responder=NULL;
    int rc=1;

    //  Socket to talk to clients
    context = zmq_ctx_new ();
printf("Context pointer = %p\n",context);
    responder = zmq_socket (context, ZMQ_REP);
printf("Responder pointer = %p\n",responder);
    rc = zmq_bind (responder, "tcp://*:5555");
printf("rc = %d\n",rc);

    assert (rc == 0);

    while (1) {
        char buffer [10];
        zmq_recv (responder, buffer, 10, 0);
        printf ("Received Hello\n");
        sleep (1);          //  Do some 'work'
        zmq_send (responder, "World", 5, 0);
    }
    return 0;
}

Исходный код на клиенте ПК (Cygwin):

// ZeroMQ Test Client
// Compile with:
// gcc -o zclient zclient.c -L/usr/local/lib -lzmq

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main (void)
{
  void *context=NULL,*requester=NULL;

    printf ("Connecting to hello world server\n");
    context = zmq_ctx_new ();
printf("Context pointer = %p\n",context);
    requester = zmq_socket (context, ZMQ_REQ);
printf("Requester pointer = %p\n",requester);
    zmq_connect (requester, "tcp://localhost:5555");

    int request_nbr;
    for (request_nbr = 0; request_nbr != 10; request_nbr++) {
        char buffer [10];
        printf ("Sending Hello %d\n", request_nbr);
        zmq_send (requester, "Hello", 5, 0);
        zmq_recv (requester, buffer, 10, 0);
        printf ("Received World %d\n", request_nbr);
    }
    zmq_close (requester);
    zmq_ctx_destroy (context);
    return 0;
}

На RPi LXTerminal я запускаю сервер иполучить это:

Context pointer = 0xefe308
Responder pointer = 0xf00e08
rc = 0

и в оболочке Cygwin Bash я запускаю клиент и получаю это:

Connecting to hello world server
Context pointer = 0x60005ab90
Requester pointer = 0x60005f890
Sending Hello 0

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

Ответы [ 2 ]

1 голос
/ 19 сентября 2019

+ 1 для медицинской помощи, использующей явное zmq_close() и zmq_ctx_term() освобождение ресурсов ...
Если вы впервые работаете с ZeroMQ,
здесь можно сначала посмотреть « ZeroMQ Принципы менее чем за пять секунд », прежде чем углубляться в дальнейшие детали

Q: Какие первые шаги я должен предпринять, чтобы выяснить, почему это происходит?

Тест линии прямой видимости как нулевой шаг не имеет смысла здесь.Все localhost размещенные интерфейсы трудно "не увидеть" друг друга.

Далее, протестируйте как первый шаг вызов { .bind() | .connect() } -методов, используя явный адрес, такой как tcp://127.0.0.1:56789(чтобы избежать расширения как * -wildcard, так и localhost -символических переводов имен)

Всегда будьте готовы прочитать / оценить предоставляемую API errno, о которой ZeroMQ постоянно сообщает опоследняя API-операция ZeroMQ, приводящая к состоянию ошибки.

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


Mea Culpa: LoS наверняка не был установлен кодом O / P:

  • RPi .bind() -s на его локальном I / F(и не может быть иначе)
  • ПК .connect() -s не для RPi, но локальный I / F ПК
  • ПК .connect( "tcp://<address_of_RPi>:5555" ) сделает его (используйте тот же IP-адрес, что и в Xrdp или SSH для подключения к RPiили может прочитать одно явно из CLI-терминала RPi после ~$ ip address и использовать его для клиентского кода на стороне ПК)

Два непересекающихся ZeroMQ AccessPoint имеют нулевой способ связи, один разнет транспорта - "провод" от A до B

// Zero MQ Test Server
// Compile with
// gcc -o zserver zserver.c -lzmq

#include <zmq.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main (void)
{
    void *context=NULL,*responder=NULL;
    int rc=1;

    //  Socket to talk to clients
    context = zmq_ctx_new ();                  printf("Context pointer = %p\n",context);
    responder = zmq_socket (context, ZMQ_REP); printf("Responder pointer = %p\n",responder);
    rc = zmq_bind (responder, "tcp://*:5555"); printf("rc = %d\n",rc);
/* ----------------------------------^^^^^^------------RPi interface-----------*/
    assert (rc == 0);

    while (1) {
        char buffer [10];
        zmq_recv (responder, buffer, 10, 0);   printf("Received Hello\n");
        sleep (1);                         //  Do some 'work'
        zmq_send (responder, "World", 5, 0);
    }
    return 0;
}

Исходный код на клиенте ПК (Cygwin):

// ZeroMQ Test Client
// Compile with:
// gcc -o zclient zclient.c -L/usr/local/lib -lzmq

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main (void)
{
    void *context=NULL,*requester=NULL;
                                               printf("Connecting to hello world server\n");
    context = zmq_ctx_new ();                  printf("Context pointer = %p\n",context);
    requester = zmq_socket (context, ZMQ_REQ); printf("Requester pointer = %p\n",requester);
    zmq_connect (requester, "tcp://localhost:5555");
/*---------------------------------^^^^^^^^^^^^^^---------PC-local-interface------*/
    int request_nbr;
    for (request_nbr = 0; request_nbr != 10; request_nbr++) {
        char buffer [10];                      printf("Sending Hello %d\n", request_nbr);
        zmq_send (requester, "Hello", 5, 0);
        zmq_recv (requester, buffer, 10, 0);   printf("Received World %d\n", request_nbr);
    }
    zmq_close (requester);
    zmq_ctx_destroy (context);
    return 0;
}

Может также пожелать прочитать больше о ZeroMQСубъекты здесь


Эпилог:

Проблема, о которой сообщается в отчете о прибылях и убытках, фактически маскируется и остается скрытой от обнаружения API.ZeroMQ позволяет одному AccessPoint иметь 0+ соединений транспортного класса одновременно при условии правильного синтаксиса и выполнения других условий.

Вызов
zmq_connect( reguester, "tcp://<address-not-intended-but-correct>:<legal-port>" ) приведет к юридически справедливому состоянию, и ни один из определенных и задокументированных случаев возможных состояний ошибки не будет зарегистрирован, потому что ни один из всех таких случаев фактически не произошел:

EINVAL
Недопустимая конечная точка.

EPROTONOSUPPORT
Запрошенный транспортный протокол не поддерживается.

ENOCOMPATPROTO
Запрошенный транспортный протокол не совместим с типом сокета.

ETERM
Контекст ØMQ, связанный с указаннымсокет был прерван.

ENOTSOCK
Указанный сокет был недействительным.

EMTHREAD
Нет ввода / выводапоток доступен для выполнения задачи.


Есть некоторые шансы на хотя бы как-то - "обнаружить" проблема будет заключаться в принудительном применении другого рода исключений / ошибок, но отложенных до вызова { zmq_recv() | zmq_recv() } в их неблокирующей форме, куда они могут превратитьсяв отчет EAGAIN или может быть EFSM за то, что он не завершил сквозное повторно подтвержденное квитирование ZMTP-протокола (ни один из контрагентов не был и никогда не будет встречен на локальном порте ПК с удаленным RPi-сервером).сторона ).Для этого также требуются предварительные настройки zmq_setsockopt( responder, ZMQ_IMMEDIATE, 1 ) и другие подробности конфигурации.

Следующий, в ZeroMQ v4. +, Есть возможность проверить подмножество событий, о которых сообщалось в AccessPoint, используя «сокет проверки».с помощью довольно сложной стратегии создания
int zmq_socket_monitor (void *socket, char *endpoint, int events);, прикрепленной к внутренним компонентам AccessPoint через inproc:// транспортный класс ~ здесь "inproc://myPCsocketAccessPOINT_monitor", например:

rc = zmq_socket_monitor( responder,                               // AccessPoint to monitor
                        "inproc://myPCsocketAccessPOINT_monitor", // symbolinc name
                         ZMQ_ALL_EVENTS                           // scope of Events
                         );

Такой созданный внутренний «контрольный сокет» может затем получить zmq_connect() -ed, например:

void             *my_end_of_monitor_socket = zmq_socket ( context, ZMQ_PAIR );
rc = zmq_connect( my_end_of_monitor_socket,               // local-end PAIR-socket AccessPoint
                 "inproc://myPCsocketAccessPOINT_monitor" // symbolic name
                  );

и, наконец, мы можем использовать это, чтобы прочитать последовательность событий (и действовать соответственно):

int event = get_monitor_event( my_end_of_monitor_socket, NULL, NULL );
if (event == ZMQ_EVENT_CONNECT_DELAYED) { ...; }
if (event == ... ) { ...; }

с использованием в качестве инструмента тривиализированного get_monitor_event(), подобного этому, который обрабатывает некоторые из внутренних правил чтения и интерпретации сообщений, состоящих из нескольких частей, которые поступают в порядке, указанном в экземпляре подключенного внутреннего "монитора"в AccessPoint:

// Read one event off the monitor socket; return value and address
// by reference, if not null, and event number by value. Returns -1
// in case of error.

static int
get_monitor_event ( void *monitor, int *value, char **address )
{
// First frame in message contains event number and value
   zmq_msg_t msg;
   zmq_msg_init (&msg);
   if (zmq_msg_recv (&msg, monitor, 0) == -1) return -1; // Interrupted, presumably
   assert (zmq_msg_more (&msg));

   uint8_t *data = (uint8_t *) zmq_msg_data (&msg);
   uint16_t event = *(uint16_t *) (data);

   if (value) *value = *(uint32_t *) (data + 2);

// Second frame in message contains event address
   zmq_msg_init (&msg);
   if (zmq_msg_recv (&msg, monitor, 0) == -1) return -1; // Interrupted, presumably
   assert (!zmq_msg_more (&msg));

   if (address) {
      uint8_t *data = (uint8_t *) zmq_msg_data (&msg);
      size_t size = zmq_msg_size (&msg);
      *address = (char *) malloc (size + 1);
      memcpy (*address, data, size);
      (*address)[size] = 0;
   }
   return event;
}

Какие внутренние API-события можно отслеживать?

Начиная с состояния API v4.2, существует этот набор "внутренних"-monitor (способны) внутренние API-события:

ZMQ_EVENT_CONNECTED
Сокет успешно подключен к удаленному узлу.Значением события является файловый дескриптор (FD) базового сетевого сокета.Предупреждение: нет никакой гарантии, что FD все еще действителен к тому времени, когда ваш код получает это событие.
ZMQ_EVENT_CONNECT_DELAYED
Ожидается запрос на подключение к сокету.Значение события не указано.
ZMQ_EVENT_CONNECT_RETRIED
Запрос на подключение не выполнен, и сейчас повторяется попытка.Значение события - это интервал повторного подключения в миллисекундах.Обратите внимание, что интервал переподключения пересчитывается при каждой повторной попытке.
ZMQ_EVENT_LISTENING
Сокет был успешно связан с сетевым интерфейсом.Значением события является FD основного сетевого сокета.Предупреждение: нет никакой гарантии, что FD все еще действителен к тому времени, когда ваш код получает это событие.
ZMQ_EVENT_BIND_FAILED
Сокет не может привязаться к данному интерфейсу.Значением события является ошибка, сгенерированная системным вызовом привязки.
ZMQ_EVENT_ACCEPTED
Сокет принял соединение от удаленного узла.Значением события является FD основного сетевого сокета.Предупреждение: нет никакой гарантии, что FD все еще действителен к тому времени, когда ваш код получает это событие.
ZMQ_EVENT_ACCEPT_FAILED
Сокет отклонил соединение от удаленного узла.Значением события является ошибка, сгенерированная вызовом accept.
ZMQ_EVENT_CLOSED
Розетка была закрыта.Значением события является FD (теперь закрытого) сетевого сокета.
ZMQ_EVENT_CLOSE_FAILED
Ошибка закрытия сокета.Значением события является ошибка, возвращаемая системным вызовом.Обратите внимание, что это событие происходит только на транспортах IPC.
ZMQ_EVENT_DISCONNECTED
Розетка была неожиданно отключена.Значением события является FD основного сетевого сокета.Предупреждение: этот разъем будет закрыт.
ZMQ_EVENT_MONITOR_STOPPED
Контроль на этом разъеме завершен.
ZMQ_EVENT_HANDSHAKE_FAILED
Сбой квитирования механизма безопасности ZMTP.Значение события не указано.

ПРИМЕЧАНИЕ: в состоянии DRAFT, еще не доступно в стабильных выпусках.
ZMQ_EVENT_HANDSHAKE_SUCCEED


ПРИМЕЧАНИЕ: при добавлении новых событий значение catch-all будетначать возвращать их.Приложение, которое использует строгую и фиксированную последовательность событий, не должно использовать ZMQ_EVENT_ALL, чтобы гарантировать совместимость с будущими версиями.
Каждое событие отправляется в виде двух кадров.Первый кадр содержит номер события (16 бит) и значение события (32 бита), который предоставляет дополнительные данные в соответствии с номером события.Второй кадр содержит строку, которая указывает затронутую конечную точку TCP или IPC.

0 голосов
/ 19 сентября 2019

В zmq_connect необходимо указать IP-адрес малины (который выполнил zmq_bind:

Это должно было быть:

// on PC, remote ip is the raspberry one, the one you use for ssh for instance
rc = zmq_connect(requester, "tcp://<remote ip>:5555");
...