因工控软件开发中需要保存大量设备数据,因此通过在网上查阅发现时序数据库InfluxDb用的还是很多的,InfluxDb集群版是商业软件,且在工控机上也不太适合用集群版本。单机版InfluxDb是基于命令的,一直有个黑窗口,现场操作工不小心很容易把它关掉,因此我就把InfluxDb封装成一个Windows服务,隐藏掉黑窗口,并随开机自启动。
InfluxDb版本:influxdb-1.4.3-1
开发语言:C#
Windows宿主框架:TopShelf
任务调度框架:Quartz
日志记录框架:Log4net
关键代码就是如何隐藏黑窗口打开Influxd.exe进程,如下
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace Mesnac.Basic
{
///
/// windows命令辅助类
///
public class WinCmdHelper
{
#region 执行Dos命令
///
/// 执行Dos命令
///
/// Dos命令及参数
/// 是否显示cmd窗口
/// 执行完毕后是否关闭cmd进程
/// 成功返回true,失败返回false
public static bool ExcuteDosCommand(string cmd, bool isShowCmdWindow, bool isCloseCmdProcess)
{
try
{
Process p = new Process();
p.StartInfo.FileName = "cmd";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = !isShowCmdWindow;
p.OutputDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) {
if (!String.IsNullOrEmpty(e.Data))
{
Mesnac.Log.LogService<WinCmdHelper>.Instance.Debug(e.Data);
}
});
p.Start();
StreamWriter cmdWriter = p.StandardInput;
p.BeginOutputReadLine();
if (!String.IsNullOrEmpty(cmd))
{
cmdWriter.WriteLine(cmd);
}
cmdWriter.Close();
p.WaitForExit();
if (isCloseCmdProcess)
{
p.Close();
}
Mesnac.Log.LogService<WinCmdHelper>.Instance.Info(String.Format("成功执行Dos命令[{0}]!", cmd));
return true;
}
catch (Exception ex)
{
Mesnac.Log.LogService<WinCmdHelper>.Instance.Error("执行命令失败,请检查输入的命令是否正确:" + ex.Message, ex);
return false;
}
}
#endregion
#region 判断指定的进程是否在运行中
///
/// 判断指定的进程是否在运行中
///
/// 要判断的进程名称,不包括扩展名exe
/// 进程文件的完整路径
/// 存在返回true,否则返回false
public static bool CheckProcessExists(string processName, string processFileName)
{
Process[] processes = Process.GetProcessesByName(processName);
foreach (Process p in processes)
{
if (!String.IsNullOrEmpty(processFileName))
{
if (processFileName == p.MainModule.FileName)
{
return true;
}
}
else
{
return true;
}
}
return false;
}
#endregion
#region 结束指定的windows进程
///
/// 结束指定的windows进程,如果进程存在
///
/// 进程名称,不包含扩展名
/// 进程文件完整路径,如果为空则删除所有进程名为processName参数值的进程
public static bool KillProcessExists(string processName, string processFileName)
{
try
{
Process[] processes = Process.GetProcessesByName(processName);
foreach (Process p in processes)
{
if (!String.IsNullOrEmpty(processFileName))
{
if (processFileName == p.MainModule.FileName)
{
p.Kill();
p.Close();
}
}
else
{
p.Kill();
p.Close();
}
}
Mesnac.Log.LogService<WinCmdHelper>.Instance.Info(String.Format("成功结束[{0}]进程!", processes));
return true;
}
catch(Exception ex)
{
Mesnac.Log.LogService<WinCmdHelper>.Instance.Error("结束指定的Widnows进程异常:" + ex.Message, ex);
return false;
}
}
#endregion
}
}
读取App.config的辅助类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace Mesnac.Basic
{
///
/// 应用程序配置文件访问辅助类
///
public class AppConfigHelper
{
///
/// 读取string配置项
///
/// AppSetting配置项的Key值
/// 默认值
/// 返回对应配置项的string类型value值
public static string GetAppSettingValue(string appSettingKey, string defaultValue)
{
string result = ConfigurationManager.AppSettings[appSettingKey];
if (String.IsNullOrEmpty(result))
{
return defaultValue;
}
return result;
}
///
/// 读取bool配置项
///
/// AppSetting配置项的Key值
/// 默认值
/// 返回对应配置项的bool类型value值
public static bool GetAppSettingValue(string appSettingKey, bool defaultValue)
{
string result = ConfigurationManager.AppSettings[appSettingKey];
if (String.IsNullOrEmpty(result))
{
return defaultValue;
}
bool boolResult = false;
if (bool.TryParse(result, out boolResult))
{
return boolResult;
}
else
{
return defaultValue;
}
}
///
/// 读取int配置项
///
/// AppSetting配置项的Key值
/// 默认值
/// 返回对应配置项的int类型value值
public static int GetAppSettingValue(string appSettingKey, int defaultValue)
{
string result = ConfigurationManager.AppSettings[appSettingKey];
if (String.IsNullOrEmpty(result))
{
return defaultValue;
}
int intResult = 0;
if (int.TryParse(result, out intResult))
{
return intResult;
}
else
{
return defaultValue;
}
}
///
/// 读取double类型配置项
///
/// AppSetting配置项的Key值
/// 默认值
/// 返回对应配置项的double类型value值
public static double GetAppSettingValue(string appSettingKey, double defaultValue)
{
string result = ConfigurationManager.AppSettings[appSettingKey];
if (String.IsNullOrEmpty(result))
{
return defaultValue;
}
double doubleResult = 0.0;
if (double.TryParse(result, out doubleResult))
{
return doubleResult;
}
else
{
return defaultValue;
}
}
}
}
日志框架封装代码
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.IO;
using log4net;
using log4net.Config;
namespace Mesnac.Log
{
///
/// 日志服务类,封装Log4Net
///
public sealed class LogService<T>
{
#region 单例实现
private static LogService<T> _instance = null;
private ILog log;
private LogService()
{
XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
log = LogManager.GetLogger(typeof(T));
}
public static LogService<T> Instance
{
get
{
if (_instance == null)
{
_instance = new LogService<T>();
}
return _instance;
}
}
#endregion
#region 封装log4net的ILog方法
public void Debug(object message)
{
log.Debug(message);
}
public void DebugFormatted(string format, params object[] args)
{
log.DebugFormat(CultureInfo.InvariantCulture, format, args);
}
public void Info(object message)
{
log.Info(message);
}
public void InfoFormatted(string format, params object[] args)
{
log.InfoFormat(CultureInfo.InvariantCulture, format, args);
}
public void Warn(object message)
{
log.Warn(message);
}
public void Warn(object message, Exception exception)
{
log.Warn(message, exception);
}
public void WarnFormatted(string format, params object[] args)
{
log.WarnFormat(CultureInfo.InvariantCulture, format, args);
}
public void Error(object message)
{
log.Error(message);
}
public void Error(object message, Exception exception)
{
log.Error(message, exception);
}
public void ErrorFormatted(string format, params object[] args)
{
log.ErrorFormat(CultureInfo.InvariantCulture, format, args);
}
public void Fatal(object message)
{
log.Fatal(message);
}
public void Fatal(object message, Exception exception)
{
log.Fatal(message, exception);
}
public void FatalFormatted(string format, params object[] args)
{
log.FatalFormat(CultureInfo.InvariantCulture, format, args);
}
public bool IsDebugEnabled
{
get
{
return log.IsDebugEnabled;
}
}
public bool IsInfoEnabled
{
get
{
return log.IsInfoEnabled;
}
}
public bool IsWarnEnabled
{
get
{
return log.IsWarnEnabled;
}
}
public bool IsErrorEnabled
{
get
{
return log.IsErrorEnabled;
}
}
public bool IsFatalEnabled
{
get
{
return log.IsFatalEnabled;
}
}
#endregion
#region 删除日志文件
///
/// 删除日志
///
public void DeleteLogFile()
{
try
{
int maxSize = 90; //默认保留90天的日志
if (!String.IsNullOrEmpty(System.Configuration.ConfigurationManager.AppSettings["LogSaveDays"]))
{
string strMaxSize = System.Configuration.ConfigurationManager.AppSettings["LogSaveDays"];
int.TryParse(strMaxSize, out maxSize);
}
string logFilePath = String.Empty; //定义日志文件目录变量
log4net.Appender.IAppender[] appenders = this.log.Logger.Repository.GetAppenders();
foreach(log4net.Appender.IAppender appender in appenders)
{
if (appender.Name == "FileAppender" && appender.GetType().Name == "RollingFileAppender")
{
log4net.Appender.RollingFileAppender rollingFileAppender = appender as log4net.Appender.RollingFileAppender;
logFilePath = Path.GetDirectoryName(rollingFileAppender.File); //获取日志文件目录
break;
}
}
if (!String.IsNullOrEmpty(logFilePath))
{
//logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logFilePath);
if (Directory.Exists(logFilePath))
{
DirectoryInfo dir = new DirectoryInfo(logFilePath);
FileInfo[] logFiles = dir.GetFiles();
if (logFiles != null && logFiles.Length > maxSize)
{
Dictionary<string, FileInfo> dicFiles = new Dictionary<string, FileInfo>();
List<string> lstFileNames = new List<string>();
foreach (FileInfo fi in logFiles)
{
dicFiles.Add(fi.FullName, fi);
lstFileNames.Add(fi.FullName);
}
lstFileNames.Sort();
for (int i = 0; i < lstFileNames.Count - maxSize; i++)
{
dicFiles[lstFileNames[i]].Delete();
}
}
}
}
}
catch (Exception ex)
{
this.Error("删除日志文件发生错误:" + ex.Message);
}
}
#endregion
}
}
写一个启动influxd.exe的任务类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Mesnac.Log;
using Quartz;
namespace Mesnac.Jobs
{
public class StartInfluxDbServerJob : IJob
{
private string influxDbPath = @"D:\influxdb-1.4.3-1";
public void Execute(IJobExecutionContext context)
{
if (!Mesnac.Basic.WinCmdHelper.CheckProcessExists("influxd", String.Empty))
{
//System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(this.DoStart));
//t.Start();
this.DoStart();
}
}
public void DoStart()
{
string cmd = String.Format("{0} -config {1}", System.IO.Path.Combine(this.influxDbPath, "influxd.exe"), System.IO.Path.Combine(this.influxDbPath, "influxdb.conf"));
bool result = Mesnac.Basic.WinCmdHelper.ExcuteDosCommand(cmd, false, false);
if (result)
{
LogService<StartInfluxDbServerJob>.Instance.Info("The InfluxDb Server start success!");
}
else
{
LogService<StartInfluxDbServerJob>.Instance.Error("The InfluxDb Server Start failure!");
}
}
}
}
写任务调度类
using System;
using System.Collections.Generic;
using System.Text;
using Mesnac.Log;
using Topshelf;
using Quartz;
using Quartz.Impl;
namespace InfluxDbService
{
public sealed class ServiceRunner : ServiceControl, ServiceSuspend
{
private readonly IScheduler scheduler; //定义任务调度器
///
/// 构造方法
///
public ServiceRunner()
{
scheduler = StdSchedulerFactory.GetDefaultScheduler(); //从任务调度工厂获取默认的任务调度器
}
///
/// 服务启动
///
///
///
public bool Start(HostControl hostControl)
{
try
{
this.scheduler.Start();
LogService<ServiceRunner>.Instance.Info("已启动InfluxDbServer!");
return true;
}
catch (Exception ex)
{
LogService<ServiceRunner>.Instance.Error("服务启动失败:" + ex.Message);
return false;
}
}
///
/// 服务继续
///
///
///
public bool Continue(HostControl hostControl)
{
try
{
this.scheduler.ResumeAll();
return true;
}
catch (Exception ex)
{
LogService<ServiceRunner>.Instance.Error("服务继续失败:" + ex.Message);
return false;
}
}
///
/// 服务暂停
///
///
///
public bool Pause(HostControl hostControl)
{
try
{
this.scheduler.PauseAll();
return true;
}
catch (Exception ex)
{
LogService<ServiceRunner>.Instance.Error("服务暂停失败:" + ex.Message);
return false;
}
}
///
/// 服务停止
///
///
///
public bool Stop(HostControl hostControl)
{
try
{
bool result = Mesnac.Basic.WinCmdHelper.KillProcessExists("influxd", String.Empty);
if (result)
{
LogService<ServiceRunner>.Instance.Info("已终止InfluxDbServer!");
}
this.scheduler.Shutdown(false);
return true;
}
catch (Exception ex)
{
LogService<ServiceRunner>.Instance.Error("服务停止失败:" + ex.Message);
return false;
}
}
}
}
程序入口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Topshelf;
using Topshelf.HostConfigurators;
namespace InfluxDbService
{
class Program
{
static void Main(string[] args)
{
Global.Init();
Action<HostConfigurator> d = new Action<HostConfigurator>(Global.ActionMethod);
HostFactory.Run(d);
}
}
}
完整代码下载:时序数据库InfluxDb服务化完整代码