.NET सांत्वना अनुप्रयोग विंडोज सेवा के रूप में


145

मेरे पास कंसोल एप्लिकेशन है और इसे विंडोज सेवा के रूप में चलाना चाहते हैं। VS2010 में प्रोजेक्ट टेम्प्लेट है जो कंसोल प्रोजेक्ट को संलग्न करने और विंडोज सेवा का निर्माण करने की अनुमति देता है। मैं अलग से सेवा परियोजना नहीं जोड़ना चाहूंगा और यदि संभव हो तो कंसोल एप्लिकेशन में कंसोल एप्लिकेशन को रखने के लिए कंसोल एप्लिकेशन में सर्विस कोड को एकीकृत करें जो कि स्विच का उपयोग करके कमांड लाइन से उदाहरण के लिए चलने पर कंसोल एप्लिकेशन या विंडोज़ सेवा के रूप में चल सकता है।

हो सकता है कि कोई व्यक्ति क्लास लाइब्रेरी या कोड स्निपेट का सुझाव दे सकता है जो सी # कंसोल एप्लिकेशन को सेवा में जल्दी और आसानी से बदल सकता है?


आप बस एक अस्थायी सेवा परियोजना क्यों नहीं बनाते हैं और बिट्स की नकल करते हैं जो इसे सेवा बनाते हैं?
गेबे


आप यहाँ वर्णित तकनीक को आजमा सकते हैं: einaregilsson.com/2007/08/15/…
जो

है ना? मुझे यकीन नहीं है। इसके बारे में।

2
एक बहुत ही सरल शीर्ष शेल्फ विकल्प: runasservice.com
लुइस पेरेज़

जवाबों:


185

मैं आम तौर पर कंसोल एप्लिकेशन के रूप में या सेवा के रूप में एक ही ऐप को चलाने के लिए निम्नलिखित टेचिंक का उपयोग करता हूं:

public static class Program
{
    #region Nested classes to support running as service
    public const string ServiceName = "MyService";

    public class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
    #endregion

    static void Main(string[] args)
    {
        if (!Environment.UserInteractive)
            // running as service
            using (var service = new Service())
                ServiceBase.Run(service);
        else
        {
            // running as console app
            Start(args);

            Console.WriteLine("Press any key to stop...");
            Console.ReadKey(true);

            Stop();
        }
    }

    private static void Start(string[] args)
    {
        // onstart code here
    }

    private static void Stop()
    {
        // onstop code here
    }
}

Environment.UserInteractiveकंसोल ऐप के लिए सामान्य रूप से सही है और किसी सेवा के लिए गलत है। तकनीकी रूप से, उपयोगकर्ता-इंटरैक्टिव मोड में सेवा चलाना संभव है, इसलिए आप इसके बजाय कमांड-लाइन स्विच की जांच कर सकते हैं।


3
आप ServiceInstaller वर्ग का उपयोग करते हैं, देखें msdn.microsoft.com/en-us/library/…
व्लादिवी

2
यह अपेक्षित है - आपकी सेवा एक अलग प्रक्रिया के रूप में चलेगी (इसलिए इसे कार्य प्रबंधक में दिखाया जाएगा), लेकिन इस प्रक्रिया को सिस्टम द्वारा नियंत्रित किया जाएगा (उदाहरण के लिए, सेवा सेटिंग्स के अनुसार शुरू, बंद, पुनः आरंभ किया गया)।
व्लादिवी

2
यदि आप इसे कंसोल ऐप के रूप में चलाते हैं, तो आपको कोई सेवा दिखाई नहीं देगी। इस कोड का पूरा उद्देश्य आपको इसे कंसोल ऐप या सेवा के रूप में चलाने में सक्षम बनाना है। एक सेवा के रूप में चलाने के लिए आपको पहले इसे स्थापित करना होगा (ServiceInstaller वर्ग का उपयोग करके - ऊपर MSDN लिंक देखें - या installuitil.exe), और नियंत्रण कक्ष से सेवा चलाएँ।
व्लादिवी

