Skip to content

Response Channels

Dmytro Vyazelenko edited this page May 1, 2024 · 12 revisions

This feature is currently experimental and only available in Aeron Transport (not in Archive or Cluster) from 1.44.0 and later.

Response channels are a mechanism to provide a simpler server/client request-response configuration to Aeron clients. One of the difficulties when with implementing bi-directional behaviour with Aeron is that the server needs to know the address of the client. This is problematic for two reasons. Firstly, the client needs to resolve a local address that is reachable by the server, which can be complicated if the client machine has multiple network addresses; it requires extra configuration for the clients. Secondly, it doesn't work when network address translation is in place between the client and the server hosts.

The response channels feature addresses this by making it possible to setup the return channel by specifying a control endpoint of the server instead of the endpoint on the client. Because Aeron channels are unidirectional, in order to get bi-directional communication two channels are required, one for each direction. In this case we have a channel that flows from client to server, which we refer to as the request channel. The channel that sends data back from the server to the client is the response channel.

The examples below show the bare minimum needed to configure the response channels. For a more complete code sample please look in the repository's sample directory.

Client Setup

When we setup the client we start with configuring the subscription to the response channel.

final Subscription responseSubscription = aeron.addSubscription(
    "aeron:udp?control-mode=response|control=server.host.name:10001", 
    RESPONSE_STREAM_ID);

The important part to note is that the response channel (both the publication and subscriptions sides) will set the control-mode=response parameter and instead of specifying an endpoint a control address will be used. It is also worth noting that it is still possible to set an endpoint here if there is a specific endpoint that the channel would to have bound on the client machine, which is not necessary most of the time, but can be useful if a setup requires fixed ports to make it easier to configure firewall rules. Once the subscription is created we can then setup the client's publication.

final Publication requestPublication = aeron.addPublication(
    "aeron:udp?endpoint=server.host.name=10000|response-correlation-id=" + responseSubscription.registrationId(),
    REQUEST_STREAM_ID);

The interesting difference here is that we are setting the new parameter of response-correlation-id to be the registration id of the response subscription that we have just created. This allows the media driver to maintain a correlation between the response and subscription indicating that this publication is the request publication for the previously constructed subscription.

Server Setup

On the server side of the pairing we have some similar configuration, but there are some important differences. The main one is that on the server we will end up having to maintain multiple response publications as we will have multiple clients connecting. In this scenario, we represent an incoming connection as an image, so to write an effective server we would need to maintain an association between each image and its response publication to route responses back to the appropriate client.

Similar to the client we will want to setup the subscription first.

final Subscription requestSubscription = aeron.addSubscription(
    "aeron:udp?endpoint=server.host.name:10000", 
    REQUEST_STREAM_ID,
    this::handleConnect,
    this::handleDisconnect);


private void handleConnect(final Image image)
{
    final Publication publication = aeron.addPublication(
        "aeron:udp?control-mode=response|control=server.host.name:10001|response-correlation-id=" + image.correlationId(), 
        RESPONSE_STREAM_ID);
    // Maintain the association somewhere.
    clientConnections.put(image, publication);
}

private void handleDisconnect(final Image image)
{
    final Publication publication = clientConnections.remove(image);
    CloseHelper.quietClose(publication);
}

The most obvious thing here is that there is no special configuration option for the request subscription; it is just a standard subscription. However the important part is that we want to listen for an image becoming available and unavailable as that will represent client connections coming and going. When the image becomes available we want to establish the connection back to the incoming client. We do this by creating a response publication. This is represented in a similar way to the response subscription on the client by specifying control-mode=response. We also need to create a link between this publication and the image that was received from the client connection, so we also put the response-correlation-id with the image's correlation id as a parameter onto the channel URI. This link allows for the necessary control traffic to be sent back to the client to establish the connection.

When the server starts polling the incoming subscription, it will receive messages from all clients. It will need to use the message to resolve the source image of that message, then use the image to look up the response publication so that the response can be sent back to the client.

Each time a publication is added with a unique response-correlation-id it will create a unique publication within the driver. On the client side, once the subscription is connected to the incoming response publication, internally the media driver will use a session specific subscription so it will only see data that is flowing from the associated response publication on the server.