Wednesday, June 26, 2019

Kubernetes Addendum


Kubernetes Addendum

In the previous post – Kubernetes Follow-On (Part 4) – I mentioned how the IP Address selection might be backwards.  That is, the instantiation of the Socket Server might be selecting the IP Address that should be used by the Socket Client and vice versa. 

So I went ahead and created an Ada App3 to execute on a second PC with the RemoteComponent to communicate with the Component2 of the App2 Ada project and then, after getting that working, I added to the C# NewComponent the code to communicate with the App3 RemoteComponent.  This later required that the Delivery.dat file have this new pairing added to it.

First in the Ada App2 and the new App3 the IP Address selection had to be reversed to allow the two components to communicate with each other.  Then with the trial extended to between the C# NewComponent and the App3 RemoteComponent, the IP Address selection also had to be reversed in the C# Socket Server and Socket Client.  50% chance of having the selection correct and the coin landed the wrong way up.  (Note: Until the other PC got involved all the IP Addresses were the same so it didn't matter which was selected.)

App3 is almost a clone of App2 so no need to provide the code here.  Just with RemoteComponent replacing Component2 of App2.  The Delivery.dat file already provided the pairing between these two components.  For the C# application, the pair of records to provide the names, IP Addresses, and ports for the connection had to be added to Delivery.dat.  And the NewComponent was modified to also request communication between it and the RemoteComponent of the other PC with its different IP Address.  Nothing new requiring the code to be displayed.

The changes to the Socket Server and Socket Client are simple enough.

The only change to Socket.Server.adb of Ada is that
        IPAddress := Delivery.IP_Address(MatchIndex);
in the Request function is changed to
        IPAddress := Delivery.IP_Address(Partner);
to use the IP Address specified for the opposite component of the pair. 

Similarly for Socket.Client.adb,
        IPAddress := Delivery.IP_Address(MatchIndex);
in the Request function is changed to
        IPAddress := Delivery.IP_Address(Partner);
after Partner was declared after MatchIndex
    MatchIndex : Delivery.LocationType;
    Partner    : Delivery.LocationType;
and obtained from the Delivery package via
      -- Set the IP addresses and the ports.
      if MatchIndex > 0 then
        Partner := Delivery.Partner( MatchIndex );
as in Socket.Server.

For the C# application, SocketServer.cs was changed from
                SocketData.ListenerData.list[Count].ToAddress =
                    Delivery.DeliveryTable.list[Partner].IP_Address;
to
                SocketData.ListenerData.list[Count].ToAddress =
                  Delivery.DeliveryTable.list[MatchIndex].IP_Address;
and SocketClient.cs was changed from
                SocketData.SenderData.list[Count].FromAddress =
                    Delivery.DeliveryTable.list[MatchIndex].IP_Address;
to
                int Partner = Delivery.DeliveryTable.list[MatchIndex].Partner;
                SocketData.SenderData.list[Count].FromAddress =
                    Delivery.DeliveryTable.list[Partner].IP_Address;

That's all there was to it.

Saturday, June 22, 2019

Kubernetes Follow-On (Part 4)



The previous post (Kubernetes Follow-On (Part 3)) completed as set of three posts to use Microsoft WinSock as invoked via a C interface from GNAT Ada to communicate between the components of various Ada applications whether the components reside in one application, multiple applications running on the same PC, or multiple applications running on combinations of PCs.  That is, each component (via a Delivery.dat file) had its IP address along with its send port and its receive port.  The component to which it was to communicate had its particular IP address and the same pair of ports but in the reverse order since it would send on the port that the first used to receive and vice versa.

This post will extend the communications to include applications written in C#.

There is a C# example on the internet "Basic WinSock Sockets Programming with C# and .NET" of Walt Smith https://code.msdn.microsoft.com/windowsapps/Basic-WinSock-Sockets-6448d6ed

This is a basic example that communicates with itself where the operator enters console text that the example code then sends via a socket port and receives on the same port and then displays.  (Not even in a loop to enter other text.  But, of course, if it can do it once it can do it again.)

This post will use the concept of the three previous posts to be able to send to a combination of Ada and C# applications containing components that communicate with each other.  A particular component can communicate with multiple other components via the use of a different port pair for each of the component partners.  Where the send and receive ports are unique to each component pair.  (Of course, a component could send to itself – as in the internet example – by specifying the same port for both send and receive if such a thing were desirable.)

After reworking the C# application I failed in my attempts to communicate with an Ada application.  So I decided to search the internet for C++ support of sockets since this has worked when I was trying for Named Pipe communications between C# and Ada.  (C++ needing to use similar interfaces to the C interfaces that Ada has to use.) 

This resulted in my finding "Programming Windows TCP Sockets in C++ for the Beginner" of the CodeProject.  It had its own ideas as to which side was the Server and which the Client.  So I decided to start over and attempt to follow it.

I first redid the C# application attempting to follow the approach of the CodeProject.  This resulted in my being able to communicate between the Component1 and NewComponent components of the C# application.  That is, between two components of the same application.

However, I still couldn't communicate with the Ada application.  So I decided to take the bull by the horns and redo the Ada application taking the same approach.  Pretty much a translation from C# to Ada.

This pretty much turned the structure of the Ada application on its head from what I had been using – client instead of server and vice versa.  That is, the Server listening for a connection, accepting the client connection, and Recv'ing the message with the Client requesting a Connect each time a message was to be transmitted and Send'ing the message when the Connect was successful.  As well as not retaining sockets from one message to the next as I had been doing. 

This resulted in the Ada package structure and Server vs Client nomenclature matching that of the C# class implementation using my interpretation of the C++ document.

I first had the C# application only attempt to receive from Component2 (along with the continuing sending and receiving between the local Component1 and NewComponent components) and the Ada application only attempt to transmit from its Component2 to the Component1 of the C# application.  Much to my surprise this worked the first time.

I then reversed the inter-application messaging to have Component1 send a message to the Ada application Component2.  This resulted in an exception for invalid addressing that puzzled me for half a day or so trying to figure out the code location that was throwing the exception.  When that was solved and the code corrected, it also worked.  So I completed the change to have Component1 request to both send and receive from Component2 and vice versa.

And the communications between C# and Ada via TCP Sockets was complete.  (Although, since the debug attempts haven't tried to send to another PC, the implemented usage of the IP address may need adjustment.)

Comments

And oh my; the reduction in code.  As can be seen in earlier posts such as using Named Pipes where I was sending messages between applications and then having the application figure out to what component(s) to deliver the message. 

Although, thinking about it now, it would seem that the use of named pipes could achieve the same result.  Just like the TCP sockets require a port for every component, the use of pipes must be able to use a pipe name for every component rather than every application.  Something to look into to see what would need to be done and whether there would be a similar reduction in the amount of code. 

Of course the entire messaging approach is different.  My original approach of years past was that there could be different types of messages.  Those that were just broadcast and any component of any application could register to consume the topic; and, for instance, those that were meant for a particular consumer (where ever it might be located) and that consumer component could generate a response and, if it did so, the delivery system was to route that response back to the publisher of the original message.  Such an approach would still take overhead about which the current TCP Socket approach doesn't need to be concerned.

The current approach requires that a component know in advance (at build time) which message is to be sent to which component.  And that component has to specify the component from which it is to receive a message.  The small amount of socket communications structure code then matches the components to their TCP ports (where Windows sockets are being used rather than Linux).  The support system doesn't scan the applications for which components want to consume a particular message topic and then figure out what TCP port to use.  Or differentiate between one message of a component and another.  That is, there are no message topics where a particular topic sent by a component could end up being received by an entirely different component than another.  Of course, this could be another direction to explore.

The Code

The structure is that there are components versus the socket communications code to interface to the Windows socket support.  The only other area is code to generate threads in which to run the components and to allow separate socket receive waits.

Currently all the components do is
1) Request the socket communications code to setup to transmit to and/or receive from particular identified other components.  The socket communications code looks up the component pair and determines whether or not there is a TCP port assigned for component pair and direction of transfer. 
2) Provide a callback by which the a received message can be delivered to the component.
3) Have a forever callback method to run in its assigned thread to request transmits of messages.

The socket communications code consists of the Socket Client, the Socket Server, and a repository of component requests along with the interface by which the components make these requests.  Since the receive code has to await a message, each request to receive has to have a separate thread to wait in.  (Note: In writing this it has occurred to me that instead of the receive thread being in the Socket Server for each receive port that it could be in the component.  That is, the component would have a thread by which to transmit messages and one for each remote component from which it wishes to receive.  We can leave that as an exercise for the reader.)

Another area of the socket communications is the class/package that supports the reading of the file that specifies the IP Addresses and ports to be associated with each component.

Program.cs

As is normal with C#, the application starts in the instance of the Program class.  It does the Delivery setup and then creates the instances of the component classes which causes their constructors to be executed.  Finally, when everything has been initiated, the Threads Create method is execute that causes the thread callbacks to be executed.
using System.Linq;
using System.Text;

namespace SocketApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            // Locate, read, and parse Delivery.dat to build DeliveryTable
            Delivery.Initialize();

            // Install the components via their constructors
            Component1 com1 = new Component1();
            NewComponent com4 = new NewComponent();

            // Start the threads
            Threads.Create();
        } // end Main
    } // end Program class
} // end namespace

App2.adb

The similar main procedure of Ada App2 is App2 which could have been named Main or anything else.  It's just identified as the main procedure to the project.  As can be seen it is directly equivalent to Program.cs.

with Component2;
with Delivery;
with Text_IO;
with Threads;

procedure App2 is

begin -- App2

  -- Build the DeliveryTable from the Delivery.dat file
  Delivery.Initialize;

  -- Install the components of App2
  Component2.Install;

  Text_IO.Put_Line("calling Threads Create");

  -- Create the threads for the thread table objects and enter the callbacks
  Threads.Create;

end App2;

Component1 (a C# component)

The variables such as socket1from4 are named as reminders of the pair being considered and the direction of the message – that is, that it is a server socket to receive from the component with an id of 4.

The constructor requests, via the Threads Install method, to get a thread that will run in the MainEntry method.  The thread MainEntry callback won't actually be entered until the Threads.Create is invoked.  Following the successful request for its thread, the component makes its requests to setup sockets to send and receive messages between Component2 (Id of 2) and NewComponent (Id of 4).  Since a constructor doesn't return a result, the ValidPair method is immediately invoked to determine whether the 1,2 and 1,4 pairs are valid as far as the Delivery table is concerned.

The MainEntry callback starts running when everything has been setup and it attempts to Transmit to the remote Component2 and the local NewComponent.  The NewComponent should be ready but the remote component may not be running since the application may not be running.  Therefore, the message wouldn't be able to be sent.

When one of the Receive threads of the SocketServer class receives a message it is forwarded to the component via its ReceiveCallback method.  In this case it is just reported whereas a real component would act upon the message.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace SocketApplication
{
    public class Component1
    {
        static private SocketServer socket1from4;
        static private SocketClient socket1to4;

        static private SocketServer socket1from2;
        static private SocketClient socket1to2;

        public Component1() // constructor
        {
           // Install the component into the Threads package.
           Threads.RegisterResult Result;
           Result = Threads.Install( "Component1",
                                     Threads.TableCount(),
                                     Threads.ComponentThreadPriority.NORMAL,
                                     MainEntry
                                   );
           if (Result.Status == Threads.InstallResult.VALID)
           {
               // Install this component via a new instance of the Windows Sockets
               // class with its threads to transmit to Component2
               socket1to2 = new SocketClient( "Component1",
                                              1,
                                              "Component2",
                                              2 );
               if (!socket1to2.ValidPair())
               {
                   Console.WriteLine(
                       "ERROR: SocketClient not valid for Component1, Component2 pair");
               }

               socket1from2 = new SocketServer( "Component1",
                                                1,
                                                "Component2",
                                                2,
                                                ReceiveCallback );
               if (!socket1from2.ValidPair())
               {
                   Console.WriteLine(
                       "ERROR: SocketServer not valid for Component1, Component2 pair");
               }

               // Install this component via a new instance of the Windows Sockets
               // class with its threads to receive from NewComponent
               socket1to4 = new SocketClient( "Component1",   // to
                                              1,
                                              "NewComponent", // from
                                              4 );
               if (!socket1to4.ValidPair())
               {
                   Console.WriteLine(
                       "ERROR: SocketClient not valid for Component1, NewComponent pair");
               }

               // Install this component via a new instance of the Windows Sockets
               // class with its threads to transmit to NewComponent
               socket1from4 = new SocketServer( "Component1",   // to
                                                1,
                                                "NewComponent", // from
                                                4,
                                                ReceiveCallback );
               if (!socket1from4.ValidPair())
               {
                   Console.WriteLine(
                       "ERROR: SocketServer not valid for Component1, NewComponent pair");
               }

           } // end if

        } // end constructor
       
        // Entry point from Threads
        static void MainEntry(int Index)
        {
            Console.WriteLine("in Component1 callback " + Index);

            while (true) // loop forever
            {
                Console.WriteLine("Component1 sending to Component2");
                if (!socket1to2.Transmit(1, 2,  // DeliveryTo component id of 2
                                         "Component1 message for Component2"))
                {
                   Console.WriteLine("Message not sent to Component2");
                }

               Console.WriteLine("Component1 sending to NewComponent");
               if (!socket1to4.Transmit(1, 4, // DeliveryTo component id of 4
                                "Component1 message for NewComponent"))
               {
                   Console.WriteLine("Message not sent to NewComponent");
               }

               Thread.Sleep(1000); // 1.0 sec

            } // end forever loop

        } // end MainEntry

        // Notify of received message
        static void ReceiveCallback( string Message )
        {
            Console.WriteLine("Component1 received a message: " + Message);
        }

    } // end class Component1

} // end namespace

NewComponent is similar.  It just requests to send and receive from Component1.

Component2 (an Ada package)

Again, the Ada package directly corresponds to the C# Component1.
(spec)
with ExecItf;

package Component2 is

  -- Return component's wakeup event handle
  function WakeupEvent
  return ExecItf.HANDLE;

  procedure Install;

end Component2;
(body)
with Socket.Client;
with Socket.Server;
with System;
with Text_IO;
with Threads;
with Unchecked_Conversion;

package body Component2 is

  package Int_IO is new Text_IO.Integer_IO( Integer );

  ComponentWakeup
  -- Wakeup Event handle of the component
  : ExecItf.HANDLE;

  Socket2to1   : Boolean;
  Socket2from1 : Boolean;

  Message
  : String(1..23)
  := "Component2 message to 1";

  procedure Callback
  ( Id : in Integer
  );

  procedure ReceiveCallback
  ( Message : in String
  );

  procedure Install is

    Result : Threads.RegisterResult;

    use type Threads.InstallResult;

    function to_Callback is new Unchecked_Conversion
                                ( Source => System.Address,
                                  Target => Threads.CallbackType );
    function to_RecvCallback is new Unchecked_Conversion
                                    ( Source => System.Address,
                                      Target => Socket.ReceiveCallbackType );

  begin -- Install

    -- Install the component into the Threads package.
    Result := Threads.Install
              ( Name     => "Component2",
                Index    => 0, -- value doesn't matter
                Priority => Threads.NORMAL,
                Callback => to_Callback(Callback'Address)
              );
    if Result.Status = Threads.VALID then
      ComponentWakeup := Result.Event; -- make visible to WinSock via function

       -- Request the ability to send to Component1.
      Socket2to1 := Socket.Client.Request( "Component2",
                                           2,
                                           "Component1",
                                           1 );
      if not Socket2to1 then
        Text_IO.Put_Line(
                 "Socket.Client not valid for Component2, Component1 pair" );
      end if;

      -- Request the ability to receive from Component1.
      Socket2from1 := Socket.Server.Request
                        ( "Component2",
                          2,
                          "Component1",
                          1,
                          to_RecvCallback(ReceiveCallback'Address) );
      if not Socket2from1 then
        Text_IO.Put_Line(
                 "Socket.Server not valid for Component2, Component1 pair" );
      end if;

    end if;

  end Install;

  -- Return component's wakeup event handle
  function WakeupEvent
  return ExecItf.HANDLE is
  begin -- WakeupEvent
    return ComponentWakeup;
  end WakeupEvent;

  -- Received message from WinSock Recv
  procedure ReceiveCallback
  ( Message : in String
  ) is

  begin -- ReceiveCallback

    Text_IO.Put("Component2 received a message: ");
    declare
      Msg : String(1..Message'Length);
      for Msg use at Message'Address;
    begin
      Text_IO.Put_Line(Msg);
    end;

  end ReceiveCallback;

  -- Forever loop as initiated by Threads
  procedure Callback
  ( Id : in Integer
  ) is

  begin -- Callback

    Text_IO.Put("in Component2 callback");
    Int_IO.Put(Id);
    Text_IO.Put_Line(" ");

    loop -- forever

      if Socket2to1 then
        Text_IO.Put_Line("Component2 to send to Component1");
        if not Socket.Client.Transmit( 2, 1, -- from 2 to 1
                                       Message )
        then
          Text_IO.Put_Line( "Message not sent to Component1" );
        end if;
      end if;

      Delay(1.0);

    end loop;

  end Callback;

end Component2;

Threads

The Threads C# class and Ada package are directly similar to what have been reported in the past.  Just with a function added to return the current number of entries in its table for use by the Socket Server and Client C# constructors and Ada Request functions.  Therefore the two versions won't be repeated here.

Delivery

For this pair of applications there is a C# Delivery class and an Ada Delivery package that read a common Delivery.dat file (although a copy is placed such as to be in path of the executables for each project).  The code of each language locates the file, reads it, parses it to build a table, as well as locating the component pairs and doing some checking that the contents of the file is valid.  Along with the ability to lookup a pair of components and return whether the table supports that the pair is supported by the supplied Delivery file.

As in past posts the Delivery.dat file contains the same fields (except that the unused extra IP Address field that would have been used by Linux has been eliminated).  The current file, with the IP Addresses modified, is
1|Component1|192.xxx.y.c1|8001|8002|
6|RemoteComponent|192.xxx.y.c2|8006|8005|
2|Component2|192.xxx.y.c1|8002|8001|
2|Component2|192.xxx.y.c1|8009|8010|
6|RemoteComponent|192.xxx.y.c2|8010|8009|
1|Component1|192.xxx.y.c1|8003|8004|
8|Component8|192.xxx.y.c1|8007|8008|
4|NewComponent|192.xxx.y.c1|8004|8003|
5|ExComponent|192.xxx.y.c1|8005|8006|
where xxx.y.c1 and xxx.y.c2 are the other three bytes of the IP address of the first and second computers.  (And where communications with the RemoteComponent involved by any of the three components of this trial.  Therefore, the correct selection of the IP Address by the Socket Server and Client wasn't verified since the IP Address is the same for both the Send and the Receive.)

Delivery.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace SocketApplication
{
    // The Delivery class locates the Delivery.dat file, inputs and parses it
    // to create the Delivery Table for the instances of the Socket class to
    // access, validates it, and locates the component partners in the table.
    static public class Delivery
    {
      const int maxEntries = 16; // maximum number of Delivery Table entries

      const int ComponentIdsRange = 63; // maximum of 63 components

      public struct IPAddressType
      {
        public byte quad1;
        public byte quad2;
        public byte quad3;
        public byte quad4;
      }

      // Delivery table data type
      public struct DataType
      {
        public int ComId;         // numeric id of component
        public string ComName;    // name of component
        public bool validIP;      // true if dotted quad notation
        public string IP_Address; // IP address as string
        public IPAddressType IPAddress; // IP address as 4 bytes
        public int MyPort;        // server port
        public int OtherPort;     // client port
        public int Partner;       // index of component with opposite ports
      };
       
      // Delivery table
      public class TableType
      {
        public int count;
        public DataType[] list = new DataType[maxEntries];
      };

      static public TableType DeliveryTable = new TableType();

      static public bool DeliveryError = false;


      // Locate Delivery.dat file and parse to create DeliveryTable
      static public bool Initialize()
      {
        bool Valid = true;

        // Obtain the path of the delivery file.
        string deliveryFile = FindDeliveryFile();
        if (deliveryFile.Length < 8)
        {
          Console.WriteLine("ERROR: No Delivery.dat file found");
          return false;
        }

        // Open and parse the configuration file.
        using (FileStream fs = File.Open(deliveryFile, FileMode.Open,
                                         FileAccess.Read, FileShare.None))
        {
          byte[] fileData = new byte[1024];
          UTF8Encoding temp = new UTF8Encoding(true);

          while (fs.Read(fileData, 0, fileData.Length) > 0)
          {
            Console.WriteLine(temp.GetString(fileData));
          }
          fs.Close();

          Parse(fileData);

          Console.WriteLine("Delivery Table");
          for (int i = 0; i < DeliveryTable.count; i++)
          {
            Console.Write(DeliveryTable.list[i].ComId);
            Console.Write(" ");
            Console.Write(DeliveryTable.list[i].ComName);
            Console.Write(" ");
            Console.Write(DeliveryTable.list[i].IPAddress.quad1);
            Console.Write(" ");
            Console.Write(DeliveryTable.list[i].IPAddress.quad2);
            Console.Write(" ");
            Console.Write(DeliveryTable.list[i].IPAddress.quad3);
            Console.Write(" ");
            Console.Write(DeliveryTable.list[i].IPAddress.quad4);
            Console.Write(" ");
            Console.Write(DeliveryTable.list[i].MyPort);
            Console.Write(" ");
            Console.WriteLine(DeliveryTable.list[i].OtherPort);
          }
        } // end using
         
        // Validate the parsed table and match up component partners
        if (!Validate())
        {
            return false;
        }

        return Valid;

      }  // end Initialize

      // Lookup and return location of ComId with a Partner of OtherId
      static public int Lookup(int ComId, int OtherId)
      {
        int location = -1;

        for (int i = 0; i < DeliveryTable.count; i++)
        {
          if (DeliveryTable.list[i].ComId == ComId)
          {
            int Partner = DeliveryTable.list[i].Partner;
            if (DeliveryTable.list[Partner].ComId == OtherId)
            {
              return i;
            }
          }
        } // end for

        return location;

      } // end Lookup

      // Locate the Delivery.dat file in the path of application execution.
      static private string FindDeliveryFile()
      {
        string nullFile = "";

        // Get the current directory/folder.
        string path = Directory.GetCurrentDirectory();

        // Find the Apps-Configuration.dat file in the path.
        bool notFound = true;
        while (notFound)
        {
          // Look for the file in this directory
          string newPath;
          char backSlash = '\\';
          int index = path.Length - 1;
          for (int i = 0; i < path.Length; i++)
          {
            int equal = path[index].CompareTo(backSlash);
            if (equal == 0)
            {
              newPath = path.Substring(0, index); // the portion of path that
                                                  //  ends just before '\'
              string[] dirs = Directory.GetFiles(newPath, "*.dat");
              string file = "Delivery.dat";
              int fileLength = file.Length;
              foreach (string dir in dirs)
              {
                string datFile = dir.Substring(index + 1, fileLength);
                equal = datFile.CompareTo(file);
                if (equal == 0)
                {
                  return dir;
                }
              }
              path = newPath; // reduce path to look again
              if (path.Length < 10)
              { return nullFile; }
            } // end equal == 0
            index--;

          } // end for loop
        } // end while loop

        return nullFile;

      } // end FindConfigurationFile

      public struct ParseParameters
      {
        public char delimiter;
        public int decodePhase;
        public int comCount;
        public int field;
        public string temp;
      };

      static private ParseParameters p;

      static private void Parse(byte[] data)
      {
        // Initialize
        DeliveryTable.count = 0;
        DeliveryError = false;

        p.delimiter = '|';
        p.decodePhase = 0;
        p.comCount = 0;
        p.field = 0;
        p.temp = "";

        // Decode application data
        for (int i = 0; i < data.Length; i++)
        {
          if (p.field == 5)
          { // Bypass end of line characters
            if ((data[i] == '\r') || (data[i] == '\n'))
            {
            }
            else
            {
              p.temp += (char)data[i]; // retain char for next phase
              p.field = 0;  // start over for next application
            }
          }
          else // not end-of-line; parse within the record
          { // Get component id
            if (data[i] != p.delimiter)
            {
              p.temp += (char)data[i];
            }
            else
            { // treat field prior to delimiter
              if (p.field == 0)
              { // initialize IP address for dotted quad
                DeliveryTable.list[p.comCount].validIP = true;
                // decode component id
                try
                {
                  DeliveryTable.list[p.comCount].ComId = Convert.ToInt32(p.temp);
                }
                catch (OverflowException)
                {
                  Console.WriteLine(
                      "ERROR: {0} is outside the range of the Int32 type.",
                      p.temp);
                }
                catch (FormatException)
                {
                  Console.WriteLine(
                      "ERROR: The {0} value '{1}' is not in a recognizable format.",
                      p.temp.GetType().Name, p.temp);
                }

                p.temp = ""; // initialize for next field
                p.field++;
              }
              else if (p.field == 1)
              { // decode component name
                DeliveryTable.list[p.comCount].ComName = p.temp;

                p.temp = ""; // initialize for next field
                p.field++;
              }
              else if (p.field == 2)
              { // decode IP Address of form nnn.nnn.n.nn
                DeliveryTable.list[p.comCount].IPAddress = DecodeIP(p.temp);
                DeliveryTable.list[p.comCount].IP_Address = p.temp;
                p.temp = ""; // initialize for next field
                p.field++;
              }
              else if (p.field == 3)
              { // decode first port
                DeliveryTable.list[p.comCount].MyPort = DecodePort(p.temp);

                p.temp = ""; // initialize for next field
                p.field++;
              }
              else if (p.field == 4)
              { // decode second port
                DeliveryTable.list[p.comCount].OtherPort = DecodePort(p.temp);

                p.temp = ""; // initialize for next field
                p.field++;

                p.comCount++; // increment index for the list

                DeliveryTable.count++;
              }
            }
          }
        } // end for loop

      } // end Parse

      static private Int32 DecodePort(string Port)
      {
        Int32 port = 0;
        try
        {
          port = Convert.ToInt32(Port);
          return port;
        }
        catch (FormatException e)
        {
          Console.WriteLine(
                "ERROR: Input string is not a sequence of digits.");
        }
        catch (OverflowException e)
        {
          Console.WriteLine(
                "ERROR: The number cannot fit in an integer.");
        }
        return 0;

      } // end DecodePort
       
      static IPAddressType IP;

      static private IPAddressType DecodeIP(string Addr)
      {
        IP.quad1 = 0;
        IP.quad2 = 0;
        IP.quad3 = 0;
        IP.quad4 = 0;

        int loc = 0;
        int count = 0; // number of '.' found
        string quad4 = "";

        for (int i = 0; i < Addr.Length; i++)
        {
          if (Addr[i] != '.')
          {
            quad4 += Addr[i];
            if (i + 1 == Addr.Length)
            {
              if (!DecodeIPQuad(loc, quad4))
              {
                  DeliveryTable.list[p.comCount].validIP = false;
              }
            }
          }
          else
          {
            count++;
            if (!DecodeIPQuad(loc, quad4))
            {
                DeliveryTable.list[p.comCount].validIP = false;
            }
            quad4 = "";
            loc++;
          }
        } // end for
        return IP;
      } // end DecodeIP

      static private bool DecodeIPQuad(int loc, string quad)
      {
        bool valid = false;
        byte quadByte = 0;
        try
        {
          quadByte = Convert.ToByte(quad);
          valid = true;
        }
        catch (FormatException e)
        {
          Console.WriteLine(
                "ERROR: Input string is not a sequence of digits.");
        }
        catch (OverflowException e)
        {
          Console.WriteLine(
                "ERROR: The number cannot fit in a byte.");
        }
        switch (loc)
        {
          case 0:
            IP.quad1 = quadByte;
            break;
          case 1:
            IP.quad2 = quadByte;
            break;
          case 2:
            IP.quad3 = quadByte;
            break;
          case 3:
            IP.quad4 = quadByte;
            break;
        }
        return valid;
      } // end DecodeIPQuad

      // Validate the parsed table and match up component partners
      static private bool Validate()
      {
        bool valid = true;

        for (int i = 0; i < DeliveryTable.count; i++)
        {
            DeliveryTable.list[i].Partner = 0;

            // Check that ComId is within range
            if ((DeliveryTable.list[i].ComId > 0) &&
                (DeliveryTable.list[i].ComId <= ComponentIdsRange))
            {
            }
            else
            {
                Console.WriteLine("ERROR: Delivery.dat ComId of " +
                  DeliveryTable.list[i].ComId + " is out-of-range");
                valid = false;
            }

            // Check that an entry with a duplicate ComId has the same ComName
            for (int j = i + 1; j < DeliveryTable.count; j++)
            {
                if (DeliveryTable.list[j].ComId == DeliveryTable.list[i].ComId)
                {
                    if (DeliveryTable.list[j].ComName != DeliveryTable.list[i].ComName)
                    {
                        Console.WriteLine(
                          "WARNING: ComponentName mismatch between Delivery.dat records at "
                          + i + " and " + j);
                    }
                }
            } // end loop

            // Check PortServer and PortClient for some range of values
            if (((DeliveryTable.list[i].MyPort < 8000) ||
                 (DeliveryTable.list[i].MyPort > 9999)) ||
                ((DeliveryTable.list[i].OtherPort < 8000) ||
                 (DeliveryTable.list[i].OtherPort > 9999)))
            {
                Console.WriteLine(
                  "ERROR: Server or Client Port not within selected range of 8000-9999");
                valid = false;
            } // end if

            // Check that another record doesn't have the same MyPort or OtherPort
            for (int j = 0; j < DeliveryTable.count; j++)
            {
              if (i != j) // avoid current entry
              {
                if (DeliveryTable.list[i].MyPort ==
                    DeliveryTable.list[j].MyPort)
                {
                  Console.WriteLine("ERROR: Repeated use of MyPort of " +
                                    DeliveryTable.list[i].MyPort);
                  valid = false;
                }
                if (DeliveryTable.list[i].OtherPort ==
                    DeliveryTable.list[j].OtherPort)
                {
                  Console.WriteLine("ERROR: Repeated use of OtherPort of " +
                                    DeliveryTable.list[i].OtherPort);
                  valid = false;
                }

              } // if i = j

            } // end for loop

          // Find component partner of this entry
          for (int j = 0; j < DeliveryTable.count; j++)
          {
            if (i != j) // avoid current entry
            {
              if ((DeliveryTable.list[i].MyPort ==
                   DeliveryTable.list[j].OtherPort) &&
                  (DeliveryTable.list[i].OtherPort ==
                   DeliveryTable.list[j].MyPort))
              {
                DeliveryTable.list[i].Partner = j;
                break; // exit inner loop; can't be more than one partner
              }
            } // end if i = j
          } // end loop

        } // end loop over i

        return valid;

      } // end Validate

    } // end Delivery class

} // end namespace

The Ada version is similar although the DeliverTable isn't visible. Therefore, the Partner, IP_Address, and  Port functions are provided to allow the Server and Client packages to lookup those values.
(spec)
with ExecItf;
with Itf;

package Delivery is

  type LocationType
  -- Range of Delivery Table entry locations
  is new Integer range 0..16;

  type PositionPortType
  -- First or Second column of ports in Delivery.dat
  is ( Mine, Other );

  type BytesType
  is record
    Count : Integer; -- number of bytes in message
    Bytes : Itf.ByteArray(1..15);
  end record;

  -- Read and parse Delivery.dat file to create DeliveryTable
  procedure Initialize;

  -- Lookup and return location of ComId with a Partner of OtherId
  function Lookup
  ( ComId   : in Integer;
    -- Identifier of invoking component
    OtherId : in Integer
    -- Identifier of other component of pair
  ) return LocationType;

  -- Return Partner of component at table location
  function Partner
  ( Index : in LocationType
  ) return LocationType;

  -- Return the IP Address at the table location
  function IP_Address
  ( Index : in LocationType
  ) return BytesType;

  -- Return the Port at the 1st/2nd position at the table location
  function Port
  ( Position : in PositionPortType;
    Index    : in LocationType
  ) return Natural;

end Delivery;

The body is very similar to that of the Kubernetes Follow-On (Part 2) post with the addition of
  -- Return Partner of component at table location
  function Partner
  ( Index : in LocationType
  ) return LocationType is

  begin -- Partner
 
    return DeliveryTable.List(Index).Partner;

  end Partner;

  function IP_Address
  ( Index : in LocationType
  ) return BytesType is
 
  begin -- IP_Address

    return DeliveryTable.List(Index).PCAddress;

  end IP_Address;
   
  function Port
  ( Position : in PositionPortType;
    Index    : in LocationType
  ) return Natural is

  begin -- Port

    if Position = Mine then
      return DeliveryTable.List(Index).PortServer;
    else
      return DeliveryTable.List(Index).PortClient;
    end if;

  end Port;

Socket

There are three C# Socket classes – SocketData, SocketClient, and SocketServer.  Likewise there are four Ada Socket packages – Socket with a subpackage of Socket-Data and child packages of Socket-Client and Socket-Server where I attempted to follow the C# as closely as possible and also my understanding of the "Programming Windows TCP Sockets in C++ for the Beginner" internet post.

In the C# implementation the components request the ability to send or receive from a second component via the SocketClient (transmit) or SocketServer (receive) constructor.  For Ada it’s the same except that the Request function of the Socket-Client and Socket-Server packages is invoked.

Except for some Ada support packages (Itf, CStrings, TextIO that I have included in past posts) and the ExecItf package that has the interfaces to the C Windows socket support this is the extent of what's necessary.  The reviewer can do their own on-line searches for these C interfaces.

SocketData (C#)

As implied, the Sender type and data are for transmit.  Hence this data is associated with the Client.  While the Listener type and data are for receive and associated with the Server.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace SocketApplication
{
    // This class contains the tables to support Windows Sockets.
    static public class SocketData
    {
        const int MaxComponents = 16;

        // Sender data

        public struct SenderDataType
        {
            public string FromName; // Name and Id of the invoking component
            public int FromId;      //   from which message is to be sent
            public int ToId;        // Name and Id of remote component Name and Id of
            public string ToName;   //   remote component to be sent the message 

            public string FromAddress; // the sending component
            public int FromPort;       //  IP address and port

            public System.Net.Sockets.Socket sender;
            public string clientInfo;
        };

        public class SenderType
        {
            public int count;
            // Number of registered senders of the application
            public SenderDataType[] list = new SenderDataType[MaxComponents];
            // Registration supplied data concerning the component as well as
            // run-time status data
        };

        // Retained data for multiple sender instantiations
        static public SenderType SenderData = new SenderType();

        // Listener Data

        public struct ListenerDataType
        {
            public int ToId;         // Name and Id of this component waiting to
            public string ToName;    //   receive the message and the callback
            public ReceiveCallback recdCallback; // to return the message
            public string FromName;  // Name and Id of the component from
            public int FromId;       //   which message is to be received

            public string ToAddress; // the listening component
            public int ToPort;       //  IP address and port

            public int ThreadId;     // Id of Receive thread

            public System.Net.Sockets.Socket listener;
            public string serverInfo;
        };

        public class ListenerType
        {
            public int count;
            // Number of registered senders of the application
            public ListenerDataType[] list = new ListenerDataType[MaxComponents];
            // Registration supplied data concerning the component as well as
            // run-time status data
        };

        // Retained data for multiple sender instantiations
        static public ListenerType ListenerData = new ListenerType();

    } // end class SocketData

} // end namespace

The Ada Socket package is an umbrella for the Data, Client, and Server packages.  Note that it contains the WSARestart procedure that I found needed to continue after a WSA error as reported in the previous post.  The trial results illustrated below from starting the Ada App2 first show it working since the 10061 error can occur a number of times before the C# application starts.

with Delivery;
with ExecItf;
with Itf;
with System;
with Threads;
with Unchecked_Conversion;

package Socket is

  subtype ComponentIdsType
  -- Identifier of the hosted components.
  -- Notes:
  --   This allows for a configuration with a maximum of 63 components.
  is Integer range 0..63;

  type ComponentNameType
  -- Name of the hosted components
  is record
    Count : Integer; -- number of characters in name
    Value : String(1..20);
  end record;

  type ReceiveCallbackType
  -- Callback to return received message to its component
  is access procedure( Message : in String );

  function to_ac_SOCKADDR_t -- convert address to ExecItf.WinSock pointer
  is new Unchecked_Conversion( Source => System.Address,
                               Target => ExecItf.PSOCKADDR );

  procedure WSARestart;

  package Data is

    -- SocketServer Listener Data

    type SockAddr_In
    is record
      SIn_Family : ExecItf.SHORT;   -- Internet protocol (16 bits)
      SIn_Port   : ExecItf.USHORT;  -- Address port (16 bits)
      SIn_Addr   : ExecItf.ULONG;   -- IP address (32 bits)
      SIn_Zero   : Itf.ByteArray(1..8);
    end record;
    for SockAddr_In
    use record
      SIn_Family at 0 range 0 .. 15;
      SIn_Port   at 2 range 0 .. 15;
      SIn_Addr   at 4 range 0 .. 31;
      SIn_Zero   at 8 range 0 .. 63;
    end record;
    for SockAddr_In'size use 16*8; -- bits

    type ListenerDataType
    is record
      ToId         : ComponentIdsType;  -- Name and Id of this component waiting to
      ToName       : ComponentNameType; --   receive the message & the callback
      RecvCallback : ReceiveCallbackType; -- to return the message
      FromName     : ComponentNameType; -- Name and Id of the component from
      FromId       : ComponentIdsType;  --  which message is to be received

      ThreadId     : Integer;           -- Id of Receive thread

      Data         : SockAddr_In;
      -- SA_family, port and IP address of the Server Socket
      Addr         : ExecItf.PSOCKADDR;
      -- Pointer to description of local address of Server Socket.
      -- The SOCKADDR to which it points is a record that contains
      --   SA_family : u_short;
      --   SA_data   : ExecItf.WSA_CHAR_Array(0..13);
      Listener :  ExecItf.Socket;
      -- Socket handle to be supplied to accept function, etc;
    end record;

    type ListenerListType
    is array (1..ComponentIdsType'Last) of ListenerDataType;

    type ListenerType
    is record
      Count : ComponentIdsType;
      List  : ListenerListType;
    end record;

    ListenerData
    : ListenerType;


    -- SocketClient Sender Data

    type SenderDataType
    is record
      FromName    : ComponentNameType;  -- Name and Id of the invoking component
      FromId      : ComponentIdsType;   --  from which message is to be sent
      ToId        : ComponentIdsType;   -- Name and Id of remote component
      ToName      : ComponentNameType;  --   to be sent the message

      ThreadId    : Integer;            -- Id of Transmit thread

      Data         : SockAddr_In;
      -- SA_family, port and IP address of the Client Socket
      Addr         : ExecItf.PSOCKADDR;
      -- Pointer to description of local address of Client Socket.
      -- The SOCKADDR to which it points is a record that contains
      --   SA_family : u_short;
      --   SA_data   : ExecItf.WSA_CHAR_Array(0..13);
      Sender      : ExecItf.Socket;
      -- Socket handle to be supplied to accept function, etc
    end record;

    type SenderListType
    is array (1..ComponentIdsType'Last) of SenderDataType;

    type SenderType
    is record
      Count : ComponentIdsType;
      List  : SenderListType;
    end record;

    SenderData
    : SenderType;

  end Data;

end Socket;

The package spec for Data is included above inside the Socket package spec.  The body below only sets the data counts to 0 where Ada will do this initialize first.  Being in the spec means that the Client and Server code can directly reference the same as the C# public data.

separate (Socket)

package body Data is

begin -- initialize

  ListenerData.Count := 0;
  SenderData.Count := 0;

end Data;

SocketClient (C#)

The constructor saves the data in the SenderData and the rest is done by the Transmit method.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace SocketApplication
{
    // This class interfaces with Windows Sockets.
    //
    // One instance of this class is needed for each pair of components -- the
    // from component of a message and the component to which it is to be sent.
    // This class is for the component from which the message is sent.
    public class SocketClient
    {

        // Client socket stuff

        // DeliveryTable index of ComId with ToId as Partner. 
        // -1 if instantiation of WinSocket for component pair is invalid
        static public int MatchIndex;

        // constructor
        public SocketClient( string fromName, // Name and Id of
                             int fromId,      //  sending component
                             string toName,   // Name and Id of
                             int toId )       //   receiving component
        {
            // save constructor parameters
            int Count = SocketData.SenderData.count;
            SocketData.SenderData.list[Count].FromName = fromName;
            SocketData.SenderData.list[Count].FromId = fromId;
            SocketData.SenderData.list[Count].ToName = toName;
            SocketData.SenderData.list[Count].ToId = toId;


            // Find the partner in DeliveryTable.  This is a validation as
            // well that the invocating component is correct that the from
            // and to component ids and names match the table.
            MatchIndex = Delivery.Lookup(fromId, toId);

            // Set the IP addresses and the ports.
            if (MatchIndex >= 0)
            {
                SocketData.SenderData.list[Count].FromAddress =
                    Delivery.DeliveryTable.list[MatchIndex].IP_Address;
                SocketData.SenderData.list[Count].FromPort =
                    Delivery.DeliveryTable.list[MatchIndex].OtherPort;
            }

            SocketData.SenderData.count++;
            Console.WriteLine("SenderData count " + SocketData.SenderData.count
                              + " " + fromId);

        } // end constructor

        // Return whether FromId, ToId pair is available for the Delivery.dat file
        // Note: ValidPair must be called immediately after constructor so not
        //       overwritten
        public bool ValidPair()
        {
            if (MatchIndex < 0)
            {
                return false;
            }
            else
            {
                return true;
            }

        } // end ValidPair

        private int Lookup(int FromId, int ToId)
        {
            for (int i = 0; i < SocketData.SenderData.count; i++)
            {
                if ((SocketData.SenderData.list[i].FromId == FromId) &&
                    (SocketData.SenderData.list[i].ToId == ToId))
                {
                    return i;
                }
            }
            return -1;
        }

        public bool Transmit(int FromId, int ToId, string Message)
        { // Message to be sent
            if (string.IsNullOrEmpty(Message))
            {
                return false;
            }

            int Index = Lookup(FromId, ToId);
            if (Index < 0)
            {
                return false;
            }

            // The sender always starts up on the localhost
            IPHostEntry hostInfo =
                Dns.GetHostByName(SocketData.SenderData.list[Index].FromAddress);
            IPAddress ipAddress = hostInfo.AddressList[0];
            IPEndPoint remoteEP = new
                IPEndPoint(ipAddress, SocketData.SenderData.list[Index].FromPort);

            Console.WriteLine("Transmit " + FromId + " " +
                SocketData.SenderData.list[Index].FromAddress
                + " " + SocketData.SenderData.list[Index].FromPort);

            // Create a client socket and connect it to the remote
            SocketData.SenderData.list[Index].sender =
                new System.Net.Sockets.Socket
                    (System.Net.Sockets.AddressFamily.InterNetwork,
                     System.Net.Sockets.SocketType.Stream,
                     System.Net.Sockets.ProtocolType.Tcp);
            try
            {
                SocketData.SenderData.list[Index].sender.Connect(remoteEP);
                Console.WriteLine("Socket connected to {0} by {1}",
                    SocketData.SenderData.list[Index].sender.RemoteEndPoint.ToString(),
                    FromId);
                byte[] byData = System.Text.Encoding.ASCII.GetBytes(Message);
                byte[] msg = Encoding.ASCII.GetBytes(Message);
                int bytesSent = SocketData.SenderData.list[Index].sender.Send(msg);
                // Release the socket
                SocketData.SenderData.list[Index].sender.Shutdown(SocketShutdown.Send);
                SocketData.SenderData.list[Index].sender.Close();
                return true;
            } // end try
            catch (ArgumentNullException ane)
            {
                Console.WriteLine("ArgumentNullException : {0}", ane.ToString());
            }
            catch (SocketException se)
            {
                Console.WriteLine("SocketException : {0}", se.ToString());
            }
            catch (Exception e)
            {
                Console.WriteLine("Unexpected exception : {0}", e.ToString());
            }
            return false;
        } // end Transmit

    } // end class SocketClient

} // end namespace

Socket-Client (Ada)

The Ada is like the C#.  Only the Transmit interfaces to the Windows socket support to set the socket handle (Sender), wait for a Connect with the other component's Server, and then Send the message.  As will be seen in the trial results, if the remote application isn't ready the WSA error 10061 (connection refused) occurs.

package Socket.Client is

  -- child package of Socket

  function Request
  -- Request a Client component pairing
  ( FromName : in String;
    FromId   : in ComponentIdsType;
    ToName   : in String;
    ToId     : in ComponentIdsType
  ) return Boolean;

  function Transmit
  ( FromId  : in ComponentIdsType;
    ToId    : in ComponentIdsType;
    Message : in String
  ) return Boolean;

end Socket.Client;

with Delivery;
with ExecItf;
with TextIO;
with Text_IO;

package body Socket.Client is

-- child package of Socket

  package Int_IO is new Text_IO.Integer_IO( Integer );

  function Request
  -- Request a Client component pairing
  ( FromName : in String;
    FromId   : in ComponentIdsType;
    ToName   : in String;
    ToId     : in ComponentIdsType
  ) return Boolean is

    Index : Integer;

    MatchIndex : Delivery.LocationType;

    IPAddress  : Delivery.BytesType;
    Port       : Integer;

    function to_Ptr is new Unchecked_Conversion
                           ( Source => System.Address,
                             Target => ExecItf.PCSTR );

    use type Delivery.LocationType;

  begin -- Request

    if Data.SenderData.Count < Threads.MaxComponents then
 -- The use of Threads.MaxComponents needs to be changed.  It includes receive threads as well as
 -- component threads.  Need to use a value that only involves components.
      Index := Data.SenderData.Count + 1;
      Data.SenderData.Count := Index;

      Data.SenderData.List(Index).FromName.Count := FromName'Length;
      Data.SenderData.List(Index).FromName.Value(1..FromName'Length) := FromName;
      Data.SenderData.List(Index).FromId := FromId;
      Data.SenderData.List(Index).ToName.Count := ToName'Length;
      Data.SenderData.List(Index).ToName.Value(1..ToName'Length) := ToName;
      Data.SenderData.List(Index).ToId := ToId;

      -- Find the partner in DeliveryTable.  This is a validation as
      -- well that the invocating component is correct that the from
      -- and to component ids and names match the table.
      MatchIndex := Delivery.Lookup(FromId, ToId);

      -- Set the IP addresses and the ports.
      if MatchIndex > 0 then

        -- Fill in Data and address of Data
        Data.SenderData.List(Index).Data.SIn_Family := ExecItf.AF_INET;
        IPAddress := Delivery.IP_Address(MatchIndex);
        Data.SenderData.List(Index).Data.SIn_Addr :=
          ExecItf.inet_addr(to_Ptr(IPAddress.Bytes'Address));

        Port := Delivery.Port(Delivery.Other, MatchIndex);
        Data.SenderData.List(Index).Data.SIn_Port :=
          ExecItf.htons(ExecItf.USHORT(Port));

        for I in 1..8 loop
          Data.SenderData.List(Index).Data.SIn_Zero(I) := 0;
        end loop;

        Data.SenderData.List(Index).Addr :=
          to_ac_SOCKADDR_t(Data.SenderData.List(Index).Data'address);

        Text_IO.Put( "MatchIndex " );
        Int_IO.Put( Integer(MatchIndex) );
        Text_IO.Put( " " );
        Text_IO.Put( " ClientPort " );
        Int_IO.Put( Integer(Port) );
        Int_IO.Put( Integer(Data.SenderData.List(Index).Data.SIn_Port) );
        Text_IO.Put_Line( " " );
      else
        Text_IO.Put_Line( "ERROR: From-To not valid for Client" );
        return False;
      end if;

      Text_IO.Put( "SenderData count " );
      Int_IO.Put( FromId );
      Text_IO.Put_Line( " " );

      return True;

    else

      Text_IO.Put_Line( "ERROR: Too many Senders" );
      return False;

    end if;

  end Request;

  function Lookup
  ( FromId : in ComponentIdsType;
    ToId   : in ComponentIdsType
  ) return ComponentIdsType is

  begin -- Lookup

    for I in 1..Data.SenderData.Count loop

      if Data.SenderData.List(I).FromId = FromId and then
         Data.SenderData.List(I).ToId = ToId
      then
        return I;
      end if;

    end loop;
    return 0;

  end Lookup;

  function Transmit
  ( FromId  : in ComponentIdsType;
    ToId    : in ComponentIdsType;
    Message : in String
  ) return Boolean is

    Bytes_Written
    -- Number of bytes sent
    : ExecItf.INT;

    Index : ComponentIdsType;

    Status
    -- 0 means function was successful; -1 otherwise
    : ExecItf.INT;

    function to_PCSTR is new Unchecked_Conversion( Source => System.Address,
                                                   Target => ExecItf.PCSTR );


    use type ExecItf.INT;
    use type ExecItf.SOCKET;

  begin -- Transmit

    if Message'Length = 0 then
      return False;
    end if;

    Index := Lookup( FromId, ToId );
    if Index <= 0 then
      return False;
    end if;

    -- The sender always starts up on the localhost.

    -- Create a client socket and connect it to the remote
    Data.SenderData.List(Index).Sender :=
      ExecItf.Socket_Func( AF       => ExecItf.AF_INET,       -- address family
                           C_Type   => ExecItf.SOCK_STREAM,   -- connection-oriented
                           Protocol => ExecItf.IPPROTO_TCP ); -- for TCP
    if Data.SenderData.List(Index).Sender = ExecItf.INVALID_SOCKET then

      declare
        Text : Itf.V_80_String_Type;
      begin
        Text.Data(1..32) := "ERROR: Client Socket NOT created";
        Text := TextIO.Concat( Text.Data(1..32), Integer(Index) );
        TextIO.Put_Line( Text );
      end;
      ExecItf.Display_Last_WSA_Error;
      declare
        Text : Itf.V_80_String_Type;
      begin
        Text.Data(1..9) := "WSA Error";
        Text := TextIO.Concat(Text.Data(1..9),Integer(Index));
        TextIO.Put_Line(Text);
      end;

      WSARestart;

      return False;

    end if;

    -- Connect

    Status :=
      ExecItf.Connect( S       => Data.SenderData.List(Index).Sender,
                       Name    => Data.SenderData.List(Index).Addr,
                       NameLen => Data.SenderData.List(Index).Data'size/8 );
    if Status = 0 then
      Text_IO.Put_Line( "Client Socket Connected to Transmit" );

      -- Send
      -- Convert string to byte array
      declare
        Msg : Itf.ByteArray(1..Message'Length);
        for Msg use at Message'Address;
      begin
        Bytes_Written :=
          ExecItf.Send( S     => Data.SenderData.List(Index).Sender,
                        Buf   => to_PCSTR(Msg'Address),
                        Len   => ExecItf.INT(Message'Length),
                        Flags => 0 );
      end;
      if Bytes_Written /= ExecItf.INT(Message'Length) then
        Text_IO.Put("ERROR: Socket-Client Message Send failed");
        Int_IO.Put(Integer(Bytes_Written));
        Text_IO.Put(" ");
        Text_IO.Put(Data.SenderData.List(Index).ToName.Value
                      (1..Data.SenderData.List(Index).ToName.Count));
        Int_IO.Put(Integer(Index));
        Int_IO.Put(Integer(Data.SenderData.List(Index).Data.SIn_Port));
        Text_IO.Put_Line(" ");
        ExecItf.Display_Last_WSA_Error;

        return False;

      else -- successful

        declare
          Text : Itf.V_80_String_Type;
        begin
          Text := TextIO.Concat
                  ( "Transmit sent using client socket port",
                    Integer(Data.SenderData.List(Index).Data.SIn_Port) );
          TextIO.Put_Line( Text );
        end;

        -- Close the socket since it will be opened again for the next Transmit
        Status := ExecItf.CloseSocket( S => Data.SenderData.List(Index).Sender );
        Data.SenderData.List(Index).Sender := ExecItf.INVALID_SOCKET;

        return True;

      end if;

    else

      ExecItf.Display_Last_WSA_Error;
      WSARestart;

      Status := ExecItf.CloseSocket( S => Data.SenderData.List(Index).Sender );
      Data.SenderData.List(Index).Sender := ExecItf.INVALID_SOCKET;

      return False;

    end if;

  end Transmit;

end Socket.Client;

SocketServer (C#)

With the Server, the constructor saves the data in the ListenerData but also creates the listener socket and then Binds the socket.  Each Receive thread callback then checks which socket the thread will wait upon via Lookup and in its forever loop Listens for the transmitting component's socket.  It then Accepts the connection, Receives the message, passes the message to the component via its recdCallback method, and then returns to the top of the forever loop to await the next message from the particular remote component.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace SocketApplication
{
    // This class interfaces with Windows Sockets.
    //
    // One instance of this class is needed for each pair of components -- the
    // from component of a message and the component to which it is to be sent.
    // This class is for the component to which the message is sent.
    public class SocketServer
    {
        // Server socket stuff

        // DeliveryTable index of ComId with ToId as Partner. 
        // -1 if instantiation of WinSocket for component pair is invalid
        static public int MatchIndex;

        // constructor
        public SocketServer( string toName,             // Name and Id of the
                             int toId,                  //   local component
                             string fromName,           // Name and Id of the
                             int fromId,                //  remote component
                            ReceiveCallback callback ) // Callback to forward message
       {
            // save constructor parameters
            int Count = SocketData.ListenerData.count;
            SocketData.ListenerData.list[Count].FromName = fromName;
            SocketData.ListenerData.list[Count].FromId = fromId;
            SocketData.ListenerData.list[Count].recdCallback = callback;
            SocketData.ListenerData.list[Count].ToName = toName;
            SocketData.ListenerData.list[Count].ToId = toId;

            // Find the partner in DeliveryTable.  This is a validation as
            // well that the invocating component is correct that the from
            // and to component ids and names match the table.
            MatchIndex = Delivery.Lookup(toId, fromId);

            // Set the IP addresses and the ports.
            if (MatchIndex >= 0)
            {
                int Partner = Delivery.DeliveryTable.list[MatchIndex].Partner;
                SocketData.ListenerData.list[Count].ToAddress =
                    Delivery.DeliveryTable.list[Partner].IP_Address;
                SocketData.ListenerData.list[Count].ToPort =
                    Delivery.DeliveryTable.list[Partner].OtherPort;
                Console.WriteLine("MatchIndex " + MatchIndex + " " +
                    SocketData.ListenerData.list[Count].ToAddress +
                    " ServerPort " + SocketData.ListenerData.list[Count].ToPort);
            }

            // Create thread for receive.
            Threads.RegisterResult Result;
            int id = Threads.TableCount(); // index in table after Install
            SocketData.ListenerData.list[SocketData.ListenerData.count].ThreadId = id;
            Result = Threads.Install("Receive" + id,
                                      id,
                                      Threads.ComponentThreadPriority.HIGH,
                                      Receive
                                    );
            if (Result.Status == Threads.InstallResult.VALID)
            {
                IPHostEntry hostInfo = Dns.GetHostByName(
                    SocketData.ListenerData.list[Count].ToAddress);
                IPAddress serverAddr = hostInfo.AddressList[0];
                var serverEndPoint = new IPEndPoint(
                    serverAddr, SocketData.ListenerData.list[Count].ToPort);

                // Create a listener socket.
                SocketData.ListenerData.list[Count].listener =
                    new System.Net.Sockets.Socket
                               (System.Net.Sockets.AddressFamily.InterNetwork,
                                System.Net.Sockets.SocketType.Stream,
                                System.Net.Sockets.ProtocolType.Tcp);
                try
                {
                    SocketData.ListenerData.list[Count].listener.Bind(serverEndPoint);
                    SocketData.ListenerData.list[Count].serverInfo =
                        SocketData.ListenerData.list[Count].listener.LocalEndPoint.ToString();
                     Console.WriteLine("Server started at:" +
                                 SocketData.ListenerData.list[Count].serverInfo + " " +
                                 SocketData.ListenerData.list[Count].ToId);
                                 SocketData.ListenerData.list[Count].serverInfo = Listen(Count);
                                 Console.WriteLine(SocketData.ListenerData.list[Count].serverInfo);
                }
                catch (Exception e)
                {
                    var w32ex = e as Win32Exception;
                    if (w32ex == null)
                    {
                        w32ex = e.InnerException as Win32Exception;
                    }
                    if (w32ex != null)
                    {
                        int code = w32ex.ErrorCode;
                    }
                }
            }

            SocketData.ListenerData.count++;
            Console.WriteLine("ListenerData count " + SocketData.ListenerData.count + " " + fromId);

        } // end constructor


        // Return whether ToId, From pair is available for the Delivery.dat file
        public bool ValidPair()
        {
            if (MatchIndex < 0)
            {
                return false;
            }
            else
            {
                return true;
            }

        } // end ValidPair

        // Lookup location in array of thread id
        static private int Lookup(int threadId)
        {
            for (int i = 0; i < SocketData.ListenerData.count; i++)
            {
                if (SocketData.ListenerData.list[i].ThreadId == threadId)
                {
                    return i;
                }
            }
            return -1;
        }

        static public string Listen(int index)
        {
            try
            {
                SocketData.ListenerData.list[index].listener.Listen(1);
                return "Server listening " +
                       SocketData.ListenerData.list[index].ToId;
            }
            catch (Exception ex)
            {
                return "Failed to listen" + ex.ToString();
            }
        } // end Listen

        // Entry point from Threads to receive messages
        static void Receive(int Index)
        {
            byte[] bytes = new Byte[1024];
            string data = null;
            Console.WriteLine("WinSocket Receive " + Index);
            while (true) // loop forever
            {
                int index = Lookup(Index); // lookup index matching thread Index
                if (index < 0)
                {
                    Console.WriteLine("WinSocket Receive with failed lookup " +
                                      Index + " " + SocketData.ListenerData.count);
                    Thread.Sleep(500); // 0.5 sec 
                }
                else
                {
                    Console.WriteLine("WinSocket Receive " + Index + " entered for "
                        + index + " " + SocketData.ListenerData.list[index].ToId +
                        " " + SocketData.ListenerData.list[index].ToPort);

                    // Listen for a remote request to Connect
                    Console.WriteLine("Server Listen info: " +
                        SocketData.ListenerData.list[index].serverInfo + " " +
                        SocketData.ListenerData.list[index].ToId);
                    SocketData.ListenerData.list[index].serverInfo = Listen(index);
                    Console.WriteLine(SocketData.ListenerData.list[index].serverInfo);

                    Socket handler =
                        SocketData.ListenerData.list[index].listener.Accept();
                    int bytesrecd = handler.Receive(bytes);
                    data = Encoding.ASCII.GetString(bytes, 0, bytesrecd);
                    handler.Close();
                    // Transfer message to the component's callback
                    SocketData.ListenerData.list[index].recdCallback(data);
                }
            } // end forever loop
        } // end Receive

    } // end class SocketServer

} // end namespace

Socket-Server (Ada)

The Ada package follows the C# class and obtains the instance of the socket followed by the Bind at the start and then does the Listen and Accept each time thru the receive Threads Callback's forever loop.

Note:  The C# used a byte array for sending and receiving and converted the strings being used for the messages to the byte array for Transmit and from bytes to a string to notify the component.  The Ada does similar.  Therefore, when Component1 sends a message to Component2 the C# code first converts to a byte array before doing the Send.  Then when received by App2 it is converted to a string before passing it to Component2.  Likewise in Socket-Client, App2 converts to a byte array before the message is passed to Send and C# will convert it to a string before passing it to Component1. 

I don't know if this is strictly necessary but the components would need to agree on the format.  Likely the components could work with byte arrays rather than strings and encode and decode according to the agreed format.

package Socket.Server is

-- child package of Socket

  function Request
  -- Request a Client component pairing
  ( FromName     : in String;
    FromId       : in ComponentIdsType;
    ToName       : in String;
    ToId         : in ComponentIdsType;
    RecvCallback : in ReceiveCallbackType
  ) return Boolean;


end Socket.Server;

with CStrings;
with Delivery;
with Interfaces.C;
with System;
with TextIO;
with Text_IO;
with Unchecked_Conversion;

package body Socket.Server is

-- child package of Socket

  package Int_IO is new Text_IO.Integer_IO( Integer );

  procedure Callback
  ( Id : in Integer
  );

  function Request
  -- Request a Server component pairing
  ( FromName     : in String;
    FromId       : in ComponentIdsType;
    ToName       : in String;
    ToId         : in ComponentIdsType;
    RecvCallback : in ReceiveCallbackType
  ) return Boolean is

    Index : Integer;

    MatchIndex : Delivery.LocationType;
    Partner    : Delivery.LocationType;

    IPAddress  : Delivery.BytesType;
    Port       : Integer;
    TDigits    : String(1..2);
    Status
    -- 0 means function was successful; -1 otherwise
    : ExecItf.INT;
    Success    : Boolean;
    ThreadName : String(1..3);

    TransmitResult
    -- Result of Install of Receive with Threads
    : Threads.RegisterResult;

    function to_Callback is new Unchecked_Conversion
                                ( Source => System.Address,
                                  Target => Threads.CallbackType );
    function to_Ptr is new Unchecked_Conversion
                           ( Source => System.Address,
                             Target => ExecItf.PCSTR );
    function to_Int is new Unchecked_Conversion
                           ( Source => ExecItf.PSOCKADDR,
                             Target => Integer );

    use type Interfaces.C.Int;
    use type Delivery.LocationType;
    use type ExecItf.SOCKET;
    use type Threads.InstallResult;

  begin -- Request

    if Data.ListenerData.Count < Threads.MaxComponents then
 -- Refer to comment in Socket-Client
      Index := Data.ListenerData.Count + 1;
      Data.ListenerData.Count := Index;

      Data.ListenerData.List(Index).FromName.Count := FromName'Length;
      Data.ListenerData.List(Index).FromName.Value(1..FromName'Length) := FromName;
      Data.ListenerData.List(Index).FromId := FromId;
      Data.ListenerData.List(Index).ToName.Count := ToName'Length;
      Data.ListenerData.List(Index).ToName.Value(1..ToName'Length) := ToName;
      Data.ListenerData.List(Index).ToId := ToId;
      Data.ListenerData.List(Index).RecvCallback := RecvCallback;

      -- Find the partner in DeliveryTable.  This is a validation as
      -- well that the invocating component is correct that the from
      -- and to component ids and names match the table.
      MatchIndex := Delivery.Lookup(ToId, FromId);

      -- Set the IP addresses and the ports.
      if MatchIndex > 0 then
        Partner := Delivery.Partner( MatchIndex );

        -- Fill in Data and address of Data
        Data.ListenerData.List(Index).Data.SIn_Family := ExecItf.AF_INET;
        IPAddress := Delivery.IP_Address(MatchIndex);
        Data.ListenerData.List(Index).Data.SIn_Addr :=
          ExecItf.inet_addr(to_Ptr(IPAddress.Bytes'Address));

        Port := Delivery.Port(Delivery.Other, MatchIndex);
        Data.ListenerData.List(Index).Data.SIn_Port :=
          ExecItf.htons(ExecItf.USHORT(Port));

        for I in 1..8 loop
          Data.ListenerData.List(Index).Data.SIn_Zero(I) := 0;
        end loop;

        Data.ListenerData.List(Index).Addr :=
          to_ac_SOCKADDR_t(Data.ListenerData.List(Index).Data'address);

        Text_IO.Put( "MatchIndex " );
        Int_IO.Put( Integer(MatchIndex) );
        Text_IO.Put( " " );
        Text_IO.Put( " ServerPort " );
        Int_IO.Put( Integer(Port) ); --Delivery.Port(Delivery.Other, Partner)) );
        Int_IO.Put( Integer(Data.ListenerData.List(Index).Data.SIn_Port) );
        Text_IO.Put_Line( " " );
      else
        Text_IO.Put_Line( "ERROR: To-From not valid for Server" );
        return False;
      end if;

      -- Create thread for receive.
      Data.ListenerData.List(Index).ThreadId := Threads.TableCount + 1;
        -- index in table after Install
      ThreadName(1..3) := "R00";
      CStrings.IntegerToString( From    => Index,
                                Size    => 2,
                                CTerm   => False,
                                Result  => TDigits,
                                Success => Success );
      ThreadName(2..3) := TDigits;
      if ThreadName(2) = ' ' then
        ThreadName(2) := '0';
      end if;
      TransmitResult := Threads.Install
                        ( Name     => ThreadName,
                          Index    => Data.ListenerData.List(Index).ThreadId,
                          Priority => Threads.NORMAL,
                          Callback => to_Callback(Callback'Address) );
      if TransmitResult.Status /= Threads.Valid then

        return False;

      end if;

      -- Create a listener socket.
      Data.ListenerData.List(Index).Listener :=
        ExecItf.Socket_Func( AF       => ExecItf.AF_INET,       -- address family
                             C_Type   => ExecItf.SOCK_STREAM,   -- connection-oriented
                             Protocol => ExecItf.IPPROTO_TCP ); -- for TCP
      if Data.ListenerData.List(Index).Listener = ExecItf.INVALID_SOCKET then

        declare
          Text : Itf.V_80_String_Type;
        begin
          Text.Data(1..32) := "ERROR: Server Socket NOT created";
          Text := TextIO.Concat( Text.Data(1..32), Integer(Index) );
          TextIO.Put_Line( Text );
        end;
        ExecItf.Display_Last_WSA_Error;
        declare
          Text : Itf.V_80_String_Type;
        begin
          Text.Data(1..9) := "WSA Error";
          Text := TextIO.Concat(Text.Data(1..9),Integer(Index));
          TextIO.Put_Line(Text);
        end;

        WSARestart;

        return False;

      end if;

      -- Bind server socket
      Status :=
        ExecItf.Bind
        ( S       => Data.ListenerData.List(Index).Listener,
          Addr    => Data.ListenerData.List(Index).Addr,
          NameLen => ExecItf.INT(Data.ListenerData.List(Index).Data'size/8) );

      if Status /= 0 then

        ExecItf.Display_Last_WSA_Error;
        declare
          Text : Itf.V_80_String_Type;
        begin
          Text.Data(1..44) := "ERROR: Server created socket but Bind FAILED";
          Text := TextIO.Concat( Text.Data(1..44), Integer(Index) );
          TextIO.Put_Line( Text );
        end;
        declare
          Text : Itf.V_80_String_Type;
        begin
          Text.Data(1..9) := "WSA Error";
          Text := TextIO.Concat(Text.Data(1..9),Integer(Index));
          TextIO.Put_Line(Text);
        end;

        Status := ExecItf.CloseSocket
                  ( S => Data.ListenerData.List(Index).Listener );
        Data.ListenerData.List(Index).Listener := ExecItf.INVALID_SOCKET;
        WSARestart;
        return False;

      else

        Text_IO.Put("ListenerData count ");
        Int_IO.Put(Data.ListenerData.Count);
        Text_IO.Put(" ");
        Int_IO.Put(fromId);
        Text_IO.Put_Line(" ");

        return True;

      end if;

    else

      Text_IO.Put_Line( "ERROR: Too many Listeners" );
      return False;

    end if;

  end Request;

  function Lookup
  ( Id : in Integer
  ) return ComponentIdsType is

  begin -- Lookup

    for I in 1..Data.ListenerData.Count loop

      if Data.ListenerData.List(I).ThreadId = Id then
        return I;
      end if;

    end loop;
    return 0;

  end Lookup;

  function SocketListen
  ( Index : in ComponentIdsType
  ) return Boolean is

    Status
    -- 0 means function was successful; -1 otherwise
    : ExecItf.INT;

    use type Interfaces.C.int;

  begin -- SocketListen

    if ExecItf.Listen
       ( S       => Data.ListenerData.List(Index).Listener,
         Backlog => 1 ) < 0 -- only allow one connection per remote client
    then

      declare
        Text : Itf.V_80_String_Type;
      begin
        Text.Data(1..27) := "ERROR: Server Listen FAILED";
        Text := TextIO.Concat( Text.Data(1..27), Integer(Index) );
        TextIO.Put_Line( Text );
      end;
      ExecItf.Display_Last_WSA_Error;
      declare
        Text : Itf.V_80_String_Type;
      begin
        Text.Data(1..9) := "WSA Error";
        Text := TextIO.Concat(Text.Data(1..9),Integer(Index));
        TextIO.Put_Line(Text);
      end;

      Status := ExecItf.CloseSocket
                ( S => Data.ListenerData.List(Index).Listener );
      Data.ListenerData.List(Index).Listener := ExecItf.INVALID_SOCKET;
      WSARestart;

      return False;

    end if;

    return True;

  end SocketListen;

  function to_Digit
  ( Number : in Integer
  ) return Character is
  -- Convert number from 1 thru 9 to a alpha digit.

  begin -- to_Digit

    case Number is
      when 1 => return '1';
      when 2 => return '2';
      when 3 => return '3';
      when 4 => return '4';
      when 5 => return '5';
      when 6 => return '6';
      when 7 => return '7';
      when 8 => return '8';
      when 9 => return '9';
      when others =>
        Text_IO.Put("ERROR: to_Digit for Number not 1 thru 0");
        Int_IO.Put(Number);
        Text_IO.Put_Line(" ");
        return '0';
    end case;

  end to_Digit;

  -- Forever loop as initiated by Threads to Receive a message
  procedure Callback
  ( Id : in Integer
  ) is
  -- This procedure runs in the particular thread assigned to accept the
  -- connection for a component and receive a message.

    Client_Socket
    -- Accepted client socket
    : ExecItf.SOCKET := ExecItf.INVALID_SOCKET;

    Listen : Boolean;

    Message
    -- Message as read from socket
    : Itf.Message_Buffer_Type;

    Received_Size
    -- Size of received message
    : ExecItf.INT;

    Result
    -- Return value for Close
    : ExecItf.INT;

    type Int_Ptr_Type is access ExecItf.INT;

    function to_Ptr is new Unchecked_Conversion
                           ( Source => System.Address,
                             Target => ExecItf.PSTR );

    use type Interfaces.C.int;

    Index
    -- Index for Component in Data Listener
    : ComponentIdsType;

    use type ExecItf.SOCKET;

  begin -- Callback

    -- Obtain the Index in the Data Listener
    Index := Lookup(Id);

    if Index = 0 then
      Text_IO.Put( "ERROR: No Index for Socket-Server Callback" );
    end if;

    Connect:
    loop

      Listen := SocketListen( Index => Index );
      declare
        Text : Itf.V_80_String_Type;
      begin
        Text.Data(1..27) := "Server Receive after Listen";
        Text := TextIO.Concat(Text.Data(1..27),Integer(Index));
        if Listen then
          Text := TextIO.Concat(Text.Data(1..Text.Count), "True");
        else
          Text := TextIO.Concat(Text.Data(1..Text.Count), "False");
        end if;
        TextIO.Put_Line(Text);
      end;

      -- Accept a client connection.
      Client_Socket :=
        ExecItf.C_Accept( S       => Data.ListenerData.List(Index).Listener,
                          Addr    => null,
                          AddrLen => null );
      if Client_Socket = ExecItf.INVALID_SOCKET then

        Text_IO.Put_Line("ERROR: Server Client Socket NOT accepted");
        ExecItf.Display_Last_WSA_Error;

      else -- Accepted

        declare
          function to_Int is new Unchecked_Conversion
                                 ( Source => System.Address,
                                   Target => Integer );
        begin
          Received_Size :=
            ExecItf.Recv( S     => Client_Socket,
                          Buf   => to_Ptr(Message'address),
                          Len   => ExecItf.INT(Message'size/8),
                          Flags => 0 );
        end;

        if Received_Size < 0 then
          declare
            Text : Itf.V_80_String_Type;
          begin
            Text.Data(1..32) := "ERROR: Socket-Server Recv failed";
            Text := TextIO.Concat( Text.Data(1..32),
                                   Integer(Index) );
            TextIO.Put_Line(Text);
          end;
          ExecItf.Display_Last_WSA_Error;
          Result := ExecItf.CloseSocket( S => Client_Socket );
        elsif Received_Size = 0 then
          Text_IO.Put_Line("ERROR: Socket-Server Receive of 0 bytes");
        else

          -- Pass the message to its associated component
          declare
            Msg : String(1..Integer(Received_Size));
            for Msg use at Message'Address;
          begin
            Data.ListenerData.List(Index).RecvCallback(Msg);
          end;
          Result := ExecItf.CloseSocket( S => Client_Socket );

        end if; -- Received_Size < 0

      end if; -- invalid Client_Socket

    end loop Connect;

  end Callback;

end Socket.Server;

Trial Run Results

Do WSAStartup
Delivery file doesn't exist          2
next path that will be searched C:\Source\KP\Try7\App2\
WARNING: Delivery.dat lacks a partner component for          8 Component8
Threads Install Component2
Thread item Name Component2
EventName Component2         168
MatchIndex           3  ClientPort        8001      16671
SenderData count           2
MatchIndex           1  ServerPort        8002      16927
Note: The second port value is with the bytes reversed for use by Windows.
Threads Install R01
Thread item Name R01
EventName R01         172
ListenerData count           1           2
calling Threads Create
in Component2 callback          0
Component2 to send to Component1
ERROR: WSA LastError      10061
Message not sent to Component1
Note: The three lines above and the similar ones below are the attempts to send when the C# app isn't running so nothing to which to send.
Component2 to send to Component1
ERROR: WSA LastError      10061
Message not sent to Component1
Component2 to send to Component1
ERROR: WSA LastError      10061
Message not sent to Component1
Component2 to send to Component1
ERROR: WSA LastError      10061
Message not sent to Component1
Server Receive after Listen 1 True
Component2 to send to Component1
ERROR: WSA LastError      10061
Message not sent to Component1
Component2 to send to Component1
ERROR: WSA LastError      10061
Message not sent to Component1
Note: The lines below are after the C# app is running.  Component2 is able to send to Component1 of the C# app and Component2 receives the message from Component1.
Component2 to send to Component1
Client Socket Connected to Transmit
Transmit sent using client socket port 16671
Component2 received a message: Component1 message for Component2
Server Receive after Listen 1 True
Component2 to send to Component1
Client Socket Connected to Transmit
Transmit sent using client socket port 16671
Component2 received a message: Component1 message for Component2
Server Receive after Listen 1 True
Component2 to send to Component1
Client Socket Connected to Transmit
Transmit sent using client socket port 16671
Component2 received a message: Component1 message for Component2
Server Receive after Listen 1 True
Component2 to send to Component1
Client Socket Connected to Transmit
Transmit sent using client socket port 16671
Component2 received a message: Component1 message for Component2
Server Receive after Listen 1 True
Component2 to send to Component1
Client Socket Connected to Transmit
Transmit sent using client socket port 16671
Component2 received a message: Component1 message for Component2
Server Receive after Listen 1 True
Component2 to send to Component1
Client Socket Connected to Transmit
Transmit sent using client socket port 16671
Component2 received a message: Component1 message for Component2
Server Receive after Listen 1 True
Component2 to send to Component1
Client Socket Connected to Transmit
Transmit sent using client socket port 16671
The three lines below are after the C# app was terminated so Component2 can no longer transmit to Component1.
Component2 to send to Component1
ERROR: WSA LastError      10061
Message not sent to Component1

The C# app console output is
Delivery Table
1 Component1 192 168 1 67 8001 8002
6 RemoteComponent 192 168 1 70 8006 8005
2 Component2 192 168 1 67 8002 8001
2 Component2 192 168 1 67 8009 8010
6 RemoteComponent 192 168 1 70 8010 8009
1 Component1 192 168 1 67 8003 8004
8 Component8 192 168 1 67 8007 8008
4 NewComponent 192 168 1 67 8004 8003
5 ExComponent 192 168 1 67 8005 8006
Thread item Name Component1
SenderData count 1 1
MatchIndex 0 192.168.1.67 ServerPort 8001
Thread item Name Receive1
Server started at:192.168.1.67:8001 1
Server listening 1
ListenerData count 1 2
SenderData count 2 1
MatchIndex 5 192.168.1.67 ServerPort 8003
Thread item Name Receive2
Server started at:192.168.1.67:8003 1
Server listening 1
ListenerData count 2 4
Thread item Name NewComponent
SenderData count 3 4
MatchIndex 7 192.168.1.67 ServerPort 8004
Thread item Name Receive4
Server started at:192.168.1.67:8004 4
Server listening 4
ListenerData count 3 1
TimingScheduler Start
TimingScheduler Start
TimingScheduler Start
TimingScheduler Start
TimingScheduler Start
Note: The lines above are from the initialization.  Those immediately below are as the three Receive callbacks begin executing in their threads for NewComponent to receive from Component1, Component1 to receive from Component2, and Component1 to receive from NewComponent.
WinSocket Receive 4
WinSocket Receive 4 entered for 2 4 8004
Server Listen info: Server listening 4 4
Server listening 4
WinSocket Receive 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1
WinSocket Receive 2
WinSocket Receive 2 entered for 1 1 8003
Server Listen info: Server listening 1 1
Server listening 1
in Component1 callback 0
Component1 sending to Component2
Component1 received a message: Component2 message to 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1
in Component4 callback 3
Note: The lines below are the two components sending messages with Component1 sending to two different components.  And lines showing that the two components received messages with Component1 receiving from both NewComponent and Component2.
NewComponent sending to Component1
Transmit 1 192.168.1.67 8002
Transmit 4 192.168.1.67 8003
Socket connected to 192.168.1.67:8002 by 1
Component1 sending to NewComponent
Transmit 1 192.168.1.67 8004
Socket connected to 192.168.1.67:8003 by 4
Component1 received a message: NewComponent message for Component1
WinSocket Receive 2 entered for 1 1 8003
Server Listen info: Server listening 1 1
Server listening 1
Socket connected to 192.168.1.67:8004 by 1
NewComponent received a message: Component1 message for NewComponent
WinSocket Receive 4 entered for 2 4 8004
Server Listen info: Server listening 4 4
Server listening 4
Component1 received a message: Component2 message to 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1
Component1 sending to Component2
Transmit 1 192.168.1.67 8002
Socket connected to 192.168.1.67:8002 by 1
Component1 sending to NewComponent
Transmit 1 192.168.1.67 8004
Socket connected to 192.168.1.67:8004 by 1
NewComponent received a message: Component1 message for NewComponent
WinSocket Receive 4 entered for 2 4 8004
Server Listen info: Server listening 4 4
Server listening 4
Component1 received a message: Component2 message to 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1
in Component4 callback 3
NewComponent sending to Component1
Transmit 4 192.168.1.67 8003
Socket connected to 192.168.1.67:8003 by 4
Component1 received a message: NewComponent message for Component1
WinSocket Receive 2 entered for 1 1 8003
Server Listen info: Server listening 1 1
Server listening 1
Component1 sending to Component2
Transmit 1 192.168.1.67 8002
Socket connected to 192.168.1.67:8002 by 1
Component1 sending to NewComponent
Transmit 1 192.168.1.67 8004
Socket connected to 192.168.1.67:8004 by 1
NewComponent received a message: Component1 message for NewComponent
WinSocket Receive 4 entered for 2 4 8004
Server Listen info: Server listening 4 4
Server listening 4
Component1 received a message: Component2 message to 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1
in Component4 callback 3
NewComponent sending to Component1
Transmit 4 192.168.1.67 8003
Socket connected to 192.168.1.67:8003 by 4
Component1 received a message: NewComponent message for Component1
WinSocket Receive 2 entered for 1 1 8003
Server Listen info: Server listening 1 1
Server listening 1
Component1 sending to Component2
Transmit 1 192.168.1.67 8002
Socket connected to 192.168.1.67:8002 by 1
Component1 sending to NewComponent
Transmit 1 192.168.1.67 8004
Socket connected to 192.168.1.67:8004 by 1
NewComponent received a message: Component1 message for NewComponent
WinSocket Receive 4 entered for 2 4 8004
Server Listen info: Server listening 4 4
Server listening 4
Component1 received a message: Component2 message to 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1
Component1 sending to Component2
Transmit 1 192.168.1.67 8002
Socket connected to 192.168.1.67:8002 by 1
Component1 sending to NewComponent
Transmit 1 192.168.1.67 8004
Socket connected to 192.168.1.67:8004 by 1
NewComponent received a message: Component1 message for NewComponent
WinSocket Receive 4 entered for 2 4 8004
Server Listen info: Server listening 4 4
Server listening 4
Component1 received a message: Component2 message to 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1
in Component4 callback 3
NewComponent sending to Component1
Transmit 4 192.168.1.67 8003
Socket connected to 192.168.1.67:8003 by 4
Component1 received a message: NewComponent message for Component1
WinSocket Receive 2 entered for 1 1 8003
Server Listen info: Server listening 1 1
Server listening 1
Component1 sending to Component2
Transmit 1 192.168.1.67 8002
Socket connected to 192.168.1.67:8002 by 1
Component1 sending to NewComponent
Transmit 1 192.168.1.67 8004
Socket connected to 192.168.1.67:8004 by 1
NewComponent received a message: Component1 message for NewComponent
WinSocket Receive 4 entered for 2 4 8004
Server Listen info: Server listening 4 4
Server listening 4
Component1 received a message: Component2 message to 1
WinSocket Receive 1 entered for 0 1 8001
Server Listen info: Server listening 1 1
Server listening 1