You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:22:24