做这个聊天室的主要目的是想了解下WCF下广播和局部广播的实现原理。原理或者说是过程是这样的。
首先A用户登录后调用客户代理中的Login方法(客户端的方法其实很多都是在服务器上执行的,这个要搞清楚),此时服务端坐这样一些事情:
服务端声明一个静态泛型通道列表public static List<IMyEvents> m_ClientList = new List<IMyEvents>();,A登录时加入A的回调通道public IMyEvents subscriber = OperationContext.Current.GetCallbackChannel<IMyEvents>();到m_ClientList中。
服务端声明一个静态字典用以保存登录客户的uuid和登录名,这个字典作用是给所有客户端调用当前在线列表时候使用。
再声明一个静态字典来保存客户端的uuid和回调通道,这个字典主要是给私聊的时候根据客户端传来的uuid调出被私聊的客户的通道,该通道已经在登录时被加入一个静态在线通道列表中。
B用户登录过程与A一样。
C用户也一样。
登录时Login方法会调用回调方法OnLogin返回在线列表给所有客户端并刷新客户端在线列表。
接着开始聊天。
聊天有2种情况,群聊和私聊。这边坐的很简单,私聊就是1对1。都是通过广播来完成。
群聊的时候,广播对象是所有在线用户。这个比较好说。
私聊的情况,发起者A想跟B私聊,那么A在A客户端选择聊天私聊对象为B,在发送通话时也同时将B的uuid列表发送给了服务端,服务端根据uuid得到B的回调通道,此时,服务端再开一个回调通道列表D,并在回调通道列表的总列表里查找这个列表是否已经存在,如果没有,添加之。而后D就成为广播的限定范围,此时正常广播就可以在A和B之间传送通话了。
大致原理就是如此。下面是主要代码,很多事件方面的代码没有用到,下一篇看看委托和事件在WCF中的使用,委托和事件和异步委托总是看的我有些头大。
服务端:
IService1.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WcfWithEvent
{
public enum EventType
{
Event1 = 1,
Event2 = 2,
Event3 = 3,
AllEvents = Event1 | Event2 | Event3
}
// 注意: 如果更改此处的接口名称“IService1”,也必须更新 App.config 中对“IService1”的引用。
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyEvents))]
public interface IService1
{
[OperationContract(IsOneWay = true)]
void BroadCast(string str,string touuid);
[OperationContract]
void Subscribe(EventType mask);
[OperationContract]
void Unsubscribe(EventType mask);
[OperationContract(IsOneWay = true)] //Login一定要配置IsOneWay = true,否则一直要等待结果
void Login(string uuid, string name);
[OperationContract(IsOneWay = true)]
void Quit(string uuid); //注销,其实是登录的反向操作,参数只要 uuid 就可以了
}
public interface IMyEvents //被服务设为回调后自动成为服务契约一部分,不需要加ServiceContract特性
{
[OperationContract(IsOneWay = true)]
void OnEvent1();
[OperationContract(IsOneWay = true)]
void OnEvent2(int number);
[OperationContract(IsOneWay = true)]
void OnEvent3(int number, string text);
[OperationContract(IsOneWay = true)]
void OnLogin(Dictionary<string, string> onlinenamelist); //此时 onlinenamelist 应该是一个数组类型,但是WCF不支持。字典可以?
[OperationContract(IsOneWay = true)]
void OnQuit(Dictionary<string, string> onlinenamelist);
}
// 使用下面示例中说明的数据协定将复合类型添加到服务操作
}
Service1.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
namespace WcfWithEvent
{
// 注意: 如果更改此处的类名“IService1”,也必须更新 App.config 中对“IService1”的引用。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service1 : IService1, IMyEvents
{
public Service1()
{
}
public static object lockobj = new object();
public static List<IMyEvents> m_ClientList = new List<IMyEvents>(); //存储客户端列表
public IMyEvents subscriber = OperationContext.Current.GetCallbackChannel<IMyEvents>(); //获得当前调用通道
public List<IMyEvents> m_ChatList = new List<IMyEvents>(); //一个聊天通道,服务端需要维护多个聊天通道
public static List<List<IMyEvents>> ListOfChatlist = new List<List<IMyEvents>>(); //IMyEvents的二维数组
//public class evtargs : EventArgs
//{
// public IMyEvents subscriber = OperationContext.Current.GetCallbackChannel<IMyEvents>(); //回调接口
//}
//public delegate void del(object sender, evtargs e);
//public event del evt;
public delegate void GenericEventHandler();
public delegate void GenericEventHandler<T>(T t);
public delegate void GenericEventHandler<T, U>(T t, U u);
public delegate void GenericEventHandler<T, U, V>(T t, U u, V v);
public delegate void GenericEventHandler<T, U, V, W>(T t, U u, V v, W w); //可以定义到7个
static GenericEventHandler m_Event1 = delegate { }; //为什么是空的呢?
//static GenericEventHandler m_Event1 = new Service1().Fun;
static GenericEventHandler<int> m_Event2 = delegate { };
static GenericEventHandler<int, string> m_Event3 = delegate { };
public void Subscribe(EventType mask)
{
IMyEvents subscriber = OperationContext.Current.GetCallbackChannel<IMyEvents>(); //回调接口
if ((mask & EventType.Event1) == EventType.Event1)
{
m_Event1 += subscriber.OnEvent1; //?m_Event1 这东西是事件?是的话为什么没有event关键字定义?
}
if ((mask & EventType.Event2) == EventType.Event2)
{
m_Event2 += subscriber.OnEvent2; //?m_Event1 这东西是事件?是的话为什么没有event关键字定义?
}
if ((mask & EventType.Event3) == EventType.Event3)
{
m_Event3 += subscriber.OnEvent3; //?m_Event1 这东西是事件?是的话为什么没有event关键字定义?
}
}
public void Unsubscribe(EventType mask)
{
IMyEvents Unsubscriber = OperationContext.Current.GetCallbackChannel<IMyEvents>(); //回调接口
if ((mask & EventType.Event1) == EventType.Event1)
{
m_Event1 -= Unsubscriber.OnEvent1; //?m_Event1 这东西是事件?是的话为什么没有event关键字定义?
}
}
public static void FireEvent(EventType eventType)
{
switch (eventType)
{
case EventType.Event1:
{
m_Event1();
return;
}
case EventType.Event2:
{
m_Event2(42);
return;
}
case EventType.Event3:
{
m_Event3(42, "hello");
return;
}
default:
{
throw new InvalidOperationException("unknow event type...");
}
}
}
//static string OnLineNameList;
static Dictionary<string, string> OnLineNameList = new Dictionary<string, string>();
static Dictionary<string, IMyEvents> chatlist = new Dictionary<string, IMyEvents>();
public void Login(string uuid, string name)
{
uuid = OperationContext.Current.SessionId;
IMyEvents subscriber = OperationContext.Current.GetCallbackChannel<IMyEvents>(); //回调接口
if (subscriber != null)
{
if (!m_ClientList.Contains(subscriber))
{
m_ClientList.Add(subscriber); //将当前客户通道加入静态在线客户通道列表中
chatlist.Add(uuid, subscriber); //聊天的一个用户字典,包含每个客户端的uuid和通道
OnLineNameList.Add(uuid, name); //用户字典,包含客户端 uuid 和 用户名
}
foreach (IMyEvents i in m_ClientList) //刷新所有客户端在线列表
{
i.OnLogin(OnLineNameList);
}
}
}
public void Quit(string uuid)
{
uuid = OperationContext.Current.SessionId;
IMyEvents subscriber = OperationContext.Current.GetCallbackChannel<IMyEvents>(); //回调接口
if (subscriber != null)
{
OnLineNameList.Remove(uuid); //字典中删除
foreach (IMyEvents i in m_ClientList)
{
i.OnQuit(OnLineNameList);
}
if (m_ClientList.Contains(subscriber)) //下次重新登录,sessionid 变了
{
m_ClientList.Remove(subscriber);
}
}
}
public void OnLogin(Dictionary<string, string> onlinenamelist)
{ }
public void OnQuit(Dictionary<string, string> onlinenamelist)
{ }
static string tempsay;
public void BroadCast(string str, string touuid) //通话内容和刷新需要刷新的客户端由它来做,第二个参数表示广播范围,这个参数应该是什么类型呢?
{
lock (lockobj)
{
tempsay = str;
//只要有私聊
//就产生一个私聊通道的列表 m_ChatList ,这个列表再登记到所有私聊列表中
if (touuid != null) //判断是不是一个私聊通话
{
IMyEvents toSay = null;
IMyEvents fromSay = null;
chatlist.TryGetValue(touuid, out toSay); //chatlist维护的是一个所有已经登录的用户的uuid和回调通道的字典
chatlist.TryGetValue(OperationContext.Current.SessionId, out fromSay); //获取说话人的回调通道
m_ChatList.Add(toSay); //临时通道列表里加入说话目标通道
m_ChatList.Add(fromSay); //临时通道列表里加入说话人通道
if (!ListOfChatlist.Contains(m_ChatList)) //如果说话人通道列表的总列表里没有当前这个列表,说明这个私聊以前没有发生
{
ListOfChatlist.Add(m_ChatList); //那么在静态通道列表的总列表里加入当前私聊通话的列表
foreach (IMyEvents i in m_ChatList)
{
i.OnEvent3(3, tempsay); //私聊范围内广播当前通话
}
}
}
else
{
//foreach (List<IMyEvents> i in ListOfChatlist) //这样做就是广播了,但这样效率低。m_ChatList存储了所有登录的客户
//{
// foreach (IMyEvents ii in i)
// {
// ii.OnEvent3(2, tempsay);
// }
//}
////m_ChatList = null;
foreach (IMyEvents i in m_ClientList)
{
i.OnEvent3(3, tempsay);
}
}
//MessageBox.Show(OperationContext.Current.SessionId); //每次调用 Sessionid 都不一样,导致重复执行
//根据touuid,得到对应的存储在服务端的chatlist里的对应通道对象,然后将这个通道对象添加到广播范围列表里
//if (touuid != null)
//{
// IMyEvents toSay = null;
// IMyEvents fromSay = null;
// chatlist.TryGetValue(touuid, out toSay); //chatlist维护的是一个所有已经登录的用户的uuid和回调通道的字典
// chatlist.TryGetValue(OperationContext.Current.SessionId, out fromSay);
// if (!m_ChatList.Contains(toSay)) //m_ChatList维护的是一个私聊队列里的参与私聊的客户端信息
// m_ChatList.Add(toSay);
// if (!m_ChatList.Contains(fromSay))
// m_ChatList.Add(fromSay);
// ListOfChatlist.Add(m_ChatList);
// foreach (IMyEvents i in m_ChatList)
// {
// i.OnEvent3(3, str);
// }
//}
//else
// foreach (IMyEvents i in m_ClientList) //广播范围,私聊啊什么的都需要限定这个范围
// {
// // i.OnEvent1(); //每个客户端都执行一次,就是广播
// i.OnEvent3(3, str);
// }
//subscriber.OnEvent1();
//if (!m_ClientList.Contains(subscriber))
//{
// m_ClientList.Add(subscriber);
//}
//MessageBox.Show(m_ClientList.Count.ToString());
//foreach (IMyEvents i in m_ClientList) //广播范围,私聊啊什么的都需要限定这个范围
//{
// // i.OnEvent1(); //每个客户端都执行一次,就是广播
// i.OnEvent3(3, str);
//}
//Type type = typeof(IMyEvents);
//MethodInfo methodInfo = type.GetMethod("DoSomething");
//foreach (IMyEvents subscriberM in m_ClientList)
//{
// try
// {
// methodInfo.Invoke(subscriberM, null);
// }
// catch
// { }
//}
}
}
public void OnEvent1()
{
//List < IMyEvents > m_ClientList = new List<IMyEvents>();
//Type type = typeof(IMyEvents);
//MethodInfo methodInfo = type.GetMethod("OnEvent1");
//foreach (IMyEvents subscriberM in m_ClientList)
//{
// try
// {
// methodInfo.Invoke(subscriberM, null);
// }
// catch
// { }
//}
MessageBox.Show("aaa");
}
public void OnEvent2(int number)
{ }
public void OnEvent3(int number, string text)
{ }
static void Main()
{ }
}
}
客户端
Form1.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 EventClient.ServiceReference1;
using System.ServiceModel;
namespace EventClient
{
public partial class Form1 : Form, IService1Callback
{
InstanceContext context; //这边很容易出错,如果定义放在Button事件里,会导致客户端反复new出新的代理类,导致客户端sessionid总是变化
Service1Client sc;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//IService1Callback subscriber = new Form1();
// sc.Subscribe(EventType.Event1); // 订阅事件,如何触发呢?
string msg = username.Text + ":" + textBox1.Text;
string touuid = null;
if (listView1.SelectedItems.Count!=0)
{
touuid = listView1.Items[listView1.SelectedIndices[0]].Name;
}
sc.BroadCast(msg, touuid);
}
public void OnEvent1()
{
//MessageBox.Show("客户端");
//richTextBox1.Text = textBox1.Text; //还实现不了
}
public void OnEvent2(int i)
{ }
public void OnEvent3(int i, string s)
{
richTextBox1.Text += s + "\n";
}
private void button2_Click(object sender, EventArgs e) //登录
{
context = new InstanceContext(this); //参数要求是一个回调实例
sc = new Service1Client(context);
sc.Login(sc.InnerChannel.SessionId, username.Text); // sc.InnerDuplexChannel.SessionId 两者有什么区别吗?
//MessageBox.Show(sc.InnerChannel.SessionId);
this.Text = username.Text;
button1.Visible = true;
}
public void OnLogin(Dictionary<string, string> onlinenamelist)
{
//uuid = sc.InnerChannel.SessionId;
// listView1.Items.Add(onlinenamelist);
//MessageBox.Show(onlinenamelist.Count.ToString());
//MessageBox.Show(onlinenamelist.ToString());
listView1.Clear();
foreach (KeyValuePair<string, string> user in onlinenamelist)
{
listView1.Items.Add(user.Key, user.Value, null);
}
}
public void OnQuit(Dictionary<string, string> onlinenamelist)
{
listView1.Clear();
foreach (KeyValuePair<string, string> user in onlinenamelist)
{
listView1.Items.Add(user.Key, user.Value, null);
}
}
private void button3_Click(object sender, EventArgs e)
{
sc.Quit(sc.InnerChannel.SessionId);
this.Text = "未登录...";
this.button1.Visible = false;
}
private void listView1_Click(object sender, EventArgs e) //选择需要私聊的人的时候让需要私聊的这个人自动去服务器登记
{
//label1.Text = listView1.Items[listView1.SelectedIndices[0]].Name; //name就是key
label1.Text = listView1.Items[listView1.SelectedIndices[0]].Text; //name就是value
}
}
//public class MySubsriber : ServiceReference1.IService1Callback
//{
// public void OnEvent1()
// {
// }
// public void OnEvent2(int i)
// { }
// public void OnEvent3(int i, string s)
// { }
//}
}