Почему я получаю Sys_error («Неверный дескриптор файла»), когда я вызываю `close_out` на выходном канале? - PullRequest
0 голосов
/ 17 сентября 2018

Чтение этой статьи о программировании сокетов с 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 в родительском?процесс а не у ребенка?

1 Ответ

0 голосов
/ 17 сентября 2018

Вы дважды закрываете сокет.

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_in, и close_out собираются полностью закрыть сокет (и, следовательно, не оставлять его в так называемом полуоткрытом состоянии).Поэтому я бы просто позвонил close_out.

. Может быть, лучше сделать ввод-вывод через сокет полностью через интерфейс Unix.Кажется немного хрупким, когда два канала 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)

Я протестировал этот код, и он работал без исключений из-за плохих дескрипторов файлов.

...