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

2 篇文章 0 订阅
1 篇文章 0 订阅
一、源起
学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类:
<DataContract()>
Public Class job
    Private m_Name As String
    Private m_Status As String
    Private m_LastRunTime As DateTime
    Private m_LastRunResult As String

    Public Sub New()
        Me.Status = "Sleeping"
        Me.LastRunResult = ""
    End Sub

    <DataMember()>
    Public Property Name As String
        Get
            Return m_Name
        End Get
        Set(ByVal value As String)
            m_Name = value
        End Set
    End Property

    <DataMember()>
    Public Property Status As String
        Get
            Return m_Status
        End Get
        Set(ByVal value As String)
            m_Status = value
        End Set
    End Property

    <DataMember()>
    Public Property LastRunTime As DateTime
        Get
            Return m_LastRunTime
        End Get
        Set(ByVal value As DateTime)
            m_LastRunTime = value
        End Set
    End Property

    <DataMember()>
    Public Property LastRunResult As String
        Get
            Return m_LastRunResult
        End Get
        Set(ByVal value As String)
            m_LastRunResult = value
        End Set
    End Property
End Class

2)项目中用到的回调接口
<ServiceContract()>
Public Interface ICallback
    <OperationContract(IsOneWay:=True)>
    Sub StatusChanged(ByVal job As job)

    <OperationContract(IsOneWay:=True)>
    Sub OnAdd(ByVal newJob As job)


End Interface

3)项目中用到的服务契约
'<ServiceContract(SessionMode:=SessionMode.Required, CallbackContract:=GetType(ICallback))>
'不使用事件,只使用委托时,是否可以不用会话
'<ServiceContract(SessionMode:=SessionMode.Allowed, CallbackContract:=GetType(ICallback))>
<ServiceContract(CallbackContract:=GetType(ICallback))>
Public Interface IService

    '<OperationContract()>
    'Function GetData(ByVal value As Integer) As String

    '<OperationContract()>
    'Function GetDataUsingDataContract(ByVal composite As CompositeType) As CompositeType

    ' TODO: 在此添加您的服务操作
    'IsInitiating 表示 是否可以在服务器上启动会话
    'IsTerminating 指示服务操作在发送答复消息(如果存在)后,是否会导致服务器关闭会话。
    <OperationContract(IsOneWay:=True, IsInitiating:=True, IsTerminating:=False)>
    Sub Accept()

    <OperationContract(IsOneWay:=True, IsInitiating:=True, IsTerminating:=False)>
    Sub DoJob(ByVal jobName As String)

    <OperationContract(IsOneWay:=True, IsInitiating:=True, IsTerminating:=False)>
    Sub AddJob(ByVal newJob As job)

    <OperationContract(IsOneWay:=False, IsInitiating:=True, IsTerminating:=False)>
    Function GetJobs() As List(Of job)


End Interface

