WebSockets-Unterstützung in ASP.NET Core

In diesem Artikel erfahren Sie, wie Sie mit WebSockets in ASP.NET beginnen. Bei WebSockets (RFC 6455) handelt es sich um ein Protokoll, das bidirektionale persistente Kommunikationskanäle über TCP-Verbindungen ermöglicht. Es wird in Apps verwendet, die von schneller Kommunikation in Echtzeit profitieren, z.B. Chats, Dashboards und Spiele-Apps.

Beispielcode anzeigen oder herunterladen (Vorgehensweise zum Herunterladen, Vorgehensweise zum Ausführen).

HTTP/2-Unterstützung für WebSocket

Wenn Sie WebSocket über HTTP/2 verwenden, können Sie die folgenden neuen Features nutzen:

  • Headerkomprimierung
  • Multiplexing, was die Zeit und Ressourcen reduziert, die beim Erstellen mehrerer Anforderungen an den Server erforderlich sind

Diese unterstützten Features sind in Kestrel für alle aktivierten HTTP/2-Plattformen verfügbar. Die Versionsaushandlung findet in Browsern und in Kestrel automatisch statt, sodass keine neuen APIs benötigt werden.

.NET 7 hat WebSocket über die HTTP/2-Unterstützung für Kestrel, den SignalR-JavaScript-Client und SignalR mit Blazor WebAssembly eingeführt.

Hinweis

WebSocket mit HTTP/2 verwendet CONNECT-Anforderungen statt GET, sodass Ihre eigenen Routen und Controller möglicherweise aktualisiert werden müssen. Weitere Informationen finden Sie unter Hinzufügen der HTTP/2-Unterstützung für WebSocket für vorhandene Controller in diesem Artikel.

Bei Chrome und Edge ist WebSocket mit HTTP/2 standardmäßig aktiviert, und in FireFox können Sie dies auf der about:config-Seite mit dem network.http.spdy.websockets-Flag aktivieren.

WebSocket wurde ursprünglich für HTTP/1.1 entwickelt, wurden seitdem jedoch angepasst, um über HTTP/2 zu laufen. (RFC 8441)

SignalR

ASP.NET Core SignalR ist eine Bibliothek, die das Hinzufügen von Echtzeit-Webfunktionalität zu Apps erleichtert. Sie verwendet wenn möglich immer WebSockets.

Für die meisten Anwendungen wird SignalR anstelle von RAW-WebSocket empfohlen. SignalR:

  • Stellt ein Transportfallback für Umgebungen bereit, in denen WebSocket nicht verfügbar ist
  • Bietet ein einfaches App-Modell für Remoteprozeduraufrufe
  • Hat in den meisten Szenarios hat keinen signifikanten Leistungsnachteil gegenüber der Verwendung von RAW-WebSocket

WebSocket über HTTP/2 wird für Folgendes unterstützt:

  • SignalR-JavaScript-Client von ASP.NET Core
  • SignalR von ASP.NET Core mit Blazor WebAssembly

Für einige Apps stellt gRPC in .NET eine Alternative zu WebSockets dar.

Voraussetzungen

  • Jedes Betriebssystem, das ASP.NET Core unterstützt:
    • Windows 7/Windows Server 2008 und höher
    • Linux
    • macOS
  • Wenn die App unter Windows mit IIS ausgeführt wird:
  • Wenn die App unter HTTP.sys ausgeführt wird:
    • Windows 8/Windows Server 2012 und höher
  • Weitere Informationen zu unterstützten Browsern finden Sie unter Can I use?.

Konfigurieren der Middleware

Fügen Sie die WebSockets-Middleware in Program.cs hinzu:

app.UseWebSockets();

Sie können die folgenden Einstellungen konfigurieren:

  • KeepAliveInterval: Legt fest, wie oft Ping-Frames an den Client gesendet werden, um sicherzustellen, dass Proxys die Verbindung aufrechterhalten Der Standardwert beträgt zwei Minuten.
  • AllowedOrigins – Eine Liste der zulässigen Origin-Headerwerte für WebSocket-Anforderungen. Alle Ursprünge sind standardmäßig zulässig. Weitere Informationen finden Sie unter WebSocket-Ursprungseinschränkung in diesem Artikel.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Akzeptieren der Anforderungen von WebSocket

Prüfen Sie zu einem späteren Zeitpunkt im Lebenszyklus einer Anforderung (z. B. später in Program.cs oder einer Aktionsmethode), ob es sich um eine WebSocket-Anforderung handelt, und akzeptieren Sie diese.

Das folgende Beispiel stammt aus dem Abschnitt Program.cs weiter unten:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

WebSocket-Anforderungen können bei jeder URL eingehen. Dieser Beispielcode akzeptiert jedoch nur Anforderungen für /ws.

Ein ähnlicher Ansatz kann in einer Controllermethode verwendet werden:

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

