Click or drag to resize

BinaryTextEncoding Class

Represents a binary text exconding.
Inheritance Hierarchy
SystemObject
  Demo3D.IOBinaryTextEncoding

Namespace:  Demo3D.IO
Assembly:  Demo3D.IO (in Demo3D.IO.dll) Version: 15.0.2.11458
Syntax
C#
public abstract class BinaryTextEncoding

The BinaryTextEncoding type exposes the following members.

Constructors
  NameDescription
Protected methodBinaryTextEncoding
Initializes a new instance of the BinaryTextEncoding class
Top
Properties
  NameDescription
Public propertyStatic memberDefault
The default encoding is 2-byte big-endian length-encoded ASCII.
Public propertyStatic memberCode exampleFixedLengthASCII
An encoder that will encode and decode fixed length ASCII string.
Public propertyStatic memberCode exampleFixedLengthUnicode
An encoder that will encode and decode fixed length Unicode string.
Public propertyStatic memberCode exampleLengthEncodedASCII1
Returns an encoder that will encode and decode 1-byte length encoded ASCII string. The string is formatted in the data buffer with the first byte containing the length of the string that follows. The characters in the string are ASCII.
Public propertyStatic memberCode exampleLengthEncodedASCII2BE
Returns an encoder that will encode and decode 2-byte big-endian length encoded ASCII string. The string is formatted in the data buffer with the first two bytes containing the length of the string that follows. The characters in the string are ASCII.
Public propertyStatic memberCode exampleLengthEncodedASCII2LE
Returns an encoder that will encode and decode 2-byte little-endian length encoded ASCII string. The string is formatted in the data buffer with the first two bytes containing the length of the string that follows. The characters in the string are ASCII.
Public propertyStatic memberCode exampleLengthEncodedASCII4LE
Returns an encoder that will encode and decode 4-byte little-endian length encoded ASCII string. The string is formatted in the data buffer with the first two bytes containing the length of the string that follows. The characters in the string are ASCII.
Public propertyStatic memberCode exampleLengthEncodedUnicode2LE
Returns an encoder that will encode and decode 2-byte little-endian length encoded Unicode string. The string is formatted in the data buffer with the first two bytes containing the length of the string that follows. The characters in the string are Unicode.
Public propertyStatic memberCode exampleNullTerminatedASCII
An encoder that will encode and decode null terminated ASCII strings.
Public propertyStatic memberCode exampleNullTerminatedUnicode
An encoder that will encode and decode null terminated Unicode strings.
Public propertyStatic memberCode exampleStxEtx
An encoder that will encode and decode STX/ETX delimted ASCII strings.
Top
Methods
  NameDescription
Public methodStatic memberCode exampleFixedLength
Returns an encoder that will encode and decode fixed length string given a character encoding.
Public methodGetByteCount(BinaryString)
Calculates the number of bytes produced by encoding the characters in the specified string.
Public methodGetByteCount(String)
Calculates the number of bytes produced by encoding the characters in the specified string.
Public methodGetBytes(BinaryString)
Encodes a string into a byte array.
Public methodGetBytes(BinaryString, IDataWriter)
Encodes a string into a byte array.
Public methodGetBytes(BinaryString, ArraySegmentByte)
Encodes a string into a byte array.
Public methodGetBytes(BinaryString, Byte, Int32, Int32)
Encodes a string into a byte array.
Protected methodGetBytesInternal
Encodes a string into a byte array.
Public methodGetString(ArraySegmentByte)
Decodes a string from a byte array.
Public methodGetString(IDataReader)
Decodes a string from a byte array.
Public methodGetString(Byte, Int32, Int32)
Decodes a string from a byte array.
Public methodGetStringAsync
Decodes a string from a byte array.
Protected methodGetStringInternal
Decodes a string from a byte array.
Protected methodGetStringInternalAsync
Decodes a string from a byte array.
Public methodStatic memberCode exampleLengthEncoded
Returns an encoder that will encode and decode length encoded string.
Public methodStatic memberCode exampleNullTerminated
Returns an encoder that will encode and decode null terminated string given a character encoding.
Public methodStatic memberCode exampleStringDelimited
Returns an encoder that will encode and decode string-delimited strings given a character encoding.
Top
Remarks

Like System.Text.Encoding except that this class takes into account not just the encoding of the characters in the text, but also the marashalling of the characters into the byte stream. It doesn't just assume it's "length encoded" as BinaryWriter does. It also copes with binary data containing nulls and control characters, and removes them from the string.

Use of this class is essential for passing string data between peers. All the IO ReadString and WriteString methods use an instance of this class to determine how to encode strings into network/IO packets. (See ReadString(IDataReader, BinaryTextEncoding) and WriteString(IDataWriter, BinaryString, BinaryTextEncoding) for example).

Examples

The following example shows simple examples for reading and writing strings.

C#
// These first two examples show how to read/write variable-length strings to/from a packet.
public void VariableLengthStrings(IDataReader receivedPacket, IDataWriter packetToSend) {
    // Reads all the bytes in 'receivedPacket' and converts it all into an ASCII string.
    var str = receivedPacket.ReadString(BinaryTextEncoding.FixedLengthASCII);

    // Writes the string "hello" into a packet ready to be sent to the peer.
    packetToSend.WriteString("hello", BinaryTextEncoding.FixedLengthASCII);
}

// Sometimes there's more data in a packet than just one string.  In which case, it may be
// necessary to specify exactly how long the string is that we're expecting to read.
public void FixedLengthStrings(IDataReader receivedPacket, ushort lengthOfString) {
    // Reads 'lengthOfString' bytes of data from 'receivedPacket' and then interprets them as Unicode (UTF-16).
    var str = receivedPacket.ReadString(BinaryTextEncoding.FixedLength(System.Text.Encoding.Unicode, lengthOfString));
}

Very often, especially when communicating over TCP, strings are encapsulated in some way that describes how much of the incoming data belongs to the string. Common techniques include null-terminated (NullTerminated(Encoding)), length-encoded (LengthEncoded(Int32, Boolean, Encoding)), or string-delimited (StringDelimited(String, String, Encoding)) strings.

Null-terminated is common with ASCII strings.

C#
public void NullTerminatedStrings(IDataReader receivedPacket, IDataWriter packetToSend) {
    // Reads bytes of data from 'receivedPacket' as an ASCII string until it reads the 'null' character.
    var str = receivedPacket.ReadString(BinaryTextEncoding.NullTerminatedASCII);

    // Writes the string "hello" into a packet as Unicode characters ready to be sent to the peer,
    // finishing it with a 'null' character.
    packetToSend.WriteString("hello", BinaryTextEncoding.NullTerminatedUnicode);
}

Length-encoding is common when communicating with low-level systems; systems that typically use a binary protocol.

C#
public void LengthEncodedStrings(IDataReader receivedPacket, IDataWriter packetToSend) {
    // Length encoded strings are prefixed with a binary number that states exactly how many
    // bytes belong to the string.  Most commonly, the number is itself 2-bytes (a 16-bit number),
    // and that number is itself encoded using the Big Endian format.
    var str = receivedPacket.ReadString(BinaryTextEncoding.LengthEncodedASCII2BE);

    // This example uses a 4-byte (32-bits) Little Endian number to describe the number of bytes
    // in the string.  The string is then interpretted as Unicode.
    str = receivedPacket.ReadString(BinaryTextEncoding.LengthEncoded(4, true, System.Text.Encoding.Unicode));

    // Writes the string "hello" into a packet ready to be sent to the peer.  The string is prepended with
    // two bytes 0x00, 0x05 (the length of the string).
    packetToSend.WriteString("hello", BinaryTextEncoding.LengthEncodedASCII2BE);
}

String-delimited is common when communicating with high-level systems; systems that typically use a string-based protocol. These protocols usually encode the packet entirely in ASCII.

C#
public string StringDelimitedStrings(IDataReader receivedPacket) {
    // The string to be read will start with '<<' and end with '>>'.  The string returned will have the
    // quotes ('<<' and '>>') stripped off.  Just the original string between the quotes will be returned.
    return receivedPacket.ReadString(BinaryTextEncoding.StringDelimited("<<", ">>"));
}

public string PipeDelimitedStrings(IDataReader receivedPacket) {
    // The string is terminated with a '|' character.  Effectively the protocol uses strings separated with '|'.
    return receivedPacket.ReadString(BinaryTextEncoding.StringDelimited(null, "|"));
}

public void StxEtxDelimitedStrings(IDataReader receivedPacket, IDataWriter packetToSend) {
    // The standard STX ETX encapsulated format.
    var str = receivedPacket.ReadString(BinaryTextEncoding.StxEtx);

    // Writes the string "hello" prepended with the STX character and appended with the ETX character.
    packetToSend.WriteString("hello", BinaryTextEncoding.StxEtx);
}

The following examples shows complete client and server implementations using a simple string-based protocol. The high-level system connects to this server, and then the Demo3D model (the server) and the high-level system (the client) exchange string commands. The protocol dictates that each string is encapsulated with the STX and ETX characters.

Either side can initiate a command. The other side needs to respond with "ACK" once the command has been received and executed.

C#
using System;
using System.Threading.Tasks;
using Demo3D.IO;
using Demo3D.Native;
using Demo3D.Net;
using Demo3D.Net.Protocols;
using Demo3D.Visuals;

namespace Examples.Net.StringServerExample {
    [Auto] public class Server {
        [Auto] PrintDelegate            print;
        [Auto] SimplePropertyValue<int> tcpPort;                   // The port to run the server on.
        [Auto] TextVisual               StatusDisplay;             // Requires corresponding TextVisual child visual
        [Auto] TextVisual               MessageDisplay;            // Requires corresponding TextVisual child visual

        ProtocolSocket                 serverSocket;
        ServerClient<IPacketIOService> clientSocket;
        BinaryTextEncoding             stxEtx = BinaryTextEncoding.StringDelimited("\u0002", "\u0003");

        void HandleMessage(ServerClient<IPacketIOService> clientSocket, string message) {
            print("Server: Message received \"" + message + "\"");

            if (message == "ACK") {                                // One of our requests was acknowledged.
                return;
            }

            if (MessageDisplay != null) {
                MessageDisplay.Text = "Received: \"" + message + "\"";
            }

            if (message.StartsWith("Hello")) {                     // Raise the lift, and then return "ACK".
                // ... Raise the lift here...
                print("Server: Sending message \"ACK\"");
                var buffer = stxEtx.GetBytes("ACK");               // Encode with STX/ETX.
                clientSocket.IO.Write(buffer);
            }
        }

        void ReceiveMessage(ProtocolSocket socket, object service, NotifyDataChangedEventArgs e) {
            try {
                var    args    = (PacketChangedEventArgs)e;        // A TCP server uses PacketChangedEventArgs.
                var    message = args.GetData();                   // Read from the packet.
                string str     = stxEtx.GetString(message);        // Decode STX/ETX string.

                // Handle the message.
                HandleMessage(clientSocket, str);
            }
            catch (Exception x) {
                socket.Close(x);
            }
        }

        // Disconnect the client.
        void CloseClient() {
            if (clientSocket != null) {
                clientSocket.Close();
                clientSocket.IO.DataChanged -= ReceiveMessage;
                clientSocket = null;
            }
        }

        // Close the server.
        void CloseServer() {
            if (serverSocket != null) {
                serverSocket.Close();
                serverSocket = null;
            }
        }

        // Accecpt a new client connection.
        Task ServiceClient(ServerClient<IPacketIOService> connection) {
            CloseClient();                // Close any existing client connection (this example only allows one client at a time).
            clientSocket = connection;
            clientSocket.IO.DataChanged += ReceiveMessage;
            return Task.CompletedTask;
        }

        [Auto] void OnReset(Visual sender) {
            if (clientSocket != null || serverSocket != null) {
                print("Server: Closing connection and stopping server");
                CloseServer();
                CloseClient();
            }

            if (StatusDisplay != null) {
                StatusDisplay.Text           = "Status: Closed";
                StatusDisplay.Material.Color = System.Drawing.Color.Gray;
            }

            if (MessageDisplay != null) {
                MessageDisplay.Text = "Received: ";
            }
        }

        [Auto] void OnInitialize(Visual sender) {
            CloseServer();
            CloseClient();

            // Open a server on the configured tcpPort.
            serverSocket = TcpServer.Open(tcpPort, ServiceClient);

            if (StatusDisplay != null) {
                StatusDisplay.Text           = "Status: Connected Port " + tcpPort.ToString();
                StatusDisplay.Material.Color = System.Drawing.Color.Lime;
            }
        }
    }
}
C#
using System;
using System.Collections;
using System.Threading.Tasks;
using Demo3D.IO;
using Demo3D.Native;
using Demo3D.Net;
using Demo3D.Net.Protocols;
using Demo3D.Visuals;

namespace Examples.Net.StringClientExample {
    [Auto] public class Client {
        [Auto] IBuilder                    app;
        [Auto] PrintDelegate               print;
        [Auto] SimplePropertyValue<string> tcpAddress;               // The hostname to connect to.
        [Auto] SimplePropertyValue<int>    tcpPort;                  // The port to connect to.
        [Auto] TextVisual                  StatusDisplay;            // Requires corresponding TextVisual child visual

        Client<IPacketIOService> tcpClient;
        BinaryTextEncoding       stxEtx = BinaryTextEncoding.StringDelimited("\u0002", "\u0003");

        async Task SendMessages() {
            // Send a message to the server once a second while the model is running.
            for (int msg = 0; app.Running; msg++) {
                var str = "Hello " + msg.ToString();
                print("Client: Sending message \"" + str + "\"");

                var bytes = stxEtx.GetBytes(str);                    // Encode with STX/ETX
                await tcpClient.IO.WriteAsync(bytes);

                // Wait one second.
                await Task.Delay(1000);
            }
        }

        void HandleMessage(Client<IPacketIOService> clientSocket, string message) {
            print("Client: Message received \"" + message + "\"");

            if (message == "ACK") {                                  // One of our requests was acknowledged.
                return;
            }
        }

        void ReceiveMessage(ProtocolSocket socket, object service, NotifyDataChangedEventArgs e) {
            try {
                var    args    = (PacketChangedEventArgs)e;          // A TCP server uses PacketChangedEventArgs.
                var    message = args.GetData();                     // Read from the packet.
                string str     = stxEtx.GetString(message);          // Decode STX/ETX string.

                // Handle the message.
                HandleMessage(tcpClient, str);
            }
            catch (Exception x) {
                socket.Close(x);
            }
        }

        void CloseConnection(Exception error = null) {
            if (tcpClient != null) {
                tcpClient.Close(error);
                tcpClient.IO.DataChanged -= ReceiveMessage;
                tcpClient = null;
            }
        }

        async Task Connect() {
            try {
                // Connect to the server using the configured tcpAddress and tcpPort.
                tcpClient = await TcpClient.OpenAsync(sync: false, tcpAddress, tcpPort);
                tcpClient.IO.DataChanged += ReceiveMessage;

                if (StatusDisplay != null) {
                    StatusDisplay.Text           = "Status: Connected " + tcpAddress.Value + ":" + tcpPort.ToString();
                    StatusDisplay.Material.Color = System.Drawing.Color.Lime;
                }

                // Start sending messages to the server.
                await SendMessages();
            }
            catch (Exception e) {
                // If there's an error, close the connection and report the error.
                CloseConnection(e);
            }
        }

        [Auto] void OnReset(Visual sender) {
            CloseConnection();

            if (StatusDisplay != null) {
                StatusDisplay.Text           = "Status: Closed";
                StatusDisplay.Material.Color = System.Drawing.Color.Gray;
            }
        }

        [Auto] IEnumerable OnInitialize(Visual sender) {
            yield return Connect();
        }
    }
}
See Also