说明:按照 网上文章的说法,事件广播要使用到 会话。但实际上,会话在这里并不重要。这我在后面验证。
4)项目中的服务实现类
这里,要用到InstanceContextMode特性,因为项目在这时还要保持会话。
'<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession, ConcurrencyMode:=ConcurrencyMode.Multiple)>
'不使用事件,只使用委托,是否可以不用会话
'<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>
Public Class JobServerImplement
    Implements IService

    Public Shared m_jobs As List(Of job) '用来返回 job列表
    Public Shared jobs_Lock As Object  '同步锁用
    '事件委托定义
    Public Delegate Sub JobServerEventHandler(ByVal sender As Object, ByVal e As JobCallbackEventArg)

    '定义共享的 事件 列表
    Public Shared onChangeEvents As List(Of JobServerEventHandler)
    Public Shared onAddEvents As List(Of JobServerEventHandler)

    '定义客户端 回调通道 列表
    Public Shared clientCallBackChannles As List(Of ICallback)


    'job状态改变事件定义,事件必须定义为 共享
    'Public Shared Event onJobStatusChanged As JobServerEventHandler
    Public Shared Custom Event onJobStatusChanged As JobServerEventHandler
        AddHandler(ByVal value As JobServerEventHandler)
            If jobs_Lock Is Nothing Then
                jobs_Lock = New Object()
            End If
            If onChangeEvents Is Nothing Then
                SyncLock jobs_Lock
                    onChangeEvents = New List(Of JobServerEventHandler)
                End SyncLock
            End If

            SyncLock jobs_Lock
                onChangeEvents.Add(value)
                Console.WriteLine("AddHandler Done!")
            End SyncLock
        End AddHandler

        RemoveHandler(ByVal value As JobServerEventHandler)
            If onChangeEvents Is Nothing Then
                Return
            End If
            SyncLock jobs_Lock
                onChangeEvents.Remove(value)
                Console.WriteLine("RemoveHandler Done!")
            End SyncLock
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As JobCallbackEventArg)
            For Each tmpH As JobServerEventHandler In onChangeEvents
                tmpH.BeginInvoke(sender, e, Nothing, Nothing) ' New System.AsyncCallback(AddressOf EndAsync), Nothing)

                Console.WriteLine("RaiseEvent")
            Next
        End RaiseEvent
    End Event

    'Public Custom Event 

    '内部事件传递 委托
    'Private MyChangeHandler As JobServerEventHandler

    'job添加事件定义,事件必须 共享
    'Public Shared Event onAdd As JobServerEventHandler
    '内部事件传递委托
    'Private MyAddHandler As JobServerEventHandler

    '回调 接口 
    Dim callbackInterface As ICallback

    '每一次回调,分开后,如果 哪个客户端失败,不会影响其他存在客户端
    Private Sub CallBackOnce(ByVal theCallBack As ICallback, ByVal e As JobCallbackEventArg)
        Try
            If e.ChangeReason = "Add" Then
                Console.WriteLine("CallBackOnce reason: Add")
                theCallBack.OnAdd(e.Job)
            Else
                Console.WriteLine("CallBackOnce reason: Modi")
                theCallBack.StatusChanged(e.Job)
            End If
        Catch ex As Exception
            Console.WriteLine("CallBackOnce Got Error:" & ex.Message)
        End Try
    End Sub

    '不使用事件,直接使用委托来回调客户端
    Private Sub Server_Delegate_StatusChanged(ByVal sender As Object, ByVal e As JobCallbackEventArg)
        Try
            If e Is Nothing Or e.Job Is Nothing Then
                Console.WriteLine("Server_Delegate_StatusChanged e Or e.Job is nothing.")
                Exit Sub
            Else
                Console.WriteLine("Server_Delegate_StatusChanged Begin!")
                '
                'Dim callBackOnce As ICallback 
                For Each callOnce As ICallback In clientCallBackChannles
                    CallBackOnce(callOnce, e)
                    'If e.ChangeReason = "Add" Then
                    '    Console.WriteLine("Server_Delegate_StatusChanged reason: Add")
                    '    callOnce.OnAdd(e.Job)
                    'Else
                    '    Console.WriteLine("Server_Delegate_StatusChanged reason: Modi")
                    '    callOnce.StatusChanged(e.Job)
                    'End If
                Next

                Console.WriteLine("Server_Delegate_StatusChanged Done!")
            End If
        Catch ex As Exception
            Console.WriteLine("Server_OnStatusChange  Errors:" & ex.Message)
        End Try
    End Sub

    '服务器端状态改变 ,则回调客户端 方法
    Private Sub Server_OnStatusChanged(ByVal sender As Object, ByVal e As JobCallbackEventArg)
        Try
            If e Is Nothing Or e.Job Is Nothing Then
                Console.WriteLine("Server_OnStatusChange e Or e.Job is nothing.")
                Exit Sub
            Else
                Console.WriteLine("Server_OnStatusChange Begin!")
                '回调
                If e.ChangeReason = "Add" Then
                    Console.WriteLine("server_onStatusChanged reason: Add")
                    'callbackInterface.StatusChanged(e.Job)
                    callbackInterface.OnAdd(e.Job)
                Else
                    Console.WriteLine("server_onStatusChanged reason: Modi")
                    callbackInterface.StatusChanged(e.Job)
                End If

                Console.WriteLine("Server_OnStatusChange Done!")
            End If
        Catch ex As Exception
            Console.WriteLine("Server_OnStatusChange  Errors:" & ex.Message)
        End Try
    End Sub

    '异步调用结束 处理
    Private Sub EndAsync(ByVal ar As IAsyncResult)
        '定义 处理 委托
        Dim dEndHandler As JobServerEventHandler = Nothing

        Console.WriteLine("EndAsync Begin!")
        Try
            '取得 异步 参数 返回的 IAsyncResult,并转换为 远程 类型
            '定义远程异步处理参数, 对委托的异步操作结果
            Dim arRemote As System.Runtime.Remoting.Messaging.AsyncResult = CType(ar, System.Runtime.Remoting.Messaging.AsyncResult)
            '取得 返回 的异步 委托,并转换为 本地的事件委托
            dEndHandler = CType(arRemote.AsyncDelegate, JobServerEventHandler)
            '进行 异步 调用结束  处理
            dEndHandler.EndInvoke(ar)
            Console.WriteLine("EndAsync End!")
        Catch ex As Exception
            '发生错误 ,从事件委托 列表中 去除该 委托
            RemoveHandler onJobStatusChanged, dEndHandler
            'RemoveHandler onAdd, dEndHandler

            Console.WriteLine("EndAsync Errors:" & ex.Message)
        End Try
    End Sub

    '广播事件
    Private Sub BroadcastEvent(ByVal e As JobCallbackEventArg) ', ByVal tmpHandler As JobServerEventHandler)
        '判断是否有订阅事件
        'C#中 可以使用 null判断,但VB中不可以,但却可以直接 调用,不会出现异常
        '
        Console.WriteLine("BroadcastEvent Begin!")
        Try
            'For Each tmpH As JobServerEventHandler In onChangeEvents  '.GetInvocationList() 'onJobStatusChangedEvent.GetInvocationList()
            '    '异步调用
            '    tmpH.BeginInvoke(Me, e, New System.AsyncCallback(AddressOf EndAsync), Nothing)
            'Next

            '保存了客户端通道列表后,不需要 委托列表直接在通道列表中进行回调即可
            Server_Delegate_StatusChanged(Me, e)

            Console.WriteLine("BroadcastEvent End!")
        Catch ex As Exception
            Console.WriteLine("BroadcastEvent Errors:" & ex.Message)
        End Try

    End Sub

    Public Sub Accept() Implements IService.Accept
        Console.WriteLine("Accept Begin!")
        Console.WriteLine("启动服务实例")
        Try
            'callBack = OperationContext.Current.GetCallbackChannel<Core.ICallback>()
            '取得调用当前操作的客户端实例的通道
            callbackInterface = OperationContext.Current.GetCallbackChannel(Of ICallback)()
            '添加 服务器 端委托 链表
            'MyChangeHandler = New JobServerEventHandler(AddressOf Me.Server_OnStatusChanged)
            'AddHandler onJobStatusChanged, MyChangeHandler
            'AddHandler onJobStatusChanged, AddressOf Me.Server_OnStatusChanged
            'MyAddHandler = New JobServerEventHandler(AddressOf Me.Server_OnAdd)
            'AddHandler onAdd, MyAddHandler

            '如果不用事件,直接使用委托
            AddDelegate(AddressOf Me.Server_Delegate_StatusChanged)
            '保存 回调通道列表
            AddCallBack(callbackInterface)
        Catch ex As Exception
            Console.WriteLine("Accept Errors: " & ex.Message)
        End Try

        Console.WriteLine("Accept End!")
    End Sub

    Private Sub AddCallBack(ByVal chl As ICallback)
        Console.WriteLine("AddCallBack Begin!")
        Try
            If jobs_Lock Is Nothing Then
                jobs_Lock = New Object()
            End If
            If clientCallBackChannles Is Nothing Then
                SyncLock jobs_Lock
                    clientCallBackChannles = New List(Of ICallback)
                End SyncLock
            End If

            SyncLock jobs_Lock
                clientCallBackChannles.Add(chl)
                Console.WriteLine("AddCallBack Done!")
            End SyncLock

        Catch ex As Exception
            Console.WriteLine("AddCallBack Got Error:" & ex.Message)
        End Try


    End Sub

    Private Sub AddDelegate(ByVal theDelegate As JobServerEventHandler)
        Console.WriteLine("AddDelegate Begin!")
        Try
            If jobs_Lock Is Nothing Then
                jobs_Lock = New Object()
            End If
            If onChangeEvents Is Nothing Then
                SyncLock jobs_Lock
                    onChangeEvents = New List(Of JobServerEventHandler)
                End SyncLock
            End If

            SyncLock jobs_Lock
                onChangeEvents.Add(theDelegate)
                Console.WriteLine("AddDelegate Done!")
            End SyncLock
        Catch ex As Exception
            Console.WriteLine("AddDelegate Got Error:" & ex.Message)
        End Try

    End Sub

    Public Sub AddJob(ByVal newJob As job) Implements IService.AddJob
        Console.WriteLine("AddJob")
        Try
            If jobs_Lock Is Nothing Then
                jobs_Lock = New Object()
            End If
            If m_jobs Is Nothing Then
                SyncLock jobs_Lock
                    m_jobs = New List(Of job)
                End SyncLock
            End If
            For Each jobItem As job In m_jobs
                If newJob.Name = jobItem.Name Then
                    Console.WriteLine("Job 已存在。")
                    Return
                End If
            Next

            '添加
            newJob.Status = "Sleeping"
            newJob.LastRunResult = "Created"

            SyncLock jobs_Lock
                m_jobs.Add(newJob)
            End SyncLock

            Console.WriteLine("AddJob 添加完毕。开始广播事件...")
            '引发事件
            Dim e As JobCallbackEventArg = New JobCallbackEventArg()
            e.Job = newJob
            e.ChangeReason = "Add"
            '广播事件
            BroadcastEvent(e)
            'BroadcastEvent(e, onAdd)
            'RaiseEvent onAdd(Me, e)
            'RaiseEvent onJobStatusChanged(Me, e)
            Console.WriteLine("AddJob 广播事件完毕。")
        Catch ex As Exception
            Console.WriteLine("AddJob Errors: " & ex.Message)
        End Try

        Console.WriteLine("AddJob End!")
    End Sub

    '内部方法,用来取得 指定名称的 job
    Private Function GetJobByname(ByVal theJobName As String) As job
        For Each jobItem As job In m_jobs
            If jobItem.Name = theJobName Then
                Return jobItem
            End If
        Next

        Return Nothing
    End Function

    Public Sub DoJob(ByVal jobName As String) Implements IService.DoJob
        Dim theJob As job = GetJobByname(jobName)
        Try
            Dim e As JobCallbackEventArg = New JobCallbackEventArg()

            If theJob.Status = "Finished" Then
                Return
            End If

            theJob.Status = "Running"
            theJob.LastRunResult = "Working"
            theJob.LastRunTime = DateTime.Now
            e.Job = theJob

            '广播事件
            BroadcastEvent(e)
            '
            Console.WriteLine("任务" + theJob.Name + "正在执行")
            '耗时
            System.Threading.Thread.Sleep(1000 * 10)

            e = New JobCallbackEventArg()
            theJob.Status = "Finished"
            theJob.LastRunTime = DateTime.Now
            theJob.LastRunResult = "Done!"
            e.Job = theJob
            '广播事件
            BroadcastEvent(e)

        Catch ex As Exception
            '将 job状态修改 为
            theJob.Status = "Sleeping"
            theJob.LastRunResult = "Fail"
        End Try
    End Sub

    Public Function GetJobs() As System.Collections.Generic.List(Of job) Implements IService.GetJobs
        'System.Threading.Thread.Sleep(1000 * 5) '为何要如此设置?耗时吗?还是为了 错开 同步 问题
        Console.WriteLine("GetJobs")

        Return m_jobs
    End Function
