Skip to content

Conversation

@ArgoZhang
Copy link
Member

@ArgoZhang ArgoZhang commented Jun 30, 2025

Link issues

fixes #6330

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add a new DataAdapter sample demonstrating custom protocol handling with ITcpSocketClient, update related socket samples to use options-based configuration, introduce dedicated mock server services for receive and adapter scenarios, and refresh menu entries and extensibility points.

New Features:

  • Add Adapters sample component illustrating custom protocol data handling with ITcpSocketClient
  • Introduce MockCustomProtocolSocketServerService to simulate a custom protocol server

Enhancements:

  • Refactor Receives sample and SocketFactories snippet to use options-based GetOrCreate with explicit IPEndPoint
  • Make TcpSocketClientBase.Log method virtual to allow overriding
  • Update menu localization to include SocketDataAdapter entry and remove outdated IsNew/IsUpdate flags

Documentation:

  • Add Razor page and localized strings for the Adapters sample

Chores:

  • Rename MockSocketServerService to MockReceiveSocketServerService and register it alongside the custom protocol server in DI

@bb-auto bb-auto bot added the documentation Improvements or additions to documentation label Jun 30, 2025
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jun 30, 2025

Reviewer's Guide

This PR introduces a new DataAdapter sample under Sockets, refactors the existing Receives sample to use configurable endpoints, updates menu localization flags, renames and adds mock socket services, adjusts the TcpSocketClientBase logging method, and registers new services for custom-protocol demos.

Class diagram for new Adapters socket sample and related services

classDiagram
    class Adapters {
        - ITcpSocketFactory TcpSocketFactory
        - ITcpSocketClient _client
        - List~ConsoleMessageItem~ _items
        - IPEndPoint _serverEndPoint
        - CancellationTokenSource _connectTokenSource
        - CancellationTokenSource _sendTokenSource
        - CancellationTokenSource _receiveTokenSource
        + OnInitialized()
        + OnConnectAsync()
        + OnSendAsync()
        + OnCloseAsync()
        + OnClear()
        + Dispose(bool disposing)
        + Dispose()
    }
    class MockCustomProtocolSocketServerService {
        + ExecuteAsync(CancellationToken stoppingToken)
        - OnDataHandlerAsync(TcpClient client, CancellationToken stoppingToken)
    }
    class MockReceiveSocketServerService {
        + ExecuteAsync(CancellationToken stoppingToken)
        - MockSendAsync(TcpClient client, CancellationToken stoppingToken)
    }
    class TcpSocketClientBase {
        # ISocketClientProvider SocketClientProvider
        # ILogger Logger
        + Log(LogLevel logLevel, Exception? ex, string? message)
    }
    Adapters --|> IDisposable
    MockCustomProtocolSocketServerService --|> BackgroundService
    MockReceiveSocketServerService --|> BackgroundService
    TcpSocketClientBase <|.. ITcpSocketClient
    Adapters --> ITcpSocketFactory
    Adapters --> ITcpSocketClient
    Adapters --> ConsoleMessageItem
    Adapters --> IPEndPoint
    Adapters --> CancellationTokenSource
Loading

File-Level Changes

Change Details Files
Update Receives and Factory samples to use endpoint constants and GetOrCreate options
  • Remove unnecessary ComponentBase base class in Receives sample
  • Introduce a private _serverEndPoint field for connection configuration
  • Pass LocalEndPoint option when calling GetOrCreate
  • Use _serverEndPoint in ConnectAsync instead of hardcoded values
  • Adjust SocketFactories sample snippet to demonstrate LocalEndPoint option
Receives.razor.cs
SocketFactories.razor
Add new Adapters sample demonstrating custom protocol data handling
  • Create Adapters.razor page with UI and documentation
  • Implement Adapters.razor.cs with connect, send, receive, and dispose logic
  • Use custom CancellationTokenSources and manual receive via ITcpSocketClient
  • Illustrate data package structure (header + body) in demo
Adapters.razor
Adapters.razor.cs
Rename and extend mock socket server services
  • Rename MockSocketServerService to MockReceiveSocketServerService
  • Add MockCustomProtocolSocketServerService to simulate custom protocol responses
  • Update service registration to include both new services
