Publish/Subscribe API for PubNub Mbed SDK

Publish

Description

The pubnub_publish() function is used to send a message to all subscribers of a channel. To publish a message you must first specify a valid publish_key at initialization. A successfully published message is replicated across the PubNub Real-Time Network and sent simultaneously to all subscribed clients on a channel.

Messages in transit can be secured from potential eavesdroppers with SSL/TLS by setting ssl to true during initialization.

Publish Anytime

It's not required to be subscribed to a channel in order to publish to that channel.

Message Data

The message argument can contain any JSON serializable data, including: Objects, Arrays, Ints and Strings. data should not contain special mbed classes or functions as these will not serialize. String content can include any single-byte or multi-byte UTF-8 character.

JSON serialize

It is important to note that you should JSON serialize when sending signals/messages via PUBNUB.

Message Size

The maximum number of characters per message is 32 KiB by default. The maximum message size is based on the final escaped character count, including the channel name. An ideal message size is under 1800 bytes which allows a message to be compressed and sent using single IP datagram (1.5 KiB) providing optimal network performance.

If the message you publish exceeds the configured size, you will receive the following message:

Message Too Large Error

["PUBLISHED",[0,"Message Too Large","13524237335750949"]]

For further details, check Calculating Message Payload Size Before Publish.

Message Publish Rate

Messages can be published as fast as bandwidth conditions will allow. There is a soft limit based on max throughput since messages will be discarded if the subscriber can't keep pace with the publisher.

For example, if 200 messages are published simultaneously before a subscriber has had a chance to receive any messages, the subscriber may not receive the first 100 messages because the message queue has a limit of only 100 messages stored in memory.

Publishing to Multiple Channels

It is not possible to publish a message to multiple channels simultaneously. The message must be published to one channel at a time.

Publishing Messages Reliably

There are some best practices to ensure messages are delivered when publishing to a channel:

  • Publish to any given channel in a serial manner (not concurrently).
  • Check that the return code is success (for example, [1,"Sent","136074940..."])
  • Publish the next message only after receiving a success return code.
  • If a failure code is returned ([0,"blah","<timetoken>"]), retry the publish.
  • Avoid exceeding the in-memory queue's capacity of 100 messages. An overflow situation (aka missed messages) can occur if slow subscribers fail to keep up with the publish pace in a given period of time.
  • Throttle publish bursts in accordance with your app's latency needs, for example, Publish no faster than 5 msgs per second to any one channel.

Publishing Compressed Messages

Message compression lets you send the message payload as the compressed body of an HTTP POST call.

Every PubNub SDK supports sending messages using the publish() call in one or more of the following ways:

  • Uncompressed, using HTTP GET (message sent in the URI)
  • Uncompressed, using HTTP POST (message sent in the body)
  • Compressed, using HTTP POST (compressed message sent in the body)

This section outlines what compressing a message means, and how to use compressed messages to your advantage.

Compressed messages support

Currently, the C and Objective-C SDKs support compressed messages.

Message compression can be helpful if you want to send data exceeding the default 32 KiB message size limit, or use bandwidth more efficiently. Compressing messages is useful for scenarios that include high channel occupancy and quick exchange of information like ride hailing apps or multiplayer games.

Compression Trade-offs

  • Small messages can expand - Compressed messages generally have a smaller size, and can be delivered faster, but only if the original message is over 1 KiB. If you compress a signal (whose size is limited to 64 bytes), the compressed payload exceeds the signal's initial uncompressed size.

  • CPU overhead can increase - While a smaller payload size is an advantage, working with compressed messages uses more CPU time than working with uncompressed messages. CPU time is required to compress the message on the sending client, and again to decompress the message on the receiving client. Efficient resource management is especially important on mobile devices, where increased usage affects battery life. Carefully consider the balance of lower bandwidth and higher speed versus any increased CPU usage.

  • Using Compression - Compression methods and support vary between SDKs. If the receiving SDK doesn't support the sender's compression method, or even if it doesn't support compression at all, the PubNub server automatically changes the compressed message's format so that it is understandable to the recipient. No action is necessary from you.

Messages are not compressed by default; you must always explicitly specify that you want to use message compression. Refer to the code below for an example of sending a compressed message.

struct pubnub_publish_options opt = pubnub_publish_defopts();
opt.method = pubnubSendViaPOSTwithGZIP;
pbresult = pubnub_publish_ex(pn, "channel_name", "{\"message\":\"This message will be compressed\"}", opt);
}]

Method(s)

To Publish a message you can use the following method(s) in the mbed SDK:

enum pubnub_res pubnub_publish (pubnub_t *p, const char *channel, const char *message)
ParameterTypeRequiredDescription
ppubnub_t*YesPointer to PubNub context. Can't be NULL
channelconst char*YesPointer to string with the channel name (or comma-delimited list of channel names) to publish to.
messageconst char*YesPointer to string containing message to publish in JSON format.
enum pubnub_res pubnub_publishv2 (pubnub_t *p, const char *channel, const char *message, bool store_in_history, bool eat_after_reading)
ParameterTypeRequiredDescription
ppubnub_t*YesPointer to Pubnub Client Context
channelconst char*YesPointer to string with the channel name (or comma-delimited list of channel names) to publish to.
messageconst char*YesPointer to string containing message to publish in JSON format.
store_in_historyboolYesIf false, message will not be stored in history of the channel
eat_after_readingconst char*YesIf true, message will not be stored for delayed or repeated retrieval or display

