Showing posts with label sockets. Show all posts
Showing posts with label sockets. Show all posts

Wednesday, October 16, 2019

Kubernetes Using Named Pipes and TCP Sockets


Kubernetes Using Named Pipes and TCP Sockets

This post covers two things.  The minor one being combining the Named Pipes code of the previous post (Kubernetes Using Named Pipes) with the Sockets code of the previous posts (Kubernetes Follow-On (Part 4) and Pseudo Visual Compiler Using Sockets). 

Since this wasn't much of an accomplishment I decided to add the transmit of Broadcast messages to selected components to be designated in the Delivery.dat file.  This proved a "little" more difficult.  As will be commented upon.

Besides the indication which components were allowed to receive broadcast messages, the Delivery.dat file also, as time went by, had the application that supports the component added and whether the component could publish/transmit broadcast messages.  This latter mainly to eventually allow any component to transmit a broadcast message and also eventually a message of a particular topic.  This latter will need the receiving components to indicate which topics they wish to receive – that is, to subscribe to those topics in a separate section of the file.

Note: In the framework delivery method of older posts the methodology to support message topics and to subscribe to consume particular topics was the responsibility of the component and there was program support to capture these requests into a library.  In the current approach, there is the separate Delivery.dat file where the "subscription" would take place.  The implementation of topic delivery is for the future while the ability to send and receive broadcast messages (of any topic) is preparation for the delivery of particular topics to the components indicated by the Delivery.dat file.

The combination of the use of the Named Pipe and TCP Socket delivery methods was relatively minor and won't be gone into here.  It did involve specifying in the Delivery.dat file which method the component expected.  Both for transmit and receive.  Hence, the paired component had to support the same method.  Eventually, the broadcast message producer had to support both methods so delivery could be made to a component based upon which method the receiving component expected.

For broadcast messages I assumed that I would be able to use the "To" pipe name of each paired component as the pipe name for transmitting the message just as, for components using sockets, that the component's IP address and Windows sockets port could be used.  This proved not to be the case – or at least I didn't find a way to get it to work.

Wild Goose Chase


For instance, I puzzled over a problem for quite some time.  I had Component1 receiving the broadcast message (as it should have) but it appeared to be received via TCP Sockets instead of Named Pipes as I thought that it should have been.  As can be seen in the Delivery.dat file data below, Component1 was marked in the Delivery.dat file to receive broadcast messages twice – once when it was paired with NewComponent to receive messages via Named Pipes where both components were application App1 components and a second time when paired with Component2 of App2 to receive messages via TCP Sockets. 

While chasing this perceived problem I changed the broadcast message so that it indicated which communication method was going to be used.  And as I thought, Component1 was receiving the message that was being sent via sockets.  Either before or after, I had changed the table of components that were marked to receive broadcast messages to not include a component again if it was already in the table.  So this resulted in Component1 only being in the table for its first occurrence in the Delivery.dat file where it was paired with NewComponent and to use Named Pipes.  But still the problem persisted.  So I chased it and chased a solution. 

During this time Component7 kept receiving the broadcast message even though it wasn’t supposed to and components 2 and 3 didn’t receive the message although marked to receive it via Sockets.  This I thought was strange but I concentrated on the Component1 problem.

Not until the morning of the last day did the light dawn even though it should have been staring me in the face all along.  In the Delivery.dat pairings Component7 is paired with Component3 and Component1 is paired with Component2 where Component2 and Component3 were supposed to receive the broadcast message and Component1 (the source of my mystery) and Component7 were receiving the message. 

For named pipes I had created the pipe name as 8toN where N was the component id of the component to receive the message.  This because for the paired components the Delivery.dat file contained the basic name to be used via a pair of names – one name for sending to the opposite component and one for receiving from it where the names were reversed in the record of the other component.  Since the BdComponent, a sample of a component to send broadcast messages, wasn’t to receive messages and hence not paired with each of the components to receive its messages there wasn’t the Delivery file pipe name to be used.  Hence, the creation of the name; 8to1, 8to5, 8to6 for the three components marked to allow the receive of broadcast messages via named pipes where 8 was the component id of BdComponent.

However, for TCP Sockets the IP Address and Port of the Delivery file had to be used and was selected from the entries for the components to be allowed to receive broadcast messages via sockets.  These were for Component2 and Component3.  Due to my chasing what I perceived as the Component1 problem I had been blind to the obvious – that Component1 and Component7 were the components paired with 2 and 3.  The problem being that the wrong port of the Component2 and Component3 was being selected.  That is, the port that was to be used to send messages rather than receive them.  Therefore, the broadcast messages were being received by the opposite component of the pair!  Talk about being blind to the obvious and hence the wild goose chase.

Results


Delivery.dat file
1|Component1|Pipe|Bd|COSTCO-HP|1to4|4to1|
4|NewComponent|Pipe| |COSTCO-HP|4to1|1to4|
5|Component5|Pipe|Bd|COSTCO-HP|5to6|6to5|
6|Component6|Pipe|Bd|COSTCO-HP|6to5|5to6|
3|Component3|Socket|Bd|x1.x2.x.y|8004|8003|
7|Component7|Socket| |x1.x2.x.y|8003|8004|
2|Component2|Socket|Bd|x1.x2.x.y|8002|8001|
1|Component1|Socket|Bd|x1.x2.x.y|8001|8002|
8|BdComponent|Both| |COSTCO-HP|8to1|1to8|x1.x2.x.y|8008|8008|
where x1, x2, x and y have been substituted for the actual IP address for this blog post.

Text output at the start of such output for App1:
. . .
portions of the DeliveryTable as parsed from Delivery.dat
  1 Component1 Pipe COSTCO-HP 1to4 4to1 0 0
  4 NewComponent Pipe COSTCO-HP 4to1 1to4 0 0
  5 Component5 Pipe COSTCO-HP 5to6 6to5 0 0
  6 Component6 Pipe COSTCO-HP 6to5 5to6 0 0
  3 Component3 Sock    x1.x2.x.y 8004 8003
  7 Component7 Sock    x1.x2.x.y 8003 8004
  2 Component2 Sock    x1.x2.x.y 8002 8001
  1 Component1 Sock    x1.x2.x.y 8001 8002
  8 BdComponent Sock COSTCO-HP 8to1 1to8 x1.x2.x.y 8008 8008
