Posix CPosix CWindows CFreeRTOSmbedCPosix C Publish/Subscribe Tutorial for Realtime Apps

PubNub utilizes a Publish/Subscribe model for real-time data streaming and device signaling which lets you establish and maintain persistent socket connections to any device and push data to global audiences in less than ¼ of a second.
The atomic components that make up a data stream are API Keys, Messages, and Channels.
To build an application that leverages the PubNub Network for Data Streams with Publish and Subscribe, you will need PubNub API Keys which we provide when you Sign-Up.

You will need at the minimum a subscribe_key and publish_key. If a client will only subscribe, and not publish, then the client only need to initialize with the subscribe_key. For clients who will be publishing only, or publishing and subscribing (a client can both publish and subscribe), it will need to initialize with both the subscribe_key and the publish_key.

You only need to supply the publish_key to clients that will publish (send) data to your application over the PubNub network. A read-only client for example would not need to initialize with this key.

 
Although a secret_key is also provided to you along with your publish and subscribe keys in the admin portal, it is not required for plain-old publish and subscribe. You'll only need the secret_key if you are using PAM functionality, which we discuss more in the PAM Feature Tutorial.
A message consists of a channel, and its associated data payload. A publishing client publishes messages to a given channel, and a subscribing client receives only the messages associated with the channels its subscribed to.

PubNub Message payloads can contain any JSON data including Booleans, Strings, Numbers, Arrays, and Objects. Simply publish the native type per your platform, and the clients will JSON serialize the data for you. Subscribers will automatically deserialize the JSON for you into your platform's associated native type.
 
When creating a message, keep these limits in mind:
  • Maximum message size is 32KB
  • The message size includes the channel name
  • The message size is calculated after all URL encoding and JSON serialization has occured. Depending on your data, this could add > 4X the original message size.

Keeping your messages < 1.5KB in size will allow them to fit into a single TCP packet!

For further details please check: https://support.pubnub.com/support/discussions/topics/14000006322

Channels are created on-the-fly, and do not incur any additional charges to use one or many in your application. When you create a PubNub application, all messages will be associated with a channel.

In a unicast (AKA 1:1) design pattern, the channels can be unique for each client in one-to-one communication. For example, user1 subscribes to user1-private, and user2 subscribes to user2-private. Using this pattern, each client listens on a channel which only relevant data to that client is sent. It has the advantage of minimal network usage (each client receives only the data it needs) and minimal processing (no need for filtering unneeded data).

PubNub Galaxy                                                                                                                            
In a multicast (AKA 1:Many) design pattern, a public (AKA system, global, or admin) channel is used for global communications amongst all clients. For example, building off our previous example, while a user can speak to any other user on their private channel, since each client in the application is listening on their private channel AND the public channel, they can receive on either. When receiving a message on the public channel, it may or may not be relevant for that particular receiving client -- to get around this, the client can filter on some sort of key, allowing them to selectively process messages with specific interest to them.

PubNub Pulse
In many cases, based on the use case of your application, the pattern you choose may be unicast, multicast, or a combination. There is no right or wrong pattern to implement, but based on your use case, there may be an optimal, most efficient pattern.
Since the text length of the channel name is counted as part of the entire message, and as such, as part of the maximum message length, it is best to keep the channel name as short as efficiency and utility allows.

Channel names are UTF-8 compatible. Prohibited chars in a channel name are:
  • comma: ,
  • slash: /
  • backslash: \
  • period: .
  • asterisks: *
  • colon: :
The procedure to publish and subscribe is really quite simple:

  • Include the PubNub header file
  • pubnub_init() - instantiate a PubNub instance.
  • pubnub_subscribe() - subscribe to a specific channel.
  • pubnub_publish() - send a message on a specific channel.
  • To unsubscribe, you need to cancel a subscribe transaction using pubnub_cancel().

There is no installation of this SDK. Just clone the Git(hub) repo, or download a ZIP or tarball of a release from https://github.com/pubnub/c-core/releases. It has the code and example Makefiles to get you started.

 
Use the Makefiles as a starting point in your own projects (whether they are based on Make or some other build tool / system or IDE).