Basic Usage

Publish a message to a channel

pubnub_publish(
ctx,
"my_channel",
"\"message\""
);
pbresult = pubnub_await(ctx);
if (PNR_OK == pbresult) {
/* Published successfully */
}
Subscribe to the channel

Before running the above publish example, either using the Debug Console or in a separate script running in a separate terminal window, subscribe to the same channel that is being published to.

Rest Response from Server

The function returns the following formatted response:

[1, "Sent", "13769558699541401"]

Other Examples

Publish a JSON serialized message

pubnub_t *ctx = pubnub_alloc();
if (NULL == ctx) {
puts("Couldn't allocate a Pubnub context");
return -1;
}
pubnub_init(ctx, "demo", "demo");
pubnub_publish(ctx, "hello_world", "{\"msg\": \"Hello from Pubnub C-core docs!\"}");
pbresult = pubnub_await(ctx);
if (pbresult != PNR_OK) {
printf("Failed to publish, error %d\n", pbresult);
pubnub_free(ctx);
return -1;
}

Publish with cipher key

res = pubnub_publish_encrypted(pbp, chan, "\"Hello world from crypto sync!\"", cipher_key);
if (res != PNR_STARTED) {
printf("pubnub_publish() returned unexpected: %d\n", res);
pubnub_free(pbp);
return -1;
}

Publish with cipher key using pubnub_publish_ex

Using this method you can reuse the cipherKey from the options.

struct pubnub_publish_options opt = pubnub_publish_defopts();
opt.cipher_key = my_cipher_key;
pbresult = pubnub_publish_ex(pn, "my_channel", "42", opt);

Subscribe

Description

This function causes the client to create an open TCP socket to the PubNub Real-Time Network and begin listening for messages on a specified channel. To subscribe to a channel the client must send the appropriate subscribe_key at initialization. By default a newly subscribed client will only receive messages published to the channel after the pubnub_subscribe() call completes.

Retrieving messages

Typically, you will want two separate contexts for publish and subscribe. When changing the active set of subscribed channels, first call pubnub_leave() on the old set.

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.

Unsubscribing from all channels

Unsubscribing from all channels, and then subscribing to a new channel Y is not the same as subscribing to channel Y and then unsubscribing from the previously-subscribed channel(s). Unsubscribing from all channels resets the last-received timetoken and thus, there could be some gaps in the subscription that may lead to message loss.

Method(s)

To Subscribe to a channel you can use the following method(s) in the mbed SDK:

enum pubnub_res pubnub_subscribe (pubnub_t *p, const char *channel, const char *channel_group)
ParameterTypeRequiredDescription
ppubnub_t*YesPointer to PubNub client context. Can't be NULL.
channelconst char*OptionalThe string with the channel name (or comma-delimited list of channel names) to subscribe to.
channel_groupconst char*OptionalThe string with the channel group name (or comma-delimited list of channel group names) to subscribe to.

Basic Usage

Subscribe to a channel:

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

Rest Response from Server

The output below demonstrates the response format to a successful call:

[[], "Time Token"]

Other Examples

Subscribing to multiple channels

It's possible to subscribe to more than one channel using the Multiplexing feature. The example shows how to do that using an array to specify the channel names.

Alternative subscription methods

You can also use Wildcard Subscribe and Channel Groups to subscribe to multiple channels at a time. To use these features, the Stream Controller add-on must be enabled on your keyset in the Admin Portal.

