|
4 | 4 |
|
5 | 5 | 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. |
6 | 6 |
|
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). |
8 | 8 |
|
9 | 9 | Beyond framing, WatsonTcp provides: |
10 | 10 | - Automatic connection lifecycle management with event-driven notifications |
@@ -109,15 +109,32 @@ Process by MessageStatus: |
109 | 109 | +-- RegisterClient --> GUID registration |
110 | 110 | +-- Normal + SyncRequest --> invoke SyncRequestReceived callback, send response |
111 | 111 | +-- Normal + SyncResponse --> resolve matching TaskCompletionSource |
112 | | - +-- Normal --> read data, fire MessageReceived or StreamReceived event |
| 112 | + +-- Normal --> read data, dispatch MessageReceived, StreamReceivedAsync, or StreamReceived |
113 | 113 | ``` |
114 | 114 |
|
115 | 115 | ### Stream vs. Byte Array Delivery |
116 | 116 |
|
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: |
118 | 118 |
|
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. |
121 | 138 |
|
122 | 139 | ## 3. Component Diagram |
123 | 140 |
|
@@ -464,13 +481,13 @@ Key details: |
464 | 481 |
|
465 | 482 | **`WatsonTcpServerEvents`**: `ClientConnected`, `ClientDisconnected`, `MessageReceived`, `StreamReceived`, `ExceptionEncountered`, `ServerStarted`, `ServerStopped`, `AuthenticationSucceeded`, `AuthenticationFailed` |
466 | 483 |
|
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`. |
468 | 485 |
|
469 | 486 | ### Callbacks Classes |
470 | 487 |
|
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` |
472 | 489 |
|
473 | | -**`WatsonTcpServerCallbacks`**: `SyncRequestReceived` / `SyncRequestReceivedAsync` (handles sync requests from clients) |
| 490 | +**`WatsonTcpServerCallbacks`**: `SyncRequestReceived` / `SyncRequestReceivedAsync` (handles sync requests from clients), `AuthorizeConnectionAsync`, `HandshakeAsync`, `StreamReceivedAsync` |
474 | 491 |
|
475 | 492 | ### Keepalive Settings |
476 | 493 |
|
|
0 commit comments