Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / operating-systems / Windows

A simple peer to peer chat application using WCF netPeerTcpBinding

3.62/5 (27 votes)
24 Jan 20074 min read 1   11.3K  
A sample to demonstrate the use of netPeerTcpBinding in WCF

Sample Image - Chat_application_using_WC.png

Introduction

As you all know Microsoft has launched .NET 3.0 with four very powerful foundations.

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

There is already a lot of material available on what is WCF, WPF, WF and cardspace. I am not going in details of these technologies. While exploring WCF, I came across many interesting things which are newly introduced. In my previous chat application using remoting, I was using an interface that is distributed across the client and server. There was an abstract class that was implemented at the client side. When the server wants to send a message to the client, the server should have the list of clients and something which is common in all clients. An interface implementation was the best way to achieve this. There was serialization, channels, protocols and much more in .NET remoting.

In WCF things are made pretty simple. The first and the most important thing is that you can have as many endpoints as you want depending upon the requirements of your application. If you want your application to be used by a .NET client, Java client then you can go for TCP binding and basic HTTP binding. What you need to do is add those many end points in the configuration file of the server and start the server. It is the responsibility of WCF to give you performance benefits of communication with different clients. The basic communication protocol for WCF is SOAP. But when you establish communication between WCF service and WCF client, the foundation uses binary over SOAP to give you optimum performance. I will say that you can have WCF service with everything under one roof. Currently I am also into the process of exploring more about WCF and specifically the bindings. There are some features that are required but I do not have any idea about how to get them from netPeerTcpBinding and PeerResolvers.

About the application

The simple WCF chat application I have written, uses netTcpPeerBinding. Using this binding, it is very easy to create a simple intranet chat application that can be used over intranet. You do not have to write much of the code and you do not even have to write
any special interfaces, classes at server side. Everything is encapsulated in:

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

Chat Server

A very simple chat server with merely four lines of code. As said earlier, you do not need to write any special code. Hence the server application looks like this.

C#
 <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;
            }
        }
    }
} 

The code is self explanatory. You create an object of CustomPeerResolverService and pass that object as an input parameter to the ServiceHost class. Open peer resolver service and host and you are done. No new classes and no end points. Surprising right? But wait, I have to show you the config file. The config file plays the most important role of specifying the required details.

XML
 <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 

I have removed '<' and '>' because I was not able to show config files directly. If you know how to show then please let me know. Config file is very simple. We are using .NET predefined service in this case System.ServiceModel.PeerResolvers.CustomPeerResolverService. Give the base address for hosting the resolver service as shown. The important thing about the end point is that it uses System.ServiceModel.PeerResolvers.IPeerResolverContract contract which is already available in the foundation. As said earlier, we are using TCP end point for the communication. Hence we specify the endpoint with TCP binding and we configure it with security mode as none. That's it and you are done. Just start the server and you are ready for your chat client.

Chat Client

As compared to the Chat server, the client becomes little bit complicated. It does everything on its own. As said earlier, we need something common that the server will use to communicate with clients and that can be established using interfaces.
Same concepts apply here also and we have client code that looks like this:

C#
 <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<ichatchannel /> 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<ichatchannel />(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());
            }
        }
    }
} 

Again the client is also very simple. The important thing is the config file. We will see the details.

C#
 <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 

Here in the config file we have endpoint names "ChatEndpoint", which point to a chat mesh and use netPeerTcpBinding. When we configure the binding, we use the custom resolver mode and give the custom TCP address where our actual server is running.
Giving port number as zero will automatically detect the free port for communication. We use security mode as none. Thus using this configuration we can start the clients and send a message across the intranet.

Limitations

I did not find a way to send back a list of online users to the new joined user. I'll appreciate the solution. Also let me know if you find anything more interesting. I am also into WCF exploring stage and this might not be the perfect chat application.
Any suggestions, corrections are welcome.

Reference

I have referred to the Microsoft Technology Samples for getting familiar with WCF and working of different bindings.

Conclusion

We can conclude that WCF provides us a very simple but very powerful way of establishing communication between diverse systems, maintaining the performance benefits with similar OS.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here