Erlang socket 工作原理

原文

由于对并发的原生支持,erlang在很多时候被用来构建网络服务器(socket server, http server...)的底层,处理大量的并发连接。理解erlang在底层如何处理socket,以及如何与自己的并发机制结合,对于使用erlang来构建网络服务至关重要。

erlang的对socket的处理被分为两个部分。

Port Driver的中的处理

基本的io模型

在《Erlang Port Driver工作原理》中,已经对port driver的运行机制做了初步的介绍。在erlang中,一个socket被表示为一个port。socket的port driver的实现全部在OTP/erts/emulator/drivers/common/inet_drv.c中。Port driver遵循非阻塞的原则,不会在处理socket io时阻塞scheduler线程。所有需要阻塞的操作,比如accept,connect,send等等,都是通过driver_select向系统的事件分派器注册可读写事件,等待系统回调后进行处理。

系统的事件分派器在linux上使用epoll实现。多个scheduler线程并发在这个分派器上监听系统的io事件,这就是整个erlang虚拟机的io处理模型。这与使用C++的ACE Reactor模式基本上是一致的。

所以理论上,在不考虑scheduler线程对于runqueue中process运行的消耗,erlang虚拟机处理io的效率应该与多线程驱动的ACE Reactor(epoll)模型基本一致。

系统调用和数据返回

socket是port,所以当然应该使用port相关的bif与port进行交互。

使用socket发送数据是通过调用port_command或者使用"!"操作符实现的。如果不能立即发送,消息会缓存到driver queue中,等待系统的可写事件。在driver中有控制driver queue的高低水位(watermark),当累积的发送数据大于高水位时,driver会被设置成busy状态,直到driver queue中的数据低于地水位才解除。如果向一个busy状态的socket发送数据,将会造成port_command的阻塞,从而挂起发送process,直到busy状态解除后退出。

所有其他的命令(open, shutdown, listen, accept, connect...)都作为socket port的控制命令,通过port_control发送给driver。所有的port_control命令都被实现为异步模式,会立即返回。比如connect命令会立即返回,而不会等待connect成功。connect的结果是通过process本身的消息队列返回的。需要注意的是,结果会返回给命令的调用者process,也就是caller,而不是port的owner process,这不同于接收数据都是给owner process。也就是说我们在任何process中调用port_control来对socket发出命令,都可以在本process的消息队列中得到结果。

Erlang OTP中的封装

这里的otp指的是全部用erlang本身实现的otp库。

在otp中,socket的功能是通过prim_inet.erl模块向外提供访问的。prim_inet在本身原始的port命令上进行封装,将与socket port的消息交互封装成了函数(connect, accept...)。而prim_inet一个更重要的工作,就是将port层次基于消息的异步交互机制,通过在发送消息后立即receive结果,挂起当前process,变成了阻塞的同步交互机制。比如prim_inet中的connect函数,就是通过调用port_control向driver发送INET_REQ_CONNECT消息后,立即receive结果并将结果返回来实现一个阻塞的connect语义。所以,erlang中对socket的阻塞操作都是在process层面实现的,这样的阻塞可以被scheduler调度。


你可能感兴趣的:(Erlang,OTP)