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

I'm building a TCP server to learn more about networking. What I have so far is based on various online examples: a server that responds to requests using a series of if, elsif, elsif attempts to match the request to an appropriate response.

It seems like I should be looking to implement an application layer protocol to transfer data (text) between server and client, but I don't want to invent my own protocol or use one that's more complex than I need. Google searches have not resulted in a definitive answer. Is there an existing protocol I should look into?

At the moment my client is just nc to the appropriate server port. I'm assuming I'll have to build a client if I implement an application level protocol

#!/usr/bin/perl use warnings; use strict; use POSIX; use IO::Socket; use IO::Select; use Tie::RefHash; my $port = 1800; ### Create the server socket. my $server = IO::Socket::INET->new( LocalPort => $port, Listen => 10, ) or die "can't make server socket: $@\n"; $server->blocking(0); ### Set up structures to track input and output data. my %inbuffer = (); my %outbuffer = (); my %ready = (); tie %ready, "Tie::RefHash"; ### The select loop itself. my $select = IO::Select->new($server); while (1) { # Process sockets that are ready for reading. foreach my $client ($select->can_read(1)) { handle_read($client); } # Process any complete requests. Echo the data back to the client, # by putting the ready lines into the client's output buffer. foreach my $client (keys %ready) { foreach my $request (@{$ready{$client}}) { process_request($client,$request); } delete $ready{$client}; } # Process sockets that are ready for writing. foreach my $client ($select->can_write(1)) { handle_write($client); } } exit; sub process_request { my ($client,$request) = @_; print "Got request: $request"; chomp $request; if ( $request eq "HELLO" ) { $outbuffer{$client} .= "TEST SERVER 0.1\n"; } elsif ($request eq "GET NODES" ) { $outbuffer{$client} .= "NODES: 0 ZERO, 1 ONE, 2 TWO, 4 FOUR\n" +; } elsif ( $request =~ /^JOIN / ) { $outbuffer{$client} .= "JOINING NOT IMPLEMENTED\n"; } elsif ( $request =~ /^MSG / ) { $outbuffer{$client} .= "MSGING NOT IMPLEMENTED\n"; } else { $outbuffer{$client} .= "UNKNOWN COMMAND\n"; } } ### Handle a socket that's ready to be read from. sub handle_read { my $client = shift; # If it's the server socket, accept a new client connection. if ($client == $server) { my $new_client = $server->accept(); $new_client->blocking(0); $select->add($new_client); return; } # Read from an established client socket. my $data = ""; my $rv = $client->recv($data, POSIX::BUFSIZ, 0); # Handle socket errors. unless (defined($rv) and length($data)) { handle_error($client); return; } # Successful read. Buffer the data we got, and parse it into lines. # Place the lines into %ready, where they will be processed later. $inbuffer{$client} .= $data; while ($inbuffer{$client} =~ s/(.*\n)//) { push @{$ready{$client}}, $1; } } ### Handle a socket that's ready to be written to. sub handle_write { my $client = shift; # Skip this client if there's nothing to write. return unless exists $outbuffer{$client}; # Attempt to write pending data to the client. my $rv = $client->send($outbuffer{$client}, 0); unless (defined $rv) { warn "I was told I could write, but I can't.\n"; return; } # Successful write. Remove what was sent from the output buffer. if ( $rv == length($outbuffer{$client}) or $! == POSIX::EWOULDBLOCK) { substr($outbuffer{$client}, 0, $rv) = ""; delete $outbuffer{$client} unless length $outbuffer{$client}; return; } # Otherwise there was an error. handle_error($client); } ### Handle client errors. Clean up after the dead socket. sub handle_error { my $client = shift; delete $inbuffer{$client}; delete $outbuffer{$client}; delete $ready{$client}; $select->remove($client); close $client; }

Replies are listed 'Best First'.
Re: TCP Server: Beyond echoing request
by haukex (Archbishop) on May 25, 2017 at 15:07 UTC
    It seems like I should be looking to implement an application layer protocol to transfer data (text) between server and client, but I don't want to invent my own protocol or use one that's more complex than I need. Google searches have not resulted in a definitive answer. Is there an existing protocol I should look into?

    That really depends on what you want to transfer. It looks like you've already got a basic line-based protocol, which is actually not too uncommon for simple applications, so it's a very good start. But since you say you're doing this to learn, I might suggest that writing your own protocol while reading the specifications of another existing protocol might be best.

    For example, HTTP/1.0 (RFC 1945) (although it's mostly been superseded by HTTP/1.1 and grown quite complex through all sorts of extensions) can show you the basics of "one way to do it", plus you'll learn a lot about HTTP. Note that if you look at the Session Initiation Protocol (SIP, RFC 3261), one of the basics of VoIP, you'll recognize a very similar message structure as HTTP (one line for the request / response, plus headers, blank line, then content).

    Based on your example messages, it looks like you're interested in something like IRC, so perhaps a read of some technical information like RFC 1459 would be helpful. For something a bit more modern, see e.g. XMPP.

    Other classic protocols that are still in wide use today are e.g FTP and SMTP.

Re: TCP Server: Beyond echoing request
by thanos1983 (Parson) on May 25, 2017 at 14:23 UTC

    Hello jeremywakeman

    I am a bit stuck with your question:

    It seems like I should be looking to implement an application layer protocol to transfer data (text) between server and client

    By this you mean files or text input?

    If you mean text input there are many examples online that you can setup a TCP client/server. A few years ago I tried my version (how to control segmented messages in TCP chat client) take a look if this is something that you are looking for.

    I think the code is working just fine if not let me know and I will update it.

    If in any case I have understand clearly your question, update it and I will try to elaborate more.

    Update: Use <readmore></readmore> from top to bottom of your code so you can reduce the size of the scrolling page output. ;)

    Update2: I remembered even more, I have implemented a "telnet behavior" client/server if this is something that you are looking for I have it on my github repo (thanos1983/Perl5-TCP-Chat-Server-Multiple-Clients), I have not updated the code for years but I think it should work just fine.

    Hope this helps.

    Seeking for Perl wisdom...on the process of learning...not there...yet!