首页 > 解决方案 > 当我在输出通道上调用 `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父进程而不是子进程?

标签: socketsocamlfile-descriptor

解决方案


您正在关闭套接字两次。

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)

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


推荐阅读