如何在.Net中优雅地关闭双向WebSocket

时间:2015-02-04 20:21:04

标签: c# asp.net-web-api websocket iis-8

我有一个WebSocket服务器,它接受来自客户端的二进制数据流,并响应每4MB读取的另一个文本数据流。服务器使用IIS 8和asp.net web api

服务器

public class WebSocketController : ApiController
{
    public HttpResponseMessage Get()
    {
        if (!HttpContext.Current.IsWebSocketRequest)
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest);
        }

        HttpContext.Current.AcceptWebSocketRequest(async (context) =>
        {
            try
            {
                WebSocket socket = context.WebSocket;

                byte[] requestBuffer = new byte[4194304];
                int offset = 0;

                while (socket.State == WebSocketState.Open)
                {
                    var requestSegment = new ArraySegment<byte>(requestBuffer, offset, requestBuffer.Length - offset);
                    WebSocketReceiveResult result = await socket.ReceiveAsync(requestSegment, CancellationToken.None);

                    if (result.MessageType == WebSocketMessageType.Close)
                    {
                        // Send one last response before closing
                        var response = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Server got " + offset + " bytes\n"));
                        await socket.SendAsync(response, WebSocketMessageType.Text, true, CancellationToken.None);

                        // Close
                        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                        break;
                    }

                    offset += result.Count;
                    if (offset == requestBuffer.Length)
                    {
                        // Regular response
                        var response = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Server got 4194304 bytes\n"));
                        await socket.SendAsync(response, WebSocketMessageType.Text, true, CancellationToken.None);
                        offset = 0;
                    }
                }
            }
            catch (Exception ex)
            {
                // Log and continue
            }
        });

        return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
    }
}

c#客户端使用ClientWebSocket类连接到服务器并发送请求。它创建一个任务,用于从服务器接收与请求发送并行运行的响应。完成发送请求后,它会在套接字上调用CloseAsync,然后等待Receive任务完成。

客户端

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebSocketClient
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                CallWebSocketServer().Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        static async Task CallWebSocketServer()
        {
            using (ClientWebSocket socket = new ClientWebSocket())
            {
                await socket.ConnectAsync(new Uri("ws://localhost/RestWebController"), CancellationToken.None);
                byte[] buffer = new byte[128 * 1024];

                Task receiveTask = Receive(socket);
                for (int i = 0; i < 1024; ++i)
                {
                    await socket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Binary, true, CancellationToken.None);
                }

                await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                receiveTask.Wait();

                Console.WriteLine("All done");
            }
        }

        static async Task Receive(ClientWebSocket socket)
        {
            try
            {
                byte[] recvBuffer = new byte[64 * 1024];
                while (socket.State == WebSocketState.Open)
                {
                    var result = await socket.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None);
                    Console.WriteLine("Client got {0} bytes", result.Count);
                    Console.WriteLine(Encoding.UTF8.GetString(recvBuffer, 0, result.Count));
                    if (result.MessageType == WebSocketMessageType.Close)
                    {
                        Console.WriteLine("Close loop complete");
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception in receive - {0}", ex.Message);
            }
        }
    }
}

问题是客户端阻止了CloseAsync电话。

在这种情况下优雅关闭WebSocket的正确方法是什么?

2 个答案:

答案 0 :(得分:10)

想出来了。

服务器

基本上,我必须调用ClientWebSocket.CloseOutputAsync(而不是CloseAsync)方法告诉框架,不再从客户端发送输出。

await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);

<强>客户端

然后在Receive函数中,我必须允许套接字状态WebSocketState.CloseSent从服务器接收Close响应

static async Task Receive(ClientWebSocket socket)
{
    try
    {
        byte[] recvBuffer = new byte[64 * 1024];
        while (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent)
        {
            var result = await socket.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None);
            Console.WriteLine("Client got {0} bytes", result.Count);
            Console.WriteLine(Encoding.UTF8.GetString(recvBuffer, 0, result.Count));
            if (result.MessageType == WebSocketMessageType.Close)
            {
                Console.WriteLine("Close loop complete");
                break;
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception in receive - {0}", ex.Message);
    }
}

答案 1 :(得分:-1)

我建议你看看这些链接:

异步服务器: https://msdn.microsoft.com/en-us/library/fx6588te%28v=vs.110%29.aspx

异步客户端: https://msdn.microsoft.com/en-us/library/bew39x2a(v=vs.110).aspx

最近我用这些链接实现了类似的东西作为例子。方法“BeginReceive”(用于服务器)和“BeginConnect”(用于客户端)启动每个新线程。所以不会有任何阻止

的东西