Sockets in .Net Core – Part 3

Framing using Pipelines

One of the latest additions to .Net Core 2.x was Pipelines, that I consider “Microsoft to the rescue” concept. In a previous article I talked about manual framing which was the most popular solution despite of optimizations and nuances, until Microsoft release the Pipelines package.

If you haven’t read the previous article about framing I suggest you to review it, it will help to understand pipelines much better.

This code creates a Pipe and starts a writer and a reader method. Something very interesting is the concept of decoupling reader and writer. In theory, we don’t need to write anything we are reading from the socket leading a potential null writer implementation.

Names can be a bit confusing in this case. The “Writer” means a Pipe Writer, it will consume the socket and write the Pipe. The Pipe is a very optimized circular queue intended to manage back pressure and flow control efficiently. It saves us of building our own buffers but also controls very important aspects of network communications.

Both tasks are executed and never end until connection is closed. It will be easier to see in the following code.

At the beginning this code might seem intimidating but it is actually very simple. First, we allocate a buffer at the application level from the Pipe Writer. Socket reading is exactly the same of before, the difference is now we will write our bytes to the Pipe which will efficiently manage our buffer. Exception handling is added as a real-life example.

Once we write data to the writer to need to inform the Reader that data is ready using FlushAsync. This method has two goals, making data available and also returning a flag to indicate communication is finished. IsCompleted means there is no more data to read and we can exit which is what this code does. The writer signals when it is done with the Complete method.

Part 3: Pipe reading – Framing

I have to tell you the truth. Framing is not entire eliminated, it is still necessary to determine boundaries, but pipelines create a way simpler code that let you focus on the main part which is trivial after releasing the heavy work to the pipes.

Code is extremely simple and way cleaner. We need to read from the Pipe reader that handles the buffer for us, extracting a ReadOnlySequence of bytes. The code that searches for boundaries uses byte array slicing and markers to determine messages.

In the same way we advance the writer position in the pipe we need to do it in the reader, indicating where we ended reading. Same concept applies to stop reading, if the flag for completed is set, socket has finished reading and it’s safe to leave. The pipe reader also needs to flag the pipe as complete before ending.

Once both tasks exit the main method used to orchestrate will detect the tasks have finished and it will exit as well.

Summary

If you are using .Net Core 2.2 or greater and you are still using manual buffer handling, switch to Pipelines immediately. It will simplify the code and also, performance will mostly always be better, especially if you are managing multiple threads at the same time. I noted a significant better CPU utilization after switching to Pipelines.

Published by maxriosflores

Solution Architect for a decade.
I designed, built and implemented software solutions for more than 25 years and every single day more interested on technology. I learned to code in a Texas Instruments with 16kb at 8 years old. I shared this passion with friends coding CZ Spectrums, MSX's and C64's. I worked in computers since my early 17's with super old tools like plain C and Quick Basic. I love math and computers as much as outdoors and family life.
View more posts