0
votes

In order for nodes to find the master node on my local area network, I get the master node to broadcast a message (with it's IP address). It's working with Python, no problem, but with Elixir I get an "address in use error" when trying to open a broadcast socket. Herewith some Python code that works:

udplisten.py:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('', 8477))
while True:
    msg = s.recvfrom(1024)
    print(msg)

udpsend.py:

import socket 
import time
from datetime import datetime

cs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
cs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
cs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
while True:
    cs.sendto(str(datetime.utcnow()).encode(), ('255.255.255.255', 8477)) # broadcast my address!  
    time.sleep(0.5)

So udpsend.py simply broadcasts a time string on port 8477 and udplisten.py prints whatever it gets. There is no port "address in use" conflict when running this code, no matter which of the two programs is started first.

Now if I run udplisten.py and then try to open a UDP socket in Elixir:

tbrowne@calculon:~/Dropbox/code/elixir/xxmaster/lib/priv$ iex
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {:ok, sock} = :gen_udp.open(8477, [broadcast: true, reuseaddr: true])
** (MatchError) no match of right hand side value: {:error, :eaddrinuse}

I get the :error tuple with :eaddrinuse.

Note that I do not get this if udplisten.py is not running:

iex(1)> {:ok, sock} = :gen_udp.open(8477, [broadcast: true, reuseaddr: true])
{:ok, #Port<0.1291>}
iex(2)> 

However now my udplisten.py will not work:

tbrowne@calculon:~/Dropbox/code/elixir/xxmaster/lib/priv$ python udplisten.py
Traceback (most recent call last):
  File "udplisten.py", line 3, in <module>
    s.bind(('', 8477))
OSError: [Errno 98] Address already in use

So clearly something in my socket setup in Python needs to be setup in the same way in Elixir, but I can't seem to find the correct options in the gen_udp docs. How can I open a socket in Elixir for broadcast that will work the same as my udpsend.py routine in Python?

I am happy to accept an Erlang answer too.

1
To add to Dogbert's answer below - the number you send along to :gen_udp.open is the local port address. An UDP message has a sender and a destination and both are specified by an IP address and a port. You tried to bind your socket to the local port number 8477, which was a port that the Python script already was using; by sending "0" along , you tell the networking stack "give me a random port number". - cdegroot

1 Answers

2
votes

As explained here, with UDP, you don't "open" a connection to an address/port, you simply send data to an address/port. In Erlang this is done using :gen_udp.send/4 which accepts a socket, address, port, and data.

If I start python udplisten.py in one shell and run the following from another:

iex(1)> {:ok, socket} = :gen_udp.open(0, [broadcast: true])
{:ok, #Port<0.1338>}
iex(2)> :gen_udp.send(socket, '255.255.255.255', 8477, "hello!")
:ok

I get this printed in the first shell:

('hello!', ('127.0.0.1', 54182))