主要是想弄成一個系列,所以標題中的UDP字段我就沒有修改.
這篇主要是講解基於WCF實現的聊天室,它可以群聊,可以單聊,可以發送表情,支持智能的用戶上線,下線提示功能.下面讓我們先來看看具體的實現方式.
設計方式
首先,我們知道聊天室一般就是許多人聚在一起聊天,所以用戶上線,用戶下線功能必須有, 這樣能夠很方便的通知用戶每個人的登錄狀態;當然,更為重要的是,聊天室中的人需要能夠進行交流,所以,這里我設計了群聊和單聊的兩種交流方式.
對於上線,我們的設想就是: 用戶登錄,然后向所有登錄的用戶發送一條信息,意即某某某登錄了系統,然后系統中所有的用戶會回饋這條信息,將自己的姓名發給登陸者,這樣所有登錄進來的人都能加載進來了. 這是不是和這個系列的第一章設計的一樣呢?
當然,如何記錄登陸的用戶的信息呢? 這里我們用到了靜態的委托事件來處理.每當有用戶進來,只需要給其注冊登錄事件即可.
具體代碼如下:

//加入會議 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處理鏈式委托).

//觸發委托鏈上面的事件 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); } } }
群聊代碼:

//群聊 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方法即可.

//單聊 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; } }
最后則是離開會議,這個需要發送所有的信息給線上的用戶,所以依然是觸發委托鏈,然后循環發送下線信息.最后需要記住的是一定要將注冊的事件從委托鏈中取消,否則聊天室發送的信息依然會被接收到.

//離開會議 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來生成異步的客戶端代理類即可.

#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, 本圖展示的是群聊,單聊,上線,下線提示等各種效果)
源碼下載
如果這篇文章對你有用,還請多給我一點支持哦.