Newbie needs help with IO::Socket (maybe IO::Select)

B

Boerni

Hi all

I'm playing around with IO::Socket and try to build a small Client/Server
App.

The Serverpart is working as it's supposed to, but I have some problems with
the Clientpart.

So I have a couple of Questions:
1. How can a Client react to a Servermessage (Server prints to Socket), when
it's not expecting a Servermessage (For example another client connects to
the server and kicks the first one or the server is beeing shutdown). If I
use the IO::Select approach like in the Serverpart, the Client is blocked
and the User can't interact with it anymore.

2. Why does $socket->connected still return the peer-address, eventhough the
server is shutdown?

I'm sure there is a solution for this (probably by using IO::Select), but I
can't find any examples of Clientcode using this.

Here's what I have (simplified... the "real" Client is using Tk):

------ Star Serverpart -------
#!perl -w
use strict;
use IO::Socket;
use IO::Select;
use Data::Dumper;

our $socket;
my %users;
my $port = 9901;

#Socket erstellen
my $main_socket = IO::Socket::INET->new(LocalPort => $port,
Type => SOCK_STREAM,
Reuse => 1,
Listen => 1) or die "Kann kein
TCP-Server an Port $port sein: @!\n";

my $lesbar = new IO::Select();
$lesbar->add($main_socket);

#Pruefen ob ein Client connectet
while(1) {
my ($neu_lesbare) = IO::Select->select($lesbar, undef, undef, undef);

foreach $socket(@{$neu_lesbare}) {
if ($socket == $main_socket) {
#Neue Verbindung kommt rein...
my $neues_socket = $socket->accept();
$lesbar->add($neues_socket);
}
else {
# Hier drin passiert Zeug mit dem Client
my $buf = <$socket>;
if ($buf) {
chomp($buf);
my @args = split(',', $buf);
if ($args[0] eq "AUTH") {
auth($args[1], $args[2]);
}
else {
print "Nicht unterstuetzter Command\n";
}
}
else {
# print "Client hat Socket geschlossen\n";
$lesbar->remove($socket);
close ($socket);

foreach (keys %users) {
if ($socket eq $users{$_}{socket}) {
print localtime().": User [$_] disconnected\n";
delete $users{$_};
}
}
}
}
}
}

#---- Subroutinen ---#
sub write2socket {
my $socket = shift;
my $text = shift;
my $length = sprintf("%04d", length($text));
my $newstring = $length.$text;
print $socket "$newstring";
}

sub auth {
my $usr = shift;
my $pass = shift;

#Na wer ists denn...
my $iaddr = inet_ntoa($socket->peeraddr());
my $rem_host = gethostbyaddr($socket->peeraddr(), AF_INET);
my $rem_port = $socket->peerport();
print localtime().": Neue Verbindung von Host: $rem_host
[$iaddr:$rem_port]\n";

if (($usr eq "boerni") or ($usr eq "admin")) {
if ($users{$usr}) {
write2socket ($socket,"User $usr ist bereits verbunden
($users{$usr}{host}:$users{$usr}{port})");
}
else {
write2socket ($socket,"Verbindung mit User $usr akzeptiert");
$users{$usr}{socket} = $socket;
$users{$usr}{host} = $rem_host;
$users{$usr}{port} = $rem_port;
}
}
else {
write2socket ($socket, "Verbindung abgelehnt");
$lesbar->remove($socket);
$socket->shutdown(2);
close ($socket);
}

#print Dumper \%users;
#print Dumper \%sockets;

foreach (keys %users) {
my $handle = $users{$_}{socket};
write2socket($handle, "User $usr hat sich verbunden");
}
}
------ End Serverpart -------

------ Star Clientpart -------
#!perl -w
use strict;
use IO::Socket;
use IO::Select;

my $socket;
my $usr = "admin";
my $pw = "pass";
my $remote_host = 'localhost';
my $remote_port = 9901;

my $svrmsg;

my $lesbar = new IO::Select();
$lesbar->add($socket);

while (<>) {
chomp;
if ($_ eq "connect") {
connect2server();
}
elsif ($_ eq "man_move") {
man_move("up");
}
elsif ($_ eq "check") {
check_status();
}
else {
print "Nicht unterstuetzt\n";
}
}

#---- Subroutinen ----#
sub connect2server {
#---- Socket aufbauen ----#
$socket = IO::Socket::INET->new(PeerAddr => $remote_host,
PeerPort => $remote_port,
Proto => "tcp",
Type => SOCK_STREAM)
or die "Konnte Verbindung zu $remote_host:$remote_port nicht herstellen:
@!\n";

print $socket "AUTH,$usr,$pw\n";

$svrmsg = read_from_sock($socket);
print "FROM SERVER: [$svrmsg]\n";

}

sub man_move {
return if ! $socket;
my $direction = shift;
print $socket "man_move,$direction\n";

$svrmsg = read_from_sock($socket);
print "FROM SERVER: $svrmsg\n";
}

sub read_from_sock {
my $socket = shift;
my $length = 0;
my $data;

$socket->read($length, 4);
$socket->read($data,$length);

return $data;
}

sub check_status {
if (! $socket) {
print "CHECK: No socket\n";
}
elsif ($socket->connected()) {
print "CHECK: Connected with ".inet_ntoa($socket->peeraddr())."\n";
}
else {
print "CHECK: Not connected";
}
}
------ End Clientpart -------

Thanks a lot
 
X

xhoster

Boerni said:
Hi all

I'm playing around with IO::Socket and try to build a small Client/Server
App.

The Serverpart is working as it's supposed to, but I have some problems
with the Clientpart.

So I have a couple of Questions:
1. How can a Client react to a Servermessage (Server prints to Socket),
when it's not expecting a Servermessage (For example another client
connects to the server and kicks the first one or the server is beeing
shutdown).

