>웹 프론트엔드 >JS 튜토리얼 >디노 통신 구현 방법(코드 포함)

디노 통신 구현 방법(코드 포함)

不言
不言앞으로
2019-03-11 17:02:272565검색

이 기사는 데노 통신 구현 방법(코드 포함)에 대한 내용입니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

통신 방법

deno 실행 코드는 동기식 및 비동기식 방법을 포함하여 노드와 유사합니다. Promise.then을 통해 구현됩니다.

Typescript/Javascript는 Rust를 호출합니다

이전 섹션에서는 deno가 시작될 때 v8 격리 인스턴스가 초기화된다고 언급했습니다. 초기화 프로세스에서 C++ 함수는 v8 격리 인스턴스에 바인딩됩니다. v8이 Javascript 코드를 실행할 때 이러한 바인딩된 함수는 Javascript 함수처럼 호출될 수 있습니다. 구체적인 바인딩 구현은 다음과 같습니다.

void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) {
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(context);

  auto global = context->Global();

  auto deno_val = v8::Object::New(isolate);
  CHECK(global->Set(context, deno::v8_str("libdeno"), deno_val).FromJust());

  auto print_tmpl = v8::FunctionTemplate::New(isolate, Print);
  auto print_val = print_tmpl->GetFunction(context).ToLocalChecked();
  CHECK(deno_val->Set(context, deno::v8_str("print"), print_val).FromJust());

  auto recv_tmpl = v8::FunctionTemplate::New(isolate, Recv);
  auto recv_val = recv_tmpl->GetFunction(context).ToLocalChecked();
  CHECK(deno_val->Set(context, deno::v8_str("recv"), recv_val).FromJust());

  auto send_tmpl = v8::FunctionTemplate::New(isolate, Send);
  auto send_val = send_tmpl->GetFunction(context).ToLocalChecked();
  CHECK(deno_val->Set(context, deno::v8_str("send"), send_val).FromJust());

  auto eval_context_tmpl = v8::FunctionTemplate::New(isolate, EvalContext);
  auto eval_context_val =
      eval_context_tmpl->GetFunction(context).ToLocalChecked();
  CHECK(deno_val->Set(context, deno::v8_str("evalContext"), eval_context_val)
            .FromJust());

  auto error_to_json_tmpl = v8::FunctionTemplate::New(isolate, ErrorToJSON);
  auto error_to_json_val =
      error_to_json_tmpl->GetFunction(context).ToLocalChecked();
  CHECK(deno_val->Set(context, deno::v8_str("errorToJSON"), error_to_json_val)
            .FromJust());

  CHECK(deno_val->SetAccessor(context, deno::v8_str("shared"), Shared)
            .FromJust());
}

바인딩 완료 후 다음 코드를 통해 Typescript에서 C++ 메서드와 Typescript 메서드의 매핑을 구현할 수 있습니다.

libdeno.ts #🎜 🎜#
interface Libdeno {
  recv(cb: MessageCallback): void;

  send(control: ArrayBufferView, data?: ArrayBufferView): null | Uint8Array;

  print(x: string, isErr?: boolean): void;

  shared: ArrayBuffer;

