無服務器端的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, 本圖展示的是群聊,單聊,上線,下線提示等各種效果)

源碼下載

請點擊這里下載

如果這篇文章對你有用,還請多給我一點支持哦.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM