Message Subscription

Principles

Figure 1 shows the process of invoking key interfaces for subscribing to common messages.

Figure 1 Process of invoking the common message subscription interface
  1. Initialize resources.

    Before invoking the OpenHiva interface, invoke the OpenHiva::Init interface for initialization. If the return value is 0, the initialization is successful. Otherwise, the initialization fails.

    Initialization includes node name registration, thread group creation, and resource application.

    • Node name registration:
      • The names of different nodes must be unique. Otherwise, the nodes that are started later will fail to be initialized.
      • If the initialization fails, the node cannot perform the publishing or subscription action.
    • Thread group creation:

      The thread group parameters need to be transferred when the OpenHiva::Init interface is invoked. Create a thread group (ScheduleGroup) as required in advance. Each thread group stores information about the callback thread (for receiving data), including the thread group name (groupName) and thread group scheduling type (scheduleType).

      The internal processing flow of OpenHiva varies according to the value of ThreadGroup.scheduleType in the thread group.

      • UNBIND_AICPU (recommended): indicates threads in the non-deterministic domain. The thread group is not bound to cores. Each group starts a working thread waiting for the event to be activated. The operating system schedules the CPU on which the working thread runs.
      • BIND_AICPU: indicates threads in the deterministic domain, and the thread group is bound to cores. A maximum of four working threads can be started in each group (depending on the number of CPU cores). Each working thread is bound to one core. The scheduling status is determined by the event scheduling mechanism. This mechanism provides high real-time performance, avoiding time jitter caused by kernel scheduling of the operating system to a large extent, and ensuring the determinism of thread switching from sleep to running.
      • USER_DEFINED: indicates that the working thread is not started. This type of group is not scheduled by the framework and must be used together with OpenHiva::SpinOnce. When a user invokes OpenHiva::SpinOnce, the value of groupName must be the same as that of ScheduleGroup.groupName. This interface processes messages in the thread invoked by the user.
  2. Subscribe to messages.
    1. Before subscribing to a topic, you must create a node handle OpenHiva::Node n.
    2. Use the created node handle n to invoke the CreateSubscriber interface to subscribe to the specified topic. After the CreateSubscriber interface is invoked, the Subscriber object is returned.

      Pay attention to the following when setting the input parameters of the CreateSubscriber interface:

      • The input parameter topicName of the CreateSubscriber interface of the subscriber must be the same as that of the CreatePublisher interface of the publisher.
      • The input parameter groupName of the CreateSubscriber interface must be the same as that set in the OpenHiva::Init interface.

      The message subscription process over the CreateSubscriber interface is as follows:

      1. Register the callback function of a specified topic with the working thread to process messages received in the subscription queue.
      2. Create a subscription queue and register subscriber information such as the queue ID and topic with the DataMaster.
      3. After receiving the registration message, OpenHiva (the DataMaster process) searches for the IDs of all subscription queues that subscribe to the topic and binds the IDs to the publish queue IDs.
      4. When a message is published to a publishing queue, the queue scheduler transfers the message from the publishing queue to the subscription queue, sends an event to the working thread of the subscription end, activates the worker thread, and invokes the callback function to process the message.
    3. Invoke the Ready interface of the Subscriber object to check whether the subscriber is successfully created.
  3. Release resources.
    The corresponding resources need to be released before the process ends. The defined OpenHiva APIs cannot be used.
    • In the exception branch, the OpenHiva::Shutdown interface needs to be invoked to release resources, including deregistering the queue ID and topic information and releasing the corresponding memory block.
    • In the main thread, the OpenHiva::WaitForShutdown interface needs to be proactively invoked to release resources to prevent the main thread from exiting in advance. When the process stops abnormally, the WaitForShutdown interface automatically invokes the Shutdown interface to clear resources.

Sample Code

Common and relatively stable parameters (such as queueSize) in the code can be managed in a unified manner through the configuration management module to facilitate subsequent maintenance. For details, see Configuration Management.