Bei Verwendung eines WebSockets muss die Middlewarepipeline während der gesamten Dauer der Verbindung ausgeführt werden. Wenn Sie versuchen, eine WebSocket-Nachricht zu senden oder zu empfangen, nachdem die Middlewarepipeline beendet wurde, erhalten Sie möglicherweise eine Ausnahme wie die folgende:

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Wenn Sie einen Hintergrunddienst zum Schreiben von Daten an ein WebSocket verwenden, stellen Sie sicher, dass die Middlewarepipeline dauerhaft ausgeführt wird. Verwenden Sie dafür ein TaskCompletionSource<TResult>. Übergeben Sie die TaskCompletionSource an Ihren Hintergrunddienst, und lassen Sie sie TrySetResult aufrufen, wenn Sie das WebSocket beenden. Verwenden Sie dann await für die Task-Eigenschaft während der Anforderung, wie im folgenden Beispiel gezeigt:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Die Ausnahme „WebSocket closed“ kann auch auftreten, wenn die Rückkehr von einer Aktionsmethode zu früh erfolgt. Wenn Sie einen Socket in einer Aktionsmethode akzeptieren, warten Sie vor dem Zurückkehren von der Aktionsmethode, bis der Code, der den Socket verwendet, abgeschlossen ist.

Verwenden Sie nie Task.Wait, Task.Result oder ähnliche Blockierungsaufrufe, um auf den Abschluss des Sockets zu warten, da dies zu schwerwiegenden Threadingproblemen führen kann. Verwenden Sie immer await.

Hinzufügen von HTTP/2-Unterstützung für WebSocket für vorhandene Controller

.NET 7 hat WebSocket über die HTTP/2-Unterstützung für Kestrel, den SignalR-JavaScript-Client und SignalR mit Blazor WebAssembly eingeführt. WebSocket über HTTP/2 verwendet CONNECT-Anforderungen anstelle von GET. Wenn Sie zuvor in der Aktionsmethode des Controllers für Websocket-Anforderungen [HttpGet("/path")] verwendet haben, aktualisieren Sie sie stattdessen so, dass [Route("/path")] verwendet wird.

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

Komprimierung

Warnung

Das Aktivieren der Komprimierung über verschlüsselte Verbindungen kann dazu führen, dass eine App CRIME/BREACH Angriffen ausgesetzt ist. Wenn Sie vertrauliche Informationen senden, sollten Sie die Komprimierung nicht aktivieren oder beim Aufrufen von WebSocket.SendAsyncWebSocketMessageFlags.DisableCompression verwenden. Dies gilt für beide WebSockets-Seiten. Beachten Sie, dass die WebSockets-API im Browser nicht über eine Konfiguration zum Deaktivieren der Komprimierung beim Senden verfügt.

Wenn eine Komprimierung von Nachrichten über WebSockets gewünscht ist, muss der Akzeptierungscode angeben, dass er die Komprimierung wie folgt zulässt:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits und WebSocketAcceptContext.DisableServerContextTakeover sind erweiterte Optionen, die die Funktionsweise der Komprimierung steuern.

Die Komprimierung wird zwischen Client und Server ausgehandelt, wenn zum ersten Mal eine Verbindung hergestellt wird. Weitere Informationen zur Aushandlung finden Sie unter Compression Extensions for WebSocket RFC (Komprimierungserweiterungen für WebSocket RFC).

Hinweis

Wenn die Komprimierungsaushandlung weder vom Server noch vom Client akzeptiert wird, wird die Verbindung dennoch hergestellt. Die Verbindung verwendet jedoch keine Komprimierung beim Senden und Empfangen von Nachrichten.

Senden und Empfangen von Nachrichten

Die AcceptWebSocketAsync-Methode ändert die TCP-Verbindung in eine WebSocket-Verbindung und stellt ein WebSocket-Objekt bereit. Verwenden Sie das WebSocket-Objekt, um Nachrichten zu senden und zu empfangen.

Der weiter oben gezeigte Code, der WebSocket-Anforderungen akzeptiert, übergibt das WebSocket-Objekt an eine Echo-Methode. Der Code empfängt eine Nachricht und sendet diese umgehend wieder zurück. Nachrichten werden in einer Schleife gesendet und empfangen, bis der Client die Verbindung schließt:

private static async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

        receiveResult = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None);
    }

    await webSocket.CloseAsync(
        receiveResult.CloseStatus.Value,
        receiveResult.CloseStatusDescription,
        CancellationToken.None);
}

Wenn Sie die WebSocket-Verbindung vor Beginn der Schleife akzeptieren, endet die Middlewarepipeline. Wenn Sie das Socket schließen, wird die Pipeline entladen. Das bedeutet, dass sich die Anforderung in der Pipeline nicht mehr weiter bewegt, wenn der WebSocket akzeptiert wird. Wenn die Schleife beendet und der Socket geschlossen ist, wird die Anforderung in der Pipeline weiter verarbeitet.

Behandeln der Trennung der Verbindung mit dem Client

Der Server wird nicht automatisch informiert, wenn die Verbindung mit dem Client wegen Konnektivitätsverlust getrennt wird. Der Server empfängt nur dann eine Trennungsnachricht, wenn der Client sie sendet, was bei Verlust der Verbindung nicht möglich ist. Wenn Sie Maßnahmen ergreifen möchten, wenn dies der Fall ist, legen Sie ein Timeout fest für den Fall, dass innerhalb eines bestimmten Zeitfensters keine Eingabe vom Client empfangen wird.

