带有 Javascript 客户端的 WCF 自托管 WebSocket 服务 [英] WCF self-hosted WebSocket Service with Javascript client

查看:22
本文介绍了带有 Javascript 客户端的 WCF 自托管 WebSocket 服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have this WCF self-hosted WebSocket service code:

主要内容:

//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 代码似乎都很简单,但我无法让它工作.

This works perfect if i add a reference of this service in another .NET application. But, what i need is to consume this service from an HTML+Javascript application, and i´m realy lost in how to do that. I couldn´t find a good example or tutorial with a Javascript client consuming a self-hosted WCF WebSocket service. All the Javascript WebSocket code that i could find seems to be very simple, but i couldn´t make it work.

这是我的简短 JavaScript 客户端测试:

Here is my short JavaScript client test:

var ws = new WebSocket("ws://192.168.1.95:8080/service");
            ws.onopen = function () {
                console.log("WEBSOCKET CONNECTED");
            };

它返回WebSocket 错误:错误的 HTTP 响应.状态代码 400,错误请求"用 Fiddler 测试它.

it returns "WebSocket Error: Incorrect HTTP response. Status code 400, Bad Request" testing it with Fiddler.

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

What am i missing? Could you please give me some doc links to get more information or a code example?

谢谢!

现在我已经尝试使用Microsoft.ServiceModel.WebSocket"库来让它工作.

但是,首先,我不知道它是否仍然由 Microsoft 维护或是否已弃用,因为我在 MSDN 上找不到任何信息,而且互联网上的信息很少.其次,没有找到WebSocketHost"类的Open()"方法,所以我不知道如何让服务器运行...

But, first, i don´t know if it´s still maintained by Microsoft or if it is deprecated, because i couldn´t find any information at MSDN and there is few info at internet. And second, the "Open()" method of the "WebSocketHost" class is not found, so i don´t know how to make the server run...

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

Here is my code, i´ve taken it from a question at the ASP.NET forum.

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 类的信息...有什么帮助吗?

But, like i said before, the "host.Open()" method is not found, so i don´t know if i´m missing some reference or what, because i couldn´t find info about the WebSocketHost class... Any help?

推荐答案

经过一天的相同任务,我终于找到了可行的解决方案.希望它会在未来对某人有所帮助.

After spending a day with the same task I finally got working solution. Hope it will help someone in the future.

客户端 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 支持安全网络套接字 (WSS).

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
}  

这篇关于带有 Javascript 客户端的 WCF 自托管 WebSocket 服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