使用JavaScript客户端的WCF自托管WebSocket服务

13
我可以帮忙翻译。以下是翻译的结果:

我有这个自托管的WCF WebSocket服务代码:

Main:

//Create a URI to serve as the base address
Uri httpUrl = new Uri("http://192.168.1.95:8080/service");
//Create ServiceHost
ServiceHost host = new ServiceHost(typeof(WebSocketService), httpUrl);            
//Add a service endpoint
host.AddServiceEndpoint(typeof(IWebSocket), new NetHttpBinding(), "");
//Enable metadata exchange
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);
//Start the Service
host.Open();

Console.WriteLine("Service is host at " + DateTime.Now.ToString());
Console.WriteLine("Host is running... Press <Enter> key to stop");
Console.ReadLine();

接口:

namespace IWebSocketHostTest
{
    [ServiceContract]
    interface IWebSocketCallBack
    {
        [OperationContract(IsOneWay = true)]
        void Send(int num);
    }

    [ServiceContract(CallbackContract = typeof(IWebSocketCallBack))]
    public interface IWebSocket
    {
        [OperationContract]
        void StartSend();
    }
}

服务:

namespace IWebSocketHostTest
{

class WebSocketService : IWebSocket
{
    Timer timer = null;

    List<IWebSocketCallBack> callbackClientList = null;        

    public WebSocketService()
    {
        callbackClientList = new List<IWebSocketCallBack>();

        timer = new Timer(3000);
        timer.Elapsed += new ElapsedEventHandler(sendNumber);
        timer.Start();
    }

    public void StartSend()
    {
        sender.addClient(OperationContext.Current.GetCallbackChannel<IWebSocketCallBack>());            
    }

    private void sendNumber(Object o, ElapsedEventArgs eea)
    {
        timer.Stop();
        var random = new Random();
        int randomNum = random.Next(100);
        foreach (IWebSocketCallBack callback in callbackClientList)
        {
            callback.Send(randomNum);
        }

        timer.Interval = random.Next(1000, 10000);
        timer.Start();
    }

}
}

如果我在另一个.NET应用程序中添加此服务的引用,它就可以完美运行。 但是,我需要从一个HTML + Javascript应用程序中使用此服务,并且我真的不知道如何做到这一点。我无法找到一个好的示例或教程,其中包含使用自托管的WCF WebSocket服务的Javascript客户端。 我能找到的所有Javascript WebSocket代码似乎都非常简单,但我无法使其正常工作。
以下是我的简短JavaScript客户端测试:
var ws = new WebSocket("ws://192.168.1.95:8080/service");
            ws.onopen = function () {
                console.log("WEBSOCKET CONNECTED");
            };

当我使用Fiddler测试时,它返回“WebSocket错误:HTTP响应不正确。状态码400,错误请求”。

我错过了什么?你能否给我一些文档链接以获取更多信息或代码示例?

谢谢!

编辑:

现在我尝试使用“Microsoft.ServiceModel.WebSocket”库来使其工作。

但是,首先,我不知道它是否仍由Microsoft维护或已被弃用,因为我在MSDN上找不到任何信息,而且互联网上的信息很少。

其次,“WebSocketHost”类的“Open()”方法未找到,因此我不知道如何运行服务器...

这是我的代码,我从ASP.NET论坛的问题中获取了它。

using System;
using Microsoft.ServiceModel.WebSockets;

namespace WebSocketTest
{
class Program
{
    static void Main(string[] args)
    {
        var host = new WebSocketHost<EchoService>(new Uri("ws://localhost:8080/echo"));
        host.AddWebSocketEndpoint();
        host.Open();

        Console.Read();

        host.Close();
    }
}

class EchoService : WebSocketService
{

    public override void OnOpen()
    {
        base.OnOpen();
        Console.WriteLine("WebSocket opened.");
    }

    public override void OnMessage(string message)
    {
        Console.WriteLine("Echoing to client:");
        Console.WriteLine(message);

        this.Send(message);
    }

    protected override void OnClose()
    {
        base.OnClose();
        Console.WriteLine("WebSocket closed.");
    }

    protected override void OnError()
    {
        base.OnError();
        Console.WriteLine("WebSocket error occured.");
    }
}
}

但是,就像我之前说的那样,“host.Open()”方法找不到,所以我不知道是否缺少某些引用或其他东西,因为我找不到关于WebSocketHost类的信息...有什么帮助吗?


请查看此链接:http://code.msdn.microsoft.com/WCF-WebSocket-Listener-3adf403d - vtortola
我可以问一下,为什么你想要使用WCF作为WebSocket服务器?只是好奇... :) - vtortola
2
因为我的想法是在客户端与服务器在同一台机器上时使用命名管道进行内部通信,而在客户端在外部时使用WebSockets(需要全双工),但如果有更好的选择,我会倾听。顺便说一下,我考虑使用SignalR,但我还没有时间阅读足够的文档,也没有进行任何测试。 - MorgoZ
你使用的操作系统是什么? - vtortola
Windows 8.1 谢谢您提供的链接,对我很有帮助。但是我的Microsoft.ServiceModel.WebSockets(0.2.3版本)库中没有“WebSocketHost”的“Open()”方法,所以这个例子对我来说没用。 - MorgoZ
抱歉啊。如果你无法让它工作,可以看一下我的WebSocket连接器:http://vtortola.github.io/WebSocketListener/ ,至少它会工作 :D。不管怎样,如果你成功让 Microsoft.ServiceModel.WebSockets 工作了,请回复这篇文章并分享答案,因为这很有趣。 - vtortola
1个回答

31

在花费一整天时间完成同一个任务后,我终于得到了一个可行的解决方案。希望将来能帮助到有需要的人。

客户端JS脚本:

<!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>

自托管服务器:

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;
        }
    }
}

更新

从.NET 4.5开始,出现了一种新的服务器端编写方式。这种方式的好处是代码更加清晰,同时还可以支持通过https进行安全Web套接字(WSS)。

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
}  

好的。小巧、简单、易于实现。谢谢! - ojek
完美!但我有一个问题。如何将身份验证设置更改为Windows身份验证。我正在从MyWebSite连接到WebSocket服务器。MyWebsite的身份验证模式是Windows。但是当我更改了tranport.AuthenticationSchema = AuthenticationSchema.NTLM时,我无法连接到websocket。请帮忙。 - TC Tarım Köy İşleri
我认为这是不可能的。关于身份验证的更多信息,您可以查看此问题:https://dev59.com/xWEh5IYBdhLWcg3wRRuh - Amid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接