whisker_perl_demo.pl |
Top Previous Next |
#!/usr/bin/perl -w # whisker_perl_demo.pl # 7 Feb 2010 # --------------------------- # Syntax: whisker_perl_demo.pl [server [port]] # # Note that Perl comments are preceded by a hash (#)
require 5.002; # Require this version of Perl. use strict; # Enable strict syntax checking. use Socket; # Use the Socket module for TCP/IP communications. use IO::Handle; # IO::Handle provides the autoflush command. use DBI; # Database access functions. use DBI qw(:sql_types); # Use SQL keywords. # use DBI qw(:get_info); # for DBI::SQL_DBMS_NAME; not yet supported; see http://www.mail-archive.com/dbi-dev@perl.org/msg00624.html
my $server = shift || 'localhost'; # Get server name or supply default. my $mainport = shift || 3233; # Get (main) port number or supply default. my ($immport, $line, $event, $reply); # Declare other variables. my $DEBUGNETWORK = 1; # Controls verbose printing of network communications my $DEBUGDATABASE = 1; # Controls verbose printing of database communications
print "Whisker demo in Perl\n"; print "--------------------\n";
# Declare handles our ($OUTFILE, $MAINSOCK, $IMMSOCK);
# Open text file. print "--- Enter text-based results file details.\n"; my $textfile = AskUser("Text results file", "whiskertemp.txt"); open($OUTFILE, ">$textfile");
# Open database connection. print "--- Enter database details.\n"; my $driver = AskUser("Database driver for DBI (e.g. mysql, ODBC) (NB case-sensitive)", "ODBC"); my $database = AskUser("Name of database", "testdb"); my $username = AskUser("Database username", "root"); my $password = AskUserPassword("Database password"); my $dbh = ConnectToDatabase($driver, $database, $username, $password);
# Open network connection. $immport = ConnectBothPorts($server, $mainport);
# Initial commands to server: claiming lines and setting up the task. Send("ReportName Whisker Perl demo program"); Send("ReportStatus Absolutely fine."); Send("WhiskerStatus"); $reply = SendImmediate("TimerSetEvent 1000 9 TimerFired"); $reply = SendImmediate("TimerSetEvent 12000 0 EndOfTask"); Send("TestNetworkLatency");
# Enter a loop to listen to messages from the server. while (chomp($line = <$MAINSOCK>)) { if ($DEBUGNETWORK) { print "SERVER: $line\n"; } # For info only. if ($line =~ /^Ping/) { # If the server has sent us a Ping, acknowledge it. Send("PingAcknowledged"); } if ($line =~ /^Event: (.+)/) { # The server has sent us an event. $event = $1; if ($DEBUGNETWORK) { print "EVENT RECEIVED: $event\n"; } # For info only.
# Event handling for the behavioural task is dealt with here. if ($event =~ /^EndOfTask$/) { last; } # Exit the while loop. } }
# Create some sample data. # For arrays of arrays, see http://perldoc.perl.org/perldsc.html#ARRAYS-OF-ARRAYS # For passing array references to functions, see http://www.cs.cf.ac.uk/Dave/PERL/node61.html my $table = "table1"; my @fields = ("field1","field2","field3"); my @values = ( [ "data1","data2","data3" ], [ "data4","data5","data6" ], [ "data7","data8","data9" ], );
# Write data to disk/database. Do these separately, disk first, in case there are database problems. foreach my $i (0..$#values) { my @record = @{$values[$i]}; # Long-winded for illustration WriteRecordToCSVFile($OUTFILE, ($i == 0), \@fields, \@record); } foreach my $i (0..$#values) { SQLInsertRecord($dbh, $table, \@fields, \@{$values[$i]}); }
LogOut(); print "----------------\n"; print "Finished.\n"; exit;
# ----------------------------------------------------------- # --------------------------- Networking routines. # -----------------------------------------------------------
sub ConnectBothPorts { my $server = shift; # Fetch first parameter my $mainport = shift; # Fetch next parameter
my ($immport, $code, $line); # Declare other local variables. ConnectMain($server, $mainport); # Log in to the server. # Listen to the server until we can connect the immediate socket. while (chomp($line = <$MAINSOCK>)) { # chomp removes the trailing newline # The server has sent us a message via the main socket. if ($DEBUGNETWORK) { print "SERVER: $line\n"; } # Print the message, for info only. if ($line =~ /^ImmPort: (\d+)/) { $immport = $1; } if ($line =~ /^Code: (\w+)/) { $code = $1; ConnectImmediate($server, $immport, $code); last; # Exit the while loop. } }
return $immport; }
sub ConnectMain { my $server = shift; # Fetch first parameter. my $port = shift; # Fetch next parameter.
print "Connecting main port to server.\n"; if ($port =~ /\D/) { $port = getservbyname($port, 'tcp'); } die "No port" unless $port; my $iaddr = inet_aton($server) || die "No host: $server"; my $paddr = sockaddr_in($port, $iaddr); my $proto = getprotobyname('tcp'); socket($MAINSOCK, PF_INET, SOCK_STREAM, $proto) || die "Can't make socket: $!"; connect($MAINSOCK, $paddr) || die "Can't connect: $!"; print "Connected to main port $port on " . inet_ntoa($iaddr) . "\n"; use Socket qw(IPPROTO_TCP TCP_NODELAY); setsockopt($MAINSOCK, IPPROTO_TCP, TCP_NODELAY, 1); # Disable the Nagle algorithm. $MAINSOCK->autoflush(1); # Ensure that output to the socket gets sent immediately. }
sub ConnectImmediate { my $server = shift; my $port = shift; my $code = shift;
print "Connecting immediate port to server.\n"; if ($port =~ /\D/) { $port = getservbyname($port, 'tcp'); } die "No port" unless $port; my $iaddr = inet_aton($server) || die "No host: $server"; my $paddr = sockaddr_in($port, $iaddr); my $proto = getprotobyname('tcp'); socket($IMMSOCK, PF_INET, SOCK_STREAM, $proto) || die "Can't make socket: $!"; connect($IMMSOCK, $paddr) || die "Can't connect: $!"; print "Connected to immediate port $port on " . inet_ntoa($iaddr) . "\n"; use Socket qw(IPPROTO_TCP TCP_NODELAY); setsockopt($IMMSOCK, IPPROTO_TCP, TCP_NODELAY, 1); $IMMSOCK->autoflush(1); SendImmediate("Link $code"); }
sub LogOut { close ($MAINSOCK) || die "Error closing main socket: $!"; close ($IMMSOCK) || die "Error closing immediate socket: $!"; }
sub Send { # Send something to the server on the main socket, with a trailing newline. if ($DEBUGNETWORK) { print STDOUT "Main socket command: @_\n"; } # For info only. print $MAINSOCK "@_\n"; }
sub SendImmediate { # Send a command to the server on the immediate socket, and retrieve its reply. if ($DEBUGNETWORK) { print STDOUT "Immediate socket command: @_\n"; } # For info only. print $IMMSOCK "@_\n"; chomp(my $reply = <$IMMSOCK>); if ($DEBUGNETWORK) { print STDOUT "Immediate socket reply: $reply\n"; } # For info only. return $reply; }
# ----------------------------------------------------------- # --------------------------- User interface routines. # -----------------------------------------------------------
sub AskUser { print "$_[0] [$_[1]]: "; my $rc = <>; chomp $rc; if($rc eq "") { $rc = $_[1]; } return $rc; }
sub AskUserPassword { # http://www.perlmonks.org/?node_id=352298 $|=1; #Turn on AutoFlush use Term::ReadKey; ReadMode('noecho'); ReadMode('raw'); my $pass = ''; print "$_[0]: "; while (1) { my $c; 1 until defined($c = ReadKey(-1)); last if (($c eq "\n") || ($c eq "\r")); # Windows machines: Enter gives \r, not \n! print "*"; $pass .= $c; } ReadMode('restore'); print "\n";
return $pass; }
# ----------------------------------------------------------- # --------------------------- Database routines, using ODBC. # -----------------------------------------------------------
sub ConnectToDatabase { my $driver = shift; my $database = shift; my $username = shift; my $password = shift;
print "Connecting to database.\n"; my $dbh = DBI->connect('DBI:'.$driver.':'.$database,$username,$password) or die "Failed to connect to database: $database\n"; # *** weaken "die" my $dbtype = $dbh->get_info(17); # 17 is DBI::SQL_DBMS_NAME print "Connected to database: $driver:$database. Database type is: $dbtype\n";
return $dbh; }
sub SQLInsertRecord { my $dbh = shift; my $table = shift; my $ref_fields = shift; my @fields = @{$ref_fields}; my $ref_values = shift; my @values = @{$ref_values};
my $fields_size = @fields; my $values_size = @values; if ($fields_size != $values_size) { die "SQLInsertRecord failed: size of fields array differs from size of values array\n"; } # SQL ultimately e.g. INSERT INTO table (field1, field2, field3) VALUES (value1, value2, value3); # but we start with INSERT INTO table (field1, field2, field3) VALUES (?, ?, ?); my $sqlstring = "INSERT INTO $table ("; foreach my $i (0..$#fields) { if ($i > 0) { $sqlstring .= ", "; } $sqlstring .= $fields[$i]; } $sqlstring .= ") VALUES ("; foreach my $i (0..$#values) { if ($i > 0) { $sqlstring .= ", "; } $sqlstring .= "?"; } # if ($DEBUGDATABASE) { print "SQL so far: $sqlstring\n"; } $sqlstring .= ");"; my $insert_sql = $dbh->prepare($sqlstring); foreach my $i (0..$#values) { $insert_sql->bind_param($i+1, $values[$i]); # refers to the i'th ? in the query; bind_param indexed from 1, not 0 } $insert_sql->execute(); # excute the SQL statement }
# ----------------------------------------------------------- # --------------------------- Textfile results storage. # -----------------------------------------------------------
sub WriteRecordToCSVFile { my $OUTFILE = shift; my $firstrecord = shift; my $ref_fields = shift; my @fields = @{$ref_fields}; my $ref_values = shift; my @values = @{$ref_values};
my $fields_size = @fields; my $values_size = @values; if ($fields_size != $values_size) { die "WriteRecordToCSVFile failed: size of fields array differs from size of values array\n"; } if ($firstrecord) { # For the first record, print the field names foreach my $i (0..$#fields) { if ($i > 0) { print $OUTFILE ","; } print $OUTFILE $fields[$i]; } print $OUTFILE "\n"; } # Print the values in comma-separated format # could use (0..($values-1)) or (0..$#values) to iterate through foreach my $i (0..$#values) { if ($i > 0) { print $OUTFILE ","; } print $OUTFILE $values[$i]; } print $OUTFILE "\n"; } |