Ocaml/FAQ/Sockets

Материал из Wiki.crossplatform.ru

Версия от 20:49, 24 ноября 2010; ViGOur (Обсуждение | вклад)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Содержание

[править] 17. Sockets

[править] Introduction

open Unix
 
(* Convert human readable form to 32 bit value *)
let packed_ip = inet_addr_of_string "208.146.240.1" in
 
let host = gethostbyname "www.oreilly.com" in
let packed_ip = host.h_addr_list.(0) in
 
(* Convert 32 bit value to ip adress *)
let ip_address = string_of_inet_addr (packed_ip) in
 
(* Create socket object *)
let sock = socket PF_INET SOCK_STREAM 0 in
 
(* Get socketname *)
let saddr = getsockname sock ;;

[править] Writing a TCP Client

(* For real applications you should the SMTP module in Ocamlnet. *)
open Unix
 
let sock_send sock str =
            let len = String.length str in
    send sock str 0 len []
 
let sock_recv sock maxlen =
    let str = String.create maxlen in
    let recvlen = recv sock str 0 maxlen [] in
    String.sub str 0 recvlen
 
let client_sock = socket PF_INET SOCK_STREAM 0 in
let hentry = gethostbyname "coltrane" in
connect client_sock (ADDR_INET (hentry.h_addr_list.(0), 25)) ; (* SMTP *)
 
sock_recv client_sock 1024 ;
 
sock_send client_sock "mail from: <pleac@localhost>\n" ;
sock_recv client_sock 1024 ;
 
sock_send client_sock "rcpt to: <erikd@localhost>\n" ;
sock_recv client_sock 1024;
 
sock_send client_sock "data\n" ;
sock_recv client_sock 1024 ;
 
sock_send client_sock "From: Ocaml whiz\nSubject: Ocaml rulez!\n\nYES!\n.\n" ;
sock_recv client_sock 1024 ;
 
close client_sock ;;

[править] Writing a TCP Server

(* Writing a TCP Server *)
(* Run this and then telnet <machinename> 1027 *)
 
#load "unix.cma" ;;
open Unix ;;
 
let server_sock = socket PF_INET SOCK_STREAM 0 in
 
(* so we can restart our server quickly *)
setsockopt server_sock SO_REUSEADDR true ;
 
(* build up my socket address *)
let address = (gethostbyname(gethostname())).h_addr_list.(0) in
bind server_sock (ADDR_INET (address, 1029)) ;
 
(* Listen on the socket. Max of 10 incoming connections. *)
listen server_sock 10 ;
 
(* accept and process connections *)
while true do
        let (client_sock, client_addr) = accept server_sock in
        let str = "Hello\n" in
        let len = String.length str in
        let x = send client_sock str 0 len [] in
        shutdown client_sock SHUTDOWN_ALL
        done ;;

[править] Communicating over TCP

#load "unix.cma";;
 
let () =
  let server_in = Unix.in_channel_of_descr server in
  let server_out = Unix.out_channel_of_descr server in
  output_string server_out "What is your name?\n";
  flush server_out;
  let response = input_line server_in in
  print_endline response
 
(*-----------------------------*)
 
let () =
  try
    ignore
      (Unix.send server data_to_send 0 (String.length data_to_send) flags)
  with Unix.Unix_error (e, _, _) ->
    Printf.eprintf "Can't send: %s\n%!"
      (Unix.error_message e);
    exit 1
 
let data_read =
  let data_read = String.create maxlen in
  let data_length =
    try
      Unix.recv server data_read 0 maxlen flags
    with Unix.Unix_error (e, _, _) ->
      Printf.eprintf "Can't receive: %s\n%!"
        (Unix.error_message e);
      exit 1 in
  String.sub data_read 0 data_length
 
(*-----------------------------*)
 
let () =
  let read_from, _, _ =
    Unix.select [from_server; to_client] [] [] timeout in
  List.iter
    (fun socket ->
       (* read the pending data from socket *)
       ())
    read_from
 
(*-----------------------------*)
 
(* Requires OCaml 3.11 or newer. *)
let () =
  try Unix.setsockopt server Unix.TCP_NODELAY true
  with Unix.Unix_error (e, _, _) ->
    Printf.eprintf "Couldn't disable Nagle's algorithm: %s\n%!"
      (Unix.error_message e)
 
(*-----------------------------*)
 
(* Requires OCaml 3.11 or newer. *)
let () =
  try Unix.setsockopt server Unix.TCP_NODELAY false
  with Unix.Unix_error (e, _, _) ->
    Printf.eprintf "Couldn't enable Nagle's algorithm: %s\n%!"
      (Unix.error_message e)

