在 ASP.NET Web API 应用程序中使用原始 Websocket 的首选方法是什么?
我们希望在 ASP.NET Web API 应用程序的几个接口上使用 binary WebSocket。我很难确定应该如何完成此操作,因为 .NET 的在线实现似乎有几个相互冲突和/或过时的实现。
有一些看起来像这样的 ASP.NET one示例,但我认为必须有一种方法可以在 Web API 框架内使用 websockets。据我所知,您可以在 WebAPI 中使用 Signalr。
我认为使用 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 应用程序中实现原始 websockets 的链接或快速示例吗?
更新: 经过我自己和同事的更多研究,我们得出的结论是 WebSocketHandler 类似乎不打算在 SignalR 的内部进程之外使用。因为没有明显的方法来利用与 SignalR 隔离的 WebSocketHandler。不幸的是,我发现它的接口比 System.Web/System.Net 接口稍微高级一些。此外,下面描述的方法使用了 HttpContext,我相信应该避免使用。
因此,我们计划采用与 Mrchief 所示的方法类似的方法,但具有更多的 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 的正确使用留给读者作为练习。
这是一个较旧的问题,但我想为此添加另一个答案。
事实证明,你可以使用它,但我不知道为什么他们把它做得如此“隐藏”。如果有人可以向我解释这个类有什么问题,或者我在这里所做的事情在某种程度上是“禁止的”或“糟糕的设计”,那就太好了。
如果我们查看
Microsoft.Web.WebSockets.WebSocketHandler
,我们会发现这个公共方法:
[EditorBrowsable(EditorBrowsableState.Never)]
public Task ProcessWebSocketRequestAsync(AspNetWebSocketContext webSocketContext);
此方法对智能感知是隐藏的,但它确实存在,并且可以在没有编译错误的情况下调用。
我们可以使用这个方法来获取我们需要在
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...
我找到了这个示例:
示例代码(转载自帖子):
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;
}
}
}
}
根据托尼的回答分享我的代码,并进行更清晰的任务处理。此代码大约每秒发送一次当前 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
}
}
}
使用 SignalR 2 怎么样?
这是一个老问题,但不幸的是,直到今天还没有官方对编写纯 WebSocket 服务器的良好支持。如果您无法使用 SignalR,官方的后备方案是 System.Net.WebSockets。
为了使 SignalR 具有今天的性能和可靠性,我们付出了相当多的努力,这使得差距变得更大。
出于这个原因,我创建了一个库,它是 SignalR 的一个分支,但剥离了所有自定义协议。您可以在这里找到它。它现在作为 nuget 包(SimpleR.Server)提供,希望它能有所帮助,如果有任何问题和建议,请联系我或在 github 中提出问题。