I have this WCF self-hosted WebSocket service code:
Main:
//Create a URI to serve as the base address
Uri httpUrl = new Uri(\"http:/
After spending a day with the same task I finally got working solution. Hope it will help someone in the future.
Client JS script:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>WebSocket Chat</title>
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.1.js"></script>
<script type="text/javascript">
var ws;
$().ready(function ()
{
$("#btnConnect").click(function ()
{
$("#spanStatus").text("connecting");
ws = new WebSocket("ws://localhost:8080/hello");
ws.onopen = function ()
{
$("#spanStatus").text("connected");
};
ws.onmessage = function (evt)
{
$("#spanStatus").text(evt.data);
};
ws.onerror = function (evt)
{
$("#spanStatus").text(evt.message);
};
ws.onclose = function ()
{
$("#spanStatus").text("disconnected");
};
});
$("#btnSend").click(function ()
{
if (ws.readyState == WebSocket.OPEN)
{
var res = ws.send($("#textInput").val());
}
else
{
$("#spanStatus").text("Connection is closed");
}
});
$("#btnDisconnect").click(function ()
{
ws.close();
});
});
</script>
</head>
<body>
<input type="button" value="Connect" id="btnConnect" />
<input type="button" value="Disconnect" id="btnDisconnect" /><br />
<input type="text" id="textInput" />
<input type="button" value="Send" id="btnSend" /><br />
<span id="spanStatus">(display)</span>
</body>
</html>
Self hosted server:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;
namespace WebSocketsServer
{
class Program
{
static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8080/hello");
// Create the ServiceHost.
using(ServiceHost host = new ServiceHost(typeof(WebSocketsServer), baseAddress))
{
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
CustomBinding binding = new CustomBinding();
binding.Elements.Add(new ByteStreamMessageEncodingBindingElement());
HttpTransportBindingElement transport = new HttpTransportBindingElement();
//transport.WebSocketSettings = new WebSocketTransportSettings();
transport.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;
transport.WebSocketSettings.CreateNotificationOnConnection = true;
binding.Elements.Add(transport);
host.AddServiceEndpoint(typeof(IWebSocketsServer), binding, "");
host.Open();
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
// Close the ServiceHost.
host.Close();
}
}
}
[ServiceContract(CallbackContract = typeof(IProgressContext))]
public interface IWebSocketsServer
{
[OperationContract(IsOneWay = true, Action = "*")]
void SendMessageToServer(Message msg);
}
[ServiceContract]
interface IProgressContext
{
[OperationContract(IsOneWay = true, Action = "*")]
void ReportProgress(Message msg);
}
public class WebSocketsServer: IWebSocketsServer
{
public void SendMessageToServer(Message msg)
{
var callback = OperationContext.Current.GetCallbackChannel<IProgressContext>();
if(msg.IsEmpty || ((IChannel)callback).State != CommunicationState.Opened)
{
return;
}
byte[] body = msg.GetBody<byte[]>();
string msgTextFromClient = Encoding.UTF8.GetString(body);
string msgTextToClient = string.Format(
"Got message {0} at {1}",
msgTextFromClient,
DateTime.Now.ToLongTimeString());
callback.ReportProgress(CreateMessage(msgTextToClient));
}
private Message CreateMessage(string msgText)
{
Message msg = ByteStreamMessage.CreateMessage(
new ArraySegment<byte>(Encoding.UTF8.GetBytes(msgText)));
msg.Properties["WebSocketMessageProperty"] =
new WebSocketMessageProperty
{
MessageType = WebSocketMessageType.Text
};
return msg;
}
}
}
UPDATE
As of .net 4.5 new way of writing server side have emerged. The benefits are cleaner code and possibility to support secure web sockets (WSS) over https.
public class WebSocketsServer
{
#region Fields
private static CancellationTokenSource m_cancellation;
private static HttpListener m_listener;
#endregion
#region Private Methods
private static async Task AcceptWebSocketClientsAsync(HttpListener server, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var hc = await server.GetContextAsync();
if (!hc.Request.IsWebSocketRequest)
{
hc.Response.StatusCode = 400;
hc.Response.Close();
return;
}
try
{
var ws = await hc.AcceptWebSocketAsync(null).ConfigureAwait(false);
if (ws != null)
{
Task.Run(() => HandleConnectionAsync(ws.WebSocket, token));
}
}
catch (Exception aex)
{
// Log error here
}
}
}
private static async Task HandleConnectionAsync(WebSocket ws, CancellationToken cancellation)
{
try
{
while (ws.State == WebSocketState.Open && !cancellation.IsCancellationRequested)
{
String messageString = await ReadString(ws).ConfigureAwait(false);
var strReply = "OK"; // Process messageString and get your reply here;
var buffer = Encoding.UTF8.GetBytes(strReply);
var segment = new ArraySegment<byte>(buffer);
await ws.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
}
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None);
}
catch (Exception aex)
{
// Log error
try
{
await ws.CloseAsync(WebSocketCloseStatus.InternalServerError, "Done", CancellationToken.None).ConfigureAwait(false);
}
catch
{
// Do nothing
}
}
finally
{
ws.Dispose();
}
}
private static async Task<String> ReadString(WebSocket ws)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);
WebSocketReceiveResult result = null;
using (var ms = new MemoryStream())
{
do
{
result = await ws.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}
#endregion
#region Public Methods
public static void Start(string uri)
{
m_listener = new HttpListener();
m_listener.Prefixes.Add(uri);
m_listener.Start();
m_cancellation = new CancellationTokenSource();
Task.Run(() => AcceptWebSocketClientsAsync(m_listener, m_cancellation.Token));
}
public static void Stop()
{
if(m_listener != null && m_cancellation != null)
{
try
{
m_cancellation.Cancel();
m_listener.Stop();
m_listener = null;
m_cancellation = null;
}
catch
{
// Log error
}
}
}
#endregion
}