c# - visual - WCF alojado en el servicio WebSocket con cliente de Javascript
websocket visual studio (1)
Tengo este código de servicio WebSocket alojado en WCF:
Principal:
//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();
Interfaz:
namespace IWebSocketHostTest
{
[ServiceContract]
interface IWebSocketCallBack
{
[OperationContract(IsOneWay = true)]
void Send(int num);
}
[ServiceContract(CallbackContract = typeof(IWebSocketCallBack))]
public interface IWebSocket
{
[OperationContract]
void StartSend();
}
}
Servicio:
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();
}
}
}
Esto funciona perfecto si agrego una referencia de este servicio en otra aplicación .NET. Pero lo que necesito es consumir este servicio desde una aplicación HTML + Javascript, y estoy realmente perdido en cómo hacerlo. No pude encontrar un buen ejemplo o tutorial con un cliente de Javascript que consuma un servicio alojado en WCF WebSocket. Todo el código Javascript WebSocket que pude encontrar parece ser muy simple, pero no pude hacerlo funcionar.
Aquí está mi prueba corta de cliente de JavaScript:
var ws = new WebSocket("ws://192.168.1.95:8080/service");
ws.onopen = function () {
console.log("WEBSOCKET CONNECTED");
};
devuelve "Error de WebSocket: respuesta HTTP incorrecta. Código de estado 400, solicitud incorrecta" probándolo con Fiddler.
¿Qué me estoy perdiendo? ¿Podría darme algunos enlaces de documentos para obtener más información o un ejemplo de código?
¡Gracias!
EDITAR:
Ahora he intentado utilizar la biblioteca "Microsoft.ServiceModel.WebSocket" para intentar que funcione.
Pero, en primer lugar, no sé si todavía lo mantiene Microsoft o si está obsoleto, porque no pude encontrar información en MSDN y hay poca información en Internet. Y segundo, el método "Open ()" de la clase "WebSocketHost" no se encuentra, por lo que no sé cómo hacer que el servidor se ejecute ...
Aquí está mi código, lo tomé de una pregunta en el foro de 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.");
}
}
}
Pero, como dije antes, el método "host.Open ()" no se encuentra, entonces no sé si me falta alguna referencia o qué, porque no pude encontrar información sobre la clase WebSocketHost ... ¿Alguna ayuda?
Después de pasar un día con la misma tarea, finalmente obtuve una solución de trabajo. Espero que ayude a alguien en el futuro.
Script de cliente 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>
Servidor autohospedado:
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;
}
}
}
ACTUALIZAR
A partir de .NET 4.5, surgió una nueva forma de escribir el lado del servidor. Los beneficios son un código más limpio y la posibilidad de admitir sockets web seguros (WSS) sobre 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
}