Erlang gen_tcp接受与操作系统线程接受

kupeojn6  于 2022-12-08  发布在  Erlang
关注(0)|答案(2)|浏览(142)

在Erlang中,我有两种侦听套接字和接收器模型:
第一个

-module(listeners).
....

start() ->
{ok, Listen}=gen_tcp:listen(....),
accept(Listen).

%%%%%%%%%%%%%%%%%%%%%

accept(Listen) ->
{ok, Socket}=gen_tcp:accept(Listen),
spawn(fun() ->handle(Socket) end),
accept(Listen).

%%%%%%%%%%%%%%%%%%%%%

handle(Socket) ->
....

-module(listener).
....

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

%%%%%%%%%%%%%

init([]) ->
{ok, Listen}=gen_tcp:listen(....),
spawn(fun() ->free_acceptors(5) end), 
{ok, {{simple_one_for_one, 5,1},[{child,{?MODULE,accept,[Listen]},....}]}.

%%%%%%%%%%%%%

free_acceptors(N) ->
[supervisor:start_child(?MODULE, []) || _ <-lists:seq(1,N)],
ok.

%%%%%%%%%%%%%

accept(Listen) ->
{ok, Socket}=gen_tcp:accept(Listen). 
handle(Socket). 

%%%%%%%%%%%%%%

handle(Socket) ->
....

第一段代码很简单,主进程创建一个listen socket并侦听接受新连接,当一个连接到来时,它接受这个连接并派生一个新进程来处理它并返回接受其他新连接。
第二个代码也很简单,主进程创建一个监督树,Supervisor创建一个侦听套接字并启动5个child(生成一个新进程以运行free_acceptors/1,因为此函数调用管理程序进程,而管理程序处于其init函数中,并且在启动自己的子进程之前无法启动子进程,因此新进程将等待管理程序,直到完成其初始化)并将listen套接字作为参数提供给它的child,五个child同时开始listen以接受新的连接。
因此,我们在一台单独的机器上运行这两个代码,这台机器具有单核CPU,5个客户端尝试同时连接到第一台服务器,另外5个客户端尝试同时连接到第二台服务器:首先,我认为第二个服务器速度更快,因为所有的连接都将被同时并行接受,在第一个代码中,第五个客户端将等待服务器接受,第四个客户端接受,依此类推。但深入到ERTS,每个内核有一个操作系统线程来处理Erlang进程,由于Socket是一种操作系统结构,因此gen_tcp:listen将调用OS-Thread:listen(这只是要理解的伪代码)来创建OS套接字,并且gen_tcp:accept调用OS-Thread:accept来接受新连接,并且这之后一次只能接受一个连接,并且第五个客户端仍然等待服务器接受第四个先例,那么这两个代码有什么区别呢?我希望你能理解我的意思。
即使代码不包括套接字,Erlang进程也将始终是并发的,而不是并行的,因为只有一个内核,但Sheduler将非常快速地管理进程之间的任务,并接近并行运行,因此问题在于使用套接字,这些套接字在单个操作系统线程上使用操作系统调用。
注意:Ejabberd使用第一个实现,Cowboy使用第二个实现。

qlzsbp2j

qlzsbp2j1#

At OS level, a listen socket has associated a queue of OS-threads waiting to accept connections, regardless of whether this queue has any OS-thread blocked on it or is empty because it will be handled differently (busy-waiting non-blocking accept, select, epoll...).
The BEAM does not have a single OS-thread even if you run it on a system with a single CPU, it has different types of OS-threads
Regarding your question I suspect that it will be, if anything, better to have multiple acceptor erlang-threads continuously blocking on the gen_tcp:accept call because that way the ERTS has knowledge about the erlang code willing to accept more connections (the handle(Socket) in your second example should spawn a worker or send the accepted socket to a worker and get back to accept connections) while with the single accept-spawn loop this knowledge is hidden.
I'm not familiar enough with the code to know the nuances, but it seems that the code handles multiple accepts nicely, queueing them internally, so it might be marginally better to have multiple acceptors.
I.e. In the first example with a single request there is a moment where there is nobody accept ing connections, while you need a higher number of simultaneous request in the second example for this to happen.

xwbd5t1u

xwbd5t1u2#

iam cumming from lot of search about, so i think that i found the answer for that, but i want just to correct me Mr José if i have wrong in anything :
1-when we run gen_tcp:listen the ERTS opens an ERLANG port (a listen socket) to communicate with a linked-in C driver, this driver which runs under a MAIN OS-Thread opens a REAL SOCKET.
2-when we run gen_tcp:accept the ERTS use this port to call the driver using a Specified Macro as an argument to the function erlang:port_control , the driver MAIN OS-Thread will spawn an OS-Thread that will run a REAL accept at the opened Socket(Blocking Accept) but this is just my view me too i'm not familiar with the C Accept function, anyway this is the Ericsson's Team job.
3-when a client send a request to connect to this Socket, the OS-Thread accept the connection and create a new Socket of communication with this client and the Erlang process creates a new Port and link it to this OS-Thread to be the driver for this specified communication with that client.
4-when the Erlang process send Data via this new port the new driver send this Data via the new Socket, and the same with Receive Data.
5-The MAIN OS-Thread driver will not spawn a new OS-Thread at each Erlang Accept and will do a balance between OS-Threads and connections (again this is the Ericsson Design) and these Threads will manage connections with one of the known functions (select, poll, epoll,.....) and generally it's epoll for Linux and Kqueue for Bsd Systems and each OS-Thread will run this function at dual sides :one side to interact with clients sockets and one side to interact with Erlang ports.
this is the exact work of any driver, it hidden things and let's the Emulator behaves like it does the work directly.
the answer to the first question is that the second code is more efficient and like you have tell me, when there are many Erlang-Acceptors the Driver knows about this and spawns many OS-Acceptors, here there is another problem: how many acceptors i can spawn for a socket ?
the free acceptors design is for accepting connections in PARALLEL and it's clear that one OS-Thread can't accept two connections at the same time so if the number of acceptors is larger than the number of cores for example if we have 8 cores and 10 acceptors and 20 clients came at the same time, we have 8 accepted connections in parallel and next another 8 in parallel and next 4 so this has the same efficiency that we create 8 free acceptors.( i talk about correct version of code when we have always 8 free acceptors and when an acceptor accept a connection it spawns a process to handle this connection and return to accept other connections)
Networking is the most important part in designing fault-tolerant and scalable servers in Erlang/OTP and i want to understand it well before doing anything so Please Mr José if iam wrong in something just tell me Thank you.

相关问题