Erlang gen_tcp连续发送消息时部分消息未接收的原因
问题
我创建了一个简单的Erlang服务器用于接收单个客户端的消息,但当连续发送多条消息时,部分消息无法按预期被接收。
服务器代码
test() -> {ok, LSock} = gen_tcp:listen(7777, [binary, {active, true}, {packet, 0}, {reuseaddr, true}]), spawn(fun() -> acceptor(LSock) end). loop() -> receive {tcp, Socket, Bin} -> io:format("Received: ~p~n", [binary_to_term(Bin)]), loop(); {tcp_closed, Socket} -> io:format("Socket Closed~n"), exit(normal); {tcp_error, _} -> exit(normal); _ -> io:format("Unknown Message~n"), exit(normal) end. acceptor(LSock) -> {ok, Sock} = gen_tcp:accept(LSock), io:format("Client Connected~n"), Pid = spawn(fun() -> loop() end), gen_tcp:controlling_process(Sock, Pid).
客户端连接方式
{ok, Socket} = gen_tcp:connect("localhost", 7777, [binary, {packet, 0}, {active, true}]).
问题现象
当连续执行gen_tcp:send(Socket, term_to_binary(data1)),gen_tcp:send(Socket, term_to_binary(data3)).多次时,多数情况下仅能收到“Received: data1”,无法收到“Received: data3”。例如连续执行两次该代码后,输出如下:
Received: data1 Received: data3 Received: data1
逐条执行send操作则能正常工作,但作为单条命令执行时就会出现问题。
注:曾尝试在连接与发送之间添加等待时间,因为了解到gen_tcp:controlling_process()可能存在竞态条件。
原因与解决方案
核心原因
问题根源是**gen_tcp:controlling_process/2的竞态条件**:
- 服务器调用
gen_tcp:accept/1拿到Socket后,立刻Spawn新进程并切换控制进程,但此时内核可能已经把客户端发送的消息推送给原acceptor进程,而新的loop进程还未准备好接收这些消息。 - 客户端连续快速发送时,部分消息会被投递到原acceptor进程,而acceptor进程没有处理
tcp消息的逻辑,直接导致消息丢失。 - 逐条执行send时,中间的时间间隔让控制进程切换完成,消息能被新进程正确接收。
修复方案
在切换控制进程前,先将Socket设置为被动模式,切换完成后再恢复主动模式,从根源避免消息在切换过程中发送给原进程:
修改acceptor/1函数:
acceptor(LSock) -> {ok, Sock} = gen_tcp:accept(LSock), io:format("Client Connected~n"), % 先将Socket设为被动模式,阻止内核向当前进程推送消息 inet:setopts(Sock, [{active, false}]), Pid = spawn(fun() -> % 新进程接管Socket后,恢复主动模式 inet:setopts(Sock, [{active, true}]), loop() end), gen_tcp:controlling_process(Sock, Pid).
补充说明
{active, false}模式下,内核不会主动把消息发送给进程,需要调用gen_tcp:recv/2,3读取,但这里我们在新进程接管后立刻恢复{active, true},完全不影响原有业务逻辑。- 这种方式彻底解决了控制进程切换时的消息丢失问题,不需要依赖等待时间这类不可靠的临时方案。
内容的提问来源于stack exchange,提问作者opbravo
相关产品推荐
相关产品推荐