2
Windows सेवाओं से निपटने के लिए ServiceInstaller सिर्फ एक उपयोगिता वर्ग है (थोड़ा बहुत installutil.exe या sc.exe उपयोगिताओं की तरह)। आप इसे सेवा के रूप में जो चाहें स्थापित करने के लिए उपयोग कर सकते हैं, ओएस आपके द्वारा उपयोग किए जाने वाले प्रोजेक्ट प्रकार की परवाह नहीं करता है।
व्लाद

5
बस अपनी परियोजना में एक संदर्भ जोड़ने System.ServiceProcess और आप ऊपर कोड का उपयोग करने में सक्षम हो जाएगा
Danimal

59

मुझे टॉपशेलफ के साथ बड़ी सफलता मिली है ।

TopShelf एक Nuget पैकेज है जिसे .NET विंडोज ऐप बनाने में आसान बनाया गया है जो कंसोल ऐप या विंडोज सर्विसेज के रूप में चल सकता है। आप अपनी सेवा प्रारंभ और रुकने जैसी घटनाओं को जल्दी से अंजाम दे सकते हैं, जिस कोड को उपयोग करना चाहते हैं उसे सेट करने के लिए कोड का उपयोग करके कॉन्फ़िगर करें, अन्य सेवाओं पर निर्भरता को कॉन्फ़िगर करें, और कॉन्फ़िगर करें कि यह त्रुटियों से कैसे उबरता है।

पैकेज प्रबंधक कंसोल (Nuget) से:

स्थापित-पैकेज Topshelf

आरंभ करने के लिए कोड के नमूने देखें ।

उदाहरण:

HostFactory.Run(x =>                                 
{
    x.Service<TownCrier>(s =>                        
    {
       s.ConstructUsing(name=> new TownCrier());     
       s.WhenStarted(tc => tc.Start());              
       s.WhenStopped(tc => tc.Stop());               
    });
    x.RunAsLocalSystem();                            

    x.SetDescription("Sample Topshelf Host");        
    x.SetDisplayName("Stuff");                       
    x.SetServiceName("stuff");                       
}); 

TopShelf सेवा की स्थापना का भी ध्यान रखता है, जो बहुत समय बचा सकता है और आपके समाधान से बॉयलरप्लेट कोड निकाल सकता है। एक सेवा के रूप में अपने .exe को स्थापित करने के लिए आप केवल कमांड प्रॉम्प्ट से निम्नलिखित को निष्पादित करते हैं:

myservice.exe install -servicename "MyService" -displayname "My Service" -description "This is my service."

आपको ServiceInstaller और सभी को हुक करने की आवश्यकता नहीं है - TopShelf यह सब आपके लिए करता है।


1
नमस्ते, मुझे यह मिल रहा है: - "पैकेज 'Topshelf 4.0.1' को स्थापित नहीं किया जा सका। आप इस पैकेज को '.NETFramework, संस्करण = v4.5' को लक्षित करने वाले प्रोजेक्ट में स्थापित करने का प्रयास कर रहे हैं, लेकिन पैकेज में कोई भी शामिल नहीं है असेंबली रेफरेंस या कंटेंट फाइल जो उस फ्रेमवर्क के अनुकूल हैं। यहाँ क्या गलत है?

3
सुनिश्चित करें कि आप पूर्ण .NET 4.5.2 रनटाइम को लक्षित कर रहे हैं, क्लाइंट प्रोफाइल को नहीं।
१३:३३ पर नाविक

कृपया आप myservice.exe पर अधिक प्रकाश फेंक सकते हैं और किस निर्देशिका से आप कमांड प्रॉम्प्ट खोलने जा रहे हैं
Izuagbala

1
@Izuagbala myservice.exe आपके द्वारा बनाए गए कंसोल एप्लिकेशन है, जिसमें TopShelf बूटस्ट्रैप के साथ कोड नमूने में दिखाया गया है।
सेल

एक सेवा के रूप में स्थापित होने के बाद myservice.exe को कंसोल के रूप में चलाया जा सकता है? दस्तावेज़ीकरण स्पष्ट नहीं है: "एक बार कंसोल एप्लिकेशन बनने
माइकल फ्रीजिम

27

तो यहाँ पूर्ण walkthrough है:

  1. नया कंसोल एप्लिकेशन प्रोजेक्ट बनाएं (जैसे MyService)
  2. दो पुस्तकालय संदर्भ जोड़ें: System.ServiceProcess और System.Configuration.Install
  3. नीचे मुद्रित तीन फ़ाइलों को जोड़ें
  4. प्रोजेक्ट बनाएँ और "InstallUtil.exe c: \ path \ to \ MyService.exe" चलाएं
  5. अब आपको सेवा सूची पर MyService देखना चाहिए (चलाएँ services.msc)

* InstallUtil.exe आमतौर पर यहां पाया जा सकता है: C: \ windows \ Microsoft.NET \ Framework \ v4.0.30319 \ InstallUtil.exh e

Program.cs

using System;
using System.IO;
using System.ServiceProcess;

namespace MyService
{
    class Program
    {
        public const string ServiceName = "MyService";

        static void Main(string[] args)
        {
            if (Environment.UserInteractive)
            {
                // running as console app
                Start(args);

                Console.WriteLine("Press any key to stop...");
                Console.ReadKey(true);

                Stop();
            }
            else
            {
                // running as service
                using (var service = new Service())
                {
                    ServiceBase.Run(service);
                }
            }
        }

        public static void Start(string[] args)
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} started{1}", DateTime.Now, Environment.NewLine));
        }

        public static void Stop()
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} stopped{1}", DateTime.Now, Environment.NewLine));
        }
    }
}

MyService.cs

using System.ServiceProcess;

namespace MyService
{
    class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
}

MyServiceInstaller.cs

using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;

namespace MyService
{
    [RunInstaller(true)]
    public class MyServiceInstaller : Installer
    {
        public MyServiceInstaller()
        {
            var spi = new ServiceProcessInstaller();
            var si = new ServiceInstaller();

            spi.Account = ServiceAccount.LocalSystem;
            spi.Username = null;
            spi.Password = null;

            si.DisplayName = Program.ServiceName;
            si.ServiceName = Program.ServiceName;
            si.StartType = ServiceStartMode.Automatic;

            Installers.Add(spi);
            Installers.Add(si);
        }
    }
}

1
यदि आप अपने प्रोजेक्ट को 64 बिट के लिए संकलित कर रहे हैं, तो आपको 64 बिट के लिए इंस्टालयूटल। Exe का उपयोग करना होगा जो यहां पाया जा सकता है: C: \ windows \ Microsoft.NET \ Framework64 \ ... 32 बिट के लिए संस्करण (C: \ windows) \ Microsoft.NET \ Framework) आप पर एक BadImageFormatException फेंक देगा ...
snytek

यह बहुत अच्छी तरह से काम करता है, ध्यान दें कि जैसा कि @snytek कहता है, यदि आप आधार 64 का उपयोग कर रहे हैं, तो सुनिश्चित करें कि आप सही निर्देशिका का उपयोग करते हैं। इसके अलावा, यदि आप मेरे जैसा ही करते हैं और "MyService" के अलावा किसी अन्य सेवा का नाम बदलना भूल जाते हैं, तो सुनिश्चित करें कि आप कोड में परिवर्तन करने से पहले सेवा की स्थापना रद्द कर दें।
dmoore1181

3

मैं बार-बार कोड को रोकने के लिए एक असेंबली चाहने के बारे में आपकी बात सुनता हूं लेकिन, यह सबसे सरल होगा और कोड पुनरावृत्ति को कम करेगा और भविष्य में अन्य तरीकों से अपने कोड का पुन: उपयोग करना आसान बना देगा यदि ...... आप इसे 3 विधानसभाओं में तोड़ सकते हैं।

  1. एक लाइब्रेरी असेंबली जो सभी काम करती है। फिर दो बहुत ही पतली / सरल परियोजनाएं हैं:
  2. एक जो कमांडलाइन है
  3. एक जो विंडोज़ सेवा है।

