无服务器端的UDP群聊功能剖析(WCF版)

主要是想弄成一个系列,所以标题中的UDP字段我就没有修改.

这篇主要是讲解基于WCF实现的聊天室,它可以群聊,可以单聊,可以发送表情,支持智能的用户上线,下线提示功能.下面让我们先来看看具体的实现方式.

设计方式

首先,我们知道聊天室一般就是许多人聚在一起聊天,所以用户上线,用户下线功能必须有, 这样能够很方便的通知用户每个人的登录状态;当然,更为重要的是,聊天室中的人需要能够进行交流,所以,这里我设计了群聊和单聊的两种交流方式.

对于上线,我们的设想就是: 用户登录,然后向所有登录的用户发送一条信息,意即某某某登录了系统,然后系统中所有的用户会回馈这条信息,将自己的姓名发给登陆者,这样所有登录进来的人都能加载进来了. 这是不是和这个系列的第一章设计的一样呢?

当然,如何记录登陆的用户的信息呢? 这里我们用到了静态的委托事件来处理.每当有用户进来,只需要给其注册登录事件即可.

具体代码如下:

View Code
 //加入会议
       public string[] JoinMeeting(string name)
        {
            bool flag = true;
            bool userIncluded = false;
            if (String.IsNullOrEmpty(name))
                flag = false;

            if (flag)
            {
                lock (lockSync)
                {
                    if (!chatDict.ContainsKey(name))
                    {
                        joiner = name;
                        //这里保存的是chatEvent委托并且触发的事件
                        chatDict.Add(name, MessageToSendToClientSide);
                        userIncluded = true;
                    }
                }
            }

           //perform callback operation to client side so that the users in the chat session will have their chat user list refreshed.
            if (userIncluded)
            {
                //实例
                chatCallback = OperationContext.Current.GetCallbackChannel<IChatServiceCallback>();
                MessageEntity entity = new MessageEntity();
                entity.Sender = name;
                entity.UAction = UserAction.JoinMeeting;
                //用户一旦登录,首先会发送自己上线的消息给其他人
                HandleMessageInDelegatePool(entity);
                //之后用户会订阅其他相关操作: 群聊,单聊,退出 
                ChatEvent += MessageToSendToClientSide;

                //copy user list and return.
                string[] userList = new string[chatDict.Count];
                lock (lockSync)
                    chatDict.Keys.CopyTo(userList, 0);
                return userList;
            }
            else
                return null;
        }

其中,我们用Dictionary保存了用户名和其触发的事件的键值对.然后,当确认用户有效之后,就会通过HandleMessageInDelegatePool将信息发送出去(实质上是通过WCF服务端的回调方法将信息发送给了客户端),最后就是将当前登录用户加入静态委托链.

上面的登录做好以后,我们就得到了一个静态的委托链.这个委托链中的委托都可以共同触发同一个函数MessageToSendToClientSide, 所以,当用户有不同的行为时,都可以通过委托出去(实质上是调用MessageToSendToClientSide函数而已).那么对于群聊来说,就是遍历委托链,然后通过Delegate.Invoke或者是Delegate.BeginInvoke方法来循环发送群聊信息即可(可参见文章委托-利用GetInvocationList处理链式委托). 

View Code
 //触发委托链上面的事件
        private void HandleMessageInDelegatePool(MessageEntity msg)
        {
            if (ChatEvent != null)
            {
                foreach (ChatDelegate chatDelegate in ChatEvent.GetInvocationList())
                {
                    //触发事件,实际触发的是回调函数,也就是服务端会通过触发chatDelegate来将不同的登录用户的信息给返回给客户端,这样我们就可以知道谁加入了会议,谁说了什么话了.
                    chatDelegate.BeginInvoke(this, msg, new AsyncCallback(new Action<IAsyncResult>((iar) =>
                    {
                        ChatDelegate dele = null;
                        try
                        {
                            dele = (ChatDelegate)iar.AsyncState;
                            dele.EndInvoke(iar);
                        }
                        catch
                        {
                            ChatEvent -= dele;
                        }
                    })), chatDelegate);
                }
            }
        }

群聊代码:

