目录
首先说下题外话,有些园友看了前一篇【权限管理系统系列之序言】博客加了QQ群(186841119),看了我写的权限管理系统的相关文档(主要是介绍已经开发的功能),给出了一些建议,感觉非常好,希望后续有更多的园友能再接再厉给出更多的指导意见,在平常的开发中个人会结合你们的建议做出适当修改和完善,促进共同学习和进步。关于源码共享的问题,可能会过段时间公布,不会现在公开源码,个人还在不断完善中,等完成差不多后会公开源码。
客户端与服务器的通信在一个程序中会占住关键的作用,处理起来可能会有很多方式,比如说Remoting、Socket、WebServices、WCF等等都可以实现。本人这几种基本上都用过,Socket可能比较少些,一些聊天室的程序就会使用Socket,通过字节的形式接收数据;WebServices会WinCE开发中使用到,数据传输进行压缩,这样操作数据就比较方便,实时操作数据库;Remoting主要用在MIS系统的客户端与服务端通信,个人也说不出那种好;WCF也是我最近一两年才接触到的,公司现在使用的就是WCF通信的,个人感觉用WCF比较方便和简单,实用起来使用三个函数(一个函数是检测客户端与服务器端的心跳,一个是用于登录的、一个是公共的接口,基本上所有的客户端和服务端的通信都是用這个函数),這个函数可以搞定所有的客户端访问服务端的方法,所有的SQL在服务端执行,便于维护和日常的分工,不过在平常的开发中也不会分客户端和服务端的开发,基本上也是一个一个模块进行分工的。
WCF的配置(包括客户端和服务端)
客户端的配置文件:
1 <?xml version="1.0"?>
2 <configuration>
3 <system.serviceModel>
4 <bindings>
5 <netTcpBinding>
6 <binding name="TcpBinding_AppService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="10485760" maxBufferSize="10485760" maxConnections="10" maxReceivedMessageSize="10485760">
7 <readerQuotas maxDepth="32" maxStringContentLength="10485760" maxArrayLength="10485760" maxBytesPerRead="10485760" maxNameTableCharCount="10485760"/>
8 <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/>
9 <security mode="None">
10 <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign"/>
11 <message clientCredentialType="Windows"/>
12 </security>
13 </binding>
14
15 <binding name="TcpBinding_MessageService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="10485760" maxBufferSize="10485760" maxConnections="10" maxReceivedMessageSize="10485760">
16 <readerQuotas maxDepth="32" maxStringContentLength="10485760" maxArrayLength="10485760" maxBytesPerRead="10485760" maxNameTableCharCount="10485760"/>
17 <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/>
18 <security mode="None">
19 <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign"/>
20 <message clientCredentialType="Windows"/>
21 </security>
22 </binding>
23
24 </netTcpBinding>
25 </bindings>
26 <client>
27 <endpoint address="net.tcp://localhost:9090/AppService" binding="netTcpBinding" bindingConfiguration="TcpBinding_AppService" contract="IAppService" name="TcpBinding_AppService"/>
28
29 <endpoint address="net.tcp://localhost:7070/MessageService" binding="netTcpBinding" bindingConfiguration="TcpBinding_MessageService" contract="IMessageService" name="TcpBinding_MessageService"/>
30 </client>
31 </system.serviceModel>
32 </configuration>
服务端的配置文件:
1 <?xml version="1.0"?>
2 <configuration>
3 <system.serviceModel>
4 <services>
5 <service behaviorConfiguration="Service.Behavior" name="Server.AppService">
6 <endpoint address="AppService" binding="netTcpBinding" bindingConfiguration="AppServiceBinding" name="TcpBinding_AppService" contract="Server.IAppService" />
7 <endpoint address="AppService/mex" binding="mexTcpBinding" contract="IMetadataExchange" />
8 <host>
9 <baseAddresses>
10 <add baseAddress="net.tcp://localhost:9090" />
11 </baseAddresses>
12 </host>
13 </service>
14 <service behaviorConfiguration="Service.Behavior" name="Server.MessageService">
15 <endpoint address="MessageService" binding="netTcpBinding" bindingConfiguration="MessageServiceBinding" name="TcpBinding_MessageService" contract="Server.IMessageService" />
16 <endpoint address="MessageService/mex" binding="mexTcpBinding" contract="IMetadataExchange" />
17 <host>
18 <baseAddresses>
19 <add baseAddress="net.tcp://localhost:7070" />
20 </baseAddresses>
21 </host>
22 </service>
23 </services>
24 <bindings>
25 <netTcpBinding>
26 <binding name="AppServiceBinding" maxBufferSize="10485760" maxReceivedMessageSize="10485760">
27 <readerQuotas maxDepth="32" maxStringContentLength="10485760"
28 maxArrayLength="10485760" maxBytesPerRead="10485760" maxNameTableCharCount="10485760" />
29 <reliableSession ordered="true" inactivityTimeout="00:10:00"
30 enabled="false" />
31 <security mode="None" />
32 </binding>
33 <binding name="MessageServiceBinding" maxBufferSize="10485760" maxReceivedMessageSize="10485760">
34 <readerQuotas maxDepth="32" maxStringContentLength="10485760"
35 maxArrayLength="10485760" maxBytesPerRead="10485760" maxNameTableCharCount="10485760" />
36 <reliableSession ordered="true" inactivityTimeout="00:10:00"
37 enabled="false" />
38 <security mode="None" />
39 </binding>
40 </netTcpBinding>
41 </bindings>
42 <behaviors>
43 <serviceBehaviors>
44 <behavior name="Service.Behavior">
45 <serviceMetadata />
46 <serviceDebug includeExceptionDetailInFaults="true" />
47 <!--会话最大数量(并发会话)-->
48 <serviceThrottling maxConcurrentSessions="100" />
49 <!--数据序列最大量-->
50 <dataContractSerializer maxItemsInObjectGraph="10485760" />
51 </behavior>
52 <behavior name="mexConfig">
53 <serviceDebug includeExceptionDetailInFaults="True" />
54 <serviceMetadata />
55 </behavior>
56 </serviceBehaviors>
57 </behaviors>
58 </system.serviceModel>
59 </configuration>
以上为客户端与服务端的配置文件,有两个配置,一个为基本通信所用,一个为双工通信所用。
介绍完配置文件后再介绍实现函数:
1 [ServiceContract(Name = "IAppService", SessionMode = SessionMode.Allowed, Namespace = "http://tempuri.org/")] 2 public interface IAppService 3 { 4 //心跳 5 [OperationContract] 6 string HeartBeat(string echo); 7 8 //登录 9 [OperationContract] 10 bool Login(string UserName, string Password); 11 12 //统一的应用业务请求调用,用于会话控制和调用转发 13 [OperationContract] 14 Result AppCall(Request request); 15 } 16 17 #region * 推送消息 18 [ServiceContract(CallbackContract = typeof(IPushClient))] 19 public interface IMessageService 20 { 21 [OperationContract] 22 void RegisterClient(); 23 } 24 25 public interface IPushClient 26 { 27 [OperationContract(IsOneWay = true)] 28 void SendMessage(string message); 29 } 30 #endregion
分别有三个函数,一个是维持服务端与客户端的心跳,一个为登陆所用,一个为所有函数的接口,基本上所有的通信都是通过这个函数进行调用。最下面的为双工通信的定义,定义成回调函数的形式。
以上函数实现逻辑:
1 //每个会话一个实例,同一个会话下的多线程并发 2 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)] 3 public class AppService : IAppService 4 { 5 //记录是否登录 6 bool IsLogined = false; 7 Context context = new Context(); 8 9 private static readonly string _username = "Server"; 10 private static readonly string _password = "123456"; 11 12 //心跳请求,维持链路正常 13 public string HeartBeat(string echo) 14 { 15 Log.Log.Debug("收到请求:" + echo); 16 return "Re:" + echo; 17 } 18 19 //用于登录 20 public bool Login(string UserName, string Password) 21 { 22 Log.Log.Debug("收到请求:" + UserName + ";" + Password); 23 if (_username.Equals(Encrypt.DecryptDES(UserName, Const.EncryptKey)) && _password.Equals(Encrypt.DecryptDES(Password, Const.EncryptKey))) 24 { 25 IsLogined = true; 26 context.UserName = UserName; 27 return IsLogined; 28 } 29 else 30 { 31 IsLogined = false; 32 context.UserName = ""; 33 return IsLogined; 34 } 35 } 36 37 //统一的业务请求调用代理 38 delegate Result ActionDelegate(string reqdata); 39 //统一的应用业务请求调用,用于会话控制和调用转发 40 public Result AppCall(Request request) 41 { 42 if (!string.IsNullOrEmpty(request.data)) 43 { 44 if (request.data.Contains(JSON.CompressionFlag)) 45 { 46 Log.Log.Debug("收到请求:" + Compression.DecompressString(request.data.Replace(JSON.CompressionFlag, JSON.ReplaceFlag))); 47 } 48 else 49 { 50 Log.Log.Debug("收到请求:" + request.data); 51 } 52 } 53 if (!IsLogined) 54 { 55 Result result = new Result() { success = false, errors = "用户未登录,请登录后再提交请求!" }; 56 Log.Log.Info("Server.AppService.AppCall(Request request):" + JSON.Object2Json(result, false)); 57 return result; 58 } 59 60 if (request.action == null || request.method == null) 61 { 62 Result result = new Result() { success = false, errors = "请求数据格式不正确{request.action==null || request.method==null},请检查!" }; 63 Log.Log.Error("Server.AppService.AppCall(Request request)出错:" + JSON.Object2Json(result, false)); 64 return result; 65 66 } 67 try 68 { 69 Type type = Type.GetType("Server.Action." + request.action); 70 object action = Activator.CreateInstance(type, new object[] { this.context }); 71 ActionDelegate doAction = (ActionDelegate)Delegate.CreateDelegate(typeof(ActionDelegate), action, request.method); 72 Result result = doAction(request.data); 73 return result; 74 } 75 catch (Exception e) 76 { 77 Log.Log.Error("Server.AppService.AppCall(Request request)异常:" + e.ToString()); 78 return new Result() { success = false, errors = "执行业务请求错误!" }; 79 } 80 } 81 } 82 83 #region * 推送消息 84 [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] 85 public class MessageService : IMessageService, IDisposable 86 { 87 public static List<IPushClient> ClientCallbackList { get; set; } 88 public MessageService() 89 { 90 ClientCallbackList = new List<IPushClient>(); 91 } 92 93 public void RegisterClient() 94 { 95 var client = OperationContext.Current.GetCallbackChannel<IPushClient>(); 96 var id = OperationContext.Current.SessionId; 97 //Console.WriteLine("{0}registered.", id); 98 Log.Log.Info(string.Format("{0}registered.", id)); 99 OperationContext.Current.Channel.Closing+=new EventHandler(Channel_Closing); 100 ClientCallbackList.Add(client); 101 } 102 103 private void Channel_Closing(object sender, EventArgs e) 104 { 105 lock (ClientCallbackList) 106 { 107 ClientCallbackList.Remove((IPushClient)sender); 108 } 109 } 110 111 public void Dispose() 112 { 113 ClientCallbackList.Clear(); 114 } 115 } 116 #endregion 117 }
通信接口通过以上实体,success为函数执行状态,msg为函数返回的信息,data为返回的数据(格式为json格式的),errors为返回的错误信息,sql为函数执行的sql,返回给前台界面。
WCF服务生成客户端的配置文件步骤:
a.打开vs命令行,用cd进入到exe文件目录。
b.svcutil .exe
c.svcutil *.wsdl *.xsd
完成以上即可。
客户调用CS文件:
1 //------------------------------------------------------------------------------ 2 // <auto-generated> 3 // 此代码由工具生成。 4 // 运行时版本:4.0.30319.18444 5 // 6 // 对此文件的更改可能会导致不正确的行为,并且如果 7 // 重新生成代码,这些更改将会丢失。 8 // </auto-generated> 9 //------------------------------------------------------------------------------ 10 11 namespace Server.Domain 12 { 13 using System.Runtime.Serialization; 14 15 16 [System.Diagnostics.DebuggerStepThroughAttribute()] 17 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")] 18 [System.Runtime.Serialization.DataContractAttribute(Name = "Request", Namespace = "http://schemas.datacontract.org/2004/07/Server.Domain")] 19 public partial class Request : object, System.Runtime.Serialization.IExtensibleDataObject 20 { 21 22 private System.Runtime.Serialization.ExtensionDataObject extensionDataField; 23 24 private string actionField; 25 26 private string dataField; 27 28 private string methodField; 29 30 public System.Runtime.Serialization.ExtensionDataObject ExtensionData 31 { 32 get 33 { 34 return this.extensionDataField; 35 } 36 set 37 { 38 this.extensionDataField = value; 39 } 40 } 41 42 [System.Runtime.Serialization.DataMemberAttribute()] 43 public string action 44 { 45 get 46 { 47 return this.actionField; 48 } 49 set 50 { 51 this.actionField = value; 52 } 53 } 54 55 [System.Runtime.Serialization.DataMemberAttribute()] 56 public string data 57 { 58 get 59 { 60 return this.dataField; 61 } 62 set 63 { 64 this.dataField = value; 65 } 66 } 67 68 [System.Runtime.Serialization.DataMemberAttribute()] 69 public string method 70 { 71 get 72 { 73 return this.methodField; 74 } 75 set 76 { 77 this.methodField = value; 78 } 79 } 80 } 81 82 [System.Diagnostics.DebuggerStepThroughAttribute()] 83 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")] 84 [System.Runtime.Serialization.DataContractAttribute(Name = "Result", Namespace = "http://schemas.datacontract.org/2004/07/Server.Domain")] 85 public partial class Result : object, System.Runtime.Serialization.IExtensibleDataObject 86 { 87 88 private System.Runtime.Serialization.ExtensionDataObject extensionDataField; 89 90 private string dataField; 91 92 private string errorsField; 93 94 private string msgField; 95 96 private string sqlField; 97 98 private bool successField; 99 100 public System.Runtime.Serialization.ExtensionDataObject ExtensionData 101 { 102 get 103 { 104 return this.extensionDataField; 105 } 106 set 107 { 108 this.extensionDataField = value; 109 } 110 } 111 112 [System.Runtime.Serialization.DataMemberAttribute()] 113 public string data 114 { 115 get 116 { 117 return this.dataField; 118 } 119 set 120 { 121 this.dataField = value; 122 } 123 } 124 125 [System.Runtime.Serialization.DataMemberAttribute()] 126 public string errors 127 { 128 get 129 { 130 return this.errorsField; 131 } 132 set 133 { 134 this.errorsField = value; 135 } 136 } 137 138 [System.Runtime.Serialization.DataMemberAttribute()] 139 public string msg 140 { 141 get 142 { 143 return this.msgField; 144 } 145 set 146 { 147 this.msgField = value; 148 } 149 } 150 151 [System.Runtime.Serialization.DataMemberAttribute()] 152 public string sql 153 { 154 get 155 { 156 return this.sqlField; 157 } 158 set 159 { 160 this.sqlField = value; 161 } 162 } 163 164 [System.Runtime.Serialization.DataMemberAttribute()] 165 public bool success 166 { 167 get 168 { 169 return this.successField; 170 } 171 set 172 { 173 this.successField = value; 174 } 175 } 176 } 177 } 178 179 180 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 181 [System.ServiceModel.ServiceContractAttribute(ConfigurationName = "IAppService")] 182 public interface IAppService 183 { 184 185 [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IAppService/HeartBeat", ReplyAction = "http://tempuri.org/IAppService/HeartBeatResponse")] 186 string HeartBeat(string echo); 187 188 [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IAppService/Login", ReplyAction = "http://tempuri.org/IAppService/LoginResponse")] 189 bool Login(string UserName, string Password); 190 191 [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IAppService/AppCall", ReplyAction = "http://tempuri.org/IAppService/AppCallResponse")] 192 Server.Domain.Result AppCall(Server.Domain.Request request); 193 } 194 195 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 196 public interface IAppServiceChannel : IAppService, System.ServiceModel.IClientChannel 197 { 198 } 199 200 [System.Diagnostics.DebuggerStepThroughAttribute()] 201 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 202 public partial class AppServiceClient : System.ServiceModel.ClientBase<IAppService>, IAppService 203 { 204 205 public AppServiceClient() 206 { 207 } 208 209 public AppServiceClient(string endpointConfigurationName) : 210 base(endpointConfigurationName) 211 { 212 } 213 214 public AppServiceClient(string endpointConfigurationName, string remoteAddress) : 215 base(endpointConfigurationName, remoteAddress) 216 { 217 } 218 219 public AppServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 220 base(endpointConfigurationName, remoteAddress) 221 { 222 } 223 224 public AppServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 225 base(binding, remoteAddress) 226 { 227 } 228 229 public string HeartBeat(string echo) 230 { 231 return base.Channel.HeartBeat(echo); 232 } 233 234 public bool Login(string UserName, string Password) 235 { 236 return base.Channel.Login(UserName, Password); 237 } 238 239 public Server.Domain.Result AppCall(Server.Domain.Request request) 240 { 241 return base.Channel.AppCall(request); 242 } 243 } 244 245 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 246 [System.ServiceModel.ServiceContractAttribute(ConfigurationName = "IMessageService", CallbackContract = typeof(IMessageServiceCallback))] 247 public interface IMessageService 248 { 249 250 [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IMessageService/RegisterClient", ReplyAction = "http://tempuri.org/IMessageService/RegisterClientResponse")] 251 void RegisterClient(); 252 } 253 254 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 255 public interface IMessageServiceCallback 256 { 257 258 [System.ServiceModel.OperationContractAttribute(IsOneWay = true, Action = "http://tempuri.org/IMessageService/SendMessage")] 259 void SendMessage(string message); 260 } 261 262 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 263 public interface IMessageServiceChannel : IMessageService, System.ServiceModel.IClientChannel 264 { 265 } 266 267 [System.Diagnostics.DebuggerStepThroughAttribute()] 268 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 269 public partial class MessageServiceClient : System.ServiceModel.DuplexClientBase<IMessageService>, IMessageService 270 { 271 272 public MessageServiceClient(System.ServiceModel.InstanceContext callbackInstance) : 273 base(callbackInstance) 274 { 275 } 276 277 public MessageServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) : 278 base(callbackInstance, endpointConfigurationName) 279 { 280 } 281 282 public MessageServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) : 283 base(callbackInstance, endpointConfigurationName, remoteAddress) 284 { 285 } 286 287 public MessageServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 288 base(callbackInstance, endpointConfigurationName, remoteAddress) 289 { 290 } 291 292 public MessageServiceClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 293 base(callbackInstance, binding, remoteAddress) 294 { 295 } 296 297 public void RegisterClient() 298 { 299 base.Channel.RegisterClient(); 300 } 301 }
1 public static AppServiceClient AppService; 2 public static ServiceHearBeat HeartBeat; 3 /// <summary> 4 /// 连接是否正常 5 /// </summary> 6 public static bool IsAlive 7 { 8 get { return HeartBeat.IsAlive; } 9 } 10 /// <summary> 11 /// 是否已经弹出过提示 12 /// </summary> 13 public static bool HasNotice 14 { 15 get { return HeartBeat.HasNotice; } 16 set { HeartBeat.HasNotice = value; } 17 } 18 19 20 //连接到应用服务 21 if (!ConnectToAppServer()) 22 { 23 Comm.MessageBox.Info("连接服务端失败,请检查服务端是否已经启动。"); 24 return; 25 } 26 27 //连接到应用服务 28 public static bool ConnectToAppServer() 29 { 30 try 31 { 32 //如果服务存在,则尝试关闭链接 33 if (AppService != null) 34 { 35 try 36 { 37 AppService.Close(); 38 } 39 catch (Exception) 40 { 41 } 42 } 43 //创建新的服务对象,并进行连接和登录 44 AppService = new AppServiceClient(); 45 AppService.Open(); 46 if (!AppClient.AppService.Login(Encrypt.EncryptDES("Server", Const.EncryptKey), Encrypt.EncryptDES("123456", Const.EncryptKey))) 47 { 48 return false; 49 } 50 return true; 51 } 52 catch (EndpointNotFoundException) 53 { 54 Log.Error("连接服务端失败,服务端IP端口配置错误或者是服务端尚未启动"); 55 } 56 catch (SocketException) 57 { 58 Log.Error("连接服务端失败,服务端IP端口配置错误或者是服务端尚未启动"); 59 } 60 catch (Exception e) 61 { 62 Log.Error("连接服务端出错:" + e.ToString()); 63 } 64 return false; 65 66 }
以上即可完成对客户端连接服务端了,基本上完成以后步骤可以说完成了WCF的通信,实现了客户端连接服务端。
服务端打开的效果:
客户端打开的效果:
如对权限管理系统有兴趣可加QQ群:186841119,可参与相关话题讨论。相互学习交流,共同进步。