[править] Setting Up a UDP Client

#load "unix.cma";;
 
(* Create a UDP socket. *)
let socket =
  Unix.socket Unix.PF_INET Unix.SOCK_DGRAM
    (Unix.getprotobyname "udp").Unix.p_proto
 
(*-----------------------------*)
 
(* Send a UDP message. *)
let ipaddr = (Unix.gethostbyname hostname).Unix.h_addr_list.(0)
let portaddr = Unix.ADDR_INET (ipaddr, portno)
let len = Unix.sendto socket msg 0 (String.length msg) [] portaddr
 
(*-----------------------------*)
 
(* Receive a UDP message. *)
let msg = String.create maxlen
let len, portaddr = Unix.recvfrom socket msg 0 maxlen []
 
(*-----------------------------*)
 
#!/usr/bin/ocaml
(* clockdrift - compare another system's clock with this one *)
#load "unix.cma";;
 
let secs_of_70_years = 2_208_988_800L
 
let msgbox =
  Unix.socket Unix.PF_INET Unix.SOCK_DGRAM
    (Unix.getprotobyname "udp").Unix.p_proto
 
let him =
  Unix.ADDR_INET ((Unix.gethostbyname
                     (if Array.length Sys.argv > 1
                      then Sys.argv.(1)
                      else "127.1")).Unix.h_addr_list.(0),
                  (Unix.getservbyname "time" "udp").Unix.s_port)
 
let () = ignore (Unix.sendto msgbox "" 0 0 [] him)
 
let ptime = String.create 4
 
let host =
  match Unix.recvfrom msgbox ptime 0 4 [] with
    | _, Unix.ADDR_INET (addr, port) ->
        (Unix.gethostbyaddr addr).Unix.h_name
    | _ -> assert false
 
let delta =
  Int64.to_float
    (Int64.sub
       (Int64.of_string (Printf.sprintf "0x%02x%02x%02x%02x"
                           (int_of_char ptime.[0])
                           (int_of_char ptime.[1])
                           (int_of_char ptime.[2])
                           (int_of_char ptime.[3])))
       secs_of_70_years)
    -. (Unix.time ())
 
let () =
  Printf.printf "Clock on %s is %d seconds ahead of this one.\n"
    host (int_of_float delta)

[править] Setting Up a UDP Server

#load "unix.cma";;
let () =
  begin
    try
      Unix.bind socket (Unix.ADDR_INET (Unix.inet_addr_any, server_port));
    with Unix.Unix_error (e, _, _) ->
      Printf.eprintf "Couldn't be a udp server on port %d: %s\n"
        server_port (Unix.error_message e);
      exit 1
  end;
  let him = String.create max_to_read in
  while true do
    ignore (Unix.recvfrom socket him 0 max_to_read []);
    (* do something *)
  done
 
(*-----------------------------*)
 
#!/usr/bin/ocaml
(* udpqotd - UDP message server *)
#load "unix.cma";;
 
let maxlen = 1024
let portno = 5151
 
let sock =
  Unix.socket Unix.PF_INET Unix.SOCK_DGRAM
    (Unix.getprotobyname "udp").Unix.p_proto
 
let () =
  Unix.bind sock (Unix.ADDR_INET (Unix.inet_addr_any, portno));
  Printf.printf "Awaiting UDP messages on port %d\n%!" portno
 
let oldmsg = ref "This is the starting message."
 
let () =
  let newmsg = String.create maxlen in
  while true do
    let newmsg, hishost, sockaddr =
      match Unix.recvfrom sock newmsg 0 maxlen [] with
        | len, (Unix.ADDR_INET (addr, port) as sockaddr) ->
            String.sub newmsg 0 len,
            (Unix.gethostbyaddr addr).Unix.h_name,
            sockaddr
        | _ -> assert false in
    Printf.printf "Client %s said ``%s''\n%!" hishost newmsg;
    ignore
      (Unix.sendto sock !oldmsg 0 (String.length !oldmsg) [] sockaddr);
    oldmsg := Printf.sprintf "[%s] %s" hishost newmsg
  done
 
(*-----------------------------*)
 
#!/usr/bin/ocaml
(* udpmsg - send a message to the udpqotd server *)
#load "unix.cma";;
 
let maxlen  = 1024
let portno  = 5151
let timeout = 5
 
let server_host, msg =
  match Array.to_list Sys.argv with
    | _ :: head :: tail -> head, String.concat " " tail
    | _ ->
        Printf.eprintf "Usage: %s server_host msg ...\n" Sys.argv.(0);
        exit 1
 
let sock =
  Unix.socket Unix.PF_INET Unix.SOCK_DGRAM
    (Unix.getprotobyname "udp").Unix.p_proto
 
let sockaddr =
  let addr = (Unix.gethostbyname server_host).Unix.h_addr_list.(0) in
  Unix.ADDR_INET (addr, portno)
 
let handle_alarm signal =
  Printf.eprintf "recv from %s timed out after %d seconds.\n"
    server_host timeout;
  exit 1
 
let () =
  ignore (Unix.sendto sock msg 0 (String.length msg) [] sockaddr);
  Sys.set_signal Sys.sigalrm (Sys.Signal_handle handle_alarm);
  ignore (Unix.alarm timeout);
  let msg = String.create maxlen in
  let msg, hishost =
    match Unix.recvfrom sock msg 0 maxlen [] with
      | len, Unix.ADDR_INET (addr, port) ->
          String.sub msg 0 len,
          (Unix.gethostbyaddr addr).Unix.h_name
      | _ -> assert false in
  ignore (Unix.alarm 0);
  Printf.printf "Server %s responded ``%s''\n" hishost msg

[править] Using UNIX Domain Sockets

#load "unix.cma";;
 
(* Create a Unix domain socket server - you can also use SOCK_STREAM. *)
let server = Unix.socket Unix.PF_UNIX Unix.SOCK_DGRAM 0
let () = try Unix.unlink "/tmp/mysock" with Unix.Unix_error _ -> ()
let () = Unix.bind server (Unix.ADDR_UNIX "/tmp/mysock")
 
(* Create a Unix domain socket client - you can also use SOCK_STREAM. *)
let client = Unix.socket Unix.PF_UNIX Unix.SOCK_DGRAM 0
let () = Unix.connect client (Unix.ADDR_UNIX "/tmp/mysock")

[править] Identifying the Other End of a Socket

#load "unix.cma";;
 
(* Get the remote IP address. *)
let () =
  let other_end = Unix.getpeername socket in
  let name_info = Unix.getnameinfo other_end [Unix.NI_NUMERICHOST] in
  let ip_address = name_info.Unix.ni_hostname in
  (* ... *)
  ()
 
(*-----------------------------*)
 
(* Attempt to determine the remote host name, with forward and reverse
   DNS lookups to detect spoofing. *)
let () =
  let other_end = Unix.getpeername socket in
  let name_info = Unix.getnameinfo other_end [Unix.NI_NUMERICHOST] in
  let actual_ip = name_info.Unix.ni_hostname in
  let claimed_hostname =
    (Unix.gethostbyaddr (Unix.inet_addr_of_string actual_ip))
      .Unix.h_name in
  let name_lookup = Unix.gethostbyname claimed_hostname in
  let resolved_ips =
    Array.to_list (Array.map
                     Unix.string_of_inet_addr
                     name_lookup.Unix.h_addr_list) in
  (* ... *)
  ()

[править] Finding Your Own Name and Address

(*-----------------------------*)
 
(*
** Finding Your Own Name and Address.
** The Unix module to the rescue again.
*)
 
#load "unix.cma" ;;
open Unix ;;
 
let hostname = gethostname () in
Printf.printf "hostname : %s\n" hostname ;;
 
(*-----------------------------*)
 
(*
** Unfortunately there is no easy way of retreiving the
** uname without using Unix.open_process_in.
*)
 
(*-----------------------------*)
 
let hentry = gethostbyname hostname in
let address = hentry.h_addr_list.(0) in
Printf.printf "address : %s\n" (string_of_inet_addr address) ;;
 
let hentry = gethostbyaddr address in
Printf.printf "hostname : %s\n" hentry.h_name ;;

[править] Closing a Socket After Forking

(* Closing a Socket After Forking *)
 
(*-----------------------------*)
shutdown sock SHUTDOWN_RECEIVE ;    (* I/we have stopped reading data *)
shutdown sock SHUTDOWN_SEND ;       (* I/we have stopped writing data *)
shutdown sock SHUTDOWN_ALL ;;       (* I/we have stopped using this socket *)
 
(*-----------------------------*)
(* Using the sock_send and sock_recv functions from above. *)
 
sock_send sock "my request\n" ;    (* send some data *)
shutdown sock SHUTDOWN_SEND ;      (* send eof; no more writing *)
let answer = sock_recv sock 4096 ;; (* but you can still read *)

[править] Writing Bidirectional Clients

#!/usr/bin/ocaml
(* biclient - bidirectional forking client *)
#load "unix.cma";;
 
let host, port =
  match Array.to_list Sys.argv with
    | [_; host; port] -> host, int_of_string port
    | _ -> Printf.eprintf "usage: %s host port\n" Sys.argv.(0); exit 1
 
let sockaddr =
  let addr = (Unix.gethostbyname host).Unix.h_addr_list.(0) in
  Unix.ADDR_INET (addr, port)
 
let () =
  let socket = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  Unix.connect socket sockaddr;
  Printf.eprintf "[Connected to %s:%d]\n%!" host port;
 
  (* split the program into two processes, identical twins *)
  match Unix.fork () with
    | 0 ->
        (* child copies standard input to the socket *)
        let output = Unix.out_channel_of_descr socket in
        while true do
          let line = input_line stdin in
          output_string output line;
          output_string output "\n";
          flush output
        done
    | kidpid ->
        (* parent copies the socket to standard output *)
        let input = Unix.in_channel_of_descr socket in
        try
          while true do
            let line = input_line input in
            output_string stdout line;
            output_string stdout "\n";
            flush stdout
          done
        with End_of_file ->
          Unix.kill kidpid Sys.sigterm
 
let () = exit 0

[править] Forking Servers

(* set up the socket SERVER, bind and listen ... *)
#load "unix.cma";;
 
let rec reaper signal =
  try while true do ignore (Unix.waitpid [Unix.WNOHANG] (-1)) done
  with Unix.Unix_error (Unix.ECHILD, _, _) -> ();
  Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper)
 
