WCF综合运用之:聊天系统

      使用WCF做聊天系统,也是对WCF的一个初步的综合运用,请看下面的整体框架
                          
      整个聊天系统需要考虑下面的几点:
      1、使用什么样的消息交换模式(如果不清楚参考:http://www.cnblogs.com/artech/archive/2007/03/02/661969.html):消息交换包括one-way,request/reply,duplex,前面两种相对的比较的简单,duplex关键是实现回调-服务端可以回调客户端的代码,所以我们需要定义两个接口:
    [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
    public interface IChat  //服务端的接口
    {
        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false, Action = "http://tempuri.org/IChat/Say")]
        void Say(string msg);   //公聊

        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false, Action = "http://tempuri.org/IChat/Whisper")]
        void Whisper(string to, string msg);    //私聊

        [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false, Action = "http://tempuri.org/IChat/Connect")]
        bool Connect(Person name);  //连接服务端 注册当前用户

        [OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false, Action = "http://tempuri.org/IChat/PersonList")]
        List<Person> PersonList(Person name);   //获取所有的页面信息

        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true, Action = "http://tempuri.org/IChat/Leave")]
        void Leave();   //注销用户
    }

    public interface IChatCallback //回调接口
    {
        [OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChat/Receive")]
        void Receive(Person sender, string message);    //接收公聊信息

        [OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChat/ReceiveWhisper")]
        void ReceiveWhisper(Person sender, string message); //接收私聊信息

        [OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChat/UserEnter")]
        void UserEnter(Person person);  //接收进入聊天室的用户

        [OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChat/UserLeave")]
        void UserLeave(Person person);  //移除在聊天室的用户
    }
     这里注意:要实现回调,必须在服务契约上指明回调的接口CallbackContract = typeof(IChatCallback),IChatCallback接口需要在客户端实现.
      2、使用什么样的实例模型,由于我们需要需要保持客户端的状态。我们需要使用PerSession模型(如果不清楚请参见:http://www.cnblogs.com/wanqiming/archive/2009/09/17/1568401.html)。代码如下:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class ChatService : IChat
    {
        #region Instance fields

        //定义一个静态对象用于线程部份代码块的锁定,用于lock操作
        private static Object syncObj = new Object();

        //创建一个IChatCallback 回调接口实例,接口成员始终是公共的,所有没有访问修饰符
        IChatCallback callback = null;

        //定义一个委托
        public delegate void ChatEventHandler(object sender, ChatEventArgs e);

        //定义一个静态的委托事件
        public static event ChatEventHandler ChatEvent;

        //创建委托(ChatEventHandler)的一个空实例
        private ChatEventHandler myEventHandler = null;

        //创建一个静态Dictionary(表示键和值)集合(字典),用于记录在线成员,Dictionary<(Of <(TKey, TValue>)>) 泛型类
        static Dictionary<Person, ChatEventHandler> chatters = new Dictionary<Person, ChatEventHandler>();

        //当前用户
        private Person person;

        //确认用户是否存在
        private bool checkIfPersonExists(string name)
        {
            foreach (Person p in chatters.Keys)
            {
                if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        //根据用户搜索字典中是否存在 ChatEventHandler , 以便调用执行
        private ChatEventHandler getPersonHandler(string name)
        {
            foreach (Person p in chatters.Keys)
            {
                if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                {
                    ChatEventHandler chatTo = null;
                    chatters.TryGetValue(p, out chatTo);
                    return chatTo;
                }
            }
            return null;
        }
       
        //根据用户名,寻找用户
        private Person getPerson(string name)
        {
            foreach (Person p in chatters.Keys)
            {
                if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                {
                    return p;
                }
            }
            return null;
        }

        #endregion

        #region IChat implementation

        //连接服务器,如果存在重复的用户名,返回失败,否则连接成功,把当前用户加到服务器用户列表中
        public bool Connect(Person person)
        {
            bool userAdded = false;

            //创建一个新的委托代理,指向 MyEventHandler 方法
            myEventHandler = new ChatEventHandler(MyEventHandler);

            //锁定,保持lock块中的代码段始终只有一个线程在调用,原因是ConcurrencyMode.Multiple 为异步的多线程实例,存在并发竞争问题
            lock (syncObj)
            {
                if (!checkIfPersonExists(person.Name) && person != null)    //根据用户名判断用户名是否被占用
                {
                    this.person = person;
                    chatters.Add(person, MyEventHandler); //添加当前用户到服务器列表中
                    userAdded = true;
                }
            }
            if (userAdded)
            {
                callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
                ChatEventArgs e = new ChatEventArgs();
                e.msgType = MessageType.UserEnter;
                e.person = this.person;
                BroadcastMessage(e);                
                ChatEvent += myEventHandler; //把当前实例加到事件中,便于调用对客户端的回调               
                return false;
            }
            else
            {
                return true;
            }
        }

        //获得聊天室中的所有用户信息
        public  List<Person> PersonList(Person person)
        {
            List<Person> list = new List<Person>();

            foreach (var p in chatters.Keys)
            {
                if( p.Name != person.Name )
                {
                    list.Add(p);
                }
            }
            return list;
        }

        //发送信息给所有的人
        public void Say(string msg)
        {
            ChatEventArgs e = new ChatEventArgs();
            e.msgType = MessageType.Receive;
            e.person = this.person;
            e.message = msg;
            BroadcastMessage(e);
        }

        //私聊 to 接受者姓名,msg 接受的信息
        public void Whisper(string to, string msg)
        {
            ChatEventArgs e = new ChatEventArgs();
            e.msgType = MessageType.ReceiveWhisper;
            e.person = this.person;
            e.message = msg;
            try
            {
                ChatEventHandler chatterTo;
                lock (syncObj)
                {
                    chatterTo = getPersonHandler(to);
                    if (chatterTo == null)
                    {
                        throw new KeyNotFoundException("The person whos name is " + to +
                                                        " could not be found");
                    }
                }                
                //异步回调
                chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
            }
            catch (KeyNotFoundException)
            {
            }
        }

        //用户离开
        public void Leave()
        {
            if (this.person == null)
                return;

            ChatEventHandler chatterToRemove = getPersonHandler(this.person.Name);

            lock (syncObj)
            {
                chatters.Remove(this.person);
            }
            ChatEvent -= chatterToRemove;
            ChatEventArgs e = new ChatEventArgs();
            e.msgType = MessageType.UserLeave;
            e.person = this.person;
            this.person = null;
           
            //告示所有的人
            BroadcastMessage(e);
        }
        #endregion
        #region private methods

        //判断当前用户的操作
        private void MyEventHandler(object sender, ChatEventArgs e)
        {
            try
            {
                switch (e.msgType)
                {
                    case MessageType.Receive:
                        callback.Receive(e.person, e.message);
                        break;
                    case MessageType.ReceiveWhisper:
                        callback.ReceiveWhisper(e.person, e.message);
                        break;
                    case MessageType.UserEnter:
                        callback.UserEnter(e.person);
                        break;
                    case MessageType.UserLeave:
                        callback.UserLeave(e.person);
                        break;
                }
            }
            catch
            {
                Leave();
            }
        }

       
        //把信息发给所有的在线用户
        private void BroadcastMessage(ChatEventArgs e)
        {

            ChatEventHandler temp = ChatEvent;
          
            if (temp != null)
            {

                ChatEvent(this, e);
            }
        }


        //广播中线程调用完成的回调方法
        //功能:清除异常多路广播委托的调用列表中异常对象(空对象)
        private void EndAsync(IAsyncResult ar)
        {
            ChatEventHandler d = null;

            try
            {
                System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)ar;
                d = ((ChatEventHandler)asres.AsyncDelegate);
                d.EndInvoke(ar);
            }
            catch
            {
                ChatEvent -= d;
            }
        }
        #endregion
    }

      需要注意的是:Persession下,需要回调客户端,可能会产生死锁,需要使用ConcurrencyMode = ConcurrencyMode.Multiple这种并发模式,但是这个不能保证数据的唯一,所以需要lock的方式。定义chatters来保持聊天室中所有用户的信息。运行的效果如下:
    

      看代码吧,下载地址:/Files/wanqiming/Chat.rar
posted @ 2009-09-30 10:40  成都ABC  阅读(853)  评论(1编辑  收藏  举报