Uh oh!
There was an error while loading. Please reload this page.
Add C# csm-tcp-router-client SDK - #38
Conversation
…sts, example, NuGet packaging, and GitHub Actions workflow Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/1c9d260f-6983-4c60-9d7a-f07538723b70 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new C#/.NET client SDK under SDK/csharp/ that mirrors the existing Python client’s protocol and feature surface, including a single-file implementation, tests, example app, packaging metadata, and CI workflow.
Changes:
- Introduces the single-file .NET client implementation (
TcpRouterClient, protocol codec, models, exceptions, transport). - Adds xUnit protocol + integration tests with an in-process loopback
MockServer. - Adds docs, runnable console example, NuGet packaging metadata, solution file, CI workflow, and .gitignore updates.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.cs | Core single-file SDK: protocol codec, transport, client APIs, models, exceptions |
| SDK/csharp/src/CsmTcpRouter/CsmTcpRouter.csproj | Multi-targeted NuGet packaging metadata and deterministic CI build settings |
| SDK/csharp/tests/CsmTcpRouter.Tests/ProtocolTests.cs | Protocol codec + model/error parsing unit tests |
| SDK/csharp/tests/CsmTcpRouter.Tests/MockServer.cs | Loopback TCP mock server used by integration tests |
| SDK/csharp/tests/CsmTcpRouter.Tests/ClientIntegrationTests.cs | End-to-end tests for connect, commands, subscriptions, callbacks, timeouts, disconnect behavior |
| SDK/csharp/tests/CsmTcpRouter.Tests/CsmTcpRouter.Tests.csproj | Test project dependencies + reference to the SDK project |
| SDK/csharp/examples/BasicUsage/Program.cs | Runnable example showing typical client flows |
| SDK/csharp/examples/BasicUsage/BasicUsage.csproj | Example project referencing the SDK |
| SDK/csharp/README.md | English SDK documentation and API overview |
| SDK/csharp/README.zh-cn.md | Chinese SDK documentation and API overview |
| SDK/csharp/CHANGELOG.md | Initial changelog entry for v0.1.0 |
| SDK/csharp/LICENSE | MIT license for the C# SDK distribution |
| SDK/csharp/CsmTcpRouter.sln | Solution grouping src/tests/examples |
| .github/workflows/CSharp_SDK.yml | Cross-OS build/test + pack + tag-gated NuGet publish workflow |
| .gitignore | Ignore .NET build artifacts and NuGet outputs |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using var client = new TcpRouterClient(); | ||
| // Port 1 is virtually guaranteed to be closed; use a short timeout. | ||
| Assert.Throws<RouterConnectionException>( | ||
| () => client.Connect("127.0.0.1", 1, TimeSpan.FromMilliseconds(500))); | ||
| } |
There was a problem hiding this comment.
These tests assume TCP port 1 is closed to trigger connection/availability failures. On some machines/containers that port can be open, which would make the tests flaky. Consider allocating an unused port more deterministically (e.g., bind a TcpListener to port 0 to obtain a free port, stop it, then test against that port) or use an address/port combination guaranteed to fail in the test environment.
| var ct = probe.ConnectAsync(host, port); | ||
| var winner = await Task.WhenAny(ct, Task.Delay(TimeSpan.FromSeconds(1))).ConfigureAwait(false); | ||
| if (winner == ct && !ct.IsFaulted) | ||
| { | ||
| try { probe.Close(); } catch { /* ignore */ } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
WaitForServerAsync starts TcpClient.ConnectAsync and uses Task.WhenAny with a 1s delay, but it never awaits/observes the connect task when it faults or times out. This can leave unobserved exceptions and potentially keep an in-flight connect attempt running until the socket is disposed. Consider awaiting the connect task in both success and failure paths (to observe exceptions) and proactively closing the probe socket when the delay wins (or using a CancellationToken/WaitAsync timeout) so each retry is cleanly cancelled.
| varct=probe.ConnectAsync(host,port); | |
| varwinner=awaitTask.WhenAny(ct,Task.Delay(TimeSpan.FromSeconds(1))).ConfigureAwait(false); | |
| if(winner==ct&&!ct.IsFaulted) | |
| { | |
| try{probe.Close();}catch{/* ignore */} | |
| returntrue; | |
| } | |
| varconnectTask=probe.ConnectAsync(host,port); | |
| varwinner=awaitTask.WhenAny(connectTask,Task.Delay(TimeSpan.FromSeconds(1))).ConfigureAwait(false); | |
| if(winner==connectTask) | |
| { | |
| awaitconnectTask.ConfigureAwait(false); | |
| try{probe.Close();}catch{/* ignore */} | |
| returntrue; | |
| } | |
| try{probe.Close();}catch{/* ignore */} | |
| try | |
| { | |
| awaitconnectTask.ConfigureAwait(false); | |
| } | |
| catch(SocketException){/* not ready yet */} | |
| catch(IOException){/* not ready yet */} | |
| catch(ObjectDisposedException){/* connect aborted by closing probe */} |
| var tcs = _respTcs; | ||
| var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false); | ||
| if (winner != tcs.Task) | ||
| throw new RouterTimeoutException($"No response received within {timeout.TotalSeconds:F1}s."); | ||
| object item = await tcs.Task.ConfigureAwait(false); | ||
| if (item is Exception exc) throw exc; | ||
| var packet = (Packet)item; | ||
| return new CommandResponse(packet.Data); | ||
| } |
There was a problem hiding this comment.
If WaitForRespAsync times out, the connection remains open and subsequent SendAndWait calls can consume a late RESP from the previous request (there is no correlation ID in the protocol, and OnPacket always completes the current _respTcs). This can cause callers to receive the wrong response for a later command. A safer approach is to treat a RESP timeout as a protocol desync and force a Disconnect() (or otherwise prevent further requests until reconnect) so late frames cannot be misattributed.
| var tcs = _cmdRespTcs; | ||
| var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false); | ||
| if (winner != tcs.Task) | ||
| throw new RouterTimeoutException($"No cmd-resp received within {timeout.TotalSeconds:F1}s."); | ||
| object item = await tcs.Task.ConfigureAwait(false); | ||
| if (item is Exception exc) throw exc; | ||
| // CMD_RESP payload is a handshake acknowledgment; discard it. | ||
| } |
There was a problem hiding this comment.
WaitForCmdRespAsync has the same desync risk as WaitForRespAsync: after a cmd-resp timeout the client stays connected, and a late CMD_RESP from the prior request can complete the next in-flight _cmdRespTcs, making a later Post/Subscribe appear to succeed incorrectly. Consider disconnecting (or marking the connection unusable) on CMD_RESP timeouts to avoid misattributing late frames.
| client.SubscribeStatus("Status", "AI", _ => Interlocked.Increment(ref hits), DefaultTimeout); | ||
| client.UnsubscribeStatus("Status", "AI", DefaultTimeout); | ||
| server.PushStatus("Status >> v1 <- AI"); | ||
| Thread.Sleep(150); |
There was a problem hiding this comment.
This test uses Thread.Sleep to assert that a callback is not invoked after UnsubscribeStatus. Sleep-based timing assertions are inherently flaky across CI environments. Prefer using a synchronization primitive (e.g., ManualResetEventSlim) and asserting it is not signaled within a timeout window, or push multiple status messages and assert the counter remains unchanged with a bounded wait loop.
| client.SubscribeStatus("Status","AI", _ =>Interlocked.Increment(refhits),DefaultTimeout); | |
| client.UnsubscribeStatus("Status","AI",DefaultTimeout); | |
| server.PushStatus("Status >> v1 <- AI"); | |
| Thread.Sleep(150); | |
| usingvarev=newManualResetEventSlim(); | |
| client.SubscribeStatus("Status","AI", _ =>{Interlocked.Increment(refhits);ev.Set();},DefaultTimeout); | |
| client.UnsubscribeStatus("Status","AI",DefaultTimeout); | |
| server.PushStatus("Status >> v1 <- AI"); | |
| Assert.False(ev.Wait(150),"callback was invoked after unsubscribe"); |
nevstop
commented
Apr 27, 2026
@copilot apply changes based on the comments in this thread |
…m in unsubscribe test, observe WaitForServerAsync connect task, force disconnect on RESP/CMD_RESP timeout Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a52165ab-7829-42b0-91f9-e4ba84e0b6b9 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com>
Addressed all five review comments in 050485e:
All 51 tests still pass. |
* Refactor bilingual README for clarity and replace Mermaid diagrams with Excalidraw-based PNG images (#29) * docs: rewrite bilingual README with mermaid diagrams Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/457dc57e-fd6d-4087-a604-3a7a4dd68b7b Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: replace mermaid diagrams with static PNG images Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: replace mermaid with png diagrams and add excalidraw sources Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: align client diagram alt text with sequence content Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: normalize client diagram naming and alt text Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: unify client diagram alt text naming Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: improve diagram alt text accessibility in both READMEs Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a92684df-d1ef-4e7f-801e-473ee3d2911d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Convert `.doc/CSM-TCP-Router.drawio` to Excalidraw source and synced PNG export (#30) * docs: add excalidraw and png converted from drawio Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/8747d072-0055-4877-a383-891476e8e333 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: fix application spelling in converted diagram Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/8747d072-0055-4877-a383-891476e8e333 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * 17 python sdk (#32) * feat: add Python pip-publishable SDK for CSM-TCP-Router (#31) * feat: add Python pip-publishable SDK for CSM-TCP-Router Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/4a1ee665-7464-4bd0-8898-0725daef43d5 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * fix: add least-privilege permissions to CI workflow jobs Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/4a1ee665-7464-4bd0-8898-0725daef43d5 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * feat: add asyncio client, Chinese README, and TestPyPI CI stage (v0.2.0) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/93afe5c4-c917-4b9b-a347-189efb3bf4db Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * fix: address all PR review comments (shared _errors, socket leak, locks, disconnect sentinels, isawaitable, changelog) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/b0917f8e-c50c-4a63-ae30-a1c245a6d3d7 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Add bilingual VI API reference docs for CSM-TCP-Router (Server + Client) (#35) * Initial plan * docs: add bilingual VI API documentation for CSM-TCP-Router Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/cacb955d-6c38-4fc7-b30d-9381fbbd06e1 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: move VI API docs under src and align reference format Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/117da425-df26-4ecc-a8c4-ab0e98412e50 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * docs: restore compatibility notes in status API sections Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/117da425-df26-4ecc-a8c4-ab0e98412e50 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> * update PythonClientAPI/* * Consolidate Python SDK into single-file `csm_tcp_router_client` module and release as v0.3.0 (#37) * Consolidate Python SDK into single-file csm_tcp_router_client module Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/ffc74aa4-b55d-45b4-b066-749d1db8c176 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Bump SDK to 0.3.0 and fix workflow paths so publish jobs trigger Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/f2eec384-ae45-4817-bcb3-1f990db4954b Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Add C# csm-tcp-router-client SDK (#38) * Initial plan * Add C# csm-tcp-router-client SDK (single-file), VS solution, xUnit tests, example, NuGet packaging, and GitHub Actions workflow Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/1c9d260f-6983-4c60-9d7a-f07538723b70 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Address review: deterministic closed-port helper, ManualResetEventSlim in unsubscribe test, observe WaitForServerAsync connect task, force disconnect on RESP/CMD_RESP timeout Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/a52165ab-7829-42b0-91f9-e4ba84e0b6b9 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Add C csm-tcp-router-client SDK (multi-platform, VS2026, CMake, tests) (#40) * Add C csm-tcp-router-client SDK with VS2026 + CMake + tests Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/ea6c42b2-b6f8-4ebe-8438-786601832ef5 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Address PR review comments on the C SDK Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/de04612d-484c-4905-9c04-06c451a01e15 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Drop redundant g_wsa_lock_inited; route cleanup through InitOnceExecuteOnce Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/de04612d-484c-4905-9c04-06c451a01e15 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Fix INADDR_LOOPBACK undeclared on macOS in mock_server.c (#41) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/eab9769d-1260-4715-8d72-1e3241719ef1 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * 将 SDK/ 下所有代码注释翻译为中文 (#42) * translate: convert all C SDK comments from English to Chinese Translated all code comments in 11 C SDK files from English to Chinese, preserving all code logic, variable names, function names, string literals, Doxygen tags (@PARAM, @return, etc.) and technical proper names (TCP, CSM, WSA, POSIX, BSD, Win32, Winsock2, pthreads, CMake, NUL, DLL, etc.). Files translated: - SDK/c/include/csm_tcp_router_client.h - SDK/c/src/csm_tcp_router_client.c - SDK/c/examples/basic_usage.c - SDK/c/examples/subscribe_status.c - SDK/c/tests/test_harness.h - SDK/c/tests/mock_server.h - SDK/c/tests/mock_server.c - SDK/c/tests/test_main.c - SDK/c/tests/test_client.c - SDK/c/tests/test_protocol.c - SDK/c/tests/test_integration.c Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * translate: convert all C# SDK comments from English to Chinese Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * translate: convert all Python SDK comments from English to Chinese Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * translate: convert all Python SDK comments from English to Chinese (partial) Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/097602f2-f319-49ea-84a2-3c8a30aa0915 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Add interactive ClientConsole example to Python, C# and C SDKs (#43) * Add interactive ClientConsole example to Python, C# and C SDKs Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/ca3628f9-8283-448f-8789-d860c873c1dc Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * Validate port argument in Python and C# ClientConsole examples Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-TCP-Router-App/sessions/1a565ac6-daf6-4291-8c0c-b3444c37d8ba Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> * update deps * 重命名example * backup code * mass compile to trigger build --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: nevstop <nevstop@NEVSTOP-LAB>
SDK/csharp/directory mirroringSDK/python/src/CsmTcpRouter/CsmTcpRouter.csCsmTcpRouter.csprojlibrary project (netstandard2.0 + net8.0, NuGet metadata)tests/CsmTcpRouter.Tests/examples/BasicUsage/console exampleCsmTcpRouter.slnREADME.md,README.zh-cn.md,CHANGELOG.md,LICENSE.github/workflows/CSharp_SDK.yml.gitignorefor C# build artifactsGetClosedPort()helper that binds aTcpListenerto port 0, captures the OS-assigned port, then stops the listenerThread.SleepinUnsubscribeStatus_RemovesCallbackwithManualResetEventSlim.Wait(150)assertionWaitForServerAsyncnow awaits the connect task in both success and timeout-loss paths so faults are observed; proactively closes the probe when the delay wins so the in-flight connect attempt is cancelledWaitForRespAsyncandWaitForCmdRespAsyncnow force_transport.Disconnect()on timeout so a lateRESP/CMD_RESPfrom the timed-out command can no longer be misattributed to the next request (protocol v0 has no correlation id)