將WCF寄宿在托管的Windows服務中


  在我之前的一篇博客中我介紹了如何發布WCF服務並將該服務寄宿於IIS上,今天我再來介紹一種方式,就是將WCF服務寄宿在Windows服務中,這樣做有什么好處呢?當然可以省去部署IIS等一系列的問題,能夠讓部署更加簡單,當然WCF的寄宿方式一般分為以下四種方式,針對每一種方式我來簡單介紹以下:

  具體的寄宿方式詳細信息請參考MSDN:https://msdn.microsoft.com/zh-cn/library/ms733109(v=vs.100).aspx

  一、WCF服務寄宿方式:

  1):寄宿在IIS上:與經典的webservice托管類似,把服務當成web項目,需要提供svc文件,其缺點是只能使用http協議,也就是說,只能使用單調服務,沒有會話狀態。IIS還受端口的限制(所有服務必須使用相同的端口),主要優勢是:客戶端第一次請求是自動啟動宿主進程。 

  2):寄宿在WAS上(全稱Windows激活服務):WAS 是一個新的進程激活服務,它是使用非 HTTP 傳輸協議的 Internet 信息服務 (IIS) 功能的一般化。WCF 使用偵聽器適配器接口來傳遞通過 WCF 支持的非 HTTP 協定(例如,TCP、命名管道和消息隊列)接收的激活請求。可托管網站,可托管服務,可使用任何協議,可以單獨安裝和配置,不依賴IIS。需要提供svc文件或在配置文件內提供等價的信息。 

  3):自承載:開發者提供和管理宿主進程生命周期的一種方法。可使用控制台程序,WinForm窗口程序,WPF程序提供宿主服務。可使用任意協議。必須先於客戶端啟動。可以實現WCF高級特性:服務總線,服務發現,單例服務。 

      4):寄宿在Windows服務上:此方案可通過托管 Windows 服務承載選項啟用,此選項是在沒有消息激活的安全環境中在 Internet 信息服務 (IIS) 外部承載的、長時間運行的 WCF 服務。服務的生存期改由操作系統控制。此宿主選項在 Windows 的所有版本中都是可用的。可以使用 Microsoft 管理控制台 (MMC) 中的 Microsoft.ManagementConsole.SnapIn 管理 Windows 服務,並且可以將其配置為在系統啟動時自動啟動。此承載選項包括注冊承載 WCF 服務作為托管 Windows 服務的應用程序域,因此服務的進程生存期由 Windows 服務的服務控制管理器 (SCM) 來控制。

  這一篇主要用來介紹第四種即:WCF程序寄宿在托管的Windows服務中。

      1 新建一個WCF服務,並按照相關規則來建立一個完整的WCF程序。

      a:定義服務接口    

