Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to gracefully close a two-way WebSocket in .Net

I have a WebSocket server that accepts a stream of binary data from a client and responds with another stream of text data for every 4MB read. The server uses IIS 8 and asp.net web api.

Server

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);
    }
}

The c# client uses the ClientWebSocket class to connect to the server and send requests. It creates a task for receiving responses from the server that runs in parallel with the request sending. When it is done sending the requests it calls CloseAsync on the socket and then waits for the Receive task to complete.

Client

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);
            }
        }
    }
}

The problem is that the client blocks at the CloseAsync call.

What would be the correct way of gracefully closing the WebSocket in this scenario?

like image 747
tcb Avatar asked Feb 04 '15 20:02

tcb


People also ask

How do I close a WebSocket connection?

close() The WebSocket. close() method closes the WebSocket connection or connection attempt, if any. If the connection is already CLOSED , this method does nothing.

When should I close WebSocket connection?

If you are writing a server, you should make sure to send a close frame when the server closes a client connection. The normal TCP socket close method can sometimes be slow and cause applications to think the connection is still open even when it's not.

What is close frame in WebSocket?

Closing a WebSocket connection — The WebSocket Close Handshake. To close a WebSocket connection, a closing frame is sent (opcode 0x08 ). In addition to the opcode, the close frame may contain a body that indicates the reason for closing.

Is WebSocket two way communication?

The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.


1 Answers

Figured this out.

Server

Basically, I had to call the ClientWebSocket.CloseOutputAsync (instead of the CloseAsync) method to tell the framework no more output is going to be sent from the client.

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

Client

Then in the Receive function, I had to allow for socket state WebSocketState.CloseSent to receive the Close response from the server

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);
    }
}
like image 160
tcb Avatar answered Oct 12 '22 23:10

tcb