65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET 4.5 中使用 WebSocket (第三部分)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (15投票s)

2013年7月11日

CPOL

4分钟阅读

viewsIcon

96318

downloadIcon

7082

在 WCF 服务中使用 WebSocket, 并与客户端应用程序或网页上的 JavaScript 进行通信

介绍 

第一部分 概述了 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 中,netHttpBindingnetHttpsBinding 已重新实现以支持 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> 

默认的 netHttpBindingnetHttpsBinding 将以 二进制 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 8Windows Server 2012 及更高版本上运行。

Web 页面中的 JavaScript

通过使用 netHttpBindingnetHttpsBinding,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” 以在连接建立时通知客户端。

服务接口和实现也需要相应更改。我修改了 IWSChatServiceIWSChatCallback,如下所示:

[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: 

© . All rights reserved.