MockSocketServerService.cs
MockReceiveSocketServerService.cs
MockCustomProtocolSocketServerService.cs
ServiceCollectionExtensions.cs
Adjust menu localization flags on demo menu entries
  • Mark SocketDataAdapter menu item as new
  • Remove outdated IsNew/IsUpdate flags on various demo items
MenusLocalizerExtensions.cs
Enhance TcpSocketClientBase logging extensibility
  • Make Log method virtual to allow derived classes to override
TcpSocketClientBase.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#6330 Add DataHandler sample code to the documentation for ITcpSocketClient.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@bb-auto bb-auto bot added this to the 9.8.0 milestone Jun 30, 2025
@ArgoZhang ArgoZhang merged commit 8775218 into main Jun 30, 2025
3 checks passed
@ArgoZhang ArgoZhang deleted the doc-socket branch June 30, 2025 12:29
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ArgoZhang - I've reviewed your changes - here's some feedback:

  • The Receives component removed ComponentBase but still overrides OnInitialized and depends on Blazor lifecycle—please restore ComponentBase inheritance so lifecycle methods work correctly.
  • Both Receives and Adapters implement IDisposable but never call CloseAsync or dispose the underlying _client—ensure the TCP client is properly closed or disposed in Dispose to avoid resource leaks.
  • You’ve registered two mock socket server hosted services unconditionally; consider only adding them in a development or testing environment (e.g. via IConfiguration or environment checks) to prevent them running in production.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The Receives component removed ComponentBase but still overrides OnInitialized and depends on Blazor lifecycle—please restore ComponentBase inheritance so lifecycle methods work correctly.
- Both Receives and Adapters implement IDisposable but never call CloseAsync or dispose the underlying _client—ensure the TCP client is properly closed or disposed in Dispose to avoid resource leaks.
- You’ve registered two mock socket server hosted services unconditionally; consider only adding them in a development or testing environment (e.g. via IConfiguration or environment checks) to prevent them running in production.

## Individual Comments

