Obsługa obiektów WebSocket w środowisku ASP.NET Core

W tym artykule wyjaśniono, jak rozpocząć pracę z elementami WebSocket w środowisku ASP.NET Core. WebSocket (RFC 6455) to protokół, który umożliwia dwukierunkowe trwałe kanały komunikacyjne za pośrednictwem połączeń TCP. Jest ona używana w aplikacjach, które korzystają z szybkiej komunikacji w czasie rzeczywistym, takiej jak czat, pulpit nawigacyjny i aplikacje gier.

Wyświetl lub pobierz przykładowy kod (jak pobrać, jak uruchomić).

Obsługa obiektów WebSocket http/2

Korzystanie z obiektów WebSocket za pośrednictwem protokołu HTTP/2 korzysta z nowych funkcji, takich jak:

  • Kompresja nagłówka.
  • Multipleksowanie, co skraca czas i zasoby potrzebne podczas podejmowania wielu żądań na serwerze.

Te obsługiwane funkcje są dostępne na Kestrel wszystkich platformach z obsługą protokołu HTTP/2. Negocjowanie wersji jest automatyczne w przeglądarkach i Kestrel, więc nie są potrzebne żadne nowe interfejsy API.

Platforma .NET 7 wprowadziła protokoły Websocket za pośrednictwem protokołu HTTP/2 dla Kestrelklienta SignalR Języka JavaScript i SignalR za pomocą polecenia Blazor WebAssembly.

Uwaga

Protokoły HTTP/2 WebSocket używają żądań CONNECT, a nie GET, więc konieczne może być zaktualizowanie własnych tras i kontrolerów. Aby uzyskać więcej informacji, zobacz Dodawanie obsługi obiektów WebSocket PROTOKOŁU HTTP/2 dla istniejących kontrolerów w tym artykule.

Chrome i Edge mają domyślnie włączone protokoły WebSocket HTTP/2 i można je włączyć na about:config stronie z flagą network.http.spdy.websockets .

Zestawy WebSocket zostały pierwotnie zaprojektowane pod kątem protokołu HTTP/1.1, ale od tego czasu zostały dostosowane do pracy za pośrednictwem protokołu HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR to biblioteka, która upraszcza dodawanie funkcji internetowych w czasie rzeczywistym do aplikacji. Używa on obiektów WebSocket zawsze, gdy jest to możliwe.

W przypadku większości aplikacji zalecamy SignalR , a nie nieprzetworzone obiekty WebSocket. SignalR:

  • Zapewnia rezerwę transportu dla środowisk, w których obiekty WebSocket nie są dostępne.
  • Udostępnia podstawowy model aplikacji wywołania procedury zdalnej.
  • W większości scenariuszy nie ma znaczącej wady wydajności w porównaniu z używaniem nieprzetworzonych obiektów WebSocket.

Protokoły WebSocket za pośrednictwem protokołu HTTP/2 są obsługiwane w następujących celach:

  • ASP.NET Core SignalR JavaScript client
  • ASP.NET Core SignalR z Blazor WebAssembly

W przypadku niektórych aplikacji gRPC na platformie .NET stanowi alternatywę dla obiektów WebSocket.

Wymagania wstępne

  • Każdy system operacyjny obsługujący ASP.NET Core:
    • Windows 7/ Windows Server 2008 lub nowszy
    • Linux
    • macOS
  • Jeśli aplikacja działa w systemie Windows z usługami IIS:
    • Windows 8/ Windows Server 2012 lub nowszy
    • USŁUGI IIS 8/ IIS 8 Express
    • Należy włączyć zestawy WebSocket. Zobacz sekcję pomocy technicznej usług IIS/IIS Express.
  • Jeśli aplikacja działa w systemie HTTP.sys:
    • Windows 8/ Windows Server 2012 lub nowszy
  • Aby uzyskać informacje o obsługiwanych przeglądarkach, zobacz Can I use (Czy mogę używać).

Konfigurowanie oprogramowania pośredniczącego

Dodaj oprogramowanie pośredniczące WebSockets w pliku Program.cs:

app.UseWebSockets();

Można skonfigurować następujące ustawienia:

  • KeepAliveInterval - Jak często wysyłać ramki "ping" do klienta, aby zapewnić, że serwery proxy zachowają otwarte połączenie. Wartość domyślna to dwie minuty.
  • AllowedOrigins — Lista dozwolonych wartości nagłówków źródła dla żądań protokołu WebSocket. Domyślnie wszystkie źródła są dozwolone. Aby uzyskać więcej informacji, zobacz Ograniczenia pochodzenia protokołu WebSocket w tym artykule.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Akceptowanie żądań protokołu WebSocket

Gdzieś później w cyklu życia żądania (na przykład w Program.cs metodzie akcji lub w metodzie akcji) sprawdź, czy jest to żądanie protokołu WebSocket i zaakceptuj żądanie protokołu WebSocket.

Poniższy przykład pochodzi z późniejszego kodu w pliku Program.cs:

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

});

Żądanie protokołu WebSocket może pojawić się pod dowolnym adresem URL, ale ten przykładowy kod akceptuje tylko żądania dla elementu /ws.

