Simple chat with Async.Tcp module

Hi,

I just picked up OCaml and been playing around with the Async TCP module. I’m trying to build a server/client that chat with each other over a TCP connection. (Similar to the behavior of netcat when you open client and server)

This is the function that starts the server, listening on port p.

let start_server p =
    let host_and_port =
         Tcp.Server.create
         ~on_handler_error:`Raise
         (Tcp.Where_to_listen.of_port p)
         (fun sock reader writer ->
             conn_handler sock reader writer)
    in
    ignore (host_and_port : (Socket.Address.Inet.t, int) Tcp.Server.t Deferred.t);
    Deferred.never ()

This is the function that starts the client, connecting to an address, a and port p.

let start_client a p =
    Tcp.with_connection
    (Tcp.Where_to_connect.of_host_and_port { host = a; port = p})
    (fun sock reader writer ->
        conn_handler sock reader writer)

As you can see from the code snippets, both call the function, conn_handler after server is started/client is connected.

My logic behind conn_handler: client/server has to simultaneously:

  1. Read from stdin and write to the socket using w,
  2. Read from r, print to stdout and write to the socket “Acknowledged”.

Current implementation of conn_handler:

let rec conn_handler s r w =`
    let stdin = Lazy.force Reader.stdin in
    Reader.read_line stdin >>= function
    | `Eof -> return ()
    | `Ok x ->
        Writer.write_line w x;

    Reader.read_line r >>= function
    | `Eof  -> return ()
    | `Ok "exit" -> return ()
    | `Ok x -> 
        print_endline x;
        Writer.write_line w "Acknowledged";
        conn_handler s r w

This implementation allows both client & server to send message to each other, but the behavior is buggy, probably have to do with the logic behind when Deferreds are determined.

Is my logic wrong? Should I implement separate connection handler functions for server and client?
If not, how do i modify the above conn_handler to achieve (1) and (2) simultaneously?

I assume by buggy you mean your conn_handler, as written, isn’t reading from the socket until after you’ve read from stdin.

That’s because you’re specifically sequencing, with the >>= operator, for the read from socket to happen after the read from stdin.

If you want to watch both stdin and the reader at the same time and react to either, you can try Deferred.choose. See this recent thread Beginner async question

For the sake of future generations, I thought I’d mention that the poster seems to have dual asked this question on r/ocaml and received a more detailed answer. https://www.reddit.com/r/ocaml/comments/ej1niq/help_with_writing_a_tcp_server_with_async/

1 Like