jsarrel has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to duplicate something similar to the port forwarding examples in the cookbook except with one difference...I want the TCP client to connect first and if successful, the TCP server will then starts listening for remote connections. This is opposite of the examples where the TCP server starts listening first, then the TCP client connects.

I've read the different cookbook examples trying to get a hold on how to do this but I can't figure out how to send a "shutdown" to the TCP server when the TCP client gets disconnected. Below is a very basic example. This is my first time delving into POE so any pointers are helpful.

#!/usr/bin/perl use warnings; use strict; use POE qw(Component::Server::TCP Component::Client::TCP); my $forward_address = "localhost"; my $forward_port = 7500; my $remote_port = 7400; POE::Session->create ( inline_states => { _start => \&forwarder_start, listener_start => \&spawn_client_listener, } ); $poe_kernel->run(); exit 0; sub forwarder_start { POE::Component::Client::TCP->new( Alias => 'ForwardConnection', RemoteAddress => $forward_address, RemotePort => $forward_port, Connected => \&forward_server_connected, ConnectError => \&forward_server_reconnect, ServerInput => \&forward_client_input, Disconnected => \&forward_disconnected, ); } sub spawn_client_listener { POE::Component::Server::TCP->new( Alias => 'RemoteListener', Port => $remote_port, ClientInput => \&remote_client_input, InlineStates => { remote_shutdown => \&remote_shutdown, } ); } sub forward_server_connected { $_[KERNEL]->yield('listener_start'); } sub forward_server_reconnect { print "not connected\n"; $_[KERNEL]->delay(_start => 15); } sub forward_client_input { } sub forward_disconnected { $_[KERNEL]->post(RemoteListener => "remote_shutdown"); $_[KERNEL]->yield("_start"); } sub remote_client_input { } sub remote_shutdown { print "shutting down\n"; $_[KERNEL]->yield("shutdown"); }

Thanks, Jason
  • Comment on POE - can't shutdown Component::Server::TCP from Component::Client::TCP
  • Download Code

Replies are listed 'Best First'.
Re: POE - can't shutdown Component::Server::TCP from Component::Client::TCP
by Anonymous Monk on Mar 18, 2009 at 04:42 UTC

      Hi there, I seem to have the same problem. I am pretty new to Perl (not new to programming). I would like to create a TCP server, so I read some stuff at the POE site and decided to use this one (POE: Cookbook - Chat Server) as a skeleton. I added a few things in order to have the sockets stuff right, before I continue developing. Here is my code:

      #!/usr/bin/env perl use warnings; use strict; use POE::Kernel { loop => 'POE::XS::Loop::EPoll' }; use POE qw(Component::Server::TCP); POE::Component::Server::TCP->new( Alias => "chat_server", Port => 32080, InlineStates => {send => \&handle_send}, ClientConnected => \&client_connected, ClientError => \&client_error, ClientDisconnected => \&client_disconnected, ClientInput => \&client_input, ); $poe_kernel->run(); exit 0; my %users; sub broadcast { my ($sender, $message) = @_; foreach my $user (keys %users) { if ($user == $sender) { $poe_kernel->post($user => send => "You $message"); } else { $poe_kernel->post($user => send => "$sender $message"); } } } sub handle_send { my ($heap, $message) = @_[HEAP, ARG0]; $heap->{client}->put($message); } sub client_connected { my $session_id = $_[SESSION]->ID; $users{$session_id} = 1; broadcast($session_id, "connected."); } sub client_disconnected { my $session_id = $_[SESSION]->ID; delete $users{$session_id}; broadcast($session_id, "disconnected."); } sub client_error { my $session_id = $_[SESSION]->ID; delete $users{$session_id}; broadcast($session_id, "disconnected."); $_[KERNEL]->yield("shutdown"); } sub client_input { my ($kernel, $session, $input) = @_[KERNEL, SESSION, ARG0]; my $session_id = $session->ID; if ($input eq "/quit") { # user entered /quit $poe_kernel->post($session_id => send => "goodbye"); $kernel->yield("shutdown"); return; } elsif ($input eq "/shutdown") { # user entered /shutdown $poe_kernel->post($session_id => send => "don't do that!"); ### how to do this, shutdown the server $kernel->call('chat_server', 'shutdown'); ######################################### return; } broadcast($session_id, "said: $input"); }

      I added:

      • I use POE::XS::Loop::EPoll. I understand that in order to serve many connections, using epoll is a good thing. (How can I check whether the POE kernel is indeed using epoll or not?)
      • I added functionality for users to enter commands. With the /quit command a user can leave the server. With /shutdown a user can shutdown the whole program. (Anyone can do a shutdown, security comes later.)

      When I use a telnet client to connect, the /shutdown command works, because the "don't do that!" debug message is displayed. The line $kernel->call('chat_server', 'shutdown'); might be wrong because it doesn't shutdown the server program, but there isn't an error message displayed. Can anyone please tell me which instruction to use? I tried several things, like $kernel->yield('chat_server', "shutdown"); and $poe_kernel->post('chat_server, 'shutdown');. The POE::Component::Server::TCP can't help me.

        Oh yes, I did it! It came to mind that the server shuts down after all sessions have been shut down. My own session was still open. So the solution is:

        elsif ($input eq "/shutdown") { # user entered /shutdown $poe_kernel->post($session_id => send => "don't do that!"); $kernel->yield("shutdown"); # <<< CLOSE MY SESSION $kernel->call('chat_server', 'shutdown'); return; }

        That was simple. Of course this should be improved: I should close all sessions, not only mine. After sending them a message informing them about the shutdown, off course. :-)

        I am used to the Lua programming language which is similar to Perl (no, I'm not provoking discussion about similarities and differences), and I'm trying to switch to Perl. With Lua and the LuaCopas module, one isn't able to stop sessions without shutting down the program itself, as far as I know. It uses a coroutine for every session. So I wasn't used to that. :-)

        To me this case is closed. I'm not sure about jsarrel's problem.

        About my epoll question, I'll continue my research...

        How can I check whether the POE kernel is indeed using epoll or not?
        use POE::Kernel; print POE::Kernel::poe_kernel_loop; # --> POE::Loop::Select
        or:
        use POE::Kernel { loop => 'POE::XS::Loop::EPoll' }; print POE::Kernel::poe_kernel_loop; # --> POE::XS::Loop::EPoll
        and the tricky one:
        use POE::Kernel; use POE::Kernel { loop => 'POE::XS::Loop::EPoll' }; print POE::Kernel::poe_kernel_loop; # --> POE::Loop::Select
      Yes, I understand the Disconnected and "shutdown" and that's exactly what I'm trying to use. The problem is I can't get the Component::Client to successfully send a "shutdown" to the Component::Server. I've tried to duplicate different ways used in different examples but with no success. I've even used alias' and was still surprised that it didn't work (using exactly like the alias example in the CPAN pages). The example I have given doesn't shutdown the server, but it does illustrate my problem, I can't send a command from one component to the other (but vice versa it works fine). Perhaps I'm just getting confused with all the different sessions and heaps and trying to do make something work when it's really not even available in that particular scope?