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

10 comments:

Unknown said...

nice job man .. begad gamed awi awi

Anonymous said...

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.

Islam Eldemery said...

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

}

Unknown said...

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

Unknown said...

Because I tested this application in a network environment...

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

Unknown said...

The disconnect event do not rise. If I disablethe network on the client app, nothing happens on the server, no event.
Any ideea ? Thanks.

Anonymous said...

Can anyone recommend the best Network Management system for a small IT service company like mine? Does anyone use Kaseya.com or GFI.com? How do they compare to these guys I found recently: N-able N-central it automation
? What is your best take in cost vs performance among those three? I need a good advice please... Thanks in advance!

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...

I wish not approve on it. I over precise post. Especially the title-deed attracted me to be familiar with the intact story.