Web Sockets in .NET Core

Web Sockets are cool: they give Web Service developers a duplex data communications channel over which they can send an arbitrary binary stream, or even messages in a proprietary protocol. I think they are a fascinating blend of new fangled – powerful but not widely used – and old school – writing your own communications protocols is so much like the early days of TCP/IP.

.NET Core makes web sockets easy.  I’ve created a GitHub project, CSharpWebSocks, that demonstrates how simple they are to use.

My goal in this sort of GitHub project is to provide enough so that the project could be expanded into to a real project, but on the other hand that minimizes clutter and code not directly related to the technology that’s demonstrated.

The application in CSharpWebSocks is a file upload from client to server.  Normal files are immediately serializable into a byte stream. In the project, the client uses a web socket to write a file’s contents to the server, which writes the bytes into a new file.

In my opinion, the biggest thing missing from this demo is two-way communication over the socket. But with two-way communications, the same approach would be used for opening a socket.

Server Internals

In my project, the server sets up listening for web sockets in the Configure method of its Startup class:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole();

    log = loggerFactory.CreateLogger("CSharpWebSock");

    uploads        = new Dictionary<int, string="">();
    nextUploadID   = 1;
    idLock         = new object();
    uploadTokenRng = new Random();

    app
        .UseDeveloperExceptionPage()
        .UseRouter(BuildRoutes(app))
        .UseWebSockets()
        .Use(async (context, next) => 
        {
            // If server does not know what else to do, try a Web Socket.
            if (context.WebSockets.IsWebSocketRequest) 
            {
                // Assume that all WebSocket requests are for uploads.
                var webSocket = 
                    await context.WebSockets.AcceptWebSocketAsync();
                await Upload(context, webSocket);
            } 
            else 
            {
                await next();
            }
        });
}
</int,>

For regular HTTP requests, “Routing” –  defining handlers – is specified by the UseRouter method. What to do with web socket requests is defined in the lambda expression, which specifies that they are received by the asynchronous handler, Upload.

Client Internals

The client code that opens and sends a byte array to the server is quite terse:

/*
 * SendBytes: Write bytes to a web socket
 */
private static void SendBytes(Uri controllerUri, byte[] data)
{
    ClientWebSocket sock = new ClientWebSocket();
    Task sockTask = sock.ConnectAsync(controllerUri, CancellationToken.None);
    if (!sockTask.Wait(5000))
    {
        throw new Exception($"Timeout attempting to connect to {controllerUri}");
    }

    sock.SendAsync(new ArraySegment(data), WebSocketMessageType.Binary, true, CancellationToken.None).Wait();

Note that a single SendAsync call is used for the entire byte array; the app relies on the .NET Core to segment the message as appropriate.

Demo Operation

The CSharpWebSocks project produces two executable CSharp assemblies: a web service daemon and a command-line client. The client initiates a login request by posting an upload name (i.e., the name of the file to upload) to the server, at URI “/upload”; the server responds with a random upload token. Next, the client requests a websocket from the server, at URI “/upload/<token>“. After the socket is established, the client writes a file’s bytes to it, and then closes the socket.

When the server opens the socket, it retrieves the upload name from a cache, opens a file based on that name, and writes bytes it receives from the web socket to the file.

Glitches

I initially developed the example on 64-bit WIndows 10, and it worked without complaints. When I tried running on Linux Mint, however, the server logged an “Upload error” (see the yellow warning below) and complained of the client closing the connection “without completing the close handshake”.  The server left a zero length destination file in its directory.

WebSocketException

But in fact client executes CloseOutputAsync on the socket; I had run into a .NET Core bug, that is documented at the .NET Core Github site.

In order to get the upload to work at least some of the time, I hacked the client by adding a 2-second sleep between ending the Send and executing the close. This is really regrettable , but mostly working is better than not working at all.

Kick the Tires!

If you have an Internet connection, you can build and run the demo.

First, download and install the free .NET Core SDK from https://dot.net. You will also need to have installed git.

To build the client and server, open a console window and execute the following commands:

    1. git clone git@github.com:rstinejr/CSharpWebSocks.git
    2. cd CSharpWebSocks
    3. pushd webservice
    4. dotnet restore
    5. dotnet build
    6. popd
    7. pushd webclientCli
    8. dotnet restore
    9. dotnet build
    10. popd

To run the server, open a console window, cd to CSharpWebSocks\webservice, and execute dotnet run.

To run the client, open another console window, cd to CSharpWebSocks\webclientCli, and execute dotnet run localhost data.xml

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s