代码之家  ›  专栏  ›  技术社区  ›  Jonathan C Dickinson

我是服务生吗

  •  42
  • Jonathan C Dickinson  · 技术社区  · 16 年前

    我目前正在为一个可以在控制台中运行的服务编写一些引导代码。本质上,它归结为调用onStart()方法,而不是使用ServiceBase来启动和停止服务(因为如果应用程序未安装为服务,它不会运行应用程序,并且会使调试成为一场噩梦)。

    现在我正在使用debugger.isattached来确定是否应该使用servicebase.run或[service].onstart,但我知道这不是最佳的主意,因为有时最终用户希望在控制台中运行该服务(以实时查看输出等)。

    关于如何确定Windows服务控制器是否启动了“我”,或者用户是否在控制台中启动了“我”,有什么想法吗?明显地 Environment.IsUserInteractive 不是答案。我考虑过使用命令行参数,但这似乎是“脏的”。

    我总是能看到关于servicebase.run的try-catch语句,但这看起来很糟糕。编辑:尝试捕获不起作用。

    我有一个解决方案:把它放在这里,用于所有其他感兴趣的堆垛机:

        public void Run()
        {
            if (Debugger.IsAttached || Environment.GetCommandLineArgs().Contains<string>("-console"))
            {
                RunAllServices();
            }
            else
            {
                try
                {
                    string temp = Console.Title;
                    ServiceBase.Run((ServiceBase[])ComponentsToRun);
                }
                catch
                {
                    RunAllServices();
                }
            }
        } // void Run
    
        private void RunAllServices()
        {
            foreach (ConsoleService component in ComponentsToRun)
            {
                component.Start();
            }
            WaitForCTRLC();
            foreach (ConsoleService component in ComponentsToRun)
            {
                component.Stop();
            }
        }
    

    编辑:StackOverflow上还有一个问题,那家伙在这里遇到了环境问题。当前目录是“C:\windows\system32”,看起来可能就是答案了。我今天要考试。

    12 回复  |  直到 6 年前
        1
  •  15
  •   Manfred Radlwimmer Michael Canan    6 年前

    和ash一样,我在一个单独的类库程序集中编写所有实际的处理代码,然后由Windows服务可执行文件和控制台应用程序引用。

    但是,有时了解类库是否在服务可执行文件或控制台应用程序的上下文中运行是很有用的。我这样做的方式是考虑托管应用程序的基类。(对vb感到抱歉,但我认为以下内容很容易被C化):

    Public Class ExecutionContext
        ''' <summary>
        ''' Gets a value indicating whether the application is a windows service.
        ''' </summary>
        ''' <value>
        ''' <c>true</c> if this instance is service; otherwise, <c>false</c>.
        ''' </value>
        Public Shared ReadOnly Property IsService() As Boolean
            Get
                ' Determining whether or not the host application is a service is
                ' an expensive operation (it uses reflection), so we cache the
                ' result of the first call to this method so that we don't have to
                ' recalculate it every call.
    
                ' If we have not already determined whether or not the application
                ' is running as a service...
                If IsNothing(_isService) Then
    
                    ' Get details of the host assembly.
                    Dim entryAssembly As Reflection.Assembly = Reflection.Assembly.GetEntryAssembly
    
                    ' Get the method that was called to enter the host assembly.
                    Dim entryPoint As System.Reflection.MethodInfo = entryAssembly.EntryPoint
    
                    ' If the base type of the host assembly inherits from the
                    ' "ServiceBase" class, it must be a windows service. We store
                    ' the result ready for the next caller of this method.
                    _isService = (entryPoint.ReflectedType.BaseType.FullName = "System.ServiceProcess.ServiceBase")
    
                End If
    
                ' Return the cached result.
                Return CBool(_isService)
            End Get
        End Property
    
        Private Shared _isService As Nullable(Of Boolean) = Nothing
    #End Region
    End Class
    
        2
  •  25
  •   rnr_never_dies    14 年前

    另一个解决方案……因此可以作为WinForm或Windows服务运行

    var backend = new Backend();
    
    if (Environment.UserInteractive)
    {
         backend.OnStart();
         Application.EnableVisualStyles();
         Application.SetCompatibleTextRenderingDefault(false);
         Application.Run(new Fronend(backend));
         backend.OnStop();
    }
    else
    {
         var ServicesToRun = new ServiceBase[] {backend};
         ServiceBase.Run(ServicesToRun);
    }
    
        3
  •  20
  •   Sean    16 年前

    我通常将Windows服务标记为一个控制台应用程序,它使用命令行参数“-console”作为控制台运行,否则它作为服务运行。要调试,只需将项目选项中的命令行参数设置为“-console”,就可以了!

    这使得调试变得简单易行,意味着应用程序在默认情况下作为一个服务运行,这就是您想要的。

        4
  •  14
  •   JoeBrockhaus    16 年前

    什么对我有效:

    • 执行实际服务工作的类在单独的线程中运行。
    • 此线程从onStart()方法中启动,从onStop()停止。
    • 服务和控制台模式之间的决定取决于 Environment.UserInteractive

    样例代码:

    class MyService : ServiceBase
    {
        private static void Main()
        {
            if (Environment.UserInteractive)
            {
                startWorkerThread();
                Console.WriteLine ("======  Press ENTER to stop threads  ======");
                Console.ReadLine();
                stopWorkerThread() ;
                Console.WriteLine ("======  Press ENTER to quit  ======");
                Console.ReadLine();
            }
            else
            {
                Run (this) ;
            }
        }
    
        protected override void OnStart(string[] args)
        {
            startWorkerThread();
        }
    
        protected override void OnStop()
        {
            stopWorkerThread() ;
        }
    }
    
        5
  •  9
  •   Ash    16 年前

    乔纳森,不完全是您问题的答案,但我刚刚完成了Windows服务的编写,还注意到了调试和测试的困难。

    通过简单地在一个单独的类库程序集中编写所有实际的处理代码来解决这个问题,该程序随后被Windows服务可执行文件以及控制台应用程序和测试工具引用。

    除了基本的计时器逻辑之外,所有更复杂的处理都发生在公共组件中,并且可以非常容易地根据需要进行测试/运行。

        6
  •  9
  •   Rolf Kristensen Raúl Diego    11 年前

    我已经修改了projectinstaller,以便在将其安装为服务时附加命令行参数/service:

    static class Program
    {
        static void Main(string[] args)
        {
            if (Array.Exists(args, delegate(string arg) { return arg == "/install"; }))
            {
                System.Configuration.Install.TransactedInstaller ti = null;
                ti = new System.Configuration.Install.TransactedInstaller();
                ti.Installers.Add(new ProjectInstaller());
                ti.Context = new System.Configuration.Install.InstallContext("", null);
                string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
                ti.Context.Parameters["assemblypath"] = path;
                ti.Install(new System.Collections.Hashtable());
                return;
            }
    
            if (Array.Exists(args, delegate(string arg) { return arg == "/uninstall"; }))
            {
                System.Configuration.Install.TransactedInstaller ti = null;
                ti = new System.Configuration.Install.TransactedInstaller();
                ti.Installers.Add(new ProjectInstaller());
                ti.Context = new System.Configuration.Install.InstallContext("", null);
                string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
                ti.Context.Parameters["assemblypath"] = path;
                ti.Uninstall(null);
                return;
            }
    
            if (Array.Exists(args, delegate(string arg) { return arg == "/service"; }))
            {
                ServiceBase[] ServicesToRun;
    
                ServicesToRun = new ServiceBase[] { new MyService() };
                ServiceBase.Run(ServicesToRun);
            }
            else
            {
                Console.ReadKey();
            }
        }
    }
    

    然后修改projectinstaller.cs以重写onbeforeinstall()和onbeforeuninstall()。

    [RunInstaller(true)]
    public partial class ProjectInstaller : Installer
    {
        public ProjectInstaller()
        {
            InitializeComponent();
        }
    
        protected virtual string AppendPathParameter(string path, string parameter)
        {
            if (path.Length > 0 && path[0] != '"')
            {
                path = "\"" + path + "\"";
            }
            path += " " + parameter;
            return path;
        }
    
        protected override void OnBeforeInstall(System.Collections.IDictionary savedState)
        {
            Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service");
            base.OnBeforeInstall(savedState);
        }
    
        protected override void OnBeforeUninstall(System.Collections.IDictionary savedState)
        {
            Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service");
            base.OnBeforeUninstall(savedState);
        }
    }
    
        7
  •  4
  •   shockwave121    12 年前

    这条线真的很旧,但我想我会把我的解决方案扔到外面去。很简单,为了处理这种情况,我构建了一个“服务线束”,用于控制台和Windows服务案例。如上所述,大多数逻辑都包含在一个单独的库中,但这更多是为了测试和“可链接性”。

    所附的代码决不是解决这个问题的“最佳可能”方法,只是我自己的方法。在这里,当处于“控制台模式”时,控制台应用程序调用服务线束,当作为服务运行时,相同应用程序的“启动服务”逻辑调用服务线束。这样做,你现在可以打电话给

    ServiceHost.Instance.RunningAsAService (布尔)

    从代码中的任何地方检查应用程序是作为服务运行还是仅仅作为控制台运行。

    代码如下:

    public class ServiceHost
    {
        private static Logger log = LogManager.GetLogger(typeof(ServiceHost).Name);
    
        private static ServiceHost mInstance = null;
        private static object mSyncRoot = new object();
    
        #region Singleton and Static Properties
    
        public static ServiceHost Instance
        {
            get
            {
                if (mInstance == null)
                {
                    lock (mSyncRoot)
                    {
                        if (mInstance == null)
                        {
                            mInstance = new ServiceHost();
                        }
                    }
                }
    
                return (mInstance);
            }
        }
    
        public static Logger Log
        {
            get { return log; }
        }
    
        public static void Close()
        {
            lock (mSyncRoot)
            {
                if (mInstance.mEngine != null)
                    mInstance.mEngine.Dispose();
            }
        }
    
        #endregion
    
        private ReconciliationEngine mEngine;
        private ServiceBase windowsServiceHost;
        private UnhandledExceptionEventHandler threadExceptionHanlder = new UnhandledExceptionEventHandler(ThreadExceptionHandler);
    
        public bool HostHealthy { get; private set; }
        public bool RunningAsService {get; private set;}
    
        private ServiceHost()
        {
            HostHealthy = false;
            RunningAsService = false;
            AppDomain.CurrentDomain.UnhandledException += threadExceptionHandler;
    
            try
            {
                mEngine = new ReconciliationEngine();
                HostHealthy = true;
            }
            catch (Exception ex)
            {
                log.FatalException("Could not initialize components.", ex);
            }
        }
    
        public void StartService()
        {
            if (!HostHealthy)
                throw new ApplicationException("Did not initialize components.");
    
            try
            {
                mEngine.Start();
            }
            catch (Exception ex)
            {
                log.FatalException("Could not start service components.", ex);
                HostHealthy = false;
            }
        }
    
        public void StartService(ServiceBase serviceHost)
        {
            if (!HostHealthy)
                throw new ApplicationException("Did not initialize components.");
    
            if (serviceHost == null)
                throw new ArgumentNullException("serviceHost");
    
            windowsServiceHost = serviceHost;
            RunningAsService = true;
    
            try
            {
                mEngine.Start();
            }
            catch (Exception ex)
            {
                log.FatalException("Could not start service components.", ex);
                HostHealthy = false;
            }
        }
    
        public void RestartService()
        {
            if (!HostHealthy)
                throw new ApplicationException("Did not initialize components.");         
    
            try
            {
                log.Info("Stopping service components...");
                mEngine.Stop();
                mEngine.Dispose();
    
                log.Info("Starting service components...");
                mEngine = new ReconciliationEngine();
                mEngine.Start();
            }
            catch (Exception ex)
            {
                log.FatalException("Could not restart components.", ex);
                HostHealthy = false;
            }
        }
    
        public void StopService()
        {
            try
            {
                if (mEngine != null)
                    mEngine.Stop();
            }
            catch (Exception ex)
            {
                log.FatalException("Error stopping components.", ex);
                HostHealthy = false;
            }
            finally
            {
                if (windowsServiceHost != null)
                    windowsServiceHost.Stop();
    
                if (RunningAsService)
                {
                    AppDomain.CurrentDomain.UnhandledException -= threadExceptionHanlder;
                }
            }
        }
    
        private void HandleExceptionBasedOnExecution(object ex)
        {
            if (RunningAsService)
            {
                windowsServiceHost.Stop();
            }
            else
            {
                throw (Exception)ex;
            }
        }
    
        protected static void ThreadExceptionHandler(object sender, UnhandledExceptionEventArgs e)
        {
            log.FatalException("Unexpected error occurred. System is shutting down.", (Exception)e.ExceptionObject);
            ServiceHost.Instance.HandleExceptionBasedOnExecution((Exception)e.ExceptionObject);
        }
    }
    

    你要做的就是换掉那副不祥的样子 ReconcilationEngine 无论用什么方法引用都会增强你的逻辑。然后在应用程序中,使用 ServiceHost.Instance.Start() ServiceHost.Instance.Stop() 方法,无论您是在控制台模式下运行还是作为服务运行。

        8
  •  3
  •   Matias    16 年前

    可能正在检查进程父级是否为c:\windows\system32\services.exe。

        9
  •  2
  •   mdb    16 年前

    我发现实现这一点的唯一方法是,首先通过访问Try/Catch块中的任何控制台对象属性(例如标题),检查控制台是否连接到进程。

    如果服务是由SCM启动的,则没有控制台,访问该属性将引发System.IO.IOError。

    但是,由于这种感觉有点过于依赖于特定于实现的细节(如果某些平台上的SCM或某一天决定为其启动的流程提供控制台,该怎么办?)我总是在生产应用程序中使用命令行开关(控制台)。

        10
  •  1
  •   Ben Voigt Caesar    10 年前

    下面是CHKSR对.NET的答案的翻译,并避免了无法识别交互服务的错误:

    using System.Security.Principal;
    
    var wi = WindowsIdentity.GetCurrent();
    var wp = new WindowsPrincipal(wi);
    var serviceSid = new SecurityIdentifier(WellKnownSidType.ServiceSid, null);
    var localSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
    var interactiveSid = new SecurityIdentifier(WellKnownSidType.InteractiveSid, null);
    // maybe check LocalServiceSid, and NetworkServiceSid also
    
    bool isServiceRunningAsUser = wp.IsInRole(serviceSid);
    bool isSystem = wp.IsInRole(localSystemSid);
    bool isInteractive = wp.IsInRole(interactiveSid);
    
    bool isAnyService = isServiceRunningAsUser || isSystem || !isInteractive;
    
        11
  •  -1
  •   Anderson Imes    16 年前

    这是一个自我插件,但我有一个小应用程序,它可以通过反射在你的应用程序中加载你的服务类型并以这种方式执行。我包含了源代码,所以您可以稍微更改它以显示标准输出。

    使用此解决方案不需要更改代码。我有一个debugger.isattached类型的解决方案,它足够通用,可以用于任何服务。链接在本文中: .NET Windows Service Runner

        12
  •  -1
  •   chksr    10 年前

    好吧,有一些非常古老的代码(大约20年左右,不是我写的,而是在野生的,野生的网络中发现的,而且是C而不是C)应该能让你知道如何做这项工作:

    enum enEnvironmentType
        {
        ENVTYPE_UNKNOWN,
        ENVTYPE_STANDARD,
        ENVTYPE_SERVICE_WITH_INTERACTION,
        ENVTYPE_SERVICE_WITHOUT_INTERACTION,
        ENVTYPE_IIS_ASP,
        };
    
    enEnvironmentType GetEnvironmentType(void)
    {
        HANDLE  hProcessToken   = NULL;
        DWORD   groupLength     = 300;
        PTOKEN_GROUPS groupInfo = NULL;
    
        SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY;
        PSID    pInteractiveSid = NULL;
        PSID    pServiceSid = NULL;
    
        DWORD   dwRet = NO_ERROR;
        DWORD   ndx;
    
        BOOL    m_isInteractive = FALSE;
        BOOL    m_isService = FALSE;
    
        // open the token
        if (!::OpenProcessToken(::GetCurrentProcess(),TOKEN_QUERY,&hProcessToken))
            {
            dwRet = ::GetLastError();
            goto closedown;
            }
    
        // allocate a buffer of default size
        groupInfo = (PTOKEN_GROUPS)::LocalAlloc(0, groupLength);
        if (groupInfo == NULL)
            {
            dwRet = ::GetLastError();
            goto closedown;
            }
    
        // try to get the info
        if (!::GetTokenInformation(hProcessToken, TokenGroups,
            groupInfo, groupLength, &groupLength))
            {
            // if buffer was too small, allocate to proper size, otherwise error
            if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
                {
                dwRet = ::GetLastError();
                goto closedown;
                }
    
            ::LocalFree(groupInfo);
    
            groupInfo = (PTOKEN_GROUPS)::LocalAlloc(0, groupLength);
            if (groupInfo == NULL)
                {
                dwRet = ::GetLastError();
                goto closedown;
                }
    
            if (!GetTokenInformation(hProcessToken, TokenGroups,
                groupInfo, groupLength, &groupLength))
                {
                dwRet = ::GetLastError();
                goto closedown;
                }
            }
    
        //
        //  We now know the groups associated with this token.  We want
        //  to look to see if the interactive group is active in the
        //  token, and if so, we know that this is an interactive process.
        //
        //  We also look for the "service" SID, and if it's present,
        //  we know we're a service.
        //
        //  The service SID will be present iff the service is running in a
        //  user account (and was invoked by the service controller).
        //
    
        // create comparison sids
        if (!AllocateAndInitializeSid(&siaNt,
            1,
            SECURITY_INTERACTIVE_RID,
            0, 0, 0, 0, 0, 0, 0,
            &pInteractiveSid))
            {
            dwRet = ::GetLastError();
            goto closedown;
            }
    
        if (!AllocateAndInitializeSid(&siaNt,
            1,
            SECURITY_SERVICE_RID,
            0, 0, 0, 0, 0, 0, 0,
            &pServiceSid))
            {
            dwRet = ::GetLastError();
            goto closedown;
            }
    
        // try to match sids
        for (ndx = 0; ndx < groupInfo->GroupCount ; ndx += 1)
            {
            SID_AND_ATTRIBUTES  sanda = groupInfo->Groups[ndx];
            PSID                pSid = sanda.Sid;
    
            //
            //    Check to see if the group we're looking at is one of
            //    the two groups we're interested in.
            //
    
            if (::EqualSid(pSid, pInteractiveSid))
                {
                //
                //  This process has the Interactive SID in its
                //  token.  This means that the process is running as
                //  a console process
                //
                m_isInteractive = TRUE;
                m_isService = FALSE;
                break;
                }
            else if (::EqualSid(pSid, pServiceSid))
                {
                //
                //  This process has the Service SID in its
                //  token.  This means that the process is running as
                //  a service running in a user account ( not local system ).
                //
                m_isService = TRUE;
                m_isInteractive = FALSE;
                break;
                }
            }
    
        if ( !( m_isService || m_isInteractive ) )
            {
            //
            //  Neither Interactive or Service was present in the current
            //  users token, This implies that the process is running as
            //  a service, most likely running as LocalSystem.
            //
            m_isService = TRUE;
            }
    
    
    closedown:
        if ( pServiceSid )
            ::FreeSid( pServiceSid );
    
        if ( pInteractiveSid )
            ::FreeSid( pInteractiveSid );
    
        if ( groupInfo )
            ::LocalFree( groupInfo );
    
        if ( hProcessToken )
            ::CloseHandle( hProcessToken );
    
        if (dwRet == NO_ERROR)
            {
            if (m_isService)
                return(m_isInteractive ? ENVTYPE_SERVICE_WITH_INTERACTION : ENVTYPE_SERVICE_WITHOUT_INTERACTION);
            return(ENVTYPE_STANDARD);
            }
          else
            return(ENVTYPE_UNKNOWN);
    }