为什么当我在输出通道上呼叫"close_out"时会得到Sys_error("Bad file descriptor")?



阅读这篇关于使用 ocaml 进行套接字编程的文章时,我遇到了此示例服务器代码:

# let establish_server server_fun sockaddr =
let domain = domain_of sockaddr in
let sock = Unix.socket domain Unix.SOCK_STREAM 0 
in Unix.bind sock sockaddr ;
Unix.listen sock 3;
while true do
let (s, caller) = Unix.accept sock 
in match Unix.fork() with
0 -> if Unix.fork() <> 0 then exit 0 ; 
let inchan = Unix.in_channel_of_descr s 
and outchan = Unix.out_channel_of_descr s 
in server_fun inchan outchan ;
close_in inchan ;
close_out outchan ;
exit 0
| id -> Unix.close s; ignore(Unix.waitpid [] id)
done ;;
val establish_server :
(in_channel -> out_channel -> 'a) -> Unix.sockaddr -> unit = <fun>

在本地修改代码时,我很惊讶每次连接到套接字时都收到Fatal error: exception Sys_error("Bad file descriptor")。这是我的修补代码:

let my_name = Unix.gethostname();;
let my_entry_byname = Unix.gethostbyname my_name ;;
let my_addr = my_entry_byname.h_addr_list.(0);;
let socket_desc = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0;;
let hello_server sockaddr =
let domain = Unix.domain_of_sockaddr sockaddr in
let socket_desc = Unix.socket domain Unix.SOCK_STREAM 0
in Unix.bind socket_desc sockaddr;
Unix.listen socket_desc 3;
let addr_in =
match Unix.getsockname socket_desc with
Unix.ADDR_INET (a, _) -> a
| _ -> failwith "not INET";
in
print_string (String.concat "" ["Listening on "; Unix.string_of_inet_addr addr_in]);
flush stdout;
while true do
let (s, _caller) = Unix.accept socket_desc
in match Unix.fork() with
0 -> if Unix.fork() <> 0 then exit 0;
print_string "Got a connection!";
flush stdout;
let inchan = Unix.in_channel_of_descr s
and outchan = Unix.out_channel_of_descr s
in output_string outchan "Hello world!";
flush outchan;
close_in inchan;
close_out outchan;
exit 0;
| id -> Unix.close s; ignore(Unix.waitpid [] id)
done;;

let start_server () =
let addr = Unix.ADDR_INET(my_addr, 12345)
in hello_server addr;;
let () = start_server()

似乎错误可能是由于子进程中的close_out outchan调用造成的。不过,我不太清楚为什么会出现错误。在该频道上呼叫close_out有什么问题?

Fwiw,我正在用telnet my.local.ip.addr 12345连接到频道

编辑:另外:为什么我们在父进程中调用Unix.close s而不是在子进程中?

您正在关闭套接字两次。

let (s, _caller) = Unix.accept socket_desc

现在你已经有了套接字的Unix文件描述符s

let inchan = Unix.in_channel_of_descr s
and outchan = Unix.out_channel_of_descr s

现在,您拥有了 OCaml 输入和输出通道,其中套接字作为其基础流。

Unix.close s;

现在,您已经关闭了 Unix 套接字的读/写端点。

close_out outchan;

现在,您尝试再次关闭套接字。由于基础流已关闭,因此这是一个错误。

查看它(恕我直言(的方法是,在执行此操作后:

let inchan = Unix.in_channel_of_descr s
and outchan = Unix.out_channel_of_descr s

您正在签署不再使用底层 Unix 套接字的合同。从这一点开始,您应该只处理 OCaml 通道。

如果你删除Unix.close s,事情应该可以工作(或者在下一个问题中失败:-(

更新

我从教程中运行了您给定的代码,它也会出现错误的文件描述符异常。

可能这是一个有缺陷的教程。

似乎close_inclose_out都将完全关闭插座(因此不会使其处于所谓的半打开状态(。所以我会打电话给close_out.

最好完全通过Unix接口执行套接字I/O。让两个 OCaml 通道共享同一个文件描述符似乎有点脆弱。

更新 2

可以使用Unix.dup获取第二个文件描述符,以用于两个 OCaml 通道之一。生成的代码对我来说感觉不那么脆弱:

match Unix.fork() with
| 0 ->
(* Child process *)
if Unix.fork() <> 0 then exit 0; (* Daemonize *)
print_string "Got a connection!";
flush stdout;
let s' = Unix.dup s in
let inchan = Unix.in_channel_of_descr s
and outchan = Unix.out_channel_of_descr s' in
output_string outchan "Hello world!";
flush outchan;
close_in inchan;
close_out outchan;
exit 0
| id ->
(* Parent process *)
Unix.close s;
ignore(Unix.waitpid [] id)

我测试了这段代码,它没有错误的文件描述符异常。

相关内容

  • 没有找到相关文章

最新更新