The Makefile for POSIX without SSL/TLS support is available at /posix/posix.mk. See /posix/README.md for info on how to build on POSIX (and POSIX-like) systems.

The Makefile for POSIX with SSL/TLS, (via OpenSSL) is available at /openssl/posix.mk in the repo. See /openssl/README.md for info on how to build w/OpenSSL on POSIX and other OpenSSL related data.

The calling pattern you choose to use with PubNub (Synchronous vs Callback) will determine which header to import. More information on these calling patterns can be found later on in this guide.

If using the synchronous (sync) pattern, import only pubnub_sync.h:

#include "pubnub_sync.h"

If using the callback pattern, import pubnub_callback.h and pthread.h:

#include "pubnub_callback.h"
#include <pthread.h>

This client uses dynamic memory allocation for the Pubnub contexts, but the usage is the same as for any other Pubnub C client - always use pubnub_alloc() to create a context (and check its return value) and always use pubnub_free() to dispose of a context.

#include "pubnub_alloc.h"
#include <stdio.h>
int main()
{
    pubnub_t *ctx = pubnub_alloc();
    if (NULL == ctx) {
        puts("Couldn't allocate a Pubnub context");
        return -1;
    }
    /* Do something with ctx… 
        and then: */
    pubnub_free(ctx);
    return 0
}

We only provide one timer - the (total) transaction timer. In general, it is started when a transaction is started and stopped when a transaction is finished. If it expires, the transaction will be cancelled. Keep in mind that this canceling is local, so, for example, if you already published a message, but, for some reason, the HTTP response didn't arrive in time, this canceling will not revoke the publish - it will just stop the wait for response.

If the transaction timer expires, the outcome of the transaction will be timeout - different than when you cancel a transaction yourself.

The actual duration of the timer is at least as long as you set it. It could be significantly higher, depending on various platform issues. But, in general, it will be close to what you set.

You should set the timer after initializing the context and before starting a transaction. The duration you set will be used for all subsequent transactions, regardless of their type (i.e. for publish and subscribe and all other).

C-core supports thread-safe operation, though, for performance, you may think about not using it. To use thread-safety support, define the preprocessor symbol PUBNUB_THREADSAFE (just define it, the value does not matter).

Thread safety is internal. Just because you can access the pubnub context through the Pubnub C-core SDK API from different threads safely, doesn't mean you're off the hook for your own data that is related to a context. For example, if you're using the callback interface and signalling an event from it to other (worker) thread(s), you have to synchronise that data transfer yourself.

If you compiled thread-safety support in, you are free to access the same context from different threads, pretty much in any way you wish. However, there are some advised guidelines you should follow:

  • If you're using the sync blocking interface, threads that come to wait on the context may wait a long time, so try to avoid it (also, re-think your whole need for a thread-safe C-core)
  • If you're using the sync non-blocking interface by calling pubnub_await, things are pretty much the same as for sync blocking interface
  • If you're using the sync non-blocking interface and avoid pubnub_await, waiting threads will not block so long, but, pretty much the only useful thing you can do is cancel a transaction from another thread.
  • Using the sync interface, it's perfectly fine to call pubnub_await or pubnub_last_result in different threads, but, you probably shouldn't do that, as it will make debugging harder.
  • If you're using the callback interface, it's perfectly fine to call pubnub functions from your callback, but, you should avoid doing that, except for some helper functions. Following this guideline will make your debugging, thus life, a lot easier

If you compile without thread-safety support, obviously, you will have an SDK which is not thread safe - that is, it is not safe to use a single context from more than one thread at the same time. So, if you're using such SDK configuration in a multithreaded code, which, on POSIX, you likely are, then:

  1. If at all possible, use a single context from only one thread - the one that created it
  2. If 1. is not possible, provide some synchronization yourself, for example, using pthread condition variables, or just mutexes, or some higher abstraction, like message queues
  3. As a special case, if you're using the callback interface, you can start a transaction in one thread and then don't touch the context from that thread any more - use it only in the callback. This is safe.
 
