VB.net Wcf事件广播(订阅、发布)

这篇东西原写在csdn.net上,最近新开通了博客想把零散在各处的都转移到一处。

一、源起 学WCF有一段时间了,可是无论是微软的WebCast还是其他网上的教程,亦或我购买的几本书中,都没有怎么提到服务器端事件的订阅。(后来购买的一本WCF服务编程中提供了关于订阅和发布的例子) 网上也查了很多,但大部分都没有清楚地讲到这部分内容,有的虽然是讲这部分内容,但也是语焉不详。最重要的部分都没有讲到(也许是因为水平太差,看不懂)。而且网上和书上所用的语言都是C#,这让人很恼火,虽然我能使用C#,但总是不如用了十几年的VB看起来顺眼。而且最关键的是C#关于委托和事件处理上和VB有着很大的不同。比如:在C#中,事件本质上就是委托,而且处理方式都一样。但在VB中事件和委托很多差别,尤其是在处理方式上。比如,VB中事件不能直接支持返回列表,虽然编译器提供了一个变形后的变量来提供支持,但和c#中的直接遍历还是差很多。所以在把C#代码转换成VB代码时,一遇到事件或委托就很难办,因为用的方法不同。直接把代码语法改掉是编译不过的。这让我着实忧闷了很久。因为写项目,一定会遇到订阅和发布的。 前几天在网上又搜索相关的文章,这次也许运气很好,找到了一篇讲事件广播的,是一个叫张玉彬的博客上的文章,文章名叫:WCF从理论到实践(8):事件广播 ,文章地址:http://www.cnblogs.com/jillzhang/archive/2008/02/24/1079339.html  。当然一点也不意外,这篇文章里面用的代码是C#(搞什么?VB没人用了吗?),最可心的是文章提供了例子代码。没什么好说的,把代码当回来,打开直接在VS中用VB重写一遍。。。。。所以,就有了这篇日志。呵呵。这样,也算是对自己这段时间学习WCF的一个小总结。如果能帮上哪位学VB朋友的忙的话那就最好不过了。呵呵。
二、实例分析 1、这是一个微软培训教程中用过的一个例子。我记得在学习Remoting时学过这个项目。很简单的一个例子: 服务器上维护一个JOB List,客户端用来提交、执行JOB,服务器将JOB状态和结果通知给每个客户端。这样就必须实现客户端订阅服务端事件。看起来是这个样子的。其实,非网络的事件,我们知道是比较容易实现的。也就是自定义事件的问题。一直以来,我总以为客户端订阅服务器事件也象在单机程序中那样。但由于两端不在一个程序域内,所以无法实现事件在两端的传播。我曾试图把委托或事件做为数据契约(DataContract)来处理,结果大家都知道。委托根本就无法传递和准确序列化,而事件根本就不支持数据契约(后来在书看到,事件必须添加FiledData特性才能支持契约,而委托虽然支持契约,却无法实现正确序列化)。不废话了。 2、原文章部分  略。(运行部分截图也见上文提到 的文章,不管是不是原创,毕竟对我有很多帮助,就不抢别人功劳了。呵呵。) 三、我的代码 由于这个例子相当简单,再加上本人也没什么写文章的天赋,所以,话就不多说了。直接上代码。。。。  1、使用事件进行广播 1)项目用到的JOB类:

[vb] view plaincopy

  1. <DataContract()>
  2. Public Class job
  3. Private m_Name As String
  4. Private m_Status As String
  5. Private m_LastRunTime As DateTime
  6. Private m_LastRunResult As String
  7. Public Sub New()
  8. Me.Status = "Sleeping"
  9. Me.LastRunResult = ""
  10. End Sub
  11. <DataMember()>
  12. Public Property Name As String
  13. Get
  14. Return m_Name
  15. End Get
  16. Set(ByVal value As String)
  17. m_Name = value
  18. End Set
  19. End Property
  20. <DataMember()>
  21. Public Property Status As String
  22. Get
  23. Return m_Status
  24. End Get
  25. Set(ByVal value As String)
  26. m_Status = value
  27. End Set
  28. End Property
  29. <DataMember()>
  30. Public Property LastRunTime As DateTime
  31. Get
  32. Return m_LastRunTime
  33. End Get
  34. Set(ByVal value As DateTime)
  35. m_LastRunTime = value
  36. End Set
  37. End Property
  38. <DataMember()>
  39. Public Property LastRunResult As String
  40. Get
  41. Return m_LastRunResult
  42. End Get
  43. Set(ByVal value As String)
  44. m_LastRunResult = value
  45. End Set
  46. End Property
  47. End Class

2)项目中用到的回调接口

[vb] view plaincopy

  1. <ServiceContract()>
  2. Public Interface ICallback
  3. <OperationContract(IsOneWay:=True)>
  4. Sub StatusChanged(ByVal job As job)
  5. <OperationContract(IsOneWay:=True)>
  6. Sub OnAdd(ByVal newJob As job)
  7. End Interface

3)项目中用到的服务契约