. . .
and the table built by the Broadcast-Client Request procedure invoked by the BdBroadcast package
Components to Receive Broadcast Messages
Com 1 Component1 COSTCO-HP 8to1
Com 5 Component5 COSTCO-HP 8to5
Com 6 Component6 COSTCO-HP 8to6
Com 3 Component3 17439
Com 2 Component2 16927
where 17439 and 16927 are the reversed byte port integers used by Sockets for ports 8004 and 8002, the first port of the pair specified for Component3 and Component2.

Problem With Using Named Pipes


There is a problem for the use of Named Pipes for broadcast messages.

With the use of broadcast messages there has to be a named pipe for each component that can receive a broadcast message and one for every component that can send a broadcast message.  That is, there has to be a match between the pipe name used to transmit the message and that used to receive it. 

And for every such named pipe there has to be a receive thread to wait for the message.  This will quickly escalate the number of threads to be supported by the application if the same pipe name and receive thread can’t be used as for the paired messages.

I tried to send a broadcast message using the receive pipe name of a paired component such as 4to1 or 2to1.  But this didn’t work.  So I went to names like Bto1.  But this only worked for one broadcasting component.  (Note: By this time I also had a Component9 of App2 broadcasting messages.)  So for a second component sending broadcast messages I had to create yet another pipe name.  That is, it created a situation like that of the paired components of broadcast-component-to-receiving-component for each possible receiving component.  So instead of one receive thread for the receiving component of the paired component a whole set of such pairs was required.  And required that it the Delivery.dat file specify which components were going to transmit broadcast messages so that the pipe names could be generated.

Results having only one server to receive broadcast messages for a named pipe


Starting App1 before App2
Now got
BdComponent to send 29 BdComponent Message for all 2
Broadcast Client SenderData ToId 1 Bto1
NamePipe-Server Receive of 29 Index 4 Bto1 4317470
Component1 received a message: BdComponent Message for P 1 2
Transmit WriteFile OK 29
Broadcast Client SenderData ToId 5 Bto5
Transmit WriteFile OK 29
NamePipe-Server Receive of 29 Index 6 Bto5 4320654
Broadcast Client SenderData ToId 6 Bto6
Component5 received a message: BdComponent Message for P 5 2
with App1, when started first, getting the broadcast message by both components marked to receive such messages.

Also
Component1 received a message: NewComponent Message for Component1 2

NewComponent received a message: Component1 Message for NewComponent 4

Component1 received a message: Component2 Message for Component1 2
NamePipe-Server Receive of 35 Index 5 6to5 4320654
Component5 received a message: Component6 Message for Component5 2
So all the paired messages were also received.

Then I got for App2,
Component2 received a message: Component1 Message for Component2 3

Component6 received a message: BdComponent Message for P 6 2
NamePipe-Server Receive of 29 Index 4 Bto3 4318206
Component3 received a message: BdComponent Message for P 3 2
NamePipe-Server Receive of 29 Index 2 Bto2 4315754
Component2 received a message: BdComponent Message for P 2 2
NamePipe-Server Receive of 35 Index 5 5to6 4320658
Component6 received a message: Component5 Message for Component6 2

Component2 received a message: Component1 Message for Component2 4

Component7 received a message: Component3 Message for Component7 2

Component3 received a message: Component7 Message for Component3 2
So each component receives its paired messages and the broadcast messages from BdComponent but not from Component9 which sends its messages.

Then, starting App2 first App1 has the results
NamePipe-Server Receive of 35 Index 1 2to1 4317470
Component1 received a message: Component2 Message for Component1 2
NamePipe-Server Receive of 35 Index 5 6to5 4320654
Component5 received a message: Component6 Message for Component5 2
NamePipe-Server Receive of 28 Index 2 Bto1 4317470
Component1 received a message: Component9 Message for P 1 2
NamePipe-Server Receive of 28 Index 6 Bto5 4320654
Component5 received a message: Component9 Message for P 5 2

NamePipe-Server Receive of 28 Index 2 Bto1 4317470
Component1 received a message: Component9 Message for P 1 3
NamePipe-Server Receive of 28 Index 6 Bto5 4320654
Component5 received a message: Component9 Message for P 5 3
OpenReceivePipe \\.\pipe\4to1
Broadcast Callback Bto5
NamePipe-Server Receive of 35 Index 1 2to1 4317470
Component1 received a message: Component2 Message for Component1 3
NamePipe-Server Receive of 35 Index 5 6to5 4320654
Component5 received a message: Component6 Message for Component5 3

NamePipe-Server Receive of 37 Index 7 1to4 4322978
NewComponent received a message: Component1 Message for NewComponent 2

And App2 has the text output
NamePipe-Server Receive of 35 Index 7 3to7 4323110
Component7 received a message: Component3 Message for Component7 2

NamePipe-Server Receive of 35 Index 3 7to3 4318206
Component3 received a message: Component7 Message for Component3 2

NamePipe-Server Receive of 28 Index 6 Bto6 4320658
Component6 received a message: Component9 Message for P 6 2
Transmit WriteFile OK 28
Broadcast Client SenderData ToId 3 Bto3
Transmit WriteFile OK 28
NamePipe-Server Receive of 28 Index 4 Bto3 4318206
Broadcast Client SenderData ToId 2 Bto2
Component3 received a message: Component9 Message for P 3 2
NamePipe-Server Receive of 28 Index 2 Bto2 4315754
Component2 received a message: Component9 Message for P 2 2

NamePipe-Server Receive of 35 Index 1 1to2 4315754
Component2 received a message: Component1 Message for Component2 2

NamePipe-Server Receive of 35 Index 7 3to7 4323110
Component7 received a message: Component3 Message for Component7 3

NamePipe-Server Receive of 35 Index 5 5to6 4320658
Component6 received a message: Component5 Message for Component6 2

So all the paired component messages are received (although the Component5 message of App1 took quite some time) and all the broadcast messages of Component9 were received by the components of both apps.

When App1 is started first its broadcaster gets the pipes and when App2 is started first its broadcaster gets the pipes.  Therefore separate servers are required for each different transmitter.

