разделение службы wcf, сервера и клиента

#vb.net #wcf

#vb.net #wcf

Вопрос:

У меня есть решение в VS 2010: проект библиотеки служб WCF («NotifyService») и два проекта Windows Forms, один для сервера («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
 

Форма сервера содержит ссылку на 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 нет. Проблема, с которой я сталкиваюсь, заключается в том, что мой метод подписки никогда не вызывается. При отладке я получаю сообщение об ошибке после перехода через эту строку (не исключение), в котором говорится: «Невозможно автоматически перейти на сервер. Отладчику не удалось остановить серверный процесс «. Когда я возвращаюсь к своей форме сервера и нажимаю кнопку, предназначенную для вызова события на клиентах обратного вызова, оно переходит в класс NotifyService, но _callbacks список пуст, что означает, что метод Subscribe либо никогда не запускался, либо никогда не запускался в этом экземпляре.

Я боролся с этим почти неделю. Это почти идентичная копия, по крайней мере, на стороне службы, тому, что было описано здесь, и я скомпилировал это, и это работает. Итак, я немного запутался в том, где я ошибаюсь…

Ответ №1:

Я думаю, что проблема связана с InstanceContextMode:=InstanceContextMode.PerCall вашим сервисом. Это означает, что новый экземпляр вашей службы создается каждый раз, когда вызывается метод. Поэтому, когда вы вызываете _notifier.Notify("Foo") , он создает новый экземпляр вашего класса обслуживания, что означает, что у него нет подписчиков. Есть несколько способов, которыми вы можете подойти к этому:

  • Одним из способов было бы установить InstanceContextMode:=InstanceContextMode.Single , что означает, что будет один экземпляр вашего класса обслуживания, который будет обрабатывать все запросы в течение всего срока службы вашего сервиса. Вместе с другой вашей настройкой ConcurrencyMode:=ConcurrencyMode.Single это может быть узким местом, если вы ожидаете, что ваша служба будет использоваться интенсивно, поскольку одновременно будет обрабатываться только один запрос. Вы можете изменить его на ConcurrencyMode:=ConcurrencyMode.Multiple , но тогда вам придется отвечать за обработку потокобезопасности внутри вашего класса, поскольку это позволяет одному экземпляру вашего класса обрабатывать несколько запросов одновременно.
  • Другим подходом было бы изменить вашу _callbacks коллекцию на статическую (совместно используемую в VB.NET ) член клуба. Таким образом, ваш список подписчиков будет одинаковым во всех экземплярах вашего класса обслуживания. При таком подходе вы снова будете нести ответственность за решение проблем потокобезопасности, поскольку будет несколько экземпляров вашего класса обслуживания, которые могут одновременно обращаться к одной коллекции из разных потоков.
  • Вы могли бы использовать некоторый тип механизма сохранения, такой как база данных. Это было бы наиболее сложным, поскольку вам в основном пришлось бы заново создавать новый прокси для каждого подписчика для каждого уведомления, но имеет то преимущество, что вы не потеряете всех своих подписчиков, если ваше приложение будет закрыто. Похоже, это, вероятно, не самое подходящее для вашей ситуации.

Комментарии:

1. Пункт 2 сделал это. Сделано из-за моего собственного отсутствия внимания к преобразованию C # в VB. Это не будет иметь большого смысла — максимум несколько компьютеров, и все в одной сети, так что (не съеживайтесь) Я не слишком беспокоюсь о безопасности потоков. Общие члены будут работать просто отлично. Спасибо!

2. Я бы пошел с InstanceContextMode.Single / ConcurrencyMode.Single then. Это только замедлит работу, если вы получите несколько запросов одновременно, и именно тогда у вас возникнут проблемы с параллелизмом.