[vb] view plaincopy

  1. ‘<ServiceContract(SessionMode:=SessionMode.Required, CallbackContract:=GetType(ICallback))>
  2. ‘不使用事件,只使用委托时,是否可以不用会话
  3. ‘<ServiceContract(SessionMode:=SessionMode.Allowed, CallbackContract:=GetType(ICallback))>
  4. <ServiceContract(CallbackContract:=GetType(ICallback))>
  5. Public Interface IService
  6. ‘<OperationContract()>
  7. ‘Function GetData(ByVal value As Integer) As String
  8. ‘<OperationContract()>
  9. ‘Function GetDataUsingDataContract(ByVal composite As CompositeType) As CompositeType
  10. ‘ TODO: 在此添加您的服务操作
  11. ‘IsInitiating 表示 是否可以在服务器上启动会话
  12. ‘IsTerminating 指示服务操作在发送答复消息(如果存在)后,是否会导致服务器关闭会话。
  13. <OperationContract(IsOneWay:=True, IsInitiating:=True, IsTerminating:=False)>
  14. Sub Accept()
  15. <OperationContract(IsOneWay:=True, IsInitiating:=True, IsTerminating:=False)>
  16. Sub DoJob(ByVal jobName As String)
  17. <OperationContract(IsOneWay:=True, IsInitiating:=True, IsTerminating:=False)>
  18. Sub AddJob(ByVal newJob As job)
  19. <OperationContract(IsOneWay:=False, IsInitiating:=True, IsTerminating:=False)>
  20. Function GetJobs() As List(Of job)
  21. End Interface

说明:按照 网上文章的说法,事件广播要使用到 会话。但实际上,会话在这里并不重要。这我在后面验证。 4)项目中的服务实现类 这里,要用到InstanceContextMode特性,因为项目在这时还要保持会话。

[vb] view plaincopy

[vb] view plaincopy

  1. ‘<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession, ConcurrencyMode:=ConcurrencyMode.Multiple)>
  2. ‘不使用事件,只使用委托,是否可以不用会话
  3. ‘<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>
  4. <ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>
  5. Public Class JobServerImplement
  6. Implements IService
  7. Public Shared m_jobs As List(Of job) ‘用来返回 job列表
  8. Public Shared jobs_Lock As Object  ‘同步锁用
  9. ‘事件委托定义
  10. Public Delegate Sub JobServerEventHandler(ByVal sender As Object, ByVal e As JobCallbackEventArg)
  11. ‘定义共享的 事件 列表
  12. Public Shared onChangeEvents As List(Of JobServerEventHandler)
  13. Public Shared onAddEvents As List(Of JobServerEventHandler)
  14. ‘定义客户端 回调通道 列表
  15. Public Shared clientCallBackChannles As List(Of ICallback)
  16. ‘job状态改变事件定义,事件必须定义为 共享
  17. ‘Public Shared Event onJobStatusChanged As JobServerEventHandler
  18. Public Shared Custom Event onJobStatusChanged As JobServerEventHandler
  19. AddHandler(ByVal value As JobServerEventHandler)
  20. If jobs_Lock Is Nothing Then
  21. jobs_Lock = New Object()
  22. End If
  23. If onChangeEvents Is Nothing Then
  24. SyncLock jobs_Lock
  25. onChangeEvents = New List(Of JobServerEventHandler)
  26. End SyncLock
  27. End If
  28. SyncLock jobs_Lock
  29. onChangeEvents.Add(value)
  30. Console.WriteLine("AddHandler Done!")
  31. End SyncLock
  32. End AddHandler
  33. RemoveHandler(ByVal value As JobServerEventHandler)
  34. If onChangeEvents Is Nothing Then
  35. Return
  36. End If
  37. SyncLock jobs_Lock
  38. onChangeEvents.Remove(value)
  39. Console.WriteLine("RemoveHandler Done!")
  40. End SyncLock
  41. End RemoveHandler
  42. RaiseEvent(ByVal sender As Object, ByVal e As JobCallbackEventArg)
  43. For Each tmpH As JobServerEventHandler In onChangeEvents
  44. tmpH.BeginInvoke(sender, e, Nothing, Nothing) ‘ New System.AsyncCallback(AddressOf EndAsync), Nothing)
  45. Console.WriteLine("RaiseEvent")
  46. Next
  47. End RaiseEvent
  48. End Event
  49. ‘Public Custom Event
  50. ‘内部事件传递 委托
  51. ‘Private MyChangeHandler As JobServerEventHandler
  52. ‘job添加事件定义,事件必须 共享
  53. ‘Public Shared Event onAdd As JobServerEventHandler
  54. ‘内部事件传递委托
  55. ‘Private MyAddHandler As JobServerEventHandler
  56. ‘回调 接口
  57. Dim callbackInterface As ICallback
  58. ‘每一次回调,分开后,如果 哪个客户端失败,不会影响其他存在客户端
  59. Private Sub CallBackOnce(ByVal theCallBack As ICallback, ByVal e As JobCallbackEventArg)
  60. Try
  61. If e.ChangeReason = "Add" Then
  62. Console.WriteLine("CallBackOnce reason: Add")
  63. theCallBack.OnAdd(e.Job)
  64. Else
  65. Console.WriteLine("CallBackOnce reason: Modi")
  66. theCallBack.StatusChanged(e.Job)
  67. End If
  68. Catch ex As Exception
  69. Console.WriteLine("CallBackOnce Got Error:" & ex.Message)
  70. End Try
  71. End Sub
  72. ‘不使用事件,直接使用委托来回调客户端
  73. Private Sub Server_Delegate_StatusChanged(ByVal sender As Object, ByVal e As JobCallbackEventArg)
  74. Try
  75. If e Is Nothing Or e.Job Is Nothing Then
  76. Console.WriteLine("Server_Delegate_StatusChanged e Or e.Job is nothing.")
  77. Exit Sub
  78. Else
  79. Console.WriteLine("Server_Delegate_StatusChanged Begin!")
  80. ‘Dim callBackOnce As ICallback
  81. For Each callOnce As ICallback In clientCallBackChannles
  82. CallBackOnce(callOnce, e)
  83. ‘If e.ChangeReason = "Add" Then
  84. ‘    Console.WriteLine("Server_Delegate_StatusChanged reason: Add")
  85. ‘    callOnce.OnAdd(e.Job)
  86. ‘Else
  87. ‘    Console.WriteLine("Server_Delegate_StatusChanged reason: Modi")
  88. ‘    callOnce.StatusChanged(e.Job)
  89. ‘End If
  90. Next
  91. Console.WriteLine("Server_Delegate_StatusChanged Done!")
  92. End If
  93. Catch ex As Exception
  94. Console.WriteLine("Server_OnStatusChange  Errors:" & ex.Message)
  95. End Try
  96. End Sub
  97. ‘服务器端状态改变 ,则回调客户端 方法
  98. Private Sub Server_OnStatusChanged(ByVal sender As Object, ByVal e As JobCallbackEventArg)
  99. Try
  100. If e Is Nothing Or e.Job Is Nothing Then
  101. Console.WriteLine("Server_OnStatusChange e Or e.Job is nothing.")
  102. Exit Sub
  103. Else
  104. Console.WriteLine("Server_OnStatusChange Begin!")
  105. ‘回调
  106. If e.ChangeReason = "Add" Then
  107. Console.WriteLine("server_onStatusChanged reason: Add")
  108. ‘callbackInterface.StatusChanged(e.Job)
  109. callbackInterface.OnAdd(e.Job)
  110. Else
  111. Console.WriteLine("server_onStatusChanged reason: Modi")
  112. callbackInterface.StatusChanged(e.Job)
  113. End If
  114. Console.WriteLine("Server_OnStatusChange Done!")
  115. End If
  116. Catch ex As Exception
  117. Console.WriteLine("Server_OnStatusChange  Errors:" & ex.Message)
  118. End Try
  119. End Sub
  120. ‘异步调用结束 处理
  121. Private Sub EndAsync(ByVal ar As IAsyncResult)
  122. ‘定义 处理 委托
  123. Dim dEndHandler As JobServerEventHandler = Nothing
  124. Console.WriteLine("EndAsync Begin!")
  125. Try
  126. ‘取得 异步 参数 返回的 IAsyncResult,并转换为 远程 类型
  127. ‘定义远程异步处理参数, 对委托的异步操作结果
  128. Dim arRemote As System.Runtime.Remoting.Messaging.AsyncResult = CType(ar, System.Runtime.Remoting.Messaging.AsyncResult)
  129. ‘取得 返回 的异步 委托,并转换为 本地的事件委托
  130. dEndHandler = CType(arRemote.AsyncDelegate, JobServerEventHandler)
  131. ‘进行 异步 调用结束  处理
  132. dEndHandler.EndInvoke(ar)
  133. Console.WriteLine("EndAsync End!")
  134. Catch ex As Exception
  135. ‘发生错误 ,从事件委托 列表中 去除该 委托
  136. RemoveHandler onJobStatusChanged, dEndHandler
  137. ‘RemoveHandler onAdd, dEndHandler
  138. Console.WriteLine("EndAsync Errors:" & ex.Message)
  139. End Try
  140. End Sub
  141. ‘广播事件
  142. Private Sub BroadcastEvent(ByVal e As JobCallbackEventArg) ‘, ByVal tmpHandler As JobServerEventHandler)
  143. ‘判断是否有订阅事件
  144. ‘C#中 可以使用 null判断,但VB中不可以,但却可以直接 调用,不会出现异常
  145. Console.WriteLine("BroadcastEvent Begin!")
  146. Try
  147. ‘For Each tmpH As JobServerEventHandler In onChangeEvents  ‘.GetInvocationList() ‘onJobStatusChangedEvent.GetInvocationList()
  148. ‘    ‘异步调用
  149. ‘    tmpH.BeginInvoke(Me, e, New System.AsyncCallback(AddressOf EndAsync), Nothing)
  150. ‘Next
  151. ‘保存了客户端通道列表后,不需要 委托列表直接在通道列表中进行回调即可
  152. Server_Delegate_StatusChanged(Me, e)
  153. Console.WriteLine("BroadcastEvent End!")
  154. Catch ex As Exception
  155. Console.WriteLine("BroadcastEvent Errors:" & ex.Message)
  156. End Try
  157. End Sub
  158. Public Sub Accept() Implements IService.Accept
  159. Console.WriteLine("Accept Begin!")
  160. Console.WriteLine("启动服务实例")
  161. Try
  162. ‘callBack = OperationContext.Current.GetCallbackChannel<Core.ICallback>()
  163. ‘取得调用当前操作的客户端实例的通道
  164. callbackInterface = OperationContext.Current.GetCallbackChannel(Of ICallback)()
  165. ‘添加 服务器 端委托 链表
  166. ‘MyChangeHandler = New JobServerEventHandler(AddressOf Me.Server_OnStatusChanged)
  167. ‘AddHandler onJobStatusChanged, MyChangeHandler
  168. ‘AddHandler onJobStatusChanged, AddressOf Me.Server_OnStatusChanged
  169. ‘MyAddHandler = New JobServerEventHandler(AddressOf Me.Server_OnAdd)
  170. ‘AddHandler onAdd, MyAddHandler
  171. ‘如果不用事件,直接使用委托
  172. AddDelegate(AddressOf Me.Server_Delegate_StatusChanged)
  173. ‘保存 回调通道列表
  174. AddCallBack(callbackInterface)
  175. Catch ex As Exception
  176. Console.WriteLine("Accept Errors: " & ex.Message)
  177. End Try
  178. Console.WriteLine("Accept End!")
  179. End Sub
  180. Private Sub AddCallBack(ByVal chl As ICallback)
  181. Console.WriteLine("AddCallBack Begin!")
  182. Try
  183. If jobs_Lock Is Nothing Then
  184. jobs_Lock = New Object()
  185. End If
  186. If clientCallBackChannles Is Nothing Then
  187. SyncLock jobs_Lock
  188. clientCallBackChannles = New List(Of ICallback)
  189. End SyncLock
  190. End If
  191. SyncLock jobs_Lock
  192. clientCallBackChannles.Add(chl)
  193. Console.WriteLine("AddCallBack Done!")
  194. End SyncLock
  195. Catch ex As Exception
  196. Console.WriteLine("AddCallBack Got Error:" & ex.Message)
  197. End Try
  198. End Sub
  199. Private Sub AddDelegate(ByVal theDelegate As JobServerEventHandler)
  200. Console.WriteLine("AddDelegate Begin!")
  201. Try
  202. If jobs_Lock Is Nothing Then
  203. jobs_Lock = New Object()
  204. End If
  205. If onChangeEvents Is Nothing Then
  206. SyncLock jobs_Lock
  207. onChangeEvents = New List(Of JobServerEventHandler)
  208. End SyncLock
  209. End If
  210. SyncLock jobs_Lock
  211. onChangeEvents.Add(theDelegate)
  212. Console.WriteLine("AddDelegate Done!")
  213. End SyncLock
  214. Catch ex As Exception
  215. Console.WriteLine("AddDelegate Got Error:" & ex.Message)
  216. End Try
  217. End Sub
  218. Public Sub AddJob(ByVal newJob As job) Implements IService.AddJob
  219. Console.WriteLine("AddJob")
  220. Try
  221. If jobs_Lock Is Nothing Then
  222. jobs_Lock = New Object()
  223. End If
  224. If m_jobs Is Nothing Then
  225. SyncLock jobs_Lock
  226. m_jobs = New List(Of job)
  227. End SyncLock
  228. End If
  229. For Each jobItem As job In m_jobs
  230. If newJob.Name = jobItem.Name Then
  231. Console.WriteLine("Job 已存在。")
  232. Return
  233. End If
  234. Next
  235. ‘添加
  236. newJob.Status = "Sleeping"
  237. newJob.LastRunResult = "Created"
  238. SyncLock jobs_Lock
  239. m_jobs.Add(newJob)
  240. End SyncLock
  241. Console.WriteLine("AddJob 添加完毕。开始广播事件...")
  242. ‘引发事件
  243. Dim e As JobCallbackEventArg = New JobCallbackEventArg()
  244. e.Job = newJob
  245. e.ChangeReason = "Add"
  246. ‘广播事件
  247. BroadcastEvent(e)
  248. ‘BroadcastEvent(e, onAdd)
  249. ‘RaiseEvent onAdd(Me, e)
  250. ‘RaiseEvent onJobStatusChanged(Me, e)
  251. Console.WriteLine("AddJob 广播事件完毕。")
  252. Catch ex As Exception
  253. Console.WriteLine("AddJob Errors: " & ex.Message)
  254. End Try
  255. Console.WriteLine("AddJob End!")
  256. End Sub
  257. ‘内部方法,用来取得 指定名称的 job
  258. Private Function GetJobByname(ByVal theJobName As String) As job
  259. For Each jobItem As job In m_jobs
  260. If jobItem.Name = theJobName Then
  261. Return jobItem
  262. End If
  263. Next
  264. Return Nothing
  265. End Function
  266. Public Sub DoJob(ByVal jobName As String) Implements IService.DoJob
  267. Dim theJob As job = GetJobByname(jobName)
  268. Try
  269. Dim e As JobCallbackEventArg = New JobCallbackEventArg()
  270. If theJob.Status = "Finished" Then
  271. Return
  272. End If
  273. theJob.Status = "Running"
  274. theJob.LastRunResult = "Working"
  275. theJob.LastRunTime = DateTime.Now
  276. e.Job = theJob
  277. ‘广播事件
  278. BroadcastEvent(e)
  279. Console.WriteLine("任务" + theJob.Name + "正在执行")
  280. ‘耗时
  281. System.Threading.Thread.Sleep(1000 * 10)
  282. e = New JobCallbackEventArg()
  283. theJob.Status = "Finished"
  284. theJob.LastRunTime = DateTime.Now
  285. theJob.LastRunResult = "Done!"
  286. e.Job = theJob
  287. ‘广播事件
  288. BroadcastEvent(e)
  289. Catch ex As Exception
  290. ‘将 job状态修改 为
  291. theJob.Status = "Sleeping"
  292. theJob.LastRunResult = "Fail"
  293. End Try
  294. End Sub
  295. Public Function GetJobs() As System.Collections.Generic.List(Of job) Implements IService.GetJobs
  296. ‘System.Threading.Thread.Sleep(1000 * 5) ‘为何要如此设置?耗时吗?还是为了 错开 同步 问题
  297. Console.WriteLine("GetJobs")
  298. Return m_jobs
  299. End Function
  300. End Class
  301. ‘回调用 参数 类别
  302. Public Class JobCallbackEventArg
  303. Inherits EventArgs
  304. Private m_CallbackJob As job
  305. Private m_ChangeReason As String
  306. Public Property Job As job
  307. Get
  308. Return m_CallbackJob
  309. End Get
  310. Set(ByVal value As job)
  311. m_CallbackJob = value
  312. End Set
  313. End Property
  314. Public Property ChangeReason As String
  315. Get
  316. Return m_ChangeReason
  317. End Get
  318. Set(ByVal value As String)
  319. m_ChangeReason = value
  320. End Set
  321. End Property
  322. End Class

