子曾经曰过

  博客园  :: 首页  ::  ::  ::  :: 管理

做这个聊天室的主要目的是想了解下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

View Code
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

View Code
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

View 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 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)
// { }
//}
}
posted on 2011-02-25 15:07  人的本质是什么?  阅读(2390)  评论(0编辑  收藏  举报