Keep in mind that it is perfectly safe to use different contexts from different threads at the same time. To each (thread) its own (context).

The Posix C SDK operates as a set of transactions. A transaction is initiated by the client SDK and is defined as a single message exchange between the SDK and PubNub service. Every interaction that the client SDK initiates with PubNub is sequenced as a series of transactions which ultimately results in a PubNub service-specific operation.

The SDK provides a set of status and event identifiers which can help developers interact with the library. The status identifier codes are returned as part of the SDK's API invocation. These are used by the developer to check for status of transactions or for detecting normal / abnormal conditions in an API call. Some of the commonly used status codes are as follows

  1. PNR_OK : Success, the transaction finished successfully
  2. PNR_STARTED : The previously initiated transaction has started.
  3. PNR_IN_PROGRESS : Indicates that the previous transaction with PubNub service is still in progress.

Refer to the API docs for a complete list of status identifiers supported by the library.

Events refer to the PubNub REST operations which are initiated by the client SDK. The most common example of events are subscribe and publish. A client subscribing for a channel is a subscribe event and a client publishing a message on a channel is a publish event.

Some of the common event identifiers are as follows:

  1. PBTT_SUBSCRIBE : Subscriber operation
  2. PBTT_PUBLISH : Publish operation

Refer to the API docs for a complete list of operations supported by the SDK.

This SDK provides sync and callback (notification) interfaces for retrieving the outcome of a Pubnub request/transaction/operation.

The sync interface works like this:

  1. Start a transaction (say, publish - using pubnub_publish())
  2. Either pubnub_await() the outcome, or use your own loop in which you check if (PNR_STARTED != pubnub_last_result())
  3. Handle the outcome as you wish

This is illustrated in the Hello World example below (which is the same for any platform that supports sync interface).

The callback interface is somewhat more flexible, uses less CPU resources, but is, in general, a little harder to use. One way to use it is to emulate the sync interface:

  1. Create a callback function (my_callback) per the prototype required by pubnub_register_callback()
  2. In my_callback(), use a condition variable to signal that outcome was received
  3. Set the callback via pubnub_register_callback()
  4. Start a transaction (say, publish - using pubnub_publish())
  5. Wait on the condition variable (the same one used in my_callback)
  6. Handle the outcome as you wish

This is illustrated in the Hello World example below, using pthreads condition variable. Obviously, on platforms that don't support pthreads you will use some similar API (for example, SetEvent/WaitForSingleObject on Windows).

There are other ways to use the callback interface, like the state machine or similar, where the callback will handle the outcome of a transaction but will also start the next Pubnub transaction, or do other stuff that it needs to do. This is very application specific, so we don't provide an example here.

 

You need to link in the required modules and set the proxy. Use int pubnub_set_proxy_manual() from pubnub_proxy.h. If the proxy server is an authenticating one, you may need to set the username/password, with pubnub_set_proxy_authentication_username_password().

Sample makefile

If this PubNub instance will only be subscribing, you only need to pass the subscribe_key to initialize the instance. If this instance will be subscribing and publishing, you must also include the publish_key parameter.
pubnub_init(ctx, /*publish key*/"demo", /*subscribe key*/"demo");
The channel the messages will be published over is called my_channel. For this example, we will use the same instance to both publish and subscribe. To do this, we'll publish a message to the channel, but only after we're sure we've first successfully subscribed to the channel.

The pubnub_publish() and pubnub_subscribe() methods are pretty simple to use. For both pubnub_publish() and pubnub_subscribe(), the channel attribute defines the channel in use.

When using the pubnub_subscribe() method, the method pubnub_get() should be called once the transaction outcome is reached, to get the received messages:
pubnub_subscribe(ctx, "my_channel", NULL);
pbresult = pubnub_await(ctx);
if (PNR_OK == pbresult) {
    char const *message = pubnub_get(ctx);
    while (message != NULL) {
        message = pubnub_get(ctx);
    }
}
 