  /** Evaluate provided code in the current context.
   * It differs from eval(...) in that it does not create a new context.
   * Returns an array: [output, errInfo].
   * If an error occurs, `output` becomes null and `errInfo` is non-null.
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  evalContext(code: string): [any, EvalErrorInfo | null];

  errorToJSON: (e: Error) => string;
}

export const libdeno = window.libdeno as Libdeno;
Typescript 코드를 실행할 때 libdeno를 도입하고 C++ 메서드를 직접 호출하기만 하면 됩니다. 예:

import { libdeno } from "./libdeno";
function sendInternal(
  builder: flatbuffers.Builder,
  innerType: msg.Any,
  inner: flatbuffers.Offset,
  data: undefined | ArrayBufferView,
  sync = true
): [number, null | Uint8Array] {
  const cmdId = nextCmdId++;
  msg.Base.startBase(builder);
  msg.Base.addInner(builder, inner);
  msg.Base.addInnerType(builder, innerType);
  msg.Base.addSync(builder, sync);
  msg.Base.addCmdId(builder, cmdId);
  builder.finish(msg.Base.endBase(builder));
  const res = libdeno.send(builder.asUint8Array(), data);
  builder.inUse = false;
  return [cmdId, res];
}
libdeno.send 메서드를 호출하여 데이터를 전달 c++를 사용한 다음 C++를 사용하여 Rust 코드를 호출하여 특정 엔지니어링 작업을 구현합니다.

Typescript 레이어 동기화 및 비동기 구현

Synchronization
Typescript에서는 sendInternal 메서드의 sync 매개변수만 true로 설정하면 됩니다. Rust에서는 sync 매개변수를 기반으로 동기 작업을 수행할지 비동기 작업을 수행할지 결정합니다. sync가 true인 경우 libdeono.send 메서드는 Rust와 TypeScript 간에 데이터를 전송하려면 데이터를 직렬화해야 합니다. 여기서는 직렬화 작업에 플랫버퍼 라이브러리가 사용됩니다.

const [cmdId, resBuf] = sendInternal(builder, innerType, inner, data, true);
비동기 구현
마찬가지로 비동기식 메서드를 구현하려면 sync 매개변수만 false로 설정하면 됩니다. 그러나 비동기식 작업에는 동기화에 비해 대체 메서드가 더 많습니다. . 비동기 통신을 수행할 때 libdeno.send 메소드는 호출 작업을 식별하기 위해 고유한 cmdId를 반환합니다. 동시에 비동기 통신이 완료된 후 cmdId를 키로, promise를 값으로 사용하여 promise 개체가 생성되고 맵에 추가됩니다. 코드는 다음과 같습니다:

const [cmdId, resBuf] = sendInternal(builder, innerType, inner, data, false);
  util.assert(resBuf == null);
  const promise = util.createResolvable<msg.Base>();
  promiseTable.set(cmdId, promise);
  return promise;
rust는 동기화 및 비동기성을 구현합니다

Typescript에서 libdeno.send 메서드를 호출하면 C++ 파일 바인딩.cc의 Send 메서드는 다음과 같습니다. 이 메소드는 deno가 초기화될 때 v8 isolate에 바인딩됩니다. Send 메소드에서는 메시지를 함수에 매핑하는 ops.rs 파일의 디스패치 메소드가 호출됩니다. 각 메시지 유형은 기능에 해당합니다. 예를 들어 파일 읽기 메시지는 파일 읽기 기능에 해당합니다.

pub fn dispatch(
  isolate: &Isolate,
  control: libdeno::deno_buf,
  data: libdeno::deno_buf,
) -> (bool, Box<Op>) {
  let base = msg::get_root_as_base(&control);
  let is_sync = base.sync();
  let inner_type = base.inner_type();
  let cmd_id = base.cmd_id();

  let op: Box<Op> = if inner_type == msg::Any::SetTimeout {
    // SetTimeout is an exceptional op: the global timeout field is part of the
    // Isolate state (not the IsolateState state) and it must be updated on the
    // main thread.
    assert_eq!(is_sync, true);
    op_set_timeout(isolate, &base, data)
  } else {
    // Handle regular ops.
    let op_creator: OpCreator = match inner_type {
      msg::Any::Accept => op_accept,
      msg::Any::Chdir => op_chdir,
      msg::Any::Chmod => op_chmod,
      msg::Any::Close => op_close,
      msg::Any::FetchModuleMetaData => op_fetch_module_meta_data,
      msg::Any::CopyFile => op_copy_file,
      msg::Any::Cwd => op_cwd,
      msg::Any::Dial => op_dial,
      msg::Any::Environ => op_env,
      msg::Any::Exit => op_exit,
      msg::Any::Fetch => op_fetch,
      msg::Any::FormatError => op_format_error,
      msg::Any::Listen => op_listen,
      msg::Any::MakeTempDir => op_make_temp_dir,
      msg::Any::Metrics => op_metrics,
      msg::Any::Mkdir => op_mkdir,
      msg::Any::Open => op_open,
      msg::Any::ReadDir => op_read_dir,
      msg::Any::ReadFile => op_read_file,
      msg::Any::Readlink => op_read_link,
      msg::Any::Read => op_read,
      msg::Any::Remove => op_remove,
      msg::Any::Rename => op_rename,
      msg::Any::ReplReadline => op_repl_readline,
      msg::Any::ReplStart => op_repl_start,
      msg::Any::Resources => op_resources,
      msg::Any::Run => op_run,
      msg::Any::RunStatus => op_run_status,
      msg::Any::SetEnv => op_set_env,
      msg::Any::Shutdown => op_shutdown,
      msg::Any::Start => op_start,
      msg::Any::Stat => op_stat,
      msg::Any::Symlink => op_symlink,
      msg::Any::Truncate => op_truncate,
      msg::Any::WorkerGetMessage => op_worker_get_message,
      msg::Any::WorkerPostMessage => op_worker_post_message,
      msg::Any::Write => op_write,
      msg::Any::WriteFile => op_write_file,
      msg::Any::Now => op_now,
      msg::Any::IsTTY => op_is_tty,
      msg::Any::Seek => op_seek,
      msg::Any::Permissions => op_permissions,
      msg::Any::PermissionRevoke => op_revoke_permission,
      _ => panic!(format!(
        "Unhandled message {}",
        msg::enum_name_any(inner_type)
      )),
    };
    op_creator(&isolate, &base, data)
  };

  // ...省略多余的代码
}
각 함수 유형은 Typescript에서 libdeo.send 메서드를 호출할 때 전달된 동기화 매개변수 값에 따라 동기 실행 또는 비동기 실행이 결정됩니다.

let (is_sync, op) = dispatch(isolate, control_buf, zero_copy_buf);
동기 실행
디스패치 메서드를 실행한 후 is_sync 변수가 반환됩니다. is_sync가 true이면 해당 메서드가 동기적으로 실행된다는 의미이고 op는 결과를 반환했습니다. Rust 코드는 실행 결과를 다시 동기화하기 위해 C++ 파일 api.cc에서 deno_respond 메서드를 호출합니다. deno_respond 메서드는 current_args_ 값을 기반으로 동기화 메시지인지 여부를 결정합니다. .

비동기 실행
deno에서 비동기 작업의 실행은 Rust의 Tokio 모듈을 통해 구현되며, 비동기 작업인 경우 값이 지정됩니다. is_sync가 false이면 op는 더 이상 실행 결과가 아니라 실행 함수입니다. tokio 모듈을 사용하여 함수를 비동기적으로 실행하는 스레드를 파생시킵니다.

    let task = op
      .and_then(move |buf| {
        let sender = tx; // tx is moved to new thread
        sender.send((zero_copy_id, buf)).expect("tx.send error");
        Ok(())
      }).map_err(|_| ());
    tokio::spawn(task);
deno가 초기화되면 코드는 다음과 같습니다.

let (tx, rx) = mpsc::channel::<(usize, Buf)>();
Pipelines는 비동기 작업이 새로운 스레드를 생성하므로 통신이 가능합니다. 실행되므로 하위 스레드는 기본 스레드와 직접 통신할 수 없으며 파이프라인 메커니즘을 통해 구현되어야 합니다. 비동기 코드 실행이 완료된 후 tx.send 메서드를 호출하여 실행 결과를 파이프라인에 추가합니다. 이벤트 루프는 매번 파이프라인에서 결과를 읽고 반환합니다.

Event Loop

비동기 작업은 이벤트 루프에 의존하므로 먼저 deno의 이벤트 루프를 설명하겠습니다. 실제로 이벤트 루프는 매우 간단합니다. 루프에서 실행되는 코드의 조건에 도달하면 이벤트 루프가 실행을 종료합니다. deno의 주요 이벤트 루프 코드는 다음과 같이 구현됩니다.

pub fn event_loop(&self) -> Result<(), JSError> {
    // Main thread event loop.
    while !self.is_idle() {
      match recv_deadline(&self.rx, self.get_timeout_due()) {
        Ok((zero_copy_id, buf)) => self.complete_op(zero_copy_id, buf),
        Err(mpsc::RecvTimeoutError::Timeout) => self.timeout(),
        Err(e) => panic!("recv_deadline() failed: {:?}", e),
      }
      self.check_promise_errors();
      if let Some(err) = self.last_exception() {
        return Err(err);
      }
    }
    // Check on done
    self.check_promise_errors();
    if let Some(err) = self.last_exception() {
      return Err(err);
    }
    Ok(())
  }
self.is_idle 메소드가 사용됩니다. 모든 비동기 작업이 완료되었는지 확인하기 위해 모든 비동기 작업이 실행된 후 이벤트 루프를 중지합니다. is_idle 메서드 코드는 다음과 같습니다.

fn is_idle(&self) -> bool {
    self.ntasks.get() == 0 && self.get_timeout_due().is_none()
  }
비동기 메서드 호출이 발생하면 다음 메서드가 실행됩니다. ntasks의 내부 값을 1씩 늘리기 위해 호출됩니다.

fn ntasks_increment(&self) {
    assert!(self.ntasks.get() >= 0);
    self.ntasks.set(self.ntasks.get() + 1);
  }
# 🎜🎜#이벤트 루프에서는 파이프라인에서 값을 얻을 때마다 이벤트 루프가 소비자 역할을 하고 하위 스레드가 비동기 메서드를 실행하는 메서드는 생산자 역할을 합니다. 이벤트 루프에서 실행 결과를 얻으면 ntasks_decrement 메서드가 호출되어 ntasks의 내부 값을 1씩 감소시킵니다. ntasks의 값이 0이면 이벤트 루프가 실행을 종료합니다. 각 루프에서는 파이프라인에서 얻은 값을 매개변수로 사용하고, Complete_op 메소드를 호출하여 결과를 반환합니다.

rust中将异步操作结果返回回去

在初始化v8实例时,绑定的c++方法中有一个Recv方法,该方法的作用时暴露一个Typescript的函数给rust,在deno的io.ts文件的start方法中执行libdeno.recv(handleAsyncMsgFromRust),将handleAsyncMsgFromRust函数通过c++方法暴露给rust。具体实现如下:

export function start(source?: string): msg.StartRes {
  libdeno.recv(handleAsyncMsgFromRust);

  // First we send an empty `Start` message to let the privileged side know we
  // are ready. The response should be a `StartRes` message containing the CLI
  // args and other info.
  const startResMsg = sendStart();

  util.setLogDebug(startResMsg.debugFlag(), source);

  setGlobals(startResMsg.pid(), startResMsg.noColor(), startResMsg.execPath()!);

  return startResMsg;
}

当异步操作执行完成后,可以在rust中直接调用handleAsyncMsgFromRust方法,将结果返回给Typescript。先看一下handleAsyncMsgFromRust方法的实现细节:

export function handleAsyncMsgFromRust(ui8: Uint8Array): void {
  // If a the buffer is empty, recv() on the native side timed out and we
  // did not receive a message.
  if (ui8 && ui8.length) {
    const bb = new flatbuffers.ByteBuffer(ui8);
    const base = msg.Base.getRootAsBase(bb);
    const cmdId = base.cmdId();
    const promise = promiseTable.get(cmdId);
    util.assert(promise != null, `Expecting promise in table. ${cmdId}`);
    promiseTable.delete(cmdId);
    const err = errors.maybeError(base);
    if (err != null) {
      promise!.reject(err);
    } else {
      promise!.resolve(base);
    }
  }
  // Fire timers that have become runnable.
  fireTimers();
}

从代码handleAsyncMsgFromRust方法的实现中可以知道,首先通过flatbuffer反序列化返回的结果,然后获取返回结果的cmdId,根据cmdId获取之前创建的promise对象,然后调用promise.resolve方法触发promise.then中的代码执行。

위 내용은 디노 통신 구현 방법(코드 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제