Non blocking UDP


#1

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


#2

Leslie V. wrote:

does not seem to work at all (port never seems open). I need a UDP wait
Les
IO#select ?


#3

From: “Leslie V.” removed_email_address@domain.invalid

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


#4

On Wed, Jun 11, 2008 at 12:16 AM, Joel VanderWerf
removed_email_address@domain.invalid 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)


#5

On Wed, Jun 11, 2008 at 12:37 AM, Bill K. removed_email_address@domain.invalid 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


#6

Leslie V. 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?


#7

Bill K. 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…


#8

On Wed, Jun 11, 2008 at 12:52 AM, Joel VanderWerf
removed_email_address@domain.invalid wrote:

Bill K. 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 :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


#9

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