Wenn der Client nicht immer Nachrichten sendet und Sie kein Timeout festlegen möchten, nur weil die Verbindung in den Leerlauf übergeht, lassen Sie den Client einen Timer verwenden, um alle X Sekunden eine Ping-Nachricht zu senden. Wenn auf dem Server nicht innerhalb von 2*X Sekunden nach einer Nachricht die nächste eingeht, beenden Sie die Verbindung, und melden Sie, dass der Client die Verbindung getrennt hat. Warten Sie für den doppelten Zeitraum des erwarteten Zeitintervalls, um zusätzliche Zeit für Netzwerkverzögerungen einzuräumen, die die Ping-Nachricht aufhalten könnten.

Beschränkung von WebSocket-Ursprüngen

Der von CORS erzeugte Schutz gilt nicht für WebSockets. Für Browser gilt Folgendes nicht:

  • Ausführen von CORS-Preflightanforderungen
  • Berücksichtigen der Einschränkungen, die in den Access-Control-Headern bei der Erstellung von WebSocket-Anforderungen angegeben sind

Allerdings senden Browser den Origin-Header, wenn die WebSocket-Anforderungen ausgegeben werden. Anwendungen sollten zur Überprüfung dieser Header konfiguriert werden, um sicherzustellen, dass nur WebSockets von den erwarteten Ursprüngen zulässig sind.

Wenn Sie Ihren Server unter „https://server.com"“ und Ihren Client unter „https://client.com"“ hosten, fügen Sie „https://client.com"“ zur Liste AllowedOrigins hinzu, damit sie von WebSockets überprüft wird.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Hinweis

Der Origin-Header wird vom Client gesteuert und kann wie der Referer-Header überlistet werden. Verwenden Sie diese Header nicht als Authentifizierungsmechanismus.

Unterstützung für IIS und IIS Express

Windows Server 2012 oder höher sowie Windows 8 oder höher mit IIS 8 oder IIS Express 8 oder höher verfügt über eine Unterstützung für das WebSocket-Protokoll, jedoch nicht für WebSocket über HTTP/2.

Hinweis

WebSockets sind immer aktiviert, wenn Sie IIS Express verwenden.

Aktivieren von WebSockets in IIS

So aktivieren Sie die Unterstützung für das WebSocket-Protokoll unter Windows Server 2012 oder höher:

Hinweis

Diese Schritte sind nicht erforderlich, wenn Sie IIS Express verwenden.

  1. Verwenden Sie den Assistenten Rollen und Features hinzufügen im Menü Verwalten oder den Link in Server-Manager.
  2. Klicken Sie auf Rollenbasierte oder featurebasierte Installation. Klicken Sie auf Weiter.
  3. Wählen Sie den entsprechenden Server aus (standardmäßig ist der lokale Server ausgewählt). Klicken Sie auf Weiter.
  4. Erweitern Sie Webserver (IIS) in der Struktur Rollen, und erweitern Sie Webserver und anschließend Anwendungsentwicklung.
  5. Wählen Sie WebSocket-Protokoll aus. Klicken Sie auf Weiter.
  6. Wenn keine zusätzlichen Features erforderlich sind, klicken Sie auf Weiter.
  7. Klicken Sie auf Installieren.
  8. Wenn die Installation abgeschlossen ist, klicken Sie auf Schließen, um den Assistenten zu beenden.

So aktivieren Sie die Unterstützung für das WebSocket-Protokoll unter Windows 8 oder höher:

Hinweis

Diese Schritte sind nicht erforderlich, wenn Sie IIS Express verwenden.

  1. Navigieren Sie zu Systemsteuerung>Programme>Programme und Features>Windows-Features aktivieren oder deaktivieren (links auf dem Bildschirm).
  2. Öffnen Sie die folgenden Knoten: Internetinformationsdienste>WWW-Dienste>Anwendungsentwicklungsfeatures.
  3. Wählen Sie das Feature WebSocket-Protokoll aus. Klicken Sie auf OK.

Deaktivieren von WebSocket bei Verwendung von „socket.io“ in „Node.js“

Wenn Sie die WebSocket-Unterstützung in socket.io in Node.js verwenden, deaktivieren Sie das IIS-WebSocket-Standardmodul mithilfe des webSocket-Elements in web.config oder applicationHost.config. Wenn dieser Schritt nicht durchgeführt wird, versucht das IIS-WebSocket-Modul, die WebSocket-Kommunikation statt Node.js und der App zu verarbeiten.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Beispiel-App

Die Beispiel-App, die in diesem Artikel verwendet wird, ist eine Echo-App. Sie verfügt über eine Webseite, die WebSocket-Verbindungen herstellt. Der Server schickt alle empfangenen Nachrichten zurück an den Client. Die Beispiel-App unterstützt WebSocket über HTTP/2 beim Verwenden eines Zielframeworks der Version .NET 7 oder höher.

Führen Sie die App aus:

  • So führen Sie die App in Visual Studio aus: Öffnen Sie das Beispielprojekt in Visual Studio, und drücken Sie STRG+F5, um die App ohne Debugger auszuführen.
  • So führen Sie die App in einer Befehlsshell aus: Führen Sie den Befehl dotnet run aus, und navigieren Sie in einem Browser zu http://localhost:<port>.

Auf der Webseite wird der Verbindungsstatus angezeigt:

Initial state of webpage before WebSockets connection

Klicken Sie auf Connect (Verbinden), um eine WebSocket-Anforderung an die gezeigte URL zu senden. Geben Sie einen Testtext ein, und klicken Sie auf Send (Senden). Wenn dies abgeschlossen ist, klicken Sie auf Close Socket (Socket schließen). Der Abschnitt Kommunikationsprotokoll meldet jede open-, send- und close-Aktion, wenn diese durchgeführt wird.

