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" />
<PackageReference Include="VMHelper" />
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
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#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
#tool nuget:?package=VMHelper&version=2.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
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): 尝试获取整数值,成功返回truebool TryGetDouble(string key, out double value): 尝试获取双精度浮点数值,成功返回truebool TryGetFloat(string key, out float value): 尝试获取单精度浮点数值,成功返回truebool TryGetLong(string key, out long value): 尝试获取长整数值,成功返回truebool TryGetDecimal(string key, out decimal value): 尝试获取十进制数值,成功返回truebool 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 方法支持多种布尔值格式:
真值(不区分大小写):
true1yesonenabled
假值(不区分大小写):
false0nooffdisabled
自定义分隔符
// 使用分号和等号作为分隔符
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 方法可能抛出以下异常:
- KeyNotFoundException: 当请求的键不存在时抛出
- 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}");
}
最佳实践
- 必要字段使用 Get 方法:对于业务逻辑必须的字段,使用
Get方法让异常提前暴露问题 - 可选字段使用 Try 方法:对于可选字段,使用
Try方法避免异常并提供默认行为 - 集中错误处理:在调用层面统一处理
KeyNotFoundException和FormatException - 详细的错误信息:异常信息包含具体的键名和值,便于调试
使用场景对比
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 必须存在的配置参数 | Get 方法 |
缺少参数应该立即失败,不应该继续执行 |
| 可选的配置参数 | Try 方法 |
允许使用默认值或跳过处理 |
| 数据验证 | Get 方法 |
数据格式错误应该被明确识别 |
| 向后兼容性 | Try 方法 |
新增字段可能在旧版本中不存在 |
| 批量处理 | Try 方法 |
避免单个错误中断整个处理流程 |
| 调试模式 | Get 方法 |
让问题快速暴露,便于调试 |
⚙️ 配置说明
连接参数
- host: 服务器地址(默认:127.0.0.1)
- port: 服务器端口(默认:7920)
- timeoutMs: 超时时间毫秒(默认:3000)
结束符配置
- sendTerminator: 发送数据时添加的结束符(默认:"\r")
- receiveTerminator: 接收数据时检测的结束符(默认:"\r")
| Product | Versions 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.
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.6)
- System.Drawing.Common (>= 9.0.6)
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