聊天程序WMChat开发文档
无意中在一个国外的站点下到了一个利用WCF实现聊天的程序,作者是:Nikola Paljetak。研究了一下,自己做了测试和部分修改,感觉还不错,分享给大家。
先来看下运行效果:
开启服务:
客户端程序:
程序分为客户端和服务器端:
------------服务器端:
IChatService.cs:
Code
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
Code
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
{
******* 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>)>) 泛型类
******* string name;
******* 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);
}
//回调,根据客户端动作通知对应客户端执行对应的操作
******* 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();
}
}
******* 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);
}
}
}
//广播中线程调用完成的回调方法功能:清除异常多路广播委托的调用列表中异常对象(空对象)
******* 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;
}
}
}
}
------------客户端:
Code
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")]
******* static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
//当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件
******* const int WM_VSCROLL = 0x115;
******* const int SB_BOTTOM = 7;
******* int lastSelectedIndex = -1;
******* ChatServiceClient proxy;
******* string userName;
******* WaitForm wfDlg = new WaitForm();
******* delegate void HandleDelegate(string[] list);
******* delegate void HandleErrorDelegate();
public ChatForm()
{
InitializeComponent();
ShowInterChatMenuItem(true);
}
/// <summary>
/// 连接服务器
/// </summary>
******* 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();
}
******* void OnEndJoin(IAsyncResult iar)
{
try
{
string[] list = proxy.EndJoin(iar);
HandleEndJoin(list);
}
catch (Exception e)
{
HandleEndJoinError();
}
}
/// <summary>
/// 错误提示
/// </summary>
******* void HandleEndJoinError()
{
if (wfDlg.InvokeRequired)
wfDlg.Invoke(new HandleErrorDelegate(HandleEndJoinError));
else
{
wfDlg.ShowError("无法连接聊天室!");
ExitChatSession();
}
}
/// <summary>
/// 登录结束后的处理
/// </summary>
/// <param name="list"></param>
******* 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>
******* void OutInterChatMenuItem_Click(object sender, EventArgs e)
{
ExitChatSession();
Application.Exit();
}
/// <summary>
/// 群聊
/// </summary>
******* void btnChat_Click(object sender, EventArgs e)
{
SayAndClear("", txtChatContent.Text, false);
txtChatContent.Focus();
}
/// <summary>
/// 发送消息
/// </summary>
******* 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();
}
}
}
******* void txtChatContent_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == 13)
{
e.Handled = true;
btnChat.PerformClick();
}
}
/// <summary>
/// 只有选择一个用户时,私聊按钮才可用
/// </summary>
******* void lbOnlineUsers_SelectedIndexChanged(object sender, EventArgs e)
{
AdjustWhisperButton();
}
/// <summary>
/// 私聊
/// </summary>
******* 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>
******* void ShowInterChatMenuItem(bool show)
{
InterChatMenuItem.Enabled = show;
OutInterChatMenuItem.Enabled = this.btnChat.Enabled = !show;
}
******* void AppendText(string text)
{
txtChatDetails.Text += text;
SendMessage(txtChatDetails.Handle, WM_VSCROLL, SB_BOTTOM, new IntPtr(0));
}
/// <summary>
/// 退出应用程序时,释放使用资源
/// </summary>
******* void ExitChatSession()
{
try
{
proxy.Leave();
}
catch { }
finally
{
AbortProxyAndUpdateUI();
}
}
/// <summary>
/// 释放使用资源
/// </summary>
******* 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>
******* 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>
******* void ChatForm_FormClosed(object sender, FormClosedEventArgs e)
{
AbortProxyAndUpdateUI();
Application.Exit();
}
}
}
代码中我做了详细的讲解,相信园友们完全可以看懂。代码中的一些使用的方法还是值得大家参考学习的。这里涉及到了WCF的使用方法,需要注意的是:如果想利用工具生成代理类,需要加上下面的代码:
Code
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");
}
否则在生成代理类的时候会报错如下的错误: