这次是一个在线聊天的插件,插件参考了MSDN中Duplex WCF服务的实现和网上一些聊天程序,基本可以实现用户登录和聊天,如果用户不存在就保存聊天数据到数据库,等用户下次登陆的时候读入。

这个是聊天时候的图例:

为什么不使用在客户单添加WCF服务的方法生成代理类然后进行编程序,首先是生成的代理类我没有发现可以实现WCF双向传输的方法,在网上也没有找到,所以就参照msdn和一些网上的代码,自己编写代理类进行操作:

1: 定义聊天时候传输聊天的数据实体,参照服务器端的聊天数据实体的定义,在客户端定义如下的实体:

 [DebuggerStepThroughAttribute()]
    [GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
    [DataContractAttribute(Name = "ChatMessage", Namespace = "http://schemas.datacontract.org/2004/07/")]
    public partial class ChatMessage : object
    {
        private string DataField;
        private string FromField;
        private string ToField;
        private int TypeField;
        private byte[] ContentField;

        [DataMemberAttribute()]
        public string Data
        {
            get
            {
                return this.DataField;
            }
            set
            {
                if ((object.ReferenceEquals(this.DataField, value) != true))
                {
                    this.DataField = value;
                }
            }
        }

        [DataMemberAttribute()]
        public string From
        {
            get
            {
                return this.FromField;
            }
            set
            {
                if ((object.ReferenceEquals(this.FromField, value) != true))
                {
                    this.FromField = value;
                }
            }
        }

        [DataMemberAttribute()]
        public string To
        {
            get
            {
                return this.ToField;
            }
            set
            {
                if ((object.ReferenceEquals(this.ToField, value) != true))
                {
                    this.ToField = value;
                }
            }
        }
        [DataMemberAttribute()]
        public int Type
        {
            get
            {
                return this.TypeField;
            }
            set
            {
                if ((object.ReferenceEquals(this.TypeField, value) != true))
                {
                    this.TypeField = value;
                }
            }
        }
        [DataMemberAttribute()]
        public byte[] Content
        {
            get
            {
                return this.ContentField;
            }
            set
            {
                if ((object.ReferenceEquals(this.ContentField, value) != true))
                {
                    this.ContentField = value;
                }
            }

        }    

这个实体类的定义其实是可以自动生成的。

2:把对WCF服务的访问方法单独处理成一个对象,模式和结构基本和MSDN上那篇文章一样:

  public void Start()
        {
            // Instantiate the binding and set the time-outs
            PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding()
            {
                ReceiveTimeout = TimeSpan.FromSeconds(45),
                InactivityTimeout = TimeSpan.FromMinutes(1)
            };

            // Instantiate and open channel factory from binding
            IChannelFactory<IDuplexSessionChannel> factory =
                binding.BuildChannelFactory<IDuplexSessionChannel>(new BindingParameterCollection());

            IAsyncResult factoryOpenResult =
                factory.BeginOpen(new AsyncCallback(OnOpenCompleteFactory), factory);
            if (factoryOpenResult.CompletedSynchronously)
            {
                CompleteOpenFactory(factoryOpenResult);
            }
        }

        void OnOpenCompleteFactory(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteOpenFactory(result);
        }

        void CompleteOpenFactory(IAsyncResult result)
        {
            IChannelFactory<IDuplexSessionChannel> factory =
                (IChannelFactory<IDuplexSessionChannel>)result.AsyncState;

            factory.EndOpen(result);

            // The factory is now open. Create and open a channel from the channel factory.
            IDuplexSessionChannel channel =
                factory.CreateChannel(new EndpointAddress(ServiceUrl));

            IAsyncResult channelOpenResult =
                channel.BeginOpen(new AsyncCallback(OnOpenCompleteChannel), channel);
            if (channelOpenResult.CompletedSynchronously)
            {
                CompleteOpenChannel(channelOpenResult);
            }
        }

        void OnOpenCompleteChannel(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteOpenChannel(result);

        }

上边这段代码基本是固定模式,表示使用channel模式进行访问WCF服务,打开完成服务之后,开始进行第一次通信,就是发送信条信息,然后进入消息等待模式:

  void CompleteOpenChannel(IAsyncResult result)
        {
            channel = (IDuplexSessionChannel)result.AsyncState;

            channel.EndOpen(result);
            ChatMessage chatMessage = new ChatMessage();
            chatMessage.From = UserName;
            chatMessage.To = "SystemServer";
            chatMessage.Data = System.DateTime.Now.ToString();
            // Channel is now open. Send message
            Message message =
                Message.CreateMessage(channel.GetProperty<MessageVersion>(),
                 "WindCloud/WSChatService/Heart", chatMessage);
            IAsyncResult resultChannel =
                channel.BeginSend(message, new AsyncCallback(OnSend), channel);
            if (resultChannel.CompletedSynchronously)
            {
                CompleteOnSend(resultChannel);
            }

            //Start listening for callbacks from the service
            ReceiveLoop(channel);

        }

至此,客户端就会一直等待服务器发来消息,一旦接收到消息,就进入下边的消息处理过程:

 void ReceiveLoop(IDuplexSessionChannel channel)
        {
            // Start listening for callbacks.
            IAsyncResult result = channel.BeginReceive(new AsyncCallback(OnReceiveComplete), channel);
            if (result.CompletedSynchronously) CompleteReceive(result);
        }

        void OnReceiveComplete(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteReceive(result);
        }

        void CompleteReceive(IAsyncResult result)
        {
            //A callback was received so process data
            IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;

            try
            {
                Message receivedMessage = channel.EndReceive(result);

                // Show the service response in the UI.
                if (receivedMessage != null)
                {
                    ChatMessage text = receivedMessage.GetBody<ChatMessage>();
                    _UiThread.Post(Client.ProcessData, text);
                }

                ReceiveLoop(channel);
            }
            catch (CommunicationObjectFaultedException exp)
            {
                _UiThread.Post(delegate(object msg) { System.Windows.Browser.HtmlPage.Window.Alert(msg.ToString()); }, exp.Message);
            }

        }

上边的代码由于都是使用的异步模式,看起来还是比较多的,但是道理还不是很复杂。这些都是模式相对固定的代码,下边就是我们针对聊天需要的代码了:

告诉系统,加入到聊天室:

  public void Join(string userName)
        {
            ChatMessage chatMessage = new ChatMessage();
            chatMessage.From = userName;
            chatMessage.To = "";
            chatMessage.Data = "";
            //
            Message message = Message.CreateMessage(channel.GetProperty<MessageVersion>(), "WindCloud/WSChatService/Join", chatMessage);
            IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
            if (resultChannel.CompletedSynchronously)
            {
                CompleteOnSend(resultChannel);
            }

        }

最主要的一个方法,说话:

 public void Say(ChatMessage chatMessage)
        {
            // The channel is now open. Send a message.
            Message message = Message.CreateMessage(channel.GetProperty<MessageVersion>(), "WindCloud/WSChatService/Say", chatMessage);

            try
            {
                IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
                if (resultChannel.CompletedSynchronously)
                {
                    CompleteOnSend(resultChannel);
                }
            }
            catch (Exception ex)
            {
                //channel = factory.CreateChannel(new EndpointAddress("http://localhost/ChatServer/Service.svc"));

                IAsyncResult channelOpenResult = channel.BeginOpen(new AsyncCallback(OnOpenCompleteChannel), channel);
                if (channelOpenResult.CompletedSynchronously)
                {
                    CompleteOpenChannel(channelOpenResult);
                }
                IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
                if (resultChannel.CompletedSynchronously)
                {
                    CompleteOnSend(resultChannel);
                }
            }
        }

客户单服务重要的代码差不多就是这些,需要明白如何发送消息,如果进入消息接受模式。下面看看界面如何处理:

  private void StartServer()
        {


            Uri uri = System.Windows.Browser.HtmlPage.Document.DocumentUri;
            string host = uri.AbsoluteUri;
            host = host.Substring(0, host.Length - uri.LocalPath.Length);
            string servicePath = "/Services/WSChat.svc";
            string serviceUri = host + servicePath;

            chatSerrice = new ChatServer(this, serviceUri, App.GetLoginUser().UserName);
            chatSerrice.Start();

            chatSerrice.Join(App.GetLoginUser().UserName);
            //
            _Timer = new Timer(new TimerCallback(_Timer_Elapsed), null, 30000, Timeout.Infinite);

        }

建立新的服务,然后定时发送心跳信息:

  private void CreateChat(string friendName)
        {
            ///
            if (!chatUsers.ContainsKey(friendName))
            {
                UserChat userChat = new UserChat();
                userChat.txtTitle.Text = "与 " + friendName + " 交谈中";
                userChat.OnSend += (o, ev) =>
                {
                    ev.From = App.GetLoginUser().UserName;
                    chatSerrice.Say(ev);
                };
                userChat.UserName = friendName;
                userChat.OnCloseed += (o, ev) =>
                {
                    chatUsers.Remove(ev);
                    for (int i = 0; i < canvasChat.Children.Count; i++)
                    {
                        if ((canvasChat.Children[i] as UserChat).UserName == ev)
                        {
                            canvasChat.Children.RemoveAt(i);
                        }
                    }
                };
                chatUsers.Add(friendName, userChat);
                this.canvasChat.Children.Add(userChat);
            }

        }

针对每个不同的聊天对象,建立独立的窗口。

 public void ProcessData(Object receivedData)
        {
            if (receivedData != null)
            {
                ChatMessage cm = receivedData as ChatMessage;

                if (cm.Type > 0)
                {
                    //聊天窗口是否包含
                    if (!chatUsers.ContainsKey(cm.From))
                    {
                        CreateChat(cm.From);
                    }
                    if (chatUsers.ContainsKey(cm.From))
                    {
                        string txt = "";
                        txt = cm.From + " 说:" + cm.Data + ""n"; ;
                        chatUsers[cm.From].txtChat.Text += txt;
                    }
                }
            }
        }

接受到不同的信息之后,把对应的消息显示到不同的窗口上,免得混杂在一起:

 

 至此,聊天的主要功能完成。