:gen_tcp.recv returns only the 1st line of a response

When I connect to an email server via telnet and send it “helo abc”, it return 1 line. When I send i

    options = [:binary, :inet, active: false, packet: :line]
    {:ok, sock} = :gen_tcp.connect('some_email_server', 587, options)
    case :gen_tcp.recv(sock, 0) do
      {:ok, data} ->
        IO.puts(data)

      {:error, data} ->
        IO.puts(Kernel.inspect(data))
    end

    :gen_tcp.send(sock, "EHLO client.example.com\r\n")
    case :gen_tcp.recv(sock, 0) do
      {:ok, data} ->
        IO.puts(data) # <----- only the 1st line of response

      {:error, data} ->
        IO.puts(Kernel.inspect(data))
    end

After “ehlo” it in reality wil return a multiline response, I’ve tested that with telnet. However, ‘data’ contains only the 1st line.

  1. Why?

  2. How to retrieve all the lines? How do I know when there’s more data to be fetched with ‘:gen_tcp.recv’?

You have to loop on recv until you’ve got a complete message. Typically you’d do that with a recursive function. It receives the data, adds it to what has already been received, then checks to see if it is a complete command. If so it dispatches that to whatever logic is consuming it, otherwise it calls itself passing the accumulated data and blocks on recv again. You’d also want to be counting down with some kind of timeout.

2 Likes

Here you say, that packets shall be exactly one line.

Here, you receive exactly one “packet”.

Be aware that a “line” in gen_tcp (and most other similar IO abstractions on the BEAM) means LF only, not CRLF as you are using it.

As @jeremyjh already pointed out, recursion is the way to go:

def recv_loop(sock) do
  case :gen_tcp.recv(sock, 0) do
    {:ok, line} ->
      IO.puts(data)
      recv_loop(sock)

    {:error, reason} ->
      IO.puts(inspect(reason))
  end
end

PS: is there any reason why you call reason |> inspect |> IO.puts rather than IO.inspect(reason)?

1 Like

that’s what I asked – “how”?

and that’ll get stuck right after that 1st command it sends, because “wait indefinitely”

It waits for you closing the connection or sending the next line(s).

and if there’s no line, it’ll get stuck

**no new line

This is protocol dependent. You have to parse that data you’ve received and see if it is complete. That means in this case, that it depends on when you get a complete SMTP command/message. You need to read the SMTP standard and implement it.

You need to understand that when you are working at the TCP socket level, you are working at a much lower level than is typically done in application programming. You are implementing a network protocol at the OSI application level.

That is why I mentioned that you need to implement a timeout. You can pass a timeout value to :gen_tcp.recv.

1 Like

Is there a reason you want to use active: false ? It would be easier using active: true and relying on messages.

See
https://learnyousomeerlang.com/buckets-of-sockets

active: true will take messages from the wire as they are available and put them into your mailbox, so if you are not emptying your mailöbox fast enough, you will stress your network stack needlessly and your processes mailbox will flow. TCP has no way to apply backpressure and reduce used bandwith.

Therefore its often suggested to not rely on active: true for everything but only while waiting for waiting for the next communication

2 Likes

True, under load I prefer active: once, false is a good first stab for proof-of-concept. And if your process isn’t under that much load its good enough.

1 Like