Podobne podejście można zastosować w metodzie kontrolera:

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;
        }
    }

W przypadku korzystania z protokołu WebSocket należy zachować potok oprogramowania pośredniczącego uruchomiony przez czas trwania połączenia. Jeśli próbujesz wysłać lub odebrać komunikat protokołu WebSocket po zakończeniu potoku oprogramowania pośredniczącego, może wystąpić wyjątek podobny do następującego:

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'.

Jeśli używasz usługi w tle do zapisywania danych w zestawie WebSocket, upewnij się, że potok oprogramowania pośredniczącego jest uruchomiony. W tym celu należy użyć elementu TaskCompletionSource<TResult>. Przekaż element TaskCompletionSource do usługi w tle i wywołaj go TrySetResult po zakończeniu pracy z zestawem WebSocket. Task Następnie await właściwość podczas żądania, jak pokazano w poniższym przykładzie:

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

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Wyjątek zamknięty protokołu WebSocket może również wystąpić w przypadku powrotu zbyt szybko z metody akcji. Podczas akceptowania gniazda w metodzie akcji zaczekaj na ukończenie kodu, który używa gniazda przed powrotem z metody akcji.

Nigdy nie używaj Task.Waitwywołań blokowania , Task.Resultlub podobnych wywołań blokujących, aby poczekać na zakończenie gniazda, ponieważ może to powodować poważne problemy z wątkami. Zawsze używaj polecenia await.

Dodawanie obsługi obiektów WebSocket PROTOKOŁU HTTP/2 dla istniejących kontrolerów

Platforma .NET 7 wprowadziła protokoły Websocket za pośrednictwem protokołu HTTP/2 dla Kestrelklienta SignalR Języka JavaScript i SignalR za pomocą polecenia Blazor WebAssembly. Protokoły WEBSocket HTTP/2 używają żądań CONNECT, a nie GET. Jeśli wcześniej użyto [HttpGet("/path")] metody akcji kontrolera dla żądań protokołu Websocket, zaktualizuj ją do użycia [Route("/path")] .

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;
        }
    }

Kompresja

Ostrzeżenie

Włączenie kompresji za pośrednictwem zaszyfrowanych połączeń może sprawić, że aplikacja będzie mogła podlegać /BREACHatakomCRIME. W przypadku wysyłania poufnych informacji należy unikać włączania kompresji lub używania WebSocketMessageFlags.DisableCompression podczas wywoływania metody WebSocket.SendAsync. Dotyczy to obu stron protokołu WebSocket. Należy pamiętać, że interfejs API obiektów WebSockets w przeglądarce nie ma konfiguracji do wyłączania kompresji na wysyłanie.

Jeśli wymagana jest kompresja komunikatów za pośrednictwem obiektów WebSocket, akceptowany kod musi określić, że zezwala na kompresję w następujący sposób:

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

}

WebSocketAcceptContext.ServerMaxWindowBits i WebSocketAcceptContext.DisableServerContextTakeover są zaawansowanymi opcjami, które kontrolują sposób działania kompresji.

Kompresja jest negocjowana między klientem a serwerem podczas pierwszego nawiązywania połączenia. Więcej informacji na temat negocjacji można uzyskać w temacie Compression Extensions for WebSocket RFC (Rozszerzenia kompresji dla protokołu RFC protokołu WebSocket).

Uwaga

Jeśli negocjacje kompresji nie są akceptowane przez serwer lub klienta, połączenie jest nadal ustanawiane. Jednak połączenie nie używa kompresji podczas wysyłania i odbierania komunikatów.

Wysyłanie i odbieranie komunikatów

Metoda AcceptWebSocketAsync uaktualnia połączenie TCP z połączeniem Protokołu WebSocket i udostępnia WebSocket obiekt. Użyj obiektu do wysyłania i odbierania WebSocket komunikatów.

Pokazany wcześniej kod akceptujący żądanie Protokołu WebSocket przekazuje WebSocket obiekt do Echo metody. Kod odbiera komunikat i natychmiast wysyła ten sam komunikat. Komunikaty są wysyłane i odbierane w pętli, dopóki klient nie zamknie połączenia:

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

Po zaakceptowaniu połączenia protokołu WebSocket przed rozpoczęciem pętli potok oprogramowania pośredniczącego kończy się. Po zamknięciu gniazda odwija się potok. Oznacza to, że żądanie zatrzymuje się w potoku po zaakceptowaniu protokołu WebSocket. Po zakończeniu pętli i zamknięciu gniazda żądanie będzie kontynuowane z powrotem do potoku.

Obsługa rozłączeń klientów

Serwer nie jest automatycznie informowany, gdy klient rozłącza się z powodu utraty łączności. Serwer odbiera komunikat rozłączenia tylko wtedy, gdy klient go wyśle, co nie może zostać wykonane, jeśli połączenie internetowe zostanie utracone. Jeśli chcesz wykonać jakąś akcję w takim przypadku, ustaw limit czasu po odebraniu niczego od klienta w określonym przedziale czasu.

Jeśli klient nie zawsze wysyła komunikaty i nie chcesz przekroczyć limitu czasu tylko dlatego, że połączenie nie działa bezczynnie, klient używa czasomierza do wysyłania komunikatu ping co X sekund. Na serwerze, jeśli komunikat nie dotarł do 2*X sekund po poprzednim, zakończ połączenie i zgłoś, że klient rozłączył się. Poczekaj dwa razy w oczekiwanym przedziale czasu, aby pozostawić dodatkowy czas na opóźnienia sieci, które mogą wstrzymać komunikat ping.

Ograniczenie źródła protokołu WebSocket

Ochrona zapewniana przez mechanizm CORS nie ma zastosowania do obiektów WebSocket. Przeglądarki nie:

  • Wykonywanie żądań przed lotem CORS.
  • Przestrzegaj ograniczeń określonych w Access-Control nagłówkach podczas wprowadzania żądań protokołu WebSocket.

Jednak przeglądarki wysyłają nagłówek podczas wysyłania Origin żądań protokołu WebSocket. Aplikacje powinny być skonfigurowane do sprawdzania poprawności tych nagłówków, aby upewnić się, że dozwolone są tylko obiekty WebSocket pochodzące z oczekiwanych źródeł.

Jeśli hostujesz serwer na "https://server.com" i hostowanie klienta w "https://client.com", dodaj "https://client.com" do AllowedOrigins listy obiektów WebSocket w celu zweryfikowania.

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

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

app.UseWebSockets(webSocketOptions);

Uwaga

Nagłówek Origin jest kontrolowany przez klienta i, podobnie jak Referer nagłówek, może być fałszywy. Nie używaj tych nagłówków jako mechanizmu uwierzytelniania.

Obsługa usług IIS/IIS Express

System Windows Server 2012 lub nowszy i Windows 8 lub nowszy z usługami IIS/IIS Express 8 lub nowszym ma obsługę protokołu WebSocket, ale nie dla obiektów WebSocket za pośrednictwem protokołu HTTP/2.

Uwaga

Obiekty WebSocket są zawsze włączone w przypadku korzystania z usług IIS Express.

Włączanie obiektów WebSocket w usługach IIS

Aby włączyć obsługę protokołu WebSocket w systemie Windows Server 2012 lub nowszym:

Uwaga

Te kroki nie są wymagane w przypadku korzystania z usług IIS Express

  1. Użyj kreatora Dodawanie ról i funkcji z menu Zarządzanie lub linku w Menedżer serwera.
  2. Wybierz pozycję Instalacja oparta na rolach lub oparta na funkcjach. Wybierz Dalej.
  3. Wybierz odpowiedni serwer (domyślnie wybierany jest serwer lokalny). Wybierz Dalej.
  4. Rozwiń węzeł Serwer sieci Web (IIS) w drzewie Role, rozwiń węzeł Serwer sieci Web, a następnie rozwiń węzeł Tworzenie aplikacji.
  5. Wybierz pozycję Protokół WebSocket. Wybierz Dalej.
  6. Jeśli dodatkowe funkcje nie są potrzebne, wybierz pozycję Dalej.
  7. Wybierz Zainstaluj.
  8. Po zakończeniu instalacji wybierz pozycję Zamknij , aby zamknąć kreatora.

Aby włączyć obsługę protokołu WebSocket w systemie Windows 8 lub nowszym:

Uwaga

Te kroki nie są wymagane w przypadku korzystania z usług IIS Express

  1. Przejdź do pozycji Panel sterowania>Programy>Programy i funkcje>Włącz lub wyłącz funkcje systemu Windows (po lewej stronie ekranu).
  2. Otwórz następujące węzły: Funkcje tworzenia aplikacji internetowych usług Internet Information Services>World Wide Web Services.>
  3. Wybierz funkcję Protokół WebSocket. Wybierz przycisk OK.

Wyłączanie protokołu WebSocket podczas korzystania z socket.io w środowisku Node.js

W przypadku korzystania z obsługi protokołu WebSocket w socket.io w środowisku Node.js wyłącz domyślny moduł WebSocket usług IIS przy użyciu webSocket elementu w pliku web.config lub applicationHost.config. Jeśli ten krok nie zostanie wykonany, moduł Iis WebSocket próbuje obsłużyć komunikację protokołu WebSocket, a nie Node.js i aplikację.

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

Przykładowa aplikacja

Przykładowa aplikacja , której towarzyszy ten artykuł, jest aplikacją echo. Ma ona stronę internetową, która tworzy połączenia protokołu WebSocket, a serwer ponownie wysyła wszystkie komunikaty odbierane z powrotem do klienta. Przykładowa aplikacja obsługuje protokoły WebSocket za pośrednictwem protokołu HTTP/2 w przypadku korzystania z docelowej platformy .NET 7 lub nowszej.

Uruchom aplikację:

  • Aby uruchomić aplikację w programie Visual Studio: otwórz przykładowy projekt w programie Visual Studio i naciśnij klawisze Ctrl+F5, aby uruchomić bez debugera.
  • Aby uruchomić aplikację w powłoce poleceń: uruchom polecenie dotnet run i przejdź w przeglądarce do http://localhost:<port>.

Na stronie internetowej jest wyświetlany stan połączenia:

