关于c#:将WebSocket与ASP.NET Web API结合使用

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的链接或快速示例吗?


这是一个较旧的问题,但我想对此添加另一个答案。
事实证明,您可以使用它,但我不知道他们为什么如此"隐藏"。如果有人可以向我解释该课程有什么问题,或者我在这里做的是某种"禁止"或"不良设计",那就太好了。

如果我们查看Microsoft.Web.WebSockets.WebSocketHandler,则会发现以下公共方法:

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

此方法在智能感知中是隐藏的,但是它已经存在,可以被调用而不会产生编译错误。
我们可以使用此方法在AcceptWebSocketRequest方法中获取需要返回的任务。检查一下:

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
  • 无需永久循环
  • 可以广播
    -这里的教程