NTDLS.StreamFraming 1.2.4

There is a newer version of this package available.
See the version list below for details.
dotnet add package NTDLS.StreamFraming --version 1.2.4                
NuGet\Install-Package NTDLS.StreamFraming -Version 1.2.4                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NTDLS.StreamFraming" Version="1.2.4" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add NTDLS.StreamFraming --version 1.2.4                
#r "nuget: NTDLS.StreamFraming, 1.2.4"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install NTDLS.StreamFraming as a Cake Addin
#addin nuget:?package=NTDLS.StreamFraming&version=1.2.4

// Install NTDLS.StreamFraming as a Cake Tool
#tool nuget:?package=NTDLS.StreamFraming&version=1.2.4                

NTDLS.StreamFraming

📦 Be sure to check out the NuGet pacakge: https://www.nuget.org/packages/NTDLS.StreamFraming

NTDLS.StreamFraming is a set of extension methods for a Stream (typically TCPIP/NetworkStream) that enables reliable framing, compression, optional encryption, two-way communication and support for asynchronous query/reply. Messages are guaranteed to be received in their entirety and in the order which they were sent.

Sending a notification frame:

Here we are using an established TcpClient connection, getting its stream and then calling WriteNotification() to pacakge the payload "MyMessage" and write it to the stream. As per TCP/IP protocol, this can and will be received fragmented and/or concatenated with other bytes that are sent in the stream... but don't worry thats what NTDLS.StreamFraming is here to solve.

using (var tcpStream = tcpClient.GetStream())
{
    while (tcpClient.Connected)
    {
        string text = $"This is a message that was sent at {DateTime.Now.ToLongTimeString()}.";

        //Assemble a message frame and write it to the stream:
        tcpStream.WriteNotification(new MyMessage(text));

        Thread.Sleep(1000);
    }
    tcpStream.Close();
}

Receiving a notification frame:

Here we are using an established TcpClient connection, getting its stream and then calling ReadAndProcessFrames(). ReadAndProcessFrames will read from the stream and determine if the bytes that are received (if any) are a full frame, only a fragment or a concatenation of multiple packets and fragments. Any full frames will be split, validated, decompressed, deserialized and then the callback "ProcessFrameNotificationCallback" will be called for each valid frame.

var frameBuffer = new FrameBuffer();

using (var tcpStream = _tcpClient.GetStream())
{
    while (_tcpClient.Connected)
    {
        //Read from the stream, assemble the bytes into the original messages and call
        //  the handler for each message that was received.
        if (tcpStream.ReadAndProcessFrames(frameBuffer, ProcessFrameNotificationCallback) == false)
        {
            //If ReadAndProcessFrames() returns false then we have been disconnected.
            break;
        }
    }
    tcpStream.Close();
}

//This is the handler for a frame that was written to the stream with a call to WriteNotification().
//Note that the origianl class is received and we use patter matching to determine what we are receiving.
private void ProcessFrameNotificationCallback(IFrameNotification payload)
{
    //We recevied a message, see if it is of the type "MyMessage".
    if (payload is MyMessage myMessage)
    {
        Console.WriteLine($"Received from server: '{myMessage.Text}'");
    }
}

Sending a query frame:

Here we are using an established TcpClient connection, getting its stream and then calling WriteQuery() to pacakge the payload "MyQuery" and write it to the stream. We will then "wait" asynchronously or the client to receive and reply to the query.

using (var tcpStream = tcpClient.GetStream())
{
    while (tcpClient.Connected)
    {
        tcpStream.WriteQuery<MyQueryReply>(new MyQuery("Hello client!")).ContinueWith((o) =>
            {
                if (o.IsCompletedSuccessfully && o.Result != null)
                {
                    Console.WriteLine($"Received [QueryReply] from client: '{o.Result.Text}')");
                }
            });

        //In this example, we have to call ReadAndProcessFrames() even though we are not
        //  supplying it with any callbacks because it receives the replies from the query
        //  that is sent above and routes them to the correct query task handler.
        if (tcpStream.ReadAndProcessFrames(frameBuffer) == false)
        {
            break; //The client disconnected.
        }

        Thread.Sleep(1000);
    }
    tcpStream.Close();
}

Receiving a query frame and replying:

Here we are using an established TcpClient connection, getting its stream and then calling ReadAndProcessFrames(). ReadAndProcessFrames will read from the stream and determine if the bytes that are received are a full frame. Any full frames will be deserialized and routed to the callbacks "ProcessFrameNotificationCallback" or "ProcessFrameQueryCallback" based on their types. "ProcessFrameNotificationCallback" expects a return value to be of the type specified on the original call to WriteQuery(). The value returned from ProcessFrameQueryCallback will be framed and sent back to the originator and then routed to the appropriate waiting asynchronous task.

var frameBuffer = new FrameBuffer();

using (var tcpStream = _tcpClient.GetStream())
{
    while (_tcpClient.Connected)
    {
        //Read from the stream, assemble the bytes into the original messages and call
        //  the handler for each message that was received.
        if (tcpStream.ReadAndProcessFrames(frameBuffer, ProcessFrameNotificationCallback, ProcessFrameQueryCallback) == false)
        {
            //If ReadAndProcessFrames() returns false then we have been disconnected.
            break;
        }
    }
    tcpStream.Close();
}

//This is the handler for a frame that was written to the stream with a call to WriteNotification().
//Note that the origianl class is received and we use patter matching to determine what we are receiving.
private void ProcessFrameNotificationCallback(IFrameNotification payload)
{
    //We recevied a message, see if it is of the type "MyMessage".
    if (payload is MyMessage myMessage)
    {
        Console.WriteLine($"Received from server: '{myMessage.Text}'");
    }
}

private IFrameQueryReply ProcessFrameQueryCallback(IFrameQuery payload)
{
    //We recevied a message, see if it is of the type "MyQuery", if so reply with the type of MyQueryReply.
    if (payload is MyQuery myQuery)
    {
        Console.WriteLine($"Received [Query] from server: '{myQuery.Text}'");

        return new MyQueryReply("Hello Server!");
    }

    throw new Exception("The query type was unhandled.");
}

Supporting Classes:

These are the payload classes that are used in the examples:

Used in the examples to send a one-way notification

    internal class MyMessage : IFrameNotification
    {
        public string Text { get; set; }

        public MyMessage(string text)
        {
            Text = text;
        }
    }

Used in the examples to send a query that expects a reply:

internal class MyQuery : IFrameQuery
{
    public string Text { get; set; }

    public MyQuery(string text)
    {
        Text = text;
    }
}

Used in the examples to reply to a received query:

internal class MyQueryReply : IFrameQueryReply
{
    public string Text { get; set; }

    public MyQueryReply(string text)
    {
        Text = text;
    }
}
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.8 98 12/3/2024
1.2.7 116 6/20/2024
1.2.6 747 1/22/2024
1.2.5 205 1/4/2024
1.2.4 152 1/3/2024
1.2.3 202 12/27/2023
1.2.2 144 12/22/2023
1.2.1 292 11/15/2023
1.2.0 136 11/12/2023
1.1.0 174 11/10/2023
1.0.6 120 11/10/2023
1.0.5 175 11/8/2023
1.0.4 184 11/7/2023
1.0.3 116 11/6/2023
1.0.2 150 11/6/2023
1.0.1 130 11/6/2023
1.0.0 122 11/6/2023

Added types to json to enable derived type deserialization.