Initial state of webpage before WebSockets connection

Wybierz pozycję Połączenie, aby wysłać żądanie protokołu WebSocket do wyświetlonego adresu URL. Wprowadź komunikat testowy i wybierz pozycję Wyślij. Po zakończeniu wybierz pozycję Zamknij gniazdo. Sekcja Dziennik komunikacji raportuje każdą otwartą, wysyłaną i zamykaną akcję w miarę ich działania.

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

W tym artykule wyjaśniono, jak rozpocząć pracę z elementami WebSocket w środowisku ASP.NET Core. WebSocket (RFC 6455) to protokół, który umożliwia dwukierunkowe trwałe kanały komunikacyjne za pośrednictwem połączeń TCP. Jest ona używana w aplikacjach, które korzystają z szybkiej komunikacji w czasie rzeczywistym, takiej jak czat, pulpit nawigacyjny i aplikacje gier.

Wyświetl lub pobierz przykładowy kod (jak pobrać, jak uruchomić).

SignalR

ASP.NET Core SignalR to biblioteka, która upraszcza dodawanie funkcji internetowych w czasie rzeczywistym do aplikacji. Używa on obiektów WebSocket zawsze, gdy jest to możliwe.

W przypadku większości aplikacji zalecamy SignalR użycie nieprzetworzonych obiektów WebSocket. SignalR Zapewnia rezerwę transportu dla środowisk, w których obiekty WebSocket nie są dostępne. Udostępnia również podstawowy model aplikacji wywołania procedury zdalnej. W większości scenariuszy SignalR nie ma znaczącej wady wydajności w porównaniu z używaniem nieprzetworzonych obiektów WebSocket.

W przypadku niektórych aplikacji gRPC na platformie .NET stanowi alternatywę dla obiektów WebSocket.

Wymagania wstępne

  • Każdy system operacyjny obsługujący ASP.NET Core:
    • Windows 7/ Windows Server 2008 lub nowszy
    • Linux
    • macOS
  • Jeśli aplikacja działa w systemie Windows z usługami IIS:
    • Windows 8/ Windows Server 2012 lub nowszy
    • USŁUGI IIS 8/ IIS 8 Express
    • Należy włączyć zestawy WebSocket. Zobacz sekcję pomocy technicznej usług IIS/IIS Express.
  • Jeśli aplikacja działa w systemie HTTP.sys:
    • Windows 8/ Windows Server 2012 lub nowszy
  • Aby uzyskać informacje o obsługiwanych przeglądarkach, zobacz Can I use (Czy mogę używać).

Konfigurowanie oprogramowania pośredniczącego

Dodaj oprogramowanie pośredniczące WebSockets w pliku Program.cs:

app.UseWebSockets();

Można skonfigurować następujące ustawienia:

  • KeepAliveInterval - Jak często wysyłać ramki "ping" do klienta, aby zapewnić, że serwery proxy zachowają otwarte połączenie. Wartość domyślna to dwie minuty.
  • AllowedOrigins — Lista dozwolonych wartości nagłówków źródła dla żądań protokołu WebSocket. Domyślnie wszystkie źródła są dozwolone. Aby uzyskać więcej informacji, zobacz Ograniczenia pochodzenia protokołu WebSocket w tym artykule.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Akceptowanie żądań protokołu WebSocket

Gdzieś później w cyklu życia żądania (na przykład w Program.cs metodzie akcji lub w metodzie akcji) sprawdź, czy jest to żądanie protokołu WebSocket i zaakceptuj żądanie protokołu WebSocket.

Poniższy przykład pochodzi z późniejszego kodu w pliku Program.cs:

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

});

Żądanie protokołu WebSocket może pojawić się pod dowolnym adresem URL, ale ten przykładowy kod akceptuje tylko żądania dla elementu /ws.

Podobne podejście można zastosować w metodzie kontrolera:

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;
        }
    }

W przypadku korzystania z protokołu WebSocket należy zachować potok oprogramowania pośredniczącego uruchomiony przez czas trwania połączenia. Jeśli próbujesz wysłać lub odebrać komunikat protokołu WebSocket po zakończeniu potoku oprogramowania pośredniczącego, może wystąpić wyjątek podobny do następującego:

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'.

Jeśli używasz usługi w tle do zapisywania danych w zestawie WebSocket, upewnij się, że potok oprogramowania pośredniczącego jest uruchomiony. W tym celu należy użyć elementu TaskCompletionSource<TResult>. Przekaż element TaskCompletionSource do usługi w tle i wywołaj go TrySetResult po zakończeniu pracy z zestawem WebSocket. Task Następnie await właściwość podczas żądania, jak pokazano w poniższym przykładzie:

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

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Wyjątek zamknięty protokołu WebSocket może również wystąpić w przypadku powrotu zbyt szybko z metody akcji. Podczas akceptowania gniazda w metodzie akcji zaczekaj na ukończenie kodu, który używa gniazda przed powrotem z metody akcji.

Nigdy nie używaj Task.Waitwywołań blokowania , Task.Resultlub podobnych wywołań blokujących, aby poczekać na zakończenie gniazda, ponieważ może to powodować poważne problemy z wątkami. Zawsze używaj polecenia await.

