当前位置:编程学习 > C#/ASP.NET >>

WCF 心跳包导致server端性能差的问题

各位大神,我最近在设计一个wcf的程序,但是遇到了点问题。

小弟在设计一个基于观察者模式的程序,需要涉及到多个客户端和一个server端相连,所有客户端每隔5秒发个心跳包给server。在server端我用System.Collections.Concurrent.ConcurrentDictionary容器来保存对客户端连接的引用和客户端对应的那个心跳包。由于每个客户端会不停的发数据包,然后就会不停的去往这个容器里读写东西,我很担心会引起性能问题,有大神能给我解释下ConcurrentDictionary的锁的机制是什么样的么?

PS:或者,我是不是可以抛弃wcf,通过SOCKET来实现比较好一点。
不知道我描述清楚了么有,在此先谢谢各位大神了。

server端代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Threading;
using Server;
using System.Collections.Concurrent;
using System.ServiceModel.Channels;


namespace WCFLib
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public class Publisher : IPublisher
    {
        private static readonly ConcurrentDictionary<iCallBack, Package> Clients = new ConcurrentDictionary<iCallBack, Package>();
        public void Register()
        {
            var client = OperationContext.Current.GetCallbackChannel<iCallBack>();
            if (!Clients.ContainsKey(client))
            {
                Clients.TryAdd(client, new Package(DateTime.Now));
            }

        }

         public void SendHeartbeatPackage()
        {
            var client = OperationContext.Current.GetCallbackChannel<iCallBack>();

            if (Clients.ContainsKey(client))
            {
                Clients[client] = new Package(DateTime.Now);
            }

            Console.WriteLine("Begin to update heartbeat package.");
            Console.WriteLine("Updated time is {0}", (Clients[client] as Package).Time);
        }

     }
}

client端代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading;
using System.Net;

namespace ConsoleClient2
{
    class Program
    {
        static void Main(string[] args)
        {
            InstanceContext callback = new InstanceContext(new Callback());
            ServerProxy.PublisherClient proxy = new ServerProxy.PublisherClient(callback);
            proxy.Register();

            new Thread(() =>
            {
                while (true)
                {
                    Thread.Sleep(5000);
                    proxy.SendHeartbeatPackage();
                }
            }).Start();

            Console.Read();
        }
    }
}

--------------------编程问答-------------------- 如果你的客户数不过百,5秒发个心跳包,



那你想的太多了,把电脑的速度想的太差了 --------------------编程问答-------------------- rtdb,谢谢你的答复,我的客户端应该在10个左右,我很担心在往concurrentdictionary里面会写数据的时候会不停的有加锁解锁的过程,这样不会造成性能问题么? --------------------编程问答-------------------- 我说的数据还是考虑到了网络及系统整体响应速度。

若只是concurrentdictionary的加锁解锁,
呵呵你太小看现在的CPU速度了,
你可以自己测试一下,我相信每秒十万上下只是起步。 --------------------编程问答-------------------- RTDB,那我这样设计没问题吧,或者你还有什么更好的建议么?我自己纠结了这个问题好久,一直觉得性能没达到最佳。 --------------------编程问答-------------------- 不精通SOCKET的话,还是用WCF,小规模应用(10客户端)根本不用考虑太多,但回发需要注意,不能使用单线程(可用线程池或多线程),否则中途某个客户端的网络故障会导致后面其他客户端晋重延迟。 --------------------编程问答--------------------
引用 5 楼 ycg_893 的回复:
不精通SOCKET的话,还是用WCF,小规模应用(10客户端)根本不用考虑太多,但回发需要注意,不能使用单线程(可用线程池或多线程),否则中途某个客户端的网络故障会导致后面其他客户端晋重延迟。


YCG_893,谢谢,我明白了,我不用担心性能问题了。还有个附加问题,你说的回发时使用多线程是不是指在server端当我callback每个客户端时都使用独立的线程来完成针对每个客户端的调用? --------------------编程问答-------------------- Hi,ycg_893
我在server端有个方法实现的就是遍历所有客户端,然后给所有客户端发命令,代码如下

 public void KickoffTest()
        {
              foreach (var item in Clients)
            {
//我该在这里用多线程来实现么?
                item.Key.SendRequestToClient("Begin Testing");
            }
        }