Therefore, by these results, a pipe is necessary for each broadcaster with a receiver for each pipe.  This would be manageable in the configuration that I currently have with just two components sending broadcast messages.  That is, just add another set of pipes for the second broadcaster.  However, in the long run this is a non-starter. 

That is, if any component can send a broadcast message, the number of pipes and the number of receiver threads goes up substantially.  This wasn’t such a problem when messages were between applications and then forwarded to the components of interest as in the delivery frameworks of posts of the previous delivery method .  For instance, three applications than required three pairs of pipes (to make them unidirectional).  But for multiple components each potentially transmitting to a number of other components the use of named pipes becomes cumbersome and resource intensive.

Therefore, it seems that the use of sockets is a better choice.  Instead of expanding the number of named pipes from the one extra per receiving component of the current code I returned to the use of sockets for most of broadcast message delivery leaving just the application one (App1) components to receive the messages via named pipes.  This still necessitated a second broadcast pipe for each of the two App1 receiving components which will be illustrated in the code.  This doesn’t place a great burden on the application resources since there are only two components that send broadcast messages. 

However, for the general case where any component could send a broadcast message it would be necessary for the Delivery file to specify which components would do so enabling the code to create the necessary pipe names and receive threads.

The fix to allow the receive of Broadcast messages from multiple transmitters

As mentioned before, I had thought that I could use the same named pipe path name for a broadcast message as that for a paired component when the To component was the same component as that to receive the broadcast message.  I failed to achieve that so came up with the solution of the above section where a separate pipe name was used for components that were allowed to receive broadcast messages.  But it only delivered the messages of the broadcast component of the application that started first and, hence, secured the connection.

Therefore, I created in the Delivery package a table with a double dimensioned array of pipe names.  The first dimension (the table rows) for the each component that could publish a broadcast message and the second dimension (the table columns) for the receiving components that used Named Pipes for the delivery method.

This table was then filled in with pipe names – a different one for each transmitter, receiver combination.  Then the particular pipe name was used that matched both the transmitter and the receiver.

In the current configuration in Delivery.dat this resulted in
B08to01 B08to05 B08to06
B09to01 B09to05 B09to06
where the first row corresponds to the BdComponent of App1 and the second row to the Component9 of App2.  The columns are for the 3 components (Component1, 5 and 6) that receive their messages via Named Pipes.

This could then be extended for additional components to allow them to send these unpaired component messages including those that receive paired messages.  In addition, changes should be easy enough to make to allow components to register (via another .dat file or a different section of the general .dat file) for particular topics and the component that published the topic to have the Client package select the set of receiver components to which to transmit the topic message.  (Shall I say I leave this as an exercise for the reader?)

However, like the Kubernetes project of GitHub, the use of TCP Sockets is the better choice since (with Windows) this only requires one port for each component rather then one pipe name for each From-To component combination.

Results having only a server to receive broadcast messages for each named pipe


With two transmitters of broadcast messages – BdComponent installed in App1 and Component9 installed in App2, App1 got
Component1 received a message: BdComponent Message for P 1 2 ß
Transmit sending 35 bytes WriteFile
Transmit WriteFile OK 37 4to1
NamePipe-Server Receive of 37 Index 4 4to1 4321894
Component1 received a message: NewComponent Message for Component1 2
Transmit WriteFile OK 29
Broadcast Client SenderData ToId 5
Transmit WriteFile OK 35 5to6
Transmit WriteFile OK 29
Broadcast Client SenderData ToId 6
NamePipe-Server Receive of 29 Index 6  4325078
Transmit WriteFile OK 29
Component5 received a message: BdComponent Message for P 5 2 ß
Broadcast Client SenderData ToId 3
Transmit WriteFile OK 29
Broadcast Client SenderData ToId 2
Transmit WriteFile OK 29
Broadcast Callback B08to05
Broadcast Callback B08to01
Component1 to send to Component2 35 Component1 Message for Component2 4
Client Transmit Index 1
Transmit sending 35 bytes WriteFile
Transmit WriteFile OK 35 1to2
NamePipe-Server Receive of 28 Index 3  4321894
Component1 received a message: Component9 Message for P 1 5 ß
NamePipe-Server Receive of 35 Index 5 6to5 4325078
Component5 received a message: Component6 Message for Component5 5
NamePipe-Server Receive of 35 Index 1 2to1 4321894
Component1 received a message: Component2 Message for Component1 5
NamePipe-Server Receive of 28 Index 7  4325078
Component5 received a message: Component9 Message for P 5 5 ß
So the broadcast messages now received from both transmitters of broadcast messages.

And App2 received
Component6 received a message: BdComponent Message for P 6 2 ß
NamePipe-Server Receive of 29 Index 5  4322630
NamePipe-Server Receive of 29 Index 2  4320178
Component3 received a message: BdComponent Message for P 3 2 ß
Component2 received a message: BdComponent Message for P 2 2 ß
. . .
Component6 received a message: Component9 Message for P 6 5 ß
Component7 received a message: Component3 Message for Component7 5
NamePipe-Server Receive of 28 Index 6  4322630
Component3 received a message: Component9 Message for P 3 5 ß
Broadcast Client SenderData ToId 2
Transmit WriteFile OK 28
NamePipe-Server Receive of 28 Index 3  4320178
Component2 received a message: Component9 Message for P 2 5 ß

So each component that was allowed to receive broadcast messages received them from each of the transmitters.

Delivery.dat that was used to produce the above results

In order to concentrate on the changes needed to deliver broadcast messages via Named Pipes the Delivery data file was changed to only specify the use of pipes. 

The fields of each record are separated by the | token.  The fields in order are
1 Component Id and Component Name
2 Application Id
3 The delivery method
5 Whether the component will transmit broadcast messages
6 Computer name to be used for Named Pipe delivery
7 Pipe name to transmit
8 Pipe name to receive

For components that transmit broadcast messages they must support both named pipes and sockets so they can do so via sockets if the receiving component specifies the use of sockets.  The IP address isn't identified.

