Home > .NET Development > WCF Server/Client Chat Sample

WCF Server/Client Chat Sample

Download

This is a simple and smart WCF Client/Server Chat sample, Amit Gupta (visitor to my blog) asked how to convert a
client/server sockets app. into client/server WCF app., which got me interested to make this sample.

It consists of two apps. both are windows forms, the server one includes the contracts and implements the service.
The client one implements the callcack.

WCF server/client sample features:

  • Handling concurrency
  • Handling server state
  • Handling client state
  • Checking server availability
  • Reliable sessions
  • Asynchronous operations

Code will be as follows:

  • Server:
    • form1.cs
    • app.config
  • Client:
    • form1.cs

Server

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;

namespace Server
{
    [ServiceContract(CallbackContract = typeof(ISampleChatCallback), SessionMode = SessionMode.Required)]
    public interface ISampleChat
    {
        [OperationContract(IsInitiating = true, IsOneWay = true)]
        void Connect(string name);

        [OperationContract(IsOneWay = true)]
        void SayToServer(string name, string msg);

        [OperationContract(IsTerminating = true, IsOneWay = true)]
        void Disconnect(string name);
    }


    public interface ISampleChatCallback
    {
        [OperationContract(IsOneWay = true)]
        void SayToClient(string msg);
    }



    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    public partial class Form1 : Form, ISampleChat
    {
        ServiceHost host;
        private Dictionary<string, ISampleChatCallback> clients = new Dictionary<string, ISampleChatCallback>();
        private object syncObj = new object();

        public Form1()
        {
            InitializeComponent();
            EnableControls(false);
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            host = null;
            host = new ServiceHost(this);
            host.Opened += new EventHandler(host_Opened);
            host.Closed += new EventHandler(host_Closed);
            host.Faulted += new EventHandler(host_Faulted);

            //start listening..
            host.Open();
        }

        void host_Faulted(object sender, EventArgs e)
        {
            HandleHost();
        }

        void host_Closed(object sender, EventArgs e)
        {
            HandleHost();
        }

        void host_Opened(object sender, EventArgs e)
        {
            HandleHost();
        }

        private void HandleHost()
        {
            switch (host.State)
            {
                case CommunicationState.Closed:
                    labelStatus.Text = "Closed";
                    EnableControls(false);
                    break;
                case CommunicationState.Faulted:
                    labelStatus.Text = "Faulted";
                    EnableControls(false);
                    host.Abort();
                    break;
                case CommunicationState.Opened:
                    labelStatus.Text = "Opened";
                    EnableControls(true);
                    break;
            }
        }

        private void EnableControls(bool opened)
        {
            if (opened)
            {
                buttonStart.Enabled = false;
                buttonStop.Enabled = true;
                buttonSend.Enabled = true;
                richTextBox1.Enabled = true;
                textBox1.Enabled = true;
                listBox1.Enabled = true;
            }
            else
            {
                buttonStart.Enabled = true;
                buttonStop.Enabled = false;
                buttonSend.Enabled = false;
                richTextBox1.Enabled = false;
                textBox1.Enabled = false;
                listBox1.Enabled = false;
            }
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            host.Close();
        }

        private void buttonSend_Click(object sender, EventArgs e)
        {
            //send message to all clients
            foreach (ISampleChatCallback cb in clients.Values)
            {
                cb.SayToClient("Server : " + textBox1.Text.ToString());
            }
            richTextBox1.Text += "\n" + "Server : " + textBox1.Text.ToString();
            textBox1.Text = "";
        }

        private ISampleChatCallback CurrentCallback
        {
            get
            {
                return OperationContext.Current.GetCallbackChannel();
            }
        }

        #region ISampleChat Members

        public void Connect(string name)
        {
            if (!clients.ContainsKey(name))
            {
                lock (syncObj)
                {
                    clients.Add(name, CurrentCallback);
                    listBox1.Items.Add(name);
                }

                richTextBox1.Text += "\n" + name + " connected..";


                //u may want to tell other clients that someone just connected
                foreach (ISampleChatCallback cb in clients.Values)
                {
                    cb.SayToClient("Client " + name + " connected.");
                }
            }


        }

        public void Disconnect(string name)
        {
            if (clients.ContainsKey(name))
            {
                lock (syncObj)
                {
                    clients.Remove(name);
                    listBox1.Items.Remove(name);
                }
            }

            richTextBox1.Text += "\n" + name + " disconnected..";

            //u may want to tell other clients that someone just disconnected
            foreach (ISampleChatCallback cb in clients.Values)
            {
                cb.SayToClient("Client " + name + " disconnected.");
            }
        }

        public void SayToServer(string name, string msg)
        {
            //here u get the message from the client
            //do whatever u want..
            richTextBox1.Text += "\n" + name + " : " + msg;

            //u may want to tell other clients that someone said something
            foreach (ISampleChatCallback cb in clients.Values)
            {
                cb.SayToClient(name + " : " + msg);
            }
        }

        #endregion
    }
}

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>

    <services>
      <service name="Server.Form1" behaviorConfiguration="serviceBehaviorConfiguration">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:4477/SampleWCFChat/"/>
            <add baseAddress="http://localhost:4478/SampleWCFChat/"/>
          </baseAddresses>
        </host>
        <endpoint address="tcp"
                  binding="netTcpBinding"
                  bindingConfiguration="tcpBindingConfiguration"
                  contract="Server.ISampleChat"/>
        
        <endpoint address="mex"
                  binding="mexTcpBinding"
                  contract="IMetadataExchange"/>
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name="tcpBindingConfiguration" 
                 closeTimeout="00:00:05"
                 maxBufferSize="1048576"
                 maxBufferPoolSize="1048576"
                 maxConnections="10"
                 maxReceivedMessageSize="1048576"
                 openTimeout="00:00:05"
                 receiveTimeout="01:00:00"
                 sendTimeout="01:00:00"
                 transferMode="Buffered">
          <readerQuotas maxArrayLength="1048576" maxBytesPerRead="1048576" maxStringContentLength="1048576"/>
          <reliableSession enabled="true" inactivityTimeout="01:00:00"/>
        </binding>
      </netTcpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="serviceBehaviorConfiguration">
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    
    
  </system.serviceModel>
