web-dev-qa-db-fra.com

Utilisation de WebSockets avec l'API Web ASP.NET

Quelle est la méthode préférée pour utiliser des sockets Web brutes dans une application API Web ASP.NET?

Nous aimerions utiliser WebSockets binaires sur quelques-unes de nos interfaces de notre application API Web ASP.NET. J'ai du mal à déterminer comment cela doit être fait car il semble y avoir plusieurs implémentations conflictuelles et/ou obsolètes en ligne pour .NET.

Il y a des exemples qui semblent être ASP.NET comme ceci n , mais je pensez il doit y avoir un moyen d'utiliser des websockets dans le cadre de l'API Web. Comme je le sais, vous pouvez utiliser Signalr dans WebAPI.

Je pensé en utilisant Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler fonctionnerait, mais je ne sais pas comment lier le WebSocketHandler au contrôleur ...

class MyServiceController : ApiController
{
    [HttpGet]
    public HttpResponseMessage SwitchProtocols (string param)
    {
        HttpContext currentContext = HttpContext.Current;
        if (currentContext.IsWebSocketRequest || 
            currentContext.IsWebSocketRequestUpgrading)
        {
            // several out-dated(?) examples would 
            // use 'new MySocketHandler' for ???
            var unknown = new ????
            currentContext.AcceptWebSocketRequest(unknown); 
            return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
        }   
    }
}

class MySocketHandler : WebSocketHandler
{
    public MySocketHandler(): base(2048){}

    ...
}

Malheureusement, AcceptWebSocketRequest n'accepte plus un WebSocketHandler, au lieu de cela sa nouvelle signature est ...

public void AcceptWebSocketRequest(Func<AspNetWebSocketContext, Task> userFunc)

Quelqu'un a-t-il un lien ou un exemple rapide implémentant des sockets Web brutes dans l'application API Web ASP.NET qui est à jour?

20
Tony

PDATE: Après un peu plus de recherches par moi-même et un collègue, nous sommes arrivés à la conclusion que la classe WebSocketHandler ne semble pas être destinée à être utilisée en dehors des processus internes de SignalR. Comme il n'y a aucun moyen évident d'exploiter WebSocketHandler isolé de SignalR. C'est dommage car je trouve ses interfaces un peu plus haut niveau que les interfaces System.Web/System.Net. De plus, la méthode décrite ci-dessous utilise HttpContext que je pense devrait être évitée.

En tant que tel, nous prévoyons d'adopter une approche similaire à celle présentée par Mrchief, mais avec un peu plus de saveur API Web. Comme ceci ... (REMARQUE: notre socket est en écriture seule, mais je vous ai découvert DOIT effectuer des opérations de lecture si vous voulez que WebSocket.State soit correctement mis à jour .

class MyServiceController : ApiController
{
    public HttpResponseMessage Get (string param)
    {
        HttpContext currentContext = HttpContext.Current;
        if (currentContext.IsWebSocketRequest || 
            currentContext.IsWebSocketRequestUpgrading)
        {
            currentContext.AcceptWebSocketRequest(ProcessWebsocketSession); 
            return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
        }   
    }

    private async Task ProcessWebsocketSession(AspNetWebSocketContext context)
    {
        var ws = context.WebSocket;

        new Task(() =>
        {
            var inputSegment = new ArraySegment<byte>(new byte[1024]);

            while (true)
            {
                // MUST read if we want the state to get updated...
                var result = await ws.ReceiveAsync(inputSegment, CancellationToken.None);

                if (ws.State != WebSocketState.Open)
                {
                    break;
                }
            }
        }).Start();

        while (true)
        {
            if (ws.State != WebSocketState.Open)
            {
                break;
            }
            else
            {
                byte[] binaryData = { 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe };
                var segment = new ArraySegment<byte>(binaryData);
                await ws.SendAsync(segment, WebSocketMessageType.Binary, 
                    true, CancellationToken.None);
            }
        }
    }
}

REMARQUE: La vérification des erreurs et l'utilisation correcte d'un CancellationToken sont évidemment laissées à la discrétion du lecteur.

14
Tony

Il s'agit d'une question plus ancienne, mais j'aimerais ajouter une autre réponse à cela.
Il s'avère que vous POUVEZ l'utiliser, et je n'ai aucune idée pourquoi ils l'ont rendu si "caché". Ce serait bien si quelqu'un pouvait m'expliquer ce qui ne va pas avec ce cours, ou si ce que je fais ici est en quelque sorte "interdit" ou "mauvais design".