Final state of webpage after WebSockets connection and test messages are sent and received

In diesem Artikel erfahren Sie, wie Sie mit WebSockets in ASP.NET beginnen. Bei WebSockets (RFC 6455) handelt es sich um ein Protokoll, das bidirektionale persistente Kommunikationskanäle über TCP-Verbindungen ermöglicht. Es wird in Apps verwendet, die von schneller Kommunikation in Echtzeit profitieren, z.B. Chats, Dashboards und Spiele-Apps.

Beispielcode anzeigen oder herunterladen (Vorgehensweise zum Herunterladen, Vorgehensweise zum Ausführen).

SignalR

ASP.NET Core SignalR ist eine Bibliothek, die das Hinzufügen von Echtzeit-Webfunktionalität zu Apps erleichtert. Sie verwendet wenn möglich immer WebSockets.

Für die meisten Anwendungen empfehlen wir SignalR über RAW-WebSockets. SignalR stellt ein Transportfallback für Umgebungen bereit, in denen WebSockets nicht verfügbar ist. Darüber hinaus bietet es ein einfaches App-Modell für Remoteprozeduraufrufe. In den meisten Szenarien hat SignalR außerdem keinen signifikanten Leistungsnachteil gegenüber der Verwendung von RAW-WebSockets.

Für einige Apps stellt gRPC in .NET eine Alternative zu WebSockets dar.

Voraussetzungen

  • Jedes Betriebssystem, das ASP.NET Core unterstützt:
    • Windows 7/Windows Server 2008 und höher
    • Linux
    • macOS
  • Wenn die App unter Windows mit IIS ausgeführt wird:
  • Wenn die App unter HTTP.sys ausgeführt wird:
    • Windows 8/Windows Server 2012 und höher
  • Weitere Informationen zu unterstützten Browsern finden Sie unter Can I use?.

Konfigurieren der Middleware

Fügen Sie die WebSockets-Middleware in Program.cs hinzu:

app.UseWebSockets();

Sie können die folgenden Einstellungen konfigurieren:

  • KeepAliveInterval: Legt fest, wie oft Ping-Frames an den Client gesendet werden, um sicherzustellen, dass Proxys die Verbindung aufrechterhalten Der Standardwert beträgt zwei Minuten.
  • AllowedOrigins – Eine Liste der zulässigen Origin-Headerwerte für WebSocket-Anforderungen. Alle Ursprünge sind standardmäßig zulässig. Weitere Informationen finden Sie unter WebSocket-Ursprungseinschränkung in diesem Artikel.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Akzeptieren der Anforderungen von WebSocket

Prüfen Sie zu einem späteren Zeitpunkt im Lebenszyklus einer Anforderung (z. B. später in Program.cs oder einer Aktionsmethode), ob es sich um eine WebSocket-Anforderung handelt, und akzeptieren Sie diese.

Das folgende Beispiel stammt aus dem Abschnitt Program.cs weiter unten:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

WebSocket-Anforderungen können bei jeder URL eingehen. Dieser Beispielcode akzeptiert jedoch nur Anforderungen für /ws.

Ein ähnlicher Ansatz kann in einer Controllermethode verwendet werden:

public class WebSocketController : ControllerBase
{
    [HttpGet("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

Bei Verwendung eines WebSockets muss die Middlewarepipeline während der gesamten Dauer der Verbindung ausgeführt werden. Wenn Sie versuchen, eine WebSocket-Nachricht zu senden oder zu empfangen, nachdem die Middlewarepipeline beendet wurde, erhalten Sie möglicherweise eine Ausnahme wie die folgende:

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Wenn Sie einen Hintergrunddienst zum Schreiben von Daten an ein WebSocket verwenden, stellen Sie sicher, dass die Middlewarepipeline dauerhaft ausgeführt wird. Verwenden Sie dafür ein TaskCompletionSource<TResult>. Übergeben Sie die TaskCompletionSource an Ihren Hintergrunddienst, und lassen Sie sie TrySetResult aufrufen, wenn Sie das WebSocket beenden. Verwenden Sie dann await für die Task-Eigenschaft während der Anforderung, wie im folgenden Beispiel gezeigt:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Die Ausnahme „WebSocket closed“ kann auch auftreten, wenn die Rückkehr von einer Aktionsmethode zu früh erfolgt. Wenn Sie einen Socket in einer Aktionsmethode akzeptieren, warten Sie vor dem Zurückkehren von der Aktionsmethode, bis der Code, der den Socket verwendet, abgeschlossen ist.

Verwenden Sie nie Task.Wait, Task.Result oder ähnliche Blockierungsaufrufe, um auf den Abschluss des Sockets zu warten, da dies zu schwerwiegenden Threadingproblemen führen kann. Verwenden Sie immer await.

Komprimierung

Warnung

Das Aktivieren der Komprimierung über verschlüsselte Verbindungen kann dazu führen, dass eine App CRIME/BREACH Angriffen ausgesetzt ist. Wenn Sie vertrauliche Informationen senden, sollten Sie die Komprimierung nicht aktivieren oder beim Aufrufen von WebSocket.SendAsyncWebSocketMessageFlags.DisableCompression verwenden. Dies gilt für beide WebSockets-Seiten. Beachten Sie, dass die WebSockets-API im Browser nicht über eine Konfiguration zum Deaktivieren der Komprimierung beim Senden verfügt.

Wenn eine Komprimierung von Nachrichten über WebSockets gewünscht ist, muss der Akzeptierungscode angeben, dass er die Komprimierung wie folgt zulässt:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits und WebSocketAcceptContext.DisableServerContextTakeover sind erweiterte Optionen, die die Funktionsweise der Komprimierung steuern.

Die Komprimierung wird zwischen Client und Server ausgehandelt, wenn zum ersten Mal eine Verbindung hergestellt wird. Weitere Informationen zur Aushandlung finden Sie unter Compression Extensions for WebSocket RFC (Komprimierungserweiterungen für WebSocket RFC).

Hinweis

Wenn die Komprimierungsaushandlung weder vom Server noch vom Client akzeptiert wird, wird die Verbindung dennoch hergestellt. Die Verbindung verwendet jedoch keine Komprimierung beim Senden und Empfangen von Nachrichten.

Senden und Empfangen von Nachrichten

Die AcceptWebSocketAsync-Methode ändert die TCP-Verbindung in eine WebSocket-Verbindung und stellt ein WebSocket-Objekt bereit. Verwenden Sie das WebSocket-Objekt, um Nachrichten zu senden und zu empfangen.

Der weiter oben gezeigte Code, der WebSocket-Anforderungen akzeptiert, übergibt das WebSocket-Objekt an eine Echo-Methode. Der Code empfängt eine Nachricht und sendet diese umgehend wieder zurück. Nachrichten werden in einer Schleife gesendet und empfangen, bis der Client die Verbindung schließt:

private static async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

        receiveResult = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None);
    }

