Channels
Ably Platform service organizes the message traffic within applications into named channels. Channels are the medium through which messages are distributed; clients attach to channels to subscribe to messages, and every message published to a unique channel is broadcast by Ably to all subscribers. This scalable and resilient messaging pattern is commonly called pub/sub.
Getting started
The Ably Realtime client library provides a straightforward API for publishing and subscribing to messages on a channel. If the channel does not exist at the time the client is attached, a channel will be created in the Ably system immediately.
var realtime = new Ably.Realtime('xVLyHw.6sjaDQ:dw-Cm1SWO8RbwvW7un-skc-csxdKtQFtkzhmcRCbcuY');
var channel = realtime.channels.get('hue-tip-tin');
channel.subscribe(function(message) {
alert('Received: ' + message.data);
});
channel.publish('example', 'message data');
var Ably = require('ably');
var realtime = new Ably.Realtime('xVLyHw.6sjaDQ:dw-Cm1SWO8RbwvW7un-skc-csxdKtQFtkzhmcRCbcuY');
var channel = realtime.channels.get('hue-tip-tin');
channel.subscribe(function(message) {
console.log("Received: " message.data);
});
channel.publish("example", "message data");
realtime = Ably::Realtime.new('xVLyHw.6sjaDQ:dw-Cm1SWO8RbwvW7un-skc-csxdKtQFtkzhmcRCbcuY')
channel = realtime.channels.get('hue-tip-tin')
channel.subscribe do |message|
puts "Received: #{message.data}"
end
channel.publish 'example', 'message data'
AblyRealtime realtime = new AblyRealtime("xVLyHw.6sjaDQ:dw-Cm1SWO8RbwvW7un-skc-csxdKtQFtkzhmcRCbcuY");
Channel channel = realtime.channels.get("hue-tip-tin");
channel.subscribe(new MessageListener() {
@Override
public void onMessage(Message message) {
System.out.println("New messages arrived. " + message.name);
}
});
channel.publish("example", "message data");
AblyRealtime realtime = new AblyRealtime("xVLyHw.6sjaDQ:dw-Cm1SWO8RbwvW7un-skc-csxdKtQFtkzhmcRCbcuY");
IRealtimeChannel channel = realtime.Channels.Get("hue-tip-tin");
channel.Subscribe(message => {
Console.WriteLine($"Message: {message.Name}:{message.Data} received");
});
channel.Publish("example", "message data");
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"xVLyHw.6sjaDQ:dw-Cm1SWO8RbwvW7un-skc-csxdKtQFtkzhmcRCbcuY"];
ARTRealtimeChannel *channel = [realtime.channels get:@"hue-tip-tin"];
[channel subscribe:^(ARTMessage *message) {
NSLog(@"Received: %@", message.data);
}];
[channel publish:@"example" data:@"message data"];
let realtime = ARTRealtime(key: "xVLyHw.6sjaDQ:dw-Cm1SWO8RbwvW7un-skc-csxdKtQFtkzhmcRCbcuY")
let channel = realtime.channels.get("hue-tip-tin")
channel.subscribe { message in
print("Received: \(message.data)")
}
channel.publish("example", data: "message data")
If you would prefer to just dive into code and see some examples of how to use channels, then we recommend you take a look at our Realtime tutorials.
Channels
In order to publish, subscribe to, or be present on a channel, you must first obtain a channel instance and then attach to that channel. In most instances, as a convenience, it is unnecessary to explicitly attach a channel as it will implicitly attached when performing any operation on the channel such as publishing or subscribing.
Obtaining a channel instance
A Channel
object is a reference to a single channel. A channel instance is obtained from the channels
collection of the Realtime::Client
Realtime
ARTRealtime
AblyRealtime
instance, and is uniquely identified by its unicode string name. Find out more about channel naming
var channel = realtime.channels.get('channelName');
var channel = realtime.channels.get('channelName');
Channel channel = realtime.channels.get("channelName");
IRealtimeChannel channel = realtime.Channels.Get("channelName");
channel = realtime.channels.get('channelName')
ARTRealtimeChannel *channel = [realtime.channels get:@"channelName"];
let channel = realtime.channels.get("channelName")
Setting channel options and encryption
A set of “channel options”/api/realtime-sdk/channels#channel-options may also be passed to configure a channel for encryption. Find out more about symmetric message encryption.
Ably.Realtime.Crypto.generateRandomKey(function(err, key) {
var options = { cipher: { key: key } };
var channel = realtime.channels.get('channelName', options);
});
Ably.Realtime.Crypto.generateRandomKey(function(err, key) {
var options = { cipher: { key: key } };
var channel = realtime.channels.get('channelName', options);
});
CipherParams params = Crypto.getDefaultParams(key);
ChannelOptions options = new ChannelOptions();
options.encrypted = true;
options.cipherParams = params;
Channel channel = realtime.channels.get("channelName", options);
byte[] key = Crypto.GenerateRandomKey();
CipherParams cipherParams = Crypto.GetDefaultParams(key);
ChannelOptions channelOpts = new ChannelOptions(cipherParams);
IRealtimeChannel encryptedChannel = realtime.Channels.Get("channelName", channelOpts);
key = Ably::Util::Crypto.generate_random_key
options = { cipher: { key: key } }
channel = realtime.channels.get('channelName', options)
NSData *key = [ARTCrypto generateRandomKey];
ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:key];
ARTRealtimeChannel *channel = [realtime.channels get:@"channelName" options:options];
let key = ARTCrypto.generateRandomKey()
let options = ARTChannelOptions(cipherKey: key)
let channel = realtime.channels.get("channelName", options: options)
Channel lifecycle
Channels are not pre-configured or provisioned by Ably in advance; they are created on demand when clients attach, and remain active until such time that there are no remaining attached clients. Within the dashboard for your app however, you can pre-configure one or more channel namespaces (i.e. name prefixes), and associate different attributes and access rights with those namespaces. Find out more about channel namespaces.
The following example explicitly attaches to a channel, which results in the channel being provisioned in Ably’s global realtime cluster. This channel will remain available globally until there are no more clients attached to the channel:
realtime.channels.get('chatroom').attach(function(err) {
console.log('"chatroom" exists and is now available globally in every datacenter');
});
realtime.channels.get('chatroom').attach(function(err) {
console.log('"chatroom" exists and is now available globally in every datacenter');
});
realtime.channels.get('chatroom').attach do |channel|
puts "'chatroom' exists and is now available globally in every datacenter"
end
Channel channel = realtime.channels.get("chatroom");
channel.on(new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange state) {
switch (state.current) {
case attached: {
System.out.println("'chatroom' exists and is now available globally");
}
}
}
});
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.Attach((success, error) => {
Console.WriteLine("'chatroom' exists and is now available globally");
});
[[realtime.channels get:@"chatroom" options:options] attach:^(ARTErrorInfo *error) {
NSLog(@"'chatroom' exists and is now available globally in every datacenter");
}];
realtime.channels.get("chatroom").attach { error in
print("'chatroom' exists and is now available globally in every datacenter")
}
Clients attach to a channel in order to participate on that channel in any way (either to publish, subscribe, or be present on the channel).
Channel metadata
Ably provides a REST API to query your app for metadata about channels, as well as a realtime API to subscribe to channel lifecycle events. Using the REST API, you can enumerate all active channels, or obtain the status of an individual channel. Using our Realtime API, you can subscribe to channel lifecycle events (such as being created or closed etc), or subscribe to periodic occupancy updates for all active channels (such as how many people are subscribed to a channel).
Implicit attach
Although the attach operation can be initiated explicitly by a client, it is more common for the client to perform a publish or subscribe operation, and the client library will initiate the attach if the channel is not already attached. The client library allows clients to begin publishing messages to a channel as soon as the channel has been created, and messages are queued until such time as the attach has succeeded or failed.
var channel = realtime.channels.get('chatroom');
channel.subscribe('action', function(message) { // implicit attach
console.log('Message received '' + message.data);
});
channel.publish('action', 'boom!');
var channel = realtime.channels.get('chatroom');
channel.subscribe('action', function(message) { // implicit attach
console.log('Message received '' + message.data);
});
channel.publish('action', 'boom!');
channel = realtime.channels.get('chatroom')
channel.subscribe('action') do |message| # implicit attach
puts "Message received: #{message}";
end
channel.publish 'action', 'boom!'
Channel channel = realtime.channels.get("chatroom");
/* Implicit attach when subscribing */
channel.subscribe(new MessageListener() {
@Override
public void onMessage(Message message) {
System.out.println("Message received: " + message.data);
}
});
channel.publish("action", "boom!");
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.Subscribe(message => Console.WriteLine("Message received: " + message.Data));
channel.Publish("action", "boom");
ARTRealtimeChannel *channel = [realtime.channels get:@"chatroom" options:options];
[channel subscribe:@"action" callback:^(ARTMessage *message) {
NSLog(@"Message received: %@", message.data);
}]
[channel publish:@"action" data:@"boom!"];
let channel = realtime.channels.get("chatroom")
channel.subscribe("action") { message in
print("Message received: \(message.data)")
}
channel.publish("action", data: "boom!")
Normally, errors in attaching to a channel are communicated through the attach callback. For implicit attaches (and other cases where a channel is attached or reattached automatically, e.g. following the library reconnecting after a period in the suspended
state), there is no callback, so if you want to know what happens, you’ll need to listen for channel state changes.
Channel states
A channel can exist in any of the following states:
- initializedInitialized
- A
Channel
object having this state has been initialized but no attach has yet been attempted
- attachingAttaching
- An attach has been initiated by sending a request to Ably. This is a transient state; it will be followed either by a transition to attached, suspended, or failed
- attachedAttached
- Attach has succeeded. In the attached state a client may publish and subscribe to messages, or be present
- detachingDetaching
- A detach has been initiated on the attached
Channel
by sending a request to Ably. This is a transient state; it will be followed either by a transition to detached or failed
- detachedDetached
- The
Channel
, having previously been attached, has been detached by the user
- suspendedSuspended
- The
Channel
, having previously been attached, has lost continuity, usually due to the client being disconnected from Ably for more than two minutes. It will automatically attempt to reattach as soon as connectivity is restored
- failedFailed
- An indefinite failure condition. This state is entered if a
Channel
error has been received from the Ably service (such as an attempt to attach without the necessary access rights)
Listening for state changes
The Channel
object is an EventEmitter
and emits an event whose name is the new state whenever there is a channel state change. Listeners are passed a “ChannelStateChange”/api/realtime-sdk/channels#channel-state-change object in the first argument. This contains the current
and previous
states, a resumed
flag which indicated whether message continuity on this channel is preserved (from the last time that the channel was attached), and (sometimes) a reason
for the state change. As with all events from an EventEmitter
in the Ably library, this
within the listener function is a reference to an event object whose event
property is the name of the event that fired. This allows a listener to listen for all events with a single registration and know, which event fired when it is entered.
The Channel
object can also emit one event that is not a state change: an update
event. This happens when there’s a change to channel conditions for which the channel state doesn’t change. For example, a partial loss of message continuity on a channel (typically after a resume) for which the channel state remains attached
would lead to an update
event being emitted, with both current
and previous
set to “attached
”, and the resumed
flag set to false
. So if you get such an event, you’ll know there may be messages you’ve missed on the channel, and if necessary you can use the “History”/api/realtime-sdk/channels#history api to retrieve them.
channel.on('attached', function(stateChange) {
console.log('channel ' + channel.name + ' is now attached');
console.log('Message continuity on this channel ' + \
(stateChange.resumed ? 'was' : 'was not') + ' preserved');
});
channel.on('attached', function(stateChange) {
console.log('channel ' + channel.name + ' is now attached');
console.log('Message continuity on this channel ' + \
(stateChange.resumed ? 'was' : 'was not') + ' preserved');
});
channel.on(:attached) do |channel_state_change|
puts "channel #{channel.name} is now attached"
puts "Message continuity #{channel_state_change.resumed ? 'was' : 'was not'} preserved"
end
channel.on(ChannelEvent.attached, new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange) {
System.out.println("channel " + channel.name + " is now attached");
if (stateChange.resumed) {
System.out.println("Message continuity was preserved");
} else {
System.out.println("Message continuity was not preserved");
}
}
});
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.On(ChannelEvent.Attached, stateChange => {
Console.WriteLine("channel " + channel.Name + " is now attached");
if (stateChange.resumed) {
Console.WriteLine("Message continuity was preserved");
} else {
Console.WriteLine("Message continuity was not preserved");
}
});
[channel on:ARTChannelEventAttached callback:^(ARTChannelStateChange *stateChange) {
NSLog(@"channel %@ is now attached", channel.name);
if (stateChange.resumed) {
NSLog(@"Message continuity was preserved");
} else {
NSLog(@"Message continuity was not preserved");
}
}];
channel.on(.attached) { stateChange in
print("channel \(channel.name) is now attached")
if (stateChange.resumed) {
print("Message continuity was preserved")
} else {
print("Message continuity was not preserved")
}
}
Alternatively a listener may be registered so that it receives all state change events.
var myListener = function(stateChange) {
console.log('channel state is ' + stateChange.current);
console.log('previous state was ' + stateChange.previous);
if(stateChange.reason) {
console.log('the reason for the state change was: ' + stateChange.reason.toString());
}
});
channel.on(myListener);
var myListener = function(stateChange) {
console.log('channel state is ' + stateChange.current);
console.log('previous state was ' + stateChange.previous);
if(stateChange.reason) {
console.log('the reason for the state change was: ' + stateChange.reason.toString());
}
});
channel.on(myListener);
channel.on do |channel_state_change|
puts "channel state is #{channel_state_change.current}"
end
channel.on(new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason) {
System.out.println("channel state is " + ChannelState.values()[stateChange.current]);
}
});
channel.On(stateChange => Console.WriteLine("channel state is " + stateChange.Current));
ARTEventListener *listener = [channel on:^(ARTChannelStateChange *stateChange) {
NSLog(@"channel state is %@", stateChange.current);
}];
let listener = channel.on { stateChange in
print("channel state is \(stateChange.current)")
}
Previously registered listeners can be removed individually or all together.
/* remove the listener registered for a single event */
channel.off('attached', myListener);
/* remove the listener registered for all events */
channel.off(myListener);
/* remove the listener registered for a single event */
channel.off('attached', myListener);
/* remove the listener registered for all events */
channel.off(myListener);
# remove the listener proc registered for a single event
channel.off(:attached, &my_proc)
# remove the listener proc registered for all events
channel.off(&my_proc)
/* remove the listener registered for a single event */
channel.off(ChannelEvent.attached, channelStateListener);
/* remove the listener registered for all events */
channel.off(channelStateListener);
// remove the listener registered for a single event
channel.Off(ChannelEvent.Attached, channelStateListener);
// remove the listener registered for all events
channel.Off(channelStateListener);
// remove the listener registered for a single event
[channel off:ARTChannelEventAttached listener:listener];
// remove the listener registered for all events
[channel off:listener];
// remove the listener registered for a single event
channel.off(.attached, listener: listener)
// remove the listener registered for all events
channel.off(listener)
Handling channel failures
Channel attach and detach operations are asynchronous – after initiating an attach request, the client will wait for a response from Ably that confirms that the channel is established on the service and then trigger a state change event.
The client libraries will attempt to automatically recover from non-fatal error conditions. However, you can handle them yourself if you prefer by subscribing to channel state changes, or using the callbacks availablewaiting for a result when explicitly calling attach
.
realtime.channels.get('private:chatroom').attach(function(err) {
if (err) {
console.error('Attach failed: ' + err);
}
});
realtime.channels.get('private:chatroom').attach(function(err) {
if (err) {
console.error('Attach failed: ' + err);
}
});
deferrable = realtime.channels.get('private:chatroom').attach
deferrable.errback do |error|
puts "Attach failed: #{error}"
end
Channel channel = realtime.channels.get("private:chatroom");
channel.on(new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason) {
switch (stateChange.current) {
case failed: {
System.out.println("Attach failed: " + reason.message);
}
}
}
});
channel.attach();
IRealtimeChannel privateChannel = realtime.Channels.Get("private:chatroom");
privateChannel.Attach((_, error) => {
if (error != null)
{
Console.WriteLine("Attach failed: " + error.Message);
}
});
[[realtime.channels get:@"private:chatroom"] attach:^(ARTErrorInfo *error) {
if (error) {
NSLog(@"Attach failed: %@", error);
}
}];
realtime.channels.get("private:chatroom").attach { error in
if let error = error {
print("Attach failed: \(error)")
}
}
Fatal channel errors
Some classes of errors are fatal. These cause the channel to move to the FAILED
state. The client library will not attempt any automatic recovery actions. For example: attempting to attach to a channel that the token you’re using does not have the subscribe
capability for will cause that channel to enter the FAILED
state.
Note that while fatal errors won’t get better on their own, they can be fixable. For example, if a channel goes into the FAILED
state due to the client not having the right capabilities to attach to it, that client could use Auth#authorize()
to obtain a new token which does have the right capabilities, then call “attach()
”/api/realtime-sdk/channels#attach on the channel. While the library will not automatically reattach in the FAILED
state, explicit calls to “attach()
”/api/realtime-sdk/channels#attach will make the client try again.
Nonfatal errors
Other types of errors are nonfatal. For example, a client may have network connectivity issues, or a channel may experience a loss of strict message continuity. The library will automatically attempt to recover from these events. If channel continuity is lost in the process, the library will notify you though a resumed
flag in the ATTACHED
or UPDATE
event, so you can decide how to handle that failure.
For every channel ATTACHED
and UPDATE
event, the ChannelStateChange
object contains a resumed
attribute. When true, there has been no loss of continuity from the last time the channel was attached. When false, there has been a loss of continuity. So for example:
- The first time you attach to a channel on a fresh connection,
resumed
will be false, as there was nothing to continue from. - If you successfully recover a connection and reattach to your channels, the
resumed
flag on theATTACHED
events will tell you whether messages continuity was preserved. Any channel for which it’strue
is guaranteed to receive every message it missed while the client was disconnected. - If you resume or recover a connection unsuccessfully (so you get a fresh connection) — generally because you were disconnected for more than two minutes, which is how long Ably holds connection state for — continuity is lost. If you were resuming, all your channels (which will have gone into the
SUSPENDED
state after two minutes) will still reattach automatically, and you will getATTACHED
events withresumed
false. - If Ably needs to signal a loss of message continuity on an attached channel (for example, during a partially successful resume where the client was disconnected for less than two minutes), you will get an
UPDATE
event withresumed
false.
Connection state change side effects on channels
- If the connection state becomes
CLOSED
, all channels will becomeDETACHED
- If the connection state becomes
FAILED
, all channels will becomeFAILED
- If the connection state becomes
SUSPENDED
, all previously-ATTACHED
orATTACHING
channels will becomeSUSPENDED
- If the connection state becomes
CONNECTED
, any channels that wereSUSPENDED
will be automatically reattached
Channel namespaces
One or more channel namespaces, or channel name prefixes, may be configured for an app in your dashboard. When a channel is created whose name is prefixed with one of the namespaces, the channel assumes certain configured attributes associated with that namespace. For example, a channel namespace named “private
” would match channels named “private
”, “private:chat
”, “private:chat:mike
”.
Namespace-prefixed channel names are delimited by a single colon :
; the first component of the channel name (from the start up to and including the last character before the colon) is the namespace. A channel name may validly contain a colon even if the namespace component does not correspond to a namespace; also, a channel may contain multiple colons and only the component up to the first colon will be matched with a namespace. The only restriction on channel names is that a channel name may not start with a colon :
, an open square bracket [
and it may not be empty.
Namespaces are defined and configured via the application dashboard settings. The namespace attributes that can be configured are:
- Persisted messages – If enabled, all messages within this namespace will be stored according to the storage rules for your account. You can access stored messages via the history API
- Require identification – if enabled, clients will not be permitted to subscribe to matching channels unless they are both authenticated and identified (they have an assigned client ID). Anonymous clients are not permitted to join these channels. Find out more about authenticated and identified clients
- Require TLS – if enabled, only clients who have connected to Ably over TLS will be allowed to join the channel
Key or token capabilities can also specify access rights based on channel namespace, find out more about authentication
Presence
Channels expose a presence
Presence
member which a client can use to obtain channel presence information and to enter and leave the presence channel itself. See the presence documentation for details.
API Reference
View the Channels and Channel API Reference.