Erlang gen_tcp ничего не получает

Я пытаюсь получить данные на стороне клиента, но ничего не получено.

Код сервера, который отправляет сообщение

client(Socket, Server) ->
  gen_tcp:send(Socket,"Please enter your name"),
  io:format("Sent confirmation"),
  {ok, N} = gen_tcp:recv(Socket,0),
  case string:tokens(N,"\r\n") of
    [Name] ->
      Client = #client{socket=Socket, name=Name, pid=self()},
      Server ! {'new client', Client},
      client_loop(Client, Server)
  end.

Клиент, который должен получить и распечатать

    client(Port)->
  {ok, Sock} = gen_tcp:connect("localhost",Port,[{active,false},{packet,2}]),
  A = gen_tcp:recv(Sock,0),
  A.

1 ответ

Решение

Я думаю, что ваш клиент неисправен, потому что он указывает:

{packet, 2}

пока сервер указывает (в коде не показан):

{packet, 0}

В Programming Erlang (2nd) на стр. 269 ​​это говорит:

Обратите внимание, что аргументы пакета, используемые клиентом и сервером, должны согласовываться. Если сервер был открыт с {packet,2} и клиент с {packet,4} тогда ничего не получится.

Следующий клиент может успешно получать текст с сервера:

%%=== Server:  {active,false}, {packet,0} ====

client(Port) ->
    {ok, Socket} = gen_tcp:connect(localhost, Port, [{active,false},{packet,0}]),

    {ok, Chunk} = gen_tcp:recv(Socket, 0),
    io:format("Client received: ~s", [Chunk]),
    timer:sleep(1000),

    Name = "Marko",
    io:format("Client sending: ~s~n", [Name]),
    gen_tcp:send(Socket, Name),

    loop(Socket).

loop(Socket) ->
    {ok, Chunk} = gen_tcp:recv(Socket, 0),
    io:format("Client received: ~s~n", [Chunk]),
    loop(Socket).

Тем не менее, я думаю, что и chatserver, и мой клиент имеют серьезные проблемы. Когда вы отправляете сообщение через TCP (или UDP) соединение, вы должны предполагать, что сообщение будет разбито на неопределенное количество фрагментов - каждый произвольной длины. когда {packet,0} уточняется, я думаю recv(Socket, 0) будет читать только один кусок из сокета, а затем вернуться. Этот кусок может быть всем сообщением, или это может быть только часть сообщения. Чтобы гарантировать, что вы прочитали все сообщение из сокета, я думаю, что вам нужно перебрать recv():

get_msg(Socket, Chunks) ->
    Chunk = gen_tcp:recv(Socket, 0),
    get_msg(Socket, [Chunk|Chunks]).

Тогда возникает вопрос: как вы узнаете, когда прочитали сообщение целиком, чтобы завершить цикл? {packet,0} говорит Эрлангу не добавлять заголовок длины к сообщению, так как узнать, где находится конец сообщения? Прибывают ли еще фрагменты, или recv () уже прочитал последний блок? Я думаю, что маркер конца сообщения - это когда другая сторона закрывает сокет:

get_msg(Socket, Chunks) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Chunk} ->
            get_msg(Socket, [Chunk|Chunks]);
        {error, closed} ->
            lists:reverse(Chunks);
        {error, Other} ->
            Other
    end.

Но возникает другая проблема: если chatserver зацикливается на recv (), ожидая сообщения от клиента, и после того, как клиент отправляет сообщение на сервер, клиент зацикливается на recv (), ожидая сообщения от сервера, и обеим сторонам нужна другая сторона, чтобы закрыть сокет, чтобы выйти из их циклов recv (), тогда вы получите тупик, потому что ни одна из сторон не закрывает свой сокет. В результате клиент должен будет закрыть сокет, чтобы чат-сервер вышел из цикла recv () и обработал сообщение. Но тогда сервер не может ничего отправить () обратно клиенту, потому что клиент закрыл сокет. В результате я не знаю, можете ли вы сделать двустороннюю связь, когда {packet,0} указан.

Вот мои выводы о {packet, N} а также {active, true|false} читать документы и искать:


отправить ():

Когда вы звоните send(), данные * фактически не передаются в пункт назначения. Вместо, send() блоки до назначения recv() и только тогда данные передаются в пункт назначения.

* В разделе "Программирование Erlang (2nd)" на стр. 176 говорится, что небольшой объем данных будет передан в пункт назначения при вызове send() из-за того, как ОС буферизует данные, и, следовательно, send() будет блокировать до recv() тянет данные к месту назначения.


Варианты по умолчанию:

Вы можете получить значения по умолчанию для сокета, указав пустой список для его параметров, а затем выполнить:

Defaults = inet:getopts(Socket, [mode, active, packet]),
io:format("Default options: ~w~n", [Defaults]).

--output:--
Default options: {ok,[{mode,list},{active,true},{packet,0}]}

Вы можете использовать inet:getopts(), чтобы показать, что gen_tcp:accept(Socket) возвращает сокет с теми же параметрами, что и в Socket.


                    {active, true}   {active,false}
                   +--------------+----------------+
{packet, 1|2|4}:   |    receive   |     recv()     |
                   |    no loop   |     no loop    |
                   +--------------+----------------+
{packet, 0|raw}:   |    receive   |     recv()     |
  (equivalent)     |    loop      |     loop       |
                   +--------------+----------------+     

{активный, ложный}

Сообщения не попадают в почтовый ящик. Этот параметр используется для предотвращения переполнения клиентами почтового ящика сервера сообщениями. Не пытайтесь использовать блок получения для извлечения сообщений "tcp" из почтового ящика - их не будет. Когда процесс хочет прочитать сообщение, процесс должен прочитать сообщение непосредственно из сокета, вызвав recv(),

{пакет, 1 | 2 | 4}:

Кортеж пакета определяет протокол, которому каждая сторона ожидает, что сообщения будут соответствовать. {packet, 2} указывает, что каждому сообщению будет предшествовать два байта, которые будут содержать длину сообщения. Таким образом, получатель сообщения будет знать, как долго продолжать чтение из потока байтов, чтобы достичь конца сообщения. Когда вы отправляете сообщение через TCP-соединение, вы не представляете, на сколько частей будет разбито сообщение. Если получатель прекращает чтение после одного фрагмента, возможно, он прочитал не все сообщение. Следовательно, получателю нужен индикатор, чтобы сообщить ему, когда все сообщение было прочитано.

С {packet, 2} получатель будет считывать два байта, чтобы получить длину сообщения, скажем, 100, а затем получатель будет ждать, пока он не прочитает 100 байтов из кусков случайного размера байтов, которые передаются в получатель.

Обратите внимание, что когда вы звоните send(), erlang автоматически вычисляет количество байтов в сообщении и вставляет длину в N байты, как указано {packet, N} и добавляет сообщение. Точно так же, когда вы звоните recv() эрланг автоматически читает N байтов из потока, как указано {packet, N}, чтобы получить длину сообщения, то recv() блокирует, пока не будет считана длина байта из сокета, затем recv() возвращает все сообщение

{пакет, 0 | raw} (эквивалент):

когда {packet, 0} указано, recv() будет читать количество байтов, указанное его аргументом длины. Если длина равна 0, то я думаю, recv() будет читать один кусок из потока, который будет произвольным числом байтов. В результате сочетание {packet, 0} а также recv(Socket, 0) требует создания цикла для чтения всех фрагментов сообщения и индикатора для recv() прекратить чтение, потому что оно достигло конца сообщения, когда другая сторона закроет сокет:

get_msg(Socket, Chunks) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Chunk} -> 
            get_msg(Socket, [Chunk|Chunks]);
        {error, closed} ->
            lists:reverse(Chunks);
        {error, Other} ->
            Other
    end.

Я думаю, что чат-сервер неисправен, потому что он указывает {packet, 0} в комбинации с recv(Socket, 0):

client_handler(Sock, Server) ->
    gen_tcp:send(Sock, "Please respond with a sensible name.\r\n"),
    {ok,N} = gen_tcp:recv(Sock,0), %% <**** HERE ****
    case string:tokens(N,"\r\n") of

пока он не использует цикл для recv(),


{активный, правда}

Сообщения, отправленные через TCP (или UDP) соединение, автоматически считываются из сокета и помещаются в почтовый ящик контролирующего процесса. Управляющий процесс - это процесс, который вызвал accept() или процесс, вызвавший connect(). Вместо вызова recv () для чтения сообщений непосредственно из сокета, вы извлекаете сообщения из почтового ящика с блоком receive:

get_msg(Socket)
    receive
        {tcp, Socket, Chunk} ->  %Socket is already bound!
        ...
    end

{пакет, 1 | 2 | 4}:

Эрланг автоматически считывает все фрагменты сообщения из сокета и помещает полное сообщение (с удаленным заголовком длины) в почтовый ящик:

get_msg(Socket) ->
    receive
        {tcp, Socket, CompleteMsg} ->
            CompleteMsg,
        {tcp_closed, Socket} ->
            io:format("Server closed socket.~n")
    end.

{пакет, 0 | raw} (эквивалент):

Сообщения не имеют заголовка длины, поэтому, когда Erlang читает из сокета, Erlang не может узнать, когда наступил конец сообщения. В результате Erlang помещает каждый блок, который он читает из сокета, в почтовый ящик. Вам нужен цикл для извлечения всех чанков из почтового ящика, а другая сторона должна закрыть сокет, чтобы сигнализировать, что чанки не поступают:

get_msg(ClientSocket, Chunks) ->
    receive
        {tcp, ClientSocket, Chunk} ->
            get_msg(ClientSocket, [Chunk|Chunks]);
        {tcp_closed, ClientSocket} ->
            lists:reverse(Chunks)
    end.


recv() документы упоминают что-то о recv() Аргумент длины, применимый только к сокетам в необработанном режиме. Но поскольку я не знаю, когда Socket находится в режиме raw, я не доверяю аргументу Length. Но смотрите здесь: Erlang gen_tcp: семантика recv(Socket, Length). Хорошо, теперь я где-то получаю: из документации Erlang Inet:

{packet, PacketType}(TCP/IP sockets)
Defines the type of packets to use for a socket. Possible values:

raw | 0
    No packaging is done.

1 | 2 | 4
    Packets consist of a header specifying the number of bytes in the packet, followed by that  
    number of bytes. The header length can be one, two, or four bytes, and containing an  
    unsigned integer in big-endian byte order. Each send operation generates the header, and the  
    header is stripped off on each receive operation.

    The 4-byte header is limited to 2Gb [message length].

Как показывают примеры в Erlang семантика gen_tcp:recv(Socket, Length), когда {packet,0} указано, recv() Можно указать длину для чтения из потока TCP.

Другие вопросы по тегам