传说中的WCF(13):群聊天程序
来源:互联网 发布:银联钱包 网络不给力 编辑:程序博客网 时间:2024/05/23 01:11
前面吹了不少重点知识了,为了可以较为综合地运用它们,今天,我们来做一个可以群聊的应用,就像QQ群那样,一个服务器端,N个客户端,服务器端运行后,每个客户端启动的时候会自动连接服务器生成会话,只要其中任一个客户端向服务器发送消息,服务器都会将消息群发到所有客户端。
我们来看看如何用WCF来取代Socket。
这个例子会用到以下知识点:
- 在进程中承载WCF服务。
- 会话的使用。
- 回调。
在下面说明过程中,我不会粘贴所有代码,毕竟有点长,我只放出重要部分,随后我会将源码上传到【资源】。
一、服务协定和回调协定。
[ServiceContract(CallbackContract = typeof(ICallBack), SessionMode = SessionMode.Required)] public interface IService { [OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)] void Begin(); [OperationContract(IsOneWay = true)] void SendMessage(string nick, string msg, DateTime sendTime); [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)] void End(); } public interface ICallBack { [OperationContract(IsOneWay = true)] void SendToClients(string nick, string msg, DateTime sendTime); }
Begin方法和End方法分别是启动会话和终止会话,这样,每接入一个客户端连接就会实例化一个服务类(前面文章中提过了),这样就可以确保每个客户端都与服务器维持一个会话。
向服务器发送的消息包括用户的昵称、消息内容和发送时间。而回调协定中的方法也是这几个参数,但不同的是,服务协定是客户端调用服务器端,而回调中是服务器端调用客户端。
二、实现服务类。
[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.PerSession)] public class MyService : IService { public static Dictionary<string, ICallBack> ClientCallbacks = new Dictionary<string, ICallBack>(); public void Begin() { string sessionID = OperationContext.Current.SessionId; ICallBack cb = OperationContext.Current.GetCallbackChannel<ICallBack>(); MyService.ClientCallbacks[sessionID] = cb; } public void SendMessage(string nick, string msg, DateTime sendTime) { foreach (ICallBack c in MyService.ClientCallbacks.Values.ToArray()) { if (c != null) { c.SendToClients(nick, msg, sendTime); } } } public void End() { string sessionID = OperationContext.Current.SessionId; if (MyService.ClientCallbacks.ContainsKey(sessionID)) { MyService.ClientCallbacks.Remove(sessionID); } } }
我本来在代码中写了注释的,不过刚刚删了,我希望大家能在没有注释的前提下看懂代码。
在类中,声明了一个静态的Dictionary<string, ICallBack>,这是一个字典,我想大家猜到了,静态变量是基于类的,与实例无关,我们可以把它当作全局数据,在字典集合中保存所有接入客户端的回调,由于每个会话的ID是唯一的,因此,用SessionID作为Key是比较好操作的。
a、在Begin方法调用时,我们把传入的客户端的会话ID和回调存进字典中,在End方法调用时,说明会话要终结,这时候把对应的会话ID和回调从字典中删除。
b、在SendMessage方法调用时,从字典中取出所有回调,并把接收的参数传给回调方法,在回调调用后,这些消息就会转发到所有连接的客户端。
这样就能实现群聊了。
三、建立服务器。
static void Main(string[] args) { Console.Title = "WCF服务器端"; Uri baseAddr = new Uri("http://127.0.0.1:2713/wcfsv"); using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddr)) { NetTcpBinding binding = new NetTcpBinding(); // 不需要任何安全验证 binding.Security.Mode = SecurityMode.None; // 添加终结点 host.AddServiceEndpoint(typeof(IService), binding, "net.tcp://127.0.0.1:1736/channel"); // 元数据 ServiceMetadataBehavior mb = new ServiceMetadataBehavior(); mb.HttpGetEnabled = true; mb.HttpGetUrl = new Uri("http://127.0.0.1:87/WSDL"); host.Description.Behaviors.Add(mb); host.Opened += (source, arg) => { Console.WriteLine("服务已经启动。"); }; // 打开服务 try { host.Open(); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } }
因为TCP支持会话,而且传输速度快,所以终结点使用net.tcp绑定。
四、在客户端实现回调协定。
首先在客户端引用服务,然后实现回调协定接口。
public class MyCallBack : WS.IServiceCallback { public void SendToClients(string nick, string msg, DateTime sendTime) { if (this.MessageReceived != null) { CallbackRecEventArgs arg = new CallbackRecEventArgs(nick, msg, sendTime); this.MessageReceived(this, arg); } } // 事件 public event EventHandler<CallbackRecEventArgs> MessageReceived; }
还有一个事件参数类。
public class CallbackRecEventArgs : EventArgs { string _Nick, _Msg; DateTime _time; public CallbackRecEventArgs(string nk, string m, DateTime t) { _Nick = nk; _Msg = m; _time = t; } public string Nick { get { return _Nick; } } public string MessageCont { get { return _Msg; } } public DateTime SendTime { get { return _time; } } }
因为回调中的方法是服务器凋用的,记住了,它不是客户端调用的。在客户要想及时侦听到该方法被调用,可以使用事件,当回调方法被调用,就会触发事件,而事件的另一个发处是,它可以在其他类中定义处理方法。
比如这样。
cb = new MyCallBack(); cb.MessageReceived += cb_MessageReceived;
void cb_MessageReceived(object sender, CallbackRecEventArgs e) { MessageEnt msg = new MessageEnt(); msg.NickName = e.Nick; msg.Message = e.MessageCont; msg.Time = e.SendTime; if (e.Nick == MyNickName) { msg.IsMe = true; } else { msg.IsMe = false; } this.dataSource.Add(msg); }
其他的可以参考我们随后上传的源码。
我们看看运行的结果是怎么样的。
哈哈哈,怎么样?
这个比Socket猛不?
- 传说中的WCF(13):群聊天程序
- 传说中的WCF(7):“单向”&“双向”
- 传说中的WCF(12):服务器回调有啥用
- 传说中的WCF
- WCF实现聊天程序
- 传说中的WCF(5):数据协定(a)
- 传说中的WCF(6):数据协定(b)
- 传说中的WCF(11):会话(Session)
- 传说中的WCF(5):数据协定(a)
- 传说中的WCF(6):数据协定(b)
- 传说中的WCF(11):会话(Session)
- 传说中的WCF(1):这东西难学吗?
- 传说中的WCF(2):服务协定的那些事儿
- 传说中的WCF(3):多个协定
- 传说中的WCF(4):发送和接收SOAP头
- 传说中的WCF(8):玩转消息协定
- 传说中的WCF(9):流与文件传输
- 传说中的WCF(10):消息拦截与篡改
- 在java中equals和“==”的比较
- 特性 property 类别 category 协议 protocol
- Linux tcpdump命令详解
- c++写的通讯录 (2.0)
- 2012年度IT博客大赛10强花落谁家暨圆满落幕
- 传说中的WCF(13):群聊天程序
- javascript JSON详解
- net 简单图表控件 (介绍测试示例使用部分) [c/s桌面应用程序控件] II
- Android Tombstone/Crash的log分析和定位
- 用Delphi发邮件
- LINQ查询表达式--学习linq的资料和笔记(三)
- C库函数—strcpy实现
- oracle 字符集修改及子集超级关系
- 贪心算法的几个应用