End Class

'回调用 参数 类别
Public Class JobCallbackEventArg
    Inherits EventArgs

    Private m_CallbackJob As job

    Private m_ChangeReason As String

    Public Property Job As job
        Get
            Return m_CallbackJob
        End Get
        Set(ByVal value As job)
            m_CallbackJob = value
        End Set
    End Property

    Public Property ChangeReason As String
        Get
            Return m_ChangeReason
        End Get
        Set(ByVal value As String)
            m_ChangeReason = value
        End Set
    End Property
End Class

5)承载服务代码
Imports System.Runtime.Serialization
Imports System.ServiceModel
Module Module1

    Sub Main()
        Using host As ServiceHost = New ServiceHost(GetType(WcfJobService.JobServerImplement))
            host.Open()

            Console.WriteLine("Service Started!")


            Console.WriteLine("Press any key to Exit!")
            Console.ReadKey()

            host.Close()
        End Using
    End Sub

End Module

承载服务配置 app.config
上文提到 的文章的例子中并没有关于Mex终结点,之所以在这里配置无数据终结点,是为了让客户端获取代理方便些。而且,在这里用的绑定是TCP,也就是说,这是个天然支持回调的协议。而且速度也很快,只是IIS6无法承载
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <sources>
            <!-- 本节定义 My.Application.Log 的登录配置-->
            <source name="DefaultSource" switchName="DefaultSwitch">
                <listeners>
                    <add name="FileLog"/>
                    <!-- 取消注释以下一节可写入应用程序事件日志-->
                    <!--<add name="EventLog"/>-->
                </listeners>
            </source>
        </sources>
        <switches>
            <add name="DefaultSwitch" value="Information" />
        </switches>
        <sharedListeners>
            <add name="FileLog"
                 type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"
                 initializeData="FileLogWriter"/>
            <!-- 取消注释以下一节并用应用程序名替换 APPLICATION_NAME 可写入应用程序事件日志-->
            <!--<add name="EventLog" type="System.Diagnostics.EventLogTraceListener" initializeData="APPLICATION_NAME"/> -->
        </sharedListeners>
    </system.diagnostics>
    <system.serviceModel>
        <services>
            <service name="WcfJobService.JobServerImplement" behaviorConfiguration ="MEX">
              <host>
                <baseAddresses>
                  <!--<add baseAddress ="net.tcp://localhost:8322/"/>-->
                  <add baseAddress ="http://localhost:8300/"/>
                </baseAddresses>
              </host>
              
              <!--<endpoint address="Event" binding="netTcpBinding"
                    bindingConfiguration="DuplexBinding" contract="WcfJobService.IService" />-->
              <endpoint address="Event" binding="wsDualHttpBinding"
                    bindingConfiguration="DuplexWsHttpBinding" contract="WcfJobService.IService" />
              <!--<endpoint address ="MEX" binding ="mexTcpBinding" contract ="IMetadataExchange" />-->
              <endpoint address ="http://localhost:8080/MEX" binding ="mexHttpBinding" contract ="IMetadataExchange" />
            </service>
        </services>
      <bindings>
        <wsDualHttpBinding>
          <binding name="DuplexWsHttpBinding" sendTimeout ="00:00:05"></binding>
        </wsDualHttpBinding>
        <netTcpBinding>
          <binding name ="DuplexBinding" sendTimeout ="00:00:03" ></binding>
        </netTcpBinding>
        <wsHttpBinding>
          <binding name ="wsHttpBindingCallBack" sendTimeout ="00:00:05"></binding>
        </wsHttpBinding>
      </bindings>
      <behaviors >
        <serviceBehaviors >
          <behavior name="MEX">
            <serviceMetadata />
          </behavior>
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>
</configuration>

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

 