Si nous regardons dans le Microsoft.Web.WebSockets.WebSocketHandler, on retrouve cette méthode publique:

[EditorBrowsable(EditorBrowsableState.Never)]
public Task ProcessWebSocketRequestAsync(AspNetWebSocketContext webSocketContext);

Cette méthode est cachée à intellisense, mais elle est là et peut être appelée sans erreurs de compilation.
Nous pouvons utiliser cette méthode pour obtenir la tâche que nous devons retourner dans la méthode AcceptWebSocketRequest. Regarde ça:

public class MyWebSocketHandler : WebSocketHandler
{
    private static WebSocketCollection clients = new WebSocketCollection();

    public override void OnOpen()
    {
        clients.Add(this);
    }

    public override void OnMessage(string message)
    {
        Send("Echo: " + message);
    }
}

Et puis dans mon contrôleur API:

public class MessagingController : ApiController
{
    public HttpResponseMessage Get()
    {
        var currentContext = HttpContext.Current;
        if (currentContext.IsWebSocketRequest ||
            currentContext.IsWebSocketRequestUpgrading)
        {
            currentContext.AcceptWebSocketRequest(ProcessWebsocketSession);
        }

        return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
    }

    private Task ProcessWebsocketSession(AspNetWebSocketContext context)
    {
        var handler = new MyWebSocketHandler();
        var processTask = handler.ProcessWebSocketRequestAsync(context);
        return processTask;
    }
}

Cela fonctionne parfaitement. OnMessage est déclenché et fait écho à mon WebSocket instancié JavaScript ...

12
René Sackers

J'ai trouvé cela exemple :

Exemple de code (reproduit de l'article):

public class WSHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        if (context.IsWebSocketRequest)
        {
            context.AcceptWebSocketRequest(ProcessWSChat);
        }
    }

    public bool IsReusable { get { return false; } }

    private async Task ProcessWSChat(AspNetWebSocketContext context)
    {
        WebSocket socket = context.WebSocket;
        while (true)
        {
            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024]);
            WebSocketReceiveResult result = await socket.ReceiveAsync(
                buffer, CancellationToken.None);
            if (socket.State == WebSocketState.Open)
            {
                string userMessage = Encoding.UTF8.GetString(
                    buffer.Array, 0, result.Count);
                userMessage = "You sent: " + userMessage + " at " + 
                    DateTime.Now.ToLongTimeString();
                buffer = new ArraySegment<byte>(
                    Encoding.UTF8.GetBytes(userMessage));
                await socket.SendAsync(
                    buffer, WebSocketMessageType.Text, true, CancellationToken.None);
            }
            else
            {
                break;
            }
        }
    }
}
4
Mrchief

Partager mon code basé sur la réponse de Tony avec une gestion des tâches plus propre. Ce code envoie l'heure UTC actuelle environ toutes les secondes:

public class WsTimeController : ApiController
{
    [HttpGet]
    public HttpResponseMessage GetMessage()
    {
        var status = HttpStatusCode.BadRequest;
        var context = HttpContext.Current;
        if (context.IsWebSocketRequest)
        {
            context.AcceptWebSocketRequest(ProcessRequest);
            status = HttpStatusCode.SwitchingProtocols;

        }

        return new HttpResponseMessage(status);
    }

    private async Task ProcessRequest(AspNetWebSocketContext context)
    {
        var ws = context.WebSocket;
        await Task.WhenAll(WriteTask(ws), ReadTask(ws));
    }

    // MUST read if we want the socket state to be updated
    private async Task ReadTask(WebSocket ws)
    {
        var buffer = new ArraySegment<byte>(new byte[1024]);
        while (true)
        {
            await ws.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
            if (ws.State != WebSocketState.Open) break;
        }
    }

    private async Task WriteTask(WebSocket ws)
    {
        while (true)
        {
            var timeStr = DateTime.UtcNow.ToString("MMM dd yyyy HH:mm:ss.fff UTC", CultureInfo.InvariantCulture);
            var buffer = Encoding.UTF8.GetBytes(timeStr);
            if (ws.State != WebSocketState.Open) break;
            var sendTask = ws.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
            await sendTask.ConfigureAwait(false);
            if (ws.State != WebSocketState.Open) break;
            await Task.Delay(1000).ConfigureAwait(false); // this is NOT ideal
        }
    }
}
2
Ivan Krivyakov

Qu'en est-il de l'utilisation de SignalR 2?

  • Peut être installé via NuGet
  • Pour .NET 4.5+
  • Aucune boucle permanente requise
  • Diffusion possible - Tutoriel ici
0
Anytoe