WCF 客户端导致服务器挂起直至连接故障

发布于 2024-11-19 06:09:19 字数 1237 浏览 2 评论 0原文

下面的文字是对这个问题的扩展和增添色彩的努力:

如何防止行为不端的客户关闭整个服务?

我基本上有这样的场景:WCF 服务已启动并通过客户端回调运行,该回调具有直接、简单的单向通信,与此没有太大不同:

public interface IMyClientContract
{
  [OperationContract(IsOneWay = true)]
  void SomethingChanged(simpleObject myObj);
}

我可能每秒从服务调用此方法数千次最终将有大约 50 个并发连接的客户端,延迟尽可能低(<15 毫秒就很好)。这工作正常,直到我在连接到服务器的一个客户端应用程序上设置断点,然后一切都在服务挂起大约 2-5 秒后挂起,并且其他客户端都没有收到任何数据大约大约 30 秒,直到服务注册连接故障事件并断开有问题的客户端。此后,所有其他客户端继续愉快地接收消息。

我已经研究了 serviceThrotdling、并发调整、设置线程池最小线程、WCF 秘方和整个 9 码,但最终这篇文章 MSDN - WCF 基础知识、单向调用、回调和事件 准确描述了问题我没有真正提出建议。

允许服务安全回调客户端的第三种解决方案是将回调合约操作配置为单向操作。这样做即使并发设置为单线程,服务也可以回调,因为不会有任何回复消息来争夺锁。

但在本文前面,它仅从客户的角度描述了我所看到的问题

当单向调用到达服务时,它们可能不会一次全部调度,而是可能在服务端排队等待一次调度一个,这一切都根据服务配置的并发模式行为和会话模式。服务愿意排队的消息数量(无论是单向还是请求-回复)是配置的通道和可靠性模式的乘积。如果排队的消息数量超过了队列的容量,那么即使发出单向调用,客户端也会阻塞

我也只能假设相反的情况成立,发送给客户端的排队消息的数量已经超出了队列容量并且线程池现在充满了尝试调用此客户端的线程,但这些线程现在全部被阻止。

处理这个问题的正确方法是什么?我是否应该研究一种方法来检查每个客户端在服务通信层排队的消息数量,并在达到一定限制后中止它们的连接?

看起来,如果 WCF 服务本身在队列填满时阻塞,那么每当一个客户端的队列已满时,我可以在服务内部实现的所有异步/单向/即发即弃策略仍然会被阻塞。

The below text is an effort to expand and add color to this question:

How do I prevent a misbehaving client from taking down the entire service?

I have essentially this scenario: a WCF service is up and running with a client callback having a straight forward, simple oneway communication, not very different from this one:

public interface IMyClientContract
{
  [OperationContract(IsOneWay = true)]
  void SomethingChanged(simpleObject myObj);
}

I'm calling this method potentially thousands of times a second from the service to what will eventually be about 50 concurrently connected clients, with as low latency as possible (<15 ms would be nice). This works fine until I set a break point on one of the client apps connected to the server and then everything hangs after maybe 2-5 seconds the service hangs and none of the other clients receive any data for about 30 seconds or so until the service registers a connection fault event and disconnects the offending client. After this all the other clients continue on their merry way receiving messages.

I've done research on serviceThrottling, concurrency tweaking, setting threadpool minimum threads, WCF secret sauces and the whole 9 yards, but at the end of the day this article MSDN - WCF essentials, One-Way Calls, Callbacks and Events describes exactly the issue I'm having without really making a recommendation.

The third solution that allows the service to safely call back to the client is to have the callback contract operations configured as one-way operations. Doing so enables the service to call back even when concurrency is set to single-threaded, because there will not be any reply message to contend for the lock.

but earlier in the article it describes the issue I'm seeing, only from a client perspective

When one-way calls reach the service, they may not be dispatched all at once and may be queued up on the service side to be dispatched one at a time, all according to the service configured concurrency mode behavior and session mode. How many messages (whether one-way or request-reply) the service is willing to queue up is a product of the configured channel and the reliability mode. If the number of queued messages has exceeded the queue's capacity, then the client will block, even when issuing a one-way call

I can only assume that the reverse is true, the number of queued messages to the client has exceeded the queue capacity and the threadpool is now filled with threads attempting to call this client that are now all blocked.

What is the right way to handle this? Should I research a way to check how many messages are queued at the service communication layer per client and abort their connections after a certain limit is reached?

It almost seems that if the WCF service itself is blocking on a queue filling up then all the async / oneway / fire-and-forget strategies I could ever implement inside the service will still get blocked whenever one client's queue gets full.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

-残月青衣踏尘吟 2024-11-26 06:09:19

