Perl Multithreaded Server Example
- Author:
- Date:2016/08/16
Often times when writing a server you will need to be able to handle multiple client connections at a time. To do this you will want to create a threaded server.
The first thing that you will need to do is open a listening socket and create a loop that will continue listening for new clients. In this example I use IO::Socket::INET and a simple while(1) loop. We also need to create an array for our threads as shown in my Perl threading tutorial.
sub Main { # flush after every write $| = 1; my ( $socket, $client_socket ); # Bind to listening address and port $socket = new IO::Socket::INET ( LocalHost => '127.0.0.1', LocalPort => '1337', Proto => 'tcp', Listen => 5, Reuse => 1 ) or die "Could not open socket: ".$!."\n"; print "SERVER Waiting for client connections...\n"; my @clients = (); while(1) { # Waiting for new client connection. $client_socket = $socket->accept(); # Push new client connection to it's own thread push ( @clients, threads->create( \&clientHandler, $client_socket ) ); foreach ( @clients ) { if( $_->is_joinable() ) { $_->join(); } } } $socket->close(); return 1; }
The code above will accept connections from new users, then throw the client connection into a thread where commands can be processed.
To process commands you will need to create the client handler function and a while loop to handle the buffer. Within the while loop commands can be processed by matching buffer against regular expression. You could create a regular expression to get command & arguments then use a case-switch, but I won’t cover this method.
sub clientHandler { # Socket is passed to thread as first (and only) argument. my ($client_socket) = @_; # Create hash for user connection/session information and set initial connection information. my %user = (); $user{peer_address} = $client_socket->peerhost(); $user{peer_port} = $client_socket->peerport(); print "Accepted New Client Connection From:".$user{peer_address}.":".$user{peer_port}."\n"; # Let client know that server is ready for commands. print $client_socket "> "; # Listen for commands while client socket remains open while( my $buffer = <$client_socket> ) { # Accept the command `HELLO` from client with optional arguments if( $buffer =~ /^HELLO(\s|$)/i ) { #Example reply print $client_socket "HELLO THERE!!\n"; print $client_socket "Your IP:\t".$user{peer_address}."\n"; print $client_socket "Your Port:\t".$user{peer_port}."\n"; } # This will terminate the client connection to the server if( $buffer =~ /^QUIT(\s|$)/i ) { # Print to client, and print to STDOUT then exit client connection & thread print $client_socket "GOODBYE\n"; print "Client exit from ".$user{peer_address}.":".$user{peer_port}."\n"; $client_socket->shutdown(2); threads->exit(); } print $client_socket "> "; } print "Client exit from ".$user{peer_address}.":".$user{peer_port}."\n"; # Client has exited so thread should exit too threads->exit(); }
In the example above you will notice the command
allows the client to exit the server cleanly. This allows the server to issue the 1
quit
method which is a clean way of exiting a socket. Then it calls 1
$client_socket->shutdown()
which is how you cleanly exit a thread. By exiting this way the server won’t be keeping open potentially stale connections. It is also wise to add some sort of PING-PONG between the client and server to check that connections are still alive and active.1
threads-exit()
$ perl PerlServer.pl SERVER Waiting for client connections... Accepted New Client Connection From:127.0.0.1:52562 Client exit from 127.0.0.1:52562
$ telnet localhost 1337 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. > HELLO HELLO THERE!! Your IP: 127.0.0.1 Your Port: 63600 > QUIT GOODBYE Connection closed by foreign host.
Here is the full code for the example server
#!/usr/bin/perl use strict; use warnings; use IO::Socket::INET; use threads; sub Main { # flush after every write $| = 1; my ( $socket, $client_socket ); # Bind to listening address and port $socket = new IO::Socket::INET ( LocalHost => '127.0.0.1', LocalPort => '1337', Proto => 'tcp', Listen => 5, Reuse => 1 ) or die "Could not open socket: ".$!."\n"; print "SERVER Waiting for client connections...\n"; my @clients = (); while(1) { # Waiting for new client connection. $client_socket = $socket->accept(); # Push new client connection to it's own thread push ( @clients, threads->create( \&clientHandler, $client_socket ) ); foreach ( @clients ) { if( $_->is_joinable() ) { $_->join(); } } } $socket->close(); return 1; } sub clientHandler { # Socket is passed to thread as first (and only) argument. my ($client_socket) = @_; # Create hash for user connection/session information and set initial connection information. my %user = (); $user{peer_address} = $client_socket->peerhost(); $user{peer_port} = $client_socket->peerport(); print "Accepted New Client Connection From:".$user{peer_address}.":".$user{peer_port}."\n"; # Let client know that server is ready for commands. print $client_socket "> "; # Listen for commands while client socket remains open while( my $buffer = <$client_socket> ) { # Accept the command `HELLO` from client with optional arguments if( $buffer =~ /^HELLO(\s|$)/i ) { #Example reply print $client_socket "HELLO THERE!!\n"; print $client_socket "Your IP:\t".$user{peer_address}."\n"; print $client_socket "Your Port:\t".$user{peer_port}."\n"; } # This will terminate the client connection to the server if( $buffer =~ /^QUIT(\s|$)/i ) { # Print to client, and print to STDOUT then exit client connection & thread print $client_socket "GOODBYE\n"; print "Client exit from ".$user{peer_address}.":".$user{peer_port}."\n"; $client_socket->shutdown(2); threads->exit(); } print $client_socket "> "; } print "Client exit from ".$user{peer_address}.":".$user{peer_port}."\n"; # Client has exited so thread should exit too threads->exit(); } # Start the Main loop Main();
I hope this helped, sorry about the lack of indentation in some of the code, for some reason WordPress is messing it up in my code blocks.