5)承载服务代码

[vb] view plaincopy

  1. Imports System.Runtime.Serialization
  2. Imports System.ServiceModel
  3. Module Module1
  4. Sub Main()
  5. Using host As ServiceHost = New ServiceHost(GetType(WcfJobService.JobServerImplement))
  6. host.Open()
  7. Console.WriteLine("Service Started!")
  8. Console.WriteLine("Press any key to Exit!")
  9. Console.ReadKey()
  10. host.Close()
  11. End Using
  12. End Sub
  13. End Module

承载服务配置 app.config 上文提到 的文章的例子中并没有关于Mex终结点,之所以在这里配置无数据终结点,是为了让客户端获取代理方便些。而且,在这里用的绑定是TCP,也就是说,这是个天然支持回调的协议。而且速度也很快,只是IIS6无法承载

[html] view plaincopy

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3. <system.diagnostics>
  4. <sources>
  5. <!-- 本节定义 My.Application.Log 的登录配置-->
  6. <source name="DefaultSource" switchName="DefaultSwitch">
  7. <listeners>
  8. <add name="FileLog"/>
  9. <!-- 取消注释以下一节可写入应用程序事件日志-->
  10. <!--<add name="EventLog"/>-->
  11. </listeners>
  12. </source>
  13. </sources>
  14. <switches>
  15. <add name="DefaultSwitch" value="Information" />
  16. </switches>
  17. <sharedListeners>
  18. <add name="FileLog"
  19. type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"
  20. initializeData="FileLogWriter"/>
  21. <!-- 取消注释以下一节并用应用程序名替换 APPLICATION_NAME 可写入应用程序事件日志-->
  22. <!--<add name="EventLog" type="System.Diagnostics.EventLogTraceListener" initializeData="APPLICATION_NAME"/> -->
  23. </sharedListeners>
  24. </system.diagnostics>
  25. <system.serviceModel>
  26. <services>
  27. <service name="WcfJobService.JobServerImplement" behaviorConfiguration ="MEX">
  28. <host>
  29. <baseAddresses>
  30. <!--<add baseAddress ="net.tcp://localhost:8322/"/>-->
  31. <add baseAddress ="http://localhost:8300/"/>
  32. </baseAddresses>
  33. </host>
  34. <!--<endpoint address="Event" binding="netTcpBinding"
  35. bindingConfiguration="DuplexBinding" contract="WcfJobService.IService" />-->
  36. <endpoint address="Event" binding="wsDualHttpBinding"
  37. bindingConfiguration="DuplexWsHttpBinding" contract="WcfJobService.IService" />
  38. <!--<endpoint address ="MEX" binding ="mexTcpBinding" contract ="IMetadataExchange" />-->
  39. <endpoint address ="http://localhost:8080/MEX" binding ="mexHttpBinding" contract ="IMetadataExchange" />
  40. </service>
  41. </services>
  42. <bindings>
  43. <wsDualHttpBinding>
  44. <binding name="DuplexWsHttpBinding" sendTimeout ="00:00:05"></binding>
  45. </wsDualHttpBinding>
  46. <netTcpBinding>
  47. <binding name ="DuplexBinding" sendTimeout ="00:00:03" ></binding>
  48. </netTcpBinding>
  49. <wsHttpBinding>
  50. <binding name ="wsHttpBindingCallBack" sendTimeout ="00:00:05"></binding>
  51. </wsHttpBinding>
  52. </bindings>
  53. <behaviors >
  54. <serviceBehaviors >
  55. <behavior name="MEX">
  56. <serviceMetadata />
  57. </behavior>
  58. </serviceBehaviors>
  59. </behaviors>
  60. </system.serviceModel>
  61. </configuration>