6)客户端

配置文件app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" />
    </startup>
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
                <binding name="NetTcpBinding_IService" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
                    hostNameComparisonMode="StrongWildcard" listenBacklog="10"
                    maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10"
                    maxReceivedMessageSize="65536">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <reliableSession ordered="true" inactivityTimeout="00:10:00"
                        enabled="false" />
                    <security mode="Transport">
                        <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
                        <message clientCredentialType="Windows" />
                    </security>
                </binding>
            </netTcpBinding>
          <wsDualHttpBinding>
            <binding name="wsDualHttpBinding_IService"  closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    transactionFlow="false" hostNameComparisonMode="StrongWildcard" 
                    maxBufferPoolSize="524288" maxReceivedMessageSize="65536">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />              
            </binding>
          </wsDualHttpBinding>
        </bindings>
        <client>
            <!--<endpoint address="net.tcp://localhost:8322/Event" binding="netTcpBinding"
                bindingConfiguration="NetTcpBinding_IService" contract="JobServiceClient.IService"
                name="NetTcpBinding_IService">-->
          <endpoint address="http://localhost:8300/Event" binding="wsDualHttpBinding"
                bindingConfiguration="wsDualHttpBinding_IService" contract="JobServiceClient.IService"
                name="wsDualHttpBinding_IService">
                <!--<identity>
                    <userPrincipalName value="COMPUTER\Administrator" />
                </identity>-->
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>


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

 