Kompresja

Ostrzeżenie

Włączenie kompresji za pośrednictwem zaszyfrowanych połączeń może sprawić, że aplikacja będzie mogła podlegać /BREACHatakomCRIME. W przypadku wysyłania poufnych informacji należy unikać włączania kompresji lub używania WebSocketMessageFlags.DisableCompression podczas wywoływania metody WebSocket.SendAsync. Dotyczy to obu stron protokołu WebSocket. Należy pamiętać, że interfejs API obiektów WebSockets w przeglądarce nie ma konfiguracji do wyłączania kompresji na wysyłanie.

Jeśli wymagana jest kompresja komunikatów za pośrednictwem obiektów WebSocket, akceptowany kod musi określić, że zezwala na kompresję w następujący sposób:

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

}

WebSocketAcceptContext.ServerMaxWindowBits i WebSocketAcceptContext.DisableServerContextTakeover są zaawansowanymi opcjami, które kontrolują sposób działania kompresji.

Kompresja jest negocjowana między klientem a serwerem podczas pierwszego nawiązywania połączenia. Więcej informacji na temat negocjacji można uzyskać w temacie Compression Extensions for WebSocket RFC (Rozszerzenia kompresji dla protokołu RFC protokołu WebSocket).

Uwaga

Jeśli negocjacje kompresji nie są akceptowane przez serwer lub klienta, połączenie jest nadal ustanawiane. Jednak połączenie nie używa kompresji podczas wysyłania i odbierania komunikatów.

Wysyłanie i odbieranie komunikatów

Metoda AcceptWebSocketAsync uaktualnia połączenie TCP z połączeniem Protokołu WebSocket i udostępnia WebSocket obiekt. Użyj obiektu do wysyłania i odbierania WebSocket komunikatów.

Pokazany wcześniej kod akceptujący żądanie Protokołu WebSocket przekazuje WebSocket obiekt do Echo metody. Kod odbiera komunikat i natychmiast wysyła ten sam komunikat. Komunikaty są wysyłane i odbierane w pętli, dopóki klient nie zamknie połączenia:

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

Po zaakceptowaniu połączenia protokołu WebSocket przed rozpoczęciem pętli potok oprogramowania pośredniczącego kończy się. Po zamknięciu gniazda odwija się potok. Oznacza to, że żądanie zatrzymuje się w potoku po zaakceptowaniu protokołu WebSocket. Po zakończeniu pętli i zamknięciu gniazda żądanie będzie kontynuowane z powrotem do potoku.

Obsługa rozłączeń klientów

Serwer nie jest automatycznie informowany, gdy klient rozłącza się z powodu utraty łączności. Serwer odbiera komunikat rozłączenia tylko wtedy, gdy klient go wyśle, co nie może zostać wykonane, jeśli połączenie internetowe zostanie utracone. Jeśli chcesz wykonać jakąś akcję w takim przypadku, ustaw limit czasu po odebraniu niczego od klienta w określonym przedziale czasu.

Jeśli klient nie zawsze wysyła komunikaty i nie chcesz przekroczyć limitu czasu tylko dlatego, że połączenie nie działa bezczynnie, klient używa czasomierza do wysyłania komunikatu ping co X sekund. Na serwerze, jeśli komunikat nie dotarł do 2*X sekund po poprzednim, zakończ połączenie i zgłoś, że klient rozłączył się. Poczekaj dwa razy w oczekiwanym przedziale czasu, aby pozostawić dodatkowy czas na opóźnienia sieci, które mogą wstrzymać komunikat ping.

Ograniczenie źródła protokołu WebSocket

Ochrona zapewniana przez mechanizm CORS nie ma zastosowania do obiektów WebSocket. Przeglądarki nie:

  • Wykonywanie żądań przed lotem CORS.
  • Przestrzegaj ograniczeń określonych w Access-Control nagłówkach podczas wprowadzania żądań protokołu WebSocket.

Jednak przeglądarki wysyłają nagłówek podczas wysyłania Origin żądań protokołu WebSocket. Aplikacje powinny być skonfigurowane do sprawdzania poprawności tych nagłówków, aby upewnić się, że dozwolone są tylko obiekty WebSocket pochodzące z oczekiwanych źródeł.

Jeśli hostujesz serwer na "https://server.com" i hostowanie klienta w "https://client.com", dodaj "https://client.com" do AllowedOrigins listy obiektów WebSocket w celu zweryfikowania.

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

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

app.UseWebSockets(webSocketOptions);

Uwaga

Nagłówek Origin jest kontrolowany przez klienta i, podobnie jak Referer nagłówek, może być fałszywy. Nie używaj tych nagłówków jako mechanizmu uwierzytelniania.

Obsługa usług IIS/IIS Express

System Windows Server 2012 lub nowszy oraz system Windows 8 lub nowszy z usługami IIS/IIS Express 8 lub nowszym ma obsługę protokołu WebSocket.

Uwaga

Obiekty WebSocket są zawsze włączone w przypadku korzystania z usług IIS Express.

Włączanie obiektów WebSocket w usługach IIS

