Forum: Ruby Non blocking UDP

37ee5fa90f5eaeef62553629382497f7?d=identicon&s=25 Leslie Viljoen (Guest)
on 2008-06-11 00:14
(Received via mailing list)
Hello!

I need to make a UDP server that waits for clients to log on and then
drives
communication from the server side. This means that if a client doesn't
ack
subsequent comms from the server, the server needs to resend those
comms a few times before giving up.

I have gotten as far as a successful UDP server, but it blocks the
thread
forever, and having a server constantly breaking out of
socket.recvfrom using Timeout
does not seem to work at all (port never seems open). I need a UDP wait
with a timeout.

I have looked at several UDP examples, and tried the sparse docs here:
http://ruby-doc.org/stdlib/libdoc/socket/rdoc/clas...

- but no luck yet. I tried recvfrom_nonblock as a guess but it seems to
block anyway.

Any tips?

Les
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2008-06-11 00:17
(Received via mailing list)
Leslie Viljoen wrote:
> does not seem to work at all (port never seems open). I need a UDP wait
> Les
IO#select ?
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2008-06-11 00:38
(Received via mailing list)
From: "Leslie Viljoen" <leslieviljoen@gmail.com>
> with a timeout.
>
> I have looked at several UDP examples, and tried the sparse docs here:
> http://ruby-doc.org/stdlib/libdoc/socket/rdoc/clas...
>
> - but no luck yet. I tried recvfrom_nonblock as a guess but it seems to
> block anyway.

Over the years, I've never been 100% successful getting nonblock
semantics to work with UDP sockets on ruby.  I end up with code like:


require 'timeout'
require 'fcntl'

UDP_RECV_TIMEOUT = 0.5  # seconds

