Mike Parks
"If you want to be the best, you've got to work with the best"

WCF & WPF Peer Channel Chat

A few days ago I was reading up on some WCF technology called Peer Channel. It looked pretty cool so I decided to try it out. I was pretty shocked to find out how easy it was to build a chat messaging service using this this stuff. I was able to run a chat client on my computer, a chat client on my friends computer, and the WCF Chat service on a server. We were able to send messages back and forth to each other (all on the same network of course). It worked pretty fast without using a lot of resouces on either computer or over the network. I threw a WPF interface on it to look nice, which was really easy to do as well. I just wanted to post up some of the code here on my blog in case anyone wanted to see how it works.

Client 1 and Client 2:

 Service that runs on the server:

 

 Only needed a few files to make it work:

 Here's the code to the important files:

PeerChannel – Client – App.config

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <system.serviceModel>
 
    <client>
      <!-- chat instance participating in the mesh -->
      <endpoint name="ChatEndpoint" address="net.p2p://chatMesh/MyChatServer/Chat" binding="netPeerTcpBinding" bindingConfiguration="BindingCustomResolver" contract="MyChat.IChat">
      </endpoint>
 
    </client>
 
    <bindings>
      <netPeerTcpBinding>
        <!-- Refer to Peer channel security samples on how to configure netPeerTcpBinding for security -->
        <binding name="BindingCustomResolver" port="0">
          <security mode="None"/>
          <resolver mode="Custom">
            <custom address="net.tcp://SERVERNAMEGOESHERE/MyChatServer/peerResolverService" binding="netTcpBinding" bindingConfiguration="Binding3"/>
          </resolver>
        </binding>
      </netPeerTcpBinding>
 
      <netTcpBinding>
        <!-- You can change security mode to enable security -->
        <binding name="Binding3">
          <security mode="None"/>
        </binding>
      </netTcpBinding>
    </bindings>
 
  </system.serviceModel>
  
</configuration>

 

PeerChannel – Client – ChatClient.xaml.cs

 

using System;
using System.DirectoryServices;
using System.Security.Principal;
using System.Windows;
using System.Windows.Input;
 
namespace MyChat
{
    public partial class ChatClient : Window
    {
        PeerChat chat;
 
        public ChatClient()
        {
            InitializeComponent();
        }
 
        private void btnJoin_Click(object sender, RoutedEventArgs e)
        {
            if (!String.IsNullOrEmpty(txtName.Text))
            {
                chat = new PeerChat(txtName.Text);
                chat.OnChatOnline += new PeerChat.ChatOnline(chat_OnChatOnline);
                chat.OnChatOffline += new PeerChat.ChatOffline(chat_OnChatOffline);
                chat.OnJoin += new PeerChat.JoinChat(chat_OnJoin);
                chat.OnLeave += new PeerChat.LeaveChat(chat_OnLeave);
                chat.OnChat += new PeerChat.ChatSendReceive(chat_OnChat);
                string result = chat.Start();
                txtChat.Text += result;
            }
        }
 
        void chat_OnChat(string member, string msg)
        {
            txtChat.Text += String.Format("[{0}] {1}" + Environment.NewLine, member, msg);
            txtChat.ScrollToEnd();
        }
 
        void chat_OnLeave(string member)
        {
            txtChat.Text += String.Format("[{0} left]" + Environment.NewLine, member);
        }
 
        void chat_OnJoin(string member)
        {
            txtChat.Text += String.Format("[{0} joined]" + Environment.NewLine, member);
            btnSend.IsEnabled = true;
        }
 
        void chat_OnChatOffline(object sender, EventArgs e)
        {
            txtChat.Text += String.Format("**  Offline" + Environment.NewLine);
        }
 
        void chat_OnChatOnline(object sender, EventArgs e)
        {
            txtChat.Text += String.Format("**  Online" + Environment.NewLine);
        }
 
        private void btnSend_Click(object sender, RoutedEventArgs e)
        {
            chat.SendMessage(txtMessage.Text);
            txtMessage.Text = String.Empty;
        }
 
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (chat != null)
                chat.End();
        }
 
        private void txtMessage_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                chat.SendMessage(txtMessage.Text);
                txtMessage.Text = String.Empty;
            }
        }
    }
}
 

PeerChannel – Client – PeerChat.cs

 

using System;
using System.ServiceModel;
 
namespace MyChat
{
    [ServiceContract(Namespace = "MyChat", CallbackContract = typeof(IChat))]
    public interface IChat
    {
        [OperationContract(IsOneWay = true)]
        void Join(string member);
 
        [OperationContract(IsOneWay = true)]
        void Chat(string member, string msg);
 
        [OperationContract(IsOneWay = true)]
        void Leave(string member);
    }
 
    public interface IChatChannel : IChatIClientChannel
    {
    }
 
    public class PeerChat : IChat
    {
        private bool IsConnected { getset; }
 
        string member;
        IChatChannel participant;
        DuplexChannelFactory<IChatChannel> factory;
        public delegate void JoinChat(string member);
        public delegate void ChatSendReceive(string member, string msg);
        public delegate void LeaveChat(string member);
        public delegate void ChatOnline(object sender, EventArgs e);
        public delegate void ChatOffline(object sender, EventArgs e);
        public event JoinChat OnJoin;
        public event ChatSendReceive OnChat;
        public event LeaveChat OnLeave;
        public event ChatOnline OnChatOnline;
        public event ChatOffline OnChatOffline;
 
        public PeerChat(string member)
        {
            this.member = member;
        }
 
        public string Start()
        {
            string result = String.Empty;
            InstanceContext instanceContext = new InstanceContext(this);
            factory = new DuplexChannelFactory<IChatChannel>(instanceContext, "ChatEndpoint");
            participant = factory.CreateChannel();
 
            IOnlineStatus ostat = participant.GetProperty<IOnlineStatus>();
            ostat.Online += new EventHandler(OnOnline);
            ostat.Offline += new EventHandler(OnOffline);
 
            try
            {
                participant.Open();
                IsConnected = true;
            }
            catch (CommunicationException)
            {
                result += "Could not find resolver." + Environment.NewLine +
                    "Please make sure the Chat Server is running before using this chat client." + Environment.NewLine +
                    "Refer to the readme for more details." + Environment.NewLine;
 
                IsConnected = false;
                return result;
            }
 
            participant.Join(member);
            return result;
        }
 
        public void SendMessage(string message)
        {
            participant.Chat(member, message);
        }
 
        public void End()
        {
            try
            {
                if (IsConnected)
                {
                    if (participant != null)
                    {
                        participant.Leave(member);
                        participant.Close();
                        participant.Dispose();
                    }
 
                    if (factory != null)
                    {
                        factory.Close();
                    }
                }
 
                IsConnected = false;
            }
            catch (Exception)
            {
            }
        }
 
        public void Join(string member)
        {
            if (OnJoin != null)
                OnJoin(member);
        }
 
        public void Chat(string member, string msg)
        {
            if (OnChat != null)
                OnChat(member, msg);
        }
 
        public void Leave(string member)
        {
            if (OnLeave != null)
                OnLeave(member);
        }
 
        public void OnOnline(object sender, EventArgs e)
        {
            if (OnChatOnline != null)
                OnChatOnline(member, e);
        }
 
        public void OnOffline(object sender, EventArgs e)
        {
            if (OnChatOffline != null)
                OnChatOffline(member, e);
        }
    }
}

 

PeerChannel – WCF Service Console – App.config

 

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="System.ServiceModel.PeerResolvers.CustomPeerResolverService">
       <host>
          <baseAddresses>
             <add baseAddress="net.tcp://SERVERNAMEGOESHERE/MyChatServer/peerResolverService"/>
          </baseAddresses>
       </host>
        <!-- use base address provided by the host -->
            <endpoint address="net.tcp://SERVERNAMEGOESHERE/MyChatServer/peerResolverService" binding="netTcpBinding" bindingConfiguration="Binding1" contract="System.ServiceModel.PeerResolvers.IPeerResolverContract"/>
      </service>
    </services>
    <bindings>
       <netTcpBinding>
          <!-- You can change security mode to enable security -->
          <binding name="Binding1">
              <security mode="None"/>
          </binding>
       </netTcpBinding>
    </bindings>
  </system.serviceModel>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

 

PeerChannel – WCF Service Console – DefaultCustomResolver.cs

 

using System;
using System.ServiceModel;
using System.ServiceModel.PeerResolvers;
 
namespace MyChatServer
{
    class DefaultCustomResolver
    {
        public static void Main()
        {
            // Create a new resolver service
            CustomPeerResolverService crs = new CustomPeerResolverService();
            crs.ControlShape = false;
 
            // Create a new service host
            ServiceHost customResolver = new ServiceHost(crs);
 
            // Open the resolver service 
            crs.Open();
            customResolver.Open();
            Console.WriteLine("Custom resolver service is started");
            Console.WriteLine("Press <ENTER> to terminate service");
            Console.ReadLine();
        }
    }
}