65.9K
CodeProject 正在变化。 阅读更多。
Home

一个简单的点对点聊天应用程序,使用 WCF netPeerTcpBinding

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.62/5 (27投票s)

2007年1月25日

4分钟阅读

viewsIcon

269102

downloadIcon

11363

一个演示 WCF 中 netPeerTcpBinding 用法的示例

Sample Image - Chat_application_using_WC.png

引言

众所周知,微软已经推出了.NET 3.0,它拥有四个非常强大的基础。

  • WCF (Windows Communication Foundation)
  • WPF (Windows Presentation Foundation)
  • WF (Windows Workflow Foundation)
  • Windows Cardspace

关于WCF、WPF、WF和Cardspace已经有很多资料了。我将不深入探讨这些技术。在探索WCF的过程中,我遇到了一些新引入的有趣的东西。在我之前的聊天应用程序中使用Remoting时,我使用了在客户端和服务器之间分发的接口。有一个在客户端实现的抽象类。当服务器想发送消息给客户端时,服务器应该有客户端列表以及所有客户端的公共部分。接口实现是实现这一点的最佳方式。在.NET Remoting中有序列化、通道、协议等等。

在WCF中,事情变得非常简单。最重要也是最重要的一点是,您可以根据应用程序的需求拥有任意数量的终结点。如果您希望您的应用程序被.NET客户端、Java客户端使用,那么您可以选择TCP绑定和基本HTTP绑定。您需要做的是在服务器的配置文件中添加这些终结点并启动服务器。WCF负责为您提供与不同客户端通信的性能优势。WCF的基本通信协议是SOAP。但是,当您在WCF服务和WCF客户端之间建立通信时,基础结构会使用SOAP上的二进制来为您提供最佳性能。我认为您可以拥有一个WCF服务,所有东西都在一个屋檐下。目前我还在探索WCF,特别是绑定的更多内容。有一些功能是必需的,但我不知道如何从netPeerTcpBindingPeerResolvers.中获得它们。

关于应用程序

我编写的简单的WCF聊天应用程序使用了netTcpPeerBinding。使用此绑定,可以非常轻松地创建一个简单的内网聊天应用程序,该应用程序可以在内网上使用。您不必编写太多代码,甚至不需要编写
服务器端的任何特殊接口、类。一切都被封装在

  • System.ServiceModel;
  • System.ServiceModel.Channels;
  • System.ServiceModel.PeerResolvers;

聊天服务器

一个非常简单的聊天服务器,只有四行代码。如前所述,您不需要编写任何特殊代码。因此,服务器应用程序看起来是这样的。

 <CODE>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.PeerResolvers;

namespace ChatServer
{
    public partial class ChatServer : Form
    {
        private CustomPeerResolverService cprs;
        private ServiceHost host;

        public ChatServer()
        {
            InitializeComponent();            
            btnStop.Enabled = false;
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                cprs = new CustomPeerResolverService();
                cprs.RefreshInterval = TimeSpan.FromSeconds(5);
                host = new ServiceHost(cprs);
                cprs.ControlShape = true;
                cprs.Open();
                host.Open(TimeSpan.FromDays(1000000));
                lblMessage.Text = "Server started successfully.";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                btnStart.Enabled = false;
                btnStop.Enabled = true;
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            try
            {
                cprs.Close();
                host.Close();
                lblMessage.Text = "Server stopped successfully.";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                btnStart.Enabled = true;
                btnStop.Enabled = false;
            }
        }
    }
} 

代码是自解释的。您创建一个CustomPeerResolverService对象,并将该对象作为输入参数传递给ServiceHost类。打开peer resolver服务和host,您就完成了。没有新的类,也没有终结点。令人惊讶,对吧?但请稍等,我必须向您展示config文件。config文件在指定所需细节方面起着最重要的作用。

 <CODE>
?xml version="1.0" encoding="utf-8" ?
configuration
  system.serviceModel
    services
      service name="System.ServiceModel.PeerResolvers.
                        CustomPeerResolverService"
        host
          baseAddresses
            add baseAddress="net.tcp://10.34.34.241/ChatServer"/
          baseAddresses
        host
        endpoint address="net.tcp://10.34.34.241/ChatServer" 
            binding="netTcpBinding"
                  bindingConfiguration="TcpConfig"
                  contract="System.ServiceModel.PeerResolvers.
            IPeerResolverContract"          
        endpoint          
      service
    services

    bindings
      netTcpBinding
        binding name="TcpConfig"
          security mode="None"/security
        binding
      netTcpBinding
    bindings
  system.serviceModel
configuration 

我已经删除了'<'和'>',因为我无法直接显示config文件。如果您知道如何显示,请告诉我。Config文件非常简单。在这种情况下,我们使用的是.NET预定义的.*服务System.ServiceModel.PeerResolvers.CustomPeerResolverService。按所示方式为宿主解析器服务提供基地址。关于终结点,重要的是它使用了.*基础结构中.*已.*可用的System.ServiceModel.PeerResolvers.IPeerResolverContract.*合同。如前所述,我们).*使用TCP.*终结点.*进行.*通信。因此,我们.*指定.*终结点.*与TCP.*绑定,.*并.*使用.*安全.*模式.*设置为.*none。.*就这样,.*您.*就.*完成了。.*只需.*启动.*服务器,.*您.*就.*准备.*好.*进行.*聊天.*客户端.*了。

聊天客户端

与聊天服务器.*相比,.*客户端.*变得.*稍微.*复杂.*一些。.*它.*独自.*完成.*所有.*事情。.*如.*前.*所述,.*我们需要.*一些.*服务器.*用于.*与.*客户端.*通信.*的.*公共.*部分,.*这.*可以.*通过.*接口.*来实现。
.*相同的.*概念.*在这里.*也.*适用,.*并且.*我们的.*客户端.*代码.*如下所示。

 <CODE>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace ChatClient
{
    // This is the service contract at client side which uses 
    // the same contract as call back contract.
    // Using CallbackContract, server sends message to clients
    [ServiceContract(CallbackContract = typeof(IChatService))]
    public interface IChatService
    {
        // All operation contracts are one way so that client 
        // can fire the message and forget
        // When server responds, client catches it acts accordingly
        [OperationContract(IsOneWay = true)]
        void Join(string memberName);
        [OperationContract(IsOneWay = true)]
        void Leave(string memberName);
        [OperationContract(IsOneWay = true)]
        void SendMessage(string memberName, string message);
    }

    // An interface to create a channel for communication
    public interface IChatChannel : IChatService, IClientChannel
    {
    }

    public partial class ChatClient : Form, IChatService
    {
        // Different delegates that are used internally to raise 
        // events when client joins,
        // leaves or sends a message
        private delegate void UserJoined(string name);
        private delegate void UserSendMessage(string name, string message);
        private delegate void UserLeft(string name);

        // Events are made static because we want to create only once 
        // when client joins
        private static event UserJoined NewJoin;
        private static event UserSendMessage MessageSent;
        private static event UserLeft RemoveUser;
                
        private string userName;
        private IChatChannel channel;
        // As we need to establish duplex communication we use 
        // DuplexChanelFactory
        private DuplexChannelFactory factory;

        public ChatClient()
        {
            InitializeComponent();
            this.AcceptButton = btnLogin;
        }

        public ChatClient(string userName)
        {
            this.userName = userName;
        }

        private void btnLogin_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(txtUserName.Text.Trim()))
            {
                try
                {
                    // Register an event
                    NewJoin += new UserJoined(ChatClient_NewJoin);
                    MessageSent += new UserSendMessage
                        (ChatClient_MessageSent);
                    RemoveUser += new UserLeft(ChatClient_RemoveUser);

                    channel = null;
                    this.userName = txtUserName.Text.Trim();
                    // Create InstanceContext to handle call back interface
                    // Pass the object of the CallbackContract implementor
                    InstanceContext context = new InstanceContext(
                        new ChatClient(txtUserName.Text.Trim()));
                    // We create a participant with the given end point
                    // The communication is managed with CHAT MESH and 
                    // each client creates a duplex 
                    // end point with the mesh. Mesh is nothing but the 
                    // named collection of nodes.
                    factory =
                        new DuplexChannelFactory(context, "ChatEndPoint");
                    channel = factory.CreateChannel();                  
                    channel.Open();                    
                    channel.Join(this.userName);
                    grpMessageWindow.Enabled = true;
                    grpUserList.Enabled = true;                    
                    grpUserCredentials.Enabled = false;                    
                    this.AcceptButton = btnSend;
                    rtbMessages.AppendText
                        ("****WEL-COME to Chat Application*****\r\n");
                    txtSendMessage.Select();
                    txtSendMessage.Focus();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }
            }
        }

        void ChatClient_RemoveUser(string name)
        {
            try
            {
                rtbMessages.AppendText("\r\n");
                rtbMessages.AppendText(name + " left at " + 
                        DateTime.Now.ToString());
                lstUsers.Items.Remove(name);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString());
            }
        }

        void ChatClient_MessageSent(string name, string message)
        {
            if (!lstUsers.Items.Contains(name))
            {
                lstUsers.Items.Add(name);
            }
            rtbMessages.AppendText("\r\n");
            rtbMessages.AppendText(name + " says: " + message);
        }

        void ChatClient_NewJoin(string name)
        {
            rtbMessages.AppendText("\r\n");
            rtbMessages.AppendText(name + " joined at: 
                [" + DateTime.Now.ToString() + "]");            
            lstUsers.Items.Add(name);       
        }
       
        #region IChatService Members

        public void Join(string memberName)
        {            
            if (NewJoin != null)
            {
                NewJoin(memberName);
            }
        }

        public new void Leave(string memberName)
        {
            if (RemoveUser != null)
            {
                RemoveUser(memberName);
            }
        }

        public void SendMessage(string memberName, string message)
        {
            if (MessageSent != null)
            {
                MessageSent(memberName, message);
            }
        }

        #endregion

        private void btnSend_Click(object sender, EventArgs e)
        {
            channel.SendMessage(this.userName, txtSendMessage.Text.Trim());
            txtSendMessage.Clear();
            txtSendMessage.Select();
            txtSendMessage.Focus();
        }

        private void ChatClient_FormClosing(object sender, 
                        FormClosingEventArgs e)
        {
            try
            {
                if (channel != null)
                {
                    channel.Leave(this.userName);
                    channel.Close();
                }
                if (factory != null)
                {
                    factory.Close();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
    }
} 

.*同样,.*客户端.*也.*非常.*简单。.*重要的是.*config.*文件。.*我们将.*看到.*详细.*信息。

 <CODE>
  ?xml version="1.0" encoding="utf-8" ?
configuration
  system.serviceModel
    client
      endpoint name="ChatEndPoint" address="net.p2p://chatMesh/ChatServer"
                binding="netPeerTcpBinding" 
            bindingConfiguration="PeerTcpConfig"
                contract="ChatClient.IChatService"endpoint    
     
   client

    bindings
      netPeerTcpBinding
        binding name="PeerTcpConfig" port="0"
          security mode="None"security
          resolver mode="Custom"
            custom address="net.tcp://10.34.34.241/ChatServer" 
                binding="netTcpBinding"
                    bindingConfiguration="TcpConfig"custom
          resolver
        binding        
      netPeerTcpBinding
      netTcpBinding
        binding name="TcpConfig"
          security mode="None"security
        binding
      netTcpBinding
    bindings
  system.serviceModel
configuration 

在这里,*config*文件.*中有.*名为*“ChatEndpoint”*的.*终结点,.*它.*指向.*一个.*聊天.*网格.*并.*使用*netPeerTcpBinding。*当我们.*配置.*绑定.*时,.*我们.*使用.*自定义.*解析器.*模式.*并.*提供.*实际.*服务器.*正在.*运行.*的.*自定义.*TCP.*地址。
将.*端口.*号.*设置为.*零.*将.*自动.*检测.*空闲.*端口.*进行.*通信。.*我们将.*安全.*模式.*设置为.*none。.*因此,.*使用.*此.*配置,.*我们可以.*启动.*客户端.*并在.*内网.*中.*发送.*消息。

限制

我.*没有.*找到.*一种.*方法.*将.*在线.*用户.*列表.*发送.*回.*给.*新.*加入.*的用户。.*我.*会.*很.*感激.*这个.*解决方案。.*如果您.*发现.*任何.*更有.*趣.*的东西,.*也.*请.*告知.*我。.*我.*也.*正在.*探索.*WCF.*阶段,.*这.*可能.*不是.*完美的.*聊天.*应用程序。
.*任何.*建议.*和.*更正.*都.*受欢迎。

参考

.*我.*参考.*了.*微软.*技术.*示例.*来.*熟悉.*WCF.*和.*不同.*绑定的.*工作.*方式。

结论

.*我们可以.*得出.*结论,.*WCF.*为.*我们.*提供.*了.*一种.*非常.*简单.*但.*非常.*强大.*的.*方式,.*可以在.*不同.*的.*系统.*之间.*建立.*通信,.*同时.*在.*相似.*的.*操作系统.*上.*保持.*性能.*优势。

© . All rights reserved.