30 分钟用 Ranch 搭建 FTP 服务器

原文:Nine Nines: Build an FTP Server with Ranch in 30 Minutes

本文的目的是展示如何使用 Ranch 编写网络协议实现,Ranch 如何让您编写重要的代码,以及编写服务器时使用的常用技术。

让我们从创建一个空项目开始。 创建一个新目录,然后在该目录中打开一个终端。 第一步是将 Ranch 添加为依赖项。 创建 rebar.config文件并添加以下 3 行。

{deps, [
    {ranch, ".*", {git, "git://github.com/extend/ranch.git", "master"}}
]}.

这使您的应用程序依赖于 master 分支上可用的最后一个 Ranch 版本。 这对于开发来说很好,但是当您开始将应用程序推送到生产环境时,您将需要重新访问此文件以硬编码您正在使用的确切版本,以确保您在生产环境中运行相同版本的依赖项。

您现在可以获取依赖项。

$ rebar get-deps
==> ranch_ftp (get-deps)
Pulling ranch from {git,"git://github.com/extend/ranch.git","master"}
Cloning into 'ranch'...
==> ranch (get-deps)

这将创建一个 deps/ 包含 Ranch

我们实际上不需要任何其他东西来编写协议代码。 我们可以为它创建一个应用程序,但这不是本文的目的,所以让我们继续编写协议本身。 创建文件 ranch_ftp_protocol.erl 并在您喜欢的编辑器中打开它。

$ vim ranch_ftp_protocol.erl

让我们从一个空白的协议模块开始。

-module(ranch_ftp_protocol).
-export([start_link/4, init/3]).

start_link(ListenerPid, Socket, Transport, Opts) ->
    Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport]),
    {ok, Pid}.

init(ListenerPid, Socket, Transport) ->
    io:format("Got a connection!~n"),
    ok.

当 Ranch 收到一个连接时,它会调用 start_link/4函数与侦听器的 pid、套接字、要使用的传输模块以及我们在启动侦听器时定义的选项。 出于本文的目的,我们不需要选项,因此我们不会将它们传递给我们正在创建的流程。

让我们打开一个 shell 并启动一个 Ranch 侦听器以开始接受连接。 我们只需要调用一个函数。 您可能应该在另一个终端中打开它并为方便起见保持打开状态。 如果您退出 shell,您将不得不重复这些命令才能继续。

另请注意,您需要输入 c(ranch_ftp_protocol).重新编译并重新加载协议的代码。 但是,您不需要重新启动任何进程。

$ erl -pa ebin deps/*/ebin
Erlang R15B02 (erts-5.9.2) [source] [64-bit] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.9.2  (abort with ^G)

1> application:start(ranch).
ok
2> ranch:start_listener(my_ftp, 10,
       ranch_tcp, [{port, 2121}],
       ranch_ftp_protocol, []).
{ok,<0.40.0>}

这将启动一个名为 my_ftp运行你自己的 ranch_ftp_protocol通过 TCP,监听端口 2121. 最后一个参数是我们之前忽略的协议的选项。

要尝试您的代码,您可以使用以下命令。 应该可以连接,服务器会在控制台打印一条消息,然后客户端会打印一个错误。

$ ftp localhost 2121

让我们继续实际编写协议。

一旦您创建了新进程并返回了 pid,Ranch 将把套接字的所有权交给您。 不过,这需要一个同步步骤。

init(ListenerPid, Socket, Transport) ->
    ok = ranch:accept_ack(ListenerPid),
    ok.

现在您已确认新连接,您可以安全地使用它。

当 FTP 服务器接受连接时,它首先发送一条欢迎消息,该消息可以是一行或多行以代码开头 200. 然后服务器将等待客户端对用户进行身份验证,如果身份验证成功(本文将始终这样做),它将回复 230代码。

init(ListenerPid, Socket, Transport) ->
    ok = ranch:accept_ack(ListenerPid),
    Transport:send(Socket, <<"200 My cool FTP server welcomes you!\r\n">>),
    {ok, Data} = Transport:recv(Socket, 0, 30000),
    auth(Socket, Transport, Data).

auth(Socket, Transport, <<"USER ", Rest/bits>>) ->
    io:format("User authenticated! ~p~n", [Rest]),
    Transport:send(Socket, <<"230 Auth OK\r\n">>),
    ok.

如您所见,我们不需要复杂的解析代码。 我们可以简单地匹配参数中的二进制!

接下来,如果我们希望我们的服务器变得有用,我们需要循环接收数据命令并选择性地执行它们。

我们将更换 ok.符合对以下函数的调用。 新函数是递归的,每次调用都从套接字接收数据并发送响应。 现在,我们将为客户端发送的所有命令发送错误响应。

loop(Socket, Transport) ->
    case Transport:recv(Socket, 0, 30000) of
        {ok, Data} ->
            handle(Socket, Transport, Data),
            loop(Socket, Transport);
        {error, _} ->
            io:format("The client disconnected~n")
    end.

handle(Socket, Transport, Data) ->
    io:format("Command received ~p~n", [Data]),
    Transport:send(Socket, <<"500 Bad command\r\n">>).

有了这个,我们几乎准备好开始执行命令了。 但是对于这样的代码,如果客户端没有为每个数据包仅发送一个命令,或者数据包到达速度太快,或者命令被拆分为多个数据包,我们可能会遇到错误。

为了解决这个问题,我们需要使用缓冲区。 每次我们接收到数据时,我们都会追加到缓冲区中,然后在运行之前检查我们是否已经完全接收到命令。 代码可能类似于以下内容。

loop(Socket, Transport, Buffer) ->
    case Transport:recv(Socket, 0, 30000) of
        {ok, Data} ->
			Buffer2 = << Buffer/binary, Data/binary >>,
			{Commands, Rest} = split(Buffer2),
			[handle(Socket, Transport, C) || C <- Commands],
            loop(Socket, Transport, Rest);
        {error, _} ->
            io:format("The client disconnected~n")
    end.

实施 split/1留给读者作为练习。 您可能还想处理 QUIT命令,它必须停止任何处理并关闭连接。

细心的读者还会注意到,在基于文本的协议中,命令由换行符分隔,您可以使用 Transport:setopts/2并由 Erlang 自己免费为您完成所有缓冲。

正如您现在肯定注意到的那样,Ranch 允许我们通过完全超越初始设置来构建网络应用程序。 它允许您使用二进制模式匹配的强大功能,只需几行代码即可编写文本和二进制协议实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值