</configuration>

Client

Form1.cs


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace Client
{
    public partial class Form1 : Form, SampleService.ISampleChatCallback
    {
        public Form1()
        {
            InitializeComponent();
            EnableControls(false);
        }


        SampleService.SampleChatClient proxy;
        private string name = string.Empty;
        private delegate void MyInvoker();

        #region ISampleChatCallback Members

        public void SayToClient(string msg)
        {
            richTextBox1.Text += "\n" + msg;
        }

        public IAsyncResult BeginSayToClient(string msg, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndSayToClient(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        #endregion


        private void EnableControls(bool connected)
        {
            if (connected)
            {
                buttonDisconnect.Enabled = true;
                buttonConnect.Enabled = false;
                buttonSend.Enabled = true;
                richTextBox1.Enabled = true;
                textBoxMsg.Enabled = true;
            }
            else
            {
                richTextBox1.Text = "";
                richTextBox1.Enabled = false;
                textBoxMsg.Enabled = false;
                buttonDisconnect.Enabled = false;
                buttonConnect.Enabled = true;
                buttonSend.Enabled = false;
            }
        }

        private void buttonDisconnect_Click(object sender, EventArgs e)
        {
            proxy.DisconnectAsync(name);
            richTextBox1.Text = "";
        }

        private void buttonSend_Click(object sender, EventArgs e)
        {
            proxy.SayToServerAsync(name, textBoxMsg.Text.ToString());
            textBoxMsg.Text = "";
        }

        private void buttonConnect_Click(object sender, EventArgs e)
        {
            //u may want to skip CheckServer to boost speed 
            if (CheckServer())
            {
                proxy = null;
                InstanceContext context = new InstanceContext(this);
                proxy = new Client.SampleService.SampleChatClient(context);

                proxy.InnerDuplexChannel.Opened += new EventHandler(InnerDuplexChannel_Opened);
                proxy.InnerDuplexChannel.Closed += new EventHandler(InnerDuplexChannel_Closed);
                proxy.InnerDuplexChannel.Faulted += new EventHandler(InnerDuplexChannel_Faulted);

                name = textBoxName.Text.ToString();
                proxy.ConnectAsync(name);
            }
            else
            {
                MessageBox.Show("Sorry, Server is not available");
            }
        }

        private bool CheckServer()
        {
            MetadataExchangeClient mexClient;
            bool serverIsUp = false;
            try
            {
                string address = "net.tcp://localhost:4477/SampleWCFChat/mex";
                mexClient = new MetadataExchangeClient(new Uri(address), MetadataExchangeClientMode.MetadataExchange);
                MetadataSet metadata = mexClient.GetMetadata();

                serverIsUp = true;
            }
            catch
            {
                serverIsUp = false;
            }

            return serverIsUp;
        }

        void InnerDuplexChannel_Faulted(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                this.Invoke(new MyInvoker(HandleProxy));
                return;
            }
            HandleProxy();
        }

        void InnerDuplexChannel_Closed(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                this.Invoke(new MyInvoker(HandleProxy));
                return;
            }
            HandleProxy();
        }

        void InnerDuplexChannel_Opened(object sender, EventArgs e)
        {

            if (InvokeRequired)
            {
                this.Invoke(new MyInvoker(HandleProxy));
                return;
            }
            HandleProxy();
        }

        private void HandleProxy()
        {
            switch (proxy.State)
            {
                case CommunicationState.Closed:
                    labelStatus.Text = "Disconnected";
                    EnableControls(false);
                    break;
                case CommunicationState.Faulted:
                    labelStatus.Text = "Faulted";
                    EnableControls(false);
                    break;
                case CommunicationState.Opened:
                    labelStatus.Text = "Connected";
                    EnableControls(true);
                    break;
            }
        }
    }
}

