Error in handle_info/2 when implementing a UDP echo server

Hi! I’m new to Elixir. I’m trying to implement a UDP echo server.

Here is my server code:

defmodule MyServer.UdpServer do
  use GenServer

  @port 8080

  def start_link(_) do
    GenServer.start_link(__MODULE__, nil)
  end

  def init(_) do
    {:ok, _socket} = :gen_udp.open(@port)
  end

  def handle_info({:udp, socket, ip, port, data}, state) do
    IO.puts(ip <> " " <> port)
    IO.puts(data)
    :gen_udp.send(socket, data)
    {:noreply, state}
  end
end

I started the server with iex -S mix.

When a message “Hello world!” sent by a nodejs client to the server, an error happened:
$ iex -S mix
Erlang/OTP 21 [erts-10.0.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.7.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
17:53:59.005 [error] GenServer #PID<0.173.0> terminating
** (ArgumentError) argument error
    :erlang.bit_size(51413)
    (my_server) lib/my_server/udp_server.ex:16: MyServer.UdpServer.handle_info/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:udp, #Port<0.5>, {192, 168, 2, 111}, 51413, 'Hello world!'}
State: #Port<0.5>

I could not find a similar issue by Google. Could someone help me? Thanks in advance.

The problem is in the line IO.puts(ip <> " " <> port). You are using the string concatenation operator (<>) on a mix of tuples, strings and integers.

If this is for debugging you can use inspect, which can display many data types:

IO.inspect [ip, port]

For pretty output, try:

IO.puts("#{:inet.ntoa(ip)} #{port}")

6 Likes

Kernel.<>/2 needs binaries on both sides, therefore ip <> " " <> port fails. To make it work you should use string interpolation and Kernel.inspect/2: "#{inspect ip} #{inspect port}".

4 Likes

@voltone @NobbZ

Thanks for your explanation. Very helpful. I thought ip is a string.:joy:

It’s very confused that the error doesn’t say anything about Kernel.<>/2

1 Like

Yeah, that’s because <> is a macro, which expands at compile time to <<ip::binary, " ", port::binary>>:

iex(1)> quote do
...(1)>   ip <> " " <> port
...(1)> end |> Macro.expand(__ENV__) |> Macro.to_string
"<<ip::binary, \" \", port::binary>>"

See Kernel.<<>>/1 for info on this syntax.

So at runtime, Erlang assumes the port variable contains a binary, and tries to determine the length of that binary. That’s the call to :erlang.bit_size/1 that fails.

It would also fail on the ip variable, but I guess the pattern gets evaluated right-to-left, so port fails first.

5 Likes

Thanks for your detailed explanation about the macro <>.:smile:

1 Like