1|Component1|1|Pipe|Bd| |COSTCO-HP|1to4|4to1|
4|NewComponent|1|Pipe| | |COSTCO-HP|4to1|1to4|
5|Component5|1|Pipe|Bd| |COSTCO-HP|5to6|6to5|
6|Component6|2|Pipe|Bd| |COSTCO-HP|6to5|5to6|
3|Component3|2|Pipe|Bd| |COSTCO-HP|3to7|7to3|
7|Component7|2|Pipe| | |COSTCO-HP|7to3|3to7|
2|Component2|2|Pipe|Bd| |COSTCO-HP|2to1|1to2|
1|Component1|1|Pipe|Bd| |COSTCO-HP|1to2|2to1|
8|BdComponent|1|Both| |Bd|COSTCO-HP|8to1|1to8|xx.x.x.xxx|8008|8008|
9|Component9|2|Both| |Bd|COSTCO-HP|9to1|1to9|xx.x.x.xxx|8009|8009|

Notice that Component1 and Component5 of application 1 and Component6, Component3, and Component2 of application 2 are specified to receive broadcast messages while BdComponent of application 1 and Component9 of application 2 transmit broadcast messages.

Windows 10 Problems

What with the support for Windows 7 ending at the end of the year I went ahead and got a Windows 10 laptop.  What a fiasco that has turned out to be. 

It won't allow the applications that I have been using to be installed or to run.  And the GNAT GPS Ada that I have been using for these private projects won't run in the versions that I have been using and the latest version GNAT GPS 2019 that I had to install as the only one seemingly available for download has problems that prevent it from being usable.  For instance, I did manage to get a build for the App1 application but it failed for the App2 application that is identical in structure.  That is, it built to a much smaller executable that doesn't do anything.  Perhaps I'll figure that one out but I failed to do so after a couple of days of trying.

Then there is the need for special treatment to be able to run my own created applications (that is, app1 and app2).  That I found out about and can get around.  But the need to buy all new applications suitable for Windows 10 seems to me to be a RIP OFF.  A version of Windows that Microsoft is pushing us into needing to use that is completely deficient.  That is, Windows 10 can't install or run programs that earlier versions of Windows had no problems running.

So I'll need to continue to use Windows 7 even though it’s unsupported come the end of the year.

Return to the use of Sockets for delivery

Returning to the use of Win Sockets for delivery to three of the App2 components while retaining the use of Named Pipes for the App1 components and one of the App2 components the Delivery.dat file looks like
1|Component1|1|Pipe|Bd| |COSTCO-HP|1to4|4to1|
4|NewComponent|1|Pipe| | |COSTCO-HP|4to1|1to4|
5|Component5|1|Pipe|Bd| |COSTCO-HP|5to6|6to5|
6|Component6|2|Pipe|Bd| |COSTCO-HP|6to5|5to6|
3|Component3|2|Socket|Bd| |xx.x.x.xxx|8004|8003|
7|Component7|2|Socket| | |xx.x.x.xxx|8003|8004|
2|Component2|2|Socket|Bd| |xx.x.x.xxx|8002|8001|
1|Component1|1|Socket|Bd| |xx.x.x.xxx|8001|8002|
8|BdComponent|1|Both| |Bd|COSTCO-HP|8to1|1to8|xx.x.x.xxx|8008|8008|
9|Component9|2|Both| |Bd|COSTCO-HP|9to1|1to9|xx.x.x.xxx|8009|8009|

With the use of Sockets the same port could be used to transmit to a component and the same receive callback thread could be used whether the receiving component was receiving a paired component message or a broadcast message.

This is important since I had to have a different named pipe for each From component to To component connection.  So I had to have a different receive thread to await the message for each connection.  Therefore, for larger applications with more components this would become a major use of resources.

I had thought that I could find a method to reuse the paired component connection for the broadcast messages.  But as I have mentioned I failed to do so.  I suppose I could try again but to what purpose when the use of Sockets can be used in place of Named Pipes.

Results using both named pipes and sockets to receive messages


The App1 results
Component1 received a message: NewComponent Message for Component1 1  ß the paired
. . .
NewComponent received a message: Component1 Message for NewComponent 2 ßcomponents
. . .
Component5 received a message: BdComponent Message for P 5 2 ß the local broadcast
NamePipe-Server Receive of 29 Index 2  4324078
Component1 received a message: BdComponent Message for P 1 2 ß   messages
. . .
Component1 received a message: Component2 Message for Component1 1 ß remote paired
. . .
Component1 received a message: Component9 Message for P 1 2 ß the remote broadcast
NamePipe-Server Receive of 28 Index 6  4327262
Component5 received a message: Component9 Message for P 5 2 ß   messages
NamePipe-Server Receive of 35 Index 4 6to5 4327262
Component5 received a message: Component6 Message for Component5 2 ß remote paired

So the App1 components received all their messages.  Note these all used Named Pipes. 

The App2 results
Component6 received a message: Component5 Message for Component6 2 ç remote paired
Socket-Server Receive of 29 Index 2
Component3 received a message: BdComponent Message for S 3 1 ç remote broadcast
Socket Server Receive after Listen 2 True
Socket-Server Receive of 29 Index 1
Component2 received a message: BdComponent Message for S 2 1 ç remote broadcast
. . .
Component2 received a message: Component1 Message for Component2 2 ß remote paired
. . .
Component6 received a message: Component5 Message for Component6 3
NamePipe-Server Receive of 29 Index 2  4327266
Component6 received a message: BdComponent Message for P 6 2 ß all three
Socket-Server Receive of 29 Index 2
Component3 received a message: BdComponent Message for S 3 2 ß  components rec'd
Socket Server Receive after Listen 2 True
Socket-Server Receive of 29 Index 1
Component2 received a message: BdComponent Message for S 2 2 ß  remote broadcast
Socket Server Receive after Listen 1 True
Socket-Server Receive of 35 Index 1
Component2 received a message: Component1 Message for Component2 3
. . .
Component7 received a message: Component3 Message for Component7 1 ß local paired
. . .
Component3 received a message: Component7 Message for Component3 1 ß local paired
Transmit sent using client socket port 17439
ERROR: Write to pipe failed 404 0
. . .
Component3 received a message: Component9 Message for S 3 1 ß local broadcast
Broadcast Callback B09to06
OpenReceivePipe \\.\pipe\B09to06
NamedPipe Server (Receive) Handle is Valid 412
Server connecting to client...
Socket Server Receive after Listen 2 True
Client Socket Connected to Transmit
        412
NamedPipe Server setting Connected           0
Socket-Server Receive of 28 Index 1
Component2 received a message: Component9 Message for S 2 1 ß local broadcast
. . .
Component6 received a message: Component9 Message for P 6 2 ß local broadcast

