WCF也可以做聊天程序

先看一个截图。

 

上面的图,各位乍一看,可能会觉得是用Socket编写的聊天程序。告诉你吧,这玩意儿不是用Socket实现,呵呵,当然它的底层肯定与Socket有一定关系,我只说我的代码没有用到socket而已。

那么,除了Socket可以用于通信,还有其他技术吗?有啊,首先,如果你足够强大,用HTTP也行,但HTTP初始化的过程貌似比较慢。那么还有吗?当然了,各位还记得.NET以前有一个很X但又很少被关注的技术——Remoting。用过吧?没用过也没关系,因为它已经有替代品了。

这时候大家肯定想到WCF不是一盏“省油”的灯,其实不然,对比于用Socket要编写的代码数量和维护成本,用WCF编写网络通信程序,不仅省油,而且省时省力,最重要的是省心。所以,人的健康,心理健康是占主导的,你看一个心理不健康的人,其身体也不会健康到哪里去,今天这病明天那病。

因而,编程这事啊,越省心越好,有利于我们的健康,赚钱永远不是目的,身心健康才是活在这个世界上的主旋律,至于你信不信,反正我深信不疑。

 

我就这个WCF版的聊天程序的大致思路说一说。

这个程序既可以充当服务器端也同时作为客户端,每个应用实例都扮演着双重角色。这里我不需要引用服务。首先看服务协定和服务类。

using System;
using System.ServiceModel;

namespace ServiceDef
{
    [ServiceContract]
    public interface IService
    {
        [OperationContract(IsOneWay = true)]
        void SendMessage(string msg);
    }

    /// <summary>
    /// 服务
    /// </summary>
    public class MyChatService : IService
    {
        /// <summary>
        /// 收到消息后引发的事件
        /// </summary>
        public event EventHandler<MessageReceiveEventArgs> MessageGot;
        public MyChatService()
        {
            MessageGot += new EventHandler<MessageReceiveEventArgs>(TestApp.Form1.GetMessageCallBack);
        }
        public void SendMessage(string msg)
        {
            if (MessageGot != null)
            {
                MessageGot(this, new MessageReceiveEventArgs(msg));
            }
        }
    }

    /// <summary>
    /// 收到消息后引发事件的参数
    /// </summary>
    public class MessageReceiveEventArgs : EventArgs
    {
        private string m_Message = string.Empty;
        public MessageReceiveEventArgs(string message)
        {
            this.m_Message = message;
        }

        public string MessageText
        {
            get { return this.m_Message; }
        }
    }

}


服务协定没什么好看的了,相信大家都会写,这里的服务类与以往的有些不同,大家看到,里面定义了一个事件。那么,为什么要这样做呢?为什么要在服务方法被调用时引发这个事件呢?

想一想,我们以上代码是与UI分离的,也就是说,与UI分离是一种很好的编程方法,这样在修改维护时不会搞得乱七八糟。但是,我们都知道世间万物皆为阴阳所生,所以才有太极生两仪,两仪生四象,四象成八卦。而阴与阳是统一的,阴中有阳,阳在有阴。

我们的应用程序的UI就是阳,而业务逻辑就是阴,所以编程就是这么简单——阴阳互动。为了完成阴中有阳的功能,我们要想办法让这些代码与窗口上的控件互动,当然方法很多,也相当灵活。使用事件是比较好的。

于是,在服务类中定义一个事件,而事件的处理方法在主窗口类中定义,这样一来,阳与阴之间就有了一个可以相通的渠道。

为了使用访问方便,在窗口类中定义的处理事件的方法使用静态方法,静态方法的好处在于,它不基于对象,而是基于类的,你去到哪里都可以访问,它是全球化的。

这时候有人会问了,静态方法不能访问类对象的成员,那么这个静态方法又如何与窗体上的控件互动呢?技巧都是拿来用的。有了静态方法,难道我不能在窗口类中定义一个保存当前类实例的静态变量吗?

比如,我这个窗口的类名为FormMain,我只要在FormMain里面定义一个static FormMain CurrentForm = null;就完事了,这样不就可以在静态方法中访问了吗?

只要在FormMain的构造函数中赋值就行了,CurrentForm = this;

 

比如本例的代码:

        #region 静态成员
        static Form1 CurrentInstance = null;
        public static void GetMessageCallBack(object sender, ServiceDef.MessageReceiveEventArgs e)
        {
            if (CurrentInstance != null)
            {
                CurrentInstance.AddMessageToListBox(e.MessageText);
            }
        }
        #endregion

        public Form1()
        {

            InitializeComponent();
            CurrentInstance = this;
…………


你看,这就成了。

然后当然是定义服务器了,这里我们只有一个终结点,就是上面的IService,所以不用基址了,因为我们也不需要引用服务,直接利用ChannelFactory就行了。

        #region 与服操作有关
        ServiceHost host = null;

        /// <summary>
        /// 启动服务
        /// </summary>
        /// <param name="port">监听端口</param>
        void OpenService(int port)
        {
            host = new ServiceHost(typeof(ServiceDef.MyChatService));
            NetTcpBinding binding = new NetTcpBinding();
            binding.Security.Mode = SecurityMode.None;
            host.AddServiceEndpoint(typeof(ServiceDef.IService), binding, "net.tcp://" + Dns.GetHostName() + ":"+ port.ToString() + "/chatsvc/");
            host.Opened += host_Opened;
            host.Closed += host_Closed;
            try
            {
                host.Open();
            }
            catch (Exception ex)
            {
                ShowMessage(ex.Message);
            }
        }

        void host_Closed(object sender, EventArgs e)
        {
            ShowMessage("服务已关闭。");
        }

        void host_Opened(object sender, EventArgs e)
        {
            ShowMessage("服务已启动。");
        }

        /// <summary>
        /// 关闭服务
        /// </summary>
        void CloseService()
        {
            if (host != null)
            {
                host.Close();
                host.Opened -= host_Opened;
                host.Closed -= host_Closed;
            }
        }

        #endregion

 

好了,接下来就是发送消息,其实就是调用服务方法IService.SendMessage,这里我们只用ChannelFactory<TChannel>工厂来生产一个IService通道,而后直接调用就可以了,就不必引用服务,也不用生成什么WSDL文件,也不考虑SOAP版本了。

            // 发送消息,即调用服务
            NetTcpBinding binding =new NetTcpBinding();
            binding.Security.Mode = SecurityMode.None;
            
            try
            {
                ServiceDef.IService ep = ChannelFactory<ServiceDef.IService>.CreateChannel(binding,new EndpointAddress("net.tcp://" + txtSvrHostName.Text + ":" + rmPort.ToString() + "/chatsvc/"));
                ep.SendMessage(txtSendMessage.Text);
                txtSendMessage.Clear();
            }
            catch (Exception ex)
            {
                ShowMessage(ex.Message);
            }


哈哈,是不是很简单,而且,你也可以想到,如果用WCF来做文件传输,比如PC与手机上的文件传送,是不是很方便呢?也不必担心TCP粘包问题。

源代码我随后上传。

 

posted @ 2013-04-05 12:27  坚固66  阅读(198)  评论(0编辑  收藏  举报