Asynchronous 使用Async在OCaml中编写TCP服务器/客户端

Asynchronous 使用Async在OCaml中编写TCP服务器/客户端,asynchronous,ocaml,Asynchronous,Ocaml,这里是OCaml的初学者,听说它非常擅长构建网络应用程序。我决定用异步库弄脏我的手 我正在尝试实现类似netcat的东西,其中服务器和客户端可以相互发送消息 这是启动服务器的功能: let start_server p = let host_and_port = Tcp.Server.create ~on_handler_error:`Raise (Tcp.Where_to_listen.of_port p) (fun so

这里是OCaml的初学者,听说它非常擅长构建网络应用程序。我决定用异步库弄脏我的手

我正在尝试实现类似netcat的东西,其中服务器和客户端可以相互发送消息

这是启动服务器的功能:

let start_server p =
    let host_and_port =
        Tcp.Server.create
        ~on_handler_error:`Raise
        (Tcp.Where_to_listen.of_port p)
        (fun sock reader writer ->
            conn_handler sock reader writer)
in
ignore (host_and_port : (Socket.Address.Inet.t, int) Tcp.Server.t Deferred.t);
Deferred.never ()
这是启动客户端的函数:

let start_client a p =
    Tcp.with_connection
    (Tcp.Where_to_connect.of_host_and_port { host = a; port = p})
    (fun sock reader writer ->
        conn_handler sock reader writer)
这两个函数都调用函数conn_handler,该函数实现消息发送逻辑

客户端/服务器必须同时能够:

从stdin读取消息并发送。 接收消息并发回已接收的消息。 conn_处理程序的当前实现:


逻辑的顺序有个错误,我没办法弄清楚。有人知道如何同时实现这两种功能吗?

如果您启用代码的自动缩进(例如,使用ocp缩进),问题很容易被发现。例如,您的代码

let rec conn_handler s r w =
    let stdin = Lazy.force Reader.stdin in
    Reader.read_line stdin >>= function
    | `Eof -> return ()
    | `Ok x ->
        Writer.write_line w x;

    Reader.read_line r >>= function
    | `Eof  -> return ()
    | `Ok "exit" -> return ()
    | `Ok x -> 
        print_endline x;
        Writer.write_line w "Acknowledged";
        conn_handler s r w
将缩进为

let rec conn_handler s r w =
  let stdin = Lazy.force Reader.stdin in
  Reader.read_line stdin >>= function
  | `Eof -> return ()
  | `Ok x ->
    Writer.write_line w x;
    (* woops, it actually happens in this branch *)
    Reader.read_line r >>= function
    | `Eof  -> return ()
    | `Ok "exit" -> return ()
    | `Ok x ->
      print_endline x;
      Writer.write_line w "Acknowledged";
      conn_handler s r w
这个问题是双重的。首先,您尝试使用分号;将两个匹配表达式分开,但自;具有更高的优先级—它仅将第二个匹配绑定到第一个匹配的第二个分支。换句话说,分号不充当匹配终止符,它只是将一个表达式向左排序,将一个表达式向右排序

 match x with
 | X -> ()
 | Y -> 
   do_something ();
   do_something_else ();
   match y with
   | Z -> ...
如果您确实想一个接一个地进行两个匹配,则需要使用括号或开始/结束来分隔它们,例如

 begin match x with 
  | A -> ..
  | ...
  | Z -> ..
 end;
 begin match y with 
  | A -> ..
  | ...
  | Z -> ..
 end;    
因此,您的连接处理程序正在从标准输入读取一行,当它准备就绪时,它会将作业发布到写入器队列,并且只有在读取和写入之后,才会从通道r进行读取

我们不能在这里使用分号来组合两个任务,因为这两个任务都是延迟的,也就是说,它们的类型为unit deferred.t not unit。因此,我们必须使用延迟接口提供的一些组合器。我们可以使用类型为“a t->”b t->“a*”b t的两个任务,但由于我们的两个任务都评估为单位值,因此可能没有理由构建,值。因此,我们可以使用all_unit combinator,它获取任务列表并计算为unit Deferred.t,一旦确定了所有任务,就会确定unit Deferred.t,例如,下面的实现稍微接近您的意图

let rec conn_handler s r w =
  let stdin = Lazy.force Reader.stdin in
  let task1 = Reader.read_line stdin >>= function
    | `Eof -> return ()
    | `Ok x ->
      Writer.write_line w x;
      return () in
  let task2 = Reader.read_line r >>= function
    | `Eof  -> return ()
    | `Ok "exit" -> return ()
    | `Ok x ->
      print_endline x;
      Writer.write_line w "Acknowledged";
      return () in
  Deferred.all_unit [
    task1;
    task2;
  ] >>= fun () ->
  conn_handler s r w
在每个步骤中,我们从stdin中读取一行,同时从提供的输入中读取一行,并写入确认的消息。然后,它将等待两个任务完成并重复。因此,我们再次创建了一个不幸的同步。因此,我们可能设计的步骤不正确,因为每个任务都应该有自己的循环,而不是一个循环按顺序运行两个任务,例如

 let conn_handler s r w =
  let stdin = Lazy.force Reader.stdin in
  let rec task1 () = Reader.read_line stdin >>= function
    | `Eof -> return ()
    | `Ok x ->
      Writer.write_line w x;
      task1 () in
  let rec task2 () = Reader.read_line r >>= function
    | `Eof  -> return ()
    | `Ok "exit" -> return ()
    | `Ok x ->
      print_endline x;
      Writer.write_line w "Acknowledged";
      task2 () in
  Deferred.any_unit [
    task1 ();
    task2 ();
  ]
现在,我们独立运行两个任务,并在其中任何一个停止时立即停止

虽然这个示例是演示性的,但使用异步中的管道机制可以更轻松地解决任务本身。像这样的,

let conn_handler s r w =
  let input = Reader.lines (Lazy.force Reader.stdin) in
  let src = Reader.pipe r
  and dst = Writer.pipe w in
  Deferred.any_unit [
    Pipe.transfer_id input dst;
    Pipe.transfer src dst ~f:(function
        | "exit" -> Pipe.close_read src; "Goodbye"
        | s ->
          print_endline ("Received: " ^ s);
          "Acknowledged")
  ]
let conn_handler s r w =
  let input = Reader.lines (Lazy.force Reader.stdin) in
  let src = Reader.pipe r
  and dst = Writer.pipe w in
  Deferred.any_unit [
    Pipe.transfer_id input dst;
    Pipe.transfer src dst ~f:(function
        | "exit" -> Pipe.close_read src; "Goodbye"
        | s ->
          print_endline ("Received: " ^ s);
          "Acknowledged")
  ]