Aby włączyć obsługę protokołu WebSocket w systemie Windows Server 2012 lub nowszym:

Uwaga

Te kroki nie są wymagane w przypadku korzystania z usług IIS Express

  1. Użyj kreatora Dodawanie ról i funkcji z menu Zarządzanie lub linku w Menedżer serwera.
  2. Wybierz pozycję Instalacja oparta na rolach lub oparta na funkcjach. Wybierz Dalej.
  3. Wybierz odpowiedni serwer (domyślnie wybierany jest serwer lokalny). Wybierz Dalej.
  4. Rozwiń węzeł Serwer sieci Web (IIS) w drzewie Role, rozwiń węzeł Serwer sieci Web, a następnie rozwiń węzeł Tworzenie aplikacji.
  5. Wybierz pozycję Protokół WebSocket. Wybierz Dalej.
  6. Jeśli dodatkowe funkcje nie są potrzebne, wybierz pozycję Dalej.
  7. Wybierz Zainstaluj.
  8. Po zakończeniu instalacji wybierz pozycję Zamknij , aby zamknąć kreatora.

Aby włączyć obsługę protokołu WebSocket w systemie Windows 8 lub nowszym:

Uwaga

Te kroki nie są wymagane w przypadku korzystania z usług IIS Express

  1. Przejdź do pozycji Panel sterowania>Programy>Programy i funkcje>Włącz lub wyłącz funkcje systemu Windows (po lewej stronie ekranu).
  2. Otwórz następujące węzły: Funkcje tworzenia aplikacji internetowych usług Internet Information Services>World Wide Web Services.>
  3. Wybierz funkcję Protokół WebSocket. Wybierz przycisk OK.

Wyłączanie protokołu WebSocket podczas korzystania z socket.io w środowisku Node.js

W przypadku korzystania z obsługi protokołu WebSocket w socket.io w środowisku Node.js wyłącz domyślny moduł WebSocket usług IIS przy użyciu webSocket elementu w pliku web.config lub applicationHost.config. Jeśli ten krok nie zostanie wykonany, moduł Iis WebSocket próbuje obsłużyć komunikację protokołu WebSocket, a nie Node.js i aplikację.

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

Przykładowa aplikacja

Przykładowa aplikacja , której towarzyszy ten artykuł, jest aplikacją echo. Ma ona stronę internetową, która tworzy połączenia protokołu WebSocket, a serwer ponownie wysyła wszystkie komunikaty odbierane z powrotem do klienta. Przykładowa aplikacja nie jest skonfigurowana do uruchamiania z poziomu programu Visual Studio z usługami IIS Express, dlatego uruchom aplikację w powłoce dotnet run poleceń za pomocą polecenia i przejdź w przeglądarce do http://localhost:<port>. Na stronie internetowej jest wyświetlany stan połączenia:

Initial state of webpage before WebSockets connection

Wybierz pozycję Połączenie, aby wysłać żądanie protokołu WebSocket do wyświetlonego adresu URL. Wprowadź komunikat testowy i wybierz pozycję Wyślij. Po zakończeniu wybierz pozycję Zamknij gniazdo. Sekcja Dziennik komunikacji raportuje każdą otwartą, wysyłaną i zamykaną akcję w miarę ich działania.

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

W tym artykule wyjaśniono, jak rozpocząć pracę z elementami WebSocket w środowisku ASP.NET Core. WebSocket (RFC 6455) to protokół, który umożliwia dwukierunkowe trwałe kanały komunikacyjne za pośrednictwem połączeń TCP. Jest ona używana w aplikacjach, które korzystają z szybkiej komunikacji w czasie rzeczywistym, takiej jak czat, pulpit nawigacyjny i aplikacje gier.

Wyświetl lub pobierz przykładowy kod (jak pobrać). Jak uruchomić.

SignalR

ASP.NET Core SignalR to biblioteka, która upraszcza dodawanie funkcji internetowych w czasie rzeczywistym do aplikacji. Używa on obiektów WebSocket zawsze, gdy jest to możliwe.

W przypadku większości aplikacji zalecamy SignalR użycie nieprzetworzonych obiektów WebSocket. SignalR Zapewnia rezerwę transportu dla środowisk, w których obiekty WebSocket nie są dostępne. Udostępnia również podstawowy model aplikacji wywołania procedury zdalnej. W większości scenariuszy SignalR nie ma znaczącej wady wydajności w porównaniu z używaniem nieprzetworzonych obiektów WebSocket.

W przypadku niektórych aplikacji gRPC na platformie .NET stanowi alternatywę dla obiektów WebSocket.

Wymagania wstępne

  • Każdy system operacyjny obsługujący ASP.NET Core:
    • Windows 7/ Windows Server 2008 lub nowszy
    • Linux
    • macOS
  • Jeśli aplikacja działa w systemie Windows z usługami IIS:
    • Windows 8/ Windows Server 2012 lub nowszy
    • USŁUGI IIS 8/ IIS 8 Express
    • Należy włączyć zestawy WebSocket. Zobacz sekcję pomocy technicznej usług IIS/IIS Express.
  • Jeśli aplikacja działa w systemie HTTP.sys:
    • Windows 8/ Windows Server 2012 lub nowszy
  • Aby uzyskać informacje o obsługiwanych przeglądarkach, zobacz Can I use (Czy mogę używać).