So the App2 components received all their messages.  Component6 via Named Pipes and Components 3, 7, and 2 via Sockets.

Application Code

The code will be resented in a separate post.

Friday, July 5, 2019

Pseudo Visual Compiler Using Sockets


Pseudo Visual Compiler Using Sockets

This post is an update to those of 12/2018 through 3/2019 titled "Pseudo Visual Compiler of Decades Ago" and "Pseudo Visual Compiler using Interface to Ada as the OFP" and its follow on posts.  These posts were about having a C# CDU display with its Windows Forms and an Ada pseudo aircraft OFP application.  This post is about switching from the message delivery approach of that time to the new use of Windows Sockets as in the last five posts.

Using the June 2019 methods, it was an easy modification of the Ada application and a real simplification of the framework used by the C# CDU application. 

The reason for this exercise is due to my section named Comments in the "Kubernetes Follow-On (Part 4)" post.  It is an example of how the use of Windows Sockets really simplifies the delivery of a messages between application components.

Other areas that will be examined next will be the use of a different named pipe for each component to component interface to see if named pipes can then use the same methods and have direct delivery from one component to another.  Within the same application and between applications.  Then, if that succeeds, to have both socket addresses and named pipe names within the Delivery.dat file and have an intermediate software layer between the component and the socket or named pipe methods to determine which delivery method is to be used.

Ada Code

There is very little difference between ComOFP of implementing the pseudo OFP and Component2 of the "Kubernetes Follow-On (Part 4)" post.  There are also minor changes to Socket-Client and Socket-Server to be directly passed a byte array for Transmit and to pass a received byte array back via the message callback.  That is, instead of passing a string a byte array is passed and it is for the component to "know" the format of the message and encode or decode it from the byte array.

ComOFP

This simple component is only to check the received message and send a response that can be displayed in the text box of the CDU form of the C# application.  It is much like that of the "Pseudo Visual Compiler using Interface to Ada as the OFP - Part 5" post.  That is, in concept to decode the received message and create the response.  Its ComPublish has to have the code to support the delivery of the message so has extra code.

The Install procedure is directly similar to Component2 of the "Kubernetes Follow-On (Part 4)" post.  The forever loop activated by the Threads package is a nothing procedure since the receive callback as invoked by Socket-Server causes the transmit via SendResponse.  The SendResponse procedure runs in the Receive Callback thread of Socket-Server.

The message callback first checks if the format of the message matches either of the two messages that ComCDU would transmit.  If not, it aborts the execution.  If the keypush message, SendResponse is invoked to decode the string data that follows the first 3 bytes and create and transmit the response.  Where the response is the three leading bytes to specify the topic and the length of the string that follows.

with Itf;
with Socket.Client;
with Socket.Server;
with System;
with Text_IO;
with Threads;
with Unchecked_Conversion;

