Hi, today I was wandering about new features in .NET 4.5 and I try to examine in practice WebSockets feature. This feature is supported by Windows Server 2012 only. And I try to find an elegant example of running it. There was no interesting and elegant solution on the Internet, so I prepared my own for this blog entry. I used Visual Studio 2012 to implement 3 assemblies, WebSocketsSandbox.Contracts with interfaces, WebSocketsSandbox.Server with service host site implementation and WebSocketsSandbox.Client with client site implementation.
My running example was created on Windows 7 and it was running on Windows Server 2012, because that is the only one possibility to run WebSockets feature. You can see the running Server and Client on the following screen.
As you can see WebSockets are not extremely fast or maybe I did something wrong when I created this simple implementation. My example is programmatic without any XML configuration because I am used to Information Assurance requirements where such configuration cannot be used.
I implemented my example in 3 files in 3 assemblies as follows.
Contract.cs (WebSocketsSandbox.Contracts)
namespace WebSocketsSandbox.Contracts { using System.ServiceModel; using System.Threading.Tasks; [ServiceContract] public interface IClientCallback { [OperationContract(IsOneWay = true)] Task SendValues(string code, double value); } [ServiceContract(CallbackContract = typeof(IClientCallback), SessionMode = SessionMode.Required)] public interface ISendValuesService { [OperationContract(IsOneWay = true)] Task StartSendingValues(); } }
Server.cs (WebSocketsSandbox.Server)
namespace WebSocketsSandbox.Server { using System; using System.Configuration; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Configuration; using System.ServiceModel.Description; using System.Threading.Tasks; using WebSocketsSandbox.Contracts; class Server { static void Main(string[] args) { ConfigurationManager.AppSettings ["aspnet:UseTaskFriendlySynchronizationContext"] = "true"; var mapping = new System.ServiceModel.Configuration.ProtocolMappingElementCollection(); mapping.Add( element: new ProtocolMappingElement( schemeType: "http", binding: "netHttpBinding", bindingConfiguration: "default")); mapping.Add( element: new ProtocolMappingElement( schemeType: "https", binding: "netHttpsBinding", bindingConfiguration: "default")); ServiceHost serviceHost = null; try { string addressStr = "http://localhost:27272/WebSocketsServiceValues"; var binding = new NetHttpBinding( securityMode: BasicHttpSecurityMode.None, reliableSessionEnabled: true); var addressUri = new Uri(uriString: addressStr); serviceHost = new ServiceHost( serviceType: typeof(SendValuesService), baseAddresses: addressUri); var endpoint = serviceHost.AddServiceEndpoint( implementedContract: typeof(ISendValuesService), binding: binding, address: addressUri); serviceHost.Open(); Console.WriteLine( "WebSocketsServiceValues service is running, press any key to close..."); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } finally { serviceHost.Close(); } } } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class SendValuesService : ISendValuesService { public async Task StartSendingValues() { var callback = OperationContext.Current.GetCallbackChannel<IClientCallback>(); var random = new Random(); var count = 0; while (((IChannel)callback).State == CommunicationState.Opened) { var value = random.NextDouble(); await callback.SendValues("WebSocketsServiceValue", value); if (++count == 1000000) break; } } } }
Client.cs (WebSocketsSandbox.Client)
namespace WebSocketsSandbox.Client { using System; using System.Collections.Generic; using System.ServiceModel; using System.ServiceModel.Description; using System.Threading; using System.Threading.Tasks; using WebSocketsSandbox.Contracts; class Client { static void Main(string[] args) { var context = new InstanceContext( implementation: new CallbackHandler()); var binding = new NetHttpBinding( securityMode: BasicHttpSecurityMode.None, reliableSessionEnabled: true); var address = new EndpointAddress( uri: "http://localhost:27272/WebSocketsServiceValues"); var channelFactory = new DuplexChannelFactory<ISendValuesService>( callbackInstance: context, binding: binding, remoteAddress: address); var client = channelFactory.CreateChannel(address: address); client.StartSendingValues(); Console.WriteLine( "WebSocketsServiceValues client is running, press any key to close..."); Console.ReadKey(); } } class CallbackHandler : IClientCallback { int count = 0; public async Task SendValues(string code, double value) { Interlocked.Increment(ref count); if (count % 1000 == 0) Console.WriteLine("{0}, {1}", DateTime.UtcNow.ToString("HH:mm:ss.ffff"), count); } } }
As you can see, WebSockets with that implementation are very slow, it works much better without all async, await keywords and with void return type for services methods instead of Task return types. But, I will try to show you the exact way for fully asynchronically WebSockets.
You may wonder about resource usage on that machine? There is something like follows.
Today I am not trying tune this solution up. I will try to only show you a proper and nice way to create both Server and Client site implementations with extra assembly Contracts for both of them. I think that is the best pattern, and as you can see, I did not create any service reference that is not elegant. I try to do everything simple and elegant. The solution’s performance is poor, but you can simply play with this example and tune it up by treating it as an exercise. I hope you enjoy this short example.
P ;).
You can find entire solution here ;).