From: anon@anon.org (anon)
Subject: Re: Structure of the multitasking server
Date: Fri, 19 Sep 2008 23:01:11 GMT
Date: 2008-09-19T23:01:11+00:00 [thread overview]
Message-ID: <XaWAk.44302$Mh5.8741@bgtnsc04-news.ops.worldnet.att.net> (raw)
In-Reply-To: 8b4d1170-22e6-40d3-8ed1-096dc0163491@m36g2000hse.googlegroups.com
Since you are using Channels, I am assuming that your talking about TCP/IP
servers. In this case you should look into using the Check_Selector with
the use of Signalling_Fds and Socket_Sets instead of using arrays.
Now the way GNAT has written the Selector function and the way the C_Select
is written the maximum number of server per Check_Selector is 27. That is,
each C_Select function can only monitor 32 sockets, but
GNAT.Sockets.Create_Selector uses two socket and the TCP/IP sub-system
uses or predefines 3, so you are left with 27 user define servers.
Found this clent/server on the net. The following client/server shows how to
use the Check_Selector routine. They were both were written in all caps and
use Ada-95 spec so, you may need to adjust the Signalling_Fds routine
calls for Ada-2005 (GNAT-2008), but they do compile and excute under
GNAT GPL 2007 using Linux.
Note: 1) I alter for spacing and cap format. To make them more readable.
2) The procedure "Set_Socket_Option" may need to be commented
out. Some TCP/IP system do not like GNAT version of that
routine.
3) As for Windows, I did not test!
--------------------------------------------------------------
--
-- Pool_Server
--
with GNAT.Sockets ;
use GNAT.Sockets ;
with Ada.Text_IO ;
procedure Pool_Server is
MaxTasks : constant Positive := 5 ; -- buffer size
type Index is mod MaxTasks ;
function Rev ( S : String ) return String is
Res : String ( S'Range ) ;
J : Integer := S'First ;
begin
for I in reverse S'Range loop
Res ( J ) := S ( I ) ;
J := J + 1 ;
end loop ;
return Res ;
end Rev ;
protected Aborted is
procedure Set ;
function Check return Boolean ;
private
Done : Boolean := False ;
end Aborted ;
protected body Aborted is
procedure Set is
begin
Done := True ;
end Set ;
function Check return Boolean is
begin
return Done ;
end Check ;
end Aborted ;
type Echo ;
type Echo_Access is access Echo ;
task type Echo is
entry Start ( N_Sock : IN Socket_Type ;
Self : IN Echo_Access ) ;
entry ReStart ( N_Sock : IN Socket_Type ) ;
end Echo ;
type Task_Array is array ( Index ) of Echo_Access ;
protected Buffer is
entry Deposit ( X : in Echo_Access ) ;
entry Extract ( X : out Echo_Access ) ;
function NumWaiting return Natural ;
private
Buf : Task_Array ;
I, J : Index := 0 ;
Count : Natural range 0 .. MaxTasks := 0 ;
end Buffer ;
task body Echo is
Sock : Socket_Type ;
S : Stream_Access ;
Me : Echo_Access ;
Input_Selector : Selector_Type ;
Input_Set : Socket_Set_Type ;
WSet : Socket_Set_Type ;
Input_Status : Selector_Status ;
begin
--set up selector
Create_Selector ( Input_Selector ) ;
--Initialise socket sets
--WSet is always empty as we are not interested in output events
-- RSet only ever contains one socket namely Sock
Empty ( Input_Set ) ;
Empty ( WSet ) ;
ACCEPT Start ( N_Sock : IN Socket_Type ;
Self : IN Echo_Access ) DO
Sock := N_Sock ;
Me := Self ;
end Start ;
loop
begin -- block for exception handling
S := Stream ( Sock ) ; -- set up stream on socket
Boolean'Write ( S, True ) ; -- acknowledge connection
loop
-- check for input on Sock socket
Set ( Input_Set, Sock ) ;
-- time-out on check if no input within 0.5 second
Check_Selector ( Input_Selector,
Input_Set,
WSet,
Input_Status,
0.5 ) ;
if Input_Status = Completed then
-- we have input, so process it
declare
Str : String := String'Input ( S ) ;
begin
exit when Str = "quit" ;
String'Output ( S, Rev ( Str ) ) ;
end ;
end if ;
if Aborted.Check then
String'Output ( S, "Server aborted" ) ;
exit ;
end if ;
end loop ;
Ada.Text_IO.New_Line ;
Ada.Text_IO.Put_Line ( "Slave Closing Connection" ) ;
ShutDown_Socket ( Sock, Shut_Read_Write ) ;
Buffer.Deposit ( Me ) ;
exception
-- The mostly likely exception is if client quits unexpectedly
-- close the socket and deposit ourselves in the buffer
when others =>
Ada.Text_IO.New_Line ;
Ada.Text_IO.Put_Line ( "Connection closed unexpectedly" ) ;
Close_Socket ( Sock ) ;
Buffer.Deposit ( Me ) ;
end ;
select
ACCEPT ReStart ( N_Sock : IN Socket_Type ) DO
Sock := N_Sock ;
end ReStart ;
or
-- terminate if all slaves are queued here and
-- if the main server task has finished
terminate ;
end select ;
end loop ;
end Echo ;
protected body Buffer is
entry Deposit ( X : IN Echo_Access ) when Count < MaxTasks is
begin
Buf ( I ) := X ;
I := I + 1 ;
Count := Count + 1 ;
end Deposit ;
entry Extract ( X : OUT Echo_Access ) when Count > 0 is
begin
X := Buf ( J ) ;
J := J + 1 ;
Count := Count - 1 ;
end Extract ;
function NumWaiting return Natural is
begin
return Count ;
end NumWaiting ;
end Buffer ;
Server : Socket_Type ;
New_Sock : Socket_Type ;
Slave : Echo_Access ;
Addr : Sock_Addr_Type ( Family_Inet ) ;
Peer_Addr : Sock_Addr_Type ( Family_Inet ) ;
Avail : Boolean := False ;
Ch : Character ;
TotalTasks : Natural := 0 ;
Accept_Selector : Selector_Type ;
Accept_Set : Socket_Set_Type ;
WSet : Socket_Set_Type ;
Accept_Status : Selector_Status ;
begin -- main server task
Ada.Text_IO.Put_Line ( "WARNING server loops for ever." ) ;
Ada.Text_IO.Put ( "Press A to terminate server and all " ) ;
Ada.Text_IO.Put_Line ( "tasks immediately or press Q to ") ;
Ada.Text_IO.Put ( "accept no further connections and " ) ;
Ada.Text_IO.Put ( "terminate gracefully when all clients " ) ;
Ada.Text_IO.Put ( "are fully when all clients are through." ) ;
Ada.Text_IO.New_Line ;
Initialize ;
Create_Socket ( Server) ;
Addr := ( Family_Inet,
Addresses ( Get_Host_By_Name ( Host_Name ), 1 ),
50000 ) ;
-- allow server address to be reused for multiple connections
Set_Socket_Option ( Server,
Socket_Level,
( Reuse_Address, True ) ) ;
Bind_Socket ( Server, Addr ) ;
Listen_Socket ( Server, 4 ) ;
-- set up selector
Create_Selector ( Accept_Selector ) ;
-- Initialise socket sets
-- WSet is always empty as we are not interested in output
-- events Accept_Set only ever contains one socket namely
-- Server
Empty ( Accept_Set ) ;
Empty ( WSet ) ;
loop
Ada.Text_IO.Get_Immediate ( Ch, Avail ) ;
if Avail and then
( Ch = 'q' or Ch = 'Q' or Ch = 'a' or Ch = 'A' ) then
exit ;
end if ;
-- check for input (connection requests) on Server socket
Set ( Accept_Set, Server ) ;
-- time-out on check if no request within 1 second
Check_Selector ( Accept_Selector,
Accept_Set,
WSet,
Accept_Status,
1.0 ) ;
if Accept_Status = Completed then
-- must be an event on Server socket as it is the only
-- one that we are checking.
-- Hence the Accept_Socket call should not block.
Accept_Socket ( Server, New_Sock, Peer_Addr ) ;
Ada.Text_IO.New_Line ;
Ada.Text_IO.Put_Line
( "Connection accepted -- allocating slave" ) ;
if Buffer.NumWaiting = 0 and TotalTasks < MaxTasks then
Slave := NEW Echo ; -- start new task
TotalTasks := TotalTasks + 1 ;
Ada.Text_IO.Put_Line ( "New slave task started" ) ;
-- call entry Start to activate task
Slave.Start ( New_Sock, Slave ) ;
else
Ada.Text_IO.Put_Line ( "Waiting for an idle slave task" ) ;
Buffer.Extract ( Slave ) ;
-- call entry Start to re-activate task
Slave.ReStart ( New_Sock ) ;
Ada.Text_IO.Put_Line ( " Idle slave task reactivated" ) ;
end if ;
end if ;
end loop ;
if Ch = 'a' or Ch = 'A' then
-- signal slave tasks to terminate
Aborted.Set ;
end if ;
-- tidy up
Close_Selector ( Accept_Selector ) ;
Empty ( Accept_Set ) ;
Close_Socket ( Server ) ;
Ada.Text_IO.New_Line ;
Ada.Text_IO.Put_Line ( "Main server task exiting ..." ) ;
Finalize ;
end Pool_Server ;
--------------------------------------------------------------
--
-- Pool_Client
--
with Gnat.Sockets ;
use Gnat.Sockets ;
with Ada.Command_Line ;
use Ada.Command_Line ;
with Ada.Text_IO ;
use Ada.Text_IO ;
procedure Pool_Client is
Sock : Socket_Type ;
S : Stream_Access ;
Addr : Sock_Addr_Type ( Family_Inet ) ;
Msg : String ( 1 .. 80 ) ;
Last : Natural ;
B : Boolean ;
Read_Selector : Selector_Type ;
Read_Set, WSet : Socket_Set_Type ;
Read_Status : Selector_Status ;
begin
Initialize ;
Create_Socket ( Sock ) ;
Addr := ( Family_Inet,
Addresses ( Get_Host_By_Name ( Argument ( 1 ) ), 1 ),
50000 ) ;
Create_Selector ( Read_Selector ) ;
Empty ( Read_Set ) ;
Empty ( WSet ) ;
Connect_Socket ( Sock, Addr ) ;
S := Stream ( Sock ) ;
Boolean'Read ( S, B ) ;
-- wait for connection to be accepted
loop
Set ( Read_Set, Sock ) ;
-- check for input on socket (server may be aborting)
-- time-out immediately if no input pending
-- We seem to need a small delay here (using zero seems to block
-- forever)
-- Is this a GNAT bug or AB misreading Check_Selector docs?
Check_Selector ( Read_Selector,
Read_Set,
WSet,
Read_Status,
0.005 ) ;
if Read_Status = Expired then
Ada.Text_IO.Put ( "Message> " ) ; -- prompt user for message
Ada.Text_IO.Get_Line ( Msg, Last ) ;
-- send message to socket unless server is aborting
String'Output ( S, Msg ( 1 .. Last ) ) ;
exit when Msg ( 1 .. Last ) = "quit" ;
end if ;
declare
-- receive message
Str : String := String'Input ( S ) ;
begin Ada.Text_IO.Put_Line ( Str ) ;
exit when Str = "Server aborted" ;
end ;
end loop ;
Ada.Text_IO.Put_Line ( "Client quitting ..." ) ;
ShutDown_Socket ( Sock ) ;
Close_Selector ( Read_Selector ) ;
Finalize ;
exception
when others =>
Ada.Text_IO.Put_Line ("Exception: Client quitting ..." ) ;
Close_Socket ( Sock ) ;
Close_Selector( Read_Selector ) ;
Finalize ;
end Pool_Client ;
In <8b4d1170-22e6-40d3-8ed1-096dc0163491@m36g2000hse.googlegroups.com>, Maciej Sobczak <see.my.homepage@gmail.com> writes:
>Hi all,
>
>Imagine a server with fixed number of worker tasks. There is no queue
>of jobs and jobs are immediately passed to one of the tasks that is
>currently idle. There is a separate task (or just the main one) that
>provides jobs for worker tasks.
>
>I am concerned with the proper structure of objects - I mean in the
>sense of recommended Ada practice.
>Obviously there is a need for some shared resource where the
>requesting task will put the job and from where the worker task will
>pick it up.
>
>This is more or less what I came up with, where the "channel" is a
>single processing pipeline:
>
> type Worker_State is (Idle, Ready, Working);
>
> protected type Channel_State is
> procedure Post (J : in Job_Type);
> entry Get_Job (J : out Job_Type);
> function Busy return Boolean;
> private
> State : Worker_State := Idle;
> Job_To_Do : Job_Type;
> end Channel_State;
>
> protected body Channel_State is
>
> procedure Post (J : in Job_Type) is
> begin
> if State /= Idle then
> raise Program_Error;
> end if;
>
> Job_To_Do := J;
> State := Ready;
> end Post;
>
> entry Get_Job (J : out Job_Type) when State = Ready is
> begin
> J := Job_To_Do;
> State := Working;
> end Get_Job;
>
> function Busy return Boolean is
> begin
> return State /= Idle;
> end Busy;
>
> end Channel_State;
>
> type Channel;
> task type Worker_Task (Ch : access Channel);
>
> type Channel is record
> State : Channel_State;
> Worker : Worker_Task (Channel'Access);
> end record;
>
> task body Worker_Task is
> Job : Job_Type;
> begin
> loop
> Ch.all.Get_Job (Job);
>
> -- do the job ...
>
> end loop;
> end Worker_Task;
>
> Max_Channels : constant := 5;
>
> Channels : array (1 .. Max_Channels) of Channel;
>
>My question is whether this is what a seasoned Ada programmer would
>do.
>Initially I tried to have two separate arrays, one for jobs and one
>for worker tasks, but I found it difficult to link workers with their
>respective jobs. After bundling them together in a single record that
>is referenced from the task it worked and I actually find it
>structured better.
>
>The main task after constructing a job object finds some channel where
>the worker task is not busy and posts the job to its shared state
>component:
>
> loop
> Job := ...
>
> Found_Worker := False;
> for I in Channels'Range loop
> if not Channels (I).State.Busy then
> Channels (I).State.Post (Job);
> Found_Worker := True;
> exit;
> end if;
> end loop;
>
> if not Found_Worker then
> -- all pipelines are busy,
> -- but the overflow handling is not shown...
> end if;
> end loop;
>
>All this works fine, but my question considers the choice of language
>constructs and idioms.
>
>--
>Maciej Sobczak * www.msobczak.com * www.inspirel.com
>
>Database Access Library for Ada: www.inspirel.com/soci-ada
next prev parent reply other threads:[~2008-09-19 23:01 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-09-19 12:21 Structure of the multitasking server Maciej Sobczak
2008-09-19 13:34 ` Jean-Pierre Rosen
2008-09-19 17:02 ` Dmitry A. Kazakov
2008-09-21 17:30 ` Maciej Sobczak
2008-09-21 19:24 ` Dmitry A. Kazakov
2008-09-21 21:27 ` Maciej Sobczak
2008-09-22 8:12 ` Dmitry A. Kazakov
2008-09-22 12:47 ` Maciej Sobczak
2008-09-22 14:11 ` Dmitry A. Kazakov
2008-09-23 8:07 ` Maciej Sobczak
2008-09-23 9:37 ` Dmitry A. Kazakov
2008-09-23 10:47 ` Jean-Pierre Rosen
2008-09-21 17:23 ` Maciej Sobczak
2008-09-22 8:23 ` Jean-Pierre Rosen
2015-03-12 16:07 ` gautier_niouzes
2015-03-12 21:38 ` Jacob Sparre Andersen
2015-03-12 22:39 ` gautier_niouzes
2015-03-13 8:15 ` Dmitry A. Kazakov
2015-03-13 20:16 ` gautier_niouzes
2015-03-13 20:47 ` Dmitry A. Kazakov
2015-03-15 7:43 ` gautier_niouzes
2015-03-15 8:35 ` Simon Wright
2015-03-15 8:52 ` J-P. Rosen
2015-03-15 9:21 ` Jacob Sparre Andersen
2015-03-15 16:04 ` Brad Moore
2015-03-13 23:04 ` Randy Brukardt
2015-03-14 8:22 ` Simon Wright
2008-09-19 23:01 ` anon [this message]
2008-09-21 17:37 ` Maciej Sobczak
2008-09-22 2:32 ` anon
2008-09-22 13:05 ` Maciej Sobczak
2008-09-23 9:25 ` anon
replies disabled
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox