VMHelper 2.0.0

dotnet add package VMHelper --version 2.0.0
                    
NuGet\Install-Package VMHelper -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="VMHelper" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="VMHelper" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="VMHelper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add VMHelper --version 2.0.0
                    
#r "nuget: VMHelper, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package VMHelper@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=VMHelper&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=VMHelper&version=2.0.0
                    
Install as a Cake Tool

VMHelper 类库

一个基于 .NET 8.0 的 TCP 通讯服务类库,专为视觉服务通讯而设计。

📋 功能特性

  • TCP 通讯服务:提供稳定的 TCP 客户端连接管理
  • 异步操作:支持异步连接、发送和接收操作
  • 线程安全:内置信号量确保多线程环境下的安全使用
  • 自动重连:连接异常时自动重连机制
  • 灵活配置:支持自定义服务器地址、端口、超时时间和结束符
  • 数据解析:内置响应数据解析功能,支持 name:value 格式,多个数据用,分隔
  • 类型安全解析:新增 ParseResult 类,提供类型安全的数据访问方法,支持多种数据类型(int、double、string、bool等)
  • 严格的错误处理:Get 方法在键不存在或转换失败时抛出明确的异常,确保数据完整性
  • 灵活的访问模式:提供 Try 方法用于安全访问,支持严格模式和宽松模式两种使用方式
  • 自定义分隔符:支持自定义键值对分隔符和键值分隔符,适应不同的数据格式
  • 路径管理:提供视觉服务相关的路径管理工具

🚀 快速开始

基本用法

using VMHelper;
using VMHelper.Interfaces;

// 创建 TCP 通讯服务实例
ITcpCommunicationService tcpService = new TcpCommunicationService();

// 配置服务器连接参数
tcpService.SetServerConnection("127.0.0.1", 7920, 3000);

// 配置结束符(可选)
tcpService.SetTerminatorConfig("\r", "\r");

// 连接到服务器
bool connected = await tcpService.ConnectAsync();
if (connected)
{
    Console.WriteLine("连接成功!");
    
    // 发送命令并接收响应
    string response = await tcpService.SendCommandAsync("GET_STATUS");
    Console.WriteLine($"服务器响应: {response}");
    
    // 发送命令并解析响应数据(返回字典)
    var data = await tcpService.SendCommandAndParseAsync("GET_DATA");
    foreach (var kvp in data)
    {
        Console.WriteLine($"{kvp.Key}: {kvp.Value}");
    }
    
    // 使用新的解析结果类(推荐)
    var result = await tcpService.SendCommandAndParseResultAsync("GET_DATA");
    
    try
    {
        int x = result.GetInt("X");
        double y = result.GetDouble("Y");
        string status = result.GetString("STATUS");
        bool enabled = result.GetBool("ENABLED");
        
        Console.WriteLine($"X: {x}, Y: {y}, Status: {status}, Enabled: {enabled}");
    }
    catch (KeyNotFoundException ex)
    {
        Console.WriteLine($"缺少必要数据: {ex.Message}");
    }
    catch (FormatException ex)
    {
        Console.WriteLine($"数据格式错误: {ex.Message}");
    }
}

// 记得释放资源
tcpService.Dispose();

使用路径管理工具

using VMHelper;

// 创建默认目录(如果不存在)
PathSD.CreateDirectoriesIfNotExist();

// 使用预定义的路径常量
string inputPath = PathSD.DefaultImageInputPath;
string outputPath = PathSD.DefaultImageOutputPath;
string inputDir = PathSD.DefaultImageInputDirectory;
string outputDir = PathSD.DefaultImageOutputDirectory;

Console.WriteLine($"图像输入路径: {inputPath}");
Console.WriteLine($"图像输出路径: {outputPath}");

使用 ParseResult 类解析数据

using VMHelper;

// 解析服务器响应数据
string responseData = "X:123.45,Y:67.89,STATUS:OK,ENABLED:1,COUNT:10";
var result = new ParseResult(responseData);

try
{
    // 类型安全地获取数据(如果键不存在或转换失败会抛出异常)
    double x = result.GetDouble("X");           // 123.45
    double y = result.GetDouble("Y");           // 67.89
    string status = result.GetString("STATUS"); // "OK"
    bool enabled = result.GetBool("ENABLED");   // true
    int count = result.GetInt("COUNT");         // 10
    
    Console.WriteLine($"位置: ({x}, {y}), 状态: {status}, 启用: {enabled}, 计数: {count}");
}
catch (KeyNotFoundException ex)
{
    Console.WriteLine($"缺少必要数据: {ex.Message}");
}
catch (FormatException ex)
{
    Console.WriteLine($"数据格式错误: {ex.Message}");
}

// 使用Try方法处理可能不存在的键
if (result.TryGetInt("TIMEOUT", out int timeout))
{
    Console.WriteLine($"超时时间: {timeout}");
}
else
{
    Console.WriteLine("未设置超时时间");
}

🔧 API 参考

ITcpCommunicationService 接口

属性
  • bool IsConnected: 获取当前连接状态
方法
  • void SetServerConnection(string host, int port, int timeoutMs): 设置服务器连接参数
  • void SetTerminatorConfig(string? sendTerminator, string? receiveTerminator): 配置数据结束符
  • Task<bool> ConnectAsync(): 异步连接到服务器
  • void Disconnect(): 断开连接
  • Task<string> SendCommandAsync(string command): 发送命令并接收原始响应
  • Task<Dictionary<string, double>> SendCommandAndParseAsync(string command): 发送命令并解析响应数据(返回字典)
  • Task<ParseResult> SendCommandAndParseResultAsync(string command, string pairSeparator = ",", string keyValueSeparator = ":"): 发送命令并解析响应数据(返回解析结果对象)
  • Dictionary<string, double> ParseResponse(string responseString): 解析响应字符串为字典
  • ParseResult ParseToResult(string responseString, string pairSeparator = ",", string keyValueSeparator = ":"): 解析响应字符串为解析结果对象

PathSD 类

常量
  • DefaultImageInputPath: 默认图像输入路径
  • DefaultImageOutputPath: 默认图像输出路径
  • DefaultImageInputDirectory: 默认图像输入目录
  • DefaultImageOutputDirectory: 默认图像输出目录
方法
  • CreateDirectoriesIfNotExist(): 创建默认目录(如果不存在)

ParseResult 类

用于解析服务器响应数据的结果类,提供类型安全的数据访问方法。

设计理念
  • 快速失败原则Get 方法在遇到问题时立即抛出异常,避免静默的错误传播
  • 类型安全:所有数据访问都是强类型的,编译时就能发现类型错误
  • 灵活性:提供两套API(Get/Try),满足不同的使用场景
属性
  • IEnumerable<string> Keys: 获取所有键名
  • int Count: 获取数据项数量
构造函数
  • ParseResult(string responseString, string pairSeparator = ",", string keyValueSeparator = ":"): 解析响应字符串
类型安全的获取方法
  • int GetInt(string key): 获取整数值,键不存在或转换失败时抛出异常
  • double GetDouble(string key): 获取双精度浮点数值,键不存在或转换失败时抛出异常
  • float GetFloat(string key): 获取单精度浮点数值,键不存在或转换失败时抛出异常
  • long GetLong(string key): 获取长整数值,键不存在或转换失败时抛出异常
  • decimal GetDecimal(string key): 获取十进制数值,键不存在或转换失败时抛出异常
  • string GetString(string key): 获取字符串值,键不存在时抛出异常
  • bool GetBool(string key): 获取布尔值,键不存在或转换失败时抛出异常
安全获取方法
  • bool TryGetInt(string key, out int value): 尝试获取整数值,成功返回true
  • bool TryGetDouble(string key, out double value): 尝试获取双精度浮点数值,成功返回true
  • bool TryGetFloat(string key, out float value): 尝试获取单精度浮点数值,成功返回true
  • bool TryGetLong(string key, out long value): 尝试获取长整数值,成功返回true
  • bool TryGetDecimal(string key, out decimal value): 尝试获取十进制数值,成功返回true
  • bool TryGetBool(string key, out bool value): 尝试获取布尔值,成功返回true
辅助方法
  • bool HasKey(string key): 检查是否包含指定键
  • bool TryGetValue(string key, out string value): 尝试获取原始字符串值
  • IEnumerable<KeyValuePair<string, string>> GetAllPairs(): 获取所有键值对
  • Dictionary<string, string> ToDictionary(): 转换为字典

📖 详细示例

高级使用示例

using VMHelper;
using VMHelper.Interfaces;

public class VisionServiceClient
{
    private readonly ITcpCommunicationService _tcpService;
    
    public VisionServiceClient()
    {
        _tcpService = new TcpCommunicationService();
        
        // 配置连接参数
        _tcpService.SetServerConnection("192.168.1.100", 7920, 5000);
        
        // 配置结束符
        _tcpService.SetTerminatorConfig("\r\n", "\r\n");
    }
    
    public async Task<bool> InitializeAsync()
    {
        // 确保目录存在
        PathSD.CreateDirectoriesIfNotExist();
        
        // 连接到服务器
        return await _tcpService.ConnectAsync();
    }
    
    public async Task<Dictionary<string, double>> GetMeasurementDataAsync()
    {
        try
        {
            // 发送测量命令
            var data = await _tcpService.SendCommandAndParseAsync("MEASURE");
            return data;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"测量数据获取失败: {ex.Message}");
            return new Dictionary<string, double>();
        }
    }
    
    public async Task<ParseResult> GetMeasurementResultAsync()
    {
        try
        {
            // 使用新的解析结果类
            var result = await _tcpService.SendCommandAndParseResultAsync("MEASURE");
            return result;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"测量数据获取失败: {ex.Message}");
            return new ParseResult(""); // 返回空解析结果
        }
    }
    
    public async Task<bool> ProcessImageAsync(string imagePath)
    {
        try
        {
            // 发送图像处理命令
            string command = $"PROCESS_IMAGE:{imagePath}";
            string response = await _tcpService.SendCommandAsync(command);
            
            return response.Contains("SUCCESS");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"图像处理失败: {ex.Message}");
            return false;
        }
    }
    
    public void Dispose()
    {
        _tcpService?.Dispose();
    }
}

错误处理示例

using VMHelper;
using VMHelper.Interfaces;

public async Task SafeCommunicationExample()
{
    ITcpCommunicationService tcpService = new TcpCommunicationService();
    
    try
    {
        // 配置连接
        tcpService.SetServerConnection("127.0.0.1", 7920, 3000);
        
        // 尝试连接
        if (!await tcpService.ConnectAsync())
        {
            Console.WriteLine("无法连接到服务器");
            return;
        }
        
        // 发送命令
        string response = await tcpService.SendCommandAsync("GET_STATUS");
        
        // 解析响应
        var data = tcpService.ParseResponse(response);
        
        if (data.Count > 0)
        {
            Console.WriteLine("解析成功:");
            foreach (var kvp in data)
            {
                Console.WriteLine($"  {kvp.Key} = {kvp.Value}");
            }
        }
        else
        {
            Console.WriteLine("没有解析到有效数据");
        }
    }
    catch (TimeoutException)
    {
        Console.WriteLine("操作超时");
    }
    catch (SocketException ex)
    {
        Console.WriteLine($"网络错误: {ex.Message}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"未知错误: {ex.Message}");
    }
    finally
    {
        tcpService.Dispose();
    }
}

ParseResult 使用示例

using VMHelper;

public async Task ParseResultExample()
{
    // 模拟服务器响应数据
    string responseData = "X:123.45,Y:67.89,Z:12.34,STATUS:OK,ENABLED:1,COUNT:10";
    
    // 创建解析结果对象
    var result = new ParseResult(responseData);
    
    try
    {
        // 类型安全地获取各种数据类型(如果键不存在或转换失败会抛出异常)
        double x = result.GetDouble("X");           // 123.45
        double y = result.GetDouble("Y");           // 67.89  
        double z = result.GetDouble("Z");           // 12.34
        string status = result.GetString("STATUS"); // "OK"
        bool enabled = result.GetBool("ENABLED");   // true (因为值为"1")
        int count = result.GetInt("COUNT");         // 10
        
        Console.WriteLine($"位置: ({x}, {y}, {z}), 状态: {status}, 启用: {enabled}, 计数: {count}");
    }
    catch (KeyNotFoundException ex)
    {
        Console.WriteLine($"缺少必要数据: {ex.Message}");
    }
    catch (FormatException ex)
    {
        Console.WriteLine($"数据格式错误: {ex.Message}");
    }
    
    // 使用Try方法处理可能不存在的键
    if (result.TryGetInt("TIMEOUT", out int timeout))
    {
        Console.WriteLine($"超时时间: {timeout}");
    }
    else
    {
        Console.WriteLine("未设置超时时间");
    }
    
    // 检查键是否存在
    if (result.HasKey("ERROR"))
    {
        string error = result.GetString("ERROR");
        Console.WriteLine($"错误信息: {error}");
    }
    
    // 遍历所有数据
    Console.WriteLine($"解析到 {result.Count} 个数据项:");
    foreach (var kvp in result.GetAllPairs())
    {
        Console.WriteLine($"  {kvp.Key} = {kvp.Value}");
    }
    
    // 使用自定义分隔符解析
    string customData = "name1=value1;name2=value2;name3=value3";
    var customResult = new ParseResult(customData, ";", "=");
    
    Console.WriteLine($"自定义分隔符解析结果: {customResult}");
}

// 在TCP通讯中使用
public async Task TcpParseResultExample()
{
    ITcpCommunicationService tcpService = new TcpCommunicationService();
    
    try
    {
        // 配置并连接
        tcpService.SetServerConnection("127.0.0.1", 7920, 3000);
        await tcpService.ConnectAsync();
        
        // 发送命令并获取解析结果
        var result = await tcpService.SendCommandAndParseResultAsync("GET_SENSOR_DATA");
        
        try
        {
            // 类型安全地获取传感器数据
            double temperature = result.GetDouble("TEMP");
            double humidity = result.GetDouble("HUMIDITY");
            bool alertActive = result.GetBool("ALERT");
            int sensorId = result.GetInt("SENSOR_ID");
            
            Console.WriteLine($"传感器 {sensorId}:");
            Console.WriteLine($"  温度: {temperature}°C");
            Console.WriteLine($"  湿度: {humidity}%");
            Console.WriteLine($"  警报状态: {(alertActive ? "激活" : "正常")}");
        }
        catch (KeyNotFoundException ex)
        {
            Console.WriteLine($"缺少必要的传感器数据: {ex.Message}");
        }
        catch (FormatException ex)
        {
            Console.WriteLine($"传感器数据格式错误: {ex.Message}");
        }
        
        // 处理可能的错误状态
        if (result.HasKey("ERROR_CODE"))
        {
            try
            {
                int errorCode = result.GetInt("ERROR_CODE");
                string errorMsg = result.TryGetValue("ERROR_MSG", out var msg) ? msg : "未知错误";
                Console.WriteLine($"错误 {errorCode}: {errorMsg}");
            }
            catch (FormatException)
            {
                Console.WriteLine("错误代码格式不正确");
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"通讯错误: {ex.Message}");
    }
    finally
    {
        tcpService.Dispose();
    }
}

🔍 响应数据格式

服务器响应数据应遵循以下格式:

name1:value1,name2:value2,name3:value3

示例:

X:123.45,Y:67.89,Z:12.34,STATUS:1

解析后的结果:

// 使用传统的字典方式
{
    "X": 123.45,
    "Y": 67.89,
    "Z": 12.34,
    "STATUS": 1.0
}

// 使用新的 ParseResult 类(推荐)
var result = new ParseResult("X:123.45,Y:67.89,Z:12.34,STATUS:OK,ENABLED:1");
try
{
    double x = result.GetDouble("X");        // 123.45
    double y = result.GetDouble("Y");        // 67.89
    double z = result.GetDouble("Z");        // 12.34
    string status = result.GetString("STATUS"); // "OK"
    bool enabled = result.GetBool("ENABLED");   // true
    
    Console.WriteLine($"位置: ({x}, {y}, {z}), 状态: {status}, 启用: {enabled}");
}
catch (KeyNotFoundException ex)
{
    Console.WriteLine($"缺少必要数据: {ex.Message}");
}
catch (FormatException ex)
{
    Console.WriteLine($"数据格式错误: {ex.Message}");
}

布尔值处理

ParseResult 类的 GetBool 方法支持多种布尔值格式:

真值(不区分大小写)

  • true
  • 1
  • yes
  • on
  • enabled

假值(不区分大小写)

  • false
  • 0
  • no
  • off
  • disabled

自定义分隔符

// 使用分号和等号作为分隔符
string data = "name1=value1;name2=value2;name3=value3";
var result = new ParseResult(data, ";", "=");

// 使用竖线和冒号作为分隔符
string data2 = "temp:25.5|humidity:60.2|status:active";
var result2 = new ParseResult(data2, "|", ":");

安全获取方法示例

using VMHelper;

public void SafeParseExample()
{
    string responseData = "X:123.45,Y:invalid,STATUS:OK,ENABLED:1";
    var result = new ParseResult(responseData);
    
    // 使用 Try 方法安全地获取数据
    if (result.TryGetDouble("X", out double x))
    {
        Console.WriteLine($"X 坐标: {x}");
    }
    
    if (result.TryGetDouble("Y", out double y))
    {
        Console.WriteLine($"Y 坐标: {y}");
    }
    else
    {
        Console.WriteLine("Y 坐标数据无效或不存在");
    }
    
    // 检查可选字段
    if (result.TryGetInt("TIMEOUT", out int timeout))
    {
        Console.WriteLine($"超时设置: {timeout}ms");
    }
    else
    {
        Console.WriteLine("使用默认超时设置");
    }
    
    // 混合使用:必须字段抛出异常,可选字段使用 Try 方法
    try
    {
        string status = result.GetString("STATUS"); // 必须存在
        bool enabled = result.GetBool("ENABLED");   // 必须存在
        
        Console.WriteLine($"状态: {status}, 启用: {enabled}");
        
        // 可选字段
        if (result.TryGetInt("ERROR_CODE", out int errorCode))
        {
            Console.WriteLine($"错误代码: {errorCode}");
        }
    }
    catch (KeyNotFoundException ex)
    {
        Console.WriteLine($"缺少必要字段: {ex.Message}");
    }
    catch (FormatException ex)
    {
        Console.WriteLine($"数据格式错误: {ex.Message}");
    }
}

⚠️ 错误处理

异常类型

ParseResult 类的 Get 方法可能抛出以下异常:

  1. KeyNotFoundException: 当请求的键不存在时抛出
  2. FormatException: 当值无法转换为请求的数据类型时抛出

错误处理策略

// 策略1:严格模式 - 所有错误都抛出异常
try
{
    int value = result.GetInt("REQUIRED_VALUE");
    // 处理成功获取的值
}
catch (KeyNotFoundException ex)
{
    Console.WriteLine($"缺少必要参数: {ex.Message}");
    // 处理缺少键的情况
}
catch (FormatException ex)
{
    Console.WriteLine($"数据格式错误: {ex.Message}");
    // 处理格式错误的情况
}

// 策略2:宽松模式 - 使用 Try 方法避免异常
if (result.TryGetInt("OPTIONAL_VALUE", out int value))
{
    // 处理成功获取的值
}
else
{
    // 使用默认值或跳过处理
    int defaultValue = 100;
    // 使用 defaultValue 继续处理
}

// 策略3:混合模式 - 必要字段抛出异常,可选字段使用 Try 方法
try
{
    // 必要字段
    string status = result.GetString("STATUS");
    
    // 可选字段
    if (result.TryGetInt("TIMEOUT", out int timeout))
    {
        Console.WriteLine($"超时设置: {timeout}");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"处理必要字段时发生错误: {ex.Message}");
}

最佳实践

  1. 必要字段使用 Get 方法:对于业务逻辑必须的字段,使用 Get 方法让异常提前暴露问题
  2. 可选字段使用 Try 方法:对于可选字段,使用 Try 方法避免异常并提供默认行为
  3. 集中错误处理:在调用层面统一处理 KeyNotFoundExceptionFormatException
  4. 详细的错误信息:异常信息包含具体的键名和值,便于调试

使用场景对比

场景 推荐方法 原因
必须存在的配置参数 Get 方法 缺少参数应该立即失败,不应该继续执行
可选的配置参数 Try 方法 允许使用默认值或跳过处理
数据验证 Get 方法 数据格式错误应该被明确识别
向后兼容性 Try 方法 新增字段可能在旧版本中不存在
批量处理 Try 方法 避免单个错误中断整个处理流程
调试模式 Get 方法 让问题快速暴露,便于调试

⚙️ 配置说明

连接参数

  • host: 服务器地址(默认:127.0.0.1)
  • port: 服务器端口(默认:7920)
  • timeoutMs: 超时时间毫秒(默认:3000)

结束符配置

  • sendTerminator: 发送数据时添加的结束符(默认:"\r")
  • receiveTerminator: 接收数据时检测的结束符(默认:"\r")
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 466 12/10/2025

优化ITcp接口类,解决清除缓存器bug