Wednesday, May 28, 2008

WCF Server/Client Chat Sample


WCF Server/Client Chat


Download


Screen Shots



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