Skip to content

Commit 6e9cafd

Browse files
committed
Add awaited async stream receive callbacks
1 parent d89cbcf commit 6e9cafd

16 files changed

Lines changed: 2237 additions & 308 deletions

ARCHITECTURE.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
WatsonTcp is a C# TCP client/server library that provides reliable message-level delivery over TCP by implementing a custom framing protocol. TCP is a bidirectional byte stream with no inherent message boundaries; WatsonTcp solves this by prepending each message with a JSON header that declares the payload length, followed by a `\r\n\r\n` delimiter, followed by the raw data bytes. This allows receivers to know exactly how many bytes constitute each application-level message.
66

7-
The library targets .NET Standard 2.0/2.1, .NET Framework 4.62/4.8, and .NET 6.0/8.0. Both endpoints of a connection must either use WatsonTcp or implement compatible framing (see [FRAMING.md](FRAMING.md) for the wire protocol specification).
7+
The library targets .NET Standard 2.0/2.1, .NET Framework 4.62/4.8, and .NET 8.0/10.0. Both endpoints of a connection must either use WatsonTcp or implement compatible framing (see [FRAMING.md](FRAMING.md) for the wire protocol specification).
88

99
Beyond framing, WatsonTcp provides:
1010
- Automatic connection lifecycle management with event-driven notifications
@@ -109,15 +109,32 @@ Process by MessageStatus:
109109
+-- RegisterClient --> GUID registration
110110
+-- Normal + SyncRequest --> invoke SyncRequestReceived callback, send response
111111
+-- Normal + SyncResponse --> resolve matching TaskCompletionSource
112-
+-- Normal --> read data, fire MessageReceived or StreamReceived event
112+
+-- Normal --> read data, dispatch MessageReceived, StreamReceivedAsync, or StreamReceived
113113
```
114114

115115
### Stream vs. Byte Array Delivery
116116

117-
When `Events.MessageReceived` is set, the data receiver reads the full payload into a `byte[]` via `WatsonCommon.ReadMessageDataAsync()` before firing the event. When `Events.StreamReceived` is set instead, behavior depends on message size:
117+
Receive-mode precedence is:
118118

119-
- **Large messages** (ContentLength >= `Settings.MaxProxiedStreamSize`): A `WatsonStream` wrapping the raw TCP/SSL stream is delivered synchronously. The event handler must consume the stream before returning, as the underlying connection stream advances.
120-
- **Small messages**: Data is first copied into a `MemoryStream`, then wrapped in a `WatsonStream` and delivered asynchronously via `Task.Run`.
119+
1. `Events.MessageReceived`
120+
2. `Callbacks.StreamReceivedAsync`
121+
3. `Events.StreamReceived`
122+
123+
When `Events.MessageReceived` is set, the data receiver reads the full payload into a `byte[]` via `WatsonCommon.ReadMessageDataAsync()` before firing the event.
124+
125+
When `Callbacks.StreamReceivedAsync` is used instead:
126+
127+
- **Large messages** (ContentLength >= `Settings.MaxProxiedStreamSize`): A `WatsonStream` wrapping the raw TCP/SSL stream is delivered to the callback and awaited. WatsonTcp will not parse the next message on that connection until the callback returns.
128+
- **Small messages**: Data is first copied into a `MemoryStream`, then wrapped in a `WatsonStream` and passed to the callback.
129+
- **Unread remainder**: If the callback returns without consuming all bytes, WatsonTcp drains the unread remainder before continuing, so framing stays aligned for the next message.
130+
131+
When `Events.StreamReceived` is used:
132+
133+
- it remains the legacy synchronous stream API
134+
- large proxied streams must still be consumed before the handler returns
135+
- unread remainder is drained after handler return before the next message is parsed
136+
137+
Configuration conflicts are reported through `Settings.Logger` warnings rather than console/debug output.
121138

122139
## 3. Component Diagram
123140

@@ -464,13 +481,13 @@ Key details:
464481

465482
**`WatsonTcpServerEvents`**: `ClientConnected`, `ClientDisconnected`, `MessageReceived`, `StreamReceived`, `ExceptionEncountered`, `ServerStarted`, `ServerStopped`, `AuthenticationSucceeded`, `AuthenticationFailed`
466483

467-
Only one of `MessageReceived` or `StreamReceived` should be set. `MessageReceived` takes precedence if both are set.
484+
`MessageReceived` takes precedence over both stream receive modes. `Callbacks.StreamReceivedAsync` takes precedence over `Events.StreamReceived`.
468485

469486
### Callbacks Classes
470487

471-
**`WatsonTcpClientCallbacks`**: `AuthenticationRequested` (returns PSK string), `SyncRequestReceived` / `SyncRequestReceivedAsync` (handles sync requests from the server)
488+
**`WatsonTcpClientCallbacks`**: `AuthenticationRequested` (returns PSK string), `SyncRequestReceived` / `SyncRequestReceivedAsync` (handles sync requests from the server), `HandshakeAsync`, `StreamReceivedAsync`
472489

473-
**`WatsonTcpServerCallbacks`**: `SyncRequestReceived` / `SyncRequestReceivedAsync` (handles sync requests from clients)
490+
**`WatsonTcpServerCallbacks`**: `SyncRequestReceived` / `SyncRequestReceivedAsync` (handles sync requests from clients), `AuthorizeConnectionAsync`, `HandshakeAsync`, `StreamReceivedAsync`
474491

475492
### Keepalive Settings
476493

0 commit comments

Comments
 (0)