Forum: Ruby Question about GServe and IO

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
9cce4cca531f835e951309aa39bb421b?d=identicon&s=25 Michael Furmaniuk (mfurmaniuk)
on 2009-03-06 17:20
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
> <pressed ENTER>
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!
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2009-03-06 22:31
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.
9cce4cca531f835e951309aa39bb421b?d=identicon&s=25 Michael Furmaniuk (mfurmaniuk)
on 2009-03-09 13:31
Brian Candler 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!
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2009-03-09 15:10
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.
9cce4cca531f835e951309aa39bb421b?d=identicon&s=25 Michael Furmaniuk (mfurmaniuk)
on 2009-03-09 19:46
Brian Candler 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.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2009-03-10 17:16
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.
This topic is locked and can not be replied to.