無意中在一個國外的站點下到了一個利用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
新聞熱點
疑難解答