主要是想弄成一个系列,所以标题中的UDP字段我就没有修改.
这篇主要是讲解基于WCF实现的聊天室,它可以群聊,可以单聊,可以发送表情,支持智能的用户上线,下线提示功能.下面让我们先来看看具体的实现方式.
设计方式
首先,我们知道聊天室一般就是许多人聚在一起聊天,所以用户上线,用户下线功能必须有, 这样能够很方便的通知用户每个人的登录状态;当然,更为重要的是,聊天室中的人需要能够进行交流,所以,这里我设计了群聊和单聊的两种交流方式.
对于上线,我们的设想就是: 用户登录,然后向所有登录的用户发送一条信息,意即某某某登录了系统,然后系统中所有的用户会回馈这条信息,将自己的姓名发给登陆者,这样所有登录进来的人都能加载进来了. 这是不是和这个系列的第一章设计的一样呢?
当然,如何记录登陆的用户的信息呢? 这里我们用到了静态的委托事件来处理.每当有用户进来,只需要给其注册登录事件即可.
具体代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//加入会议 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处理链式委托).
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//触发委托链上面的事件 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); } } }
群聊代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//群聊 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方法即可.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//单聊 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; } }
最后则是离开会议,这个需要发送所有的信息给线上的用户,所以依然是触发委托链,然后循环发送下线信息.最后需要记住的是一定要将注册的事件从委托链中取消,否则聊天室发送的信息依然会被接收到.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//离开会议 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来生成异步的客户端代理类即可.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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, 本图展示的是群聊,单聊,上线,下线提示等各种效果)
源码下载
如果这篇文章对你有用,还请多给我一点支持哦.