Advertisements
Categories: .NET Development
  1. Michael.Nagy
    June 9, 2008 at 1:19 pm

    nice job man .. begad gamed awi awi

  2. Anonymous
    February 10, 2009 at 5:54 pm

    I do not think your demo is very good.Because if one user disconnect by unexpected reason other than pressing on the “disconnect” button, you have no action to prevent from it. So, how to do it? For your answers.
    My mail is bjq_ren@hotmail.com.

  3. Islam Eldemery
    February 11, 2009 at 7:56 am

    Hi,
    this is easy, consider this code in the service class.
    This service tracks every one connects to it and store them in a hash table, if someone disconnects for any reason, it safely removes him from the hash table.

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class MyService : IMyService
    {
        #region Feilds
    
        private static object syncObj = new object();
        private static Dictionary<IContextChannel, string> _users = new Dictionary<IContextChannel, string>();
    
        #endregion
    
        #region Properties
    
        public IContextChannel CurrentUserChannel { get { return OperationContext.Current.Channel; } }
        public string CurrentUserName
        {
            get
            {
                return OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name;
            }
        }
    
        #endregion
    
        #region IMyService Members
    
        public void Connect()
        {
            lock (syncObj)
            {
                if (_users.ContainsValue(CurrentUserName))
                {
                    _users.Remove(CurrentUserChannel);
                }
                _users.Add(CurrentUserChannel, CurrentUserName);
            }
    
            CurrentUserChannel.Faulted += new EventHandler(CurrentUserChannel_Faulted);
            CurrentUserChannel.Closing += new EventHandler(CurrentUserChannel_Closing);
        }
    
        #endregion
    
        #region Connection Handling
    
        void CurrentUserChannel_Closing(object sender, EventArgs e)
        {
            HandleChannel(sender as IContextChannel);
        }
        void CurrentUserChannel_Faulted(object sender, EventArgs e)
        {
            HandleChannel(sender as IContextChannel);
        }
        private void HandleChannel(IContextChannel channel)
        {
            if (channel.State == CommunicationState.Closing || channel.State == CommunicationState.Faulted)
            {
    
                lock (syncObj)
                {
                    if (_users.ContainsKey(channel))
                    {
                        _users.Remove(channel);
                    }
                }
            }
        }
    
        #endregion
    
    }
    
  4. Jay
    April 3, 2009 at 12:17 am

    OK… The application working fine in a single system (i.e. if I am using as a localhost)…

    What type of modification I need perform to run Server application in one machine and client application in another machine

  5. Jay
    April 3, 2009 at 12:19 am

    Because I tested this application in a network environment…

    I don’t get any luck to successfully run it in network environment

  6. Gustavo Breves
    September 27, 2010 at 12:22 pm

    Hello. I am a ‘old’ user of your code, the one from codeproject site.
    Now I found this complementar code, just what I am looking for.
    But at line 18 above: OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name;
    I obtain NullReferenceException / Object reference not set…
    Is it occouring due the fact I am using the host and the client in a same PC ?
    Note that, even this error is solved, the host and cliente both have the same windowsIdentity.Name, hasn’t they ?
    What am I doing wrong ?
    Thanks

  7. October 8, 2010 at 1:29 pm

    This was a way to get the client’s name.
    You can get it also by passing it to the connect method from the client to the server.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: