zoukankan      html  css  js  c++  java
  • 传说中的WCF(13):群聊天程序

    传说中的WCF(1):这东西难学吗?
    传说中的WCF(2):服务协定的那些事儿
    传说中的WCF(3):多个协定
    传说中的WCF(4):发送和接收SOAP头
    传说中的WCF(5):数据协定(a)
    传说中的WCF(6):数据协定(b)
    传说中的WCF(7):“单向”&“双向”
    传说中的WCF(8):玩转消息协定
    传说中的WCF(9):流与文件传输
    传说中的WCF(10):消息拦截与篡改
    传说中的WCF(11):会话(Session)
    传说中的WCF(12):服务器回调有啥用
    传说中的WCF(13):群聊天程序
    传说中的WCF(14):WCF也可以做聊天程序 

    前面吹了不少重点知识了,为了可以较为综合地运用它们,今天,我们来做一个可以群聊的应用,就像QQ群那样,一个服务器端,N个客户端,服务器端运行后,每个客户端启动的时候会自动连接服务器生成会话,只要其中任一个客户端向服务器发送消息,服务器都会将消息群发到所有客户端。

    我们来看看如何用WCF来取代Socket。

    这个例子会用到以下知识点:

    1. 在进程中承载WCF服务。
    2. 会话的使用。
    3. 回调。

    在下面说明过程中,我不会粘贴所有代码,毕竟有点长,我只放出重要部分,随后我会将源码上传到【资源】。

    一、服务协定和回调协定。

        [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猛不?

    转IT黄老邪

  • 相关阅读:
    mongoDB的常用语法
    Linux系统清除缓存
    110:类视图讲解
    109:大型CSV文件的处理方式
    108:生成和下载csv文件
    107:JsonResponse用法详解
    106:HttpResponse对象讲解
    104~105:HttpRequest对象讲解和QueryDict的用法讲解
    103:重定向详解
    102:限制请求method装饰器
  • 原文地址:https://www.cnblogs.com/jcomet/p/3064374.html
Copyright © 2011-2022 走看看