// 注意: 使用“重構”菜單上的“重命名”命令,可以同時更改代碼和配置文件中的接口名“IService1”。
    [ServiceContract]
    public interface IBasicService
    {
        [OperationContract]
        string Login(string username, string password, string version);
        [OperationContract]
        Users GetUserInfo(string userName);

        [OperationContract]
        bool SaveOption(string option_name, string option_value);
        [OperationContract]
        string GetOptionValue(string option_name);
        [OperationContract]
        bool SaveOptionByUser(string option_name, string option_value, int userid);
        [OperationContract]
        string GetOptionValueByUser(string option_name, int userid);
        [OperationContract]
        string TestSQLConnection();
    
    }

  b 實現接口(這里面的和數據庫的交互方式為:Linq To Sql)  

    public class BasicService : IBasicService
    {
        #region 用戶
        public string Login(string username, string password, string version)
        {
            try
            {
                using (var db = new dbmls.BasicDataContext())
                {
                    var entity = (from x in db.Users
                                  where x.Email == username && x.Password == password
                                  select x).SingleOrDefault() ?? null;
                    if (null == entity)
                    {
                        return "用戶名或密碼錯誤";
                    }
                    entity.LastLoginTime = DateTime.Now;
                    db.SubmitChanges();
                    Utils.LogUtil.WriteLog(Utils.LogUtil.LogTypes.User, "登錄", version, entity.id);
                    return "";
                }
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        public Users GetUserInfo(string userName)
        {
            try
            {
                using (var db = new dbmls.BasicDataContext())
                {
                    var entity = (from x in db.Users
                                  where x.Email == userName
                                  select x).SingleOrDefault() ?? null;
                    return entity;
                }
            }
            catch
            {
                return null;
            }
        }

        #endregion


        #region 數據存儲
        public bool SaveOption(string option_name, string option_value)
        {
            try
            {
                using (dbmls.BasicDataContext db = new dbmls.BasicDataContext())
                {
                    dbmls.Options option = null;
                    option = (from x in db.Options
                              where x.OptionName == option_name && x.UserID == 0
                              select x).SingleOrDefault() ?? null;
                    if (null != option)
                    {
                        option.OptionValue = option_value;
                        option.UpdateTime = DateTime.Now;
                    }
                    else
                    {
                        option = new Options()
                        {
                            OptionName = option_name,
                            OptionValue = option_value,
                            UpdateTime = DateTime.Now,
                            CreateTime = DateTime.Now,
                            UserID = 0,
                        };
                        db.Options.InsertOnSubmit(option);
                    }
                    db.SubmitChanges();
                    return true;
                }
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public string GetOptionValue(string option_name)
        {
            try
            {
                using (dbmls.BasicDataContext db = new dbmls.BasicDataContext())
                {
                    dbmls.Options option = null;
                    option = (from x in db.Options
                              where x.OptionName == option_name && x.UserID == 0
                              select x).SingleOrDefault() ?? null;
                    if (null != option)
                    {
                        return option.OptionValue;
                    }
                }
                return "";
            }
            catch (Exception ex)
            {
                return "";
            }
        }


        public bool SaveOptionByUser(string option_name, string option_value, int userid)
        {
            try
            {
                using (dbmls.BasicDataContext db = new dbmls.BasicDataContext())
                {
                    dbmls.Options option = null;
                    option = (from x in db.Options
                              where x.OptionName == option_name && x.UserID == userid
                              select x).SingleOrDefault() ?? null;
                    if (null != option)
                    {
                        option.OptionValue = option_value;
                        option.UpdateTime = DateTime.Now;
                    }
                    else
                    {
                        option = new Options()
                        {
                            OptionName = option_name,
                            OptionValue = option_value,
                            UpdateTime = DateTime.Now,
                            CreateTime = DateTime.Now,
                            UserID = userid
                        };
                        db.Options.InsertOnSubmit(option);
                    }
                    db.SubmitChanges();
                    return true;
                }
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public string GetOptionValueByUser(string option_name, int userid)
        {
            try
            {
                using (dbmls.BasicDataContext db = new dbmls.BasicDataContext())
                {
                    dbmls.Options option = null;
                    option = (from x in db.Options
                              where x.OptionName == option_name && x.UserID == userid
                              select x).SingleOrDefault() ?? null;
                    if (null != option)
                    {
                        return option.OptionValue;
                    }
                }
                return "";
            }
            catch (Exception ex)
            {
                return "";
            }
        }

        public string TestSQLConnection()
        {
            dbmls.BasicDataContext db = new dbmls.BasicDataContext();
            try
            {
                db.Connection.Open();
                return "";
            }
            catch (Exception ex)
            {
                return "無法連接SQL Server數據庫\r" + ex.Message;
            }
            finally
            {
                db.Dispose();
            }
        }
        #endregion
    }

  由於當前的程序是寄宿在Windows服務中,所以和數據庫的交互方式配置在Windows服務中的App.config中,下面會逐一進行說明。

  2 新建一個Windows服務作為當前WCF程序的宿主。

  在我們的Windows服務中,我們寫了一個繼承自ServiceBase的類CoreService,並在Windows服務的靜態Main函數中啟動這個服務。  

       #region 開啟服務
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            {
                new CoreService()
            };
            ServiceBase.Run(ServicesToRun);
            #endregion

  在CoreService.cs中,我們通過重載OnStart和OnStop函數開啟和關閉WCF服務。 

ServiceHost host = new ServiceHost(typeof(Dvap.ServicesLib.BasicService));
	protected override void OnStart(string[] args)
        {
            try
            {            
                host.Open();              
            }
            catch (Exception ex)
            {
                 Utils.LoggerHelper.WriteLog(typeof(CoreService), ex);
            }
        } 
 
    	protected override void OnStop()
        {
            host.Close();         
        }

  3  配置當前的WCF服務,在當前的Windows服務的App.config配置下面的信息,這里需要着重說明的是,WCF程序Binding的方式有多種,可以是http方式也可以是net.tcp方式,這里我們采用后面的net.tcp具體的優勢可以查閱相關資料。

 <connectionStrings>
    <add name="Dvap.ServicesLib.Properties.Settings.DvapConnectionString"
      connectionString="Data Source=LAPTOP-BFFCLBD1\SQLEXPRESS;Initial Catalog=Dvap;User ID=sa;Password=XXXX"
      providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="BasicServiceBehavior"
        name="Dvap.ServicesLib.BasicService">
        <endpoint address="" binding="netTcpBinding" bindingConfiguration=""
          contract="Dvap.ServicesLib.Interfaces.IBasicService">
          <identity>
            <dns value="127.0.0.1" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexTcpBinding" bindingConfiguration=""
          contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://127.0.0.1:9000/BasicService.svc"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="BasicServiceBehavior">
          <serviceMetadata httpGetEnabled="false" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" minFreeMemoryPercentageToActivateService="0" />
    <bindings>
      <netTcpBinding>
        <binding name="defaultBinding" maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
          <security mode="None">
            <message clientCredentialType="None"/>
            <transport clientCredentialType="None"></transport>
          </security>
          <readerQuotas />
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>

  4 安裝部署Window服務。

  這樣就完成了我們的基本需求,另外就是安裝和部署Windows服務,這里都是一些常規的操作,首先我們來看一看生成的文件。

圖一 Windows服務安裝文件

  安裝Windows服務,在我們的生成文件目錄下,我們最好寫一個安裝的bat文件,然后直接運行就可以安裝和卸載Windows服務。

@echo off
set /p var=是否要安裝可視化數據交換服務(Y/N):
if "%var%" == "y" (goto install) else if "%var%" == "Y" (goto install) else (goto batexit)

:install
copy C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe  InstallUtil.exe /Y
call InstallUtil.exe Dvap數據通信服務.exe
sc start 可視化數據交換服務
pause

:batexit
exit

//卸載
@echo off
set /p var=是否要卸載可視化數據交換服務(Y/N):
if "%var%" == "y" (goto uninstall) else (goto batexit)

:uninstall
copy C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe  InstallUtil.exe /Y
call InstallUtil.exe /u Dvap數據通信服務.exe
pause

:batexit
exit

  當然關於Windows的安裝文件的一些配置,可以參考下面的內容,這里不再進行贅述,主要都是配置一下屬性的值,下面的介紹僅供參考。

  serviceProcessInstaller1控件

  ServiceProcessInstall安裝一個可執行文件,該文件包含擴展 ServiceBase 的類。該類由安裝實用工具(如 InstallUtil.exe)在安裝服務應用程序時調用。

  在這里主要是修改其Account屬性。ServiceAccount指定服務的安全上下文,安全上下文定義其登錄類型,說白了..就是調整這個系統服務的歸屬者..如果你想只有某一個系統用戶才可以使用這個服務..那你就用User..並且制定用戶名和密碼。

  具體各參數定義:

  LocalService:充當本地計算機上非特權用戶的帳戶,該帳戶將匿名憑據提供給所有遠程服務器。 

  LocalSystem:服務控制管理員使用的帳戶,它具有本地計算機上的許多權限並作為網絡上的計算機。 

  NetworkService:提供廣泛的本地特權的帳戶,該帳戶將計算機的憑據提供給所有遠程服務器。 
  User:由網絡上特定的用戶定義的帳戶。如果為 ServiceProcessInstaller.Account 成員指定 User,則會使系統在安裝服務時提示輸入有效的用戶名和密碼,除非您為 ServiceProcessInstaller 實例的 Username 和 Password 這兩個屬性設置值。  

  serviceInstaller1控件

  ServiceInstaller安裝一個類,該類擴展 ServiceBase 來實現服務。在安裝服務應用程序時由安裝實用工具調用該類。

  具體參數含義

  在這里主要修改其StartType屬性。此值指定了服務的啟動模式。

  Automatic 指示服務在系統啟動時將由(或已由)操作系統啟動。如果某個自動啟動的服務依賴於某個手動啟動的服務,則手動啟動的服務也會在系統啟動時自動啟動。 
  Disabled 指示禁用該服務,以便它無法由用戶或應用程序啟動。 
  Manual 指示服務只由用戶(使用“服務控制管理器”)或應用程序手動啟動。  

還有一些其他的一些屬性需要進行配置:

  ServiceName  服務在服務列表里的名字

  Description    服務在服務列表里的描述

  DisplayName  向用戶標示的友好名稱..沒搞懂..一般都跟上邊的ServiceName保持一致..

  5 查看當前的WCF服務。

      首先我們來查看我們部署好的Windows服務。

圖二 發布好的Windows服務

  6 引用當前的WCF服務  

      最后貼出相關代碼,請點擊這里進行下載!

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM