0

Hi,im a beginner using Elixir to write a chatroom project, and I've been stuck on setting up the connection between client and server. My plan is to write 2 modules for client and server and had them apply all their functions within their individual module. From the client side, its no more than setting up the socket connection and other send/receive functions from gen_tcp. For the server I implemented a handle_client function after accpeted connection to new client, and take the first message as username and connection signal. Here's the code:

  # send welcome message to affirm connection, loop handler to listean and broadcast user's input
  defp handle_client(socket) do
    send_message(socket, "Welcome to the chat room!\nPlease enter your username: ")
    Logger.info("handle_client: on listen client 1")
  
    # successfully receive, other case err message
    case recv(socket) do
      {:ok, username} ->
        Logger.info("#{username} joined the chat")
        broadcast_message("#{username} joined the chat")
        Logger.info("handle_client: on listen to client 2")
  
        # keep listening to user input
        loop(socket, username)
      {:error} ->
        Logger.info("Disconnected from the client")
    end
  end

Notice here that I warpped the recv/2 from gen_tcp to a custom recv/1, technically they do the samething except in case of error, I only return the atom {:error} instead of captureing the whole err message. Here if it's a successful return, then it will recognize the username and open the thread to that client.

Here's a little bit into the recv/1:

  defp recv(socket) do
    Logger.info("recv: on listen to client 1")
    case :gen_tcp.recv(socket, 0) do
      {:ok, data} -> {:ok, String.trim(data)}
      ///not included///Logger.info("recv: message as #{data} [case :ok]")
      {:error, _reason} -> {:error}
      ///not included///Logger.info("recv: message as #{_reason} [case :error]")
    end
    Logger.info("recv: on listen to client 2")
  end

Edit: sorry about the confusion but these debug lines are neither the actual bug nor included in the actual code.

You may ignore the debug log I added between, the way it perform is just as similar to recv/2. Here if I enter the username message from client, it will pass it into {:ok, data} and return to the case...do part in handle_client/1.

Here goes the problem: I tried to run the code and even if it successfully captured the username message, a CaseClauseError still occurs in the handle_client/1 where it says no matching for :ok

iex(2)>
05:58:28.083 [error] Process #PID<0.152.0> raised an exception
** (CaseClauseError) no case clause matching: :ok
    (chatroom 0.1.0) lib/chat_server.ex:37: ChatServer.handle_client/1

Which is very confusing cause technically I think it should be passing the correct tuple instead of a single :ok atom, where I actually have the message correctly captured: debug log

Please tell me if you have any clue to the possible reason of it, and I will sincerely appreciate your help. If you think more information is needed I have very happy to discuss with you under the comment.

[wait what is this, I think I've included all the stuff above?]

zhy21345
  • 1
  • 1
  • 1
    Be careful of Elixir's implicit returns. It looks like the final statement in your `recv/1` function is actually `Logger.info()`, which returns an `:ok`, when I think you meant to return `{:ok, String.trim(data)}`. Instead, you might try using `IO.inspect/2` for debugging because it will pass everything through. – Everett Jul 20 '23 at 02:21
  • Yes as Everett said here you are passing the return value of Logger.info("") which is :ok, it's simple to fix ``` defp recv(socket) do Logger.info("recv: on listen to client 1") response = case :gen_tcp.recv(socket, 0) do {:ok, data} -> Logger.info("recv: message as #{inspect(data)} [case :ok]") {:ok, String.trim(data)} {:error, reason} -> # if you have reason marked as underscored then you can not use it Logger.info("recv: message as #{reason} [case :error]") {:error} end Logger.info("recv: on listen to client 2") response end ``` – Yatender Singh Jul 20 '23 at 07:30

1 Answers1

0

Yes as Everett said here you are passing the return value of Logger.info(""recv: on listen to client 2") which is :ok, it's simple to fix

defp recv(socket) do
    Logger.info("recv: on listen to client 1")
    response = 
        case :gen_tcp.recv(socket, 0) do
            {:ok, data} -> 
                Logger.info("recv: message as #{inspect(data)} [case :ok]")
            {:ok, String.trim(data)}
            {:error, reason} -> 
                # if you have reason marked as underscored then you can not use it
                Logger.info("recv: message as #{reason} [case :error]")
                {:error}
        end
        Logger.info("recv: on listen to client 2")
    response
end
Yatender Singh
  • 3,098
  • 3
  • 22
  • 31
  • @Everett sry I forgot to explain in the question, but these debug stuff are just one-time test to make sure which case it did fall into. Even if I delete them, the CaseClauseError still exists with :ok. Which is very confusing cause you know the original way I wrote them was like {:ok, data} -> {:ok, String.trim(data)} without any other stuff. If you have interests in further discussion, plz tell me and I can show you more code or debugging log if you would like to know. – zhy21345 Jul 20 '23 at 20:24
  • sure go ahead, describe more please. – Yatender Singh Jul 21 '23 at 06:56
  • yea like I said before, the case was '{:ok, data} -> {:ok, String.trim(data)}' and '{error, reason} -> {:error}'. I was pretty sure the recv/2 from gen_tcp did make a successfull get and captured the data, but the case do statement didn't fall into ‘{:ok, String.trim(data)}’. And this error specifically happens inside the client_handle/1, which I tried to test the state of recv/1 returns and as the error message suggests, only returned a :ok. Sorry about the debug lines that looks confusing, but the error happened before I add them there – zhy21345 Jul 21 '23 at 20:13
  • Sorry for late reply, did you try printing with normal `IO.inspect(data)` before you trim it? Also try to put a default case after `{:error, reason}`, just put `data -> IO.inspect(data) :ok ` and see if pattern matches there – Yatender Singh Jul 26 '23 at 07:51