### Comment 1
<location> `src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs:14` </location>
<code_context>
+/// <summary>
+/// 模拟 Socket 服务端服务类
+/// </summary>
+internal class MockCustomProtocolSocketServerService(ILogger<MockCustomProtocolSocketServerService> logger) : BackgroundService
+{
+    /// <summary>
</code_context>

<issue_to_address>
Typo in file/class name: 'Protocal' should be 'Protocol'.

Please rename to 'MockCustomProtocolSocketServerService' for clarity and consistency.

Suggested implementation:

```csharp
internal class MockCustomProtocolSocketServerService(ILogger<MockCustomProtocolSocketServerService> logger) : BackgroundService

```

1. Rename the file from `MockCustomProtocalSocketServerService.cs` to `MockCustomProtocolSocketServerService.cs`.
2. Update any references to `MockCustomProtocalSocketServerService` elsewhere in the codebase to use `MockCustomProtocolSocketServerService`.
</issue_to_address>

### Comment 2
<location> `src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs:23` </location>
<code_context>
+    /// <returns></returns>
+    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+    {
+        var server = new TcpListener(IPAddress.Loopback, 8900);
+        server.Start();
+        while (stoppingToken is { IsCancellationRequested: false })
</code_context>

<issue_to_address>
TcpListener is not disposed.

Call server.Stop() in a finally block or when cancellation is requested to ensure the port and resources are released properly.
</issue_to_address>

### Comment 3
<location> `src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs:29` </location>
<code_context>
+        {
+            try
+            {
+                var client = await server.AcceptTcpClientAsync(stoppingToken);
+                _ = Task.Run(() => OnDataHandlerAsync(client, stoppingToken), stoppingToken);
+            }
</code_context>

<issue_to_address>
TcpClient is not disposed after use.

Ensure TcpClient is disposed after OnDataHandlerAsync completes to prevent resource leaks.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
                var client = await server.AcceptTcpClientAsync(stoppingToken);
                _ = Task.Run(() => OnDataHandlerAsync(client, stoppingToken), stoppingToken);
=======
                var client = await server.AcceptTcpClientAsync(stoppingToken);
                _ = Task.Run(async () =>
                {
                    try
                    {
                        await OnDataHandlerAsync(client, stoppingToken);
                    }
                    finally
                    {
                        client.Dispose();
                    }
                }, stoppingToken);
>>>>>>> REPLACE

</suggested_fix>

### Comment 4
<location> `src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs:47` </location>
<code_context>
+            try
+            {
+                // 接收数据
+                var len = await stream.ReadAsync(new byte[1024], stoppingToken);
+                if (len == 0)
+                {
</code_context>

<issue_to_address>
Allocating a new buffer on every read is inefficient.

Allocate the buffer once before the loop and reuse it to improve performance.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        await using var stream = client.GetStream();
        while (stoppingToken is { IsCancellationRequested: false })
        {
            try
            {
                // 接收数据
                var len = await stream.ReadAsync(new byte[1024], stoppingToken);
                if (len == 0)
                {
=======
        await using var stream = client.GetStream();
        var buffer = new byte[1024];
        while (stoppingToken is { IsCancellationRequested: false })
        {
            try
            {
                // 接收数据
                var len = await stream.ReadAsync(buffer, stoppingToken);
                if (len == 0)
                {
>>>>>>> REPLACE

</suggested_fix>

### Comment 5
<location> `src/BootstrapBlazor.Server/Services/MockCustomProtocalSocketServerService.cs:60` </location>
<code_context>
+                // 响应头: 4 字节表示响应体长度 [0x32, 0x30, 0x32, 0x35]
+                // 响应体: 8 字节当前时间戳字符串 
+                var data = new byte[12];
+                "2025"u8.ToArray().CopyTo(data, 0);
+                System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")).CopyTo(data, 4);
+                await stream.WriteAsync(data, stoppingToken);
</code_context>

<issue_to_address>
Unnecessary ToArray() call on u8 string literal.

CopyTo can be called directly on the ReadOnlySpan<byte> from the u8 literal: use "2025"u8.CopyTo(data) for better clarity and efficiency.
</issue_to_address>

### Comment 6
<location> `src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs:26` </location>
<code_context>
+
+    private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8900);
+
+    private readonly CancellationTokenSource _connectTokenSource = new();
+    private readonly CancellationTokenSource _sendTokenSource = new();
+    private readonly CancellationTokenSource _receiveTokenSource = new();
</code_context>

<issue_to_address>
Consider consolidating cancellation logic and extracting repeated code into helpers to simplify and clarify the component.

```suggestion
// 1. Replace three CancellationTokenSources with a single one:
-private readonly CancellationTokenSource _connectTokenSource = new();
-private readonly CancellationTokenSource _sendTokenSource = new();
-private readonly CancellationTokenSource _receiveTokenSource = new();
+private readonly CancellationTokenSource _cts = new();

// …and in Dispose:
-public void Dispose()
-{
-    _connectTokenSource.Cancel();
-    _connectTokenSource.Dispose();
-    _sendTokenSource.Cancel();
-    _sendTokenSource.Dispose();
-    _receiveTokenSource.Cancel();
-    _receiveTokenSource.Dispose();
-    GC.SuppressFinalize(this);
-}
+public void Dispose()
+{
+    _cts.Cancel();
+    _cts.Dispose();
+    GC.SuppressFinalize(this);
+}

// 2. Extract message‐logging helper to remove repetition:
-private async Task OnConnectAsync()
+private Task OnConnectAsync()
 {
-    if (_client is { IsConnected: false })
+    if (!_client.IsConnected)
     {
-        await _client.ConnectAsync(_serverEndPoint, _connectTokenSource.Token);
-        var state = _client.IsConnected ? "成功" : "失败";
-        _items.Add(new ConsoleMessageItem()
-        {
-            Message = $"{DateTime.Now}: 连接 {_client.LocalEndPoint} - {_serverEndPoint} {state}",
-            Color = _client.IsConnected ? Color.Success : Color.Danger
-        });
+        await _client.ConnectAsync(_serverEndPoint, _cts.Token);
+        Log($"连接 {_client.LocalEndPoint} → {_serverEndPoint} {(_client.IsConnected ? "成功" : "失败")}",
+            _client.IsConnected ? Color.Success : Color.Danger);
     }
 }

// 3. Extract request/response parsing to utilities:
+private static byte[] BuildRequest()
+{
+    var header = Encoding.UTF8.GetBytes("2025");
+    var body   = Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss"));
+    return header.Concat(body).ToArray();
+}

+private static (string Text, string Hex) ParseResponse(ReadOnlyMemory<byte> data)
+{
+    var arr = data.ToArray();
+    return (
+        Encoding.UTF8.GetString(arr, 4, arr.Length - 4),
+        BitConverter.ToString(arr)
+    );
+}

// 4. Simplify OnSendAsync:
-private async Task OnSendAsync()
+private async Task OnSendAsync()
 {
-    if (_client is { IsConnected: true })
+    if (!_client.IsConnected) return;
+
+    var request = BuildRequest();
+    if (!await _client.SendAsync(request, _cts.Token)) return;
+
+    var payload = await _client.ReceiveAsync(_cts.Token);
+    if (payload.IsEmpty) return;
+
+    var (text, hex) = ParseResponse(payload);
+    Log($"接收到来自 {_serverEndPoint} 数据: {text} HEX: {hex}");
 }

// 5. Add Log helper:
 private void Log(string msg, Color color = default)
 {
     _items.Add(new ConsoleMessageItem()
     {
         Message = $"{DateTime.Now}: {msg}",
         Color = color
     });
 }
```  
These steps consolidate your cancellation logic, remove inline buffer manipulation/UI repetition, and keep the socket behavior unchanged.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

/// <summary>
/// 模拟 Socket 服务端服务类
/// </summary>
internal class MockCustomProtocolSocketServerService(ILogger<MockCustomProtocolSocketServerService> logger) : BackgroundService
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (typo): Typo in file/class name: 'Protocal' should be 'Protocol'.

Please rename to 'MockCustomProtocolSocketServerService' for clarity and consistency.

Suggested implementation:

internal class MockCustomProtocolSocketServerService(ILogger<MockCustomProtocolSocketServerService> logger) : BackgroundService
  1. Rename the file from MockCustomProtocalSocketServerService.cs to MockCustomProtocolSocketServerService.cs.
  2. Update any references to MockCustomProtocalSocketServerService elsewhere in the codebase to use MockCustomProtocolSocketServerService.

/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var server = new TcpListener(IPAddress.Loopback, 8900);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): TcpListener is not disposed.

Call server.Stop() in a finally block or when cancellation is requested to ensure the port and resources are released properly.

Comment on lines +29 to +30
var client = await server.AcceptTcpClientAsync(stoppingToken);
_ = Task.Run(() => OnDataHandlerAsync(client, stoppingToken), stoppingToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): TcpClient is not disposed after use.

Ensure TcpClient is disposed after OnDataHandlerAsync completes to prevent resource leaks.

Suggested change
var client = await server.AcceptTcpClientAsync(stoppingToken);
_ = Task.Run(() => OnDataHandlerAsync(client, stoppingToken), stoppingToken);
var client = await server.AcceptTcpClientAsync(stoppingToken);
_ = Task.Run(async () =>
{
try
{
await OnDataHandlerAsync(client, stoppingToken);
}
finally
{
client.Dispose();
}
}, stoppingToken);

Comment on lines +41 to +49
await using var stream = client.GetStream();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
// 接收数据
var len = await stream.ReadAsync(new byte[1024], stoppingToken);
if (len == 0)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Allocating a new buffer on every read is inefficient.

Allocate the buffer once before the loop and reuse it to improve performance.

Suggested change
await using var stream = client.GetStream();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
// 接收数据
var len = await stream.ReadAsync(new byte[1024], stoppingToken);
if (len == 0)
{
await using var stream = client.GetStream();
var buffer = new byte[1024];
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
// 接收数据
var len = await stream.ReadAsync(buffer, stoppingToken);
if (len == 0)
{

// 响应头: 4 字节表示响应体长度 [0x32, 0x30, 0x32, 0x35]
// 响应体: 8 字节当前时间戳字符串
var data = new byte[12];
"2025"u8.ToArray().CopyTo(data, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Unnecessary ToArray() call on u8 string literal.

CopyTo can be called directly on the ReadOnlySpan from the u8 literal: use "2025"u8.CopyTo(data) for better clarity and efficiency.


private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8900);

private readonly CancellationTokenSource _connectTokenSource = new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider consolidating cancellation logic and extracting repeated code into helpers to simplify and clarify the component.

Suggested change
private readonly CancellationTokenSource _connectTokenSource = new();
// 1. Replace three CancellationTokenSources with a single one:
-private readonly CancellationTokenSource _connectTokenSource = new();
-private readonly CancellationTokenSource _sendTokenSource = new();
-private readonly CancellationTokenSource _receiveTokenSource = new();
+private readonly CancellationTokenSource _cts = new();
// …and in Dispose:
-public void Dispose()
-{
- _connectTokenSource.Cancel();
- _connectTokenSource.Dispose();
- _sendTokenSource.Cancel();
- _sendTokenSource.Dispose();
- _receiveTokenSource.Cancel();
- _receiveTokenSource.Dispose();
- GC.SuppressFinalize(this);
-}
+public void Dispose()
+{
+ _cts.Cancel();
+ _cts.Dispose();
+ GC.SuppressFinalize(this);
+}
// 2. Extract message‐logging helper to remove repetition:
-private async Task OnConnectAsync()
+private Task OnConnectAsync()
{
- if (_client is { IsConnected: false })
+ if (!_client.IsConnected)
{
- await _client.ConnectAsync(_serverEndPoint, _connectTokenSource.Token);
- var state = _client.IsConnected ? "成功" : "失败";
- _items.Add(new ConsoleMessageItem()
- {
- Message = $"{DateTime.Now}: 连接 {_client.LocalEndPoint} - {_serverEndPoint} {state}",
- Color = _client.IsConnected ? Color.Success : Color.Danger
- });
+ await _client.ConnectAsync(_serverEndPoint, _cts.Token);
+ Log($"连接 {_client.LocalEndPoint}{_serverEndPoint} {(_client.IsConnected ? "成功" : "失败")}",
+ _client.IsConnected ? Color.Success : Color.Danger);
}
}
// 3. Extract request/response parsing to utilities:
+private static byte[] BuildRequest()
+{
+ var header = Encoding.UTF8.GetBytes("2025");
+ var body = Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss"));
+ return header.Concat(body).ToArray();
+}
+private static (string Text, string Hex) ParseResponse(ReadOnlyMemory<byte> data)
+{
+ var arr = data.ToArray();
+ return (
+ Encoding.UTF8.GetString(arr, 4, arr.Length - 4),
+ BitConverter.ToString(arr)
+ );
+}
// 4. Simplify OnSendAsync:
-private async Task OnSendAsync()
+private async Task OnSendAsync()
{
- if (_client is { IsConnected: true })
+ if (!_client.IsConnected) return;
+
+ var request = BuildRequest();
+ if (!await _client.SendAsync(request, _cts.Token)) return;
+
+ var payload = await _client.ReceiveAsync(_cts.Token);
+ if (payload.IsEmpty) return;
+
+ var (text, hex) = ParseResponse(payload);
+ Log($"接收到来自 {_serverEndPoint} 数据: {text} HEX: {hex}");
}
// 5. Add Log helper:
private void Log(string msg, Color color = default)
{
_items.Add(new ConsoleMessageItem()
{
Message = $"{DateTime.Now}: {msg}",
Color = color
});
}

These steps consolidate your cancellation logic, remove inline buffer manipulation/UI repetition, and keep the socket behavior unchanged.

@codecov
Copy link

codecov bot commented Jun 30, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (ef82709) to head (f9c42cf).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #6331   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          717       717           
  Lines        31558     31558           
  Branches      4457      4457           
=========================================
  Hits         31558     31558           
Flag Coverage Δ
BB 100.00% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

doc(ITcpSocketClient): add DataHandler sample code

2 participants