QQ日志其实真的挺笨的。这和腾迅出的所有产品一样,笨得要死。只是我没有用其他的博客之类的,也犯不着为了一篇文章去申请了。呵呵。其实,细想起来,我们每天离不开的很多东西其实都是粗制品,比如WINDOWS,比如QQ,真的是不用不行,用着着实不爽。呵呵。是不是和我们人生一样,不完美的才是真实的。MSN也好,ICQ也好,其实都比QQ好用得多,但是都死掉了。就拿QQ那个视频来说,真是慢得要死,4M的宽带看高清都不卡,它偏偏要卡,画面只有那么小,真不知腾迅的技术人员是怎么想的。还有那个远程,真是最没用的东西!比起TeamStream差得天上去了。不说了。QQ日志一写多了就犯病。只好把下面的内容再多分几篇了。

6)客户端

配置文件app.config

[html] view plaincopy

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3. <startup>
  4. <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" />
  5. </startup>
  6. <system.serviceModel>
  7. <bindings>
  8. <netTcpBinding>
  9. <binding name="NetTcpBinding_IService" closeTimeout="00:01:00"
  10. openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
  11. transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
  12. hostNameComparisonMode="StrongWildcard" listenBacklog="10"
  13. maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10"
  14. maxReceivedMessageSize="65536">
  15. <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
  16. maxBytesPerRead="4096" maxNameTableCharCount="16384" />
  17. <reliableSession ordered="true" inactivityTimeout="00:10:00"
  18. enabled="false" />
  19. <security mode="Transport">
  20. <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
  21. <message clientCredentialType="Windows" />
  22. </security>
  23. </binding>
  24. </netTcpBinding>
  25. <wsDualHttpBinding>
  26. <binding name="wsDualHttpBinding_IService"  closeTimeout="00:01:00"
  27. openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
  28. transactionFlow="false" hostNameComparisonMode="StrongWildcard"
  29. maxBufferPoolSize="524288" maxReceivedMessageSize="65536">
  30. <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
  31. maxBytesPerRead="4096" maxNameTableCharCount="16384" />
  32. </binding>
  33. </wsDualHttpBinding>
  34. </bindings>
  35. <client>
  36. <!--<endpoint address="net.tcp://localhost:8322/Event" binding="netTcpBinding"
  37. bindingConfiguration="NetTcpBinding_IService" contract="JobServiceClient.IService"
  38. name="NetTcpBinding_IService">-->
  39. <endpoint address="http://localhost:8300/Event" binding="wsDualHttpBinding"
  40. bindingConfiguration="wsDualHttpBinding_IService" contract="JobServiceClient.IService"
  41. name="wsDualHttpBinding_IService">
  42. <!--<identity>
  43. <userPrincipalName value="COMPUTER\Administrator" />
  44. </identity>-->
  45. </endpoint>
  46. </client>
  47. </system.serviceModel>
  48. </configuration>

