Using Named Pipes in Linux
Overview
A main feature of my Exploratory Project was the use of MS Pipes to communicate between user applications and then, when added, with the display application. WinSockets were also used but mostly MS Pipes since easier and I wanted to stay away from exposing the applications to the Internet; but used enough so that I could communicate with my apps when running on a different PC.
Therefore, to move the Exploratory Project to Linux I was going to need to use something to communicate between applications. Eventually I'll implement sockets. For first I wanted to implement named pipes; those created by mkfifo.
Using a google result that purported to show how to do it, I gave it a try. But it
didn't completely work for me. It created the pipe file descriptor pair that I shall
refer to as the pipe handle and could open the read-only handle in a secondary thread
of the test application but the application process would hang when I attempted to open the write-only handle via the terse instructions of the internet post. So I gave it up and went onto getting other op sys interfaces working.
In the last few of days I went back to it and found that if I just tried to open the
second handle as read-write rather than write-only that it worked. I could now send
messages from the main process thread to one of the created threads. Since in the
Exploratory Project the pipes are used between applications, I created a second test
application to read the named pipe rather than a different thread of the first test
application.
After that immediately worked I decided to do full-duplex that I would need for the
Exploratory Project. Thinking of what I had learned using Microsoft Pipes, I created
an Ada package to allow multiple applications to communicate with each other, one pair of applications per full-duplex pipe.
First Results
Since, at first, the full mechanism needed for the Exploratory Project had yet to be
implemented, I had the first application create the named pipe via mkfifo and then
opens it for read-write and write three short messages. In the meantime the second
application was started in a second terminal window. It spun on its read-only open
of the pipe until the open succeeded and then does a read of enough bytes to more than read everything written by the first test. This read all three messages written by the first test in one read request and returned the number of bytes read.
So all is well if the application that does the mkfifo is launched first. If the
second application is launched first, it waits for a valid open and then attempts the
read. But although the first application does its writes soon after the mkfifo (it does
create and then start its two created threads three times before writing to the pipe),
the second application never returns from its attempt to read the pipe. So something
yet to look into in that regard.
Full-Duplex Results
However, I then extended the named pipe Ada package into the Full Duplex Pipe package where two named pipes with related names are opened instead of one. This, of course, results in four file descriptors - two for each named pipe with one for read and one for write. The names have a common path with a common leading name string and a suffix that identifies the pair of applications such as "01to02" and "02to01" for applications one (test1) and two (test2).
In this implementation, both pipe pairs are symmetric. That is, both applications
attempt to create/make the pipe pairs and, when done, to unlink them from the Linux
operating system. Also, both fileClient writes
to its pipe and the Server reads that pipe. The Server writes the response to its pipe
and the Client application reads it.
So test1 names itself (i.e., 1) as the Client and 2, for test2, as the Server. This
results in a pipe name suffix of 01to02 for the Client and 02to01 for the Server. For
test2, it is the reverse so when it reads from its Server pipe it is reading from
01to02 and the same pipe that test1 is writing to as its Client pipe. (Two digits are
used since 99 applications should be way more than enough - in fact I have limited the
range from 1 to 15.)
The test1 application calls the open and then, in a loop, writes to its Client pipe
and reads from its Server pipe. The test2 application also calls the open and then,
in a loop, reads its Server pipe and responds on its Client pipe. In this test, after
three iterations both applications close the pair of pipes and exit. All worked
immediately.
full_duplex_pipe
A new Ada package was created to contain the named pipe (Linux fifo pipe) procedures for full-duplex communications. The code is as follows.
Note: More text output to the terminal window was included to check that the test
was running as expected. That output has been removed in the code below since not
really part of the use of full-duplex named pipes.
with Apps;
generic
-- ++
--| Overview:
--| Generic package to access, when instantiated, to either read or write
--| the particular, instantiated named pipe as the pipe client or the pipe
--| server. The use, by an application, of either the client instantiation
--| or the server instantiation and the use, by a different application, of
--| the reverse instantiation, will result in a full duplex pipe with the
--| pair of applications being able to communicate with each other when
--| both running under Linux on the same PC.
--|
--| At instantiation specify a number for each of a pair of applications;
--| the current application as the Client and the other application as the
--| Server. If the other application does the same it will also name itself
--| as the Client and the first application as the Server.
--|
--| When, for instance, application 1 needs to send a request to application
--| 2, it will have a named pipe called "NamedPipe01to02" as the Client pipe
--| and will use it to transmit to application 2 and application 2 will
--| have the same pipe as its Server pipe and use it to receive from
--| application 1. While application 2 will have a Client named pipe named
--| "NamedPipe02to01" to use transmit to application 1 and application 1 will
--| have the same pipe to use to receive from application 2.
-- --
--| Notes:
--| Parameters to supply when instantiate an instance of a topic.
Client_App
--| Number of the application instantiating this package
: Apps.Application_Number_Type;
Server_App
--| Number of the application to which to communicate
: Apps.Application_Number_Type;
package Full_Duplex_Pipe is
subtype Message_Length_Type
is Integer range 0..1000; -- assuming max length message is 1000
subtype Message_Data_Type
is String( 1..Message_Length_Type'last );
type Message_Type
is record
Count : Message_Length_Type;
--| Number of bytes in message
Data : Message_Data_Type;
--| Message data from 1 to Count
end record;
procedure Close;
--| Close any opened named pipe pair.
--| Notes:
--| Each named pipe of the pair has two file descriptors; one for
--| receive and one for transmit.
procedure Open
( Wait : in Boolean := True
--| True if to wait/delay for data to read
);
--| Open the named pipe pair.
--| Notes:
--| o Any particular instantiation of this package can only open for
--| one direction for each of the Client and the Server pipes.
--| o The Server pipe will be opened to Receive and the Client pipe
--| will be opened to Transmit.
--| o An open for transmit will also create the named pipe prior to
--| the open.
------------------------------------------------------------------------------
package Client is
--| Overview:
--| The pipe used by the currently running application to send
--| request messages to the other application of an application
--| pair. That is, Client-->Server. For instance, using pipe
--| "NamedPipe01to02" where the other application (that is, 2)
--| will receive using the same pipe.
procedure Transmit
( Message : in Message_Type
--| Request message to be written to the pipe
);
--| Write the Message to the opened named pipe
end Client;
------------------------------------------------------------------------------
package Server is
--| Overview:
--| The pipe used by the currently running application to receive
--| request messages from the other application of an application
--| pair. That is, Client-->Server. For instance, using pipe
--| "NamedPipe01to02" where the other application (that is, 1) will
--| transmit using this pipe and this application when it is 2 will
--| receive the requests of application 1.
procedure Receive
( Message : out Message_Type
--| Message read from the pipe
);
--| Read the contents of the opened named pipe
end Server;
end Full_Duplex_Pipe;
The package body:
with Console;
with Exec_Itf;
with Numeric_Conversion;
with Unchecked_Conversion;
package body Full_Duplex_Pipe is
type Pipe_Selector_Type
is ( Client_Pipe,
Server_Pipe );
type Pipe_Info_Type
is record
Name : Exec_Itf.Named_Pipe_Name_Type;
--| Pipe Name with its App suffix and with trailing ASCII.NUL added for C
Opened : Boolean;
--| Whether pipe has been opened
Handle
--| Handle (file descriptor) of named pipe
: Exec_Itf.Pipe_Handle;
end record;
type Pipe_Info_Array_Type
is array ( Pipe_Selector_Type ) of Pipe_Info_Type;
Pipe
--| Data about the pipe pair
: Pipe_Info_Array_Type;
------------------------------------------------------------------------------
package body Client is
procedure Transmit
( Message : in Message_Type
) is
Bytes_Written
--| Number of bytes actually written
: Integer;
begin -- Transmit
if Pipe(Client_Pipe).Opened then
Bytes_Written := Exec_Itf.Write_File
( File => Pipe(Client_Pipe).Handle,
Addr => Message.Data'address,
Len => Message.Count );
end if;
end Transmit;
end Client;
------------------------------------------------------------------------------
package body Server is
--| Notes:
--| Static data.
Msg_Buffer
--| Buffer to read into for response from Server app
--| Notes: Used to allow receive to read more data than caller wants.
: Message_Data_Type := ( others => ASCII.NUL );
procedure Receive
( Message : out Message_Type
) is
Bytes_Read
--| Number of bytes read
: Integer;
begin -- Receive
if Pipe(Server_Pipe).Opened then
Bytes_Read := Exec_Itf.Read_File( File => Pipe(Server_Pipe).Handle,
Addr => Msg_Buffer'address,
Num => Message_Length_Type'last );
Exec_Itf.Log_Error;
if Bytes_Read > 0 and then
Bytes_Read <= Message_Length_Type'last -- greater should never occur
then
Message.Data(1..Bytes_Read) := Msg_Buffer(1..Bytes_Read);
Message.Count := Bytes_Read;
end if;
end if;
end Receive;
end Server;
------------------------------------------------------------------------------
procedure Close is
-- ++
--| Logic_Flow:
--| Close both pipes of the pair if thought to be open.
--| Notes:
--| The other application may have closed the pipes first.
-- --
Result
: Integer;
Status
--| Function return status
: Boolean;
begin -- Close
if Pipe(Client_Pipe).Opened then
Status := Exec_Itf.Close_File( Handle => Pipe(Client_Pipe).Handle );
Exec_Itf.Log_Error;
-- Remove the FIFO pipe
Result := Exec_Itf.Unlink( Pipe(Client_Pipe).Name.Path'address );
Exec_Itf.Log_Error;
Pipe(Client_Pipe).Opened := False;
end if;
if Pipe(Server_Pipe).Opened then
Status := Exec_Itf.Close_File( Handle => Pipe(Server_Pipe).Handle );
Exec_Itf.Log_Error;
-- Remove the FIFO pipe
Result := Exec_Itf.Unlink( Pipe(Server_Pipe).Name.Path'address );
Exec_Itf.Log_Error;
Pipe(Server_Pipe).Opened := False;
end if;
end Close;
------------------------------------------------------------------------------
procedure Open
( Wait : in Boolean := True
) is
-- ++
--| Logic_Flow:
--| Open both the client and server pipes.
--| Notes:
--| o The client pipe is to be opened for transmit to the server and
--| the server pipe is to be opened for receive.
--| o There will be an attempt to create/make each pipe before the open
--| since don't know which application will be running first.
--| o Therefore each will be opened for read/write so it won't hang
--| the application.
--| o But only one of the pipe pair will be written to with the other
--| read by one application and the reverse by the other application.
--| Therefore, app 1 (for instance) will write to pipe 01to02 and app2
--| will read from pipe 01to02; that is, one will write to the write
--| fifo pipe and the other read from it. For full duplex the roles
--| are reversed so that the other fifo pipe of the pair is used.
-- --
Open_Mode
: Exec_Itf.Open_Mode_Type;
Result
: Integer;
Suffix1
: Numeric_Conversion.String_Type;
Suffix2
: Numeric_Conversion.String_Type;
function to_Int is new Unchecked_Conversion
( Source => Exec_Itf.Pipe_Handle,
Target => Integer );
use type Exec_Itf.Open_Mode_Type;
begin -- Open
Suffix1 := Numeric_Conversion.Integer_to_Ascii
( Number => Client_App,
Count => 2, -- two digits in 01..15
Retain => True, -- retain leading 0's
Signed => False );
Suffix2 := Numeric_Conversion.Integer_to_Ascii
( Number => Server_App,
Count => 2, -- two digits in 01..15
Retain => True,
Signed => False );
if Pipe(Client_Pipe).Opened then
Console.Write( "Named Client Pipe already opened" );
else
-- Form pipe name path with trailing 0 for C
Pipe(Client_Pipe).Name.Count := 41; -- Ada length
Pipe(Client_Pipe).Name.Path(1..35) :=
"/home/clayton/Source/Test/NamedPipe";
Pipe(Client_Pipe).Name.Path(36..37) := Suffix1.Value(1..2);
Pipe(Client_Pipe).Name.Path(38..39) := "to";
Pipe(Client_Pipe).Name.Path(40..41) := Suffix2.Value(1..2);
Pipe(Client_Pipe).Name.Path(42) := ASCII.NUL; -- for C
-- Create the named client pipe
Result := Exec_Itf.mkfifo( Path => Pipe(Client_Pipe).Name.Path'address,
Mode => 8#666# );
if Result < 0 then -- error
if Exec_Itf.Get_Error = 17 then
Result := 0; -- set successful for pipe already exists
end if;
end if;
-- Open the client pipe for transmit
if Result = 0 then -- no error code returned
Pipe(Client_Pipe).Handle :=
Exec_Itf.Open
( Path => Pipe(Client_Pipe).Name.Path'address,
Mode => Exec_Itf.O_RDWR );
Exec_Itf.Log_Error;
if to_Int(Pipe(Client_Pipe).Handle) > 0 then -- depending on a small number
Pipe(Client_Pipe).Opened := True;
end if;
end if;
end if; -- Pipe(Client_Pipe).Opened
if Pipe(Server_Pipe).Opened then
Console.Write( "Named Server Pipe already opened" );
else
-- Form pipe name path with trailing 0 for C, that is, retain the
-- Client Pipe name except reverse the application digits.
Pipe(Server_Pipe).Name := Pipe(Client_Pipe).Name;
Pipe(Server_Pipe).Name.Path(Pipe(Server_Pipe).Name.Count-5..
Pipe(Server_Pipe).Name.Count-4)
:= Suffix2.Value(1..2);
Pipe(Server_Pipe).Name.Path(Pipe(Server_Pipe).Name.Count-1..
Pipe(Server_Pipe).Name.Count)
:= Suffix1.Value(1..2);
Open_Mode := Exec_Itf.O_RDWR;
if not Wait then
Open_Mode := Open_Mode + Exec_Itf.O_NDELAY;
end if;
-- Open the named pipe to be read
Pipe(Server_Pipe).Opened := False;
while not Pipe(Server_Pipe).Opened loop
Pipe(Server_Pipe).Handle :=
Exec_Itf.Open
( Path => Pipe(Server_Pipe).Name.Path'address,
Mode => Open_Mode );
if to_Int(Pipe(Server_Pipe).Handle) > 0 then
Pipe(Server_Pipe).Opened := True;
exit; -- loop
else
delay 0.25; -- seconds for another app run
end if;
end loop;
end if; -- Pipe(Server_Pipe).Opened
end Open;
------------------------------------------------------------------------------
begin -- initialize at instantiation
Pipe := ( others => ( Name => ( Count => 0,
Path => ( others => ASCII.NUL ) ),
Opened => False,
Handle => Exec_Itf.Invalid_Pipe_Handle ) );
end Full_Duplex_Pipe;
test1.adb
The portion of the code involved with opening, writing and reading the full-duplex
named pipes and then closing them is illustrated below.
with Console;
with Exec_mC;
with Exec.Text_Log
with Full_Duplex_Pipe;
procedure Test1 is
Loop_Count
: Integer := 0;
package Named_Pipe
--| Instantiate the named pipe
is new Full_Duplex_Pipe
( Client_App => 1, -- this test1 application
Server_App => 2 ); -- the other test2 application
begin
Console.Initialize; -- output to the terminal and the log file
Exec.Text_Log.Open;
Console.Write( "Test1 started" );
Exec_mC.Initialize;
. . .
-- Open the named pipes for full-duplex communication.
Named_Pipe.Open; -- open client and server pipes to test2
. . .
while Loop_Count < 3 loop -- start each component 3 times
. . .
-- Write "Hi" to the test2 and wait for response.
declare
Msg : Named_Pipe.Message_Type;
begin
Console.Write("test1 to write Hi", Loop_Count );
Msg.Count := 2;
Msg.Data(1..2) := "Hi";
Named_Pipe.Client.Transmit( Message => Msg ); -- send to test2
Named_Pipe.Server.Receive( Message => Msg ); -- get response
Console.Write("test1 received", Msg.Count, Msg.Data(1..Msg.Count) );
end;
Loop_Count := Loop_Count + 1;
end loop;
. . .
-- Close named pipes.
Named_Pipe.Close;
. . .
end Test1;
test2.adb
with Console;
with Exec_mC;
with Exec.Text_Log;
with Full_Duplex_Pipe;
procedure Test2 is
Loop_Count
: Integer := 0;
package Named_Pipe
--| Instantiate the named pipe
is new Full_Duplex_Pipe
( Client_App => 2, -- this test2 application
Server_App => 1 ); -- the other test1 application
begin
Console.Initialize; -- output to the terminal and the log file
Exec.Text_Log.Open;
Console.Write( "Test1 started" );
Exec_mC.Initialize;
. . .
-- Open named pipes.
Named_Pipe.Open;
-- Receive 3 "requests" and respond to each.
while Loop_Count < 3 loop
declare
Msg : Named_Pipe.Message_Type;
begin
Named_Pipe.Server.Receive( Message => Msg );
Console.Write( "Message read from named pipe",
Msg.Count,
Msg.Data(1..Msg.Count) );
Msg.Count := 8;
Msg.Data(1..8) := "Response";
Named_Pipe.Client.Transmit( Message => Msg );
end;
Loop_Count := Loop_Count + 1;
end loop;
Console.Write( "test2 after mkpipe reads" );
-- Close named pipes
Named_Pipe.Close;
. . .
Exec.Text_Log.Close;
console.write("test2 to exit");
end Test2;
No comments:
Post a Comment