对客户端回调了解不多,但听起来与一般的 wcf 代码阻塞问题类似。我经常通过生成一个BackgroundWorker并在线程中执行客户端调用来解决这些问题。在此期间,主线程会计算子线程花费的时间。如果子线程在几毫秒内没有完成,主线程就会继续前进并放弃该线程(它最终会自行死亡,因此不会发生内存泄漏)。这基本上就是格雷夫斯先生用“一劳永逸”一词所建议的。

Don't know much about the client callbacks, but it sounds similar to generic wcf code blocking issues. I often solve these problems by spawning a BackgroundWorker, and performing the client call in the thread. During that time, the main thread counts how long the child thread is taking. If the child has not finished in a few milliseconds, the main thread just moves on and abandons the thread (it eventually dies by itself, so no memory leak). This is basically what Mr.Graves suggests with the phrase "fire-and-forget".

鹊巢 2024-11-26 06:09:19

更新:

我实现了即发即忘 设置调用客户端的回调通道,一旦缓冲区填充到客户端,服务器就不再阻塞

MyEvent 是一个事件,其委托与 WCF 客户端契约中定义的方法之一匹配,当它们连接时,我实质上是添加事件的回调

MyEvent += OperationContext.Current.GetCallbackChannel<IFancyClientContract>().SomethingChanged

等等......然后为了将这些数据发送给所有客户端,我

//serialize using protobuff
using (var ms = new MemoryStream())
{
    ProtoBuf.Serializer.Serialize(ms, new SpecialDataTransferObject(inputData));
    byte[] data = ms.GetBuffer();
    Parallel.ForEach(MyEvent.GetInvocationList(), p => ThreadUtil.FireAndForget(p, data));
}

在 ThreadUtil 类中执行以下操作我基本上对即发即弃文章中定义的代码进行了以下更改

static void InvokeWrappedDelegate(Delegate d, object[] args)
{
    try
    {
        d.DynamicInvoke(args);
    }
    catch (Exception ex)
    {
        //THIS will eventually throw once the client's WCF callback channel has filled up and timed out, and it will throw once for every single time you ever tried sending them a payload, so do some smarter logging here!!
        Console.WriteLine("Error calling client, attempting to disconnect.");
        try
        {
            MyService.SingletonServiceController.TerminateClientChannelByHashcode(d.Target.GetHashCode());//this is an IContextChannel object, kept in a dictionary of active connections, cross referenced by hashcode just for this exact occasion
        }
        catch (Exception ex2)
        {
            Console.WriteLine("Attempt to disconnect client failed: " + ex2.ToString());
        }
    }
}

我没有任何好主意如何去杀死服务器仍在等待的所有待处理数据包,看看它们是否会被传递。一旦我得到第一个异常,理论上我应该能够去并终止某个队列中某个地方的所有其他请求,但是这个设置是有效的并且满足目标。

Update:

I implemented a Fire-and-forget setup to call the client's callback channel and the server no longer blocks once the buffer fills to the client

MyEvent is an event with a delegate that matches one of the methods defined in the WCF client contract, when they connect I'm essentially adding the callback to the event

MyEvent += OperationContext.Current.GetCallbackChannel<IFancyClientContract>().SomethingChanged

etc... and then to send this data to all clients, I'm doing the following

//serialize using protobuff
using (var ms = new MemoryStream())
{
    ProtoBuf.Serializer.Serialize(ms, new SpecialDataTransferObject(inputData));
    byte[] data = ms.GetBuffer();
    Parallel.ForEach(MyEvent.GetInvocationList(), p => ThreadUtil.FireAndForget(p, data));
}

in the ThreadUtil class I made essentially the following change to the code defined in the fire-and-foget article

static void InvokeWrappedDelegate(Delegate d, object[] args)
{
    try
    {
        d.DynamicInvoke(args);
    }
    catch (Exception ex)
    {
        //THIS will eventually throw once the client's WCF callback channel has filled up and timed out, and it will throw once for every single time you ever tried sending them a payload, so do some smarter logging here!!
        Console.WriteLine("Error calling client, attempting to disconnect.");
        try
        {
            MyService.SingletonServiceController.TerminateClientChannelByHashcode(d.Target.GetHashCode());//this is an IContextChannel object, kept in a dictionary of active connections, cross referenced by hashcode just for this exact occasion
        }
        catch (Exception ex2)
        {
            Console.WriteLine("Attempt to disconnect client failed: " + ex2.ToString());
        }
    }
}

I don't have any good ideas how to go and kill all the pending packets the server is still waiting to see if they'll get delivered on. Once I get the first exception I should in theory be able to go and terminate all the other requests in some queue somewhere, but this setup is functional and meets the objectives.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文