Reputation: 3478
Reading this article on socket programming with ocaml, I ran into this example server code:
# 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>
Tinkering with the code locally, I was surprised that I received Fatal error: exception Sys_error("Bad file descriptor")
each time I connected to the socket. Here's my tinkering code:
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()
It seems the error is probably due to the close_out outchan
call in the child process. I can't quite figure out why I'm getting the error though. What's wrong with calling close_out
on that channel?
Fwiw, I'm connecting to the channel with telnet my.local.ip.addr 12345
Edit: Also: why do we call Unix.close s
in the parent process and not in the child?
Upvotes: 0
Views: 931
Reputation: 66823
You are closing the socket twice.
let (s, _caller) = Unix.accept socket_desc
Now you have Unix file descriptor s
of your socket.
let inchan = Unix.in_channel_of_descr s
and outchan = Unix.out_channel_of_descr s
Now you have OCaml input and output channels with the socket as their underlying streams.
Unix.close s;
Now you have closed the read/write endpoints of the Unix socket.
close_out outchan;
Now you attempt to close the socket a second time. Since the underlying stream is already closed, this is an error.
The way to look at it (IMHO) is that after you do this:
let inchan = Unix.in_channel_of_descr s
and outchan = Unix.out_channel_of_descr s
You are signing a contract not to use the underlying Unix socket any longer. From this point on you should be dealing only with the OCaml channels.
If you remove Unix.close s
, things should work (or fail at the next problem :-)
Update
I ran your given code from the tutorial and it also gets the bad file descriptor exception.
Possibly this is a flawed tutorial.
It appears that both close_in
and close_out
are going to close the socket completely (and so not leave it in a so-called half open state). So I would just call close_out
.
It might be best to do your socket I/O entirely through the Unix interface. It seems a bit fragile to have two OCaml channels sharing the same file descriptor.
Update 2
You can use Unix.dup
to get a second file descriptor to use for one of the two OCaml channels. The resulting code feels much less fragile to me:
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)
I tested this code and it worked with no bad file descriptor exceptions.
Upvotes: 1