Tcp file transfers - feedback

erlang
gen-tcp
#1

Hi all,

I’ve been reading through “Programming Erlang” and got interested in one of the exercises for writing Yet Another File Server. So I’m doing that in a very naive, only basic processes primitives (except gen_tcp) way and all is fine, it’s for learning. But I would like to know how usually one deals with the file transfer info.

Right now I have something like this:
#client_server.erl

maybe_copy_file(Client, File) ->
    case not filelib:is_dir(File) and filelib:is_file(File) of
        true -> copy_file(Client, File);
        _ ->
            io:format("invalid file path: ~p~n", [File]),
            loop(Client)
    end.

copy_file(#client{socket= Socket} = Client, File) ->
    Filename = filename:basename(File),
    {ok, #file_info{size= Size}} = file:read_file_info(File),
    Bt = term_to_binary({Filename, Size}),
    gen_tcp:send(Socket, "sendfile:" ++ Bt),
    file:sendfile(File, Socket),
    loop(Client).

Where I read the file size, then term_to_binary that info, and send, first a message with sendfile:binary_term_info

Then on the #file_server.erl I do, after receiving that first message:

file_receive(#server_client{socket= Socket, fs= #dir_struct{cwd= Cwd}} = Client, Info) ->
    {Filename, Size} = binary_to_term(Info),
    Path = filename:absname_join(Cwd, Filename),
    {ok, FileHandle} = file:open(Path, [write, binary]),
    receive_file(Socket, FileHandle, Size),
    handle(Client).

receive_file(_, FileHandle, 0) ->
    io:format("Finished Receiving File"),
    file:close(FileHandle);
receive_file(Socket, FileHandle, Size) ->
    {N_Size, ToRead} = case Size - 500 of
                           N when N >= 0 -> {N, 500};
                           N -> {0, 500 + N}
                       end,
    case gen_tcp:recv(Socket, ToRead) of
        {ok, Data} ->
            io:format("~ndata: ~p~n", [Data]),
            case file:write(FileHandle, Data) of
                ok -> receive_file(Socket, FileHandle, N_Size);
                {error, Error} ->
                    io:format("Error Writing ~n~p~n", [Error]),
                    file:close(FileHandle)
            end;
        {error, Reason} ->
            io:format("Received finished with: ~p~n", [Reason]),
            file:close(FileHandle)
    end.

So with the file size I sent earlier I just read chunks from the socket until I’ve read the whole amount. This works fine, but that part looks ugly. I’m wondering how does one usually encode this information (size & filename for instance) in a regular tcp conversation? Is there any protocol that is commonly used? Or does everyone just write their own protocol and handling semantics? Also, what plays a role in deciding the size of the “chunks” to be read from the socket?

I’m keeping the socket connection open on both ends even after the transfer finishes, otherwise I could just not worry about the size and once closed it would mean the transfer was done. I had started with gen_tcp:recv(Socket, 0), but then I can not discern when the file is finished?

Any info/samples would be welcomed as well (about either protocols themselves, or done in elixir or erlang).