背景
随着微软.net core的出现,C#程序实现跨平台不在困难,5年内微软连续退出.net core 2、.net core 3、net5、net6,到现在net7预览版本,可以发现除了原来WinForm部分,其他大部分的功能都可以通过net6实现跨平台发布。由于其运行效率相比java高出不少,会有越来越道的服务通过net6进行开发。作为工业软件开发者的一员,借助项目需求,就把项目中用到的部分冷门的功能进行整理,进行分享,希望遇到类似问题的小伙伴们少走弯路。
项目背景
本次项目需求是研发一款数字仪表采集器,数字仪表的种类包括数字电表、蒸汽表、温湿度仪表。通讯接口主要是RS485、采集器基于辉为DTU820工控机(操作系统为linux4.x Arm7,512M内存,4G存储。带4路485接口,根据采集频率的要求不同,每路485可以串联多块数字仪表)进行开发。基于开发效率和程序性能的综合考虑决定采用c#基于net6进行开发。系统分为数据采集服务、数据上传服务、终端配置站点三个子程序。因为终端配置站点需要实现修改工控机网卡IP地址,实现从服务器同步时间到工控机等功能。
实现思路
原来在windows下通过C#修改系统时间,是通过调用Win32API实现的,很明显,这种方式的代码无法运行在linux环境下。网上一顿搜索,也没有发现相关资料。因为linux下很多操作都是通过终端命令完成,就想能不能通过C#执行linux终端命令,经过尝试,是可以的。这样很多问题就可以通过这种方式解决了。(注意修改IP地址是通过修改网卡的配置文件实现,其实就是文件操作)现对实现的关键代码进行整理分享如下。
代码实现
用到的3个实体类
DiskInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wongoing.Basic.Entity
{
/// <summary>
/// 磁盘信息实体类
/// </summary>
[Serializable]
public class DiskInfo
{
#region 字段定义
private string? _fileSystem;
private long _size;
private long _used;
private long _available;
private string? _usePercent;
private string? _mountedOn;
#endregion
#region 属性定义
/// <summary>
/// 磁盘名称/文件系统
/// </summary>
public string? FileSystem { get => _fileSystem; set => _fileSystem = value; }
/// <summary>
/// 容量大小
/// </summary>
public long Size { get => _size; set => _size = value; }
/// <summary>
/// 已用大小
/// </summary>
public long Used { get => _used; set => _used = value; }
/// <summary>
/// 可用大小
/// </summary>
public long Available { get => _available; set => _available = value; }
/// <summary>
/// 已用%
/// </summary>
public string? UsePercent { get => _usePercent; set => _usePercent = value; }
/// <summary>
/// 挂载点
/// </summary>
public string? MountedOn { get => _mountedOn; set => _mountedOn = value; }
/// <summary>
/// 容量大小(GB)
/// </summary>
public double SizeGB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._size / 1024.0 / 1024.0), out result);
return result;
}
}
/// <summary>
/// 已用大小(GB)
/// </summary>
public double UsedGB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._used / 1024.0 / 1024.0), out result);
return result;
}
}
/// <summary>
/// 可用大小(GB)
/// </summary>
public double AvailableGB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._available / 1024.0 / 1024.0), out result);
return result;
}
}
#endregion
}
}
MemoryInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wongoing.Basic.Entity
{
/// <summary>
/// 内存信息实体类
/// </summary>
[Serializable]
public class MemoryInfo
{
#region 字段定义
private long _total;
private long _used;
private long _free;
private long _shared;
private long _buffers;
private long _cached;
#endregion
#region 属性定义
/// <summary>
/// 内存总大小
/// </summary>
public long Total { get => _total; set => _total = value; }
/// <summary>
/// 已用内存大小
/// </summary>
public long Used { get => _used; set => _used = value; }
/// <summary>
/// 可用内存大小
/// </summary>
public long Free { get => _free; set => _free = value; }
/// <summary>
/// 共享内存大小
/// </summary>
public long Shared { get => _shared; set => _shared = value; }
/// <summary>
/// Buffer缓冲大小
/// </summary>
public long Buffers { get => _buffers; set => _buffers = value; }
/// <summary>
/// Cache缓存大小
/// </summary>
public long Cached { get => _cached; set => _cached = value; }
/// <summary>
/// 内存总大小
/// </summary>
public double TotalMB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._total / 1024.0), out result);
return result;
}
}
/// <summary>
/// 已用内存大小
/// </summary>
public double UsedMB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._used / 1024.0), out result);
return result;
}
}
/// <summary>
/// 可用内存大小
/// </summary>
public double FreeMB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._free / 1024.0), out result);
return result;
}
}
/// <summary>
/// 共享内存大小
/// </summary>
public double SharedMB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._shared / 1024.0), out result);
return result;
}
}
/// <summary>
/// Buffer缓冲大小
/// </summary>
public double BuffersMB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._buffers / 1024.0), out result);
return result;
}
}
/// <summary>
/// Cache缓存大小
/// </summary>
public double CachedMB
{
get
{
double result = 0.0;
double.TryParse(String.Format("{0:F2}", this._cached / 1024.0), out result);
return result;
}
}
#endregion
}
}
NetworkInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wongoing.Basic.Entity
{
/// <summary>
/// 网络信息实体类
/// </summary>
[Serializable]
public class NetworkInfo
{
#region 字段定义
private string _networkCard = "eth0";
private string _address = "192.168.0.15";
private string _netmask = "255.255.255.0";
private string _gateway = "192.168.0.1";
#endregion
#region 属性定义
/// <summary>
/// 网卡(eth0、eth1)
/// </summary>
public string NetworkCard { get => _networkCard; set => _networkCard = value; }
/// <summary>
/// IP地址
/// </summary>
public string Address { get => _address; set => _address = value; }
/// <summary>
/// 子网掩码
/// </summary>
public string Netmask { get => _netmask; set => _netmask = value; }
/// <summary>
/// 网关
/// </summary>
public string Gateway { get => _gateway; set => _gateway = value; }
#endregion
}
}
C#调用 Linux终端命令的关键代码如下:
LinuxCommandHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Wongoing.Basic.Linux
{
/// <summary>
/// Linux命令工具类
/// </summary>
public class LinuxCommandHelper
{
private static string FileName { get; set; }
private static string Arguments { get; set; }
private static string WorkingDirectory { get; set; }
/// <summary>
/// 运行命令
/// </summary>
/// <param name="fileName"></param>
/// <param name="arguments"></param>
/// <param name="workingDirectory"></param>
/// <returns></returns>
public static string RunCommand(string fileName, string arguments, string workingDirectory = "")
{
FileName = fileName;
Arguments = arguments;
WorkingDirectory = workingDirectory;
return Execute();
}
private static string Execute(int milliseconds = 10000)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var rValue = "";
using (System.Diagnostics.Process p = new System.Diagnostics.Process())
{
p.StartInfo.FileName = FileName;
p.StartInfo.Arguments = Arguments;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.WorkingDirectory = WorkingDirectory;
p.Start();
p.WaitForExit(milliseconds);
rValue = p.StandardOutput.ReadToEnd();
}
return rValue;
}
else
{
return "当前系统不是Linux系统,不能通过此类执行Linux命令!";
}
}
}
}
各种操作的封装类LinuxOSHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wongoing.Basic.Linux
{
using Wongoing.Basic.Entity;
/// <summary>
/// linux操作系统基本操作辅助类
/// </summary>
public class LinuxOSHelper
{
#region 定义常量
public static readonly string InterfaceTemplate = @"# interface file auto-generated by buildroot
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address ${eth0_address}
netmask ${eth0_netmask}
gateway ${eth0_gateway}
auto eth1
iface eth1 inet static
address ${eth1_address}
netmask ${eth1_netmask}
gateway ${eth1_gateway}
";
#endregion
#region 获取当前系统内核
/// <summary>
/// 获取当前系统内核
/// </summary>
/// <returns>返回系统内核信息</returns>
public static string GetSystemCore()
{
string result = LinuxCommandHelper.RunCommand("uname", "-a");
return result;
}
#endregion
#region 获取系统内存信息
/// <summary>
/// 获取系统内存信息
/// </summary>
/// <returns>返回系统内存信息</returns>
public static MemoryInfo GetSystemMemoryInfo()
{
string result = LinuxCommandHelper.RunCommand("free", "");
string? line = String.Empty;
List<string> valueList = new List<string>();
using (StringReader reader = new StringReader(result))
{
while (!String.IsNullOrEmpty((line = reader.ReadLine())))
{
if (line.StartsWith("Mem:"))
{
string[] split = line.Split(' ');
foreach (string value in split)
{
if (!String.IsNullOrEmpty(value) && value != "Mem:")
{
valueList.Add(value);
}
}
break;
}
}
}
MemoryInfo memoryInfo = new MemoryInfo();
long val = 0;
for (int i = 0; i < valueList.Count; i++)
{
if (i == 0)
{
val = 0;
long.TryParse(valueList[i], out val);
memoryInfo.Total = val;
}
if (i == 1)
{
val = 0;
long.TryParse(valueList[i], out val);
memoryInfo.Used = val;
}
if (i == 2)
{
val = 0;
long.TryParse(valueList[i], out val);
memoryInfo.Free = val;
}
if (i == 3)
{
val = 0;
long.TryParse(valueList[i], out val);
memoryInfo.Shared = val;
}
if (i == 4)
{
val = 0;
long.TryParse(valueList[i], out val);
memoryInfo.Buffers = val;
}
if (i == 5)
{
val = 0;
long.TryParse(valueList[i], out val);
memoryInfo.Cached = val;
}
}
return memoryInfo;
}
#endregion
#region 获取系统磁盘信息
/// <summary>
/// 获取系统磁盘信息
/// </summary>
/// <returns>返回各分区磁盘信息</returns>
public static List<DiskInfo> GetSystemDisckInfo()
{
string result = LinuxCommandHelper.RunCommand("df", "");
string? line = String.Empty;
List<DiskInfo> resultLlist = new List<DiskInfo>();
DiskInfo? diskInfo = null;
using (StringReader reader = new StringReader(result))
{
while (!String.IsNullOrEmpty((line = reader.ReadLine())))
{
if (line.StartsWith("/dev"))
{
#region 处理已/dev开头的信息
List<string> valueList = new List<string>();
string[] split = line.Split(' ');
foreach (string value in split)
{
if (!String.IsNullOrEmpty(value))
{
valueList.Add(value);
}
}
long val = 0;
diskInfo = new DiskInfo();
for (int i = 0; i < valueList.Count; i++)
{
if (i == 0)
{
diskInfo.FileSystem = valueList[i];
}
if (i == 1)
{
val = 0;
long.TryParse(valueList[i], out val);
diskInfo.Size = val;
}
if (i == 2)
{
val = 0;
long.TryParse(valueList[i], out val);
diskInfo.Used = val;
}
if (i == 3)
{
val = 0;
long.TryParse(valueList[i], out val);
diskInfo.Available = val;
}
if (i == 4)
{
diskInfo.UsePercent = valueList[i];
}
if (i == 5)
{
diskInfo.MountedOn = valueList[i];
}
}
resultLlist.Add(diskInfo);
#endregion
}
}
}
return resultLlist;
}
#endregion
#region 生成网络配置文件
/// <summary>
/// 生成网络配置文件
/// </summary>
/// <param name="eth0">网卡1信息</param>
/// <param name="eth1">网卡2信息</param>
/// <returns>返回网络配置文件内容</returns>
public static string BuildNetworkFileContent(NetworkInfo eth0, NetworkInfo eth1)
{
string networkFileContent = InterfaceTemplate;
if (eth0 == null)
{
eth0 = new NetworkInfo() { NetworkCard = "eth0", Address = "192.168.0.15", Netmask = "255.255.255.0", Gateway = "192.168.0.1" };
}
if (eth1 == null)
{
eth1 = new NetworkInfo() { NetworkCard = "eth1", Address = "10.10.80.15", Netmask = "255.255.255.0", Gateway = "10.10.80.1" };
}
Type type = typeof(LinuxOSHelper);
string interfaceTemplateFilePath = String.Format("{0}.{1}", type.Assembly.GetName().Name, "Resource.interfaces.template.txt");
System.IO.Stream? stream = type.Assembly.GetManifestResourceStream(interfaceTemplateFilePath);
if (stream != null)
{
System.IO.StreamReader streamReader = new System.IO.StreamReader(stream);
networkFileContent = streamReader.ReadToEnd();
}
networkFileContent = networkFileContent.Replace("${eth0_address}", eth0.Address);
networkFileContent = networkFileContent.Replace("${eth0_netmask}", eth0.Netmask);
networkFileContent = networkFileContent.Replace("${eth0_gateway}", eth0.Gateway);
networkFileContent = networkFileContent.Replace("${eth1_address}", eth1.Address);
networkFileContent = networkFileContent.Replace("${eth1_netmask}", eth1.Netmask);
networkFileContent = networkFileContent.Replace("${eth1_gateway}", eth1.Gateway);
networkFileContent = networkFileContent.Replace("\r", String.Empty);
networkFileContent = networkFileContent.Replace(" ", String.Empty);
return networkFileContent;
}
#endregion
#region 获取系统本地时间
/// <summary>
/// 获取系统本地时间
/// </summary>
/// <returns>返回系统本地时间</returns>
public static string GetLocaleDateTime()
{
string result = LinuxCommandHelper.RunCommand("date", "");
return result;
}
#endregion
#region 设置系统本地时间
/// <summary>
/// 设置系统本地时间
/// </summary>
/// <param name="datetimestr">要设置的时间</param>
/// <returns>返回设置的时间</returns>
public static string SetLocaleDateTime(string datetimestr)
{
string result = LinuxCommandHelper.RunCommand("date", $"-s \"{datetimestr}\"");
LinuxCommandHelper.RunCommand("hwclock", "-w");
return result;
}
#endregion
#region 系统立即重启
/// <summary>
/// 系统立即重启
/// </summary>
/// <returns>返回执行结果</returns>
public static string RebootNow()
{
string result = LinuxCommandHelper.RunCommand("reboot", "now");
return result;
}
#endregion
#region 系统重启
/// <summary>
/// 系统重启
/// </summary>
/// <returns>返回执行结果</returns>
public static string Reboot()
{
string result = LinuxCommandHelper.RunCommand("reboot", "");
return result;
}
#endregion
}
}
OSHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Wongoing.Basic
{
using System.Net.NetworkInformation;
using Wongoing.Basic.Entity;
using Wongoing.Basic.Linux;
using Wongoing.Basic.Win;
/// <summary>
/// 操作系统辅助类
/// </summary>
public class OSHelper
{
#region 获取当前系统内核
/// <summary>
/// 获取当前系统内核
/// </summary>
/// <returns>返回系统内核信息</returns>
public static string GetSystemCore()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return LinuxOSHelper.GetSystemCore();
}
else
{
return Environment.OSVersion.ToString();
}
}
#endregion
#region 获取系统内存信息
/// <summary>
/// 获取系统内存信息
/// </summary>
/// <returns>返回系统内存信息</returns>
public static MemoryInfo? GetSystemMemoryInfo()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return LinuxOSHelper.GetSystemMemoryInfo();
}
else
{
return null;
}
}
#endregion
#region 获取系统磁盘信息
/// <summary>
/// 获取系统磁盘信息
/// </summary>
/// <returns>返回各分区磁盘信息</returns>
public static List<DiskInfo> GetSystemDisckInfo()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return LinuxOSHelper.GetSystemDisckInfo();
}
else
{
return new List<DiskInfo>();
}
}
#endregion
#region 生成网络配置文件
/// <summary>
/// 生成网络配置文件
/// </summary>
/// <param name="eth0">网卡1信息</param>
/// <param name="eth1">网卡2信息</param>
/// <returns>返回网络配置文件内容</returns>
public static bool BuildNetworkFileContent(NetworkInfo eth0, NetworkInfo eth1, string fileFullPath)
{
try
{
string? path = System.IO.Path.GetDirectoryName(fileFullPath.Contains(".") ? fileFullPath : String.Format("{0}.txt", fileFullPath));
if (path == null)
{
path = "/etc/network";
}
if (!System.IO.Directory.Exists(path))
{
System.IO.Directory.CreateDirectory(path);
}
string networkFileContent = LinuxOSHelper.BuildNetworkFileContent(eth0, eth1);
using(System.IO.StreamWriter sw = new System.IO.StreamWriter(fileFullPath))
{
sw.Write(networkFileContent);
sw.Flush();
sw.Close();
}
return true;
}
catch (Exception ex)
{
return false;
}
}
#endregion
#region 获取系统本地时间
/// <summary>
/// 获取系统本地时间
/// </summary>
/// <returns>返回系统本地时间</returns>
public static string GetLocaleDateTime()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return LinuxOSHelper.GetLocaleDateTime();
}
else
{
return String.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.Now);
}
}
#endregion
#region 设置系统本地时间
/// <summary>
/// 设置系统本地时间
/// </summary>
/// <param name="datetimestr">要设置的时间</param>
/// <returns>成功则返回生效的时间,失败返回String.Empty</returns>
public static string SetLocaleDateTime(string datetimestr)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return LinuxOSHelper.SetLocaleDateTime(datetimestr);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return WinOSHelper.SetLocaleDateTime(datetimestr);
}
else
{
return String.Empty;
}
}
#endregion
#region 检测某个IP或域名是否可以Ping通
/// <summary>
/// 检测某个IP或域名是否可以Ping通
/// </summary>
/// <param name="strIpOrDName">IP或域名</param>
/// <returns>真为通,假为不同</returns>
public static bool Ping(string strIpOrDName)
{
try
{
Ping objPingSender = new Ping();
PingOptions objPinOptions = new PingOptions();
objPinOptions.DontFragment = true;
string data = "";
byte[] buffer = Encoding.UTF8.GetBytes(data);
int intTimeout = 120;
PingReply objPinReply = objPingSender.Send(strIpOrDName, intTimeout, buffer, objPinOptions);
string strInfo = objPinReply.Status.ToString();
if (strInfo == "Success")
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
return false;
}
}
#endregion
#region 获取IpV4地址列表
/// <summary>
/// 获取IpV4地址列表
/// </summary>
/// <returns>返回IpV4地址列表</returns>
public static List<string> GetIpV4AddressList()
{
List<string> list = new List<string>();
string HostName = Dns.GetHostName();
IPHostEntry ipEntry = Dns.GetHostEntry(HostName);
for (int i = 0; i < ipEntry.AddressList.Length; i++)
{
//从IP地址列表中筛选出IPv4类型的IP地址
//AddressFamily.InterNetwork表示此IP为IPv4,
//AddressFamily.InterNetworkV6表示此地址为IPv6类型
if (ipEntry.AddressList[i].AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
list.Add(ipEntry.AddressList[i].ToString());
}
}
return list;
}
#endregion
#region 更改IP地址
#endregion
#region 系统立即重启
/// <summary>
/// 系统立即重启
/// </summary>
/// <returns>返回执行结果</returns>
public static string RebootNow()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return LinuxOSHelper.RebootNow();
}
else
{
return "暂不支持其他操作系统!";
}
}
#endregion
#region 系统重启
/// <summary>
/// 系统重启
/// </summary>
/// <returns>返回执行结果</returns>
public static string Reboot()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return LinuxOSHelper.Reboot();
}
else
{
return "暂不支持其他操作系统!";
}
}
#endregion
}
}