pubnub_t *ctx = pubnub_alloc();
if (NULL == ctx) {
puts("Couldn't allocate a Pubnub context");
return -1;
}
pubnub_init(ctx, "demo", "demo");
pubnub_subscribe(ctx, "hello_world1,hello_world2,hello_world3", NULL);
pbresult = pubnub_await(ctx);
if (pbresult != PNR_OK) {
printf("Failed to subscribe, error %d\n", pbresult);
pubnub_free(ctx);
return -1;
}
else {
char const *msg = pubnub_get(ctx);
show all 23 lines

Subscribing to a Presence channel

Requires Presence add-on

This method requires that the Presence add-on is enabled for your key in the Admin Portal. Read the support page on enabling add-on features on your keys.

For any given channel there is an associated Presence channel. You can subscribe directly to the channel by appending -pnpres to the channel name. For example the channel named my_channel would have the presence channel named my_channel-pnpres.

// Sync

char *presence_channel = malloc(strlen(channel) + strlen(PUBNUB_PRESENCE_SUFFIX) + 1);
strcpy(presence_channel, channel);
strcat(presence_channel, PUBNUB_PRESENCE_SUFFIX);
pubnub_subscribe(
ctx,
presence_channel,
NULL
);
pbresult = pubnub_await(ctx);
if (PNR_OK == pbresult) {
char const *presence_event = pubnub_get(ctx);
while (presnce_event != NULL) {
presence_event = pubnub_get(ctx);
show all 17 lines

Sample Responses

Join Event

{
"action": "join",
"timestamp": 1345546797,
"uuid": "175c2c67-b2a9-470d-8f4b-1db94f90e39e",
"occupancy": 2
}

Leave Event

{
"action" : "leave",
"timestamp" : 1345549797,
"uuid" : "175c2c67-b2a9-470d-8f4b-1db94f90e39e",
"occupancy" : 1
}

Timeout Event

{
"action": "timeout",
"timestamp": 1345549797,
"uuid": "76c2c571-9a2b-d074-b4f8-e93e09f49bd",
"occupancy": 0
}

Custom Presence Event (State Change)

{
"action": "state-change",
"uuid": "76c2c571-9a2b-d074-b4f8-e93e09f49bd",
"timestamp": 1345549797,
"data": {
"isTyping": true
}
}

Interval Event

{
"action":"interval",
"timestamp":1474396578,
"occupancy":2
}

When a channel is in interval mode with presence_deltas pnconfig flag enabled, the interval message may also include the following fields which contain an array of changed UUIDs since the last interval message.

  • joined
  • left
  • timedout

For example, this interval message indicates there were 2 new UUIDs that joined and 1 timed out UUID since the last interval:

{
"action" : "interval",
"occupancy" : <# users in channel>,
"timestamp" : <unix timestamp>,
"joined" : ["uuid2", "uuid3"],
"timedout" : ["uuid1"]
}

If the full interval message is greater than 30KB (since the max publish payload is ∼32KB), none of the extra fields will be present. Instead there will be a here_now_refresh boolean field set to true. This indicates to the user that they should do a hereNow request to get the complete list of users present in the channel.

{
"action" : "interval",
"occupancy" : <# users in channel>,
"timestamp" : <unix timestamp>,
"here_now_refresh" : true
}

Subscribe to a channel group

Requires Stream Controller add-on

This method requires that the Stream Controller add-on is enabled for your key in the Admin Portal. Read the support page on enabling add-on features on your keys.

enum pubnub_res res;

for (;;) {
res = pubnub_subscribe(pn, NULL, channel_group);
read_response(pn, res);
}

Subscribe to the presence channel of a channel group

Requires Stream Controller and Presence add-ons

This method requires both the Stream Controller and Presence add-ons are enabled for your key in the Admin Portal. Read the support page on enabling add-on features on your keys.

enum pubnub_res res;

for (;;) {
res = pubnub_subscribe(pn, NULL, "family-pnpres");
read_response(pn, res);
}

Using cipherKey with subscribe

For subscribe, you don't use the cipher key at subscribe, but, when you get the received messages after the subscribe transaction has finished:

char msg[MAX_MSG_LEN];
pbresult = pubnub_get_decrypted(pn, my_cipher_key, s, sizeof s);
if (PNR_OK == pbresult) {
/* Use the message in `msg` */
}

or, for a different usability/safety trade-off:

pubnub_bymebl_t msg = pubnub_get_decrypted_alloc(pn, my_cipher_key);
if (msg.ptr != NULL) {
/* use the message in `msg.ptr` */
free(msg.ptr);
}

Unsubscribe

Description

To unsubscribe, you need to cancel a subscribe transaction.

  • If you configured SDK to be thread-safe, you can cancel at any time, but, the cancelling may actually fail - that is, your thread may wait for another thread to finish working with the context, and by the time your cancel request gets processed, the transaction may finish.

  • If you configured SDK to not be thread-safe, the only safe way to do it is to use the sync interface and:

    1. Set the context to use non-blocking I/O.
    2. Wait for the outcome in a loop, checking for pubnub_last_result() - rather than calling pubnub_await().
    3. If a condition occurs that prompts you to unsubscribe, call pubnub_cancel().
    4. Wait for the cancellation to finish (here you can call pubnub_await(), unless you want to do other stuff while you wait).
Unsubscribing from all channels

Unsubscribing from all channels, and then subscribing to a new channel Y is not the same as subscribing to channel Y and then unsubscribing from the previously-subscribed channel(s). Unsubscribing from all channels resets the last-received timetoken and thus, there could be some gaps in the subscription that may lead to message loss.

Method(s)

To Unsubscribe from a channel you can use the following method(s) in the mbed SDK:

ParameterTypeRequiredDescription
ppubnub_t*YesPointer to Pubnub Client Context.

Basic Usage

Unsubscribe from a channel:

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);
show all 25 lines

Rest Response from Server

The output below demonstrates the response to a successful call:

{
"action" : "leave"
}

Other Examples

Unsubscribing from a channel group

pbresult = pubnub_subscribe(ctx, NULL, "my_channel_group");
/* 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;
}
show all 19 lines