Non blocking UDP

L

Leslie Viljoen

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
 
J

Joel VanderWerf

Leslie said:
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 ?
 
B

Bill Kelly

From: "Leslie Viljoen said:
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
 
L

Leslie Viljoen

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)
 
L

Leslie Viljoen

From: "Leslie Viljoen said:
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.

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
 
J

Joel VanderWerf

Leslie said:
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?
 
L

Leslie Viljoen

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::eek:pen_datagram_socket('10.0.0.103', 7779, UmmpServer)
EventMachine::add_periodic_timer(1) { puts "." }
end



Brilliant hey?

Les
 
L

Leslie Viljoen

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::eek:pen_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
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top