    await webSocket.CloseAsync(
        receiveResult.CloseStatus.Value,
        receiveResult.CloseStatusDescription,
        CancellationToken.None);
}

Wenn Sie die WebSocket-Verbindung vor Beginn der Schleife akzeptieren, endet die Middlewarepipeline. Wenn Sie das Socket schließen, wird die Pipeline entladen. Das bedeutet, dass sich die Anforderung in der Pipeline nicht mehr weiter bewegt, wenn der WebSocket akzeptiert wird. Wenn die Schleife beendet und der Socket geschlossen ist, wird die Anforderung in der Pipeline weiter verarbeitet.

Behandeln der Trennung der Verbindung mit dem Client

Der Server wird nicht automatisch informiert, wenn die Verbindung mit dem Client wegen Konnektivitätsverlust getrennt wird. Der Server empfängt nur dann eine Trennungsnachricht, wenn der Client sie sendet, was bei Verlust der Verbindung nicht möglich ist. Wenn Sie Maßnahmen ergreifen möchten, wenn dies der Fall ist, legen Sie ein Timeout fest für den Fall, dass innerhalb eines bestimmten Zeitfensters keine Eingabe vom Client empfangen wird.

Wenn der Client nicht immer Nachrichten sendet und Sie kein Timeout festlegen möchten, nur weil die Verbindung in den Leerlauf übergeht, lassen Sie den Client einen Timer verwenden, um alle X Sekunden eine Ping-Nachricht zu senden. Wenn auf dem Server nicht innerhalb von 2*X Sekunden nach einer Nachricht die nächste eingeht, beenden Sie die Verbindung, und melden Sie, dass der Client die Verbindung getrennt hat. Warten Sie für den doppelten Zeitraum des erwarteten Zeitintervalls, um zusätzliche Zeit für Netzwerkverzögerungen einzuräumen, die die Ping-Nachricht aufhalten könnten.

Beschränkung von WebSocket-Ursprüngen

Der von CORS erzeugte Schutz gilt nicht für WebSockets. Für Browser gilt Folgendes nicht:

  • Ausführen von CORS-Preflightanforderungen
  • Berücksichtigen der Einschränkungen, die in den Access-Control-Headern bei der Erstellung von WebSocket-Anforderungen angegeben sind

Allerdings senden Browser den Origin-Header, wenn die WebSocket-Anforderungen ausgegeben werden. Anwendungen sollten zur Überprüfung dieser Header konfiguriert werden, um sicherzustellen, dass nur WebSockets von den erwarteten Ursprüngen zulässig sind.

Wenn Sie Ihren Server unter „https://server.com"“ und Ihren Client unter „https://client.com"“ hosten, fügen Sie „https://client.com"“ zur Liste AllowedOrigins hinzu, damit sie von WebSockets überprüft wird.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Hinweis

Der Origin-Header wird vom Client gesteuert und kann wie der Referer-Header überlistet werden. Verwenden Sie diese Header nicht als Authentifizierungsmechanismus.

Unterstützung für IIS und IIS Express

In Windows Server 2012 oder höher und in Windows 8 oder höher mit IIS 8 oder IIS Express 8 oder höher ist die Unterstützung für das WebSocket-Protokoll enthalten.

Hinweis

WebSockets sind immer aktiviert, wenn Sie IIS Express verwenden.

Aktivieren von WebSockets in IIS

So aktivieren Sie die Unterstützung für das WebSocket-Protokoll unter Windows Server 2012 oder höher:

Hinweis

