https://github.com/ntdls/ntdls.streamframing
Stream wrapper (typically TCPIP/NetworkStream) that enables reliable framing, compression, optional encryption and two way communication.
https://github.com/ntdls/ntdls.streamframing
framing io network socket tcp-ip
Last synced: 24 days ago
JSON representation
Stream wrapper (typically TCPIP/NetworkStream) that enables reliable framing, compression, optional encryption and two way communication.
- Host: GitHub
- URL: https://github.com/ntdls/ntdls.streamframing
- Owner: NTDLS
- License: mit
- Created: 2023-11-06T19:08:25.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-01-04T17:47:12.000Z (4 months ago)
- Last Synced: 2025-03-27T21:22:52.620Z (about 1 month ago)
- Topics: framing, io, network, socket, tcp-ip
- Language: C#
- Homepage:
- Size: 163 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 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.
```csharp
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.
```csharp
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.
```csharp
using (var tcpStream = tcpClient.GetStream())
{
while (tcpClient.Connected)
{
tcpStream.WriteQuery(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.
```csharp
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**
```csharp
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:**
```csharp
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:**
```csharp
internal class MyQueryReply : IFrameQueryReply
{
public string Text { get; set; }public MyQueryReply(string text)
{
Text = text;
}
}
```