客户端代码(Form)  说明:由于使用了Form作为客户端,就涉及到一个工作线程的问题,因为多线程中使用工作线程是最简单的,也是最便捷的。

[vb] view plaincopy

  1. Imports System.Runtime.Serialization
  2. Imports System.ServiceModel
  3. Public Class Form1
  4. Implements JobServiceClient.IServiceCallback
  5. Private client As JobServiceClient.ServiceClient
  6. Private jobList As List(Of JobServiceClient.job) = New List(Of JobServiceClient.job)
  7. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
  8. Dim jobName As String = InputBox("输入新 JOB 名称!")
  9. If jobName = "" Then
  10. MessageBox.Show("JOB 名称必须输入!")
  11. Exit Sub
  12. End If
  13. Dim newJob As JobServiceClient.job = New JobServiceClient.job()
  14. newJob.Name = jobName
  15. newJob.LastRunResult = "Sleeping"
  16. newJob.LastRunTime = DateTime.Now
  17. newJob.Status = "Sleeping"
  18. client.AddJob(newJob)
  19. End Sub
  20. Public Sub OnAdd(ByVal newJob As JobServiceClient.job) Implements JobServiceClient.IServiceCallback.OnAdd
  21. Dim tmpitem As ListViewItem = CreateViewListItem(newJob)
  22. Me.ListView1.Items.Add(tmpitem)
  23. End Sub
  24. Public Sub StatusChanged(ByVal job As JobServiceClient.job) Implements JobServiceClient.IServiceCallback.StatusChanged
  25. ‘遍历 列表 项
  26. For Each tmpItem As ListViewItem In Me.ListView1.Items
  27. If tmpItem.Text = job.Name Then
  28. ‘修改 JOB 状态
  29. tmpItem.SubItems(1).Text = job.Status
  30. tmpItem.SubItems(2).Text = job.LastRunTime.ToString()
  31. tmpItem.SubItems(3).Text = job.LastRunResult
  32. ‘判断 JOB状态
  33. If job.LastRunResult = "Fail" Then
  34. tmpItem.ImageIndex = 2
  35. ElseIf job.Status = "Running" Then ‘ job.LastRunResult = "Done" Then
  36. tmpItem.ImageIndex = 0
  37. ElseIf job.Status = "Sleeping" Then
  38. tmpItem.ImageIndex = 1
  39. Else
  40. tmpItem.ImageIndex = 1 ‘other
  41. End If
  42. End If
  43. Next
  44. End Sub
  45. Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  46. ‘初始化 远程
  47. Try
  48. ‘取得自身 上下文实例
  49. Dim instanceMe As InstanceContext = New InstanceContext(Me)
  50. client = New JobServiceClient.ServiceClient(instanceMe)
  51. ‘订阅事务
  52. client.Accept()
  53. ‘加载Job列表
  54. LoadJobs()
  55. Catch ex As Exception
  56. MessageBox.Show(ex.Message)
  57. End Try
  58. End Sub
  59. Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
  60. ‘后台进程 工作
  61. ‘取得 JOB 列表
  62. Try
  63. jobList = client.GetJobs()
  64. If jobList Is Nothing Then
  65. Exit Sub
  66. End If
  67. ‘判断 当前线程工作状态
  68. If Me.ListView1.InvokeRequired Then
  69. ‘异步
  70. ListView1.Invoke(New MethodInvoker(AddressOf Me.UpdateViewList))
  71. Else
  72. Me.UpdateViewList()
  73. End If
  74. Catch ex As Exception
  75. MessageBox.Show(ex.Message)
  76. End Try
  77. End Sub
  78. Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
  79. ‘后台进程 工作完成
  80. Me.TextBox1.Text = "获取列表完毕!"
  81. End Sub
  82. Private Sub LoadJobs()
  83. ‘载入列表
  84. ‘启动后台线程
  85. Me.TextBox1.Text = "正在获取JOB列表"
  86. Me.BackgroundWorker1.RunWorkerAsync()
  87. End Sub
  88. Private Sub UpdateViewList()
  89. ‘重新载入列表
  90. Me.ListView1.Items.Clear()
  91. If jobList.Count < 1 Then
  92. Me.TextBox1.Text = "JOB列表为空。请先添加JOB。"
  93. Exit Sub
  94. End If
  95. For Each tmpJob As JobServiceClient.job In jobList
  96. Dim tmpItem As ListViewItem = CreateViewListItem(tmpJob)
  97. Me.ListView1.Items.Add(tmpItem)
  98. Next
  99. End Sub
  100. Private Function CreateViewListItem(ByVal theJob As JobServiceClient.job) As ListViewItem
  101. ‘通过 job 创建一个新的 列表项
  102. Dim itemStr() As String = {theJob.Name, theJob.Status, theJob.LastRunTime.ToString(), theJob.LastRunResult}
  103. Dim item As ListViewItem = New ListViewItem(itemStr)
  104. If theJob.LastRunResult = "Fail" Then ‘"Sleeping" Then
  105. item.ImageIndex = 2 ‘Sleeping
  106. ElseIf theJob.Status = "Running" Then
  107. item.ImageIndex = 0 ‘Done
  108. ‘ElseIf theJob.Status = "Sleeping" Then
  109. ‘    item.ImageIndex = 3 ‘Delete
  110. Else
  111. item.ImageIndex = 1 ‘Fail
  112. End If
  113. item.Tag = theJob
  114. Return item
  115. End Function
  116. Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
  117. ‘LoadJobs()
  118. ‘Do Job
  119. ‘取得列表中被选择的行
  120. ‘If Me.ListView1.SelectedIndices
  121. For Each iIndex As Integer In Me.ListView1.SelectedIndices
  122. client.DoJob(Me.ListView1.Items(iIndex).Text)
  123. ‘MessageBox.Show(Me.ListView1.Items(iIndex).Text)
  124. Next
  125. End Sub
  126. Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
  127. ‘delete job
  128. End Sub
  129. Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
  130. ‘Exit
  131. End
  132. End Sub
  133. End Class

