Question About TCPServer & TCPSocket classes

Discussion in 'Ruby' started by Steve Lewis, Sep 6, 2008.

  1. Steve Lewis

    Steve Lewis Guest

    Hello Rubyists,

    I've been messing around with socket programming in Ruby lately, and I've
    hit a snag with some of my experiments. My first, (successful),
    experiment was to create a basic client/server script wherein the client
    sends a string to the server, and the server responds by simply writing
    the string back to the client...that code is as follows:

    Server:
    #####################################
    require 'socket'

    class PingServer < TCPServer

    def start_server
    loop do
    Thread.start(self.accept) do |s|
    p "Connection accepted from server #{s.inspect}"
    request = s.readline.gsub(/\n$/, '')
    p "Request was #{request}"
    s.write "Your request was as follows: #{request}"
    s.write Time.now if request == 'time'
    s.close
    p "Connection #{s} closed"
    end
    end
    end

    end

    PingServer.new('localhost', 3000).start_server

    ###############################################



    The client code is thus:
    ########################################
    require 'socket'



    def send_message(message)
    TCPSocket.open('localhost', 3000) do |client|
    client.write(message)
    stuff = client.read(100)
    client.close
    p stuff
    get_message
    end
    end

    def get_message
    message = gets
    send_message(message) unless message.gsub(/\n/, '') == 'quit'
    exit
    end


    get_message

    ##################################

    As I said, the above code works as expected, I start the server, then run
    the client program...I can send strings to the server all day long, and
    the server writes them right back to me.

    The problem occurs when I try to send the following instead of a string:

    Marshal.dump(%w(foo bar baz))

    Sending the resulting string over the wire causes the server to hang...it
    doesn't return the string back to the client...I'm at a loss to figure
    out why it seems to have trouble handling the string...

    Ideas would be appreciated!!

    Thanks

    Steve
     
    Steve Lewis, Sep 6, 2008
    #1
    1. Advertisements

  2. ^^^^^^^^

    This method reads until newline, and binary data (such as Marshal.dump
    output) is not generally delimited that way. Use IO#read or Socket#recv
    instead.

    If you want to read a binary message, you can do one of several things:

    1. pick a delimiter char and escape that wherever it occurs within the
    binary data (yuck)

    2. send a length field before sending the data, so the receiver knows
    how much to wait for

    3. send one message per socket connection

    4. encode the binary data in some non-binary form: e.g. base64, using
    pack("m"), and delimit using blank lines.
     
    Joel VanderWerf, Sep 6, 2008
    #2
    1. Advertisements

  3. Steve, you are mixing #read, #write on one side and #readline on the
    other side. Since Marshal.dump creates binary data your #readline on
    the server won't properly work. You should either use a binary
    protocoll *or* a textual (line based) protocol. Mixing both causes
    problems.

    Just an additional note: if you use Marshal, you can directly read to
    and write from the socket without the intermediate String. This is
    likely more efficient and also you avoid the issue of not knowing how
    long the Marshal sequence is.

    And yet another note: just in case you need a solution with
    communicating processes where all of them are Ruby applications you can
    use DRuby. But I guess at the moment you want to learn about Ruby
    socket programming so this is probably just for keeping in the back of
    your head.

    Kind regards

    robert
     
    Robert Klemme, Sep 6, 2008
    #3
  4. Good point. I forgot that the output of Marshal records how long the
    data is, and therefore #load can read the correct number of bytes
    directly from an io.
    Another possibility, if you are communicating to C code or other languages:

    http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/177704

    It's a lib for both C and ruby that uses length fields to make tcp
    sockets "message oriented" rather than stream oriented.
     
    Joel VanderWerf, Sep 6, 2008
    #4
  5. Steve Lewis

    Steve Lewis Guest

    Robert,

    Thanks for the help! You're correct, my curiosity here is mostly
    academic, but DRuby looks like the practical solution to use when I
    decide I'd like to try something more substantial.
     
    Steve Lewis, Sep 7, 2008
    #5
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.