Konfigurowanie oprogramowania pośredniczącego

Dodaj oprogramowanie pośredniczące WebSockets w Configure metodzie Startup klasy :

app.UseWebSockets();

Uwaga

Jeśli chcesz zaakceptować żądania protokołu WebSocket w kontrolerze, wywołanie app.UseWebSockets musi nastąpić przed app.UseEndpoints.

Można skonfigurować następujące ustawienia:

  • KeepAliveInterval - Jak często wysyłać ramki "ping" do klienta, aby zapewnić, że serwery proxy zachowają otwarte połączenie. Wartość domyślna to dwie minuty.
  • AllowedOrigins — Lista dozwolonych wartości nagłówków źródła dla żądań protokołu WebSocket. Domyślnie wszystkie źródła są dozwolone. Aby uzyskać szczegółowe informacje, zobacz "Ograniczenie źródła protokołu WebSocket".
var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};

app.UseWebSockets(webSocketOptions);

Akceptowanie żądań protokołu WebSocket

Gdzieś później w cyklu życia żądania (na przykład w Configure metodzie lub w metodzie akcji) sprawdź, czy jest to żądanie protokołu WebSocket i zaakceptuj żądanie protokołu WebSocket.

Poniższy przykład pochodzi z późniejszej Configure części metody:

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();
    }

});

Żądanie protokołu WebSocket może pojawić się pod dowolnym adresem URL, ale ten przykładowy kod akceptuje tylko żądania dla elementu /ws.

Podobne podejście można zastosować w metodzie kontrolera:

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;
        }
    }

W przypadku korzystania z protokołu WebSocket należy zachować potok oprogramowania pośredniczącego uruchomiony przez czas trwania połączenia. Jeśli próbujesz wysłać lub odebrać komunikat protokołu WebSocket po zakończeniu potoku oprogramowania pośredniczącego, może wystąpić wyjątek podobny do następującego:

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'.

Jeśli używasz usługi w tle do zapisywania danych w zestawie WebSocket, upewnij się, że potok oprogramowania pośredniczącego jest uruchomiony. W tym celu należy użyć elementu TaskCompletionSource<TResult>. Przekaż element TaskCompletionSource do usługi w tle i wywołaj go TrySetResult po zakończeniu pracy z zestawem WebSocket. Task Następnie await właściwość podczas żądania, jak pokazano w poniższym przykładzie:

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

        BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

        await socketFinishedTcs.Task;
    }
});

Wyjątek zamknięty protokołu WebSocket może również wystąpić w przypadku powrotu zbyt szybko z metody akcji. Podczas akceptowania gniazda w metodzie akcji zaczekaj na ukończenie kodu, który używa gniazda przed powrotem z metody akcji.

Nigdy nie używaj Task.Waitwywołań blokowania , Task.Resultlub podobnych wywołań blokujących, aby poczekać na zakończenie gniazda, ponieważ może to powodować poważne problemy z wątkami. Zawsze używaj polecenia await.

Wysyłanie i odbieranie komunikatów

Metoda AcceptWebSocketAsync uaktualnia połączenie TCP z połączeniem Protokołu WebSocket i udostępnia WebSocket obiekt. Użyj obiektu do wysyłania i odbierania WebSocket komunikatów.

Pokazany wcześniej kod akceptujący żądanie Protokołu WebSocket przekazuje WebSocket obiekt do Echo metody. Kod odbiera komunikat i natychmiast wysyła ten sam komunikat. Komunikaty są wysyłane i odbierane w pętli, dopóki klient nie zamknie połączenia:

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

Po zaakceptowaniu połączenia protokołu WebSocket przed rozpoczęciem pętli potok oprogramowania pośredniczącego kończy się. Po zamknięciu gniazda odwija się potok. Oznacza to, że żądanie zatrzymuje się w potoku po zaakceptowaniu protokołu WebSocket. Po zakończeniu pętli i zamknięciu gniazda żądanie będzie kontynuowane z powrotem do potoku.

Obsługa rozłączeń klientów

Serwer nie jest automatycznie informowany, gdy klient rozłącza się z powodu utraty łączności. Serwer odbiera komunikat rozłączenia tylko wtedy, gdy klient go wyśle, co nie może zostać wykonane, jeśli połączenie internetowe zostanie utracone. Jeśli chcesz wykonać jakąś akcję w takim przypadku, ustaw limit czasu po odebraniu niczego od klienta w określonym przedziale czasu.

Jeśli klient nie zawsze wysyła komunikaty i nie chcesz przekroczyć limitu czasu tylko dlatego, że połączenie nie działa bezczynnie, klient używa czasomierza do wysyłania komunikatu ping co X sekund. Na serwerze, jeśli komunikat nie dotarł do 2*X sekund po poprzednim, zakończ połączenie i zgłoś, że klient rozłączył się. Poczekaj dwa razy w oczekiwanym przedziale czasu, aby pozostawić dodatkowy czas na opóźnienia sieci, które mogą wstrzymać komunikat ping.