Why would another client connecting to the server kick off the first one?
Wouldn't it be easier to fix this problem than to deal with the
consequences?

If the server has gone away, the client will realize this next time it
tries to communicate with it. While I guess it might be nice to know
immediately that the server has gone away, plenty of very good
client-server apps don't detect server failure until the next time it tries
to communicate. I'd need a very compelling reason not to follow this
paradigm for my own clients.
If I use the IO::Select approach like in the Serverpart, the
Client is blocked and the User can't interact with it anymore.

You need to add STDIN (or whatever you use to communicate with the User)
into the IO::Select, too. This means you probably have to use sysread,
to read from STDIN. said:
#Pruefen ob ein Client connectet
while(1) {
my ($neu_lesbare) = IO::Select->select($lesbar, undef, undef, undef);

foreach $socket(@{$neu_lesbare}) {
if ($socket == $main_socket) {
#Neue Verbindung kommt rein...
my $neues_socket = $socket->accept();
$lesbar->add($neues_socket);
}
else {
# Hier drin passiert Zeug mit dem Client
my $buf = <$socket>;

You are mixing buffered IO with select. This is rather dangerous. If
the client can ever print two (or more) lines into $socket in quick
succession, then this code might cause both of them to be read into the
perl internal buffer, but only one of them to be returned from there
into $buf. The next line just sits in the internal buffer, where it will
not trigger IO::Select and hence not get read. The client won't send any
more lines (which would trigger IO::Select) because it is waiting for a
response to the last line it sent, and the server will never respond to
that line because it doesn't know that it is there. Deadlock.

I think IO::Select (or some subclass thereof) should return readable for
any file handle that has data sitting in the perl internal buffer, in
addition to the handles that have data (or eof) in the system's buffers. I
haven't quite figured out how to implement that. All of those globs and
glob refs and PerlIO layers and tied handles and scalar masquerading as
handles are just too much for me.

Xho

--
-------------------- http://NewsReader.Com/ --------------------
The costs of publication of this article were defrayed in part by the
payment of page charges. This article must therefore be hereby marked
advertisement in accordance with 18 U.S.C. Section 1734 solely to indicate
this fact.
 
B

Boerni

Thank you very much for your answer!
Why would another client connecting to the server kick off the first one?
If the server has gone away, the client will realize this next time it
tries to communicate with it. While I guess it might be nice to know
immediately that the server has gone away, plenty of very good
client-server apps don't detect server failure until the next time it
tries
to communicate. I'd need a very compelling reason not to follow this
paradigm for my own clients.

I bought one of those USB-Missilelaunchers. So what I'm trying to do is,
writing a client/server app, so everyone in my team can use it. But since
I'm the one who bought it, I wan't to be able to kick anyone connected, when
I connect :)
But I guess it's ok, when they get the message the next time they try to
send any command to the launcher.
You need to add STDIN (or whatever you use to communicate with the User)
into the IO::Select, too. This means you probably have to use sysread,
rather than <>, to read from STDIN.

You are mixing buffered IO with select. This is rather dangerous. If
the client can ever print two (or more) lines into $socket in quick
succession, then this code might cause both of them to be read into the
perl internal buffer, but only one of them to be returned from there
into $buf. The next line just sits in the internal buffer, where it will
not trigger IO::Select and hence not get read. The client won't send any
more lines (which would trigger IO::Select) because it is waiting for a
response to the last line it sent, and the server will never respond to
that line because it doesn't know that it is there. Deadlock.

I think IO::Select (or some subclass thereof) should return readable for
any file handle that has data sitting in the perl internal buffer, in
addition to the handles that have data (or eof) in the system's buffers.
I
haven't quite figured out how to implement that. All of those globs and
glob refs and PerlIO layers and tied handles and scalar masquerading as
handles are just too much for me.

Xho

Thanks... I'm going to read up on this...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top