ClientServer Channel¶
Channel·Transport topic table of contents · Spec table of contents · Previous: 02. Channel Messaging · Next: 04. Network Listener Identity
A ClientServer Channel is a one-directional service boundary where the Client starts a send or request and the Server runs a handler and replies to the request. This document defines role registration, endpoint discovery, weight- based target selection, and the drain and restart contract.
1. ClientServer Channel Overview¶
| Role | Business calls it can start | Handling a received message |
|---|---|---|
Client |
Selects one Ready server (finished initialization and discovery so it can receive application messages) and starts a send or request. | Doesn't receive a message sent first by the Server without a Client request. It receives only a reply matching a request the Client started, as that request's result. |
Server |
Can't start a new business send or request targeting a connected Client. | Runs the handler for a send or request the Client sent. A request handler replies using the received reply token. |
In other words, on a ClientServer Channel, the only thing a Server can send a Client is the reply to a request the Client started first. If the Server needs to send a notification or event first, it must call through a separately registered RouteMesh, not a ClientServer connection.
A ClientServer Channel isn't a RouteMesh option. It doesn't provide the following functionality.
- Node direct (where a caller targets one specific MeshNode — a runtime node that sends or receives messages within a RouteMesh — by giving both the MeshName and the target RID) between MeshNodes
- Spot and Actor messaging
- Logical Multicast (delivering one message to multiple Spots of the same Channel by ChannelName and topic)
- Connecting a different RouteMesh on its behalf or relaying messages
This restriction means ClientServer transport doesn't automatically substitute for the functionality above. An application in the same process can register ClientServer and RouteMesh separately under different ChannelName (the name that identifies the Channel scope a message is sent to) values and start separate calls on the two send paths. A ClientServer handler can also start a new call, at the application's discretion, targeting a registered RouteMesh, a different ClientServer Channel, a Spot, or an Actor. This call is a separate operation the application chose — it isn't ClientServer automatically relaying the message.
The logical address a Channel caller specifies and the meaning of call completion are defined by Channel Messaging.
This document's C# code is a reference showing how the common contract appears in the .NET public API. It doesn't require the same signature in other languages. The precise .NET signature is defined by .NET Topology Public Interface and .NET Channel Messaging Public Interface.
2. Client and Server Role Registration¶
One process can register Client, Server, or both roles on the same
ClientServer ChannelName. The registration key is (ChannelName, Role), and
Client and Server are each registered at most once per role.
A ClientServer send or request can start only when the local process
registered the Client role for that ChannelName. Even if the ChannelName and
a Server role exist, absence of the Client role ends the call with a
NotConfigured framework error and never directly invokes a local handler.
NotFound is used when the ChannelName itself, or the target to select, does
not exist.
public interface IZLinkFrameworkOptions
{
// configures a Client and Server role for one ChannelName.
IZLinkClientServerChannelRoleBuilder AddClientServerChannel(
string channelName);
}
public interface IZLinkClientServerChannelRoleBuilder
{
// chooses the role that starts business sends and requests.
IZLinkClientServerChannelClientBuilder Client();
// chooses the role that handles Client messages and replies to requests.
IZLinkClientServerChannelServerBuilder Server();
}
The following code shows registering both roles for the same ChannelName in
the same process. Each role's configuration starts separately on the same
builder, and the two registrations merge into one ClientServer topology.
var billing = options.AddClientServerChannel("billing");
var client = billing.Client(); // registers the role that starts billing calls, once.
var server = billing
.Server() // also registers the Server role once in the same process.
.Listen()
.SetAdvertiseHost("billing-1")
.SetWeight(100)
.AddRequestHandler<ChargeHandler, Charge, ChargeResult>();
The Client's endpoint and the Server's listener/handler configuration are explained in §3 and §4.
A reply to a request the Server received isn't the Server starting a new business call. It's a reverse-direction transport message completing the same request the Client started.
If a message arrives at the Server but doesn't match the identity of a currently pending Client request, the framework can't determine which request's reply this is. So it isn't run as an application handler or used as a different request's reply — it's recorded as a protocol error.
A Server for the same ChannelName can be registered in multiple processes.
Registering Client and Server once each for the same ClientServer
ChannelName in the same process is normal and merges into one topology. The
following duplications are startup configuration errors.
- Registering the
Clientrole twice for the same ClientServerChannelName - Registering the
Serverrole twice for the same ClientServerChannelName - Registering the same
ChannelNameon both RouteMesh and ClientServer at once
Several different ClientServer ChannelNames can be registered in the same
process. The already-defined ChannelName conflict rule between RouteMesh and
fanout doesn't change.
3. How to Find a Server Endpoint and Make It Ready¶
The Client obtains a Server endpoint from one or more of the following sources.
| Discovery method | Endpoint source | Location Store (a store that keeps each Spot's and descriptor's current state where multiple nodes can check it together) |
|---|---|---|
| Manual | The application registers it via Connect(endpoint). |
Not needed if only manual endpoints are used. |
| Automatic | Queries the ClientServer Server descriptor of the same ChannelName. | Location Store is required. |
If the manual and automatic sources point to the same Server RID and lifecycle generation, the framework merges them into one connection candidate.
From endpoint discovery through target selection, three parties participate in sequence: the Client, the discovery source (a manual setting or an automatic descriptor), and the Server.
%%{init: {'sequence': {'actorFontSize': '18px', 'messageFontSize': '18px', 'noteFontSize': '18px', 'boxMargin': 8, 'width': 140}, 'themeVariables': {'fontSize': '18px'}}}%%
sequenceDiagram
participant Client as Client role
participant Discovery as Discovery source<br/>(manual setting or automatic descriptor)
participant Server as Server role
Client->>Discovery: query for endpoint (manual Connect or automatic query)
Discovery-->>Client: Server RID, lifecycle generation, and endpoint
Client->>Server: starts the transport connection (only the Client starts a connection)
Server-->>Client: confirms identity and generation, then becomes ready
Note over Client: only a ready, weight>0, non-draining Server is a candidate
Client->>Server: send or request to the Server picked by weight ratio
In this flow, discovery only produces a candidate endpoint. It becomes a ready target (a target that finished the listener/transport connection, identity check, and required handler registration, and so can receive new messages) only once identity and generation are re-verified on the actual transport connection (§3.2). The weight rule that picks the one Server that actually receives a call among ready targets is defined by §4.
3.1 Only the Client Starts a Connection to the Server¶
In both manual and automatic discovery, the Client starts the connection to the Server endpoint. The Server doesn't look up a Client endpoint or start an outbound connection toward the Client.
In automatic discovery, one connection intent, distinguished by Server RID and lifecycle generation, is created per valid Server descriptor for the same ChannelName. If multiple Servers are discovered, each Server's ready connection is kept independently, and one is selected by §4's weight rule when starting a business call.
So a ClientServer Channel is an asymmetric topology with a fixed connection direction. Unlike RouteMesh, it doesn't decide which side starts the connection by comparing the two MeshNodes' RIDs.
public interface IZLinkClientServerChannelClientBuilder
{
// adds a manual endpoint. If omitted, automatic discovery can be used.
IZLinkClientServerChannelClientBuilder Connect(string endpoint);
}
public interface IZLinkClientServerChannelServerBuilder
{
// port 0 has automatic discovery publish the actual bound port.
IZLinkClientServerChannelServerBuilder Listen(int port = 0);
IZLinkClientServerChannelServerBuilder SetBindHost(string bindHost);
IZLinkClientServerChannelServerBuilder SetAdvertiseHost(
string advertiseHost);
// 0 excludes it from new Client call selection.
IZLinkClientServerChannelServerBuilder SetWeight(int weight);
IZLinkClientServerChannelServerBuilder
AddSendHandler<THandler, TMessage>(
string? packetName = null)
where THandler : class, IZLinkSendHandler<TMessage>;
IZLinkClientServerChannelServerBuilder
AddRequestHandler<THandler, TRequest, TReply>(
string? packetName = null)
where THandler : class, IZLinkRequestHandler<TRequest, TReply>;
}
The following example configures a manual Client and an automatic-discovery Server in different processes.
clientOptions
.AddClientServerChannel("billing")
.Client()
.Connect("tcp://billing-1:7200"); // registers a manual server endpoint directly.
serverOptions
.AddClientServerChannel("billing")
.Server()
.Listen() // binds on port 0, then publishes the actual port.
.SetAdvertiseHost("billing-2")
.SetWeight(100) // becomes a selection candidate for new billing calls.
.AddRequestHandler<ChargeHandler, Charge, ChargeResult>(); // registers the request handler
3.2 Finding the Server Descriptor Alone Does Not Make It Ready¶
An automatic-discovery Server publishes a dedicated ClientServer ClientServer Server descriptor and owner lease holding the following information. An owner lease is information proving, by renewing on a fixed schedule, that this Server still has the right to keep using the ClientServer Server descriptor.
- ChannelName
- Server identity and lifecycle generation
- Endpoint
- Weight and drain state. Drain state represents a state where new-call selection is stopped while preparing for a safe shutdown or exclusion from targets, finishing only already-received calls.
- Descriptor revision (an increasing number marking the version of mutable descriptor information, such as weight or drain state, within the same lifecycle)
The Client only uses a valid ClientServer Server descriptor for the same ChannelName. After finding the endpoint from this registration information, it must re-verify that Server identity and lifecycle generation match on the actual transport connection before using it as a ready target.
The ClientServer Server descriptor doesn't include the following information.
- MeshName or RouteMesh membership
- Spot or Actor location
- Information used to decide whether to accept a MeshNode peer connection
A MeshNode descriptor isn't used for ClientServer discovery, and a ClientServer Server descriptor isn't used for a RouteMesh peer connection.
3.3 When the Location Store Is Needed¶
If only manual endpoints are used, a Location Store isn't needed. If automatic discovery is enabled but there's no Location Store, startup fails before the Server listener binds.
A manual connection also verifies the following information on the actual transport connection.
- ChannelName
- Server RID and lifecycle generation
- Weight and drain state
- Security identity
This information only controls the ClientServer connection — it isn't converted into a MeshNode descriptor or RouteMesh peer information.
4. Weight and Target Selection¶
Server weight is a relative share that determines how often new sends and requests
are assigned among several selectable Servers. The range is 0..10000, with a
default of 100. A value outside the range is a configuration error during
startup configuration or a runtime change.
This weight doesn't mean the number of
concurrent requests a Server can handle or its physical performance. For
example, if Server A's weight is 100 and Server B's is 50, with other
conditions equal, repeated target selection gives A twice B's assignment
share. It doesn't mean A is necessarily chosen for every individual
request. Servers with the same weight are chosen in rotation.
- Weight is only compared among Servers that are
Readyand not draining. A Server whose connection isn't ready, or that's draining, isn't selected even with a high weight. - The framework applies this condition first, then computes the sum of remaining positive weights using at least a 64-bit integer. This allows it to select a Server by the relative ratio without overflowing the sum.
Drain is the process of first blocking selection for new sends and requests,
to safely shut down a Server or exclude it from service targets, then
finishing work the Server already received within a set time. Weight 0 and
drain are both excluded from new target selection, but their meaning differs.
Weight 0 is a setting that zeroes only the selection share while keeping the
Server running; drain is a termination procedure that cleans up existing work
and then closes the descriptor and listener.
| Server state | Whether it's a target for new sends and requests |
|---|---|
| Ready and weight greater than 0. | Included as a selection candidate, reflecting its relative weight ratio against other selectable Servers. Servers with the same weight are chosen in rotation. |
Weight is 0. |
Excluded from new target selection. The Server can keep running, and raising weight again returns it to the candidate set. It doesn't change the Server role or an existing connection to a Client role. |
| Draining for a safe shutdown. | Excluded from new target selection. The Server stops accepting new business messages, processes only already-accepted handlers and request replies up to the deadline, then cleans up the descriptor and listener. |
4.1 A Server in the Same Process Is Also a Selection Candidate¶
If Client and Server for the same ChannelName are registered in the same
process, the local Server is included in the same candidate set as remote
Servers.
- A local Server can only be selected once its listener bind and service
admission finish (making it
Ready), its weight is greater than 0, and it isn't draining. The framework doesn't select the local Server preferentially, or exclude remote Servers from candidates, just because it's local. This is so that, with multiple candidates, the same weight ratio and rotation rule apply regardless of local/remote.
%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
C["Client role in the same process"]
S1["Ready Server in the same process"]
S2["Ready Server in a different process"]
Pick["picks one, reflecting relative assignment share and safe-shutdown status"]
C -->|starts a call| Pick
Pick -->|selectable| S1
Pick -->|selectable| S2
- Even if the local Server is selected, its handler isn't called directly.
The actual transport message is delivered from the Client
DEALERto the ServerROUTER. This is so that codec, HWM, timeout, cancellation, the identifying information linking request and reply, and the terminal-completion rule aren't bypassed.
Target selection and submit are one operation. The framework doesn't return the selected Server identity to the application as an intermediate result.
- Even if a connection closure, timeout, or cancellation occurs after submit, the same request isn't automatically resent to a different Server. This is because the first Server may have already run the request, with only the reply not delivered.
4.2 When Target-Selection Information Changes at Runtime¶
If a Server changes its weight or starts draining while running, the target-selection information the Client uses changes. The Server records this change in the ClientServer Server descriptor and increments the descriptor revision. The Client only applies a larger revision within the same lifecycle generation. So a late-arriving previous descriptor doesn't make a draining Server a selection candidate again, or revert to a pre-change weight.
Changing a local Server's weight at runtime specifies the target by ChannelName. Server RID and endpoint are values distinguishing remote targets in monitoring — the application doesn't specify them as the target for a local weight change.
5. Send, Request, and Reply¶
Send submits a one-way message to one ready Server and doesn't create a reply token.
Reply correlation is an identifying value created when sending a request to link the request with a reply that arrives later. The Server includes the same value in the reply, and the Client uses it to confirm which request the reply is the result of.
A request selects one ready Server and then builds reply correlation. It completes with whichever of reply, error, timeout, cancellation, or shutdown is confirmed first.
If the Client DEALER doesn't receive earlier one-way DATA from the Server
ROUTER in Core, or keeps receive flow PAUSED, a reply later on the same
connection is also delayed. The ClientServer request timeout can therefore be
decided before the reply, and the late reply after timeout is discarded under
the existing first-terminal rule. A disconnect, allocation failure, or timeout
can terminate first, so the reply is not guaranteed to arrive eventually.
%%{init: {'sequence': {'actorFontSize': '18px', 'messageFontSize': '18px', 'noteFontSize': '18px', 'boxMargin': 8, 'width': 140}, 'themeVariables': {'fontSize': '18px'}}}%%
sequenceDiagram
participant Caller
participant Client as Client runtime
participant Selector as Ready server selector
participant Server as Selected server
participant Handler as Request handler
Caller->>Client: submit ChannelName and request
Client->>Selector: request a ready Server selection
Selector-->>Client: return one Server reflecting weight and drain
Client->>Server: send the request including its identifying information
Server->>Handler: pass the request and reply token
Handler-->>Server: return the reply payload
Server-->>Client: send the reply with the same identifying information
Client-->>Caller: return the request result once
5.1 Reply Token¶
The reply token a Server request handler receives can only be used for the current request. Once the final reply is made once, it can't be reused.
If a reply route can be restored after any of the following failures, the request completes with a structured error reply.
- No handler found
- Payload couldn't be interpreted
- Handler exception
If the same failure occurs on a one-way send, no reply is built. The message isn't delivered to a handler — it's recorded in runtime observability information.
5.2 When a Handler Calls Another Target¶
A ClientServer handler can send a request to a different RouteMesh, ClientServer Channel, Spot, or Actor. This downstream request uses separate request-reply correlation information from the original ClientServer request.
The original request only completes once, via the reply the ClientServer handler returned. The downstream reply's correlation information doesn't replace the original ClientServer request's value.
6. Drain¶
Server drain is a procedure that blocks new requests and finishes already- received ones, processed in the following order.
- Closes local ready status and stops accepting new business messages.
- Publishes draining state and a larger revision to the ClientServer Server descriptor.
- Continues processing already-accepted handlers and request replies up to the deadline.
- Once every final result is confirmed, releases the ClientServer Server descriptor and owner lease and closes the listener.
A manual Client is notified of the same drain state via a connection control message.
- The Client can submit a request right before confirming the drain state. If the Server rejects this request, it doesn't wait indefinitely — it completes with a finite rejected result. This is so that, even in the race between the submit point and the confirmation point, the caller doesn't wait indefinitely for a result.
7. Server Restart¶
When the same Server identity restarts, it issues a lifecycle generation different from the previous value.
- This value's order isn't judged by numeric magnitude. Even at the same endpoint, a previous generation's connection and ClientServer Server descriptor aren't used as a new target. This is because the generation is a fence that only compares whether values are equal.
The Client replaces it in the following order.
- Finds the new generation's ClientServer Server descriptor.
- Re-verifies identity and generation on the transport connection.
- Makes the new generation a ready target.
- Removes the previous generation's connection.
The Client compares the reply correlation included in a reply against the value of the currently pending request.
- Only if the values match is it treated as that request's result. So even a reply sent from a previous generation can become the result of the original request, if that request is still pending — because it's judged only by reply correlation, not by which generation it came from.
If the original request's reply correlation has disappeared due to timeout, cancellation, or Client restart, a late-arriving reply is discarded. It isn't used as the result of a different request started afterward.
8. Location Store Failure¶
If the Location Store becomes unavailable, the Client keeps the last successfully obtained automatic connection candidates. During the failure, it stops computing additions and removals of new ClientServer Server descriptors.
- An already-ready connection and an already-accepted request aren't canceled merely due to a Store failure. This is because discovery updates stopping and the validity of an already-verified transport connection are separate concerns.
If a Server fails to renew its owner lease and the allowed time elapses, it
stops accepting new business messages. This final time point is called the
fencing deadline. Once the Store
recovers, the target list is re-aligned based on the latest descriptor
revision and lifecycle generation.
9. Verification Requirements¶
Verify the following using only the role registration builder, the send/request results and replies observed by Client and Server, ClientServer Server descriptor queries and owner lease state, and the connection snapshot provided by monitoring.
Role and Registration
- The Server has no public API to start a new business call targeting the Client.
- A Server message that doesn't match a Client request isn't delivered to an application handler.
ClientandServerroles can each be registered once for the same ClientServerChannelNamein the same process.- Duplicate registration of the same role for the same ClientServer
ChannelName, and aChannelNameconflict with RouteMesh, are startup configuration errors.
Endpoint Discovery and Target Selection
- In both manual and automatic discovery, only the Client starts the connection to the Server.
- If multiple Servers are discovered, each ready connection is kept independently.
- RouteMesh's RID-comparison rule isn't used to decide the connection-start direction on a ClientServer Channel.
- Automatic discovery uses a ClientServer Server descriptor, and it isn't used interchangeably with a MeshNode descriptor.
- Server weight allows
0, default100, and cap10000;-1and10001are rejected at startup configuration and runtime change. - Multiple Servers for the same ChannelName are selected based on weight, including weight 0, and drain state.
- A local Server is selected under the same readiness, weight, and drain rules as a remote Server, and the selected local Server's handler also runs through actual transport.
Restart and Failure
- When the same identity restarts, only the new lifecycle generation becomes a ready target.
- A late reply from a previous generation doesn't complete a new request.
- Even if a handler calls a different send path, the original request only completes once.
- A Store failure doesn't immediately drop an already-ready connection.
- After Store recovery, the target list is re-aligned with the latest revision and generation.
Request Completion and the Single FIFO
- If a Server sends a request reply after one-way DATA and the Client stops
receiving DATA or remains
PAUSED, the configured timeout can end the request. A late reply that arrives after DATA drains doesn't create a second terminal. - After the reply reaches the Core physical head and Core identifies it as a
completion, it acquires no Application Job Queue permit, but it doesn't
bypass earlier DATA, Core HWM, or
PAUSED.
Channel·Transport topic table of contents · Spec table of contents · Previous: 02. Channel Messaging · Next: 04. Network Listener Identity