26 KiB
Inter-Process Network Protocols and Channels
Intended Scope and Extension Strategy
The vl::inter_process namespace is designed for inter-process communication. Some abstractions are general enough to carry messages inside one process or across a custom transport, but the lifecycle, error handling, connection model and naming are intended for communication between processes.
The built-in INetworkProtocol* transports should be treated as reference implementations, validation targets and demo-friendly options. The current concrete vl::inter_process::named_pipe::NamedPipeServer / vl::inter_process::named_pipe::NamedPipeClient and vl::inter_process::windows_http::HttpServer / vl::inter_process::windows_http::HttpClient implementations are Windows-only, and they are not meant to be the only production transport choice for every application.
This distinction is especially important for HTTP. vl::inter_process::windows_http::HttpServer and vl::inter_process::windows_http::HttpClient are raw INetworkProtocol* reference/demo transports, while vl::inter_process::windows_http::HttpServerApi and vl::inter_process::windows_http::HttpClientApi are lower-level Windows HTTP helper utilities. When a Windows feature needs HTTP request/response behavior directly, the helper utilities can still be used without adopting the inter-process raw transport as the feature contract.
When the built-in raw protocol implementation does not fit the platform, security model, deployment shape, performance target or reconnection behavior of a feature, implement a custom INetworkProtocolServer, INetworkProtocolClient and INetworkProtocolConnection. The default channel bridge can still be reused as long as the custom raw transport follows the INetworkProtocol* contract.
Feature code should usually depend on IChannelServer<TPackage>, IChannelClient<TPackage> and IChannel<TPackage> instead of a concrete raw transport. Keeping the feature boundary at IChannel* decouples package delivery from the underlying INetworkProtocol*, so the transport can be replaced later without rewriting the feature logic.
The inter-process communication APIs in vl::inter_process are layered:
- The raw protocol layer, represented by
INetworkProtocolServer,INetworkProtocolClient,INetworkProtocolConnectionandINetworkProtocolCallback, exchanges asynchronousWStringmessages between a server and clients. - The channel layer, represented by
IChannelServer<TPackage>,IChannelClient<TPackage>,IChannel<TPackage>andIChannelReader<TPackage>, builds typed named channels on top of a connected client id model. - The default bridge, represented by
NetworkProtocolChannelServer<TPackage, TSerialization, TServerBase>,NetworkProtocolChannelClient<TPackage, TSerialization>andNetworkProtocolLocalChannelClient<TPackage, TSerialization>, serializes channel batches into rawWStringmessages over anINetworkProtocol*transport.
The transport-agnostic contracts and channel templates stay directly in vl::inter_process. Windows concrete implementations are grouped by feature: named-pipe types are in vl::inter_process::named_pipe, and HTTP transport, helper, request, response and error types are in vl::inter_process::windows_http.
The interfaces are transport-agnostic. Use the abstract interfaces in portable feature code, and bind them to concrete transports at application composition boundaries.
Choosing the Layer
- Use
INetworkProtocolServer,INetworkProtocolClient,INetworkProtocolConnectionandINetworkProtocolCallbackwhen the application only needs raw asynchronous text messages. - Use
IChannelServer<TPackage>,IChannelClient<TPackage>,IChannel<TPackage>andIChannelReader<TPackage>when the application needs named logical channels, typed packages, client ids, direct delivery, broadcast delivery or batched writes. - Use
NetworkProtocolChannelServer<TPackage, TSerialization, TServerBase>andNetworkProtocolChannelClient<TPackage, TSerialization>when the channel layer should run over an existingINetworkProtocolServer/INetworkProtocolClienttransport. - Use
NetworkProtocolLocalChannelClient<TPackage, TSerialization>when server-side logic needs to participate in a channel as a normal speaker with a real positive client id. - Use
ChannelSerializer<TSerialization>when an existingIChannel<DestType>should be adapted to another package type by serializing and deserializing packages. - Choose or implement the concrete
INetworkProtocol*transport at the application composition boundary; keep feature code onIChannel*when transport replacement should remain possible.
Raw Network Protocol Contract
INetworkProtocolConnection represents one text-message connection between two peers.
InstallCallbackinstalls oneINetworkProtocolCallback; passingnullptruninstalls it.BeginReadingLoopUnsafestarts asynchronous reading. Some implementations may already be receiving data before this call, so user callbacks must tolerate early delivery after callback installation.SendStringsends oneWStringmessage to the other side.Stopcloses the connection. Implementations should treatStopas the shutdown boundary and drain asynchronous callbacks before returning.
INetworkProtocolCallback receives connection events and must be thread-safe, because callbacks may run on any thread.
OnInstalledgives the callback itsINetworkProtocolConnection.OnConnectedreports that the connection became available.OnReadStringreceives a normal text message.OnReadErrorreceives an error reported by the remote side.OnLocalErrorreceives a local transport failure; thefatalargument indicates whether the connection should be considered disconnected.OnDisconnectedreports that the connection is lost.
INetworkProtocolClient owns one connection to a server.
GetConnectionalways returns an object, but using it beforeWaitForServerfinishes is undefined by contract.WaitForServerblocks until the connection is established or the implementation gives up.GetStatusreportsClientStatus::Ready,ClientStatus::WaitingForServer,ClientStatus::ConnectedorClientStatus::Disconnected.
INetworkProtocolServer owns the listening side.
Startbegins accepting clients.OnClientConnectedis called for each accepted transport connection and returnsWaitForClientResult::AcceptorWaitForClientResult::Reject.Stopends listening and disconnects owned connections.IsStoppedreports either explicit shutdown or an underlying transport failure.- No client-connected callback should happen before
Startor afterStop.
The normal raw-protocol usage pattern is:
- Derive a server from a concrete
INetworkProtocolServerimplementation and overrideOnClientConnected. - In
OnClientConnected, callInstallCallbackon the newINetworkProtocolConnection, then callBeginReadingLoopUnsafe, and returnWaitForClientResult::Accept. - Create each client, call
GetConnection()->InstallCallback, callWaitForServer, then callGetConnection()->BeginReadingLoopUnsafe. - Exchange messages through
INetworkProtocolConnection::SendString. - Call
Stopand uninstall callbacks during shutdown.
Channel API Contract
There is no separate IChannelConnection type. The channel layer models a connected participant with IChannelClient<TPackage>, its assigned positive client id, and its named IChannel<TPackage> objects.
IChannelReader<TPackage> receives typed channel packages.
OnReadreceives a positivesenderClientIdand oneTPackage.
IChannel<TPackage> represents one named logical channel.
GetChannelNamereturns the channel name.GetReaderreturns the installed reader.Initializeinstalls one reader and replays unread messages that arrived before the reader was installed.Initializecan only install a reader once; no reader uninstallation is supported.SendToClientqueues a direct message to one receiver client id.BroadcastFromClientqueues a broadcast to all other participants on the same channel.- The
BroadcastFromClientoverload withblockedReceiversexcludes specific receiver ids from a broadcast. BatchWriteflushes all queued messages and reports disconnection through itsdisconnectedoutput argument.
Channel names:
- User channel names must be non-empty.
- User channel names must not contain
!. - Names beginning with
!are reserved for system channels. ErrorChannelis used by the default implementation for fatal channel errors.SystemChannelis declared as reserved, but the default implementation currently does not use it.
IChannelClient<TPackage> represents one channel participant.
OnGetChannelNamestells the implementation which channel names this client supports.GetChannelsreturns implementation-created channel objects. The channel map is expected to be empty before connection and populated by the implementation.GetClientIdreturns-1before connection and the server-assigned positive id after connection.WaitForServerblocks until connected for network clients. Local clients return immediately.GetStatusreportsReady,WaitingForServer,ConnectedorDisconnected.OnConnectedandOnDisconnectedreport participant lifecycle.OnReadErrorreceives fatal errors broadcast by the channel server.OnLocalErrorreceives local transport errors.BroadcastErrorraises a fatal channel error.
IChannelServer<TPackage> manages connected channel participants.
Startbegins accepting channel clients.OnClientConnectedreceives the assigned client id, the client's channel names and alocalClientpointer.- The
localClientargument is non-null only when the connection is created byConnectLocalClient. ConnectLocalClientconnects an in-process server-side participant and returns its assigned positive client id, or-1if it cannot connect.IsLocalClientreports whether a client id belongs to a local client.DisconnectClientdisconnects either a network client or a local client.GetClientIdsreturns all known client ids.GetClientChannelsreturns the client-id to channel-name membership map.BroadcastErrorbroadcasts a fatal channel error and stops the server.Stopdisconnects clients and stops accepting.IsStoppedreports explicit stop or underlying transport stop.
IChannelServer<TPackage> is a delivery and bookkeeping API. It is not itself a channel speaker. When server-side behavior needs to send channel messages, create a NetworkProtocolLocalChannelClient<TPackage, TSerialization>, connect it with ConnectLocalClient, and send messages through that local client's channels.
Default Channel Implementation Over Raw Protocols
The default channel bridge serializes batches of typed packages to raw WString messages.
TSerialization for NetworkProtocolChannel<TPackage, TSerialization> must satisfy:
TSerialization::SourceTypeiscollections::List<TPackage>.TSerialization::DestTypeisWString.TSerialization::ContextTypestores optional serializer context.TSerialization::Serializeconverts a package list toWString.TSerialization::Deserializeconverts aWStringback to a package list.
NetworkPackage is the raw wire envelope.
- Its string format is
clientId,extraClientId1,...;channelName;messageBody. clientIdmay be null.extraClientIdsmay be null.- A null
clientIdwith extra ids is preserved as a leading comma, for example,1,2;Chat;Message. - Missing or empty
extraClientIdsnormalize to null after parsing.
Connection handshake:
- A channel client waits for the raw
INetworkProtocolClient. - The client sends a
NetworkPackagewith nullclientId, emptychannelName, andmessageBodyequal to all supported channel names joined by!. - The server validates the first package as a connection request.
- The server allocates a positive client id.
- The server calls
OnClientConnected(clientId, channelNames, nullptr)for network clients. - The server records channel membership.
- The server sends a response package with the assigned
clientId, emptychannelName, and emptymessageBody. - The client records the assigned id, changes status to connected, replays any queued channel packages, and calls
OnConnected.
Channel message semantics:
- Client-to-server direct messages set
NetworkPackage::clientIdto the receiver client id. - Client-to-server broadcasts use a null
clientId. - For broadcasts,
extraClientIdsmeans blocked receivers. - For direct messages,
extraClientIdsare ignored by the delivery semantics. - Server-to-client channel messages always set
NetworkPackage::clientIdto the positive sender client id. - A received channel message without a positive sender id is invalid.
NetworkProtocolChannel<TPackage, TSerialization> handles per-channel queues.
ReadBatchstores unread packages untilInitializeinstalls a reader.Initializereplays unread packages to the reader.SendToClientandBroadcastFromClientvalidate ids and queue packages.BatchWritemoves queued packages out, groups them by receiver id and blocked-receiver list, serializes each group, and callsWriteBatch.- If
WriteBatchreports disconnection,BatchWritesetsdisconnectedand stops flushing.
NetworkProtocolChannelClientBase<TPackage, TSerialization> owns the shared client-side channel state.
- It owns serializer context, client id, client status, connection notification state and generated channels.
GetChannelslazily creates channel objects fromOnGetChannelNames.- The generated channel validates that a client is connected before sending.
- Direct sends require a positive receiver id.
- Direct sends cannot also specify blocked receivers.
- Blocked receiver ids must be positive and must not be the sender's own id.
NetworkProtocolChannelClient<TPackage, TSerialization> adapts a real INetworkProtocolClient.
- The constructor installs an
INetworkProtocolCallbackon the raw connection. WaitForServerwaits for the raw transport, sends the channel-name handshake, starts raw reading, waits for the assigned client id, and notifiesOnConnected.- Incoming
ErrorChannelpackages callOnReadErrorand disconnect. - Incoming empty-channel packages are treated as connection responses.
- Incoming channel packages that arrive before the assigned id are queued and replayed after connection.
SendBatchserializes one package list into oneNetworkPackageand callsSendString.BroadcastErrorsends anErrorChannelpackage and disconnects locally.- The destructor stops the raw connection if still connected and uninstalls the callback.
NetworkProtocolLocalChannelClient<TPackage, TSerialization> adapts no transport.
- It connects only through
NetworkProtocolChannelServer::ConnectLocalClient. WaitForServeris a no-op.SendBatchcalls the server's local-client send hook.BroadcastErrorroutes through the server when connected.- If already disconnected,
BroadcastErrorreports a fatal local error and disconnects locally.
NetworkProtocolChannelServer<TPackage, TSerialization, TServerBase> combines the channel server with a raw protocol server.
TServerBaseis a concreteINetworkProtocolServerimplementation, such asvl::inter_process::named_pipe::NamedPipeServerorvl::inter_process::windows_http::HttpServer.- The class inherits
TServerBase,IChannelServer<TPackage>and the private local-client server interface. Startrecords the channel server as started, then callsTServerBase::Start.- Raw
OnClientConnected(INetworkProtocolConnection*)rejects connections before start or after stop, creates a pending connection context, installs a raw callback, starts raw reading, and waits for the channel handshake. - Before a connection has a client id,
OnReadStringrequires the channel-name handshake. - After a connection has a client id,
OnReadStringvalidates sender membership, receiver membership or blocked-receiver membership, deserializes the batch and forwards it throughSendBatch. SendBatchhandles direct delivery and broadcast delivery for both network clients and local clients.- Network recipients receive serialized
NetworkPackagestrings. - Local recipients receive packages through their local channel objects.
ConnectLocalClientrequires a started, non-stopped server and aNetworkProtocolLocalChannelClient<TPackage, TSerialization>.ConnectLocalClientvalidates local channels, allocates a positive id, callsOnClientConnected(clientId, channels, localClient), records membership and notifies the local client.DisconnectClientremoves either a network client or a local client, stops or notifies it, and callsOnClientDisconnected.Stopclears network, pending and local client state, notifies local clients, delegates transport shutdown toTServerBase::Stop, and reports network disconnections.BroadcastErrorsendsErrorChannelpackages to network clients, callsOnReadErroron local clients, gives transport clients a short chance to consume the fatal package, and stops the server.
Channel Usage Pattern
To build a typed channel application over a raw transport:
- Define a serializer whose
SourceTypeiscollections::List<TPackage>and whoseDestTypeisWString. - Define a channel server by using
NetworkProtocolChannelServer<TPackage, TSerialization, TServerBase>, whereTServerBaseis the chosen raw transport server. - Override
OnClientConnected(vint, const ChannelNameList&, IChannelClient<TPackage>*)to accept or reject clients after checking their channel names. - Define channel clients by deriving from
NetworkProtocolChannelClient<TPackage, TSerialization>for network clients. - For each client, return supported channel names from
OnGetChannelNames. - Obtain channels through
GetChannels, callInitializewith anIChannelReader<TPackage>, then callWaitForServer. - Queue channel messages with
SendToClientorBroadcastFromClient. - Call
BatchWriteto flush queued channel messages. - If the server needs to speak on a channel, derive that participant from
NetworkProtocolLocalChannelClient<TPackage, TSerialization>and connect it withConnectLocalClient.
Error and Shutdown Rules
- Keep remote channel errors and local transport errors separate.
IChannelClient<TPackage>::OnReadErroris for fatal errors broadcast through the channel server.IChannelClient<TPackage>::OnLocalErroris for local raw-transport failures, such as connection, request or response failures.INetworkProtocolCallback::OnReadErroris for remote errors at the raw transport contract level.INetworkProtocolCallback::OnLocalErroris for local transport failures at the raw transport contract level.- A fatal local error should lead to disconnection.
Stopshould be treated as a hard shutdown boundary: after it returns, transport callbacks should no longer touch the stopped object.BatchWritemay report disconnection; callers should stop assuming queued messages were delivered oncedisconnectedbecomes true.- Server-originated normal channel messages should use a connected local channel client so all normal messages have a real positive sender id.
Current Windows-Only Raw Transports
The built-in vl::inter_process::named_pipe::NamedPipeServer / vl::inter_process::named_pipe::NamedPipeClient and vl::inter_process::windows_http::HttpServer / vl::inter_process::windows_http::HttpClient classes are currently Windows-only implementations of the raw protocol interfaces. They should not be treated as cross-platform transport classes.
vl::inter_process::named_pipe::NamedPipeServer and vl::inter_process::named_pipe::NamedPipeClient
vl::inter_process::named_pipe::NamedPipeConnection implements INetworkProtocolConnection. vl::inter_process::named_pipe::NamedPipeClient derives from vl::inter_process::named_pipe::NamedPipeConnection and implements INetworkProtocolClient. vl::inter_process::named_pipe::NamedPipeServer implements INetworkProtocolServer.
NamedPipeServer::Startbegins overlapped named-pipe accepting.NamedPipeServerkeeps both acceptedNamedPipeConnectionobjects and pendingConnectNamedPipeoperations.NamedPipeServer::OnClientConnectedis the user-overridable accept hook.NamedPipeClient::WaitForServercompletes the client-side setup, switches the pipe to message-read mode, updates status toClientStatus::Connected, and calls callbackOnConnected.NamedPipeConnection::SendStringframes oneWStringwith byte count and string length data.NamedPipeConnectionchunks writes byMaxMessageSize, because the named-pipe implementation does not support one message larger than 64K.NamedPipeConnection::BeginReadingLoopUnsafeaccumulates overlapped read chunks until one complete framed message is available, then callsOnReadString.- Broken-pipe cases become
OnDisconnected; invalid handle or aborted I/O cases become fatalOnLocalErrorfollowed by disconnect. NamedPipeConnection::Stopcancels pending overlapped pipe I/O, unregisters pending waits, waits for pending callbacks, and closes handles.NamedPipeServer::Stopdrains both pending accepts and accepted connections.
vl::inter_process::windows_http::HttpServer and vl::inter_process::windows_http::HttpClient
vl::inter_process::windows_http::HttpClient implements both INetworkProtocolConnection and INetworkProtocolClient. vl::inter_process::windows_http::HttpServer derives from vl::inter_process::windows_http::HttpServerApi and implements INetworkProtocolServer. This is also a Windows-only reference/demo implementation.
The HTTP protocol uses three routes:
GET /VlppInterProcess/Connectcreates a logical connection and returns a pair of per-connection URLs.POST /VlppInterProcess/Request/GUIDis the client-maintained long-poll request for server-to-client messages.POST /VlppInterProcess/Response/GUIDsends client-to-server messages and may also receive one server-to-client message in the response.
HttpClient behavior:
HttpClient::WaitForServersends/Connect, waits for the response, validates therequestUrl;responseUrlbody, stores both URLs, changes status to connected, and callsOnConnected.HttpClient::BeginReadingLoopUnsafestarts the long-poll/Requestloop.- After each successful
/Request,HttpClientstarts the next/Requestbefore delivering a non-empty response body toOnReadString. HttpClient::SendStringsends a/Responserequest with the string body./Connectand/Responsefailures retry up toHttpRequestMaxAttempts; the last failure is fatal./Requestfailures are retried while the client is still running.HttpClient::Stopstops the underlyingHttpClientApi, signals any waitingWaitForServer, and reportsOnDisconnected.
vl::inter_process::windows_http::HttpServer and vl::inter_process::windows_http::HttpServerConnection behavior:
- On
/Connect,HttpServercreates aHttpServerConnection, assigns a GUID, callsOnClientConnected, and returns the per-connection request and response URLs, or rejects with an HTTP error response. HttpServerConnection::BeginReadingLoopUnsafeis a no-op becauseHttpServerApiowns the receive loop.HttpServerConnection::InstallCallbackstores the callback and replays queued inbound strings after releasing its queue lock.HttpServerConnection::SendStringresponds to a pending/Requestimmediately when possible; otherwise it queues outbound messages.- If a server message is produced while handling
/Response,HttpServerConnectionmay return one outbound message in that/Responseresponse and queue the rest for future/Requestcalls. HttpServerConnection::OnNewHttpRequestForPendingRequestcancels an old pending/Request, stores the new one, and replies immediately if a message is already queued.HttpServerConnection::SubmitResponsereads the client body, dispatches or queues it as inbound text, then returns one queued outbound message if available.HttpServer::OnHttpServerStoppingclears connections, cancels pending long-poll requests, disconnects connection objects from the server, and callsOnDisconnected.
Windows HTTP Helper Layer
vl::inter_process::windows_http::HttpClientApi and vl::inter_process::windows_http::HttpServerApi are reusable Windows helper classes used by vl::inter_process::windows_http::HttpClient and vl::inter_process::windows_http::HttpServer. The supporting HttpRequest, HttpResponse, HttpError and HttpServerResponse value types are in the same vl::inter_process::windows_http namespace. Use the raw transport classes only when the reference/demo INetworkProtocol* shape is desired; use the helper APIs directly when a Windows-specific feature needs lower-level HTTP request/response behavior.
HttpClientApi owns one WinHTTP session and connection for one host and port.
HttpClientApi::HttpQuerysends one asynchronous HTTP request.HttpRequestdescribes method, query, body, content type, accept types, credentials, cookies, headers, timeouts andkeepAliveOnStop.HttpRequest::SetBodyUtf8converts aWStringrequest body to UTF-8 bytes.HttpResponsecarries HTTP status code, response body bytes, cookie and content type.HttpResponse::GetBodyUtf8converts a UTF-8 response body back toWString.HttpErrorrepresents Windows or WinHTTP transport failures.- HTTP status codes such as 404 are represented as successful
HttpResponsevalues, notHttpError. HttpClientApi::Stopmarks the helper as stopping, closes active non-keep-alive requests, waits for request handle-closing callbacks, then closes WinHTTP handles.
HttpServerApi owns one HTTP.sys URL prefix.
HttpServerApi::Startbegins the receive loop.HttpServerApi::Stopunregisters the pending receive wait, waits for callbacks, callsOnHttpServerStopping, and closes HTTP.sys handles.OnHttpRequestReceivedis the virtual request-dispatch hook.OnHttpServerStoppingis the virtual shutdown hook.GetUtf8Bodyvalidatesapplication/json; charset=utf8, reads the complete request body and converts it toWString.SendResponsesends a structuredHttpServerResponse.SendResponseUtf8sends a UTF-8 body with JSON content type.- Optional
OPTIONShandling exists for cross-origin HTTP clients.