Diese Schritte sind nicht erforderlich, wenn Sie IIS Express verwenden.

  1. Verwenden Sie den Assistenten Rollen und Features hinzufügen im Menü Verwalten oder den Link in Server-Manager.
  2. Klicken Sie auf Rollenbasierte oder featurebasierte Installation. Klicken Sie auf Weiter.
  3. Wählen Sie den entsprechenden Server aus (standardmäßig ist der lokale Server ausgewählt). Klicken Sie auf Weiter.
  4. Erweitern Sie Webserver (IIS) in der Struktur Rollen, und erweitern Sie Webserver und anschließend Anwendungsentwicklung.
  5. Wählen Sie WebSocket-Protokoll aus. Klicken Sie auf Weiter.
  6. Wenn keine zusätzlichen Features erforderlich sind, klicken Sie auf Weiter.
  7. Klicken Sie auf Installieren.
  8. Wenn die Installation abgeschlossen ist, klicken Sie auf Schließen, um den Assistenten zu beenden.

So aktivieren Sie die Unterstützung für das WebSocket-Protokoll unter Windows 8 oder höher:

Hinweis

Diese Schritte sind nicht erforderlich, wenn Sie IIS Express verwenden.

  1. Navigieren Sie zu Systemsteuerung>Programme>Programme und Features>Windows-Features aktivieren oder deaktivieren (links auf dem Bildschirm).
  2. Öffnen Sie die folgenden Knoten: Internetinformationsdienste>WWW-Dienste>Anwendungsentwicklungsfeatures.
  3. Wählen Sie das Feature WebSocket-Protokoll aus. Klicken Sie auf OK.

Deaktivieren von WebSocket bei Verwendung von „socket.io“ in „Node.js“

Wenn Sie die WebSocket-Unterstützung in socket.io in Node.js verwenden, deaktivieren Sie das IIS-WebSocket-Standardmodul mithilfe des webSocket-Elements in web.config oder applicationHost.config. Wenn dieser Schritt nicht durchgeführt wird, versucht das IIS-WebSocket-Modul, die WebSocket-Kommunikation statt Node.js und der App zu verarbeiten.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Beispiel-App

Die Beispiel-App, die in diesem Artikel verwendet wird, ist eine Echo-App. Sie verfügt über eine Webseite, die WebSocket-Verbindungen herstellt. Der Server schickt alle empfangenen Nachrichten zurück an den Client. Die Beispiel-App ist nicht so konfiguriert, dass sie mit IIS Express über Visual Studio ausgeführt wird. Führen Sie die App daher mit dotnet run in einer Befehlsshell aus, und navigieren Sie in einem Browser zu http://localhost:<port>. Auf der Webseite wird der Verbindungsstatus angezeigt:

Initial state of webpage before WebSockets connection

Klicken Sie auf Connect (Verbinden), um eine WebSocket-Anforderung an die gezeigte URL zu senden. Geben Sie einen Testtext ein, und klicken Sie auf Send (Senden). Wenn dies abgeschlossen ist, klicken Sie auf Close Socket (Socket schließen). Der Abschnitt Kommunikationsprotokoll meldet jede open-, send- und close-Aktion, wenn diese durchgeführt wird.

Final state of webpage after WebSockets connection and test messages are sent and received

In diesem Artikel erfahren Sie, wie Sie mit WebSockets in ASP.NET beginnen. Bei WebSockets (RFC 6455) handelt es sich um ein Protokoll, das bidirektionale persistente Kommunikationskanäle über TCP-Verbindungen ermöglicht. Es wird in Apps verwendet, die von schneller Kommunikation in Echtzeit profitieren, z.B. Chats, Dashboards und Spiele-Apps.

Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen). Laufvergleich.

SignalR

ASP.NET Core SignalR ist eine Bibliothek, die das Hinzufügen von Echtzeit-Webfunktionalität zu Apps erleichtert. Sie verwendet wenn möglich immer WebSockets.

Für die meisten Anwendungen empfehlen wir SignalR über RAW-WebSockets. SignalR stellt ein Transportfallback für Umgebungen bereit, in denen WebSockets nicht verfügbar ist. Darüber hinaus bietet es ein einfaches App-Modell für Remoteprozeduraufrufe. In den meisten Szenarien hat SignalR außerdem keinen signifikanten Leistungsnachteil gegenüber der Verwendung von RAW-WebSockets.

Für einige Apps stellt gRPC in .NET eine Alternative zu WebSockets dar.

Voraussetzungen

  • Jedes Betriebssystem, das ASP.NET Core unterstützt:
    • Windows 7/Windows Server 2008 und höher
    • Linux
    • macOS
  • Wenn die App unter Windows mit IIS ausgeführt wird:
  • Wenn die App unter HTTP.sys ausgeführt wird:
    • Windows 8/Windows Server 2012 und höher
  • Weitere Informationen zu unterstützten Browsern finden Sie unter Can I use?.

Konfigurieren der Middleware

Fügen Sie die WebSockets-Middleware zur Configure-Methode der Startup-Klasse hinzu.

app.UseWebSockets();

Hinweis

Wenn Sie WebSocket-Anforderungen in einem Controller annehmen möchten, muss app.UseWebSockets vor app.UseEndpoints aufgerufen werden.

