Introduction
Part
1 gives an overview of the WebSocket protocol and .NET WebSocket support. Part
2 demonstrates how to use WebSocket in traditional ASP.NET and MVC 4 web
applications.
In this article, I will demonstrate how to use WCF to host a WebSocket service and write
a client application. I will also show how to communicate with a WCF WebSocket
service using JavaScript in a web
page.
- WcfWSChat.zip is the sample code of the WCF WebSocket service with a WCF client application.
- WcfWSChatForWeb.zip is the sample code of the WCF WebSocket service with a web page and JavaScript.
Preparation
To enable WebSocket on the server side, please refer to Part
1.
Host Service
In WCF 4.5, netHttpBinding
and netHttpsBinding
have been
re-implemented to support WebSocket. And to make a WCF service serve the client
through WebSocket, we should set CallbackContract
for the ServiceContract
attribute in
the service interface declaration. For example:
[ServiceContract(CallbackContract = typeof(IWSChatCallback))]
public interface IWSChatService
{
[OperationContract(IsOneWay = true)]
Task SendMessageToServer(string msg);
}
The SendMessageToServer
method will be called by the client and the msg
argument is the message sent by the user. So the server should
implement this method to handle the msg
received from the client. IsOneWay
should be set to true for the OperationContract
attribute because the client should not wait for the return value of the
service method call in duplex communication. CallbackContract
is bound to
IWSChatCallback
which is declared as below:
[ServiceContract]
interface IWSChatCallback
{
[OperationContract(IsOneWay = true)]
Task SendMessageToClient(string msg);
}
The SendMessageToClient
method will be called by the server and the msg
argument is the message sent by the server. So the client
should implement this method to handle the msg
received from the server. Again, IsOneWay
should be set to true because the server should not wait for the return value
of the callback method call in duplex communication.
Let’s simply implement IWSChatService
as an echo server:
public class WSChatService : IWSChatService
{
public async Task SendMessageToServer(string msg)
{
var callback = OperationContext.Current.GetCallbackChannel<IWSChatCallback>();
if (((IChannel)callback).State == CommunicationState.Opened)
{
await callback.SendMessageToClient(
string.Format("Got message {0} at {1}",
msg, DateTime.Now.ToLongTimeString()));
}
}
}
The service just decorates the message received from the
client and calls SendMessageToClient
to send the decorated message to the client.
Then we need to modify web.config
to add a protocol mapping schema in the <system.serviceModel>
tag like:
<protocolMapping>
<add scheme="http" binding="netHttpBinding"/>
</protocolMapping>
The default netHttpBinding
and netHttpsBinding
will transfer
data in Binary SOAP format. If you
want to use Text SOAP format, you
could create a customized configuration and set messageEncoding
to “Text
” like:
<protocolMapping>
<add scheme="http" binding="netHttpBinding" bindingConfiguration="textWSHttpBinding"/>
</protocolMapping>
<bindings>
<netHttpBinding>
<binding name="textWSHttpBinding" messageEncoding="Text"/>
</netHttpBinding>
</bindings>
OK. The WCF WebSocket service gets ready after I build it.
Client Application
To write a WCF client application, I create a console
application and add the previous WCF service to Service References. Then I implement the
IWSChatServiceCallback
interface declared previously on the server:
internal class CallbackClient : IWSChatServiceCallback
{
public void SendMessageToClient(string msg)
{
Console.WriteLine(msg);
}
}
In the SendMessageToClient
method, I just display the message received from the server.
The program’s main function looks like:
class Program
{
static void Main(string[] args)
{
var context = new InstanceContext(new CallbackClient());
var client = new WSChatServiceClient(context);
while (true)
{
Console.Write("Input (\"Exit\" to exit):");
string input = Console.ReadLine();
if (input.ToUpperInvariant() == "EXIT")
{
break;
}
client.SendMessageToServer(input);
Thread.Sleep(500);
}
client.Close();
}
}
Don’t forget to modify the endpoint address in App.config to point to the correct
machine name, domain name, or IP address.
Again to emphasize, the client application can only work on Windows 8, Windows Server 2012, and above.
JavaScript in Web Page
By using netHttpBinding
and netHttpsBinding
, all data
transferred in a WCF WebSocket connection is in SOAP format (Binary or Text). To communicate
with the WCF WebSocket service with JavaScript code in the web page, I have to parse
the SOAP by myself.
To avoid SOAP, I need to create and use a custom binding as
below:
<bindings>
<customBinding>
<binding name="textWSHttpBinding">
<byteStreamMessageEncoding/>
<httpTransport>
<webSocketSettings transportUsage="Always"
createNotificationOnConnection="true"/>
</httpTransport>
</binding>
</customBinding>
</bindings>
<protocolMapping>
<add scheme="http" binding="customBinding"
bindingConfiguration="textWSHttpBinding"/>
</protocolMapping>
I use byteStreamMessageEncoding
to specify the data should be transferred as byte stream instead of SOAP. In
webSocketSettings
,
transportUsage
is set to Always
to enable the WebSocket protocol regardless of the contract;
createNotificationOnConnection
must be set to “true
” to notify the client when connection is established.
The service interface and implementation need to change as well. I modified
IWSChatService
and IWSChatCallback
as below:
[ServiceContract(CallbackContract = typeof(IWSChatCallback))]
public interface IWSChatService
{
[OperationContract(IsOneWay = true, Action = "*")]
Task SendMessageToServer(Message msg);
}
[ServiceContract]
interface IWSChatCallback
{
[OperationContract(IsOneWay = true, Action="*")]
Task SendMessageToClient(Message msg);
}
I change the type of the msg
argument from string to
Message
(System.ServiceModel.Channels
) which will wrap my UTF-8 encoded text.
Then I re-implement IWSChatService
as
below:
public class WSChatService : IWSChatService
{
public async Task SendMessageToServer(Message msg)
{
var callback = OperationContext.Current.GetCallbackChannel<IWSChatCallback>();
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());
await callback.SendMessageToClient(
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;
}
}
The client side need not implement IWSChatCallback
. My JavaScript code is very similar
to the example
in Part
2:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>WebSocket Chat</title>
<script type="text/javascript" src="Scripts/jquery-2.0.2.js"></script>
<script type="text/javascript">
var ws;
$().ready(function () {
$("#btnConnect").click(function () {
$("#spanStatus").text("connecting");
ws = new WebSocket("ws://" + window.location.hostname
+ "/WcfWSChatForWeb/WSChatService.svc");
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) {
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>
Summary
Next in Part 4, I will demonstrate how to use Microsoft.WebSockets.dll.
Related Links
Using WebSocket in .NET 4.5: