Как правильно использовать gen_tcp: recv - PullRequest
1 голос
/ 09 января 2012

Я написал простой http-клиент и столкнулся со следующей проблемой: я скопировал это do_recv из официального документа, но он работает странным образом:

do_recv(Sock, Bs) ->
    case gen_tcp:recv(Sock, 0, ?TIMEOUT) of
        {ok, B} ->
            gen_tcp:shutdown(Sock, write), % <-- this appears to fix the problem!
            do_recv(Sock, [Bs, B]);
        {error, closed} ->
            {ok, list_to_binary(Bs)}
    end.

Последовательность чата следующая:

{ok, S} = gen_tcp:connect(Ip, Port, [inet, binary,
                  {packet, 0},
                  {active, false},
                  {nodelay, true},
                  {reuseaddr, true}], 2000),
Req = io_lib:format("GET ~s HTTP/1.1\r\nHost: ~s\r\n\r\n", [Url, UrlHost]),
ok = gen_tcp:send(S, list_to_binary(Req)) of
do_recv(S, []);

И последний вызов do_recv иногда работает, как ожидалось, и возвращает расположение сервера, но иногда он зависает и время ожидания, я полагаю, потому что сервер не закрывает сокет сам по себе.Итак, второй случай с таймаутом - это то, чего я хочу избежать, какие-либо идеи, как справиться с таким поведением?

UPD:

Я добавил gen_tcp:shutdown вызовdo_recv (см. Комментарий в примере кода), и это, похоже, решает проблему.Вопрос довольно дурацкий, я знаю, и решение похоже на предположение, может быть, кто-то еще может объяснить, что здесь происходит и как они обычно решают такую ​​проблему.

1 Ответ

3 голосов
/ 09 января 2012

У вашего кода есть проблемы.

Если вы получите 0, вы можете получить половину строки GET или больше, чем всю строку GET, в зависимости от того, как ядро ​​обрабатывает поток. TCP ориентирован на поток, поэтому вам нужно есть данные из сокета, пока у вас не будет достаточно. Кроме того, вы легко можете получить запуск {error, timeout}, так что вам также придется справиться с этой проблемой. В противном случае это не будет работать, как ожидалось. В основном вам нужен цикл, который собирает данные, пока у вас не будет достаточно данных для анализа GET. Тайм-ауты произойдут в этом цикле, прежде чем у вас есть все данные.

Что-то вроде:

do_recv(Sock, Gathered) ->
  case gen_tcp:recv(Sock, 0, ?TIMEOUT) of
    {ok, Bin} ->
      Remaining = try_decode(Sock, <<Gathered/binary, Bin/binary>>),
      do_recv(Sock, Remaining);
    {error, timeout} ->
      do_recv(Sock, Remaining);
    {error, Reason} ->
      exit(Reason)
   end.

 try_decode(Sock, Gathered) ->
   case decode(Gathered) of
      {ok, Data, Rest} ->
         processor ! Data,
         try_decode(Sock, Rest);
      need_more_data ->
         do_recv(Sock, Gathered)
   end.

Здесь предполагается пара вещей

  • decode / 1 - это функция, которая пытается декодировать данные, и она может не выполнить это и запросить дополнительные данные.
  • Процессор - это процесс, в который мы можем отправить сообщение, как только мы что-то расшифруем. Это также может быть вызов функции, который что-то делает с данными, которые мы только что декодировали.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...