在我之前的一篇博客中我介绍了如何发布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:定义服务接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | 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函数中启动这个服务。
1 2 3 4 5 6 7 8 | #region 开启服务 ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new CoreService() }; ServiceBase.Run(ServicesToRun); #endregion |
在CoreService.cs中,我们通过重载OnStart和OnStop函数开启和关闭WCF服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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具体的优势可以查阅相关资料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <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服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @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服务
最后贴出相关代码,请点击进行下载!