OCaml:Lwt 和非阻塞套接字
OCaml: Lwt and non blocking socket
我想为一个简单的客户端尝试 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;
当然没有这条线,但它仍然阻塞。我做错了什么?
有关更多信息,我之前的问题之一:
在 OS X 上,我得到:
> ./lwt_socket_client
_read
8
_read
0
totoche
这似乎是您要的。但是,我不确定这种行为是否有用,因为它取决于内核如何安排工作。你想做什么?如果你想,例如在等待输入的同时继续做其他事情,只是 运行 与(阻塞)读取并行的第二个 Lwt 线程。
概念上,Lwt_unix.read
总是阻塞Lwt线程,但是从不阻塞整个进程——除非进程正在等待对于该 Lwt 线程,运行 没有其他 Lwt 线程。 Lwt_unix.set_blocking
不影响此行为。它只是更改底层套接字上的设置,因此 Lwt 内部使用的策略来避免阻塞进程。
因此,正如@ThomasLeonard 所提到的,"idiomatic 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
.
,您不能(当前)直接响应以这种方式失败的非阻塞读取
如果你want/need对使用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
.
执行此操作
我想为一个简单的客户端尝试 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;
当然没有这条线,但它仍然阻塞。我做错了什么?
有关更多信息,我之前的问题之一:
在 OS X 上,我得到:
> ./lwt_socket_client
_read
8
_read
0
totoche
这似乎是您要的。但是,我不确定这种行为是否有用,因为它取决于内核如何安排工作。你想做什么?如果你想,例如在等待输入的同时继续做其他事情,只是 运行 与(阻塞)读取并行的第二个 Lwt 线程。
概念上,Lwt_unix.read
总是阻塞Lwt线程,但是从不阻塞整个进程——除非进程正在等待对于该 Lwt 线程,运行 没有其他 Lwt 线程。 Lwt_unix.set_blocking
不影响此行为。它只是更改底层套接字上的设置,因此 Lwt 内部使用的策略来避免阻塞进程。
因此,正如@ThomasLeonard 所提到的,"idiomatic 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
.
如果你want/need对使用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
.