Sie können die folgenden Einstellungen konfigurieren:

  • KeepAliveInterval: Legt fest, wie oft Ping-Frames an den Client gesendet werden, um sicherzustellen, dass Proxys die Verbindung aufrechterhalten Der Standardwert beträgt zwei Minuten.
  • AllowedOrigins – Eine Liste der zulässigen Origin-Headerwerte für WebSocket-Anforderungen. Alle Ursprünge sind standardmäßig zulässig. Weitere Informationen finden Sie unter „Beschränkung von WebSocket-Ursprüngen“ weiter unten.
var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};

app.UseWebSockets(webSocketOptions);

Akzeptieren der Anforderungen von WebSocket

Prüfen Sie zu einem späteren Zeitpunkt im Lebenszyklus einer Anforderung (z.B. später in der Configure-Methode oder in einer Aktionsmethode), ob es sich um eine WebSocket-Anforderung handelt, und akzeptieren Sie diese.

Das folgende Beispiel ist ein späterer Auszug der Configure-Methode.

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
            {
                await Echo(context, webSocket);
            }
        }
        else
        {
            context.Response.StatusCode = (int) HttpStatusCode.BadRequest;
        }
    }
    else
    {
        await next();
    }

});

WebSocket-Anforderungen können bei jeder URL eingehen. Dieser Beispielcode akzeptiert jedoch nur Anforderungen für /ws.

Ein ähnlicher Ansatz kann in einer Controllermethode verwendet werden:

