使用 OCaml 编组数据通信客户端 - 服务器

Dav*_*son 5 ocaml marshalling js-of-ocaml

我想用 OCaml 中的服务器做一个客户端 js_of_ocaml 应用程序,约束如下所述,我想知道下面的方法是否正确,或者是否有更有效的方法。服务器有时会发送大量数据(> 30MB)。

为了使客户端和服务器之间的通信更安全、更高效,我在 .mli 文件中共享一个类型 t,如下所示:

type client_to_server =
| Say_Hello
| Do_something_with of int

type server_to_client =
| Ack
| Print of string * int
Run Code Online (Sandbox Code Playgroud)

然后,将此类型编组为字符串并在网络上发送。我知道在客户端,缺少某些类型(Int64.t)。

另外,在客户端发送的 XMLHTTPRequest 中,我们希望从服务器接收多个编组对象,有时采用流式模式(即:处理loading在请求状态期间收到的编组对象(如果可能),而不是仅在done状态期间)。

这些约束迫使我们responseText将 XMLHTTPRequest的字段与 content-type 一起使用application/octet-stream

此外,当我们从 返回响应时responseText,会进行编码转换,因为 JavaScript 的字符串是 UTF-16。但是编组的对象是二进制数据,为了检索我们的二进制数据,我们做了必要的事情(通过覆盖字符集x-user-defined并在responseText字符串的每个字符上应用掩码)。

服务器(OCaml 中的 HTTP 服务器)正在做一些简单的事情:

let process_request req =
    let res = process_response req in
    let s = Marshal.to_string res [] in
    send s
Run Code Online (Sandbox Code Playgroud)

然而,在客户端,js_of_ocaml 的实际 JavaScript 原语caml_marshal_data_size需要一个 MlString。但是在流模式下,我们不想将javascript的字符串转换成MlString(它可以迭代完整的字符串),我们更喜欢做大小验证和解组(以及编码问题的掩码的应用)在读取的字节上。因此,我用 javascript 编写了自己的元组元组。

处理请求和响应的客户端代码是:

external marshal_total_size : Js.js_string Js.t -> int -> int = "my_marshal_total_size"
external marshal_from_string : Js.js_string Js.t -> int -> 'a = "my_marshal_from_string"

let apply (f:server_to_client -> unit) (str:Js.js_string Js.t) (ofs:int) : int =
  let len = str##length in
  let rec aux pos =
    let tsize = 
      try Some (pos + My_primitives.marshal_total_size str pos)
      with Failure _ -> None
    in
    match tsize with
    | Some tsize when tsize <= len ->
      let data = My_primitives.marshal_from_string str pos in
      f data;
      aux tsize
    | _ -> pos
  in
  aux ofs

let reqcallback f req ofs =
  match req##readyState, req##status with
  | XmlHttpRequest.DONE, 200 ->
      ofs := apply f req##responseText !ofs

  | XmlHttpRequest.LOADING, 200 ->
      ignore (apply f req##responseText !ofs)

  | _, 200 -> ()

  | _, i -> process_error i

let send (f:server_to_client -> unit) (order:client_to_server) =
  let order = Marshal.to_string order [] in
  let msg = Js.string (my_encode order) in (* Do some stuff *)
  let req = XmlHttpRequest.create () in
  req##_open(Js.string "POST", Js.string "/kernel", Js._true);
  req##setRequestHeader(Js.string "Content-Type",
            Js.string "application/octet-stream");
  req##onreadystatechange <- Js.wrap_callback (reqcallback f req (ref 0));
  req##overrideMimeType(Js.string "application/octet-stream; charset=x-user-defined");
  req##send(Js.some msg)
Run Code Online (Sandbox Code Playgroud)

原语是:

//Provides: my_marshal_header_size
var my_marshal_header_size = 20;

//Provides: my_int_of_char
function my_int_of_char(s, i) {
    return (s.charCodeAt(i) & 0xFF); // utf-16 char to 8 binary bit
}

//Provides: my_marshal_input_value_from_string 
//Requires: my_int_of_char, caml_int64_float_of_bits, MlStringFromArray
//Requires: caml_int64_of_bytes, caml_marshal_constants, caml_failwith
var my_marshal_input_value_from_string = function () {
    /* Quite the same thing but with a custom Reader which
       will call my_int_of_char for each byte read */
}


//Provides: my_marshal_data_size
//Requires: caml_failwith, my_int_of_char
function my_marshal_data_size(s, ofs) {
    function get32(s,i) {
    return (my_int_of_char(s, i) << 24) | (my_int_of_char(s, i + 1) << 16) |
        (my_int_of_char(s, i + 2) << 8) | (my_int_of_char(s, i + 3));
    }
    if (get32(s, ofs) != (0x8495A6BE|0))
    caml_failwith("MyMarshal.data_size");
    return (get32(s, ofs + 4));
}

//Provides: my_marshal_total_size
//Requires: my_marshal_data_size, my_marshal_header_size, caml_failwith
function my_marshal_total_size(s, ofs) {
    if ( ofs < 0 || ofs > s.length - my_marshal_header_size ) 
    caml_failwith("Invalid argument");
    else return my_marshal_header_size + my_marshal_data_size(s, ofs);
}
Run Code Online (Sandbox Code Playgroud)

这是将大型 OCaml 值从服务器传输到客户端的最有效方法,还是节省时间和空间的替代方案?

小智 3

您是否尝试使用EventSource https://developer.mozilla.org/en-US/docs/Web/API/EventSource

您可以流式传输 json 数据而不是封送数据。 Json.unsafe_input应该比解组更快。

class type eventSource =
 object
  method onmessage :
    (eventSource Js.t, event Js.t -> unit) Js.meth_callback
    Js.writeonly_prop
 end
and event =
 object
  method data : Js.js_string Js.t Js.readonly_prop
  method event : Js.js_string Js.t Js.readonly_prop
 end

let eventSource : (Js.js_string Js.t -> eventSource Js.t) Js.constr = 
   Js.Unsafe.global##_EventSource

let send (f:server_to_client -> unit) (order:client_to_server) url_of_order =
 let url = url_of_order order in
 let es = jsnew eventSource(Js.string url) in
 es##onmessage <- Js.wrap_callback (fun e ->
  let d = Json.unsafe_input (e##data) in
  f d);
 ()
Run Code Online (Sandbox Code Playgroud)

在服务器端,您需要依赖 deriving_json http://ocsigen.org/js_of_ocaml/2.3/api/Deriving_Json来序列化您的数据

type server_to_client =
 | Ack
 | Print of string * int 
deriving (Json)

let process_request req =
  let res = process_response req in
  let data = Json_server_to_client.to_string res in
  send data
Run Code Online (Sandbox Code Playgroud)

注意1:Deriving_json使用js_of_ocaml中值的内部表示将ocaml值序列化为json。是一个依赖浏览器原生 JSON 支持的Json.unsafe_input快速反序列化器。Deriving_json

注意2:Deriving_jsonJson.unsafe_input注意ocaml字符串编码