分享WCF聊天程序--WCFChat实现代码
无意中在一个国外的站点下到了一个利用WCF实现聊天的程序,作者是:Nikola Paljetak。研究了一下,自己做了测试和部分修改,感觉还不错,分享给大家。
先来看下运行效果:
开启服务:
客户端程序:
程序分为客户端和服务器端:
------------服务器端:
IChatService.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using System.Collections; namespace WCFChatService { // SessionMode.Required 允许Session会话。双工协定时的回调协定类型为IChatCallback接口 [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))] public interface IChatService { [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]//----->IsOneWay = false等待服务器完成对方法处理;IsInitiating = true启动Session会话,IsTerminating = false 设置服务器发送回复后不关闭会话 string[] Join(string name);//用户加入 [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)] void Say(string msg);//群聊信息 [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)] void Whisper(string to, string msg);//私聊信息 [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)] void Leave();//用户加入 } /// <summary> /// 双向通信的回调接口 /// </summary> interface IChatCallback { [OperationContract(IsOneWay = true)] void Receive(string senderName, string message); [OperationContract(IsOneWay = true)] void ReceiveWhisper(string senderName, string message); [OperationContract(IsOneWay = true)] void UserEnter(string name); [OperationContract(IsOneWay = true)] void UserLeave(string name); } /// <summary> /// 设定消息的类型 /// </summary> public enum MessageType { Receive, UserEnter, UserLeave, ReceiveWhisper }; /// <summary> /// 定义一个本例的事件消息类. 创建包含有关事件的其他有用的信息的变量,只要派生自EventArgs即可。 /// </summary> public class ChatEventArgs : EventArgs { public MessageType msgType; public string name; public string message; } }
ChatService.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WCFChatService { // InstanceContextMode.PerSession 服务器为每个客户会话创建一个新的上下文对象。ConcurrencyMode.Multiple 异步的多线程实例 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)] public class ChatService : IChatService { private static Object syncObj = new Object();////定义一个静态对象用于线程部份代码块的锁定,用于lock操作 IChatCallback callback = null; public delegate void ChatEventHandler(object sender, ChatEventArgs e);//定义用于把处理程序赋予给事件的委托。 public static event ChatEventHandler ChatEvent;//定义事件 static Dictionary<string, ChatEventHandler> chatters = new Dictionary<string, ChatEventHandler>();//创建一个静态Dictionary(表示键和值)集合(字典),用于记录在线成员,Dictionary<(Of <(TKey, TValue>)>) 泛型类 private string name; private ChatEventHandler myEventHandler = null; public string[] Join(string name) { bool userAdded = false; myEventHandler = new ChatEventHandler(MyEventHandler);//将MyEventHandler方法作为参数传递给委托 lock (syncObj)//线程的同步性,同步访问多个线程的任何变量,利用lock(独占锁),确保数据访问的唯一性。 { if (!chatters.ContainsKey(name) && name != "" && name != null) { this.name = name; chatters.Add(name, MyEventHandler); userAdded = true; } } if (userAdded) { callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();//获取当前操作客户端实例的通道给IChatCallback接口的实例callback,此通道是一个定义为IChatCallback类型的泛类型,通道的类型是事先服务契约协定好的双工机制。 ChatEventArgs e = new ChatEventArgs();//实例化事件消息类ChatEventArgs e.msgType = MessageType.UserEnter; e.name = name; BroadcastMessage(e); ChatEvent += myEventHandler; string[] list = new string[chatters.Count]; //以下代码返回当前进入聊天室成员的称列表 lock (syncObj) { chatters.Keys.CopyTo(list, 0);//将字典中记录的用户信息复制到数组中返回。 } return list; } else { return null; } } public void Say(string msg) { ChatEventArgs e = new ChatEventArgs(); e.msgType = MessageType.Receive; e.name = this.name; e.message = msg; BroadcastMessage(e); } public void Whisper(string to, string msg) { ChatEventArgs e = new ChatEventArgs(); e.msgType = MessageType.ReceiveWhisper; e.name = this.name; e.message = msg; try { ChatEventHandler chatterTo;//创建一个临时委托实例 lock (syncObj) { chatterTo = chatters[to]; //查找成员字典中,找到要接收者的委托调用 } chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);//异步方式调用接收者的委托调用 } catch (KeyNotFoundException) { } } public void Leave() { if (this.name == null) return; lock (syncObj) { chatters.Remove(this.name); } ChatEvent -= myEventHandler; ChatEventArgs e = new ChatEventArgs(); e.msgType = MessageType.UserLeave; e.name = this.name; this.name = null; BroadcastMessage(e); } //回调,根据客户端动作通知对应客户端执行对应的操作 private void MyEventHandler(object sender, ChatEventArgs e) { try { switch (e.msgType) { case MessageType.Receive: callback.Receive(e.name, e.message); break; case MessageType.ReceiveWhisper: callback.ReceiveWhisper(e.name, e.message); break; case MessageType.UserEnter: callback.UserEnter(e.name); break; case MessageType.UserLeave: callback.UserLeave(e.name); break; } } catch { Leave(); } } private void BroadcastMessage(ChatEventArgs e) { ChatEventHandler temp = ChatEvent; if (temp != null) { //循环将在线的用户广播信息 foreach (ChatEventHandler handler in temp.GetInvocationList()) { //异步方式调用多路广播委托的调用列表中的ChatEventHandler handler.BeginInvoke(this, e, new AsyncCallback(EndAsync), null); } } } //广播中线程调用完成的回调方法功能:清除异常多路广播委托的调用列表中异常对象(空对象) 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; } } } }
------------客户端:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using System.ServiceModel; namespace WCFChatClient { public partial class ChatForm : Form, IChatServiceCallback { /// <summary> /// 该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,直到窗口程序处理完消息再返回。 /// </summary> /// <param name="hWnd">其窗口程序将接收消息的窗口的句柄</param> /// <param name="msg">指定被发送的消息</param> /// <param name="wParam">指定附加的消息指定信息</param> /// <param name="lParam">指定附加的消息指定信息</param> [DllImport("user32.dll")] private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam); //当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件 private const int WM_VSCROLL = 0x115; private const int SB_BOTTOM = 7; private int lastSelectedIndex = -1; private ChatServiceClient proxy; private string userName; private WaitForm wfDlg = new WaitForm(); private delegate void HandleDelegate(string[] list); private delegate void HandleErrorDelegate(); public ChatForm() { InitializeComponent(); ShowInterChatMenuItem(true); } /// <summary> /// 连接服务器 /// </summary> private void InterChatMenuItem_Click(object sender, EventArgs e) { lbOnlineUsers.Items.Clear(); LoginForm loginDlg = new LoginForm(); if (loginDlg.ShowDialog() == DialogResult.OK) { userName = loginDlg.txtUserName.Text; loginDlg.Close(); } txtChatContent.Focus(); Application.DoEvents(); InstanceContext site = new InstanceContext(this);//为实现服务实例的对象进行初始化 proxy = new ChatServiceClient(site); IAsyncResult iar = proxy.BeginJoin(userName, new AsyncCallback(OnEndJoin), null); wfDlg.ShowDialog(); } private void OnEndJoin(IAsyncResult iar) { try { string[] list = proxy.EndJoin(iar); HandleEndJoin(list); } catch (Exception e) { HandleEndJoinError(); } } /// <summary> /// 错误提示 /// </summary> private void HandleEndJoinError() { if (wfDlg.InvokeRequired) wfDlg.Invoke(new HandleErrorDelegate(HandleEndJoinError)); else { wfDlg.ShowError("无法连接聊天室!"); ExitChatSession(); } } /// <summary> /// 登录结束后的处理 /// </summary> /// <param name="list"></param> private void HandleEndJoin(string[] list) { if (wfDlg.InvokeRequired) wfDlg.Invoke(new HandleDelegate(HandleEndJoin), new object[] { list }); else { wfDlg.Visible = false; ShowInterChatMenuItem(false); foreach (string name in list) { lbOnlineUsers.Items.Add(name); } AppendText(" 用户: " + userName + "--------登录---------" + DateTime.Now.ToString()+ Environment.NewLine); } } /// <summary> /// 退出聊天室 /// </summary> private void OutInterChatMenuItem_Click(object sender, EventArgs e) { ExitChatSession(); Application.Exit(); } /// <summary> /// 群聊 /// </summary> private void btnChat_Click(object sender, EventArgs e) { SayAndClear("", txtChatContent.Text, false); txtChatContent.Focus(); } /// <summary> /// 发送消息 /// </summary> private void SayAndClear(string to, string msg, bool pvt) { if (msg != "") { try { CommunicationState cs = proxy.State; //pvt 公聊还是私聊 if (!pvt) { proxy.Say(msg); } else { proxy.Whisper(to, msg); } txtChatContent.Text = ""; } catch { AbortProxyAndUpdateUI(); AppendText("失去连接: " + DateTime.Now.ToString() + Environment.NewLine); ExitChatSession(); } } } private void txtChatContent_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 13) { e.Handled = true; btnChat.PerformClick(); } } /// <summary> /// 只有选择一个用户时,私聊按钮才可用 /// </summary> private void lbOnlineUsers_SelectedIndexChanged(object sender, EventArgs e) { AdjustWhisperButton(); } /// <summary> /// 私聊 /// </summary> private void btnWhisper_Click(object sender, EventArgs e) { if (txtChatDetails.Text == "") { return; } object to = lbOnlineUsers.SelectedItem; if (to != null) { string receiverName = (string)to; AppendText("私下对" + receiverName + "说: " + txtChatContent.Text);//+ Environment.NewLine SayAndClear(receiverName, txtChatContent.Text, true); txtChatContent.Focus(); } } /// <summary> /// 连接聊天室 /// </summary> private void ShowInterChatMenuItem(bool show) { InterChatMenuItem.Enabled = show; OutInterChatMenuItem.Enabled = this.btnChat.Enabled = !show; } private void AppendText(string text) { txtChatDetails.Text += text; SendMessage(txtChatDetails.Handle, WM_VSCROLL, SB_BOTTOM, new IntPtr(0)); } /// <summary> /// 退出应用程序时,释放使用资源 /// </summary> private void ExitChatSession() { try { proxy.Leave(); } catch { } finally { AbortProxyAndUpdateUI(); } } /// <summary> /// 释放使用资源 /// </summary> private void AbortProxyAndUpdateUI() { if (proxy != null) { proxy.Abort(); proxy.Close(); proxy = null; } ShowInterChatMenuItem(true); } /// <summary> /// 接收消息 /// </summary> public void Receive(string senderName, string message) { AppendText(senderName + "说: " + message + Environment.NewLine); } /// <summary> /// 接收私聊消息 /// </summary> public void ReceiveWhisper(string senderName, string message) { AppendText(senderName + " 私下说: " + message + Environment.NewLine); } /// <summary> /// 新用户登录 /// </summary> public void UserEnter(string name) { AppendText("用户 " + name + " --------登录---------" + DateTime.Now.ToString() + Environment.NewLine); lbOnlineUsers.Items.Add(name); } /// <summary> /// 用户离开 /// </summary> public void UserLeave(string name) { AppendText("用户 " + name + " --------离开---------" + DateTime.Now.ToString() + Environment.NewLine); lbOnlineUsers.Items.Remove(name); AdjustWhisperButton(); } /// <summary> /// 控制私聊按钮的可用性,只有选择了用户时按钮才可用 /// </summary> private void AdjustWhisperButton() { if (lbOnlineUsers.SelectedIndex == lastSelectedIndex) { lbOnlineUsers.SelectedIndex = -1; lastSelectedIndex = -1; btnWhisper.Enabled = false; } else { btnWhisper.Enabled = true; lastSelectedIndex = lbOnlineUsers.SelectedIndex; } txtChatContent.Focus(); } /// <summary> /// 窗体关闭时,释放使用资源 /// </summary> private void ChatForm_FormClosed(object sender, FormClosedEventArgs e) { AbortProxyAndUpdateUI(); Application.Exit(); } } }
代码中我做了详细的讲解,相信园友们完全可以看懂。代码中的一些使用的方法还是值得大家参考学习的。这里涉及到了WCF的使用方法,需要注意的是:如果想利用工具生成代理类,需要加上下面的代码:
if (host.Description.Behaviors.Find<System.ServiceModel.Description.ServiceMetadataBehavior>() == null) { BindingElement metaElement = new TcpTransportBindingElement(); CustomBinding metaBind = new CustomBinding(metaElement); host.Description.Behaviors.Add(new System.ServiceModel.Description.ServiceMetadataBehavior()); host.AddServiceEndpoint(typeof(System.ServiceModel.Description.IMetadataExchange), metaBind, "MEX"); }
否则在生成代理类的时候会报错如下的错误:
源码下载:
/Files/gaoweipeng/WCFChat.rar
上一篇:C#连接数据库的方法
栏 目:C#教程
下一篇:C#基于WebBrowser获取cookie的实现方法
本文地址:https://www.xiuzhanwang.com/a1/C_jiaocheng/6874.html
您可能感兴趣的文章
- 01-10WPF实现类似360安全卫士界面的程序源码分享
- 01-10C#基于UDP实现的P2P语音聊天工具
- 01-10分享WCF文件传输实现方法---WCFFileTransfer
- 01-10C#制作简单的多人在线即时交流聊天室
- 01-10分享我在工作中遇到的多线程下导致RCW无法释放的问题
- 01-10分享一个C#编写简单的聊天程序(详细介绍)
- 01-10Windows中使用C#为文件夹和文件编写密码锁的示例分享
- 01-10C# WCF简单入门图文教程(VS2010版)
- 01-10区分WCF与WebService的异同、优势
- 01-10基于C#实现网页爬虫
阅读排行
本栏相关
- 01-10C#通过反射获取当前工程中所有窗体并
- 01-10关于ASP网页无法打开的解决方案
- 01-10WinForm限制窗体不能移到屏幕外的方法
- 01-10WinForm绘制圆角的方法
- 01-10C#实现txt定位指定行完整实例
- 01-10WinForm实现仿视频播放器左下角滚动新
- 01-10C#停止线程的方法
- 01-10C#实现清空回收站的方法
- 01-10C#通过重写Panel改变边框颜色与宽度的
- 01-10C#实现读取注册表监控当前操作系统已
随机阅读
- 01-10C#中split用法实例总结
- 08-05织梦dedecms什么时候用栏目交叉功能?
- 04-02jquery与jsp,用jquery
- 01-10delphi制作wav文件的方法
- 01-10使用C语言求解扑克牌的顺子及n个骰子
- 08-05DEDE织梦data目录下的sessions文件夹有什
- 08-05dedecms(织梦)副栏目数量限制代码修改
- 01-11Mac OSX 打开原生自带读写NTFS功能(图文
- 01-10SublimeText编译C开发环境设置
- 01-11ajax实现页面的局部加载