首页 > 解决方案 > 一个监听多个进程的套接字

问题描述

我有一个非常简单的问题,我想创建 20 个子进程,每个子进程都有相同的监听套接字,有 2 种方法我只想知道两者之间有什么区别:

module(sup). 
.....
start() ->
supervisor:start_link({local,?MODULE},?MODULE,[]).
%%%%%%%%%%%%%%%%%%

--------第一种方法------

init([]) ->
Listen=gen_tcp:listen(....),
spawn(fun() ->start_children(20) end), 
{ok,{{simple_one_for_one,5,1},[{child,{ChildModule,start,[Listen]},....}]}}.
%%%%%%%%%%%%%%%%%
start_children(N) ->
[supervisor:start_child(?MODULE, [])||_ <-lists:seq[1,N]], 
ok. 

这是一个 simple_one_for_one 树,我只是创建一个侦听套接字并将其设置为每个启动的进程的参数,这些进程将在稍后处理它,我产生了一个新进程来运行start_children/1,因为这个函数调用了主管,后来它在它的init/1函数中并且它在它自己开始之前无法启动子进程,因此该进程将等待 sup 的启动来调用它,让我们看看第二种方法:---------第二种方法---------

init([]) ->
ChildSpecs=[{Id,{ChildModule,start,[fun createListenSocket/0]},....}||Id <-lists:seq[1,20]] 
{ok,{{one_for_one,5,1},ChildSpecs}}.
%%%%%%%%%%%%%%%%%%%%
createListenSocket() ->
gen_tcp:listen(....).

这是一个 one_for_one 树,sup 在开始时创建了 20 个孩子,有 20 个套接字:每个孩子一个套接字,所以问题是:这两种方法是相同的还是不同的?如果我们认为它们是相同的,这意味着侦听套接字只是一个变量,并且套接字中的特殊事物(侦听传入连接)在我们运行时开始gen_tcp:accept/1。因为如果不是,我们会遇到第一种方法中 20 个进程共享同一个侦听套接字的情况。

编辑 :

好的,我认为 José 已经回答了我的问题,但他的回答给了我另一个问题:如何在 Erlang 中创建多个具有相同端口和 IP 地址的套接字?因为如果我想为每个节点运行 20 个套接字,则 ip 是本地 ip 地址,并且所有套接字都相同,并且端口也相同,如果我只需要一个指定的应用程序端口?有一个选项{reuseaddr, true}作为参数,gen_tcp:listen但它只能在我们为不同的 IP 地址使用相同的端口并且reuseport在 Erlang 中没有时才可以使用,那么该怎么做呢?

标签: socketstcperlangelixirerlang-otp

解决方案


免责声明:这个答案是关于 Linux 的。

不,这两种方法不一样,监听套接字是操作系统结构。一个创建一个侦听套接字,另一个创建 20 个侦听套接字(或者至少尝试这样做,因为最后 19 个将失败,除非您启用端口重用。您在这个问题中对此选项有一个很好的解释)。

您可以检查监听套接字sudo ss -punta | grep LISTEN

在具有多个侦听套接字的操作系统级别,连接的负载平衡由内核执行,忽略同级套接字是否有任何操作系统线程等待accept. 因此,在拥有单个侦听套接字时,您可能会在接受队列中遇到争用,而拥有多个侦听套接字时,您可能会看到延迟差异,因为一个 OS 线程具有更多负载。(在此 cloudflare 博客条目中有详细说明)

编辑:

reuseport仅适用于新的低级套接字接口,如果您使用gen_tcp,则仅reuseaddr可用。

话虽如此,请记住,当您在 erlang 中时,您是上面的一层:阻塞gen_tcp:accept不需要是(而且很可能不是 *) a OS-level blocking accept。同样,我之前提到的 OS 线程不是 erlang 线程,而是 BEAM 调度程序。在大多数情况下,拥有一个监听套接字就可以了。

*由于依赖于 erlang 代码的调度程序(OS 线程)和任意数量的套接字数量有限,因此blocking accept使用epoll.


推荐阅读