Imports System.Runtime.Serialization
Imports System.ServiceModel

Public Class Form1
    Implements JobServiceClient.IServiceCallback

    Private client As JobServiceClient.ServiceClient
    Private jobList As List(Of JobServiceClient.job) = New List(Of JobServiceClient.job)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim jobName As String = InputBox("输入新 JOB 名称!")
        If jobName = "" Then
            MessageBox.Show("JOB 名称必须输入!")
            Exit Sub
        End If

        Dim newJob As JobServiceClient.job = New JobServiceClient.job()
        newJob.Name = jobName
        newJob.LastRunResult = "Sleeping"
        newJob.LastRunTime = DateTime.Now
        newJob.Status = "Sleeping"

        client.AddJob(newJob)

    End Sub

    Public Sub OnAdd(ByVal newJob As JobServiceClient.job) Implements JobServiceClient.IServiceCallback.OnAdd
        Dim tmpitem As ListViewItem = CreateViewListItem(newJob)
        Me.ListView1.Items.Add(tmpitem)
    End Sub

    Public Sub StatusChanged(ByVal job As JobServiceClient.job) Implements JobServiceClient.IServiceCallback.StatusChanged
        '遍历 列表 项
        For Each tmpItem As ListViewItem In Me.ListView1.Items
            If tmpItem.Text = job.Name Then
                '修改 JOB 状态
                tmpItem.SubItems(1).Text = job.Status
                tmpItem.SubItems(2).Text = job.LastRunTime.ToString()
                tmpItem.SubItems(3).Text = job.LastRunResult
                '判断 JOB状态
                If job.LastRunResult = "Fail" Then
                    tmpItem.ImageIndex = 2
                ElseIf job.Status = "Running" Then ' job.LastRunResult = "Done" Then
                    tmpItem.ImageIndex = 0
                ElseIf job.Status = "Sleeping" Then
                    tmpItem.ImageIndex = 1
                Else
                    tmpItem.ImageIndex = 1 'other
                End If
            End If
        Next
    End Sub

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        '初始化 远程
        Try
            '取得自身 上下文实例
            Dim instanceMe As InstanceContext = New InstanceContext(Me)
            client = New JobServiceClient.ServiceClient(instanceMe)
            '订阅事务
            client.Accept()
            '加载Job列表
            LoadJobs()
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        '后台进程 工作
        '取得 JOB 列表
        Try
            jobList = client.GetJobs()
            If jobList Is Nothing Then
                Exit Sub
            End If

            '判断 当前线程工作状态
            If Me.ListView1.InvokeRequired Then
                '异步
                ListView1.Invoke(New MethodInvoker(AddressOf Me.UpdateViewList))

            Else
                Me.UpdateViewList()
            End If
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try


    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        '后台进程 工作完成
        Me.TextBox1.Text = "获取列表完毕!"
    End Sub

    Private Sub LoadJobs()
        '载入列表
        '启动后台线程
        Me.TextBox1.Text = "正在获取JOB列表"
        Me.BackgroundWorker1.RunWorkerAsync()

    End Sub

    Private Sub UpdateViewList()
        '重新载入列表
        Me.ListView1.Items.Clear()
        If jobList.Count < 1 Then
            Me.TextBox1.Text = "JOB列表为空。请先添加JOB。"
            Exit Sub
        End If
        For Each tmpJob As JobServiceClient.job In jobList
            Dim tmpItem As ListViewItem = CreateViewListItem(tmpJob)
            Me.ListView1.Items.Add(tmpItem)
        Next
    End Sub

    Private Function CreateViewListItem(ByVal theJob As JobServiceClient.job) As ListViewItem
        '通过 job 创建一个新的 列表项
        Dim itemStr() As String = {theJob.Name, theJob.Status, theJob.LastRunTime.ToString(), theJob.LastRunResult}

        Dim item As ListViewItem = New ListViewItem(itemStr)

        If theJob.LastRunResult = "Fail" Then '"Sleeping" Then
            item.ImageIndex = 2 'Sleeping
        ElseIf theJob.Status = "Running" Then
            item.ImageIndex = 0 'Done
            'ElseIf theJob.Status = "Sleeping" Then
            '    item.ImageIndex = 3 'Delete
        Else
            item.ImageIndex = 1 'Fail
        End If

        item.Tag = theJob

        Return item

    End Function

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        'LoadJobs()
        'Do Job
        '取得列表中被选择的行
        'If Me.ListView1.SelectedIndices 
        For Each iIndex As Integer In Me.ListView1.SelectedIndices
            client.DoJob(Me.ListView1.Items(iIndex).Text)
            'MessageBox.Show(Me.ListView1.Items(iIndex).Text)
        Next
    End Sub

    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        'delete job
    End Sub

    Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
        'Exit
        End
    End Sub
