Использование 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, которое является актуальным?
Ответы
Ответ 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
}
}
}