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.
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.
No comments:
Post a Comment