End Class


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

四、总结
要想在服务器端进行事件广播,其实最关键的是,将事件列表保存起来,然后在事件发生时,遍历就好了。然后去通知每一个客户端。在网上的文章 中之所以要用到会话,而且要保持会话状态 就是因只有这样才能在事件异步调用过程中找到自身的客户端回调通道。
而经过验证得之,实际上,只要我们把客户端的回调通道保存在一个共享数组中,这样,我们只要在发生事件时遍历这个数组就可以通知到每个订阅者。所以,我们只需要项目中服务和客户端可以进行回调就可以,这样我们可以使用wsDualHttpBinding进行设计 。而且也可以不用委托或者事件来进行。我们只要把事件状态回调就可以了。而这个回调本身是单向的。也不用异步处理。只要处理好服务器端的并发就可以了。
所以就有了下面的结构:

服务器端进行远程订阅:将订阅的事件和客户端回调通道保存到列表中。
服务器端发生要通知的事件:遍历两个数组进行回调通知。

当然,这个还不很完善,比如,在客户端关闭后,服务器端在回调时会发生异常。如果有大量的客户端关闭或网络异常,会导致服务器端阻滞,当然可以使用多线程 来解决这个问题。

下面是不用事件的服务契约和服务实现(部分代码,完整代码见http://url.cn/GLTdxm,这里的代码就是没有使用委托,也没有使用事件进行的广播。) 

 

 

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



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
WCF是一种用于创建分布式系统的技术,可与vb.net一起使用。 WCF中可以使用多种协议进行网络通信,例如HTTP,TCP和MSMQ等。下面是一个vb.netWCF网络通信实例: 首先,要创建一个WCF服务,您可以使用Visual Studio中的模板。在创建服务时,可以选择使用什么类型的绑定和协议。例如,默认情况下会使用基本HTTP绑定和HTTP协议。 在服务中定义操作合同,这些合同将服务公开为web服务,可以使用它们来处理远程请求。 在客户端应用程序中,您需要首先添加服务引用,可以通过Visual Studio的“添加服务引用”向导来完成。然后,您可以使用该服务的客户端代理来调用远程服务,就像在本地应用程序中一样。 以下是一个简单的WCF服务和客户端应用程序示例: 服务代码: ```vb.net Imports System.ServiceModel <ServiceContract()> Public Interface ICalculator <OperationContract()> Function Add(ByVal x As Double, ByVal y As Double) As Double End Interface Public Class Calculator Implements ICalculator Public Function Add(ByVal x As Double, ByVal y As Double) As Double Implements ICalculator.Add Return x + y End Function End Class Sub Main() Dim host As New ServiceHost(GetType(Calculator)) host.Open() Console.WriteLine("Service started. Press any key to stop.") Console.ReadLine() host.Close() End Sub ``` 客户端代码: ```vb.net Module Module1 Sub Main() Dim client As New CalculatorClient() Console.WriteLine("2 + 3 = " & client.Add(2, 3)) client.Close() Console.ReadLine() End Sub End Module <ServiceContract()> Public Interface ICalculator <OperationContract()> Function Add(ByVal x As Double, ByVal y As Double) As Double End Interface Public Class CalculatorClient Inherits ClientBase(Of ICalculator) Implements ICalculator Public Function Add(ByVal x As Double, ByVal y As Double) As Double Implements ICalculator.Add Return Channel.Add(x, y) End Function End Class ``` 在这个例子中,我定义了一个名为“Calculator”的简单计算器服务,它具有一个Add操作。在客户端应用程序中,我使用了一个名为“CalculatorClient”的客户端代理来调用Add操作,该代理使用了WCF中的默认HTTP绑定和HTTP协议来与服务通信。 这就是一个简单的WCF网络通信示例,您可以开始使用WCF来创建自己的分布式应用程序,用于实现您的网络通信需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值