套接字模式
主动模式(选项{active, true})一般让人很喜欢,非阻塞消息接收,但在系统无法应对超大流量请求时,客户端发送的数据快过服务器可以处理的速度,那么系统就可能会造成消息缓冲区被塞满,可能出现持续繁忙的流量的极端情况下,系统因请求而溢出,虚拟机造成内存不足的风险而崩溃。
使用被动模式(选项{active, false})的套接字,底层的TCP缓冲区可用于抑制请求,并拒绝客户端的消息,在接收数据的地方都会调用gen_tcp:recv,造成阻塞(单进程模式下就只能消极等待某一个具体的客户端套接字,很危险)。需要注意的是,操作系统可能还会做一些缓存允许客户端机器继续发送少量数据,然后才会将其阻塞,此时Erlang尚未调用recv函数。
混合型模式(半阻塞),使用选项{active, once}打开,主动仅针对一个消息,在控制进程发送完一个数据消息后,必须显示调用inet:setopts(Socket, [{active, once}])重新激活以便接受下一个消息(在此之前,系统处于阻塞状态)。可见,混合型模式综合了主动模式和被动模式的两者优势,可实现流量控制,防止服务器被过多消息淹没。
以下TCP Server代码,都是建立在混合型模式(半阻塞)基础上。
prim_inet相关说明
prim_inet没有官方文档,可以认为是对底层socket的直接包装。淘宝yufeng说,这是otp内部实现的细节 是针对Erlang库开发者的private module,底层模块,不推荐使用。但在Building a Non-blocking TCP server using OTP principles示范中演示了prim_inet操作Socket异步特性。
设计模式
一般来说,需要一个单独进程进行客户端套接字监听,每一个子进程进行处理来自具体客户端的socket请求。
在Building a Non-blocking TCP server using OTP principles示范中,子进程使用gen_fsm处理,很巧妙的结合状态机和消息事件,值得学习。
在Erlang: A Generalized TCP Server文章中,作者也是使用此模式,但子进程不符合OTP规范,因此个人认为不是一个很好的实践模式。
simple_one_for_one
简易的一对一监督进程,用来创建一组动态子进程。对于需要并发处理多个请求的服务器较为合适。比如socket 服务端接受新的客户端连接请求以后,需要动态创建一个新的socket连接处理子进程。若遵守OTP原则,那就是子监督进程。
TCP Server实现
基于标准API简单实现
也是基于{active, once}模式,但阻塞的等待下一个客户端连接的任务被抛给了子监督进程。
看一下入口tcp_server_app吧
读取端口,然后启动主监督进程(此时还不会监听处理客户端socket请求),紧接着启动子监督进程,开始处理来自客户端的socket的连接。
监督进程tcp_server_sup也很简单:
需要注意的是,只有调用start_child函数时,才真正调用tcp_server_handler:start_link([LSock])函数。
tcp_server_handler的代码也不复杂:
代码很精巧,有些小技巧在里面。子监督进程调用start_link函数,init会返回{ok, #state{lsock = Socket}, 0}. 数字0代表了timeout数值,意味着gen_server马上调用handle_info(timeout, #state{lsock = LSock} = State)函数,执行客户端socket监听,阻塞于此,但不会影响在此模式下其它函数的调用。直到有客户端进来,然后启动一个新的子监督进程tcp_server_handler,当前子监督进程解除阻塞。
基于prim_inet实现
这个实现师从于Non-blocking TCP server using OTP principles一文,但子进程改为了gen_server实现。
看一看入口,很简单的:
监督进程代码:
策略不一样,one_for_one包括了一个监听进程tcp_listener,还包含了一个tcp_client_sup进程树(simple_one_for_one策略)
tcp_listener单独一个进程用于监听来自客户端socket的连接:
很显然,接收客户端的连接之后,转交给tcp_client_handler模块进行处理:
和标准API对比一下,可以感受到异步IO的好处。
小结
通过不同的模式,简单实现一个基于Erlang OTP的TCP服务器,也是学习总结,不至于忘记。
您若有更好的建议,欢迎告知,谢谢。
参考资料
- Building a Non-blocking TCP server using OTP principles
- Erlang: A Generalized TCP Server
- 《Erlang程序设计》
- 《Erlang/OTP并发编程实战》