View Code
  //群聊
        public void GroupChat(string message)
        {
            //信息实体
            MessageEntity entity = new MessageEntity();
            entity.MessageBody = message;
            entity.Sender = joiner;
            entity.UAction = UserAction.GroupChat;
            //遍历委托链,触发回调函数,将内容发给所有在线的人
            HandleMessageInDelegatePool(entity);
        }

这样,通过指定MessageEntity中的UserAction为GroupChat字段,并且指定消息内容,然后通过调用callback接口发送出去,那么就可以实现通过消息分发了.

对于单聊,这个更加容易实现,主要就是将字典中存储的键值对给提取出来,然后调用HandleMessageInDelegatePool方法即可.

View Code
 //单聊
        public void SingleChat(string toWho, string message)
        {
            //信息实体
            MessageEntity entity = new MessageEntity();
            entity.MessageBody = message;
            entity.UAction = UserAction.SingleChat;
            entity.Sender = joiner;

            ChatDelegate dele = null;
            ChatDelegate myChatDele=null;
            try
            {
                lock (lockSync)
                {
                    //得到待触发的事件
                    dele = chatDict[toWho];
                }
            }
            catch(KeyNotFoundException ex)
            {
                throw new KeyNotFoundException("Can't find user, pls check:" + ex.Message);
            }

            try
            {
                //触发
                dele.BeginInvoke(this, entity, new AsyncCallback(new Action<IAsyncResult>(iar =>
                {
                    myChatDele = (ChatDelegate)iar.AsyncState;
                    myChatDele.EndInvoke(iar);
                })), dele);
            }
            catch
            {
                ChatEvent -= myChatDele;
            }
        }

最后则是离开会议,这个需要发送所有的信息给线上的用户,所以依然是触发委托链,然后循环发送下线信息.最后需要记住的是一定要将注册的事件从委托链中取消,否则聊天室发送的信息依然会被接收到.

View Code
  //离开会议
        public void LeaveMeeting()
        {
            lock (lockSync)
            {
                chatDict.Remove(joiner);
            }
            //将委托从委托链中移除,那么之后此用户将不会接收到其他人发送的信息
            ChatEvent -= MessageToSendToClientSide;
            MessageEntity entity = new MessageEntity();
            entity.UAction = UserAction.LeaveMeeting;
            entity.Sender = joiner;
            joiner = null;
            //发送给所有在线的人,通知下线
            HandleMessageInDelegatePool(entity);
        }

客户端生成

做完这些之后,按照我以前的文章来生成配置文件(请参见我所知道的CallbackContract in WCF 以及 在net.tcp模式下,由SvcUtil.exe生成代理类文件和配置文件),采用net.tcp模式,然后通过svcutil net.tcp://****/chatservice /async来生成异步的客户端代理类即可.

View Code
 #region CallBack回调方法

        //加入会议,这里可以将登录的用户加入到列表中
        public void JoinCallBack(string userName)
        {
            NotificationLog("User (" + userName + ") join at " + DateTime.Now.ToString());
            SetUserLoginUI(userName);
        }
        //群聊
        public void ReceiveGroupChatMsgCallback(string sender, string message)
        {
            string messageEx = sender + " " + DateTime.Now.ToLongTimeString() + " :" + message + Environment.NewLine;
            SetUserChatMsg(messageEx, ChatRoomMsg);
        }
        //单聊
        public void ReceiveSingleChatMsgCallback(string sender, string message)
        {
            SetUserChatMsg(sender + " say to YOU @" + DateTime.Now.ToLongTimeString() + " :" + message + Environment.NewLine, ChatRoomMsg);
        }
        //离开
        public void LeaveMeetingCallBack(string userName)
        {
            NotificationLog("User (" + userName + ") leave at " + DateTime.Now.ToString());
            ResetUserLoginUI(userName);
        }
        #endregion

这里是服务端发给客户端的信息,只需要继承callback接口,在客户端我们就可以接收到这些信息.

效果图演示

下面是具体的结果图演示,在演示中,三台机器都在外网中,我们只需要将服务端的配置文件修改为某一台主机的地址,然后运行即可.

(图1,当新用户上线时候,会自动加入到列表中)

(图2,两个用户之前可以单聊)

(图3,用户下线,有自动提示,并且用户自动从当前列表移除)

(图4, 本图展示的是群聊,单聊,上线,下线提示等各种效果)

源码下载

请点击这里下载

如果这篇文章对你有用,还请多给我一点支持哦.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值