OCaml: Lwt и неблокирующий сокет

Я хотел попробовать модуль Lwt_unix для простого клиента, который читает данные в сокете до тех пор, пока читать нечего. Некоторые сказали мне, что Lwt создает неблокирующие сокеты, но с моим кодом он все еще блокируется:

open Lwt
open Unix

(* ocamlfind ocamlc -o lwt_socket_client -package lwt,lwt.unix,unix  -linkpkg -g lwt_socket_client.ml *)
let host = Unix.inet_addr_loopback 
let port = 6600

let create_socket () =
  let sock = Lwt_unix.socket PF_INET SOCK_STREAM 0 in
  Lwt_unix.set_blocking sock false;
  sock

let s_read sock maxlen =
  let str = Bytes.create maxlen in
  let rec _read sock acc =
    Lwt.ignore_result(Lwt_io.write_line Lwt_io.stdout "_read");
    Lwt_unix.read sock str 0 maxlen >>= fun recvlen ->
    Lwt.ignore_result(Lwt_io.write_line Lwt_io.stdout (string_of_int recvlen));
    if recvlen = 0 then Lwt.return (acc)
    else _read sock (acc ^ (String.sub str 0 recvlen))
  in _read sock ""

let socket_read sock =
  Lwt.ignore_result(Lwt_unix.connect sock @@ ADDR_INET(host, port));
  s_read sock 1024 >>= fun answer ->
  Lwt_io.write_line Lwt_io.stdout answer

let () =
  let sock = create_socket () in
    Lwt_main.run (socket_read sock)

Если я попробую этот пример с термином:

echo "totoche" | netcat -l 127.0.0.1 -p 6600

тогда результат:

./lwt_socket_client
_read
8
_read

Какой блок, пока я не нажму Ctrl+c.

Я пробовал оба с:

Lwt_unix.set_blocking sock false;

и

Lwt_unix.set_blocking sock true;

и конечно без этой строки, но все равно блокирует. Что я делаю не так?

Для получения дополнительной информации, один из моих предыдущих вопросов: неблокирующий клиентский сокет OCaml


person cedlemo    schedule 24.09.2016    source источник
comment
Примечание: ignore_result x; ... означает, что это нужно делать в фоновом режиме, не дожидаясь этого. Вероятно, вы хотите x >>= fun () -> ... (подождите, пока x завершится, затем...)   -  person Thomas Leonard    schedule 25.09.2016


Ответы (2)


Концептуально Lwt_unix.read всегда блокирует поток LWT, но никогда блокирует весь процесс, если только процесс не ожидает этот поток LWT и нет других потоков LWT для выполнения. Lwt_unix.set_blocking не влияет на это поведение. Он просто изменяет настройки базового сокета и, следовательно, стратегию, используемую внутри Lwt, чтобы избежать блокировки процесса.

Итак, как упоминал @ThomasLeonard, «идиоматический Lwt» способ сделать неблокирующий read (с точки зрения процесса) — это просто запустить дополнительные потоки Lwt одновременно с Lwt_unix.read.


Что касается конкретного кода в вопросе, базовый системный вызов read завершается ошибкой с EAGAIN или EWOULDBLOCK (в зависимости от системы), если базовый сокет не блокируется, но данные недоступны, а не завершается успешно с прочитанным нулевым байтом, что указывает на сокет был закрыт.

Unix.read преобразует это в исключение Unix.Unix_error Unix.EAGAIN (соответственно Unix.Unix_error Unix.EWOULDBLOCK). Lwt_unix.read повторяет попытку Unix.read в этом случае. Таким образом, вы не можете (в настоящее время) напрямую реагировать на неблокирующие чтения, которые терпят неудачу таким образом, если используете Lwt_unix.read.

Если вам нужен/нужен этот уровень контроля над сокетом, созданным с помощью Lwt_unix, вы можете сделать это:

Lwt_unix.set_blocking sock false;

try
  Unix.read (Lwt_unix.unix_file_descr sock) str 0 maxlen
with Unix.Unix_error (Unix.EAGAIN | Unix.EWOULDBLOCK) ->
  (* Handle no data available. *)

РЕДАКТИРОВАТЬ: Кроме того, как упоминал @ThomasLeonard, некоторые варианты использования ignore_result в вашем коде, вероятно, должны быть e >>= fun () -> e'. Это заставляет Lwt ждать завершения e перед запуском e'. В частности, вы должны сделать это для Lwt_unix.connect.

person antron    schedule 27.09.2016

В OS X я получаю:

> ./lwt_socket_client 
_read
8
_read
0
totoche

Кажется, это то, о чем вы просите. Однако я не уверен, что такое поведение полезно, так как оно зависит от того, как ядро ​​планирует работу. Что ты пытаешься сделать? Если вы хотите, например. займитесь чем-нибудь еще, ожидая ввода, просто запустите второй поток Lwt параллельно с (блокирующим) чтением.

person Thomas Leonard    schedule 25.09.2016
comment
Вы только что скопировали код, который я публикую? потому что я на ArchLinux x86_64, и это не сработало. - person cedlemo; 26.09.2016
comment
Причины: Пытаюсь сделать mpd-клиент. В соединении mpd соединение инициируется и завершается клиентом. Клиент подключается, читает сообщение о состоянии от mpd, затем отправляет команду, получает результат и так далее, пока клиент не закроет соединение. Моя проблема в чтении, я прекращаю чтение и возвращаю сообщение mpd. Для этого я могу использовать два события : в сокете нет оставшихся данных (это возможность, которую я проверяю с помощью этого кода) или я полагаюсь на mpd протокол, который говорит, что каждое сообщение сервера mpd завершается \n (это я еще не тестировал). - person cedlemo; 26.09.2016
comment
никакие доступные данные не говорят вам ничего полезного, только то, что ядро ​​​​не готово дать вам следующий бит прямо сейчас. Единственное, что вы можете сделать в этом случае, это снова вызвать read, чтобы узнать, не поступило ли еще что-то. read всегда будет возвращаться немедленно, если это возможно, поэтому просто обрабатывайте как можно больше полных сообщений из своего буфера каждый раз, когда read возвращается. Предполагая, что неблокирующее чтение 0 означает, что у вас есть полное сообщение, может работать во время тестирования, но в конечном итоге произойдет сбой. - person Thomas Leonard; 26.09.2016