Uwaga

Wewnętrzna ManagedWebSocket obsługuje ramki Ping/Pong niejawnie, aby zachować połączenie aktywne, jeśli KeepAliveInterval opcja jest większa niż zero, która domyślnie wynosi 30 sekund (TimeSpan.FromSeconds(30)).

Ograniczenie źródła protokołu WebSocket

Ochrona zapewniana przez mechanizm CORS nie ma zastosowania do obiektów WebSocket. Przeglądarki nie:

  • Wykonywanie żądań przed lotem CORS.
  • Przestrzegaj ograniczeń określonych w Access-Control nagłówkach podczas wprowadzania żądań protokołu WebSocket.

Jednak przeglądarki wysyłają nagłówek podczas wysyłania Origin żądań protokołu WebSocket. Aplikacje powinny być skonfigurowane do sprawdzania poprawności tych nagłówków, aby upewnić się, że dozwolone są tylko obiekty WebSocket pochodzące z oczekiwanych źródeł.

Jeśli hostujesz serwer na "https://server.com" i hostowanie klienta w "https://client.com", dodaj "https://client.com" do AllowedOrigins listy obiektów WebSocket w celu zweryfikowania.

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

app.UseWebSockets(webSocketOptions);

Uwaga

Nagłówek Origin jest kontrolowany przez klienta i, podobnie jak Referer nagłówek, może być fałszywy. Nie używaj tych nagłówków jako mechanizmu uwierzytelniania.

Obsługa usług IIS/IIS Express

System Windows Server 2012 lub nowszy oraz system Windows 8 lub nowszy z usługami IIS/IIS Express 8 lub nowszym ma obsługę protokołu WebSocket.

Uwaga

Obiekty WebSocket są zawsze włączone w przypadku korzystania z usług IIS Express.

Włączanie obiektów WebSocket w usługach IIS

Aby włączyć obsługę protokołu WebSocket w systemie Windows Server 2012 lub nowszym:

Uwaga

Te kroki nie są wymagane w przypadku korzystania z usług IIS Express

  1. Użyj kreatora Dodawanie ról i funkcji z menu Zarządzanie lub linku w Menedżer serwera.
  2. Wybierz pozycję Instalacja oparta na rolach lub oparta na funkcjach. Wybierz Dalej.
  3. Wybierz odpowiedni serwer (domyślnie wybierany jest serwer lokalny). Wybierz Dalej.
  4. Rozwiń węzeł Serwer sieci Web (IIS) w drzewie Role, rozwiń węzeł Serwer sieci Web, a następnie rozwiń węzeł Tworzenie aplikacji.
  5. Wybierz pozycję Protokół WebSocket. Wybierz Dalej.
  6. Jeśli dodatkowe funkcje nie są potrzebne, wybierz pozycję Dalej.
  7. Wybierz Zainstaluj.
  8. Po zakończeniu instalacji wybierz pozycję Zamknij , aby zamknąć kreatora.

Aby włączyć obsługę protokołu WebSocket w systemie Windows 8 lub nowszym:

Uwaga

Te kroki nie są wymagane w przypadku korzystania z usług IIS Express

  1. Przejdź do pozycji Panel sterowania>Programy>Programy i funkcje>Włącz lub wyłącz funkcje systemu Windows (po lewej stronie ekranu).
  2. Otwórz następujące węzły: Funkcje tworzenia aplikacji internetowych usług Internet Information Services>World Wide Web Services.>
  3. Wybierz funkcję Protokół WebSocket. Wybierz przycisk OK.

Wyłączanie protokołu WebSocket podczas korzystania z socket.io w środowisku Node.js

W przypadku korzystania z obsługi protokołu WebSocket w socket.io w środowisku Node.js wyłącz domyślny moduł WebSocket usług IIS przy użyciu webSocket elementu w pliku web.config lub applicationHost.config. Jeśli ten krok nie zostanie wykonany, moduł Iis WebSocket próbuje obsłużyć komunikację protokołu WebSocket, a nie Node.js i aplikację.

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

Przykładowa aplikacja

Przykładowa aplikacja , której towarzyszy ten artykuł, jest aplikacją echo. Ma ona stronę internetową, która tworzy połączenia protokołu WebSocket, a serwer ponownie wysyła wszystkie komunikaty odbierane z powrotem do klienta. Przykładowa aplikacja nie jest skonfigurowana do uruchamiania z poziomu programu Visual Studio z usługami IIS Express, dlatego uruchom aplikację w powłoce dotnet run poleceń za pomocą polecenia i przejdź w przeglądarce do http://localhost:5000. Na stronie internetowej jest wyświetlany stan połączenia:

Initial state of webpage before WebSockets connection

Wybierz pozycję Połączenie, aby wysłać żądanie protokołu WebSocket do wyświetlonego adresu URL. Wprowadź komunikat testowy i wybierz pozycję Wyślij. Po zakończeniu wybierz pozycję Zamknij gniazdo. Sekcja Dziennik komunikacji raportuje każdą otwartą, wysyłaną i zamykaną akcję w miarę ich działania.

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