QQ不让传附件,要钱的,马化腾不当官真是可惜。全部代码可到以下地址下载:http://url.cn/GLTdxm

四、总结 要想在服务器端进行事件广播,其实最关键的是,将事件列表保存起来,然后在事件发生时,遍历就好了。然后去通知每一个客户端。在网上的文章 中之所以要用到会话,而且要保持会话状态 就是因只有这样才能在事件异步调用过程中找到自身的客户端回调通道。 而经过验证得之,实际上,只要我们把客户端的回调通道保存在一个共享数组中,这样,我们只要在发生事件时遍历这个数组就可以通知到每个订阅者。所以,我们只需要项目中服务和客户端可以进行回调就可以,这样我们可以使用wsDualHttpBinding进行设计 。而且也可以不用委托或者事件来进行。我们只要把事件状态回调就可以了。而这个回调本身是单向的。也不用异步处理。只要处理好服务器端的并发就可以了。 所以就有了下面的结构:
服务器端进行远程订阅:将订阅的事件和客户端回调通道保存到列表中。 服务器端发生要通知的事件:遍历两个数组进行回调通知。
当然,这个还不很完善,比如,在客户端关闭后,服务器端在回调时会发生异常。如果有大量的客户端关闭或网络异常,会导致服务器端阻滞,当然可以使用多线程 来解决这个问题。
下面是不用事件的服务契约和服务实现(部分代码,完整代码见http://url.cn/GLTdxm,这里的代码就是没有使用委托,也没有使用事件进行的广播。)

(图片丢失)

代码和附件说明:所见的都是最后版本,也就是没有用会话,没有用事件,也没有用委托来实现事件广播的。

注:代码部分因为是360网盘,不知为何已不能使用了。有空传一份到这里。

时间: 2024-11-08 02:03:29

VB.net Wcf事件广播(订阅、发布)的相关文章

Node中EventEmitter以及如何实现JavaScript中的订阅/发布模式

1.EventEmitter Node中很多模块都能够使用EventEmitter,有了EventEmitter才能方便的进行事件的监听.下面看一下Node.js中的EventEmitter如何使用. (1)基本使用 EventEmitter是对事件触发和事件监听功能的封装,在node.js中的event模块中,event模块只有一个对象就是EventEmitter,下面是一个最基本的使用方法: var EventEmitter = require('events').EventEmitter;

【JavaScript】让事件支持先发布后订阅

之前写过一个的事件管理器,就是普通的先订阅后发布模式.但实际场景中我们需要做到后订阅的也能收到发布的消息.比如我们关注微信公众号,还是能看到历史消息的.类似于qq离线消息,我先发给你,你登录了就能收到了.就是确保订阅该事件的方法都能被执行. var eventManger = { cached: {}, handlers: {}, //类型,绑定事件 addHandler: function (type, handler) { if (typeof handler !== "function&q

C# Event事件的订阅与发布

我们用一个简单的例子,来说明一下这种消息传递的机制. 有一家三口,妈妈负责做饭,爸爸和孩子负责吃...将这三个人,想象成三个类. 妈妈有一个方法,叫做"做饭".有一个事件,叫做"开饭".做完饭后,调用开发事件,发布开饭消息. 爸爸和孩子分别有一个方法,叫做"吃饭". 将爸爸和孩子的"吃饭"方法,注册到妈妈的"开饭"事件.也就是,订阅妈妈的开饭消息.让妈妈做完饭开饭时,发布吃饭消息时,告诉爸爸和孩子一声. 这

Publisher/Subscriber 订阅-发布模式

Publisher/Subscriber 订阅-发布模式 本博后续将陆续整理这些年做的一些预研demo,及一些前沿技术的研究,与大家共研技术,共同进步. 关于发布订阅有很多种实现方式,下面主要介绍WCF中的发布订阅,主要参考书籍<Programming WCF Services>,闲话不多说进入正题.使用传统的双工回调(例子 http://www.cnblogs.com/artech/archive/2007/03/02/661969.html)实现发布订阅模式存在许多缺陷,主要问题是,它会引

AngularJS的简单订阅发布模式例子

控制器之间的交互方式广播 broadcast, 发射 emit 事件 类似于 js中的事件 , 可以自己定义事件 向上传递直到 document 在AngularJs中 向上传递直到 rootScope 观察者模式, 订阅发布模式 类似于js中的事件机制 订阅者.on('xx发布博客', function([内容]){ 通知我, 接收到博客的[内容] }) 发布者.emit('xxx发布博客', {内容}) 优点: 业务和实际触发者分离, 代码维护性相对好 缺点: 代码复杂性更高 Angular

订阅发布模式

场景概述: 有时需要将多个应用程序集成到一个框架中,这些应用程序常见的基础通信方式包含总线模式.代理模式. 或者点对点模式.一些应用程序发送多种类型的消息,其他应用程序可能更关注这些消息类型的组合. 例如,在一个金融系统存在多个应用程序管理同一客户信息的情况,存在一个客户关系管理程序(CRM)掌握客户信息. 一种典型的情况:客户信息存在于其他系统中,且这些系统执行各自客户信息管理函数来处理客户信息. 当某个面向客户的应用程序生成更新客户信息的消息,例如客户地址的修改时,CRM和其他管理客户信息的

基于Http协议订阅发布系统设计

  --物联网系统架构设计   1,订阅发布(subscriber-publisher) 订阅发布模式最典型的应用场景就是消息系统的设计.在消息系统的架构中,消息的发送者称作(publisher),消息的接收者称作(subscriber),参见wikipedia: Publish–subscribe pattern.整个消息系统的架构可以用如下图1来描述: 图1 由图1可知消息系统主要包括3个组件: 发布者,订阅者和消息代理(Broker),而整个消息系统的核心即是Broker,而目前就业务能力

RabbitMQ下的生产消费者模式与订阅发布模式

??所谓模式,就是在某种场景下,一类问题及其解决方案的总结归纳.生产消费者模式与订阅发布模式是使用消息中间件时常用的两种模式,用于功能解耦和分布式系统间的消息通信,以下面两种场景为例: 数据接入 ??假设有一个用户行为采集系统,负责从App端采集用户点击行为数据.通常会将数据上报和数据处理分离开,即App端通过REST API上报数据,后端拿到数据后放入队列中就立刻返回,而数据处理则另外使用Worker从队列中取出数据来做,如下图所示. ??这样做的好处有:第一,功能分离,上报的API接口不关心

Android 开源项目android-open-project工具库解析之(二) 高版本向低版本兼容,多媒体相关,事件总线(订阅者模式),传感器,安全,插件化,文件

六.Android 高版本向低版本兼容 ActionBarSherlock 为Android所有版本提供统一的ActionBar,解决4.0以下ActionBar的适配问题 项目地址:https://github.com/JakeWharton/ActionBarSherlock Demo地址:https://play.google.com/store/apps/details?id=com.actionbarsherlock.sample.demos APP示例:太多了..现在连google都