public class WebSocketController : ControllerBase
{
    [HttpGet("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

Bei Verwendung eines WebSockets muss die Middlewarepipeline während der gesamten Dauer der Verbindung ausgeführt werden. Wenn Sie versuchen, eine WebSocket-Nachricht zu senden oder zu empfangen, nachdem die Middlewarepipeline beendet wurde, erhalten Sie möglicherweise eine Ausnahme wie die folgende:

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Wenn Sie einen Hintergrunddienst zum Schreiben von Daten an ein WebSocket verwenden, stellen Sie sicher, dass die Middlewarepipeline dauerhaft ausgeführt wird. Verwenden Sie dafür ein TaskCompletionSource<TResult>. Übergeben Sie die TaskCompletionSource an Ihren Hintergrunddienst, und lassen Sie sie TrySetResult aufrufen, wenn Sie das WebSocket beenden. Verwenden Sie dann await für die Task-Eigenschaft während der Anforderung, wie im folgenden Beispiel gezeigt:

app.Use(async (context, next) =>
{
    using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
    {
        var socketFinishedTcs = new TaskCompletionSource<object>();

        BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

        await socketFinishedTcs.Task;
    }
});

Die Ausnahme „WebSocket closed“ kann auch auftreten, wenn die Rückkehr von einer Aktionsmethode zu früh erfolgt. Wenn Sie einen Socket in einer Aktionsmethode akzeptieren, warten Sie vor dem Zurückkehren von der Aktionsmethode, bis der Code, der den Socket verwendet, abgeschlossen ist.

Verwenden Sie nie Task.Wait, Task.Result oder ähnliche Blockierungsaufrufe, um auf den Abschluss des Sockets zu warten, da dies zu schwerwiegenden Threadingproblemen führen kann. Verwenden Sie immer await.

Senden und Empfangen von Nachrichten

Die AcceptWebSocketAsync-Methode ändert die TCP-Verbindung in eine WebSocket-Verbindung und stellt ein WebSocket-Objekt bereit. Verwenden Sie das WebSocket-Objekt, um Nachrichten zu senden und zu empfangen.

Der weiter oben gezeigte Code, der WebSocket-Anforderungen akzeptiert, übergibt das WebSocket-Objekt an eine Echo-Methode. Der Code empfängt eine Nachricht und sendet diese umgehend wieder zurück. Nachrichten werden in einer Schleife gesendet und empfangen, bis der Client die Verbindung schließt:

private async Task Echo(HttpContext context, WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    while (!result.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

        result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    }
    await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}

Wenn Sie die WebSocket-Verbindung vor Beginn der Schleife akzeptieren, endet die Middlewarepipeline. Wenn Sie das Socket schließen, wird die Pipeline entladen. Das bedeutet, dass sich die Anforderung in der Pipeline nicht mehr weiter bewegt, wenn der WebSocket akzeptiert wird. Wenn die Schleife beendet und der Socket geschlossen ist, wird die Anforderung in der Pipeline weiter verarbeitet.

Behandeln der Trennung der Verbindung mit dem Client

Der Server wird nicht automatisch informiert, wenn die Verbindung mit dem Client wegen Konnektivitätsverlust getrennt wird. Der Server empfängt nur dann eine Trennungsnachricht, wenn der Client sie sendet, was bei Verlust der Verbindung nicht möglich ist. Wenn Sie Maßnahmen ergreifen möchten, wenn dies der Fall ist, legen Sie ein Timeout fest für den Fall, dass innerhalb eines bestimmten Zeitfensters keine Eingabe vom Client empfangen wird.

Wenn der Client nicht immer Nachrichten sendet und Sie kein Timeout festlegen möchten, nur weil die Verbindung in den Leerlauf übergeht, lassen Sie den Client einen Timer verwenden, um alle X Sekunden eine Ping-Nachricht zu senden. Wenn auf dem Server nicht innerhalb von 2*X Sekunden nach einer Nachricht die nächste eingeht, beenden Sie die Verbindung, und melden Sie, dass der Client die Verbindung getrennt hat. Warten Sie für den doppelten Zeitraum des erwarteten Zeitintervalls, um zusätzliche Zeit für Netzwerkverzögerungen einzuräumen, die die Ping-Nachricht aufhalten könnten.

Hinweis

Die interne ManagedWebSocket behandelt die Ping/Pong-Frames implizit, um die Verbindung aktiv zu halten, wenn die KeepAliveInterval Option größer als Null ist, was standardmäßig auf 30 Sekunden (TimeSpan.FromSeconds(30)) festgelegt ist.

Beschränkung von WebSocket-Ursprüngen

Der von CORS erzeugte Schutz gilt nicht für WebSockets. Für Browser gilt Folgendes nicht:

  • Ausführen von CORS-Preflightanforderungen
  • Berücksichtigen der Einschränkungen, die in den Access-Control-Headern bei der Erstellung von WebSocket-Anforderungen angegeben sind

Allerdings senden Browser den Origin-Header, wenn die WebSocket-Anforderungen ausgegeben werden. Anwendungen sollten zur Überprüfung dieser Header konfiguriert werden, um sicherzustellen, dass nur WebSockets von den erwarteten Ursprüngen zulässig sind.

Wenn Sie Ihren Server unter „https://server.com"“ und Ihren Client unter „https://client.com"“ hosten, fügen Sie „https://client.com"“ zur Liste AllowedOrigins hinzu, damit sie von WebSockets überprüft wird.

var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Hinweis

Der Origin-Header wird vom Client gesteuert und kann wie der Referer-Header überlistet werden. Verwenden Sie diese Header nicht als Authentifizierungsmechanismus.

Unterstützung für IIS und IIS Express

In Windows Server 2012 oder höher und in Windows 8 oder höher mit IIS 8 oder IIS Express 8 oder höher ist die Unterstützung für das WebSocket-Protokoll enthalten.

Hinweis

WebSockets sind immer aktiviert, wenn Sie IIS Express verwenden.

Aktivieren von WebSockets in IIS

So aktivieren Sie die Unterstützung für das WebSocket-Protokoll unter Windows Server 2012 oder höher:

Hinweis

Diese Schritte sind nicht erforderlich, wenn Sie IIS Express verwenden.

  1. Verwenden Sie den Assistenten Rollen und Features hinzufügen im Menü Verwalten oder den Link in Server-Manager.
  2. Klicken Sie auf Rollenbasierte oder featurebasierte Installation. Klicken Sie auf Weiter.
  3. Wählen Sie den entsprechenden Server aus (standardmäßig ist der lokale Server ausgewählt). Klicken Sie auf Weiter.
  4. Erweitern Sie Webserver (IIS) in der Struktur Rollen, und erweitern Sie Webserver und anschließend Anwendungsentwicklung.
  5. Wählen Sie WebSocket-Protokoll aus. Klicken Sie auf Weiter.
  6. Wenn keine zusätzlichen Features erforderlich sind, klicken Sie auf Weiter.
  7. Klicken Sie auf Installieren.
  8. Wenn die Installation abgeschlossen ist, klicken Sie auf Schließen, um den Assistenten zu beenden.

So aktivieren Sie die Unterstützung für das WebSocket-Protokoll unter Windows 8 oder höher:

Hinweis

Diese Schritte sind nicht erforderlich, wenn Sie IIS Express verwenden.

  1. Navigieren Sie zu Systemsteuerung>Programme>Programme und Features>Windows-Features aktivieren oder deaktivieren (links auf dem Bildschirm).
  2. Öffnen Sie die folgenden Knoten: Internetinformationsdienste>WWW-Dienste>Anwendungsentwicklungsfeatures.
  3. Wählen Sie das Feature WebSocket-Protokoll aus. Klicken Sie auf OK.

Deaktivieren von WebSocket bei Verwendung von „socket.io“ in „Node.js“

Wenn Sie die WebSocket-Unterstützung in socket.io in Node.js verwenden, deaktivieren Sie das IIS-WebSocket-Standardmodul mithilfe des webSocket-Elements in web.config oder applicationHost.config. Wenn dieser Schritt nicht durchgeführt wird, versucht das IIS-WebSocket-Modul, die WebSocket-Kommunikation statt Node.js und der App zu verarbeiten.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Beispiel-App

Die Beispiel-App, die in diesem Artikel verwendet wird, ist eine Echo-App. Sie verfügt über eine Webseite, die WebSocket-Verbindungen herstellt. Der Server schickt alle empfangenen Nachrichten zurück an den Client. Die Beispiel-App ist nicht so konfiguriert, dass sie mit IIS Express über Visual Studio ausgeführt wird. Führen Sie die App daher mit dotnet run in einer Befehlsshell aus, und navigieren Sie in einem Browser zu http://localhost:5000. Auf der Webseite wird der Verbindungsstatus angezeigt:

Initial state of webpage before WebSockets connection

Klicken Sie auf Connect (Verbinden), um eine WebSocket-Anforderung an die gezeigte URL zu senden. Geben Sie einen Testtext ein, und klicken Sie auf Send (Senden). Wenn dies abgeschlossen ist, klicken Sie auf Close Socket (Socket schließen). Der Abschnitt Kommunikationsprotokoll meldet jede open-, send- und close-Aktion, wenn diese durchgeführt wird.

Final state of webpage after WebSockets connection and test messages are sent and received