不是在这里发命令时候要用多线程去实现? --------------------编程问答-------------------- 人都不在了么?大神们再给我解释下这个小问题,我就结贴给分了。 --------------------编程问答-------------------- 如果你的客户端都只向服务器获取数据,这个就不存在回发问题,因为是单向的,若需要服务器也向客户端发送数据则是双向的(WCF双工通信),服务器主动发给客户端就需要注意,假设100个客户端,发到50个的时候阻塞了或者需要待到超时异常(如果不处理异常,那每51个以后都收不到)后第51个才开始发送,阻塞时间根据网络情况和超时时间确定,即使没有发生这种情况,若每一个客户端需要2秒,是不是第100个客户端需要几分钟后才接到;

使用多线程或线程池也取决于你的客户端规模,若有几千个客户端,每次每个需要一个线程的话,估计你的服务器无法承受,会因线程耗尽而崩溃。

关于线程问题不是几句话就完全说明清楚,这跟规模、设计、需求有关;

至于配置WCF服务器也有关,如单实例、一个会话一个实例、还是每次都是一个实例、要不要同步上下文等;详看WCF配置

你上面的代码可以简单地在循环内加线程,但这种仅作为测试还可以,实际部署应用你可以多尝试各种故障测试就知道不可行。 --------------------编程问答-------------------- 还需要做线程安全方面,如发送时有100个,但正在发送的,有注销退出的。按你的代码就会产生“枚举的集合已变动的异常”。也不应该说等我发完了你再退出,这当然不合理,可以简单地解决就是复制一个数组出来,实际上复制数据时就需要线程安全。如下简单示例:

      iCallBack[] ClientKeys;
      lock(lockObject)
         {
            ClientKeys = Clients.Keys.ToArray();
         }
      foreach (var item in ClientKeys)
             {
               //新线程或加入线程池
                  item.SendRequestToClient("Begin Testing");
             }
 

--------------------编程问答-------------------- ycg_893 , 谢谢你不厌其烦的指点,我大概知道怎么弄了,能不能等我实现了再给我看下?我能晚点结分给你行么? --------------------编程问答--------------------

namespace WCFLib
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public class Publisher : IPublisher
    {
        private static readonly ConcurrentDictionary<iCallBack, Package> Clients = new ConcurrentDictionary<iCallBack, Package>();
        public void Register()
        {
            var client = OperationContext.Current.GetCallbackChannel<iCallBack>();
            if (!Clients.ContainsKey(client))
            {
                Clients.TryAdd(client, new Package(DateTime.Now));
            }

        }

         public void SendHeartbeatPackage()
        {
            var client = OperationContext.Current.GetCallbackChannel<iCallBack>();

            if (Clients.ContainsKey(client))
            {
                Clients[client] = new Package(DateTime.Now);
            }

            Console.WriteLine("Begin to update heartbeat package.");
            Console.WriteLine("Updated time is {0}", (Clients[client] as Package).Time);
        }

     }
}



Reentrant 模式下 并不是真正的并发吧? 改成multi的 你的Register 方法就不对了 --------------------编程问答--------------------
引用 12 楼 zealot112345 的回复:

namespace WCFLib
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public class Publisher : IPublisher
    {
        private static readonly ConcurrentDictionary<iCallBack, Package> Clients = new ConcurrentDictionary<iCallBack, Package>();
        public void Register()
        {
            var client = OperationContext.Current.GetCallbackChannel<iCallBack>();
            if (!Clients.ContainsKey(client))
            {
                Clients.TryAdd(client, new Package(DateTime.Now));
            }

        }

         public void SendHeartbeatPackage()
        {
            var client = OperationContext.Current.GetCallbackChannel<iCallBack>();

            if (Clients.ContainsKey(client))
            {
                Clients[client] = new Package(DateTime.Now);
            }

            Console.WriteLine("Begin to update heartbeat package.");
            Console.WriteLine("Updated time is {0}", (Clients[client] as Package).Time);
        }

     }
}



Reentrant 模式下 并不是真正的并发吧? 改成multi的 你的Register 方法就不对了

能说的详细点么?不对在哪里,大哥 --------------------编程问答-------------------- 这。。。。。。。
不会的 只要不是
while(true){
  //心跳包发送
}
就不会出问题。。。。。。

另外一分钟发一个也没有问题
补充:.NET技术 ,  C#
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,