#!/usr/bin/perl # Simple POE server for zazaface2 client applets. # # This application impliments a new architechture to improve # performance on slower clients, and reduce the amount of # data exchanged during cue querying than the original # CGI-based faceClient. # # Copyright (C) 2002 Brian Rudy (brudyNO@SPAMpraecogito.com) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ## Revision History # # 0.02 10-4-2002 # Added additional debugging granularity, and fallback method if client # is too out of sync with the cue. # # 0.01 9-27-2002 # First working version. ## General info # # Typical interaction: # -client connects # -server passes it the last cue on the stack # -client send an ack when it is done playing. # -if a new cue has come in while the client was playing the last, # the next cue is sent. # -otherwise the server waits for a new cue to be requested. # # Each client has the following data stored for it: # $clientdata[session_id]{current_cue} = current cue for the listed session # $clientdata[session_id]{ready} = is client ready for next cue? use warnings; use strict; use POE; use POE::Component::Server::TCP; use IPC::Shareable; # Debugging stuff #$IPC::Shareable::Debug = 1; # 1 print only connects and disconnects # 2 also print status info # 3 print all handshaking info my $debuglevel = 4; my $faceglue = 'facedata'; my %options = ( 'key' => 'face', 'create' => 0, 'exclusive' => 0, 'mode' => 0644, 'destroy' => 0, ); my @faceinfo; tie(@faceinfo, 'IPC::Shareable', $faceglue, { %options }) or die "$$: tie failed $!\n"; # Create the server on port 26026, and start it running. POE::Component::Server::TCP->new ( Alias => "POE_voiceServer", Port => 26026, InlineStates => { send => \&handle_send }, ClientConnected => \&client_connected, ClientError => \&client_error, ClientDisconnected => \&client_disconnected, ClientInput => \&client_input, ); # Create the method to check the Shared memory segment POE::Session->create ( inline_states => { _start => \&handler_start, chkstack => \&handler_chkstack, _stop => \&handler_stop, } ); $poe_kernel->run(); exit 0; # # Handlers my %clients; my @clientdata; # Set up initial values sub handler_start { my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION]; if ($debuglevel >= 3) { print "Session ", $session->ID, " has started.\n"; } $heap->{lastcue} = $faceinfo[$#faceinfo]{timestamp}; $kernel->yield('chkstack'); } # check loop sub handler_chkstack { my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION]; # send the latest cue if ($heap->{lastcue} != $faceinfo[$#faceinfo]{timestamp}) { foreach my $user ( keys %clients ) { if ($clientdata[$user]{ready}) { if ($clientdata[$user]{current_cue} == $heap->{lastcue}) { # send them the latest cue $clientdata[$user]{current_cue} = $faceinfo[$#faceinfo]{timestamp}; $poe_kernel->post( $user => send => $faceinfo[$#faceinfo]{timestamp} . " " . $faceinfo[$#faceinfo]{stringfinger} . " " . $faceinfo[$#faceinfo]{emotion}); } # Should we do something if they aren't current, but ready? } # The client will get the next one when it is ready } $heap->{lastcue} = $faceinfo[$#faceinfo]{timestamp}; } $kernel->yield('chkstack'); } # close and shut down sub handler_stop { if ($debuglevel >= 1) { print "Session ", $_[SESSION]->ID, " has stopped.\n"; } } # Send something to everyone #sub broadcast { # my ($message); # foreach my $user ( keys %clients ) { # $poe_kernel->post( $user => send => "New cue: $message" ); # } #} # Handle an outgoing message by sending it to the client. sub handle_send { my ( $heap, $message ) = @_[ HEAP, ARG0 ]; $clientdata[$_[SESSION]->ID]{ready} = 0; $heap->{client}->put($message); } # Handle a connection. Register the new client. sub client_connected { my $heap = $_[HEAP]; my $session_id = $_[SESSION]->ID; $clients{$session_id} = 1; my $clientip = $heap->{remote_ip}; $poe_kernel->post( $session_id => send => $faceinfo[$#faceinfo]{timestamp} . " " . $faceinfo[$#faceinfo]{stringfinger} . " " . $faceinfo[$#faceinfo]{emotion}); $clientdata[$session_id]{current_cue} = $faceinfo[$#faceinfo]{timestamp}; if ($debuglevel >= 1) { print "$session_id: $clientip connected."; } if ($debuglevel >= 2) { my $numclients = keys %clients; print " There are now $numclients clients connected.\n"; } } # The client disconnected. Remove them from the server. sub client_disconnected { my $heap = $_[HEAP]; my $session_id = $_[SESSION]->ID; my $clientip = $heap->{remote_ip}; delete $clients{$session_id}; $clientdata[$session_id]{current_cue} = 0; $clientdata[$session_id]{ready} = 0; if ($debuglevel >= 1) { print "$session_id: $clientip disconnected."; } if ($debuglevel >= 2) { my $numclients = keys %clients; print " There are now $numclients clients connected.\n"; } } # The client socket has had an error. Remove them from the server. sub client_error { my $heap = $_[HEAP]; my $session_id = $_[SESSION]->ID; my $clientip = $heap->{remote_ip}; delete $clients{$session_id}; if ($debuglevel >= 2) { print "client_error, $session_id: $clientip\n"; } $_[KERNEL]->yield("shutdown"); } # Process input from the client sub client_input { my ( $kernel, $session, $input ) = @_[ KERNEL, SESSION, ARG0 ]; my $session_id = $session->ID; if ($debuglevel >= 3) { print "$session_id sent $input.\n"; } if ($clientdata[$session_id]{current_cue} != $faceinfo[$#faceinfo]{timestamp}) { sendnext($session_id, $clientdata[$session_id]{current_cue}); } else { $clientdata[$session_id]{ready} = 1; if ($debuglevel >= 2) { print "$session_id is now current with " . $clientdata[$session_id]{current_cue} . ".\n"; } } } # send the specified client the next cue sub sendnext { my ($session_id, $oldcue) = @_; my $matched = 0; for my $cueindex (0 .. $#faceinfo) { if ($faceinfo[$cueindex]{timestamp} eq $oldcue) { $clientdata[$session_id]{current_cue} = $faceinfo[$cueindex + 1]{timestamp}; $poe_kernel->post( $session_id => send => $faceinfo[$cueindex + 1]{timestamp} . " " . $faceinfo[$cueindex + 1]{stringfinger} . " " . $faceinfo[$cueindex + 1]{emotion}); $matched = 1; } } # If we don't have a match, send the latest cue if ($matched != 1) { $clientdata[$session_id]{current_cue} = $faceinfo[$#faceinfo]{timestamp}; $poe_kernel->post( $session_id => send => $faceinfo[$#faceinfo]{timestamp} . " " . $faceinfo[$#faceinfo]{stringfinger} . " " . $faceinfo[$#faceinfo]{emotion}); } }