package body ComOFP is

  package Int_IO is new Text_IO.Integer_IO( Integer );

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

  CurrentThread
  : ExecItf.HANDLE;

  SocketFromCDU : Boolean; -- keypush message
  SocketToCDU   : Boolean; -- response message

  procedure Callback
  ( Id : in Integer
  );

  -- Receive Message from ComCDU
  procedure ReceiveCallback
  ( Message : in Itf.BytesType
  );

  -- Convert Message data to a string, create response, and transmit it
  procedure SendResponse
  ( Message : in Itf.BytesType
  );

  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

    CurrentThread := ExecItf.GetCurrentThread;
   
    -- Install the component into the Threads package.
    Result := Threads.Install
              ( Name     => "ComOFP",
                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 ???

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

      -- Request the ability to receive from ComCDU.
      SocketFromCDU := Socket.Server.Request
                       ( "ComOFP",
                         2,
                         "ComCDU",
                         1,
                         to_RecvCallback(ReceiveCallback'Address) );
      if not SocketFromCDU then
        Text_IO.Put_Line(
                 "Socket.Server not valid for ComOFP, ComCDU 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 ComCDU
  procedure ReceiveCallback
  ( Message : in Itf.BytesType
  ) is

  begin -- ReceiveCallback

    Text_IO.Put("ComOFP received a message: ");
    declare
      use type Itf.Byte;
    begin
      Text_IO.Put_Line("Received Message");

      -- Check if connect message to receive from ComCDU
      if Message.Count = 3    and then
         Message.Bytes(1) = 3 and then
         Message.Bytes(2) = 3 and then
         Message.Bytes(3) = 0
      then -- "connect" message received.  Ignore it.
        Text_IO.Put_Line("Connect message received");
        return;
      end if;

      -- Check if valid message to receive from ComCDU
      if Message.Count < 4 or else     -- no room for text
         Message.Bytes(1) /= 1 or else -- Topic of CDU
         Message.Bytes(2) /= 1         -- Topic of Keypush
      then
        Text_IO.Put_Line("ERROR: Invalid message - can't be from ComCDU");
        ExecItf.ExitThread( ExitCode => 1 );
      else
        -- Parse the message and transmit the response
        SendResponse(Message);
      end if;
    end;

  end ReceiveCallback;

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

  begin -- Callback

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

  end Callback;

  procedure SendResponse
  ( Message : in Itf.BytesType
  ) is

    –- Convert data to string
    Data : String(1..Integer(Message.Bytes(3))); -- third byte is size of text
    for Data use at Message.Bytes(4)'Address;

    Response   : Itf.BytesType;
    MessageOut : String(1..16);
    for MessageOut use at Response.Bytes(4)'Address;

    use type Itf.Byte;

  begin -- SendResponse
   
    Response.Bytes(1) := 1; -- CDU topic
    Response.Bytes(2) := 2; -- CHANGEPAGE topic
    Response.Bytes(3) := 0; -- erroneous input
    MessageOut := ( others => ASCII.NUL );

    if Message.Bytes(3) = 2 then
      if Data(1..2) = "UP" then
        Response.Bytes(3) := 11;
        MessageOut(1..11) := "Up Selected";
      end if;
    elsif Message.Bytes(3) = 3 then
      if Data(1..3) = "DIR" then
        Response.Bytes(3) := 3;
        MessageOut(1..3) := "DIR";
      end if;
    elsif Message.Bytes(3) = 4 then
      if Data(1..4) = "PROG" then
        Response.Bytes(3) := 4;
        MessageOut(1..4) := "PROG";
      elsif Data(1..4) = "PERF" then
        Response.Bytes(3) := 4;
        MessageOut(1..4) := "PERF";
      elsif Data(1..4) = "INIT" then
        Response.Bytes(3) := 4;
        MessageOut(1..4) := "INIT";
      elsif Data(1..4) = "DATA" then
        Response.Bytes(3) := 4;
        MessageOut(1..4) := "DATA";
      elsif Data(1..4) = "PREV" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "Previous Page";
      elsif Data(1..4) = "NEXT" then
        Response.Bytes(3) := 9;
        MessageOut(1..9) := "Next Page";
      elsif Data(1..4) = "DOWN" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "Down Selected";
      end if;
    elsif Message.Bytes(3) = 5 then
      if Data(1..5) = "LSKL1" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect L1";
      elsif Data(1..5) = "LSKL2" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect L2";
      elsif Data(1..5) = "LSKL3" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect L3";
      elsif Data(1..5) = "LSKL4" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect L4";
      elsif Data(1..5) = "LSKL5" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect L5";
      elsif Data(1..5) = "LSKL6" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect L6";
      elsif Data(1..5) = "LSKR1" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect R1";
      elsif Data(1..5) = "LSKR2" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect R2";
      elsif Data(1..5) = "LSKR3" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect R3";
      elsif Data(1..5) = "LSKR4" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect R4";
      elsif Data(1..5) = "LSKR5" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect R5";
      elsif Data(1..5) = "LSKR6" then
        Response.Bytes(3) := 13;
        MessageOut(1..13) := "LineSelect R6";
       end if;
    elsif Message.Bytes(3) = 10 then
      if Data(1..10) = "FLIGHTPLAN" then
        Response.Bytes(3) := 10;
        MessageOut(1..10) := "FLIGHTPLAN";
      end if;
    end if;
    if Response.Bytes(3) = 0 then
      Text_IO.Put_Line("ERROR: Invalid Key data");
      Response.Bytes(3) := 16;
      MessageOut(1..16) := "Invalid Key data";
    end if;        
    Response.Count := Integer(Response.Bytes(3)+3);

    if SocketToCDU then
      Text_IO.Put_Line("ComOFP to send to ComCDU");
      if not Socket.Client.Transmit( 2, 1, -- from 2 (ComOFP) to 1 (ComCDU)
                                     Response )
      then
        Text_IO.Put_Line( "Message not sent to ComCDU" );
      end if;
    end if;

  end SendResponse;

end ComOFP;


Except for the modifications below the rest of the Ada code is the same as that in the "Kubernetes Follow-On (Part 4)" post.

Socket-Client only differs from the final Kubernetes posts in that Transmit is passed a message of type Itf.BytesType rather than String that has the Count field giving the number of bytes of data and the Bytes field.  Therefore, Transmit doesn't need to do anything to Send a byte array.  So the portion of Transmit that does the Send is now
      -- Send
      declare
      begin
        Bytes_Written :=
          ExecItf.Send( S     => Data.SenderData.List(Index).Sender,
                        Buf   => to_PCSTR(Message.Bytes'Address),
                        Len   => ExecItf.INT(Message.Count),
                        Flags => 0 );
      end;
      if Bytes_Written /= ExecItf.INT(Message.Count) 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;
while the rest of Transmit as well as the rest of Socket-Client is unchanged.

Socket-Server only differs from the final Kubernetes posts in the code that receives the message and forwards it to the component.  The declaration of Message of
    Message
    -- Message as read from socket
    : Itf.Message_Buffer_Type;
was removed from the Callback as no longer necessary.  The declare block following a valid C_Accept is now (showing the Accept)
      -- 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

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

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

          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");
          elsif Integer(Received_Size) > Itf.MessageSize then
            Text_IO.Put_Line(
              "ERROR: Socket-Server Receive of more than MessageSize bytes");
            --         terminate; -- has to be from elsewhere
            --accept Quit;
            exit; -- has to be from elsewhere

          else

            -- Pass the message to its associated component
            Message.Count := Integer(Received_Size);
            Data.ListenerData.List(Index).RecvCallback( Message => Message );

            Result := ExecItf.CloseSocket( S => Client_Socket );

          end if; -- Received_Size < 0
        end;
where the Recv function reads directly into the relocated and retyped Message with the Received_Size set as the Message.Count after the validations.  Then the received Message is passed to the component message callback without converting it to a string.

No other changes were necessary and the above two were only necessary since it is now up to the component to encode and decode the message, as a byte array, according to the format of the message.  Where the format is agreed by the two components involved.  In this case ComOFP and the remote ComCDU component.

C# Code

Program

In the Kubernetes Follow-On posts, the Program class Main method that is entered upon the application launch invokes a framework class to structure the application like other, non-Windows Forms applications.  That is discarded in this Windows Sockets version.  Instead, Program is
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;

namespace SocketApplication
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
            {
                System.Threading.Thread.CurrentThread.Name = "MainThread";
            }
            currentProcess = Process.GetCurrentProcess();

            // Open ConsoleOut text file
            ConsoleOut.Install();

            // Locate, read, and parse Delivery.dat to build DeliveryTable
            Delivery.Initialize();

            // Install the components of this application.
            ComCDU.Install();

            // Now, after all the components have been installed, create the
            // threads for the components to run in.
            //  o In addition, the TimingScheduler thread will be created.
            //  o There will be a return from Create and then the created threads
            //    will begin to execute.  And the main procedure application
            //    thread will no longer be executed -- instead, the created
            //    threads will be the only threads running.
            Threads.Create();

            // Except, that with this framework plus Windows Forms application,
            // this main procedure application thread will continue to be
            // executed in the CDUForm.
            CDUForm cduForm;
            cduForm = new CDUForm();
            Application.Run(cduForm);
        } // end Main

        static private Process currentProcess;

    } // end class Program
} // end namespace

This first gets the currently running process as before.  Next it invokes the initialization that is necessary before the Windows CDUForm can be run.  That is,
1) ConsoleOut that is necessary because, as a forms application, there is no console.  Therefore, ConsoleOut is used to write the text to a file that can be examined after the application has been terminated.
2) Delivery to locate the Delivery.dat file, read it, build the Delivery Table, and verify it.
3) Install the ComCDU component, only component of the application.
4) Create the threads requested by the preceding class instances causing their callbacks to be entered – each in its own thread.