The pubnub_subscribe() interface is essentially a transaction to start listening on the channel for arrival of next message. This has to be followed by pubnub_get() call to retrieve the actual message, once the subscribe transaction completes successfully. This needs to be performed every time it is desired to retrieve a message from the channel.
For pubnub_publish(), the message attribute contains the data you are sending.
pubnub_publish(ctx, "my_channel", "\"message\"");
pbresult = pubnub_await(ctx);
if (PNR_OK == pbresult) {
    /* Published successfully */
}

To stop receiving messages on a given channel, you must cancel the subscribe request using pubnub_cancel(). To send explicit leave event to a channel use leave request.

The effects of pubnub_cancel() are different depending on:

  • have you enabled thread-safety
  • the notification interface used (sync or callback) and,
  • with sync interface whether you're using non-blocking I/O.

If thread safety is enabled, you can unsubscribe from a different thread than the one that started the subscribe transaction. But, there is still a difference in behaviour depending on whether you're using blocking or non-blocking I/O.

Let’s do non-blocking I/O first:

/* Thread 1: starts the subscribe and awaits outcome - non-blocking I/O */
pubnub_set_non_blocking_io(ctx);
pbresult = pubnub_subscribe(ctx, "my_channel", NULL);
if (pbresult == PNR_STARTED) {
    pbresult = pubnub_await(ctx);
}


/* Thread 2: cancel the subscribe */
pubnub_cancel(ctx);

Of course, if Thread 2 is too late in cancelling, the cancellation will have no effect.

If using blocking I/O, using pubnub_await() is not advised, because it is most likely that the response will be gotten in a single read from the socket, which is a blocking call, so your cancel request will not be processed until the call is ended, thus, most likely, until the transaction is finished. So, you do something similar to what you did for non-thread-safe configuration.

/* Thread 1: starts the subscribe and awaits outcome - blocking I/O */
pubnub_set_blocking_io(ctx);
pbresult = pubnub_subscribe(ctx, "my_channel", NULL);
while (PNR_STARTED == pbresult) {
     pbresult = pubnub_last_result(ctx);
}


/* Thread 2: cancel the subscribe */
pubnub_cancel(ctx);

Since callback interface implies non-blocking I/O pretty much all the time, setting (non-) blocking I/O doesn't make much sense when using the callback interface.

But, the interesting thing is that you can cancel either from the same thread that started the transaction, or from a different thread. It doesn't matter. Of course, if you're late, and the transaction is over, cancel won't succeed, no matter from what thread you're trying to cancel.

Basically, when you want to cancel, call pubnub_cancel(). It's simple, so we don't provide any examples.

If you haven't enabled thread-safety, you can't access one Pubnub context from different threads at the same time ('cause it would produce race conditions). But, you can unsubscribe as illustrated in the following piece of code, using the sync interface:

pbresult = pubnub_subscribe(ctx, "my_channel", NULL);
/* If we don't set non-blocking I/O, we can't get out of a blocked read */
pubnub_set_non_blocking_io(ctx);
/* Can't use pubnub_await() here, it will block */
while (PNR_STARTED == pbresult) {
    pbresult = pubnub_last_result(ctx);
    /* Somehow decide we want to quit / unsubscribe */
    if (should_stop()) {
        pubnub_cancel(ctx);
        /* If we don't have anything else to do, it's OK to await now,
        but you could again have a loop "against" pubnub_last_result()
        */
        pbresult = pubnub_await(ctx);
        break;
    }
}
if (PNR_CANCELLED == pbresult) {
    puts("Subscribe cancelled - unsubscribed!");
}
 Unsubscribing from all the channel(s) and then subscribing to a new channel Y is not the same than Subscribing to the channel Y and then unsubscribing from the previously subscribe channel(s). Unsubscribing from all the channels resets the timetoken and thus, there could be some gaps in the subscription that may lead to a message loss.