From: Dennis Hoppe on
Hi,

I've written a minimal example to access an ftp server (like FileZilla).
First, let's have a look at the code snippet:

-- START
with Ada.Text_IO;
with Ada.Streams;
with GNAT.Sockets; use GNAT.Sockets;
use type Ada.Streams.Stream_Element_Count;

procedure Test is
Client: Socket_Type;
Address: Sock_Addr_Type;
Channel: Stream_Access;
Data : Ada.Streams.Stream_Element_Array (1 .. 1);
Offset : Ada.Streams.Stream_Element_Count;

begin
Initialize;
Create_Socket(Client);
Address.Addr := Inet_Addr("127.0.0.1");
Address.Port := 21;
Connect_Socket (Client,Address);
Channel := Stream(Client);

loop -- reads in the welcome message
Ada.Streams.Read (Channel.all, Data(1..1), Offset);
exit when Offset = 0;
-- alternative: exit when Offset /= Data'Last
for I in 1 .. Offset loop
Ada.Text_IO.Put (Character'Val (Data (I)));
end loop;
end loop;
end Test;
-- END

The problem is, that Ada.Streams.Read is blocking, if the end of the
stream is reached. I found many examples, that outline, that the
variable Offset will be 0, if no further elements are on the stream.
But this seems not to be the case, unfortunately.

For a ftp server/client situation, each command is terminate by <CRLF>,
so I enhanced the exit condition to:

loop
Ada.Streams.Read (Channel.all, Data(1..2), Offset);
exit when (Character'Val (Data(1)) = ASCII.CR and Character'Val
(Data(2)) = ASCII.LF);
-- code omitted
end loop;

Of course, the Stream_Element_Array is enhanced to (1..2).

This approach works very well, but some ftp commands send a messages
over several lines. I do not know in advance, how many lines I should
read in. Subsequently, Ada.Streams.Read has to be called in a loop,
which will eventually block, again.

How can I query the stream, if new elements are ready to read?

Many thanks in advance,
Dennis
From: Dmitry A. Kazakov on
On Fri, 08 Aug 2008 15:24:43 +0200, Dennis Hoppe wrote:

> This approach works very well, but some ftp commands send a messages
> over several lines. I do not know in advance, how many lines I should
> read in. Subsequently, Ada.Streams.Read has to be called in a loop,
> which will eventually block, again.
>
> How can I query the stream, if new elements are ready to read?

You have to implement the application layer protocol above the stream.
Stream is a flow of stream elements, nothing more. If you have a
record-oriented layer above it, that segments the stream into records, then
you define a procedure to receive one record according to the definition
of. For instance, a sequence of stream elements until CR/LF:

procedure Read_Record
( Channel : in out Socket_Type;
Buffer : in out Stream_Element_Array;
Last : out Stream_Element_Offset
) is
-- Reads the stream until CR/LF filling Buffer. Last is the index of
-- of the last element of the packet. It is Buffer'First - 1 when
-- the record body is empty.
Index : Stream_Element_Offset;
Has_CR : Boolean := False;
begin
while Index in Buffer'Range loop
Read (Channel, Buffer (Index));
if Has_CR and then
Storage_Element'Pos (Buffer (Index) = Character'Pos (LF)
then
Last := Index - 2;
return;
else
Has_CR :=
Storage_Element'Pos (Buffer (Index) = Character'Pos (CR);
end if;
end loop;
raise Data_Overrun_Error; -- Too large packet
end Read_Record;

> How can I query the stream, if new elements are ready to read?

You need not, because record-oriented protocols let you know either the
record size in advance or else provide a way to determine the record end.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Adam Beneschan on
On Aug 8, 6:24 am, Dennis Hoppe <dennis.ho...(a)hoppinet.de> wrote:
> Hi,
>
> I've written a minimal example to access an ftp server (like FileZilla).
> First, let's have a look at the code snippet:
>
> -- START
> with Ada.Text_IO;
> with Ada.Streams;
> with GNAT.Sockets; use GNAT.Sockets;
> use type Ada.Streams.Stream_Element_Count;
>
> procedure Test is
> Client: Socket_Type;
> Address: Sock_Addr_Type;
> Channel: Stream_Access;
> Data : Ada.Streams.Stream_Element_Array (1 .. 1);
> Offset : Ada.Streams.Stream_Element_Count;
>
> begin
> Initialize;
> Create_Socket(Client);
> Address.Addr := Inet_Addr("127.0.0.1");
> Address.Port := 21;
> Connect_Socket (Client,Address);
> Channel := Stream(Client);
>
> loop -- reads in the welcome message
> Ada.Streams.Read (Channel.all, Data(1..1), Offset);
> exit when Offset = 0;
> -- alternative: exit when Offset /= Data'Last
> for I in 1 .. Offset loop
> Ada.Text_IO.Put (Character'Val (Data (I)));
> end loop;
> end loop;
> end Test;
> -- END
>
> The problem is, that Ada.Streams.Read is blocking, if the end of the
> stream is reached. I found many examples, that outline, that the
> variable Offset will be 0, if no further elements are on the stream.
> But this seems not to be the case, unfortunately.
>
> For a ftp server/client situation, each command is terminate by <CRLF>,
> so I enhanced the exit condition to:
>
> loop
> Ada.Streams.Read (Channel.all, Data(1..2), Offset);
> exit when (Character'Val (Data(1)) = ASCII.CR and Character'Val
> (Data(2)) = ASCII.LF);
> -- code omitted
> end loop;
>
> Of course, the Stream_Element_Array is enhanced to (1..2).
>
> This approach works very well, but some ftp commands send a messages
> over several lines. I do not know in advance, how many lines I should
> read in. Subsequently, Ada.Streams.Read has to be called in a loop,
> which will eventually block, again.
>
> How can I query the stream, if new elements are ready to read?

I'm not familiar with GNAT.Sockets, but looking at the spec it appears
that there are a couple routines that might help: Control_Socket which
lets you specify non-blocking I/O, and Check_Selector which I think
can be used to query whether data is available, if you give it a
Timeout of zero. Anyway, I haven't tried anything and I have no idea
whether it's appropriate for your problem, but it seems like it might
help. If not, my apologies.

-- Adam


From: Dennis Hoppe on
Hi Dmitri,

thank you for your reply, but it does not solve my problem. You just
encapsulate the challenge in a nicer way. The application stops
at runtime while invoking Ada.Streams.Read to often, again.

I corrected your snippet to be compatible with my test-framework.


with Ada.Text_IO;
with Ada.Streams; use Ada.Streams;
with GNAT.Sockets; use GNAT.Sockets;
use type Ada.Streams.Stream_Element_Count;
with System.Storage_Elements; use System.Storage_Elements;

procedure Test is
Client: Socket_Type;
Address: Sock_Addr_Type;
Channel: Stream_Access;
Data : Ada.Streams.Stream_Element_Array (1 .. 2);
Offset : Ada.Streams.Stream_Element_Count := 1;

procedure Read_Record (
Channel : in out Stream_Access;
Buffer : in out Stream_Element_Array;
Last : out Stream_Element_Offset) is
Index : Stream_Element_Offset := 1;
Has_CR : Boolean := False;
begin
while Index in Buffer'Range loop
Ada.Streams.Read (Channel.all, Buffer, Index);
if Has_CR and then
Stream_Element'Pos (Buffer (Index)) = Character'Pos (ASCII.LF)
then
Last := Index - 2;
return;
else
Has_CR :=
Stream_Element'Pos (Buffer (Index)) = Character'Pos (ASCII.CR);
end if;
end loop;
raise Constraint_Error; -- Too large packet
end Read_Record;

begin
Initialize;
Create_Socket(Client);
Address.Addr := Inet_Addr("127.0.0.1");
Address.Port := 21;
Connect_Socket (Client, Address);
Channel := Stream (Client);

Read_Record (Channel, Data, Offset);

for I in 1 .. Offset loop
Ada.Text_IO.Put (Character'Val (Data(I)));
end loop;
end Test;


The Ada specification mentions the following:

"The Read operation transfers stream elements from the specified stream
to fill the array Item. Elements are transferred until Item'Length
elements have been transferred, or until the end of the stream is
reached. If any elements are transferred, the index of the last stream
element transferred is returned in Last. Otherwise, Item'First - 1 is
returned in Last. Last is less than Item'Last only if the end of the
stream is reached."

(<http://www.adaic.org/standards/05aarm/html/AA-13-13-1.html>)


Actually, some invocation of Ada.Streams.Read (Channel.all, Buffer,
Index) should lead to

Index < Buffer'Last

if the end of the stream is reached. This is never the case, because
"read" is blocking, if not all elements in Buffer could be filled up.
If Buffer is Buffer(1..1), Index went never to 0, for example.

I am now able to read line per line sent by the ftp server, but if
I only once invoke my read method to often, the whole program hangs up
due to the blocking behavior of Ada.Stream.Read.


Best regards,
Dennis
From: Dennis Hoppe on
Hi Adam,


Adam Beneschan wrote:
> I'm not familiar with GNAT.Sockets, but looking at the spec it appears
> that there are a couple routines that might help: Control_Socket which
> lets you specify non-blocking I/O, and Check_Selector which I think
> can be used to query whether data is available, if you give it a
> Timeout of zero. Anyway, I haven't tried anything and I have no idea
> whether it's appropriate for your problem, but it seems like it might
> help. If not, my apologies.
>
> -- Adam


I tried to implement your idea:

with Ada.Text_IO;
with Ada.Streams; use Ada.Streams;
with GNAT.Sockets; use GNAT.Sockets;
use type Ada.Streams.Stream_Element_Count;

procedure Test is
Client: Socket_Type;
Address: Sock_Addr_Type;
Channel: Stream_Access;
Data : Ada.Streams.Stream_Element_Array (1 .. 256);
Offset : Ada.Streams.Stream_Element_Count := 1;
Request : Request_Type;

begin
Initialize;
Create_Socket(Client);
Address.Addr := Inet_Addr("127.0.0.1");
Address.Port := 21;
Connect_Socket (Client, Address);

Request := (Name => Non_Blocking_IO, Enabled => True);
Control_Socket (Client, Request); -- make socket I/O non-blocking
Channel := Stream (Client);

declare
Message : String (1 .. 256);
begin
String'Read (Channel, Message);
Ada.Text_IO.Put_Line (Message);
end;
end Test;


The code blocks now in line

Control_Socket (Client, Request); -- make socket I/O non-blocking

The application crashes with: Segmentation fault. *shrugging*

Maybe Create_Selector is the way to go...


Best regards,
Dennis