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.
Target selection and waiting results follow
the Framework API channel selection result.
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. The selection order is defined by Channel Messaging §3.
ChannelName ready/drain eligibility and weighted select-one are defined by Channel Messaging §3.
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 have different meanings.
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.
Weight 0 does not turn the Server role or an existing connection into a Client role.
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 joins the same candidate set as remote Servers after
listener bind and service admission.
Eligibility and ordering are defined by Channel Messaging §3.
%%{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.
5.3 Application Record Contract¶
The internal wire contract for a ClientServer application record carries
application metadata in the JSON header's
metadata field as a string-to-string object. A missing field is interpreted as empty metadata.
Character, size-limit, error, and reply rules follow channel messaging §9. The total size in that
section is the UTF-8 JSON encoding length of the entire metadata object, including keys, values, and
structural overhead. The header's metadata field name and other fields are excluded from this size.
This length is measured on the minimal-escape form — in key and value strings only ", \, and
U+0001–U+001F are escaped (control characters use \b \f \n \r \t where one exists, otherwise
lowercase \u00xx), every other character is written as raw UTF-8, and no whitespace appears between
structural characters. A sender writes this form, and a receiver judges by the length of the received
value re-measured in this form. The business payload frame carries no metadata.
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. - ChannelName Server selection verifies Channel Messaging §3.
- A selected local Server 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