Подтвердить что ты не робот

Использование WebSockets с веб-интерфейсом ASP.NET

Каков предпочтительный метод использования сырых веб-макетов в приложении ASP.NET Web API?

Мы хотели бы использовать двоичные WebSockets на нескольких наших интерфейсах нашего приложения ASP.NET Web API. Мне сложно определить, как это должно быть сделано, поскольку в .NET для .NET существует несколько противоречивых и/или устаревших реализаций.

Есть примеры, которые выглядят как ASP.NET, такие как один, но я думаю, что должно быть средство для использования веб-сокетов в рамках веб-API. Как я знаю, вы можете использовать Signalr в WebAPI.

Я думал, что использование Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler будет работать, но я не уверен, как связать WebSocketHandler с контроллером...

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

    ...
}

К сожалению, AcceptWebSocketRequest больше не принимает WebSocketHandler, вместо этого его новая подпись...

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

Есть ли у кого-нибудь ссылка или быстрый пример внедрения сырых веб-сайтов в приложении ASP.NET Web API, которое является актуальным?

4b9b3361

Ответ 1

UPDATE: После нескольких исследований, проведенных мной и коллегой, мы пришли к выводу, что класс WebSocketHandler не предназначен для использования вне внутренних процессов SignalR. Поскольку нет очевидных средств для использования WebSocketHandler, изолированного от SignalR. Это печально, так как я нахожу его интерфейсы немного более высокими, чем интерфейсы System.Web/System.Net. Более того, описанный ниже метод использует HttpContext, который, как мне кажется, следует избегать.

Таким образом, мы планируем использовать подход, подобный тому, который был показан г-ном Фишером, но с немного большим вкусом Web API. Как это... (ПРИМЕЧАНИЕ: наш сокет - только для записи, но я обнаружил, что вы ДОЛЖНЫ выполнять операции чтения, чтобы вы хотели правильно обновить WebSocket.State.

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

ПРИМЕЧАНИЕ. Очевидно, что проверка ошибок и правильное использование CancellationToken оставлены в качестве упражнения для читателя.

Ответ 2

Это старый вопрос, но я хотел бы добавить еще один ответ на этот вопрос.
Оказывается, вы МОЖЕТЕ использовать его, и я не знаю, почему они сделали так "скрытым". Было бы хорошо, если бы кто-нибудь мог объяснить мне, что случилось с этим классом, или если то, что я здесь делаю, как-то "запрещено" или "плохой дизайн".

Если мы посмотрим в Microsoft.Web.WebSockets.WebSocketHandler, мы найдем этот общедоступный метод:

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

Этот метод скрыт от intellisense, но он существует и может быть вызван без ошибок компиляции.
Мы можем использовать этот метод, чтобы получить задачу, которую нам нужно вернуть в методе AcceptWebSocketRequest. Проверьте это:

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

И затем в моем контроллере 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;
    }
}

Это работает отлично. OnMessage запускается и возвращается к моему встроенному JavaScript WebSocket...

Ответ 3

Я нашел этот пример:

Пример кода (воспроизводится из сообщения):

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

Как насчет использования SignalR 2?

  • Может быть установлен через NuGet
  • Для .NET 4.5 +
  • Не требуется постоянная петля
  • Возможно вещание - Учебник здесь

Ответ 5

Обмен моим кодом на основе ответа Тони с более чистой обработкой задач. Этот код отправляет текущее время UTC примерно каждую секунду:

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