Document

将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:定义服务接口    

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 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 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 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 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 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 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服务  

      最后贴出相关代码,请点击这里进行下载!

 

posted @ 2017-08-25 17:05  从未被超越  阅读(341)  评论(0编辑  收藏  举报