# ...

     begin
     timeout(0.17) {
      sock = UDPSocket.open
      if defined? Fcntl::O_NONBLOCK
        if defined? Fcntl::F_GETFL
          sock.fcntl(Fcntl::F_SETFL, sock.fcntl(Fcntl::F_GETFL) |
Fcntl::O_NONBLOCK)
        else
          sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
        end
      end
      n = sock.send(cmd, 0, @server_addr, @server_port)
     }
     rescue Timeout::Error
       File.open("error.log", "a") {|f| f.puts "[#{Time.now}] q2cmd:
timeout in UDP open/send"}
     end

      if select([sock], nil, nil, UDP_RECV_TIMEOUT)
        begin
          # note, some linux kernel versions will select() positive for
a UDP
          # packet, but the packet has a bad checksum, and when we do
recvfrom()
          # the packet is thrown out, and we are blocked.  (I think, due
to ruby's
          # internals, even though we're setting NONBLOCK here, doesn't
help,
          # for some reason... i think this was explained on ruby-talk.)
          # Thus the 'timeout'.
          timeout(0.17) {
            resp = sock.recvfrom(65536)
          }
        rescue Timeout::Error
          $stdout.puts "q2cmd: Timeout::Error in sock.recvfrom !"
        end
      end


. . . and it still blocks occasionally, even though the socket is
in nonblocking mode.


I'm currently rewriting one of my UDP apps using eventmachine
http://rubyeventmachine.com/  ... which presumably will solve
the blocking issue.



Regards,

Bill
37ee5fa90f5eaeef62553629382497f7?d=identicon&s=25 Leslie Viljoen (Guest)
on 2008-06-11 00:40
(Received via mailing list)
On Wed, Jun 11, 2008 at 12:16 AM, Joel VanderWerf
<vjoel@path.berkeley.edu> wrote:
>>
>> block anyway.
>>
>> Any tips?
>>
>> Les
>
> IO#select ?

Below is my guess that doesn't work - I get connection refused when
trying to connect to the port.

Is there any proper documentation for these esoteric methods?
http://www.ruby-doc.org/core/classes/Kernel.html#M001109 doesn't help,
and even the Pickaxe ed. 3 gives not much more clue than what I tried.


#!/usr/bin/ruby -w

require 'socket'

class UdpServer
  def initialize(ip, port)
    socket = UDPSocket.new
    socket.bind(ip, port)

    loop do
      a = IO.select([socket], nil, nil, 5)
      p a
      sleep(1)
    end

  end
end

s = UdpServer.new("0.0.0.0", 7778)
37ee5fa90f5eaeef62553629382497f7?d=identicon&s=25 Leslie Viljoen (Guest)
on 2008-06-11 00:44
(Received via mailing list)
On Wed, Jun 11, 2008 at 12:37 AM, Bill Kelly <billk@cts.com> wrote:
>> I have gotten as far as a successful UDP server, but it blocks the thread
>
>
>     end
>         # packet, but the packet has a bad checksum, and when we do
>         $stdout.puts "q2cmd: Timeout::Error in sock.recvfrom !"
> the blocking issue.
Wow. I looked briefly at Eventmachine but got the idea it was for TCP
only.
If it can do UDP I'll definitely use that. Thanks a lot!

Les
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2008-06-11 00:51
(Received via mailing list)
Leslie Viljoen wrote:
> Below is my guess that doesn't work - I get connection refused when
> trying to connect to the port.

[~/tmp] cat serv.rb
require 'socket'

class UdpServer
   def initialize(ip, port)
     socket = UDPSocket.new
     socket.bind(ip, port)

     loop do
       a = IO.select([socket], nil, nil, 5)
         if a
           p socket.recvfrom(1000)
         end
     end

   end
end

s = UdpServer.new("0.0.0.0", 7778)
[~/tmp] cat clnt.rb
require 'socket'

s = UDPSocket.new
s.connect("0.0.0.0", 7778)
s.send("foo bar", 0)
[~/tmp] ruby serv.rb
["foo bar", ["AF_INET", 35088, "localhost", "127.0.0.1"]]
["foo bar", ["AF_INET", 35088, "localhost", "127.0.0.1"]]

(this shows output from running clnt.rb twice)

Does this still cause connection refused? Is it a firewall issue?
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2008-06-11 00:53
(Received via mailing list)
Bill Kelly wrote:
> I'm currently rewriting one of my UDP apps using eventmachine
> http://rubyeventmachine.com/  ... which presumably will solve
> the blocking issue.

Interested to hear how that goes....
37ee5fa90f5eaeef62553629382497f7?d=identicon&s=25 Leslie Viljoen (Guest)
on 2008-06-11 01:08
(Received via mailing list)
On Wed, Jun 11, 2008 at 12:52 AM, Joel VanderWerf
<vjoel@path.berkeley.edu> wrote:
> Bill Kelly wrote:
>>
>> I'm currently rewriting one of my UDP apps using eventmachine
>> http://rubyeventmachine.com/  ... which presumably will solve
>> the blocking issue.
>
> Interested to hear how that goes....

My issue is that I was using netcat without -u for UDP - I somehow do
these things 1am ;-)
But anyway, here's the eventmachine version which I am probably going
to use because
it is so very nice. Thanks for your example too!


require 'eventmachine'

module UmmpServer
  def post_init
    puts "client connected!"
  end

  def receive_data(data)
    p data
  end
end

EventMachine::run do
  EventMachine::open_datagram_socket('10.0.0.103', 7779, UmmpServer)
  EventMachine::add_periodic_timer(1) { puts "." }
end



Brilliant hey?

Les
37ee5fa90f5eaeef62553629382497f7?d=identicon&s=25 Leslie Viljoen (Guest)
on 2008-06-11 01:12
(Received via mailing list)
Here's the version where I actually send data back - note that I don't
even have
to explicitly mention the return IP and port as per usual:

require 'eventmachine'

module UmmpServer
  def post_init
    puts "client connected!"
  end

  def receive_data(data)
    p data
    send_data("thanks!\n")       #Eventmachine will make this
return-to-sender by default
  end
end


EventMachine::run do
  EventMachine::open_datagram_socket('0.0.0.0', 7779, UmmpServer)
  EventMachine::add_periodic_timer(1) { puts  }
end



Francis, thank-you so much for this awesome software!

Les
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.