Erlang udp 工作人员 - 希望在同一端口上有更多的 udp 侦听器

Sim*_*mon 1 erlang udp pattern-matching actor

我正在 Erlang 中制作一个游戏服务器,我希望能够为每个连接的客户端拥有一个进程。该进程应该负责处理客户端发送到服务器的所有 UDP 消息,处理这些消息并在必要时进行回复。

我该如何实现这一目标?我知道使用 TCP 我可以放置任意数量的侦听器来运行该函数

gen_tcp:accept(ListenSocket)
Run Code Online (Sandbox Code Playgroud)

它会阻塞工作人员,直到有工作可用,等等。我也希望 udp 具有这种行为,但我一直在尝试制作一个简单的示例,该示例会生成一个新进程,该进程应该根据其 ip 和每个 udp 数据包来匹配端口,查看哪个玩家发送了它,将其占为己有并忽略其余的。

loop({Sock, Ip, Port}) ->
    receive
        {udp, Sock, Ip, Port, Msg} ->
            do_stuff & loop, etc.
    end
end.
Run Code Online (Sandbox Code Playgroud)

这与从循环到消息的 Ip 匹配吗?为了实现这一点,我需要任何其他 UDP 侦听器来与该特定数据包不匹配,以便工作程序在任何给定时间获取它应该获取的每条消息,对吗?如果另一个“通用”UDP 侦听器侦听,它可能会在工作人员之前拾取消息。

所以我问这种方法是否有效,或者是否会收到错误消息,指出它不是套接字的所有者或类似的内容。到目前为止,我还没有让任何工作人员实际接收任何消息,所以我不确定这是因为不匹配还是因为套接字只向当前控制进程发送消息,我想这会使我的计划变得不可能。

Pau*_*yot 5

您的问题的标题以及措辞表明您正在尝试使用 TCP 等 UDP 套接字。

TCP和UDP是不同的协议

TCP 是一种面向连接的协议。客户端连接到服务器,给定的服务器每个连接将有一个套接字,另外还有一个用于侦听。操作系统的网络堆栈将传入数据包路由到侦听套接字或正确连接的套接字。

UDP 是无连接的。您的程序将只有一个 UDP 套接字。它将接收到给定端口的所有 UDP 数据包。您无法生成一个进程并让它在同一端口上侦听。所以你不能在同一端口上有更多的 udp 侦听器在标题中所写的

考虑 Erlang 的实现

TCP 的常见设计模式是让一个进程处理侦听套接字,为每个连接的套接字生成新的工作进程。

由于您尝试使用 UDP 实现类似的功能,因此您需要执行网络堆栈使用 TCP 为您所做的操作。您可以让一个进程接收数据包并将其转发到正确的工作进程,并在需要时根据源端口和 IP 地址生成。这个过程应该维护一个所有活动“连接”的表,即所有活动的数据包“流”。

另请注意,UDP 没有关闭机制,因此您应该依赖超时,或者可能在协议中使用附加机制。

正如您在注释中建议的那样,不可能对每个进程上的单个套接字进行过滤,因为活动模式下的 Erlang 套接字将数据包发送到单个进程(控制进程)。您可以想象某种架构,其中数据包被广播到所有进程,每个进程都进行过滤以选择感兴趣的数据包。

但是,您会发现这在您的特定场景中没有意义,因为您还需要一种机制来确定是否应该生成工作进程,并且该机制最终会告诉您哪个进程应该处理传入的数据包。如果多个工作人员要处理同一条消息,这可能是有意义的。

让我们用一些代码来具体看看:

server_loop(Workers) ->
    receive
        {udp, Sock, Ip, Port, Msg} = UDPPacket ->
            % find out if we need to spawn a new worker.
            % typically, Workers is a gb_trees:tree().
            NewWorkers = case gb_trees:lookup({Ip, Port}, Workers) of
                none ->
                    NewWorkerPid = spawn_link(fun() -> worker_loop(Ip, Port) end),
                    gb_trees:insert(NewWorkerPid, Workers);
                {value, _WorkerPid} -> Workers  %% <- look, we have the worker!
            end,
            % broadcast to all workers.
            lists:foreach(fun({_, Worker} ->
                Worker ! UDPPacket
            end, gb_trees:to_list(NewWorkers)),
            server_loop(NewWorkers);
        % ... timeout callbacks from workers would go here
    end.
Run Code Online (Sandbox Code Playgroud)

这只是上面暗示的一个过于复杂的版本,每次数据包都会发送到正确的工作人员。

server_loop(Workers) ->
    receive
        {udp, Sock, Ip, Port, Msg} = UDPPacket ->
            % find out if we need to spawn a new worker.
            % typically, Workers is a gb_trees:tree().
            {NewWorkers, Worker} = case gb_trees:lookup({Ip, Port}, Workers) of
                none ->
                    NewWorkerPid = spawn_link(fun() -> worker_loop(Ip, Port) end),
                    {gb_trees:insert(NewWorkerPid, Workers), NewWorkerPid}
                {value, WorkerPid} -> {Workers, WorkerPid}
            end,
            % send to worker.
            Worker ! UDPPacket,
            server_loop(NewWorkers);
        % ... timeout callbacks from workers would go here
    end.
Run Code Online (Sandbox Code Playgroud)