Tom Kerr
Tom Kerr

Reputation: 10720

Using SO_REUSEADDR with EventMachine (over UDP)

I am trying set socket options (SO_REUSEADDR specifically) on a UDP connection with EventMachine. As is, the code snippet works. When the second open_datagram_socket is uncommented, it will fail with this error:

eventmachine.rb:844:in `open_udp_socket': no datagram socket (RuntimeError)

From looking at the source, it looks like it just returns null if it fails, which isn't surprising. It seems like the socket option just isn't being set correctly, but I am not familiar with the library or ruby or socket programming to know if I am doing something wrong. I can't imagine the library just doesn't support something like this, but it's possible I guess.

How can I get SO_REUSEADDR to work with a datagram socket?

require 'eventmachine'

class PassThruServer < EM::Connection
    def initialize
        set_sock_opt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
    end
    def post_init()
        # too late?
#       set_sock_opt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
    end
    def receive_data(data)
        puts "PT: "+ data.to_s()
        send_datagram data, "localhost", 6060
    end
end

class MessagePrinter < EM::Connection
    def receive_data(data)
        puts "MP: "+ data.to_s()
    end
end

EM.run do
    # pass through
    EM.open_datagram_socket "localhost", 5050, PassThruServer
#   EM.open_datagram_socket "localhost", 5050, PassThruServer

    # test consumer
    EM.open_datagram_socket "localhost", 6060, MessagePrinter

    # test producer
    EM.open_datagram_socket "localhost", nil do |conn|
        i = 1
        EM.add_periodic_timer(3) do
            data = "message: "+ i.to_s() +"\n"
            conn.send_datagram data, "localhost", 5050
            i += 1
        end
    end
end

It looks like TCP might always uses SO_REUSEADDR. I don't see where the UDP even initializes the socket options. As far as I understand it, they would have to be set before the socket was actually opened?

I don't really grok ruby's C bindings to verify that I am looking in the right place however.

Upvotes: 0

Views: 1032

Answers (1)

Tom Kerr
Tom Kerr

Reputation: 10720

I was able to get the socket created with the below patch. I was not able to get the sockets to each receive a copy of the data however.

*** em.cpp  Tue Oct 25 10:52:22 2011
--- em_.cpp Tue Oct 25 10:51:38 2011
*************** EventMachine_t::OpenDatagramSocket
*** 1572,1577 ****
--- 1572,1578 ----
  const unsigned long EventMachine_t::OpenDatagramSocket (const char *address, int port)
  {
          unsigned long output_binding = 0;
+         int one = 1;

          int sd = socket (AF_INET, SOCK_DGRAM, 0);
          if (sd == INVALID_SOCKET)
*************** const unsigned long EventMachine_t::Open
*** 1606,1611 ****
--- 1607,1615 ----
                          goto fail;
          }

+         if (setsockopt (sd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)) < 0)
+                 goto fail;
+ 
          if (bind (sd, (struct sockaddr*)&sin, sizeof(sin)) != 0)
                  goto fail;

I don't think my use case has been considered by the authors of the library at all. The other socket option which needed to be set is IP_ADD_MEMBERSHIP. It could be hacked into the C layer rather easily I am guessing. I would be lost trying to expose it to the ruby layer, though. I think that is beyond my practical reach.

I suspect using the vanilla ruby library is more appropriate.

Upvotes: 1

Related Questions