Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

A Simple Socket Server Using 'inetd'

by samizdat (Vicar)
on Apr 19, 2006 at 12:26 UTC ( [id://544341]=perltutorial: print w/replies, xml ) Need Help??

Using inetd to serve a socket

It is often the case that one needs to test a system before the hardware is available or on-line. In my case, I'm developing an interface which will talk to a socket on a complex piece of Fab Metrology gear called an Applied Materials NanoSEM, using a complex protocol called SEMI SECS-II/GEM HSMS. I needed a quick and dirty handler to act as the NanoSEM while I get the protocol parser working.

UNIX-like systems such as FreeBSD and Linux have a nifty feature called inetd, which comes to our rescue. inetd runs a program you specify whenever somebody else tries to connect to the socket you've chosen. By making a few simple configuration improvements, we can send our input to the specified socket, and inetd invokes our program, passing our input to it as STDIN. Our handler then processes it and spits out its response as STDOUT back to our socket. Cool, huh? What's even more cool is that if another process (or system) also tries to connect to the same socket, inetd will invoke another copy of our handler without bothering the first one.

Interested? Okay, here's the code...

The server handler:

#!/usr/bin/perl -w -T # A simple inetd socket server. use strict; my $old_fh = select(STDOUT); $| = 1; select($old_fh); while( my $line = <STDIN> ) { $line =~ s/\r?\n$//; if ($line =~ /endit/) { die "shutting down\n"; } # do your processing here! print " $line\n"; }
This little program will process anything that comes in on the specified port, clearing carriage returns and line feeds, and (in this simple case) spitting it back with two spaces in front and a newline at the end. An input containing "endit" causes the handler to exit, and, thanks to the flush sequence at the top, output is immediate. Make your program executable:
# chmod +x /usr/local/bin/

The configuration:

Okay, now, here's the setup. In /etc (you need to be superuser), edit /etc/services to add your port number to the known services list, making up a unique name for the service. My port is 6100, its service is 'secshsms', and I've asked it to handle both stream (tcp) and datagram (udp) packets, although this example will only deal with tcp.
secshsms 6100/tcp # DSW Handler for SECS-II/GEM HSMS traffic secshsms 6100/udp # DSW
Next, edit /etc/inetd.conf to attach your program to that service:
# DSW add-on for SECS-II over HSMS secshsms stream tcp nowait nobody /usr/local/bin/sinet. +pl secshsms
Okay, find inetd and restart it.
# ps -ax | grep inetd 563 ?? Is 0:00.01 /usr/sbin/inetd -wW -C 60 # kill -1 563
From now on, any process that attempts to talk to my machine's port 6100 gets its output routed to my handler

With that in hand, here's a sample client, adapted from Perl Cookbook recipe 17.10. It can be installed on any machine within a routable network (i.e., no firewall) and it will talk to my handler.

A simple client:

#!/usr/bin/perl # a simple automated client use warnings; use strict; use IO::Socket; my ( $confstr, $host, $port, $kidpid, $handle, $line, @say ); # the config file contains host name (or IP addr) and port number, wit +h a space between # examples: localhost 6100 # 6100 # 123.456.789.1 6100 open( CONF, "<./hsms.conf") or die "conf file: $!\n"; $confstr = <CONF>; close( CONF ) or die "closing conf file: $!\n"; chomp $confstr; ( $host, $port ) = split( /\s+/, $confstr ); # This is our demo array of outputs sent to the handler @say = ( 'You are getting sleepy...', '... very sleepy.', 'Your eyes are getting very heavy!', "... it's so hard to hold them open.", "You're so very sleepy now.", 'You just want to go to sleep.', 'Sleep feels so good!', "You're asleep. Sleep!", "You've earned it, just relax and sleep!", "... Sleep!", " Sleep!", " Sleep!", '', '... zzz... zzz... ...zzz ...', 'endit'); # This creates our client socket $handle = IO::Socket::INET->new( Proto => "tcp", PeerAddr => $host, PeerPort => $port ) or die "can't connect to port '$port' on host '$host': $!\n"; # make sure it turns around inputs immediately $handle->autoflush(1); # announce our connection print STDERR "[connected to $host:$port]\n"; # fork a child to handle sending our data to the socket die "can't fork: $!\n" unless defined($kidpid = fork()); if ( $kidpid ) { # The parent handles data coming from the socket server to us while ( defined( $line = <$handle> ) ) { print STDOUT $line; } # ... until the connection is broken kill( "TERM" => $kidpid ); } else { # The child process receives data for us foreach my $item ( @say ) { print $handle $item . "\r\n"; sleep 1; } } exit;
Working from this skeleton, a more elaborate language can be developed. The program on each end can be made to parse and respond to commands from the other.

UPDATE 1: changed server user to nobody, thank you idsfa.

Don Wilde
"There's more than one level to any answer."

Replies are listed 'Best First'.
Re: A Simple Socket Server Using 'inetd'
by Anonymous Monk on Aug 17, 2007 at 06:23 UTC
    Fantastic, very nice approach. Just one question. How can you find out the client's ip address / port using this approach?
      You can't, directly, of course. With netstat and arp you could figure it out, although the connection is transient.

      Don Wilde
      "There's more than one level to any answer."
        Hey guys, Is it possible to get a sample of your SECSII client? I'd like to make a SECSII communication over TCP but seems to be difficult to me... Thanks a lot!
        I am trying it out on Windows, set up inetd via Cygwin and start it as Windows service. It always prompt "Bad file descriptor". at: my $line = <STDIN> When I connect via "telnet localhost <port>". Would you please shed some lights on it? Thanks,
      You can, though I'm not sure if there's any quirks to it (including how it behaves when not run from inetd)
      my $hersockaddr = getpeername(STDOUT); my ($port, $iaddr) = sockaddr_in($hersockaddr); my $ip = inet_ntoa($iaddr);
      That gives you $port and $ip nicely ready to use.
Re: A Simple Socket Server Using 'inetd'
by Anonymous Monk on Aug 05, 2008 at 20:01 UTC
    Based on the sample provided in this page, I have written a server handler and a client - the purpose is which is that when the client opens a socket connection on a specific port and sends data, the server receives it, parses it and logs it in a log file. The inetd and the services files have been configured and I know that it is done correctly because when my server program has compilation errors, the corresponding error message is displayed on the console where I run my client program. But when all the errors have been fixed, the job that the server program is supposed to be doing is not getting done - log file is not getting updated and the response it spits out is not getting communicated to the client. Can someone please review the code and let me know what is it that I am doing wrong? Here are the codes I have for the server program and the client program:
    The Server Program:
    #!/usr/local/bin/perl5.00506 -Tw # add the -Tw for testing use strict; # use Time::Local; use CGI; use CGI::Carp qw(fatalsToBrowser); # use DB_File; # clean up path for security $ENV{PATH} = "/usr/local/bin"; $ENV{ENV} = ""; use Time::Local; my($day, $month, $year) = (localtime)[3,4,5]; my ($val, $send_ph_num, $mail, $var, $val_untaint, $tmp, $pin, $pagese +rvice, $backupmail); my %db; my (@output_msg_array, $msgto, $msg, $phonenumber, $sendphone, $sendba +ckup, $pager_email); my ($input_data); my $old_fh = select(STDOUT); $| = 1; select($old_fh); $input_data = <STDIN>; chomp $input_data; # remove the first character from the input string $input_data = substr($input_data, 2, (length($input_data)-1)); # Parse the input data my @values = split(' ',$input_data,5); my $cdsid = $values[0]; $msgto = $values[1]; $sendbackup = $values[2]; $msg = substr($values[4],1,(length($values[4])-1)); $phonenumber = ""; $sendphone = "N"; #print "Parsed Text\n"; #print "Parsed Text as Sender $cdsid To $msgto Message $msg\n"; open (LOGFILE, ">> log/batch_call.log"); printf LOGFILE ("%04d-%02d-%02d -- %s, %s, %s\n", $year+1900, $month+1 +, $day, $cdsid, $msgto, $msg); close(LOGFILE); print "Logged message successfully\n"; exit;
    The Client program:
    #!/usr/local/bin/perl/perl -w use IO::Socket; my ($remote_host, $remote_port, $socket, $answer); $remote_host = ""; $remote_port = "5060"; print "Connecting to remote host....\n"; $socket = IO::Socket::INET->new(PeerAddr => $remote_host, PeerPort => $remote_po +rt, Proto => "tcp", Type => SOCK_STREAM) or die "Couldn't connect to $remote_host at $remote_po +rt: $@\n"; $socket->autoflush(1); print $socket "\x01jchakrab jchakrab N N Testing Text Pager thru Socke +t...\x10"; $answer = <$socket>; print ("$answer\n"); close($socket); exit;
      Did you get my sample to execute successfully? I'd suspect that your inetd is not configured correctly.

      Don Wilde
      "There's more than one level to any answer."
        No, I did not execute the sample you have provided. There is no problem with the inetd config - because the server program is doing what it is supposed to do, but only thing is the client program not receiving the status back - looks like the socket is communicating only one way.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perltutorial [id://544341]
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (2)
As of 2024-07-21 02:18 GMT
Find Nodes?
    Voting Booth?

    No recent polls found

    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.