1
इस तरह से मैंने इसे वर्षों तक किया है - सेवा में बहुत अधिक है Start()और Stop()विधियों और कंसोल ऐप में एक लूप है। TopShelf जैसे ढांचे का उपयोग करने का संक्षिप्त तरीका , यह सबसे अच्छा विकल्प है
बेसिक

उस जवाब से सबसे ज्यादा सहमत हैं। सरल समाधान के लिए 3 डी पार्टी टूल्स का उपयोग करने से भविष्य के रखरखाव को अनावश्यक जटिल बना दिया जाता है
tatigo

3

नवीनतम .Net Core 3.1 पर आधारित वर्कर सर्विस के रूप में विंडोज सर्विस के लिए कंसोल एप्लिकेशन को कैसे चालू करें, इसका एक नया तरीका है ।

यदि आप Visual Studio 2019 से एक वर्कर सर्विस बनाते हैं, तो यह आपको लगभग हर वह चीज देगा जो आपको विंडोज सर्विस को बॉक्स से बाहर करने के लिए चाहिए, जो कि आपको विंडोज सर्विस में बदलने के लिए कंसोल एप्लिकेशन में बदलने की भी जरूरत है।

यहां आपको वे परिवर्तन करने होंगे जो आपको करने होंगे:

निम्नलिखित NuGet पैकेज स्थापित करें

Install-Package Microsoft.Extensions.Hosting.WindowsServices -Version 3.1.0
Install-Package Microsoft.Extensions.Configuration.Abstractions -Version 3.1.0

नीचे की तरह कार्यान्वयन के लिए Program.cs बदलें:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).UseWindowsService().Build().Run();
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });
    }
}

और वर्कर जोड़ें। जहां आप कोड डालेंगे, जो सेवा कार्यों द्वारा चलाया जाएगा:

using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class Worker : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            //do some operation
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            return base.StartAsync(cancellationToken);
        }

        public override Task StopAsync(CancellationToken cancellationToken)
        {
            return base.StopAsync(cancellationToken);
        }
    }
}

जब सब कुछ तैयार हो जाता है, और एप्लिकेशन सफलतापूर्वक बनाया गया है, तो आप निम्न निर्देश के साथ Windows सेवा के रूप में अपने कंसोल एप्लिकेशन exe को स्थापित करने के लिए sc.exe का उपयोग कर सकते हैं :

sc.exe create DemoService binpath= "path/to/your/file.exe"

2

आप उपयोग कर सकते हैं

reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /v ServiceName /d "c:\path\to\service\file\exe"

और यह int सर्विस लिस्ट में दिखाई देगा। मुझे नहीं पता, हालांकि यह सही ढंग से काम करता है या नहीं। एक सेवा को आमतौर पर कई घटनाओं को सुनना पड़ता है।

हालांकि कई सर्विस रैपर हैं, जो किसी भी एप्लिकेशन को वास्तविक सेवा के रूप में चला सकते हैं। उदाहरण के लिए Win2003 संसाधन किट से Microsofts SrvAny


जैसा कि आप कहते हैं, सेवा exe को खिड़कियों के साथ संवाद करने की आवश्यकता होगी। श्रीवनी के लिंक के लिए +1
जोर्डेल

5
मैं इस दृष्टिकोण को असुरक्षित समझूंगा। विंडोज़ में सेवाओं के प्रबंधन के लिए विशेष पुस्तकालय और उपयोगिताओं हैं, और वे विभिन्न ओएस संस्करणों और वातावरणों में लगातार काम करने की अधिक संभावना रखते हैं। .NET ऐप के लिए VS में MSI इंस्टॉलर बनाना काफी आसान है। यह ManagedInstallerClass.InstallHelper मेथड का उपयोग करते हुए स्थापना को व्यावहारिक रूप से करने के लिए भी सकारात्मक है।
व्लादिवी

