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#.
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#)
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