2016-09-24 174 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; ......'意味着“在后台不做等待”。你可能想要'x >> = fun() - > ...'(等待x完成,然后...) –

回答

2

概念,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做这个。

1

在OS X上,我得到:

> ./lwt_socket_client 
_read 
8 
_read 
0 
totoche 

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

+0

你刚刚复制了我发布的代码吗?因为我在ArchLinux x86_64上并没有工作 – cedlemo

+0

原因:我尝试做一个mpd客户端在mpd连接中,连接由客户端发起和终止客户端连接从mpd读取状态消息,然后发送一个命令,得到一个结果,依此类推,直到客户端关闭connection_。我的问题是在阅读中,我停止阅读并返回mpd消息,为此我可以使用**两个事件**: **在套接字中没有剩余的数据**(这是我用这段代码测试的可能性)或者我依赖于mpd protocole **,它表示每个mpd服务器消息都以“\ n”结尾(我有 – cedlemo

+0

“没有数据可用”没有告诉你任何有用的信息,只是内核还没有准备好给你下一个位,在这种情况下你唯一能做的就是调用'read'再看看现在是否有更多的东西,如果可以的话,'read'将会立即返回,所以只要处理尽可能多的完整消息每当'read'返回时,你可以从你的缓冲区中读取数据。假设0的非阻塞读取意味着您在测试期间可能有完整的消息,但最终会失败。 –