1
इंस्टॉलरों और सामानों की आवश्यकता नहीं है: बस इस कमांड लाइन का उपयोग करें: sc create MyServiceName binPath = "c: \ path \ to to \ service \ file \ exe"
JDC

2

सबसे पहले मैं कंसोल एप्लिकेशन समाधान को विंडोज़ सेवा समाधान में एम्बेड करता हूं और इसे संदर्भ देता हूं।

फिर मैं कंसोल एप्लिकेशन प्रोग्राम को सार्वजनिक करता हूं

/// <summary>
/// Hybrid service/console application
/// </summary>
public class Program
{
}

फिर मैं कंसोल एप्लिकेशन के भीतर दो फ़ंक्शन बनाता हूं

    /// <summary>
    /// Used to start as a service
    /// </summary>
    public void Start()
    {
        Main();
    }

    /// <summary>
    /// Used to stop the service
    /// </summary>
    public void Stop()
    {
       if (Application.MessageLoop)
            Application.Exit();   //windows app
        else
            Environment.Exit(1);  //console app
    }

फिर विंडोज़ सेवा के भीतर ही मैं प्रोग्राम को तुरंत शुरू कर देता हूं और ऑनस्टार्ट और ऑनटॉप के भीतर जोड़े गए स्टार्ट एंड स्टॉप फ़ंक्शन को कॉल करता हूं। निचे देखो

class WinService : ServiceBase
{
    readonly Program _application = new Program();

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main()
    {
        ServiceBase[] servicesToRun = { new WinService() };
        Run(servicesToRun);
    }

    /// <summary>
    /// Set things in motion so your service can do its work.
    /// </summary>
    protected override void OnStart(string[] args)
    {
        Thread thread = new Thread(() => _application.Start());
        thread.Start();
    }

    /// <summary>
    /// Stop this service.
    /// </summary>
    protected override void OnStop()
    {
        Thread thread = new Thread(() => _application.Stop());
        thread.Start();
    }
}

इस दृष्टिकोण का उपयोग विंडोज़ एप्लिकेशन / विंडोज़ सर्विस हाइब्रिड के लिए भी किया जा सकता है


यह मूल रूप से जॉनएल्ब ने प्रचलित उत्तर में क्या कहा है, लेकिन कोड उदाहरण के लिए धन्यवाद
tatigo

0

हो सकता है कि आपको यह परिभाषित करना चाहिए कि आपको क्या चाहिए, जहां तक ​​मुझे पता है, आप अपने ऐप को कंसोल या सर्विस के साथ कमांड लाइन के साथ एक ही समय में नहीं चला सकते हैं। याद रखें कि सेवा स्थापित है और आपको इसे सेवा प्रबंधक में शुरू करना है, आप एक नया एप्लिकेशन बना सकते हैं जो सेवा शुरू करता है या आपके कंसोल ऐप को चलाने के लिए एक नई प्रक्रिया शुरू करता है। लेकिन जैसा आपने लिखा है

"एक प्रोजेक्ट के रूप में कंसोल एप्लिकेशन रखें"

एक बार, मैं आपकी स्थिति में था, एक सेवा में एक कंसोल एप्लिकेशन को चालू करना। सबसे पहले आपको वीएसएस एक्सप्रेस संस्करण के साथ काम करने की स्थिति में टेम्पलेट की आवश्यकता होती है। यहाँ एक लिंक है जहाँ आप अपने पहले कदम रख सकते हैं: सी # विंडोज सेवा , यह मेरे लिए बहुत उपयोगी था। फिर उस टेम्पलेट का उपयोग करके, अपने कोड को सेवा की वांछित घटनाओं में जोड़ें।