let () =
  Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper)
 
let () =
  while true do
    try
      let (client, addr) = Unix.accept server in
      let pid = Unix.fork () in
      if pid = 0 then                   (* parent *)
        begin
          Unix.close server;            (* no use to child *)
          (* ... do something *)
          exit 0                        (* child leaves *)
        end
      else
        begin
          Unix.close client             (* no use to parent *)
        end
    with Unix.Unix_error (Unix.EINTR, _, _) -> ()
  done

[править] Pre-Forking Servers

#!/usr/bin/ocaml
(* preforker - server who forks first *)
#load "unix.cma";;
 
(* global variables *)
let prefork = 5
let max_clients_per_child = 5
module PidSet = Set.Make(struct type t = int let compare = compare end)
let children = ref PidSet.empty
 
(* takes care of dead children *)
let rec reaper _ =
  Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper);
  match Unix.wait ()
  with (pid, _) -> children := PidSet.remove pid !children
 
(* signal handler for SIGINT *)
let rec huntsman _ =
  (* we're going to kill our children *)
  Sys.set_signal Sys.sigchld Sys.Signal_ignore;
  PidSet.iter
    (fun pid ->
       try Unix.kill Sys.sigint pid with Unix.Unix_error _ -> ())
    !children;
  (* clean up with dignity *)
  exit 0
 
let make_new_child server =
  (* block signal for fork *)
  let sigset = [Sys.sigint] in
  ignore (Unix.sigprocmask Unix.SIG_BLOCK sigset);
 
  match Unix.fork () with
    | 0 ->
        (* Child can *not* return from this subroutine. *)
        (* make SIGINT kill us as it did before *)
        Sys.set_signal Sys.sigint Sys.Signal_default;
 
        (* unblock signals *)
        ignore (Unix.sigprocmask Unix.SIG_UNBLOCK sigset);
 
        (* handle connections until we've reached max_clients_per_child *)
        for i = 1 to max_clients_per_child do
          let (client, _) = Unix.accept server in
          (* do something with the connection *)
          ()
        done;
 
        (* tidy up gracefully and finish *)
 
        (* this exit is VERY important, otherwise the child will become
           a producer of more and more children, forking yourself into
           process death. *)
        exit 0
    | pid ->
        (* Parent records the child's birth and returns. *)
        ignore (Unix.sigprocmask Unix.SIG_UNBLOCK sigset);
        children := PidSet.add pid !children
 
let () =
  (* establish SERVER socket, bind and listen. *)
  let server = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  Unix.setsockopt server Unix.SO_REUSEADDR true;
  Unix.bind server (Unix.ADDR_INET (Unix.inet_addr_any, 6969));
  Unix.listen server 10;
 
  (* Fork off our children. *)
  for i = 1 to prefork do
    make_new_child server
  done;
 
  (* Install signal handlers. *)
  Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper);
  Sys.set_signal Sys.sigint (Sys.Signal_handle huntsman);
 
  (* And maintain the population. *)
  while true do
    (* wait for a signal (i.e., child's death) *)
    Unix.pause ();
    for i = (PidSet.cardinal !children) to (prefork - 1) do
      (* top up the child pool *)
      make_new_child server
    done
  done

[править] Non-Forking Servers

#!/usr/bin/ocaml
(* nonforker - server who multiplexes without forking *)
#load "unix.cma";;
 
let port = 1685                         (* change this at will *)
 
(* Listen to port. *)
let server = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0
let () =
  Unix.setsockopt server Unix.SO_REUSEADDR true;
  Unix.bind server (Unix.ADDR_INET (Unix.inet_addr_any, port));
  Unix.listen server 10;
  Unix.set_nonblock server
 
module FDSet =
  Set.Make(struct type t = Unix.file_descr let compare = compare end)
let clients = ref (FDSet.singleton server)
 
(* begin with empty buffers *)
let inbuffer = Hashtbl.create 0
let outbuffer = Hashtbl.create 0
let ready = Hashtbl.create 0
 
let buffer_size = 8192
let buffer = String.make buffer_size '\000'
 
(* handle deals with all pending requests for client *)
let handle client requests =
  (* requests are in ready[client] *)
  (* send output to outbuffer[client] *)
  List.iter
    (fun request ->
       (* request is the text of the request *)
       let data = Printf.sprintf "You said: %s\n" request in
       (* put text of reply into outbuffer[client] *)
       Hashtbl.replace outbuffer client
         (try Hashtbl.find outbuffer client ^ data
          with Not_found -> data))
    requests
 
(* Main loop: check reads/accepts, check writes, check ready to process *)
let () =
  while true do
    (* check for new information on the connections we have *)
 
    let (can_read, _, _) =
      Unix.select (FDSet.elements !clients) [] [] 1.0 in
    List.iter
      (fun client ->
         if client = server
         then
           begin
             (* accept a new connection *)
             let (client, addr) = Unix.accept server in
             clients := FDSet.add client !clients;
             Unix.set_nonblock client
           end
         else
           begin
             (* read data *)
             let chars_read =
               try
                 Some (Unix.read client buffer 0 buffer_size)
               with Unix.Unix_error (error, _, _) ->
                 prerr_endline (Unix.error_message error);
                 None in
 
             match chars_read with
               | None | Some 0 ->
                   (* This would be the end of file, so close the client *)
                   Hashtbl.remove inbuffer client;
                   Hashtbl.remove outbuffer client;
                   Hashtbl.remove ready client;
 
                   clients := FDSet.remove client !clients;
                   Unix.close client
 
               | Some chars_read ->
                   let data = String.sub buffer 0 chars_read in
                   Hashtbl.replace inbuffer client
                     (try Hashtbl.find inbuffer client ^ data
                      with Not_found -> data);
 
                   (* test whether the data in the buffer or the data we *)
                   (* just read means there is a complete request waiting *)
                   (* to be fulfilled.  If there is, set ready[client] *)
                   (* to the requests waiting to be fulfilled. *)
                   try
                     while true do
                       let data = Hashtbl.find inbuffer client in
                       let index = String.index data '\n' in
                       Hashtbl.replace inbuffer client
                         (String.sub data
                            (index + 1)
                            (String.length data - index - 1));
                       Hashtbl.replace ready client
                         ((try Hashtbl.find ready client
                           with Not_found -> [])
                          @ [String.sub data 0 index])
                     done
                   with Not_found -> ()
           end)
      can_read;
 
    (* Any complete requests to process? *)
    Hashtbl.iter handle ready;
    Hashtbl.clear ready;
 
    (* Buffers to flush? *)
    let (_, can_write, _) =
      Unix.select [] (FDSet.elements !clients) [] 1.0 in
    (* Skip client if we have nothing to say *)
    let can_write =
      List.filter (Hashtbl.mem outbuffer) can_write in
    List.iter
      (fun client ->
         let data = Hashtbl.find outbuffer client in
         let chars_written =
           try
             Some (Unix.single_write client data 0 (String.length data))
           with
             | Unix.Unix_error (Unix.EAGAIN, _, _)
             | Unix.Unix_error (Unix.EWOULDBLOCK, _, _) ->
                 prerr_endline "I was told I could write, but I can't.";
                 Some 0
             | Unix.Unix_error (error, _, _) ->
                 prerr_endline (Unix.error_message error);
                 None in
 
         match chars_written with
           | Some chars_written ->
               if chars_written = String.length data
               then Hashtbl.remove outbuffer client
               else Hashtbl.replace outbuffer client
                 (String.sub data chars_written
                    (String.length data - chars_written))
           | None ->
               (* Couldn't write all the data, and it wasn't because *)
               (* it would have blocked.  Shutdown and move on. *)
               Hashtbl.remove inbuffer client;
               Hashtbl.remove outbuffer client;
               Hashtbl.remove ready client;
 
               clients := FDSet.remove client !clients;
               Unix.close client)
      can_write;
 
    let (_, _, has_exception) =
      Unix.select [] [] (FDSet.elements !clients) 0.0 in
    List.iter
      (fun client ->
         (* Deal with out-of-band data here, if you want to. *)
         ())
      has_exception;
  done

[править] Writing a Multi-Homed Server

#load "unix.cma";;
 
let server =
  Unix.socket Unix.PF_INET Unix.SOCK_STREAM
    (Unix.getprotobyname "tcp").Unix.p_proto
 
let () =
  Unix.setsockopt server Unix.SO_REUSEADDR true;
  Unix.bind server (Unix.ADDR_INET (Unix.inet_addr_any, server_port));
  Unix.listen server 10;
 
  (* accept loop *)
  while true do
    let client, sockaddr = Unix.accept server in
    match Unix.getsockname client with
      | Unix.ADDR_INET (addr, port) ->
          print_endline (Unix.string_of_inet_addr addr)
      | _ -> assert false
  done
 
(*-----------------------------*)
 
#load "unix.cma";;
 
let port = 4269                 (* port to bind to *)
let host = "specific.host.com"  (* virtual host to listen on *)
 
let server =
  Unix.socket Unix.PF_INET Unix.SOCK_STREAM
    (Unix.getprotobyname "tcp").Unix.p_proto
 
let () =
  let addr = (Unix.gethostbyname host).Unix.h_addr_list.(0) in
  Unix.bind server (Unix.ADDR_INET (addr, port));
  Unix.listen server 10;
  while true do
    let client, sockaddr = Unix.accept server in
    (* ... *)
    ()
  done

[править] Making a Daemon Server

#load "unix.cma";;
 
let () =
  (* for the paranoid *)
  (* Unix.handle_unix_error Unix.chroot "/var/daemon"; *)
 
  (* fork and let parent exit *)
  let pid = Unix.fork () in
  if pid > 0 then exit 0;
 
  (* create a new session and abandon the controlling process *)
  ignore (Unix.setsid ())
 
(* flag indicating it is time to exit *)
let time_to_die = ref false
 
(* trap fatal signals *)
let () =
  let signal_handler _ = time_to_die := true in
  List.iter
    (fun signal ->
       Sys.set_signal signal (Sys.Signal_handle signal_handler))
    [Sys.sigint; Sys.sigterm; Sys.sighup]
  (* trap or ignore Sys.sigpipe *)
 
(* server loop *)
let () =
  while not !time_to_die do
    (* ... *)
    ()
  done

[править] Restarting a Server on Demand

#load "unix.cma";;
 
let self = "/usr/bin/ocaml"
let args = self :: Array.to_list Sys.argv
 
let phoenix _ =
  (* close all your connections, kill your children, and *)
  (* generally prepare to be reincarnated with dignity. *)
  try
    ignore (Unix.sigprocmask Unix.SIG_UNBLOCK [Sys.sighup]);
    Unix.execv self (Array.of_list args)
  with Unix.Unix_error (e, _, _) ->
    Printf.eprintf "Couldn't restart: %s\n%!"
      (Unix.error_message e)
 
let () =
  Sys.set_signal Sys.sighup (Sys.Signal_handle phoenix)
 
(*-----------------------------*)
 
(* This recipe uses the Ocaml-Syck YAML parser available at:
   http://ocaml-syck.sourceforge.net/ *)
 
#directory "+yaml";;
#load "yaml.cma";;
#load "unix.cma";;
 
let yaml_parser = YamlParser.make ()
 
let config_file = "/usr/local/etc/myprog/server_conf.yaml"
let config = ref (YamlNode.SCALAR ("", ""))
 
let read_config _ =
  let in_channel = open_in config_file in
  let lines = ref [] in
  try
    while true do
      let line = input_line in_channel in
      lines := line :: !lines
    done
  with End_of_file ->
    close_in in_channel;
    config :=
      YamlParser.parse_string yaml_parser
        (String.concat "\n" (List.rev !lines))
 
let () =
  read_config ();
  Sys.set_signal Sys.sighup (Sys.Signal_handle read_config)

[править] Program: backsniff

Oct  4 11:01:16 pedro sniffer: Connection from 10.0.0.4 to 10.0.0.1:echo
 
(*-----------------------------*)
 
echo stream tcp nowait nobody /usr/bin/ocaml ocaml /path/to/backsniff.ml
 
(*-----------------------------*)
 
(* backsniff - log attempts to connect to particular ports *)
#load "unix.cma";;
 
(* This recipe uses syslog-ocaml, which is available at:
   http://www.cs.cmu.edu/~ecc/software.html *)
#directory "+syslog";;
#load "syslog.cma";;
 
(* identify my port and address *)
let sockname =
  try Unix.getsockname Unix.stdin
  with Unix.Unix_error (e, _, _) ->
    Printf.eprintf "Couldn't identify myself: %s\n%!"
      (Unix.error_message e);
    exit 1
let iaddr, port =
  match sockname with
    | Unix.ADDR_INET (iaddr, port) -> iaddr, port
    | _ -> assert false
let my_address = Unix.string_of_inet_addr iaddr
 
(* get a name for the service *)
let service =
  try (Unix.getservbyport port "tcp").Unix.s_name
  with Not_found -> string_of_int port
 
(* now identify remote address *)
let sockname =
  try Unix.getpeername Unix.stdin
  with Unix.Unix_error (e, _, _) ->
    Printf.eprintf "Couldn't identify other end: %s\n%!"
      (Unix.error_message e);
    exit 1
let iaddr, port =
  match sockname with
    | Unix.ADDR_INET (iaddr, port) -> iaddr, port
    | _ -> assert false
let ex_address = Unix.string_of_inet_addr iaddr
 
(* and log the information *)
let () =
  let log = Syslog.openlog ~flags:[] ~facility:`LOG_DAEMON "sniffer" in
  Syslog.syslog log `LOG_NOTICE
    (Printf.sprintf "Connection from %s to %s:%s\n"
       ex_address my_address service);
  Syslog.closelog log;
  exit 0

[править] Program: fwdport

#!/usr/bin/ocaml
(* fwdport -- act as proxy forwarder for dedicated services *)
 
#load "str.cma";;
#load "unix.cma";;
 
let children = Hashtbl.create 0    (* hash of outstanding child processes *)
let remote = ref ""                (* whom we connect to on the outside *)
let local = ref ""                 (* where we listen to on the inside *)
let service = ref ""               (* our service name or port number *)
let proxy_server = ref Unix.stdin  (* the socket we accept() from *)
 
(* process command line switches *)
let check_args () =
  Arg.parse
    [
      "-r",       Arg.Set_string remote,  "Remote host";
      "-remote",  Arg.Set_string remote,  "Remote host";
      "-l",       Arg.Set_string local,   "Local interface";
      "-local",   Arg.Set_string local,   "Local interface";
      "-s",       Arg.Set_string service, "Service";
      "-service", Arg.Set_string service, "Service";
    ]
    (fun s ->
       raise (Arg.Bad (Printf.sprintf "unexpected argument `%s'" s)))
    (Printf.sprintf "usage: %s [ -remote host ] [ -local interface ] [ -service service ]" Sys.argv.(0));
  if !remote = ""
  then (prerr_endline "Need remote"; exit 1);
  if !local = "" && !service = ""
  then (prerr_endline "Need local or service"; exit 1);
  if !local = ""
  then local := "localhost"
 
let parse_host host =
  match Str.split (Str.regexp ":") host with
    | [] -> "", ""
    | host :: [] -> host, ""
    | host :: service :: _ -> host, service
 
let resolve_host host =
  try (Unix.gethostbyname host).Unix.h_addr_list.(0)
  with Not_found ->
    Printf.eprintf "Host not found: %s\n" host;
    exit 1
 
let resolve_service service =
  try int_of_string service
  with Failure _ ->
    try (Unix.getservbyname service "tcp").Unix.s_port
    with Not_found ->
      Printf.eprintf "Service not found: %s\n" service;
      exit 1
 
(* begin our server *)
let start_proxy () =
  try
    let proto = (Unix.getprotobyname "tcp").Unix.p_proto in
    let addr, port =
      match parse_host (!local ^ ":" ^ !service) with
        | host, service ->
            (resolve_host host,
             resolve_service service) in
    proxy_server := Unix.socket Unix.PF_INET Unix.SOCK_STREAM proto;
    Unix.setsockopt !proxy_server Unix.SO_REUSEADDR true;
    Unix.bind !proxy_server (Unix.ADDR_INET (addr, port));
    Unix.listen !proxy_server 128;
    Printf.printf "[Proxy server on %s initialized.]\n%!"
      (if !local <> "" then !local else !service)
  with Unix.Unix_error (e, _, _) ->
    Printf.eprintf "Can't create proxy server: %s\n%!"
      (Unix.error_message e);
    exit 1
 
(* helper function to produce a nice string in the form HOST:PORT *)
let peerinfo sock =
  match Unix.getpeername sock with
    | Unix.ADDR_INET (addr, port) ->
        let hostinfo = Unix.gethostbyaddr addr in
        Printf.sprintf "%s:%d" hostinfo.Unix.h_name port
    | _ -> assert false
 
(* somebody just died.  keep harvesting the dead until  *)
(* we run out of them.  check how long they ran. *)
let rec reaper signal =
  begin
    let result =
      try Some (Unix.waitpid [Unix.WNOHANG] (-1))
      with Unix.Unix_error (Unix.ECHILD, _, _) -> None in
    match result with
      | Some (child, status) when Hashtbl.mem children child ->
          let start = Hashtbl.find children child in
          let runtime = Unix.time () -. start in
          Printf.printf "Child %d ran %dm%fs\n%!"
            child
            (int_of_float (runtime /. 60.))
            (mod_float runtime 60.);
          Hashtbl.remove children child;
          reaper signal
      | Some (child, status) ->
          Printf.printf "Bizarre kid %d exited with %s\n%!"
            child
            (match status with
               | Unix.WEXITED code ->
                   "code " ^ string_of_int code
               | Unix.WSTOPPED signal
               | Unix.WSIGNALED signal ->
                   "signal " ^ string_of_int signal);
          reaper signal
      | None -> ()
  end;
  (* If I had to choose between System V and 4.2, I'd resign. *)
  (* --Peter Honeyman *)
  Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper)
 
let service_clients () =
  (* harvest the moribund *)
  Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper);
 
  (* an accepted connection here means someone inside wants out *)
  while true do
    try
      begin
        let local_client = fst (Unix.accept !proxy_server) in
        let lc_info = peerinfo local_client in
        Printf.printf "[Connect from %s]\n%!" lc_info;
 
        let proto = (Unix.getprotobyname "tcp").Unix.p_proto in
        let addr, port =
          match parse_host (!remote ^ ":" ^ !service) with
            | host, service ->
                (resolve_host host,
                 resolve_service service) in
        Printf.printf "[Connecting to %s...%!" !remote;
        let remote_server = Unix.socket Unix.PF_INET Unix.SOCK_STREAM proto in
        Unix.connect remote_server (Unix.ADDR_INET (addr, port));
        Printf.printf "done]\n%!";
 
        let local_in = Unix.in_channel_of_descr local_client in
        let local_out = Unix.out_channel_of_descr local_client in
        let remote_in = Unix.in_channel_of_descr remote_server in
        let remote_out = Unix.out_channel_of_descr remote_server in
 
        match Unix.fork () with
          | 0 ->
              (* at this point, we are the forked child process dedicated *)
              (* to the incoming client.  but we want a twin to make i/o *)
              (* easier. *)
 
              Unix.close !proxy_server;  (* no use to slave *)
 
              (* now each twin sits around and ferries lines of data. *)
              (* see how simple the algorithm is when you can have *)
              (* multiple threads of control? *)
 
              (match Unix.fork () with
                 | 0 ->
                     (* this is the fork's child, the master's grandchild *)
                     (try
                          while true do
                            let line = input_line local_in in
                            Printf.fprintf remote_out "%s\n%!" line
                          done
                      with End_of_file ->
                        (* kill my twin cause we're done *)
                        Unix.kill (Unix.getppid ()) Sys.sigterm)
                 | kidpid ->
                     (* this is the fork's parent, the master's child *)
                     (try
                          while true do
                            let line = input_line remote_in in
                            Printf.fprintf local_out "%s\n%!" line
                          done
                      with End_of_file ->
                        (* kill my twin cause we're done *)
                        Unix.kill kidpid Sys.sigterm));
 
              exit 0  (* whoever's still alive bites it *)
 
          | kidpid ->
              (* remember his start time *)
              Hashtbl.replace children kidpid (Unix.time ());
              Unix.close remote_server;  (* no use to master *)
              Unix.close local_client;   (* likewise *)
      end
    with Unix.Unix_error (Unix.EINTR, "accept", _) -> ()
  done
 
let () =
  check_args ();                (* processing switches *)
  start_proxy ();               (* launch our own server *)
  service_clients ();           (* wait for incoming *)
  prerr_endline "NOT REACHED";  (* you can't get here from there *)
  exit 1