Question about GServe and IO


#1

I’m trying to make just a pass through TCP server, utilizing GServer and
some example code I am able to make something that starts up, lets me
connect multiple Clients and see what is happening. Now I want to be
able to handle data depending on the string I get, the pass should also
respond to some simple commands and return data back to the Client. But
I am getting some odd behavior on the Client when I send data, it seems
to be a step behind. What I have so far is a simple Gserver, the port I
want to be constant and ARGV[0] is basically the local hostname I want
to run on.

require ‘gserver’
port = 3472
host = ARGV[0]

class TestController < GServer
def initialize(port, host)
super(port, host)
end

def serve(io)
    @data_mode = false
    loop do
        if IO.select([io],nil,nil,5)
            data = io.readpartial(4096)
            op = handle_client(data,io)
            io.puts op
        end
        break if io.closed?
    end
    io.close
end

def handle_client(line,io)
    if line =~ /^time/
        show_time(io)
    elsif line =~ /^agents/
        display_agents(io)
    elsif line =~ /^help/
        show_help(io)
    elsif line =~ /^shutdown/
        io.puts "You don't want to do that Dave."
        self.stop
    elsif line =~ /^stop/
        stop_test(io)
    elsif line =~ /^HELO/
        io.puts "ACK"
    else
        io.puts "Command not understood."
    end
end

This basically checks the data string and then routes off depending on
what I am sending in from the client…either show time on the GServer,
display some help info or return an ACK if it receives a HELO; pretty
much basic stuff to understand it all. When I put some commands in I
get “Command not understood” then on the next input from the client I
get my previous command, so I see on the client:

time
Fri Mar 06 11:13:54 200
help
nil

This is the help function.

nil

agents
Command not understood.
nil

If there was a way to display agents it would be here.
nil

HELO
Command not understood.
nil

ACK
nil

Am I not flushing something right on the Client, or is this in how the
IO is being handled on the GServer?

The Client is set up to send like this:

    loop do
        STDOUT.print '> '
        STDOUT.flush
        local = STDIN.gets
        break if !local
        s.puts(local)
        s.flush
        # Print out servers response
        response = s.readpartial(4096)
        puts(response.chop)
    end

I’m still new to this, so I apologize if its kind of basic I couldn’t
understand what is going on or find something that describes a similar
problem in the forum.

Thanks!


#2

Michael Furmaniuk wrote:

    loop do
        if IO.select([io],nil,nil,5)
            data = io.readpartial(4096)
            op = handle_client(data,io)
            io.puts op
        end
        break if io.closed?
    end
    io.close

You don’t need the select/readpartial stuff. Each connection is handled
in its own thread. This should do:

while line = io.gets
  handle_client(line,io)
end

I don’t see the need for io.puts op, since you’re sending responses in
your handle_client already.

You should be able to test this using plain old telnet.


#3

Brian C. wrote:

You don’t need the select/readpartial stuff. Each connection is handled
in its own thread. This should do:

while line = io.gets
  handle_client(line,io)
end

This worked great! I can keep my connection up and running and thin.
Still not sure what the delay is in sending a command and getting one
back, it seems to be off by one - probably something with the
communication I don’t understand yet.

I appreciate the help!


#4

Michael Furmaniuk wrote:

This worked great! I can keep my connection up and running and thin.
Still not sure what the delay is in sending a command and getting one
back, it seems to be off by one - probably something with the
communication I don’t understand yet.

If you use “telnet 127.0.0.1 3472” and type in commands by hand, does it
work as expected? That would suggest the problem is in your client side
program rather than the server. Maybe your server is sending one extra
blank line or something which the client isn’t expecting, so it’s always
reading one line behind.

However, ‘readpartial’ is a very dubious thing to be doing at the client
side, since it may only read 1 byte when the server has more data to
send. You need to frame your protocol in such a way that it’s
unambiguous where the end of a message is.

Possible ways to do this:

  • make each response exactly one line (client just does “s.gets”)

  • the SMTP/POP3 body way: have multiline responses ended by a special
    terminating character

    This is a
    multiline
    message
    .

(note that any body line which starts with a ‘.’ has an extra ‘.’
prepended, to avoid ambiguity)

  • the SMTP response way: flag each line which is not the last

    250-This is a multi-line response
    250-which the client keeps reading
    250-until it gets to a line which doesn’t
    250 have a dash after the number

  • the SMTP/HTTP chunking way: output a length (decimal or hex), followed
    by that number of bytes.

    len = Integer(s.gets.chomp)
    body = s.read(len)

  • the DRb binary way: send a 4-byte length in network order, followed by
    that number of bytes.

After you have eliminated readpartial from your program, if you still
have a problem, please make a trimmed down client/server pair and post
them in their entirety, so the problem can be reproduced.

HTH,

Brian.


#5

Michael Furmaniuk wrote:

I’m going to check and see how Net::Telnet works in this case
since that should eliminate the problem of the reads in the socket.

Net::Telnet waits for a prompt pattern in the reply. This is less than
ideal way to delimit responses, but may be OK for you.


#6

Brian C. wrote:

After you have eliminated readpartial from your program, if you still
have a problem, please make a trimmed down client/server pair and post
them in their entirety, so the problem can be reproduced.

Ok, now I see. I did a telnet and there was no issue, just some
prompting problems depending on whether I used puts or print. I would
like something for a client end that I can wrap into an Agent at some
point, I’m going to check and see how Net::Telnet works in this case
since that should eliminate the problem of the reads in the socket.
Ideally, having something be able to act as an Agent and send
information now and again through a central point and written to a
database would be most useful.

Again, thanks for the tips.