2016-09-24 2 views
3

Я хотел попробовать модуль Lwt_unix для простого клиента, который считывает данные в сокете, пока не будет ничего, что можно прочитать. Некоторые говорили мне, что Lwt создать не блокирующие сокеты, но с моим кодом, он по-прежнему блокируют:OCaml: 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 +гр.

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

Lwt_unix.set_blocking sock false; 

и

Lwt_unix.set_blocking sock true; 

и, конечно, без этой линии, но он по-прежнему блокирует. Что я делаю не так?

Для получения дополнительной информации, один из моего предыдущего вопроса: OCaml non-blocking client socket

+1

Примечание: 'ignore_result x; ... "означает" сделать это в фоновом режиме, не дожидаясь его ". Вы probaby хотите 'x >> = fun() -> ...' (дождитесь окончания x, затем ...) –

ответ

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. *) 

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

1

На OS X, я получаю:

> ./lwt_socket_client 
_read 
8 
_read 
0 
totoche 

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

+0

Вы только что скопировали код, который я публикую? потому что я на ArchLinux x86_64, и он не работал. – cedlemo

+0

Причины: я пытаюсь выполнить клиент mpd. В соединении mpd _ соединение инициируется и завершается клиентом. Клиент подключается, считывает сообщение состояния из mpd , затем отправьте команду, получите результат и так далее, пока клиент не закроет соединение. Моя проблема в чтении, я перестаю читать и возвращать сообщение mpd. Для этого я могу использовать ** два события **: есть ** никаких оставшихся данных ** в сокете (это возможность проверить с помощью этого кода), или я ** полагаюсь на протокол mpd protocole **, в котором говорится, что каждое сообщение сервера mpd заканчивается на «\ n» (что у меня есть еще не проверено) – cedlemo

+0

«нет данных» не говорит вам ничего полезного, только то, что ядро ​​не готово дать вам следующий бит прямо сейчас. Единственное, что вы можете сделать в этом случае, - это вызов 'read' снова, чтобы узнать, появилось ли еще больше. 'read' всегда будет немедленно возвращаться, если это возможно, поэтому просто обработайте столько полных сообщений как вы можете из своего буфера каждый раз, когда 'read' возвращается. Предполагая, что неблокирующее чтение 0 означает, что у вас есть полное сообщение, может работать во время тестирования, но в конечном итоге не удастся. –

Смежные вопросы