2016-09-24 137 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的非阻塞讀取意味着您在測試期間可能有完整的消息,但最終會失敗。 –