आपको सेवा में सुधार करने के लिए, एक और चीज है जो आप कर सकते हैं, लेकिन यह त्वरित और / या आसानी से नहीं है, एपडोमेन का उपयोग कर रहा है, और लोड / अनलोड करने के लिए डीएलएस बना रहा है। एक में आप कंसोल ऐप के साथ एक नई प्रक्रिया शुरू कर सकते हैं, और दूसरे डीएल में आप बस उस कार्यक्षमता को डाल सकते हैं जो सेवा को करना है।

सौभाग्य।


0

आपको एक वर्ग या कक्षाओं में कार्यक्षमता को अलग करने और दो स्टब में से एक के माध्यम से लॉन्च करने की आवश्यकता है। कंसोल स्टब या सर्विस स्टब।

विंडोज को चलाने के दौरान इसके सादे के रूप में, बुनियादी ढांचे को बनाने वाली असंख्य सेवाएं उपयोगकर्ता को कंसोल विंडो पेश नहीं कर सकती (और सीधे नहीं कर सकती हैं)। सेवा को गैर-ग्राफ़िकल तरीके से उपयोगकर्ता के साथ संवाद करने की आवश्यकता है: एससीएम के माध्यम से; ईवेंट लॉग में, कुछ लॉग फ़ाइल आदि में। सेवा को SCM के माध्यम से विंडोज़ के साथ संवाद करने की भी आवश्यकता होगी, अन्यथा यह बंद हो जाएगा।

यह स्पष्ट रूप से स्वीकार्य होगा कि कुछ कंसोल ऐप हैं जो सेवा के साथ संवाद कर सकते हैं लेकिन सेवा को जीयूआई इंटरैक्शन की आवश्यकता के बिना स्वतंत्र रूप से चलाने की आवश्यकता है।

कंसोल स्टब डिबगिंग सेवा व्यवहार के लिए बहुत उपयोगी हो सकता है, लेकिन इसका उपयोग "उत्पादन" वातावरण में नहीं किया जाना चाहिए, जो आखिरकार, सेवा बनाने का उद्देश्य है।

मैंने इसे पूरी तरह से नहीं पढ़ा है लेकिन यह लेख सही दिशा में इंगित करता है।


0

मैं एक सेवा वर्ग का उपयोग करता हूं जो निर्धारित मानक पैटर्न का अनुसरण करता है ServiceBase, और सहायकों पर F5 डिबगिंग को आसान बनाता है। यह सेवा डेटा को सेवा के भीतर परिभाषित करता है, जिससे उन्हें ढूंढना आसान हो जाता है और उनके जीवनकाल को प्रबंधित करना आसान हो जाता है।

मैं आमतौर पर नीचे की संरचना के साथ एक विंडोज एप्लिकेशन बनाता हूं। मैं एक कंसोल एप्लिकेशन नहीं बनाता हूं; हर बार जब मैं ऐप चलाता हूं तो मुझे अपने चेहरे पर एक बड़ा ब्लैक बॉक्स नहीं आता है। मैं डिबगर में रहता हूं जहां सभी कार्रवाई होती है। मैं उपयोग करता हूं Debug.WriteLineताकि संदेश आउटपुट विंडो पर जाएं, जो अच्छी तरह से डॉक करता है और ऐप के समाप्त होने के बाद दिखाई देता है।

मैं आमतौर पर रोक के लिए डिबग कोड जोड़ने से परेशान नहीं होता; मैं इसके बजाय डिबगर का उपयोग करता हूं। यदि मुझे स्टॉपिंग डीबग करने की आवश्यकता है, तो मैं प्रोजेक्ट को एक कंसोल ऐप बनाता हूं, एक Stopफ़ॉरवर्डर विधि जोड़ता हूं , और कॉल करने के बाद कॉल करता हूं Console.ReadKey

public class Service : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        // Start logic here.
    }

    protected override void OnStop()
    {
        // Stop logic here.
    }

    static void Main(string[] args)
    {
        using (var service = new Service()) {
            if (Environment.UserInteractive) {
                service.Start();
                Thread.Sleep(Timeout.Infinite);
            } else
                Run(service);
        }
    }
    public void Start() => OnStart(null);
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.