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

时间:2014-06-16 08:59:54

标签: c# javascript wcf websocket self-hosting

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

主要

//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");
            };

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

我缺少什么?您能否给我一些文档链接以获取更多信息或代码示例?

谢谢!

修改

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

但是,首先,我不知道它是否仍由微软维护或是否已被弃用,因为我无法在MSDN上找到任何信息并且互联网上的信息很少。 第二,找不到“WebSocketHost”类的“Open()”方法,所以我不知道如何让服务器运行......

这是我的代码,我是从ASP.NET论坛的question获取的。

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

1 个答案:

答案 0 :(得分:29)

在完成相同任务的一天后,我终于得到了解决方案。希望它能帮助将来的某个人。

客户端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
}