gen_tcp смущенные сообщения [дубликаты]

Я использую socket_server из этого руководства и следующий код для клиент и сервер:

Сервер:

-module(echo_server).
-export([start/0, loop/1]).

% echo_server specific code
start() ->
    spawn(socket_server, start, [?MODULE, 7000, {?MODULE, loop}]).

loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Message} ->
            Msg = binary_to_term(Message),
            case Msg of
                start ->
                    io:format("Got start message on socket ~p.~n", [Socket]),
                    send_count(Socket, 10),
                    gen_tcp:close(Socket);
                Other ->
                    io:format("Got message on socket ~p: ~p~n",
                              [Socket, Other])
            end;
        {error, closed} ->
            io:format("Got closed message on socket ~p.~n", [Socket]),
            ok;
        Error ->
            io:format("Got bad message: ~p on socket ~p.~n", [Error, Socket])
    end.

send_count(_Socket, 0) ->
    ok;
send_count(Socket, Num) ->
    io:format("Sending ~p to ~p.~n", [Num, Socket]),
    gen_tcp:send(Socket, term_to_binary(Num)),
    send_count(Socket, Num - 1).

Клиент:

-module(echo_client).
-export([start/0, do_stuff/0]).


send(Socket, Msg) ->
    gen_tcp:send(Socket, term_to_binary(Msg)).

start() ->
    dbg:tracer(),
    Pid = spawn(?MODULE, do_stuff, []),
    dbg:p(Pid, r).

do_stuff() ->
    case gen_tcp:connect("localhost", 7000, [binary, {packet, 0}]) of
        {ok, Socket} ->
            send(Socket, start),
            rx_loop(Socket);
        Error ->
            io:format("Error connecting to server: ~p~n", [Error])
    end.

rx_loop(Socket) ->
    receive
        {tcp, Socket, Message} ->
            Msg = binary_to_term(Message),
            io:format("Received message: ~p~n", [Msg]),
            rx_loop(Socket)
    after 5000 ->
            finish_loop(Socket)
    end.

finish_loop(Socket) ->
    receive
        {tcp, Socket, Message} ->
            Msg = binary_to_term(Message),
            io:format("Received message: ~p~n", [Msg]),
            rx_loop(Socket);
        {tcp_closed, Socket} ->
            io:format("Server terminated connection.~n"),
            exit(normal);
        Error ->
            io:format("Received bad message: ~p~n", [Error]),
            rx_loop(Socket)
    end.

Я вызываю echo_server:start() и echo_client:start() из разных оболочек одной и той же системы именно в таком порядке. Вот что я вижу:

Сервер вроде работает нормально.

1>echo_server:start().
<0.39.0>
Got start message on socket #Port<0.2041>.
Sending 10 to #Port<0.2041>.
Sending 9 to #Port<0.2041>.
Sending 8 to #Port<0.2041>.
Sending 7 to #Port<0.2041>.
Sending 6 to #Port<0.2041>.
Sending 5 to #Port<0.2041>.
Sending 4 to #Port<0.2041>.
Sending 3 to #Port<0.2041>.
Sending 2 to #Port<0.2041>.
Sending 1 to #Port<0.2041>.

Клиент не совсем правильно понимает все сообщения:

2> echo_client:start().
{ok,[{matched,nonode@nohost,1}]}
3> (<0.41.0>) << {code_server,{module,gen_tcp}}
(<0.41.0>) << {code_server,{module,inet_tcp}}
(<0.41.0>) << {#Ref<0.0.0.74>,{ok,<0.43.0>}}
(<0.41.0>) << {#Ref<0.0.0.76>,
               {ok,<<4,0,0,0,2,127,0,0,1,127,0,0,1,0,0,0,3,108,111,99,97,108,
                     104,111,115,116,0,105,112,54,45,108,111,99,97,108,104,
                     111,115,116,0,105,112,54,45,108,111,111,112,98,97,99,
                     107,0>>}}
(<0.41.0>) << {inet_async,#Port<0.2058>,0,ok}
(<0.41.0>) << {inet_reply,#Port<0.2058>,ok}
Received message: 10
3> (<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,10>>}
Received message: 9
3> (<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << timeout
(<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,9>>}
(<0.41.0>) << {io_reply,<0.25.0>,ok}
Received message: 8
Received message: 5
Received message: 4
Received message: 3
Received message: 2
Received message: 1
3> (<0.41.0>) << timeout
(<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,8,131,97,7,131,97,6>>} %% <---This guy here
(<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,5>>}
(<0.41.0>) << timeout
(<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << timeout
(<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,4>>}
(<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << timeout
(<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,3>>}
(<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << timeout
(<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,2>>}
(<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << timeout
(<0.41.0>) << {tcp,#Port<0.2058>,<<131,97,1>>}
(<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << {tcp_closed,#Port<0.2058>}
(<0.41.0>) << timeout
Server terminated connection.
3> (<0.41.0>) << timeout
(<0.41.0>) << {io_reply,<0.25.0>,ok}
(<0.41.0>) << timeout

Если я посмотрю на сетевой трафик на lo, я увижу хорошие чистые пары PSH/ACK для каждого числа, идущие в обратном порядке. Строка, на которую я указал выше, показывает два пакета в одном сообщении: 7 и 6. Они пришли в сеть как два отдельных TCP-пакета. У кого-нибудь есть идеи, почему они смешаны вместе или как их раздавить?


person nmichaels    schedule 17.11.2010    source источник
comment
@closers: Этот вопрос из 2010 года. Как это обман вчерашнего вопроса?   -  person nmichaels    schedule 20.05.2014
comment
Другой вопрос был выбран в качестве канонического дубляжа.   -  person bjb568    schedule 20.05.2014


Ответы (1)


Почему их «раздавливают» на принимающей стороне: потому что TCP — это потоковый протокол, и нет требования, чтобы вызовы отправки/получения имели соответствие 1-1 с сетевыми пакетами (даже если они приходят таким образом по сети) .

Как их «размазать»: либо измените свой протокол TCP, включив в него разделитель сообщений, чтобы вы могли извлекать сообщения из потока, не зная, где были границы пакетов; или используйте UDP вместо TCP.

person Jim Lewis    schedule 17.11.2010
comment
Вау, это новинка. Я бы предпочел не использовать UDP, так как тогда мне пришлось бы делать все приятные вещи, которые TCP делает сам (например, гарантировать доставку по порядку). Не могли бы вы поподробнее рассказать о том, как получить разделитель сообщений в TCP? Есть ли что-то встроенное в gen_tcp, что я могу использовать, или мне нужно накатить собственное? - person nmichaels; 18.11.2010
comment
@Nathon: я не знаю Erlang, поэтому не могу говорить о возможностях gen_tcp. Вам почти наверняка придется управлять каким-то буфером для сообщений, которые вы читаете с уровня TCP, а затем иметь способ определить, когда доступно полное сообщение. Если ваши сообщения имеют одинаковую длину, вы почти закончили! Также принято резервировать первые несколько байтов сообщения в качестве поля длины сообщения. Или, если это неудобно на стороне отправителя, вы можете определить последовательность байтов, которая не будет отображаться в вашем потоке данных, а затем искать эту последовательность, чтобы определить, когда будет готово полное сообщение. - person Jim Lewis; 18.11.2010
comment
Ах ах! Параметр {packet, N} использует первые N байтов пакета в качестве поля длины. На erlang.org/doc/ есть длинный список параметров для этого поля. man/inet.html#setopts-2 - person nmichaels; 18.11.2010
comment
{packet, N} — быстрый способ решить проблему. Более общий путь - запросить кодирование терминов erlang напрямую или аналогичным образом. - person I GIVE CRAP ANSWERS; 18.11.2010