分离出wcf服务,服务器和客户端

时间:2011-10-25 18:54:54

标签: vb.net wcf

我在VS 2010中有一个解决方案:一个WCF服务库项目(“NotifyService”)和两个Windows窗体项目,一个用于服务器(“NotifyServer”),另一个用于客户端(“NotifyClient”)。我的目标是拥有一个双工WCF服务,当服务器推出通知时,它将通知任意数量的连接客户端。除了订阅和取消订阅服务器的更新之外,客户端不需要与服务器通信。但是,我似乎遇到了解决InstanceContext的问题。

以下是WCF服务的代码:

<ServiceContract(
    CallbackContract:=GetType(INotifyCallback),
    SessionMode:=SessionMode.Required)>
Public Interface INotifyService
    <OperationContract()>
    Sub Notify(ByVal what As String)

    <OperationContract()>
    Sub Subscribe()

    <OperationContract()>
    Sub Unsubscribe()

End Interface

Public Interface INotifyCallback
    <OperationContract(IsOneWay:=True)>
    Sub OnNotify(ByVal what As String)
End Interface

<ServiceBehavior(
    ConcurrencyMode:=ConcurrencyMode.Single,
    InstanceContextMode:=InstanceContextMode.PerCall)>
Public Class NotifyService
    Implements INotifyService

    Private _callbacks As New List(Of INotifyCallback)

    Public Sub Notify(ByVal what As String) Implements INotifyService.Notify
        For Each callback As INotifyCallback In _callbacks
            callback.OnNotify(what)
        Next
    End Sub

    Public Sub Subscribe() Implements INotifyService.Subscribe
        Dim client As INotifyCallback = OperationContext.Current.GetCallbackChannel(Of INotifyCallback)()
        If Not _callbacks.Contains(client) Then
            _callbacks.Add(client)
        End If
    End Sub

    Public Sub Unsubscribe() Implements INotifyService.Unsubscribe
        Dim client As INotifyCallback = OperationContext.Current.GetCallbackChannel(Of INotifyCallback)()
        If _callbacks.Contains(client) Then
            _callbacks.Remove(client)
        End If
    End Sub
End Class

Server表单引用了服务库创建的DLL,并在代码中自托管WCF服务器的实例:

Public Class frmServer
    Private _host As ServiceHost
    Private _notifier As NotifyService.NotifyService

    Public Sub go() Handles Me.Load
        _host = New ServiceHost(GetType(NotifyService.NotifyService), New Uri("net.tcp://localhost:10000"))
        _host.AddServiceEndpoint(GetType(NotifyService.INotifyService), New NetTcpBinding, "NotifyService")
        _host.Description.Behaviors.Add(New ServiceMetadataBehavior)
        _host.AddServiceEndpoint(GetType(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding, "mex")
        _host.Open()
        _notifier = New NotifyService.NotifyService
    End Sub

    Private Sub send() Handles Button1.Click
        _notifier.Notify("Foo")
    End Sub
End Class

在大多数情况下,一切似乎都有效。我可以使用WcfTestClient进行连接,它至少可以看到服务,但由于它是启用了双工的net.tcp绑定,我实际上无法使用该客户端对其进行测试。

当我创建客户端时,我添加了一个名为NotifyGateway的服务引用。这是客户端表单代码:

<CallbackBehavior(
    ConcurrencyMode:=ConcurrencyMode.Single,
    UseSynchronizationContext:=False)>
Public Class frmClient
    Implements NotifyGateway.INotifyServiceCallback

    Private _service As NotifyGateway.NotifyServiceClient = Nothing

    Public Sub OnNotify(ByVal what As String) Implements NotifyGateway.INotifyServiceCallback.OnNotify
        MsgBox(what)
    End Sub

    Public Sub go() Handles Button1.Click
        _service = New NotifyGateway.NotifyServiceClient(New InstanceContext(Me), New NetTcpBinding, New EndpointAddress("net.tcp://localhost:10000/NotifyService"))
        _service.Open()
        _service.Subscribe()
    End Sub
End Class

由于我通过代码设置所有内容,因此没有app.config文件。我遇到的问题是我的Subscribe方法从未被调用过。在调试时,我在单步执行“无法自动进入服务器的行”(不是例外)后收到错误。调试器无法在服务器进程中停止。当我返回到我的服务器表单并单击旨在在回调客户端上引发事件的按钮时,它会进入NotifyService类,但_callbacks列表为空,这意味着Subscribe方法从未运行过,或者从不运行跑了那个实例。

我已经与此斗争了近一个星期。这至少在服务方面与here所描述的几乎是完全相同的副本,我编译了它并且它有效。所以我有点迷失在哪里我出错......

1 个答案:

答案 0 :(得分:0)

我认为问题在于您的服务InstanceContextMode:=InstanceContextMode.PerCall。这意味着每次调用方法时都会创建一个新的服务实例。因此,当您调用_notifier.Notify("Foo")时,它会创建服务类的新实例,这意味着它没有任何订阅者。有几种方法可以解决这个问题:

  • 一种方法是设置InstanceContextMode:=InstanceContextMode.Single,这意味着您的服务类将有一个实例来处理服务生命周期的所有请求。与您的其他ConcurrencyMode:=ConcurrencyMode.Single设置一起,如果您希望大量使用您的服务,这可能会成为瓶颈,因为一次只能处理一个请求。您可以将其更改为ConcurrencyMode:=ConcurrencyMode.Multiple,但是您必须负责处理类中的线程安全,因为这样可以让您的类的单个实例同时处理多个请求。
  • 另一种方法是将您的_callbacks集合更改为静态(在VB.NET中为Shared)成员。这样,您的服务类的所有实例中的订阅者列表都是相同的。在这种方法中,您将再次负责处理线程安全问题,因为您的服务类的多个实例可能会同时从不同的线程访问单个集合。
  • 您可以使用某种类型的持久性机制,例如数据库。这将是最复杂的,因为您基本上必须为每个通知重新构建每个订阅者的新代理,但具有在应用程序关闭时不会丢失所有订阅者的优势。听起来这可能不适合你的情况。