在 .NET 4.5 中使用 WebSocket (第三部分)
在 WCF 服务中使用 WebSocket,
介绍
第一部分 概述了 WebSocket 协议和 .NET 对 WebSocket 的支持。 第二部分 演示了如何在传统的 ASP.NET 和 MVC 4 Web 应用程序中使用 WebSocket。
在本文中,我将演示如何使用 WCF 来托管 WebSocket 服务并编写一个客户端应用程序。我还会展示如何使用 Web 页面中的 JavaScript 与 WCF WebSocket 服务进行通信。
- WcfWSChat.zip 是包含 WCF WebSocket 服务和 WCF 客户端应用程序的示例代码。
- WcfWSChatForWeb.zip 是包含 WCF WebSocket 服务、Web 页面和 JavaScript 的示例代码。
准备
要启用服务器端的 WebSocket,请参阅 第一部分。
托管服务
在 WCF 4.5 中,netHttpBinding
和 netHttpsBinding
已重新实现以支持 WebSocket。要使 WCF 服务通过 WebSocket 为客户端提供服务,我们应该在服务接口声明中的 ServiceContract
特性上设置 CallbackContract
。例如:
[ServiceContract(CallbackContract = typeof(IWSChatCallback))]
public interface IWSChatService
{
[OperationContract(IsOneWay = true)]
Task SendMessageToServer(string msg);
}
SendMessageToServer
方法将由客户端调用,msg
参数是用户发送的消息。因此,服务器应实现此方法来处理从客户端接收到的 msg
。对于 OperationContract
特性,IsOneWay
应设置为 true,因为在双工通信中,客户端不应等待服务方法调用的返回值。CallbackContract
绑定到 IWSChatCallback
,它声明如下:
[ServiceContract]
interface IWSChatCallback
{
[OperationContract(IsOneWay = true)]
Task SendMessageToClient(string msg);
}
SendMessageToClient
方法将由服务器调用,msg
参数是服务器发送的消息。因此,客户端应实现此方法来处理从服务器接收到的 msg
。同样,IsOneWay
应设置为 true,因为在双工通信中,服务器不应等待回调方法调用的返回值。
我们来简单实现 IWSChatService
作为回声服务器:
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()));
}
}
}
该服务只是对从客户端接收到的消息进行装饰,然后调用 SendMessageToClient
将装饰后的消息发送给客户端。
然后,我们需要修改 web.config,在 <system.serviceModel>
标记中添加一个协议映射架构,如下所示:
<protocolMapping>
<add scheme="http" binding="netHttpBinding"/>
</protocolMapping>
默认的 netHttpBinding
和 netHttpsBinding
将以 二进制 SOAP 格式传输数据。如果您想使用 文本 SOAP 格式,可以创建一个自定义配置并将 messageEncoding
设置为 “Text
”,如下所示:
<protocolMapping>
<add scheme="http" binding="netHttpBinding" bindingConfiguration="textWSHttpBinding"/>
</protocolMapping>
<bindings>
<netHttpBinding>
<binding name="textWSHttpBinding" messageEncoding="Text"/>
</netHttpBinding>
</bindings>
好的。WCF WebSocket 服务构建完成后就可以准备就绪了。
客户端应用程序
要编写 WCF 客户端应用程序,我创建了一个控制台应用程序,并将之前的 WCF 服务添加到 服务引用 (Service References) 中。然后,我实现了服务器上之前声明的 IWSChatServiceCallback
接口:
internal class CallbackClient : IWSChatServiceCallback
{
public void SendMessageToClient(string msg)
{
Console.WriteLine(msg);
}
}
在 SendMessageToClient
方法中,我只显示从服务器接收到的消息。
程序的 main 函数如下所示:
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();
}
}
别忘了修改 App.config 中的终结点地址,使其指向正确的计算机名、域名或 IP 地址。
再次强调,客户端应用程序只能在 Windows 8、Windows Server 2012 及更高版本上运行。
Web 页面中的 JavaScript
通过使用 netHttpBinding
和 netHttpsBinding
,WCF WebSocket 连接中传输的所有数据都是 SOAP 格式(二进制或文本)。要通过 Web 页面中的 JavaScript 代码与 WCF WebSocket 服务进行通信,我必须自己解析 SOAP。
为了避免 SOAP,我需要创建一个自定义绑定并使用它,如下所示:
<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>
我使用 byteStreamMessageEncoding
来指定数据应作为字节流传输而不是 SOAP。在 webSocketSettings
中,transportUsage
设置为 Always
,以便无论合同如何都启用 WebSocket 协议;createNotificationOnConnection
必须设置为 “true
” 以在连接建立时通知客户端。
服务接口和实现也需要相应更改。我修改了 IWSChatService
和 IWSChatCallback
,如下所示:
[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);
}
我将 msg
参数的类型从 string 更改为 Message
(System.ServiceModel.Channels
),它将包装我的 UTF-8 编码文本。然后,我重新实现了 IWSChatService
,如下所示:
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;
}
}
客户端无需实现 IWSChatCallback
。我的 JavaScript 代码与 第二部分 中的示例非常相似:
<!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>
摘要
在 第四部分 中,我将演示如何使用 Microsoft.WebSockets.dll。
相关链接
在 .NET 4.5 中使用 WebSocket: