The first part in this series which I would like to do can be found here:
Part 1 : Socket Programming with C#, JAVA, C++ and Action Script 3.0 – Establishing a base connection and communication with C# Server and AS3
The files for this solution can be downloaded here: WCFCallbackExample
I am looking into how you can work with network communication with sockets using the different languages and this post will use C# and more specifically WCF. In this post I will look at the bi directional capability of WCF using the NetTcpBinding, I will follow up this post in the near future with an example using the Duplex WS binding (WSDualHttpBinding). If you are in the area of Application Development and say wanting to use this for example with a chat application, the NetTcpBinding requires that both the client and the service use WCF, so for interoperability that is not really of much use, but there is more than one way to skin a cat for sure.
In this post and the example solution I am making use of threading for the purposes of the example so I can simulate many clients, as opposed to launching the application many times. I will be making use of the Thread class, ThreadPool class, WaitHandle class, ManualResetEvent class and using a ParamterizedThreadStart and WaitCallBack delegates. I have also made an extension method which the mode value of an IList<T>.
The theme of this example will be voters casting these vote. Once all the votes are in, in this case 10, the votes will be counted and all the voters will be informed of the winning candidate.
NetTcpBinding
For the code in this example I am not using the configuration files, I am instantiating and configuring on the fly programmatically as I want to get a feel for accomplishing tasks both programmatically and through xml configuration as both have their benefits dependant on the situation. The basic idea which I want to demonstrate first of all goes like this.
- Instantiate and open a service host to accept incoming messages
- Instantiate a client a proxy a call to the listening service
- On the server side I want to process the message call from the client and then call-back to the client using the method which I define with an interface(the contract).
The Program
The files which make up this small program include the following:
- IVote
- IVoteCallback
- Client
- ListExtension
- Proxy
- Server
- ServiceHost
I have separated the client from the proxy, first because lol it is a proxy but also each one has a different responsibility, what you end up with though is Client calls Vote on Proxy, Proxy calls Vote on the Service. So it is exactly that “a proxy,” i.e. the middle person.
I have used the following class for a generic Service Host from the following book, Programming WCF Services.
public class ServiceHost<T> : ServiceHost
{
public ServiceHost()
: base(typeof(T))
{ }
public ServiceHost(params string[] baseAddresses)
: base(typeof(T), Convert(baseAddresses))
{ }
public ServiceHost(params Uri[] baseAddresses)
: base(typeof(T), baseAddresses)
{ }
static Uri[] Convert(string[] baseAddresses)
{
Converter<string, Uri> convert = delegate(string address)
{
return new Uri(address);
};
return Array.ConvertAll(baseAddresses, convert);
}
}
The call-back
This is very much an event, and as such many client can subscribe to this event. It is the service’s responsibility to ensure all subscribers of this event are notified upon the invocation of some trigger, “Don’t call us we shall call you.”
As much as this is an event, the way you program this as opposed to normal CLR Event Subscription and Invocation is different and slightly more management is required with regards to concurrency.
The contract for the call-back is simply the following:
public interface IVoteCallback
{
[OperationContract]
void OnVotesCounted(string winner);
}
The server will invoke this method on the clients, and the clients themselves will inherit from this interface so the server can invoke the method on it. The implementation of this method is up to the client, but it is the responsibility of the Service Host to invoke it.
The Service Contract
The following contract is what the service needs to use, and also any proxy that is made for this service. The client invokes the contract on the proxy and in turn the proxy invokes the contract on the service.
[ServiceContract(Name = "WCFCallbackExample.Contract.IVote",
Namespace="uk.co.andrewrea",
SessionMode=SessionMode.Required,
CallbackContract=typeof(IVoteCallback))]
public interface IVote
{
[OperationContract(IsOneWay=true)]
void PlaceVote(string name);
}
This interface requires some specific attributes for the purposes of:
- Being a service contract
- Providing operation contracts for a service
- Allowing the service to be used in a Duplex hosting scenario, i.e. the service host can send messages to the client as well as the client can send messages to the service
- Information for WCF to recognise what call-back it is dealing with.
The session mode is set to required so a call-back can be sent to the client, this is also true of the WSDualHttpBinding.
Also I will mention why I have made the operation in this contract one way below.
The Server
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Server : ServiceHost<Server>, IVote
{
//A collection of votes from the clients.
//The candidate name is the string object value
private static List<string> _votes
= new List<string>();
//A list of the callbacks which the server should invoke
//allowing the clients to be notified
private static List<IVoteCallback> _callbacks
= new List<IVoteCallback>();
//For the purposes of syncronization
private static object _syncLock = new Object();
public Server()
: base()
{
}
public Server(params string[] addresses)
: base(addresses)
{
}
#region IVote Members
/// <summary>
/// This method will be proxied and called by the client
/// </summary>
/// <param name="name"></param>
public void PlaceVote(string name)
{
lock (_syncLock)
{
//Get the callback from the client
IVoteCallback callback = OperationContext.Current
.GetCallbackChannel<IVoteCallback>();
//If the service does not already contain the callback add it
if (!_callbacks.Contains(callback))
_callbacks.Add(callback);
//Add the vote
_votes.Add(name);
//I do not want to block here, so I accept the vote and
//call the method to count the votes on another thread
//
//I origianlly used the below BUT making the operation IsOneWay
//Achieves the same thing. Without this or the IsOneWay,
//this will block the client thread.
//ThreadPool.QueueUserWorkItem(new WaitCallback(CheckVoteCount));
if (_votes.Count == 10)
{
System.Threading.Thread.Sleep(2000);
VotesCounted();
}
}
}
/*
*
* This was called by the ThreadPool, see above
private void CheckVoteCount(object state)
{
if (_votes.Count == 10)
{
System.Threading.Thread.Sleep(2000);
VotesCounted();
}
}
* */
#endregion
/// <summary>
/// This is the callback. It will loop through all of them and
/// invoke. This is the method which updates the clients
/// </summary>
private static void VotesCounted()
{
Action<IVoteCallback> invoke =
delegate(IVoteCallback clientCallback)
{
clientCallback.OnVotesCounted(_votes.MostOccurences<string>());
};
_callbacks.ForEach(invoke);
}
}
The server is keeping a list of:
I have plumbed in some thread safety, as each call made to the service will be from a different thread. You will notice that there is some code I have commented out but left in there. This is because I wanted to explain the purpose of why I have not used it and what I have used instead. What I was doing there was ensuring that the method did not block the client thread, so I did the work of the client and then kicked off another thread to check whether or not it should count the votes. After doing this though, I thought about the IsOneWay attribute property which did exactly what I require. If you specify this on an operation context, the client will call and forget about the method, i.e. it will not block as it does not care about is outcome so in this way I was able to leave the check for the votes inside the client method and this did not affect the client in terms of time.
Above when I said this was like the CLR Event model in a way, I would now say that through its implementation, it is more like the Java Event Model where you specifically add listeners and the object loops through its subscribers and calls the method the listener has provided as the call-back. I realise under the covers this is what the clr event model will do, but from a coding point of view, it is more Java’esque – The Observer Pattern.
I will show the source for the extension method, MostOccurences<T> below.
The Proxy
public class Proxy : DuplexClientBase<IVote>, IVote
{
public Proxy(InstanceContext context,
Binding binding,
EndpointAddress endpointAddress)
: base(context, binding, endpointAddress)
{
}
#region IVote Members
public void PlaceVote(string name)
{
Channel.PlaceVote(name);
}
#endregion
}
I have inherited both from the service contract but also from the DuplexClientBase<T> as opposed to the ClientBase<T> which allows the client to accept call-backs from the service. As a proxy should, the PlaceVote method calls the PlaceVote of the service. Although I have specified a constructor which allows the supply of a Binding, I am only using NetTcpBinding for this program but I have supplied it, as this is a constructor of the base class. The InstanceContext is used directly for the call-back, so the service can identify what object instance to use for the call-back method.
The Client
public class Client : IVoteCallback,IVote
{
private Proxy _proxy;
private string _voterName;
private ManualResetEvent _handle;
public Client(string voterName, string serviceEndpointAddress, ManualResetEvent handle)
{
_voterName = voterName;
_handle = handle;
InstanceContext context = new InstanceContext(this);
_proxy = new Proxy(context,
new NetTcpBinding(),
new EndpointAddress(serviceEndpointAddress));
}
#region IVoteCallback Members
public void OnVotesCounted(string winner)
{
Console.WriteLine(String.Format("{0} has been notified the winner is {1}",_voterName, winner ));
_handle.Set();
}
#endregion
#region IVote Members
public void PlaceVote(string name)
{
_proxy.PlaceVote(name);
Console.WriteLine(String.Format("From {0}: I have placed my vote for {1}!", _voterName, name));
}
#endregion
}
A lot of this code, is specific just to this example but it is the instantiation of the proxy and in turn the invocation of its method, and the implementation of both the Service Contract and the Call-back contract which are important. The threading elements are just to provide synchronization and are used so I can test the service with multiple clients(threads).
The Extension Method (ListExtenions.cs)
public static T MostOccurences<T>(this IList<T> list)
{
Dictionary<T, int> count = new Dictionary<T, int>();
foreach (T obj in list)
{
if (!count.ContainsKey(obj))
{
count.Add(obj, 1);
}
else
{
count[obj]++;
}
}
return count.Where(x => x.Value.Equals(count.Max(k=>k.Value))).FirstOrDefault().Key;
}
This will give me the value of the IList<T> which occurs most often, and its use is so I can see the candidate who won.
Program.cs
This is the main program where I implement the work. Again the threading stuff is just for the purposes of the example.
class Program
{
private static Server server;
private static string netServerAddress = "net.tcp://localhost:8000";
private static string endpointAddress = "net.tcp://localhost:8000/VotingService";
private static Random rnd;
private static string[] candidates = new string[]{
"Candidate 1",
"Candidate 2",
"Candidate 3"
};
private static ManualResetEvent[] handles = new ManualResetEvent[10];
private static Thread[] threads = new Thread[10];
static void Main(string[] args)
{
rnd = new Random();
StartService();
for (int i = 0; i < 10; i++)
{
handles[i] = new ManualResetEvent(false);
threads[i] = new Thread(new ParameterizedThreadStart(CreateClientAndVote));
threads[i].Start(i);
}
Console.WriteLine("Waiting for votes to come in...");
WaitHandle.WaitAll(handles);
server.Close();
Console.WriteLine("Voting has ended");
Console.ReadLine();
}
static void StartService()
{
server = new Server(netServerAddress);
server.AddServiceEndpoint(
"WCFCallbackExample.Contract.IVote",
new NetTcpBinding(),
endpointAddress);
server.Open();
}
static void CreateClientAndVote(object state)
{
int number = (int)state;
Client newClient = new Client(String.Format("Voter #{0}", number + 1), endpointAddress, handles[number]);
newClient.PlaceVote(candidates[(int)(rnd.Next(candidates.Length))]);
}
}
And thats it. I hope this is of some help and/or interest.
Cheers for now,
Andrew.