Non blocking UDP

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/classes/Socket.html#M004528

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

Any tips?

Les

Leslie Viljoen wrote:

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/classes/Socket.html#M004528

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

Any tips?

Les

IO#select ?

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

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/classes/Socket.html#M004528

- 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

···

From: "Leslie Viljoen" <leslieviljoen@gmail.com>

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?
module Kernel - RDoc Documentation 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)

···

On Wed, Jun 11, 2008 at 12:16 AM, Joel VanderWerf <vjoel@path.berkeley.edu> wrote:

Leslie Viljoen wrote:

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/classes/Socket.html#M004528

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

Any tips?

Les

IO#select ?

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

···

On Wed, Jun 11, 2008 at 12:37 AM, Bill Kelly <billk@cts.com> wrote:

From: "Leslie Viljoen" <leslieviljoen@gmail.com>

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/classes/Socket.html#M004528

- 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.

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....

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

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"]]

(this shows output from running clnt.rb twice)

Does this still cause connection refused? Is it a firewall issue?

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

My issue is that I was using netcat without -u for UDP - I somehow do
these things 1am :wink:
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

···

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....

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