WCF双工 - 如何向每个客户端推送不同的通知?

5
我正在设计两个WCF服务,所有客户端都将连接到其中之一的通知服务。其中一个服务将是通知服务。
我希望每个客户端都连接到服务,订阅它,然后使用双工回调接口接收通知(服务将在客户端中触发“Notify”操作)。
这是我的设计思路:
我的问题是:当每个客户端连接到我的服务时,我将根据我的数据库中的“用户”表对其进行验证(我将使用UserNamePasswordValidator并实现“Validate”函数)。
要求:每个用户需要根据在数据库中定义的规则接收不同的通知,但他们都使用相同的合同。
例如:
John Smith在数据库中的规则可能是:通知我所有价格超过100美元的新产品。
Jane Doe在数据库中的规则可能是:通知我所有名称以“JA”开头的新产品。
Jim Jabra在数据库中的规则可能是:通知我所有类型为“食品”的新产品。
我的服务将具有检测数据库更改的工作线程(已插入新产品到数据库中)。
然后,它应该循环遍历所有连接的客户端,并为每个客户端仅在其匹配客户端的通知请求时发送新产品的通知。
再次强调-所有客户端都接收相同类型的更新(新产品),但每个客户端应根据数据库中的规则接收不同的产品。
我认为实现此操作的一种方法是使用单例服务,该服务包含以下列表:
客户端终结点
用户对象(来自数据库)
这样-每次工作线程检测到新产品时,它都会循环遍历此列表,并向需要它的人发送通知。这种方法的问题在于为了拥有一个全局客户端列表,我需要将服务设置为Singleton,对吗?
第二种方法是...好吧...我没有另一个想法,可以从工作线程访问连接到服务的客户端列表...
我想我遇到的主要问题是每个客户端可能希望收到不同类型的产品通知。也就是说,发布/订阅方法在这里不太好用,因为我的场景需要服务了解客户端。
有关如何解决这些问题的建议?

你有没有想过使用队列?每个客户端连接到队列并轮询可用的数据。简单地将WCF服务作为订阅数据的服务。然后在工作线程中,当数据涉及到客户端时,将数据推送到客户端队列中。 - rpgmaker
一个队列会如何解决我的问题?你说的“WCF服务将是一个用于订阅数据的服务”是什么意思?你有我可以查看的示例吗? - John Miner
2个回答

2
无论使用双工通信的哪种方式,您都需要保持TCP通道从服务器到客户端打开,以便能够发送通知。
客户端是发起与服务器连接的一方,并且您需要保持此连接处于打开状态。如果此连接丢失,则不应从服务器向客户端发起连接,因为客户端可能在NAT后面,有防火墙等。
因此,在任何情况下,必须在服务器端保留一些静态(单例)对象,以保持客户端连接列表,尽管这不一定是WCF服务。您可以将此对象依赖注入到服务构造函数中。
public class ProductRepository
{
    private EventAggregator eventAggregator;

    public void Add(Product product)
    {
        //...
        eventAggregator.Publish(new NewProductEvent(product))
    }
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class EventPublishingService
{
    private IClientCallback client;
    private EventAggregator eventAggregator;
    private Func<Product, bool> predicate;

    public EventPublishingService(...)
    {
        eventAggregator.Subscibe<NewProductEvent>(OnNewProduct);
    }
    private void OnNewProduct(NewProductEvent e)
    {
        if (predicate(e.Product)==true) client.Notify(e.Product);
    }

    public void Subscribe()
    {
        client = OperationContext.Current.GetCallbackChannel<IClientCallback>()
        var user = ServiceSecurityContext.PrimaryIdentity;
        predicate = GetFilterForUser(user);
    }
}

那么你的意思是我确实需要一个单例服务,并将后台工作线程注入到其构造函数中? 难道我不应该反过来吗?将服务注入到后台工作线程的构造函数中? - John Miner
@JohnMiner,不确定您需要什么样的后台工作者。我写了个小例子。 - Alex Burtsev
哇,感谢您提供如此详细的示例!我将尝试解释为什么我需要一个工作线程。数据库中有一个表,保存了每个用户的规则 - 他想要哪些产品。USER1的规则是他想收到价格大于50的产品通知。USER2的规则是他想收到价格在30到70之间的产品通知。USER3的规则是他想要名称中带有“FOOD”的产品。请在下一条评论中继续。 - John Miner
我将拥有一个后台工作线程,等待数据库中“product”表中新事件的发生(可能使用SqlDependency)。当通过某些外部服务向表中添加新产品时,工作线程将检测到此情况,并检查哪些用户希望收到有关此产品的通知。如果产品价格为58,则需要通知USER1和USER2有关这个新产品的信息。因此,后台线程现在将遍历连接客户端的列表,并检查其中是否有USER1或USER2。如果是,则会向他们发送有关这个新产品的通知。 - John Miner
你明白我为什么需要一个后台工作线程吗?(我已经简化了我的需求,但基本上就是我需要的)。我不确定'Repository'是用来做什么的,以及'EventAggregator'是什么。根据我在之前评论中写的内容,你的代码示例是否仍然符合我的需求? - John Miner
让我们在聊天中继续这个讨论 - Alex Burtsev

0

我的意思是以下内容。

创建一个WCF服务,每个客户端都可以调用该服务一次以订阅过滤器。 WCF服务本身将简单地向数据库添加数据,并在数据存储中包括客户端名称和过滤器信息等信息。然后,您的工作线程将位于窗口服务中,它将简单地轮询数据库以获取数据,当数据可用时,它将从订阅表中读取。然后,它将把数据推送到每个客户端的队列中,这可能是像RabbitMQ这样的共享队列服务器。

在客户端方面,假设它是基于窗口的应用程序,它将通过在队列中查找自己的名称(client1等)来简单地轮询RabbitMQ队列服务器以获取数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接