The following is a code example of key steps in common message subscription, which is for reference only. When a user creates a callback thread, the internal processing of OpenHiva varies according to the value of ThreadGroup.scheduleType in the thread group.

  • In non-USER_DEFINED mode, the subscriber can automatically start a thread to obtain subscription messages.
  • In USER_DEFINED mode, the subscriber does not automatically start a thread to obtain subscription messages. You need to invoke OpenHiva::SpinOnce to obtain subscription messages.
#include <thread>
#include <unistd.h>
#include "open/subscriber.h"
#include "open/init.h"
#include "open/node.h"
#include "std_msgs/include/StringMessage.h"

// Users define callback functions based on their own service logic to process subscription messages.
void ChatterCallback(const Hiva::StdMgs::StringMessage msg)
{
    HIVA_WARN("I heard    :%s", msg.stringData.c_str());
}

int main(int argc, char **argv)
{
    // 1. Initialize resources.
    uint32_t queueSize = 10;            // Length of the data publish queue. The maximum value is 128.
    uint32_t maxMsgSize = 20000;      // Maximum size of a frame, in bytes.
    uint32_t blockNum = 20;            // Number of blocks in the shared memory pool. The default value is queueSize x 2. The maximum value is 256.
    uint32_t queueSize = 10U;          // Queue length.
    bool overwrite = true;            // Indicates whether to overwrite data.
    bool queueFCFlag = false;        // Indicates whether to enable queue flow control.
    uint16_t queueTTL = 0U;          // Time difference of flow control
    const std::string groupName = testGroup.groupName;   // The value of groupName must be the same as that of ThreadGroup.groupName in the Init interface.
    const std::string topicName = "/chatter";           // Name of the subscribed topic, which must be the same as that of the topic published by the publisher.
    // Define a thread group.
    std::vector<OpenHiva::ScheduleGroup> threadGroup;
    OpenHiva::ScheduleGroup testGroup;
    testGroup.groupName =  "charter_listener";                       // Thread group name, which must be unique in each process.
    testGroup.scheduleType = OpenHiva::ScheduleType::UNBIND_AICPU;  // Indicates whether the working thread is deterministic.
    threadGroup.push_back(testGroup);
    // Invoke the resource initialization interface.
    OpenHiva::Init(argc, argv, threadGroup); 
    HIVA_ERROR("Listener init ok!");

    // 2. Subscribe to messages.
    // Construct the Node object.
    OpenHiva::Node n("/listener");
    // Construct TopicOptions.
    OpenHiva::TopicOptions topicOps;
    topicOps.BuildGroupName(groupName)
            .BuildMessageTraits<Hiva::StdMsgs::StringMessage>()
            .BuildShmOptions(maxMsgSize, blockNum)
            .BuildQueueOptions(queueSize, overwrite, queueFCFlag, queueTTL)
            .BuildTopicName(topicName);
    // Invoke the Subscriber interface through the NodeHandle object to subscribe to the topic. The Subscriber object is returned.
    std::function<void(Hiva::StdMgs::StringMessage)> callBack = ChatterCallback;
    std::shared_ptr<OpenHiva::Subscriber> sub = n.CreateSubscriber(topicName, callBack, topicOps);
    // Check whether the Subscriber object is successfully constructed. If the operation fails, invoke the Shutdown function to release resources and exit.
    if ((sub == nullptr) || !sub->Ready()) {
        HIVA_ERROR("sample_listener: sub.Ok() is false!");
        OpenHiva::Shutdown();    
        return 0;
}  
    // 3. Release resources.
    // Check the Hiva node status. If the node is enabled, true is returned. If the node is shut down or fails to be initialized, false is returned. In this case, packets cannot be sent or received correctly.
    while (OpenHiva::Ready()) {  
       OpenHiva::SpinOnce(testGroup.groupName);   // In USER_DEFINED mode, SpinOnce is invoked. In non-USER_DEFINED mode, SpinOnce does not need to be invoked.
        sleep(1);
        std::cout<<"I'm listerner"<<std::endl;
    }
    // This function is invoked in blocking mode to prevent the process from exiting. When you press Ctrl+C or use the kill-2 command on the process, the function returns immediately.
    OpenHiva::WaitForShutdown(); 
    return 0;
}