Using WebSockets with ASP.NET Web API
在ASP.NET Web API应用程序中使用原始Websocket的首选方法是什么?
我们想在ASP.NET Web API应用程序的几个接口上使用二进制WebSocket。我很难确定应该怎么做,因为.NET似乎在线存在多个冲突和/或过时的实现。
有些例子看起来像是ASP.NET,但我认为必须有一种在Web API框架中使用websocket的方法。据我所知,您可以在WebAPI中使用Signalr。
我认为可以使用Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler,但是我不确定如何将WebSocketHandler链接到控制器...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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,而是其新签名为...
1 | public void AcceptWebSocketRequest(Func<AspNetWebSocketContext, Task> userFunc) |
有人在最新的ASP.NET Web API应用程序中具有实现原始websocket的链接或快速示例吗?
这是一个较旧的问题,但我想对此添加另一个答案。
事实证明,您可以使用它,但我不知道他们为什么如此"隐藏"。如果有人可以向我解释该课程有什么问题,或者我在这里做的是某种"禁止"或"不良设计",那就太好了。
如果我们查看
1 2 | [EditorBrowsable(EditorBrowsableState.Never)] public Task ProcessWebSocketRequestAsync(AspNetWebSocketContext webSocketContext); |
此方法在智能感知中是隐藏的,但是它已经存在,可以被调用而不会产生编译错误。
我们可以使用此方法在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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控制器中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 ...
更新:经过我本人和同事的进一步研究,我们得出的结论是,WebSocketHandler类似乎不打算在SignalR的内部过程之外使用。由于没有明显的方法来利用与SignalR隔离的WebSocketHandler。这是不幸的,因为我发现它的接口比System.Web / System.Net接口更高级。而且,下面描述的方法使用了HttpContext,我相信应该避免使用它。
因此,我们计划采用与Mrchief所示方法类似的方法,但具有更多的Web API风格。像这样...(注意:我们的套接字是只写的,但是我发现您必须执行要使WebSocket.State正确更新的读取操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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的正确使用留给读者练习。
我找到了这个例子:
示例代码(摘自帖子):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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; } } } } |
基于Tony的答案共享我的代码,并提供更简洁的任务处理。该代码大约每秒钟发出当前UTC时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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怎么办?
- 可以通过NuGet安装
- 对于.NET 4.5
- 无需永久循环
-
可以广播
-这里的教程