【问题标题】:OCaml: Lwt and non blocking socketOCaml:Lwt 和非阻塞套接字
【发布时间】:2016-09-27 22:14:32
【问题描述】:

我想为一个简单的客户端尝试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 non-blocking client socket

【问题讨论】:

  • 注意:ignore_result x; ... 的意思是“在后台执行此操作,无需等待”。你可能想要x >>= fun () -> ...(等待 x 完成,然后...)

标签: ocaml lwt


【解决方案1】:

从概念上讲,Lwt_unix.read总是阻塞 Lwt 线程,但从不阻塞整个进程——除非进程正在等待那个 Lwt 线程,并且没有其他线程Lwt 线程运行。 Lwt_unix.set_blocking 不会影响此行为。它只是改变了底层套接字的设置,因此 Lwt 内部使用的策略是为了避免阻塞进程。

因此,正如 @ThomasLeonard 所提到的,执行非阻塞 read(从进程的角度来看)的“惯用 Lwt”方法只是与 Lwt_unix.read 同时运行额外的 Lwt 线程。


关于问题中的特定代码,如果底层套接字是非阻塞的,但没有可用数据,则底层 read 系统调用会失败并显示 EAGAINEWOULDBLOCK(取决于系统)——而不是成功读取零字节,表示套接字已关闭。

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 执行此操作。

【讨论】:

    【解决方案2】:

    在 OS X 上,我得到:

    > ./lwt_socket_client 
    _read
    8
    _read
    0
    totoche
    

    这似乎是您所要求的。但是,我不确定这种行为是否有用,因为它取决于内核如何安排工作。你想做什么?如果你想例如在等待输入的同时继续做其他事情,只需在(阻塞)读取的同时运行第二个 Lwt 线程。

    【讨论】:

    • 你只是复制我发布的代码吗?因为我在 ArchLinux x86_64 上,但它没有工作。
    • 原因:我尝试做一个mpd客户端。在 mpd 连接中,连接由客户端发起和终止。客户端连接,从 mpd 读取状态消息,然后发送命令,获取结果,等等,直到客户端关闭连接。我的问题在阅读中,我停止阅读并返回 mpd 消息。为此,我可以使用 两个事件:套接字中没有 没有剩余数据(这是我用这段代码测试的可能性)或者我 依赖 mpd protocole 表示每个 mpd 服务器消息都以 "\n" 终止(我还没有测试过)。
    • "no data available" 并没有告诉你任何有用的信息,只是内核现在还没有准备好给你下一点。在这种情况下,您唯一能做的就是再次致电read,看看现在是否有更多的人到货。 read 如果可以的话,总是会立即返回,所以每次read 返回时,从缓冲区中处理尽可能多的完整消息。假设非阻塞读取 0 意味着你有一个完整的消息可能在测试期间工作,但最终会失败。
    猜你喜欢
    • 1970-01-01
    • 2015-04-24
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    • 2010-12-16
    相关资源
    最近更新 更多