# Using Inter-Process Channels The **vl::inter_process** namespace is organized in layers. Feature code normally uses the channel interfaces, because they describe typed named message delivery between connected participants. Transport code implements the raw network protocol interfaces, because they describe how one server and many clients exchange asynchronous **WString** messages. Portable async-socket transports and Windows-specific transports are composition choices that connect these two layers. VlppOS provides the **INetworkProtocol** and **IChannel** interface families only for test-purpose local cross-process communication. Applications may use these public interfaces as integration contracts, but product code is strongly recommended to provide its own implementations instead of treating the bundled transports and channel stack as production networking. Despite the names, these contracts are not tied to TCP/IP. Any mechanism that provides the required asynchronous message and lifecycle behavior can fit behind them, including stdio, DLL calls, shared memory, WebAssembly host calls, pipes, sockets, HTTP or a custom network protocol. Keep this boundary in application code: - Use **IChannelServer\**, **IChannelClient\**, **IChannel\** and **IChannelReader\** when the feature sends and receives packages. - Use **INetworkProtocolServer**, **INetworkProtocolClient**, **INetworkProtocolConnection** and **INetworkProtocolCallback** when adding a new pipe, socket, HTTP, shared-memory or other underlying transport. - Use **NetworkProtocolChannelServer\** and **NetworkProtocolChannelClient\** when a channel should run on top of a raw protocol implementation. ## Channel Interfaces for Features A channel client is a participant with a server-assigned positive client id and a set of named channels. A channel is not a socket and it is not owned by the server alone. It is a logical lane shared by every connected participant that declares the same channel name. The channel interfaces are the stable API for feature code: - **IChannelReader\::OnRead** receives the sender client id and one typed package. - **IChannel\** exposes **Initialize**, **SendToClient**, **BroadcastFromClient** and **BatchWrite**. - **IChannelClient\** exposes channel names, generated channel objects, client status, **WaitForServer** and lifecycle callbacks. - **IChannelServer\** accepts or rejects clients, records client ids, supports in-process local clients and broadcasts fatal channel errors. Channel names are user-defined strings. A user channel name must not be empty and must not contain **!**. Names beginning with **!**, including **!Error** and **!System**, are reserved for the implementation. **Initialize** installs one reader and replays messages that arrived before the reader was installed. There is no reader uninstallation. **SendToClient** queues a direct message to one client id. **BroadcastFromClient** queues a message for all other participants on the same channel, optionally excluding selected receiver ids. **BatchWrite** flushes queued messages and reports disconnection through its output argument. **IChannelServer\** manages delivery and bookkeeping. It is not itself a channel speaker. When server-side logic must send normal channel messages, create a **NetworkProtocolLocalChannelClient\**, connect it with **ConnectLocalClient**, and send through that local client's channel objects. This keeps every normal channel message tied to a real positive sender client id. A small channel client follows this shape: ```C++ using namespace vl; using namespace vl::collections; using namespace vl::inter_process; constexpr const wchar_t* ChatChannelName = L"Chat"; struct WStringListSerializer { using SourceType = List; using DestType = WString; using ContextType = std::nullptr_t; static void Serialize(const ContextType&, const SourceType& source, DestType& dest) { dest = WString::Empty; for (auto&& item : source) { dest += itow(item.Length()) + L":" + item; } } static void Deserialize(const ContextType&, const DestType& dest, SourceType& source) { source.Clear(); const wchar_t* reading = dest.Buffer(); const wchar_t* end = reading + dest.Length(); while (reading < end) { auto delimiter = wcschr(reading, L':'); CHECK_ERROR(delimiter && delimiter < end, L"Invalid package."); auto length = wtoi(WString::CopyFrom(reading, (vint)(delimiter - reading))); CHECK_ERROR(length >= 0 && delimiter + 1 + length <= end, L"Invalid package length."); source.Add(WString::CopyFrom(delimiter + 1, length)); reading = delimiter + 1 + length; } } }; using ChatChannelClientBase = NetworkProtocolChannelClient; class ChatClient : public ChatChannelClientBase , public virtual IChannelReader { private: IChannelClient::ChannelMap channelNames; IChannel* chat = nullptr; public: ChatClient(Ptr transport) : ChatChannelClientBase(transport) { channelNames.Add(ChatChannelName, nullptr); auto&& channels = GetChannels(); auto index = channels.Keys().IndexOf(ChatChannelName); CHECK_ERROR(index != -1, L"Missing chat channel."); chat = channels.Values()[index]; chat->Initialize(this); } const IChannelClient::ChannelNameList& OnGetChannelNames() override { return channelNames.Keys(); } void OnConnected(vint clientId) override { // Record the assigned positive client id if the feature needs it. } void OnRead(vint senderClientId, const WString& package) override { // Handle one typed package from senderClientId. } void Send(vint receiverClientId, const WString& package) { bool disconnected = false; chat->SendToClient(receiverClientId, package); chat->BatchWrite(disconnected); if (disconnected) { // Stop using this client. } } }; ``` ## Network Protocol Interfaces for Transports The raw protocol layer is the extension point for a new underlying implementation. It intentionally knows nothing about channel names or package types. One **INetworkProtocolConnection** exchanges asynchronous **WString** messages with the other side. This message contract can be implemented by a local pipe or socket, stdio, DLL calls, shared memory, a WebAssembly host bridge or another mechanism; the name **INetworkProtocol** does not require an IP network. Implement these interfaces for a new transport: - **INetworkProtocolConnection**: install or uninstall one callback, start the asynchronous read loop, send one string message and stop the connection. - **INetworkProtocolCallback**: receive strings, remote errors, local errors, connected and disconnected events. Callback methods may run on any thread, so implementations and users must be thread-safe. - **INetworkProtocolClient**: own one connection, block in **WaitForServer** until the connection is established or fails, and report **ClientStatus**. - **INetworkProtocolServer**: start accepting, call **OnClientConnected** for accepted transport connections, stop all owned connections and report stopped state. **InstallCallback** should call **OnInstalled** with the connection. Passing **nullptr** uninstalls the callback. **BeginReadingLoopUnsafe** starts receiving messages asynchronously, but callers must tolerate implementations that have already received data after the callback was installed. **Stop** is the shutdown boundary; after it returns, pending transport callbacks should no longer touch the stopped object. A raw client must return a connection object from **GetConnection**, but using that connection before **WaitForServer** finishes is outside the contract. A raw server should not report client-connected callbacks before **Start** or after **Stop**. If a connection is lost, the same connection object does not reconnect; a reconnecting transport should create a new connection object. After a raw transport exists, reuse the channel bridge instead of reimplementing channel routing: ```C++ class MyConnection : public Object, public virtual INetworkProtocolConnection { // Store one callback, frame transport bytes into WString messages, // call OnReadString / OnLocalError / OnDisconnected, and drain callbacks in Stop. }; class MyClient : public Object, public virtual INetworkProtocolClient { // Own one MyConnection and implement WaitForServer / GetStatus. }; class MyServer : public Object, public virtual INetworkProtocolServer { // Accept MyConnection objects and pass them to OnClientConnected. }; using MyChannelServer = NetworkProtocolChannelServer; using MyChannelClient = NetworkProtocolChannelClient; ``` ## Default Channel Bridge **NetworkProtocolChannelServer**, **NetworkProtocolChannelClient** and **NetworkProtocolLocalChannelClient** translate channel batches into raw **WString** protocol messages. The bridge requires a serializer where: - **TSerialization::SourceType** is **collections::List\**. - **TSerialization::DestType** is **WString**. - **TSerialization::ContextType** stores optional serializer state, or **std::nullptr_t** when no state is required. - **Serialize** converts a package list to one **WString**. - **Deserialize** converts one **WString** back to a package list. The bridge uses **NetworkPackage** as the raw text envelope. Its string shape is **clientId,extraClientId1,...;channelName;messageBody**. For client-to-server messages, **clientId** is the direct receiver id; an empty id means broadcast, and extra ids are blocked broadcast receivers. For server-to-client messages, **clientId** is the positive sender id. A network channel client connects by sending one handshake package with an empty client id, an empty channel name and all supported channel names joined by **!**. The server validates the names, assigns a positive client id, records channel membership and sends the id back in an empty-channel response. After that, normal channel packages are delivered only when sender and receiver membership matches the channel name. **BroadcastError** is a terminal admission boundary. The first broadcast error is retained, later broadcasts are ignored, and new network or local admissions are rejected. If an application **OnClientConnected** callback was already running, the server keeps the underlying transport alive until an accepted client receives that retained error and disconnects. A committed client is not included in the broadcast snapshot until its client-id response or local **OnConnected** callback completes, preserving connected-before-fatal ordering. Concurrent **Stop** calls share this barrier and cannot stop the transport early. Callback-reentrant terminal calls never wait on themselves; when broadcast owns the terminal boundary, physical stop is deferred until protected raw protocol, admission, fatal-delivery and disconnection callbacks unwind. If the last barrier is a raw protocol callback, completion moves to another thread so the underlying transport can drain that callback. A non-reentrant **Stop** reports a recorded shutdown exception once; a most-derived server destructor must call **Stop** before destroying callback-visible fields and catch any exception. A channel server over a Windows named pipe can be declared like this: ```C++ #include #include using namespace vl; using namespace vl::inter_process; class ChatServer : public NetworkProtocolChannelServer { public: ChatServer(const WString& pipeName) : NetworkProtocolChannelServer(pipeName) { } WaitForClientResult OnClientConnected( vint clientId, const IChannelClient::ChannelNameList& availableChannels, IChannelClient* localClient) override { return availableChannels.Contains(ChatChannelName) ? WaitForClientResult::Accept : WaitForClientResult::Reject; } }; auto server = Ptr(new ChatServer(L"ExamplePipe")); server->Start(); ``` ## Portable Async-Socket Implementations The types in **vl::inter_process::async_tcp_socket** form a portable, loopback-only stack for asynchronous TCP bytes, HTTP/1.1 requests and Mini HTTP services. Platform-neutral factories select **windows_socket::AsyncSocketServer** and **AsyncSocketClient**, **linux_socket::AsyncSocketServer** and **AsyncSocketClient**, or **macos_socket::AsyncSocketServer** and **AsyncSocketClient** at the application composition boundary. ### Direct Length-Framed Transport Use **async_tcp_socket::NetworkProtocolServer\** and **async_tcp_socket::NetworkProtocolClient\** when both peers use VlppOS and HTTP compatibility is unnecessary. One logical connection is one physical TCP socket. Each **WString** is framed with its character count and native **wchar_t** bytes, so this is not a portable wire encoding between peers with different **wchar_t** representations. These templates construct their socket backend from forwarded constructor arguments, so choose the concrete type for the target: **windows_socket::AsyncSocketServer** and **AsyncSocketClient**, **linux_socket::AsyncSocketServer** and **AsyncSocketClient**, or **macos_socket::AsyncSocketServer** and **AsyncSocketClient**, and pass the port to the protocol adapter. They do not accept an injected **Ptr\** or **Ptr\**. **IAsyncSocketServer**, **IAsyncSocketClient**, **IAsyncSocketConnection** and **IAsyncSocketCallback** expose the underlying ordered full-duplex byte streams. The interface-returning **CreateDefaultAsyncSocketServer(port)** and **CreateDefaultAsyncSocketClient(port)** factories are for code that consumes these socket interfaces, including the injected **SocketHttpServer** and **SocketHttpClient** dependencies below. Each socket exposes its immutable loopback port through **GetPort**, and **IAsyncSocketClient::CreateSameEndpointClient** creates a fresh client for another physical lane on the same endpoint. ### HTTP-Compatible Socket Transport Use **async_tcp_socket::SocketHttpServer** and **async_tcp_socket::SocketHttpClient** when the transport must use the VlppOS HTTP wire protocol or interoperate with the Windows HTTP implementation. They implement **INetworkProtocolServer**, **INetworkProtocolClient** and **INetworkProtocolConnection** over the portable Mini HTTP stack. The public construction surface keeps native socket composition explicit: ```C++ SocketHttpServer( Ptr socketServer, const WString& urlPrefix ); SocketHttpClient( Ptr socketClient, const WString& server, const WString& urlPrefix ); ``` The server adapter uses the injected listener and never creates another one. The client uses the injected client for its first physical lane and calls **CreateSameEndpointClient** for the additional lanes required by full-duplex polling and recovery. The server name must select loopback, and the URL prefix is empty for the origin root or begins with **/**. The adapter uses the same **/VlppInterProcess/Connect**, **/VlppInterProcess/Request/{token}** and **/VlppInterProcess/Response/{token}** routes as **windows_http::HttpServer** and **windows_http::HttpClient**. A successful nonempty Request response is provisional delivery: the next Request for the same token is its implicit acknowledgement and must arrive within five seconds. The deadline starts only when the server actually sends a message, so an idle connection has no heartbeat deadline. If the response fails, the server restores the message to the FIFO head; if the acknowledgement is missing, it reports a nonfatal local error. A raw callback may decline promotion, while an admitted **NetworkProtocolChannelServer** connection promotes either error, disconnects, unregisters the token and rejects any late Request carrying that stale token. The protocol still has no deduplication, heartbeat or explicit disconnect route and must not be treated as exactly-once delivery. ### Portable Mini HTTP Request Helpers **async_tcp_socket::HttpRequest**, **HttpResponse**, **HttpRequestServer**, **HttpRequestClient** and **HttpRequestConnection** provide the lower HTTP/1.1 message layer. Use them when code needs complete binary-safe request and response objects and controls each connection directly. **async_tcp_socket::SocketHttpServerApi**, **SocketHttpRequestContext** and **SocketHttpClientApi** add prefix dispatch and request/response conveniences. A server API receives **(socketServer, urlPrefix)**, and multiple APIs share one listener only when they receive the exact same server object. Override **OnHttpRequestReceived**, inspect the relative path, query or UTF-8 body, and complete the one-shot context with **Respond**, **RespondStatus**, **RespondBytes**, **RespondUtf8** or **Cancel**. A client API receives **(socketClient, server)** and queues asynchronous **HttpQuery** calls. **SocketHttpClientApi** uses the **windows_http::HttpRequest**, **HttpResponse** and **HttpError** value types, but those types are platform-neutral and using them here does not invoke WinHTTP. The TCP, HTTP and Mini HTTP implementations in this section are local test, validation and demonstration infrastructure rather than a production web stack. ## Windows Implementations Named pipes and the legacy HTTP.sys/WinHTTP transport are Windows-only. They are available for local tests and as reference implementations for the raw protocol contract, but do not make portable feature code depend on them or treat them as production communication stacks. ### Named Pipe Transport **vl::inter_process::named_pipe::NamedPipeServer** implements **INetworkProtocolServer**. **vl::inter_process::named_pipe::NamedPipeClient** derives from **vl::inter_process::named_pipe::NamedPipeConnection** and implements **INetworkProtocolClient**. **vl::inter_process::named_pipe::NamedPipeConnection** implements **INetworkProtocolConnection**. The server starts overlapped named-pipe accepting in **Start** and owns both accepted connections and pending accepts. The client opens **\\.\pipe\NAME**, waits for the server and switches the pipe into message-read mode. **SendString** frames one **WString** with length data. The implementation chunks writes because a Windows named pipe message is limited to 64K bytes. Broken-pipe cases become disconnection events, and fatal local pipe failures become **OnLocalError** followed by disconnection. ### HTTP Transport **vl::inter_process::windows_http::HttpServer** derives from **vl::inter_process::windows_http::HttpServerApi** and implements **INetworkProtocolServer**. **vl::inter_process::windows_http::HttpClient** implements both **INetworkProtocolClient** and **INetworkProtocolConnection**. This transport is a raw protocol implementation over Windows HTTP APIs, not a general web framework. The raw HTTP protocol uses these routes under the configured base URL: - **GET /VlppInterProcess/Connect** creates a logical connection and returns per-connection request and response URLs. - **POST /VlppInterProcess/Request/GUID** is the client-maintained long-poll request for server-to-client messages. - **POST /VlppInterProcess/Response/GUID** sends client-to-server messages and may also receive one queued server-to-client message. **vl::inter_process::windows_http::HttpClient::WaitForServer** sends the connect request, validates the returned URLs, records them and reports connection. **BeginReadingLoopUnsafe** starts the long-poll request loop. **SendString** posts to the response URL. Each recoverable failed Connect, Request or Response exchange calls **INetworkProtocolCallback::OnLocalError** with **fatal == false** before retrying; Connect and Response retry a limited number of times, while Request retries while the client is still running. Bounded retry exhaustion remains raw-fatal. Returning **true** from **OnLocalError** promotes a recoverable failure and makes the raw client stop after the callback returns. **NetworkProtocolChannelClient** does this for every local error after its channel reaches **Connected**, forwards **fatal == true** to its **IChannelClient** user and disconnects the channel; pre-handshake Connect retries remain under raw-client policy. **vl::inter_process::windows_http::HttpServer** creates a **vl::inter_process::windows_http::HttpServerConnection** for each connect request. Server-to-client messages are returned through a pending long-poll request when possible, or queued until the next request. A successful message response starts a five-second acknowledgement deadline; the next Request for the same GUID cancels it. A failed response retains the message, while a missing acknowledgement reports a recoverable **OnLocalError**. An admitted channel promotes either error, stops the logical connection and unregisters the GUID, so a late Request is rejected as unknown. No deadline runs while the server is idle. Client-to-server request bodies are dispatched as inbound strings. When the server stops, pending long-poll requests are cancelled and connection callbacks receive disconnection. ### Windows HTTP Helper APIs **vl::inter_process::windows_http::HttpClientApi** and **vl::inter_process::windows_http::HttpServerApi** are lower-level Windows helpers used by **vl::inter_process::windows_http::HttpClient** and **vl::inter_process::windows_http::HttpServer**. Use them directly when a Windows feature needs asynchronous HTTP request and response behavior without adopting the raw **INetworkProtocol** transport shape. **vl::inter_process::windows_http::HttpClientApi** owns one WinHTTP session and connection for a host and port. **HttpQuery** sends one asynchronous request described by **vl::inter_process::windows_http::HttpRequest**, whose fields include method, query, body, content type, accept types, credentials, cookies, extra headers and timeouts. Results are either **vl::inter_process::windows_http::HttpResponse** or **vl::inter_process::windows_http::HttpError**; HTTP status codes such as 404 are represented as **HttpResponse** values. **vl::inter_process::windows_http::HttpServerApi** owns one HTTP.sys URL prefix. Override **OnHttpRequestReceived** to dispatch requests and **OnHttpServerStopping** to clean up feature state. Helpers such as **GetUtf8Body**, **SendResponse** and **SendResponseUtf8** handle common request-body and response tasks.