在我之前的一篇博客中我介紹了如何發布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服務 
最后貼出相關代碼,請點擊這里進行下載!