Then, the Windows CDUForm is run.  This is the replacement for the Form1 created by the Visual C# Express application of Microsoft which is created when the project is setup as a Windows Forms project.  As I discovered when first attempting to blend Windows Forms with my delivery framework code, this allows Windows forms events to occur while sending messages between applications as long as there is an interface that waits to return to the Windows form until it can proceed.  The created Form1 is ignored and, in this case, the CDUForm is run instead.  The CDUForm uses the CDUForm.Designer and the CDUForm.cs[Design] of the Kubernetes Follow-On posts unchanged while CDUForm.cs itself was used with only slight changes.

CDUForm

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace SocketApplication
{
    public partial class CDUForm : Form
    {
        static private ComCDU comCDU = new ComCDU();
        static private CDUForm cduForm = new CDUForm();

        // The type to be returned by ComCDU
        public struct Result
        {
            public bool success;
            public string text;
        }

        public enum Key
        { // the line select keys and a portion of the other non-alphanumeric keys
            LSKL1,
            LSKL2,
            LSKL3,
            LSKL4,
            LSKL5,
            LSKL6,
            LSKR1,
            LSKR2,
            LSKR3,
            LSKR4,
            LSKR5,
            LSKR6,
            DATA,
            DIR,
            FLIGHTPLAN,
            INIT,
            PERF,
            PROG,
            PREV,
            NEXT,
            UP,
            DOWN
        } // end enum Key
       
        public CDUForm()
        {
            InitializeComponent();
        } // end constructor

        public void DisplayPageTitle(string text)
        {
            // Display new Title if key push was to known page
            if ((text == "DIR") || (text == "PROG") || (text == "PERF") ||
                (text == "INIT") || (text == "DATA") || (text == "FLIGHTPLAN"))
            {
                DisplayLabel.Text = text;
            }
            // Display result in text box no matter what
            textBoxL2.Text = text;
        } // end DisplayPageTitle

        // This method is to react to a key push
        private void React(Key key)
        {
           ConsoleOut.WriteLine("React " + key);
//           if (ComOFP.connected) // fully connected to remote app
           {
              // Send key to OFP application
              Result result = new Result();
              result = comCDU.TreatKey(key);
              if (result.success)
              {
                  DisplayPageTitle(result.text);
              }
              else
              {
                  textBoxL2.Text = "key ignored";
              }
           }

        } // end React

        //**********************************************************************
        // Beginning of event handlers

        // These event handlers all invoke the React method to allow a common
        // method to determine whether in the build table mode or the OFP mode.
        // This allows a common form to be used to interpret the button push
        // as from the visual compiler / table builder or from the OFP using
        // the created table.
        private void LSKL1_Click(object sender, EventArgs e)
        {
            React(Key.LSKL1);
        }

        private void LSKL2_Click(object sender, EventArgs e)
        {
            React(Key.LSKL2);
        }

        private void LSKL3_Click(object sender, EventArgs e)
        {
            React(Key.LSKL3);
        }

        private void LSKL4_Click(object sender, EventArgs e)
        {
            React(Key.LSKL4);
        }

        private void LSKL5_Click(object sender, EventArgs e)
        {
            React(Key.LSKL5);
        }

        private void LSKL6_Click(object sender, EventArgs e)
        {
            React(Key.LSKL6);
        }

        private void LSKR1_Click(object sender, EventArgs e)
        {
            React(Key.LSKR1);
        }

        private void LSKR2_Click(object sender, EventArgs e)
        {
            React(Key.LSKR2);
        }

        private void LSKR3_Click(object sender, EventArgs e)
        {
            React(Key.LSKR3);
        }

        private void LSKR4_Click(object sender, EventArgs e)
        {
            React(Key.LSKR4);
        }

        private void LSKR5_Click(object sender, EventArgs e)
        {
            React(Key.LSKR5);
        }

        private void LSKR6_Click(object sender, EventArgs e)
        {
            React(Key.LSKR6);
        }

        private void DIR_Click(object sender, EventArgs e)
        {
            React(Key.DIR);
        }

        private void PROG_Click(object sender, EventArgs e)
        {
            React(Key.PROG);
        }

        private void PERF_Click(object sender, EventArgs e)
        {
            React(Key.PERF);
        }

        private void INIT_Click(object sender, EventArgs e)
        {
            React(Key.INIT);
        }

        private void DATA_Click(object sender, EventArgs e)
        {
            React(Key.DATA);
        }

        private void FPLAN_Click(object sender, EventArgs e)
        {
            React(Key.FLIGHTPLAN);
        }

        private void PREV_Click(object sender, EventArgs e)
        {
            React(Key.PREV);
        }

        private void NEXT_Click(object sender, EventArgs e)
        {
            React(Key.NEXT);
        }

        private void UP_Click(object sender, EventArgs e)
        {
            React(Key.UP);
        }

        private void DOWN_Click(object sender, EventArgs e)
        {
            React(Key.DOWN);
        }

        private void DisplayLabel_Click(object sender, EventArgs e)
        {

        }

    } // end class

} // end namespace

This version of CDUForm is practically identical to the previous version.  Except, as can be seen, the check for connected to the remote component has been commented out and the local component has been renamed ComCDU rather then ComOFP.

Any key click event invokes the React method passing the enumerated literal to it.  React then passes the enumerated literal for the key to TreatKey of ComCDU.  TreatKey doesn't return until the key has been treated by attempting to transmit it to its paired component – that is, ComOFP of the Ada application.  This takes advantage of sockets where SocketClient Transmit will return immediately if there isn't a connection.  Therefore, TreatKey will return a negative result and the "key ignored" message will be displayed in the text box.

ComCDU

This component generally follows ComOFP of the Kubernetes Follow-On posts.  It has the SocketServer and SocketClient declarations, the removal of the Topic references and the Disburse queue, the addition of the connectMessage towards the beginning.  The Install is simplified to be like Component1 of the previous post to request the separate thread and the socket connections to transmit to and receive from ComOFP.

The MainEntry thread callback has the requested Index passed to the change to Threads that it doesn't really need but which is needed for the modification to Threads.  This thread only loops attempting to transmit the connectMessage until the transmit is successful indicating a connection with the ComOFP component.

It contains the ReceiveCallback method in place of the AnyMessage that previously treated received messages from the Disburse queue.  ReceiveCallback being what SocketServer invokes to pass the received message to the component.  This method just checks if the first 3 bytes of the message correspond to a valid response.  If so, the receivedChangePage is set to the string value of the bytes that follow the first three and responseReceived is set to true to notify TreatKey that it can return the response to the CDUForm.

TreatKey differs somewhat from the Kubernetes Follow-On posts.  It first has to obtain the string that represents the enumerated type in data, format the byte array to contain the new topic identifier in the first two bytes and the length of data in the third byte, and then format the string into a byte array in the following bytes.  Then it can attempt to transmit the byte array.  It the Transmit fails, a response indicating the failure is returned to CDUForm.  Otherwise, the method waits (as previously) for responseReceived to be indicated.  When this happens, it returns success along with the response text to CDUForm for display in the text box.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace SocketApplication
{
    class ComCDU
    {
        static private SocketServer socket1from2;
        static private SocketClient socket1to2;

        // This component contains only two topics.  To send the selected CDU
        // key to the remote OFP app and to receive the response.

        static private CDUForm cduForm = new CDUForm();

        static private byte[] connectMessage = new byte[] {3, 3, 0}; // topic and no data
        static public bool connected = false; // connected to remote app 2

        static private bool responseReceived = false;
        static private CDUForm.Result response = new CDUForm.Result();
        static private string receivedChangePage = "";

        static public void Install()
        {
           // Install the component into the Threads package.
           Threads.RegisterResult Result;
           Result = Threads.Install( "ComCDU",
                                     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 ComOFP of the Ada app
               socket1to2 = new SocketClient( "ComCDU",
                                              1,
                                              "ComOFP",
                                              2);
               if (!socket1to2.ValidPair())
               {
                   ConsoleOut.WriteLine(
                       "ERROR: SocketClient not valid for ComCDU 1, ComOFP 2 pair");
               }

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

           } // end if

        } // end Install

        // Entry point
        static void MainEntry(int Index)
        {
            while (true) // loop forever
            {
                if (connected)
                {
                }
                // wait for remote app (remoteAppId of 2) before wait for event -
                // inform user that key pushes can now be handled
                else
                {
                    if (!socket1to2.Transmit(1, 2, connectMessage))
                    {
                        Thread.Sleep(100); // wait and check for connected by
                    }                      //  attempted a new transmit
                    else
                    {
                        var result = MessageBox.Show("Ok to use keys", "TreatKey",
                                                     MessageBoxButtons.OK);
                        connected = true;
                    }
                }

            } // end forever loop

        } // end MainEntry

        // Notify of received message
        static void ReceiveCallback(byte[] Message)
        {
            ConsoleOut.Write("ComOFP received a message: ");
            ConsoleOut.Write(Message.Length.ToString());
            ConsoleOut.Write(" ");
            ConsoleOut.Write(Message[0].ToString());
            ConsoleOut.Write(" ");
            ConsoleOut.Write(Message[1].ToString());
            ConsoleOut.Write(" ");
            ConsoleOut.Write(Message[2].ToString());
            ConsoleOut.Write(" ");
            ConsoleOut.WriteLine(Message[3].ToString());
            if ((Message[2] > 0) && (Message[0] == 1) && (Message[1] == 2))
            { // valid message
                receivedChangePage = Encoding.ASCII.GetString
                                     (Message, 3, Message[2]);
                responseReceived = true;
            }
            else
            {
                ConsoleOut.WriteLine("ERROR: Invalid message received " + Message[0] +
                    " " + Message[1] + " " + Message[2] + " " + Message.Length);
            }
        }


        // Publish key push to be delivered to App2; wait for response
        public CDUForm.Result TreatKey(CDUForm.Key key)
        {
            responseReceived = false;

            string data = key.ToString(); // convert enum to string for message
            byte[] keyMessage = new byte[4 + data.Length];
            keyMessage[0] = 1; // CDU Topic of
            keyMessage[1] = 1; //   Keypush
            keyMessage[2] = (byte)data.Length;
            for (int i = 0; i < data.Length; i++)
            {
                keyMessage[3 + i] = (byte)data[i];
            }
            if (!socket1to2.Transmit(1, 2, keyMessage))
            {
                ConsoleOut.WriteLine("ERROR: Couldn't send Key Push message");
                // Return the response to CDUForm
                responseReceived = false; // set in advance of the next request
                response.success = false; // no response received
                response.text = "";       // no string received
                return response;
            }
            // Wait until response received
            while (true)
            {
                if (!responseReceived)
                {
                    Thread.Sleep(100); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response to CDUForm
            responseReceived = false; // set in advance of the next request
            response.success = true;  // response received
            response.text = receivedChangePage; // the string received
            return response;

        } // end TreatKey

    } // end ComCDU class
} // end namespace

Therefore, the amount of code is much less than before.

Modifications to SocketClient and SocketServer

The only differences in SocketClient from the previous post is
        public bool Transmit(int FromId, int ToId, byte[] Message)
        { // Message to be sent
            if (Message.Length == 0)
            {
                return false;
            }
at the beginning of Transmit to pass a byte array and check for no bytes passed; the use of ConsoleOut rather than Console, and directly sending the byte array rather than first needing to convert the string to a byte array.

For SocketServer the only differences are again the use of ConsoleOut rather than Console and directly using the bytes array to pass to the recdCallback rather than needing an intermediate step.

The other socket class (that is, SocketData) is unchanged.  The same for Threads and Delivery (again except for the use of ConsoleOut rather than Console).

Results

The Delivery.dat file is
1|ComCDU|192.1xx.y.zz|8001|8002|
2|ComOFP|192.1xx.y.zz|8002|8001|
where 1xx.y.zz is replaced by the IP address of the particular PC.

A screen shot of CDUForm following a click on the F-PLAN button is



That is, the same results as in the last "Pseudo Visual Compiler using Interface to Ada as the OFP" posts while using the simpler interface to Windows Sockets where the messages are sent directly from one component to another.