;#
;# Copyright (c) 1995-1998
;# Ikuo Nakagawa. All rights reserved.
;#
;# Redistribution and use in source and binary forms, with or without
;# modification, are permitted provided that the following conditions
;# are met:
;#
;# 1. Redistributions of source code must retain the above copyright
;# notice unmodified, this list of conditions, and the following
;# disclaimer.
;# 2. Redistributions in binary form must reproduce the above copyright
;# notice, this list of conditions and the following disclaimer in the
;# documentation and/or other materials provided with the distribution.
;#
;# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
;# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
;# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
;# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
;# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
;# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
;# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
;# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
;# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;#
;# $Id: TCP.pm,v 1.20 1998/09/19 03:58:35 ikuo Exp $
;#
;# Description:
;# TCP.pm - TCP Class definitions
;# - Socket based operations.
;# - Multiple addresses for a single server are supported.
;#
package Fan::TCP;
use strict;
use vars qw($VERSION $LOG $seq_id $sent_octets $recv_octets);
use Carp;
use Socket;
use Socket6;
use AutoLoader 'AUTOLOAD';
$VERSION = '0.03';
;#
BEGIN {
$LOG = 5;
$seq_id = 0;
$sent_octets = 0;
$recv_octets = 0;
}
;# status report...
END {
&status_report if $LOG >= 6;
}
;# A special marker for AutoSplit.
1;
__END__
;# Show statistics report...
sub status_report ($) {
my $this = shift;
my $s = $sent_octets + 0;
my $r = $recv_octets + 0;
1 while $s =~ s/(\d+)(\d\d\d)/$1,$2/;
1 while $r =~ s/(\d+)(\d\d\d)/$1,$2/;
my $len = 0;
$len = length($s) if $len < length($s);
$len = length($r) if $len < length($r);
warn("TCP status summary report:\n");
warn(" total $seq_id objects created\n");
warn(" sent". '.' x ($len + 3 - length($s)) . "$s octets\n");
warn(" recv". '.' x ($len + 3 - length($r)) . "$r octets\n");
}
;# Constants - a very simple routine
sub FATAL () {
0x5555; # magic number...
}
;# Destroy a TCP object.
sub DESTROY ($) {
my $self = shift;
# close and leave socket information
$self->close;
# count up...
$sent_octets += $self->{tcp_sent_octets};
$recv_octets += $self->{tcp_recv_octets};
carp("TCP DESTROYING $self") if $LOG > 5;
}
;# Creat a new TCP object.
;#
;# tcp_bindaddr => undef
;# tcp_port => undef
;# tcp_host => undef
;# tcp_timeout => 120
;#
sub new ($%) {
my $this = shift;
my $class = ref($this) || $this;
my %params = @_;
my $self = \%params;
# setup default values...
$self->{tcp_timeout} = 120 if !defined($self->{tcp_timeout});
$self->{tcp_state} = 1; # initial status is OK
$self->{tcp_error} = ''; # error message will be stored.
$self->{tcp_sent_octets} = 0;
$self->{tcp_recv_octets} = 0;
# bless me.
bless $self, $class or return undef;
# count up sequence #
$seq_id++;
# log message
carp("TCP CREATING $self") if $LOG > 5;
# return myself.
return $self;
}
;# Show statistics report...
sub stats ($) {
my $self = shift;
my $s = $self->{tcp_sent_octets} + 0;
my $r = $self->{tcp_recv_octets} + 0;
1 while $s =~ s/(\d+)(\d\d\d)/$1,$2/;
1 while $r =~ s/(\d+)(\d\d\d)/$1,$2/;
my $len = 0;
$len = length($s) if $len < length($s);
$len = length($r) if $len < length($r);
warn("$self status report:\n");
warn(" sent". '.' x ($len + 3 - length($s)) . "$s octets\n");
warn(" recv". '.' x ($len + 3 - length($r)) . "$r octets\n");
}
;# $tcp->clearerror trys clear error flag, or
;# $tcp->clearerror(1) force to clear error flag.
;#
sub clearerror ($;$) {
my $self = shift;
my $force = @_ && shift(@_) ? 1 : 0;
return undef if !$force && !defined($self->{tcp_state});
$self->{tcp_state} = 1; # status is o.k.
$self->{tcp_error} = ''; # no error message
1;
}
;# TCP object's error message at the last operation.
;#
sub error ($;$$) {
my $self = shift;
if (@_) {
$self->{tcp_error} = shift;
$self->{tcp_state} = @_ && (shift == &FATAL) ? undef : 0;
}
$self->{tcp_error};
}
;# Returns 1 if this TCP object has fatal error status.
sub fatal ($) {
my $self = shift;
if (@_) {
my $force = shift;
$self->{tcp_state} = undef if $force;
}
defined($self->{tcp_state}) ? 0 : 1;
}
;# refer or change current status of an object.
;# status may have OK, ERROR, FATAL.
sub status ($;$) {
my $self = shift;
# once status was undefined, you can't clear it.
return undef if !defined($self->{tcp_state});
# `change status' or `refer status' ?
$self->{tcp_state} = shift if @_;
# result is the current status.
$self->{tcp_state};
}
;# dump all key/val pairs.
;# for debug purpose only.
sub dump ($@) {
my $self = shift;
my @index = @_ ? @_ : sort keys %{$self};
my $count = 0;
print("TCP dump $self");
for my $key (@index) {
if (exists($self->{$key})) {
print(" $key => $self->{$key}\n");
$count++;
}
}
$count;
}
;# Send a line with CR/LF.
sub putln ($$) {
my $self = shift;
my $sock = $self->handle;
my $line = shift;
my $ok = 0;
# DEBUG only - check socket!
defined($sock) or confess("TCP: socket is not defined");
# try to clear current status.
$self->clearerror || return undef;
# remove trailing spaces. DO NOT in this version.
# $line =~ s/\s+$//;
# print a line with CR/LF.
eval {
local $SIG{'ALRM'} = sub { die("alarm\n") };
alarm($self->{tcp_timeout});
$ok = print $sock ($line."\r\n");
alarm(0);
};
# check result.
if ($@) {
if ($@ eq "alarm\n") {
warn("putln: TIME OUT\n") if $LOG > 5;
$self->error("operation timed out", &FATAL);
return undef;
}
croak($@); # other evaluation error
}
# check result.
unless ($ok) {
my $e = $!.'';
return $self->error($e, &FATAL);
warn("TCP putln($line): $e\n") if $LOG > 6;
carp("$self: print - $e");
return undef;
}
# debug log
if ($LOG > 6 || $self->{tcp_debug}) {
my $fno = fileno($self->{tcp_sock});
warn("$self [$fno] putln: $line\n");
}
# count up sent data size
$self->{tcp_sent_octets} += length($line) + 2;
# success to put lines.
1;
}
;# recieve one line respone from server.
sub getln ($) {
my $self = shift;
my $sock = $self->handle;
my $line = undef;
# DEBUG only - check socket!
defined($sock) or confess("TCP: socket is not defined");
# try to clear current status.
$self->clearerror || return undef;
# if we already found end-of-file, return undef
return undef if $self->{endoffile};
# get a line from socket
eval {
local $SIG{'ALRM'} = sub { die("alarm\n") };
alarm($self->{tcp_timeout});
$line = <$sock>;
alarm(0);
};
# check result.
if ($@) {
if ($@ eq "alarm\n") {
warn("getln: TIME OUT\n") if $LOG > 5;
$self->error("operation timed out", &FATAL);
return undef;
}
carp($@); # other evaluation error
}
# check result
unless (defined($line)) { # this is not an error
warn("TCP getln: END-OF-FILE detected.\n") if $LOG > 6;
$self->{endoffile}++, return undef;
}
# or success to read a line
chomp($line);
$line =~ s/\r?$//;
# debug log
if ($LOG > 6 || $self->{tcp_debug}) {
my $fno = fileno($self->{tcp_sock});
warn("$self [$fno] getln: $line\n");
}
# count up sent data size
$self->{tcp_recv_octets} += length($line) + 2;
# success, and return this line.
$line;
}
;# send data to server
sub putdata ($$) {
my $self = shift;
my $data = shift;
my $length = length($data);
my $sock = $self->handle;
my $ok = undef;
# DEBUG only - check socket!
defined($sock) or confess("TCP: socket is not defined");
# try to clear current status.
$self->clearerror || return undef;
# if we already found end-of-file, return undef
return undef if $self->{endoffile};
# get a line from socket
eval {
local $SIG{'ALRM'} = sub { die("alarm\n") };
alarm($self->{tcp_timeout});
$ok = print $sock ($data);
alarm(0);
};
# check result.
if ($@) {
if ($@ eq "alarm\n") {
warn("putdata: TIME OUT\n") if $LOG > 5;
$self->error("operation timed out", &FATAL);
return undef;
}
croak($@); # other evaluation error
}
# check result
unless ($ok) {
my $e = $!.'';
$self->error($e, &FATAL);
carp("$self: print - $e");
return undef;
}
# or success to send data
if ($LOG > 6 || $self->{tcp_debug}) {
my $fno = fileno($self->{tcp_sock});
warn("$self [$fno] wrote $length octets.\n");
}
# count up sent data size
$self->{tcp_sent_octets} += $length;
# success, and return this line.
1;
}
;# recv data to server
sub getdata ($$) {
my $self = shift;
my $length = shift;
my $sock = $self->handle;
my $data = '';
my $len = 0;
my $ok = undef;
# DEBUG only - check socket!
defined($sock) or confess("TCP: socket is not defined");
# try to clear current status.
$self->clearerror || return undef;
# get a line from socket
eval {
local $SIG{'ALRM'} = sub { die("alarm\n") };
alarm($self->{tcp_timeout});
$len = read($sock, $data, $length);
alarm(0);
};
# check result.
if ($@) {
if ($@ eq "alarm\n") {
warn("getdata: TIME OUT\n") if $LOG > 5;
$self->error("operation timed out", &FATAL);
return undef;
}
croak($@); # other evaluation error
}
# check result
unless (defined($len) && $len > 0) {
$self->{endoffile}++, return undef;
}
# or success to recv data
if ($LOG > 6 || $self->{tcp_debug}) {
my $fno = fileno($self->{tcp_sock});
warn("$self [$fno] read $len octets.\n");
}
# count up sent data size
$self->{tcp_recv_octets} += $len;
# success, and return this line.
$data;
}
;#
sub nowait ($) {
my $self = shift;
my $sock = $self->handle;
defined($sock) || return undef;
my $a = select($sock); $| = 1; select($a);
1;
}
;# opening socket...
;# and if bindport / bindaddr was specified, we try to
;# bind the socket.
;# this should be a internal routine.
sub open_socket ($%) {
my $self = shift;
my %params = @_;
# try clear error first.
$self->clearerror || return undef;
# if we already have a socket, close it first.
$self->close;
# check local side port #.
my $port = $params{tcp_bindport} || $self->{tcp_bindport} || 0;
# define local side address if bindaddr is not null string.
my $family = $params{tcp_family} ? $params{tcp_family} : AF_INET;
my $tcp_bindaddr = $params{tcp_bindaddr} || $self->{tcp_bindaddr} ||
(($family == AF_INET) ? '0.0.0.0' : '::');
my ($socktype, $proto, $me, $canonname);
($family, $socktype, $proto, $me, $canonname)
= getaddrinfo($tcp_bindaddr, $port, $family, SOCK_STREAM);
# local file handle...
local *SOCKET;
# creating a stream socket.
unless (socket(SOCKET, $family, $socktype, $proto)) {
my $e = $!.'';
$self->error($e, &FATAL);
carp("$self: socket - $e") if $LOG >= 5;
return undef;
}
# bind addresses.
unless (bind(SOCKET, $me)) {
my $e = $!.'';
$self->close;
$self->error($e, &FATAL);
carp("$self: bind - $e") if $LOG >= 5;
return undef;
}
# debug log...
if ($LOG > 5 || $self->{tcp_debug}) {
warn("$self [".fileno(SOCKET)."] was opened.\n");
}
# save it
$self->{tcp_sock} = *SOCKET;
# success to create and bind socket.
*SOCKET;
}
;# connecting the server.
sub do_client ($%) {
my $self = shift;
my %params = @_;
my $sock;
# close handle if exists
$self->close;
# clear error or return
$self->clearerror(1);
# parse argument
my $port = $params{tcp_port} || $self->{tcp_port};
my $host = $params{tcp_host} || $self->{tcp_host};
# check required parameters
if ($port eq '') {
$self->error("no tcp_port", &FATAL);
carp("$self: tcp_port not defined");
return undef;
}
if ($host eq '') {
$self->error("no tcp_host", &FATAL);
carp("$self: tcp_host not defined");
return undef;
}
# check server name and try to parse port number
my @infos = getaddrinfo($host, $port, AF_UNSPEC, SOCK_STREAM);
if ($#infos < 1) {
carp("$self: getaddrinfo($host, $port) - $?");
my $e = $?.'';
$self->error($e, &FATAL);
carp("$self: getaddrinfo($host, $port) - $e");
return undef;
}
# Perl's bug? once connect fails, we could not any more
# connect (connect returns "Invalid Argument"). So we
# create/close a socket in each iteration.
while ($#infos >= 1) {
my ($family, $socktype, $proto, $peer, $canonname)
= splice(@infos, 0, 5);
# open socket stores any error
$params{tcp_family} = $family;
$self->open_socket(%params) || return undef;
my $result = undef;
# do real work.
eval {
local $SIG{'ALRM'} = sub { die("alarm\n") };
alarm($self->{tcp_timeout});
$result = connect($self->{tcp_sock}, $peer);
alarm(0);
};
# check result.
if ($result) {
$self->nowait; # let this socket non-blocking
warn("$self connect ok, local="
.$self->sockname.", remote=".$self->peername."\n")
if $LOG > 5 || $self->{tcp_debug};
return 1; # success
}
if ($@) {
if ($@ ne "alarm\n") {
croak($@); # other evaluation error
}
warn("do_client: TIME OUT\n") if $LOG > 5;
$self->error("operation timed out"); # not fatal.
} else {
# this is not a critical error, yet.
my $e = $!.'';
$self->error($e);
carp("$self: connect - $e") if $LOG >= 6;
}
# perhaps, this is a perl's bug...
$self->close; # or error found.
}
# or all connect were failed.
# carp("all connect sessions were failed");
$self->error($self->error, &FATAL);
undef;
}
;#
;# open accept socket, and listen at specified addr/port.
;#
sub do_server ($%) {
my $self = shift;
my %param = @_;
# close handle if exists
$self->close;
# clear error first
$self->clearerror(1);
# default backlog is 5.
my $backlog = $param{tcp_backlog} > 0 ? $param{tcp_backlog} : 5;
# opening new socket.
$self->open_socket(%param) || return undef;
# try real work
unless (listen($self->{tcp_sock}, $backlog)) {
my $e = $!.'';
$self->error($e, &FATAL);
carp("$self: listen - $e");
return undef;
}
# success to listen
1;
}
;#
;# Accept a new connection at listening socket,
;# and create a new TCP object.
;#
sub new_client ($) {
my $self = shift;
my $sock;
defined($sock = $self->handle) || return undef;
my $client = $self->new; # client = new TCP object.
my $result = 0;
local *SOCKET;
eval {
local $SIG{'ALRM'} = sub { die("alarm\n") };
alarm($self->{tcp_timeout});
$result = accept(SOCKET, $sock);
alarm(0);
};
# check result.
if ($@) {
if ($@ eq "alarm\n") {
warn("new_client: TIME OUT\n") if $LOG > 5;
$self->error("operation timed out", &FATAL);
return undef;
}
croak($@); # other evaluation error
}
# result of accept
unless ($result) {
my $e = $!.'';
$self->error($e, &FATAL);
carp("accept: $e");
return undef;
}
# store...
$client->{tcp_sock} = *SOCKET;
# debug log...
if ($LOG > 5 || $self->{tcp_debug}) {
my $fno = fileno($client->{tcp_sock});
warn("$self [$fno] was accepted.\n");
}
# set no wait
$client->nowait;
$client;
}
;#
sub close ($) {
my $self = shift;
my $sock = $self->handle;
# close socket if already we have opened.
if (defined($sock)) {
my $fno = fileno($sock);
shutdown($sock, 2);
CORE::close($sock);
# debug log...
if ($LOG > 5 || $self->{tcp_debug}) {
warn("$self [$fno] was closed.\n");
}
}
# delete socket file handle
delete($self->{tcp_sock});
#
1;
}
;#
sub handle ($) {
my $self = shift;
# check existence of tcp_sock - we must have this.
unless (exists($self->{tcp_sock})) {
carp("$self has no tcp_sock") if $LOG > 6;
return undef;
}
# copy to a local variable.
my $s = $self->{tcp_sock};
# validate our socket.
unless (defined($s) && defined(fileno($s))) {
carp("$self->tcp_sock is not a file handle") if $LOG > 7;
return undef;
}
# DEBUG information.
if ($LOG > 7) {
my $fno = fileno($s);
warn("$self->tcp_sock=[$fno]\n");
}
# return file handle itself.
$s;
}
;#
sub sockname ($) {
my $self = shift;
my $sock = $self->handle;
if (defined($sock)) {
my $sa = getsockname($sock);
my $family = (unpack('CC', $sa))[1];
my ($addr, $port) = getnameinfo($sa, NI_NUMERICHOST |
NI_NUMERICSERV);
carp("$self sockname=$addr|$port|$family") if $LOG > 7;
return wantarray ? ($port, $addr, $family)
: "$addr|$port|$family";
}
return wantarray ? () : undef;
}
;#
sub peername ($) {
my $self = shift;
my $sock = $self->handle;
if (defined($sock)) {
my $sa = getpeername($sock);
my $family = (unpack('CC', $sa))[1];
my ($addr, $port) = getnameinfo($sa, NI_NUMERICHOST |
NI_NUMERICSERV);
carp("$self sockpeer=$addr|$port|$family") if $LOG > 7;
return wantarray ? ($port, $addr, $family)
: "$addr|$port|$family";
}
return wantarray ? () : undef;
}
;# end of Fan::TCP module
syntax highlighted by Code2HTML, v. 0.9.1