分享WCF聊天程序--WCFChat實現(xiàn)代碼
無意中在一個國外的站點下到了一個利用WCF實現(xiàn)聊天的程序,作者是:Nikola Paljetak。研究了一下,自己做了測試和部分修改,感覺還不錯,分享給大家。
先來看下運行效果:
開啟服務(wù):
客戶端程序:
程序分為客戶端和服務(wù)器端:
------------服務(wù)器端:
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會話。雙工協(xié)定時的回調(diào)協(xié)定類型為IChatCallback接口 [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))] public interface IChatService { [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]//----->IsOneWay = false等待服務(wù)器完成對方法處理;IsInitiating = true啟動Session會話,IsTerminating = false 設(shè)置服務(wù)器發(fā)送回復(fù)后不關(guān)閉會話 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> /// 雙向通信的回調(diào)接口 /// </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> /// 設(shè)定消息的類型 /// </summary> public enum MessageType { Receive, UserEnter, UserLeave, ReceiveWhisper }; /// <summary> /// 定義一個本例的事件消息類. 創(chuàng)建包含有關(guān)事件的其他有用的信息的變量,只要派生自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 服務(wù)器為每個客戶會話創(chuàng)建一個新的上下文對象。ConcurrencyMode.Multiple 異步的多線程實例 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)] public class ChatService : IChatService { private static Object syncObj = new Object();////定義一個靜態(tài)對象用于線程部份代碼塊的鎖定,用于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>();//創(chuàng)建一個靜態(tài)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方法作為參數(shù)傳遞給委托 lock (syncObj)//線程的同步性,同步訪問多個線程的任何變量,利用lock(獨占鎖),確保數(shù)據(jù)訪問的唯一性。 { if (!chatters.ContainsKey(name) && name != "" && name != null) { this.name = name; chatters.Add(name, MyEventHandler); userAdded = true; } } if (userAdded) { callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();//獲取當(dāng)前操作客戶端實例的通道給IChatCallback接口的實例callback,此通道是一個定義為IChatCallback類型的泛類型,通道的類型是事先服務(wù)契約協(xié)定好的雙工機(jī)制。 ChatEventArgs e = new ChatEventArgs();//實例化事件消息類ChatEventArgs e.msgType = MessageType.UserEnter; e.name = name; BroadcastMessage(e); ChatEvent += myEventHandler; string[] list = new string[chatters.Count]; //以下代碼返回當(dāng)前進(jìn)入聊天室成員的稱列表 lock (syncObj) { chatters.Keys.CopyTo(list, 0);//將字典中記錄的用戶信息復(fù)制到數(shù)組中返回。 } 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;//創(chuàng)建一個臨時委托實例 lock (syncObj) { chatterTo = chatters[to]; //查找成員字典中,找到要接收者的委托調(diào)用 } chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);//異步方式調(diào)用接收者的委托調(diào)用 } 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); } //回調(diào),根據(jù)客戶端動作通知對應(yīng)客戶端執(zhí)行對應(yīng)的操作 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) { //循環(huán)將在線的用戶廣播信息 foreach (ChatEventHandler handler in temp.GetInvocationList()) { //異步方式調(diào)用多路廣播委托的調(diào)用列表中的ChatEventHandler handler.BeginInvoke(this, e, new AsyncCallback(EndAsync), null); } } } //廣播中線程調(diào)用完成的回調(diào)方法功能:清除異常多路廣播委托的調(diào)用列表中異常對象(空對象) private void EndAsync(IAsyncResult ar) { ChatEventHandler d = null; try { //封裝異步委托上的異步操作結(jié)果 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> /// 該函數(shù)將指定的消息發(fā)送到一個或多個窗口。此函數(shù)為指定的窗口調(diào)用窗口程序,直到窗口程序處理完消息再返回?!? /// </summary> /// <param name="hWnd">其窗口程序?qū)⒔邮障⒌拇翱诘木浔?lt;/param> /// <param name="msg">指定被發(fā)送的消息</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); //當(dāng)一個窗口標(biāo)準(zhǔn)垂直滾動條產(chǎn)生一個滾動事件時發(fā)送此消息給那個窗口,也發(fā)送給擁有它的控件 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> /// 連接服務(wù)器 /// </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);//為實現(xiàn)服務(wù)實例的對象進(jìn)行初始化 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> /// 登錄結(jié)束后的處理 /// </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> /// 發(fā)送消息 /// </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> /// 退出應(yīng)用程序時,釋放使用資源 /// </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> /// 窗體關(guān)閉時,釋放使用資源 /// </summary> private void ChatForm_FormClosed(object sender, FormClosedEventArgs e) { AbortProxyAndUpdateUI(); Application.Exit(); } } }
代碼中我做了詳細(xì)的講解,相信園友們完全可以看懂。代碼中的一些使用的方法還是值得大家參考學(xué)習(xí)的。這里涉及到了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
- II7添加應(yīng)用程序測試時 無法驗證對路徑(c:\test\WcfService)的訪問
- WinForm窗體調(diào)用WCF服務(wù)窗體卡死問題
- WCF配置心得
- 關(guān)于.NET/C#/WCF/WPF 打造IP網(wǎng)絡(luò)智能視頻監(jiān)控系統(tǒng)的介紹
- IIS7 配置大全(ASP.NET 2.0, WCF, ASP.NET MVC,php)
- 讓IIS8支持WCF的更簡單方法
- 在WCF數(shù)據(jù)訪問中使用緩存提高Winform字段中文顯示速度的方法
- C# yield在WCF中的錯誤用法(一)
- C# yield在WCF中的錯誤使用(二)
- 區(qū)分WCF與WebService的異同、優(yōu)勢
相關(guān)文章
C#關(guān)聯(lián)自定義文件類型到應(yīng)用程序并實現(xiàn)自動導(dǎo)入功能
今天通過本文給大家分享C#關(guān)聯(lián)自定義文件類型到應(yīng)用程序并實現(xiàn)自動導(dǎo)入功能,代碼中寫入了兩個注冊表,實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09C#實現(xiàn)Excel轉(zhuǎn)PDF時設(shè)置內(nèi)容適應(yīng)頁面寬度
將Excel轉(zhuǎn)為PDF格式時,通常情況下轉(zhuǎn)換出來的PDF頁面都是默認(rèn)的寬度大小。所以本文提供了C#實現(xiàn)Excel轉(zhuǎn)PDF時設(shè)置內(nèi)容適應(yīng)頁面寬度的示例代碼,需要的可以參考一下2022-04-04