{"id":201344,"date":"2020-09-25T08:09:42","date_gmt":"2020-09-25T00:09:42","guid":{"rendered":"https:\/\/lrxjmw.cn\/?p=201344"},"modified":"2020-09-21T17:12:36","modified_gmt":"2020-09-21T09:12:36","slug":"nodejs-udp","status":"publish","type":"post","link":"https:\/\/lrxjmw.cn\/nodejs-udp.html","title":{"rendered":"Nodejs\u6e90\u7801\u89e3\u6790"},"content":{"rendered":"\n\n\n
\u5bfc\u8bfb<\/td>\n\u6211\u4eec\u770b\u5230\u521b\u5efa\u4e00\u4e2audp\u670d\u52a1\u5668\u5f88\u7b80\u5355\uff0c\u9996\u5148\u7533\u8bf7\u4e00\u4e2asocket\u5bf9\u8c61\uff0c\u5728nodejs\u4e2d\u548c\u64cd\u4f5c\u7cfb\u7edf\u4e2d\u4e00\u6837\uff0csocket\u662f\u5bf9\u7f51\u7edc\u901a\u4fe1\u7684\u4e00\u4e2a\u62bd\u8c61\uff0c\u6211\u4eec\u53ef\u4ee5\u628a\u4ed6\u7406\u89e3\u6210\u5bf9\u4f20\u8f93\u5c42\u7684\u62bd\u8c61\uff0c\u4ed6\u53ef\u4ee5\u4ee3\u8868tcp\u4e5f\u53ef\u4ee5\u4ee3\u8868udp\u3002<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n

\"\"<\/p>\n

\u6211\u4eec\u4ece\u4e00\u4e2a\u4f7f\u7528\u4f8b\u5b50\u5f00\u59cb\u770b\u770budp\u6a21\u5757\u7684\u5b9e\u73b0\u3002<\/p>\n

\r\nconst dgram = require('dgram'); \r\n\/\/ \u521b\u5efa\u4e00\u4e2asocket\u5bf9\u8c61 \r\nconst server = dgram.createSocket('udp4'); \r\n\/\/ \u76d1\u542cudp\u6570\u636e\u7684\u5230\u6765 \r\nserver.on('message', (msg, rinfo) => { \r\n  \/\/ \u5904\u7406\u6570\u636e});\/\/ \u7ed1\u5b9a\u7aef\u53e3 \r\nserver.bind(41234); \r\n<\/pre>\n

\u6211\u4eec\u770b\u5230\u521b\u5efa\u4e00\u4e2audp\u670d\u52a1\u5668\u5f88\u7b80\u5355\uff0c\u9996\u5148\u7533\u8bf7\u4e00\u4e2asocket\u5bf9\u8c61\uff0c\u5728nodejs\u4e2d\u548c\u64cd\u4f5c\u7cfb\u7edf\u4e2d\u4e00\u6837\uff0csocket\u662f\u5bf9\u7f51\u7edc\u901a\u4fe1\u7684\u4e00\u4e2a\u62bd\u8c61\uff0c\u6211\u4eec\u53ef\u4ee5\u628a\u4ed6\u7406\u89e3\u6210\u5bf9\u4f20\u8f93\u5c42\u7684\u62bd\u8c61\uff0c\u4ed6\u53ef\u4ee5\u4ee3\u8868tcp\u4e5f\u53ef\u4ee5\u4ee3\u8868udp\u3002\u6211\u4eec\u770b\u4e00\u4e0bcreateSocket\u505a\u4e86\u4ec0\u4e48\u3002<\/p>\n

\r\nfunction createSocket(type, listener) { \r\n  return new Socket(type, listener); \r\n} \r\n \r\nfunction Socket(type, listener) { \r\n  EventEmitter.call(this); \r\n  let lookup; \r\n  let recvBufferSize; \r\n  let sendBufferSize; \r\n \r\n  let options; \r\n  if (type !== null && typeof type === 'object') { \r\n    options = type; \r\n    type = options.type; \r\n    lookup = options.lookup; \r\n    recvBufferSize = options.recvBufferSize; \r\n    sendBufferSize = options.sendBufferSize; \r\n  } \r\n  const handle = newHandle(type, lookup);  \r\n  this.type = type; \r\n  if (typeof listener === 'function') \r\n    this.on('message', listener); \r\n \r\n  this[kStateSymbol] = { \r\n    handle, \r\n    receiving: false, \r\n    bindState: BIND_STATE_UNBOUND, \r\n    connectState: CONNECT_STATE_DISCONNECTED, \r\n    queue: undefined, \r\n    reuseAddr: options && options.reuseAddr, \/\/ Use UV_UDP_REUSEADDR if true. \r\n    ipv6Only: options && options.ipv6Only, \r\n    recvBufferSize, \r\n    sendBufferSize \r\n  };} \r\n<\/pre>\n

\u6211\u4eec\u770b\u5230\u4e00\u4e2asocket\u5bf9\u8c61\u662f\u5bf9handle\u7684\u4e00\u4e2a\u5c01\u88c5\u3002\u6211\u4eec\u770b\u770bhandle\u662f\u4ec0\u4e48\u3002<\/p>\n

\r\nfunction newHandle(type, lookup) { \r\n  \/\/ \u7528\u4e8edns\u89e3\u6790\u7684\u51fd\u6570\uff0c\u6bd4\u5982\u6211\u4eec\u8c03send\u7684\u65f6\u5019\uff0c\u4f20\u7684\u662f\u4e00\u4e2a\u57df\u540d \r\n  if (lookup === undefined) { \r\n    if (dns === undefined) { \r\n      dns = require('dns'); \r\n    } \r\n \r\n    lookup = dns.lookup; \r\n  }  \r\n \r\n  if (type === 'udp4') { \r\n    const handle = new UDP(); \r\n    handle.lookup = lookup4.bind(handle, lookup); \r\n    return handle; \r\n  } \r\n  \/\/ \u5ffd\u7565ipv6\u7684\u5904\u7406} \r\n<\/pre>\n

handle\u53c8\u662f\u5bf9UDP\u6a21\u5757\u7684\u5c01\u88c5\uff0cUDP\u662fc++\u6a21\u5757\uff0c\u6211\u4eec\u770b\u770b\u8be5c++\u6a21\u5757\u7684\u5b9a\u4e49\u3002<\/p>\n

\r\n\/\/ \u5b9a\u4e49\u4e00\u4e2av8\u51fd\u6570\u6a21\u5757 \r\nLocal t = env->NewFunctionTemplate(New); \r\n  \/\/ t\u65b0\u5efa\u7684\u5bf9\u8c61\u9700\u8981\u989d\u5916\u62d3\u5c55\u7684\u5185\u5b58 \r\n  t->InstanceTemplate()->SetInternalFieldCount(1); \r\n  \/\/ \u5bfc\u51fa\u7ed9js\u5c42\u4f7f\u7528\u7684\u540d\u5b57 \r\n  Local udpString = FIXED_ONE_BYTE_STRING(env->isolate(), \"UDP\"); \r\n  t->SetClassName(udpString); \r\n  \/\/ \u5c5e\u6027\u7684\u5b58\u53d6\u5c5e\u6027 \r\n  enum PropertyAttribute attributes = static_cast(ReadOnly | DontDelete); \r\n \r\n  Local signature = Signature::New(env->isolate(), t); \r\n  \/\/ \u65b0\u5efa\u4e00\u4e2a\u51fd\u6570\u6a21\u5757 \r\n  Local get_fd_templ = \r\n      FunctionTemplate::New(env->isolate(), \r\n                            UDPWrap::GetFD, \r\n                            env->as_callback_data(), \r\n                            signature); \r\n  \/\/ \u8bbe\u7f6e\u4e00\u4e2a\u8bbf\u95ee\u5668\uff0c\u8bbf\u95eefd\u5c5e\u6027\u7684\u65f6\u5019\uff0c\u6267\u884cget_fd_templ\uff0c\u4ece\u800c\u6267\u884cUDPWrap::GetFD \r\n  t->PrototypeTemplate()->SetAccessorProperty(env->fd_string(), \r\n                                              get_fd_templ, \r\n                                              Local<\/functiontemplate>(), \r\n                                              attributes); \r\n  \/\/ \u5bfc\u51fa\u7684\u51fd\u6570 \r\n  env->SetProtoMethod(t, \"open\", Open); \r\n  \/\/ \u5ffd\u7565\u4e00\u7cfb\u5217\u51fd\u6570 \r\n  \/\/ \u5bfc\u51fa\u7ed9js\u5c42\u4f7f\u7528 \r\n  target->Set(env->context(), \r\n              udpString, \r\n              t->GetFunction(env->context()).ToLocalChecked()).Check(); \r\n<\/functiontemplate><\/signature><\/propertyattribute><\/string><\/functiontemplate><\/pre>\n

\u5728c++\u5c42\u901a\u7528\u903b\u8f91\u4e2d\u6211\u4eec\u8bb2\u8fc7\u76f8\u5173\u7684\u77e5\u8bc6\uff0c\u8fd9\u91cc\u5c31\u4e0d\u8be6\u7ec6\u8bb2\u8ff0\u4e86\uff0c\u5f53\u6211\u4eec\u5728js\u5c42new UDP\u7684\u65f6\u5019\uff0c\u4f1a\u65b0\u5efa\u4e00\u4e2ac++\u5bf9\u8c61\u3002<\/p>\n

\r\nUDPWrap::UDPWrap(Environment* env, Local object) \r\n    : HandleWrap(env, \r\n                 object, \r\n                 reinterpret_cast(&handle_), \r\n                 AsyncWrap::PROVIDER_UDPWRAP) { \r\n  int r = uv_udp_init(env->event_loop(), &handle_);} \r\n<\/object><\/pre>\n

\u6267\u884c\u4e86uv_udp_init\u521d\u59cb\u5316udp\u5bf9\u5e94\u7684handle\u3002\u6211\u4eec\u770b\u4e00\u4e0blibuv\u7684\u5b9a\u4e49\u3002<\/p>\n

\r\nint uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) { \r\n  int domain; \r\n  int err; \r\n  int fd; \r\n \r\n  \/* Use the lower 8 bits for the domain *\/ \r\n  domain = flags & 0xFF; \r\n  \/\/ \u7533\u8bf7\u4e00\u4e2asocket\uff0c\u8fd4\u56de\u4e00\u4e2afd \r\n  fd = uv__socket(domain, SOCK_DGRAM, 0); \r\n  uv__handle_init(loop, (uv_handle_t*)handle, UV_UDP); \r\n  handle->alloc_cb = NULL; \r\n  handle->recv_cb = NULL; \r\n  handle->send_queue_size = 0; \r\n  handle->send_queue_count = 0; \r\n  \/\/ \u521d\u59cb\u5316io\u89c2\u5bdf\u8005\uff08\u8fd8\u6ca1\u6709\u6ce8\u518c\u5230\u4e8b\u4ef6\u5faa\u73af\u7684poll io\u9636\u6bb5\uff09\uff0c\u76d1\u542c\u7684\u6587\u4ef6\u63cf\u8ff0\u7b26\u662ffd\uff0c\u56de\u8c03\u662fuv__udp_io \r\n  uv__io_init(&handle->io_watcher, uv__udp_io, fd); \r\n  \/\/ \u521d\u59cb\u5316\u5199\u961f\u5217 \r\n  QUEUE_INIT(&handle->write_queue); \r\n  QUEUE_INIT(&handle->write_completed_queue); \r\n  return 0;} \r\n<\/pre>\n

\u5230\u8fd9\u91cc\uff0c\u5c31\u662f\u6211\u4eec\u5728js\u5c42\u6267\u884cdgram.createSocket('udp4')\u7684\u65f6\u5019\uff0c\u5728nodejs\u4e2d\u4e3b\u8981\u7684\u6267\u884c\u8fc7\u7a0b\u3002\u56de\u5230\u6700\u5f00\u59cb\u7684\u4f8b\u5b50\uff0c\u6211\u4eec\u770b\u4e00\u4e0b\u6267\u884cbind\u7684\u65f6\u5019\u7684\u903b\u8f91\u3002<\/p>\n

\r\nSocket.prototype.bind = function(port_, address_ \/* , callback *\/) { \r\n  let port = port_; \r\n  \/\/ socket\u7684\u72b6\u6001 \r\n  const state = this[kStateSymbol]; \r\n  \/\/ \u5df2\u7ecf\u7ed1\u5b9a\u8fc7\u4e86\u5219\u62a5\u9519 \r\n  if (state.bindState !== BIND_STATE_UNBOUND) \r\n    throw new ERR_SOCKET_ALREADY_BOUND(); \r\n  \/\/ \u5426\u5219\u6807\u8bb0\u5df2\u7ecf\u7ed1\u5b9a\u4e86 \r\n  state.bindState = BIND_STATE_BINDING; \r\n  \/\/ \u6ca1\u4f20\u5730\u5740\u5219\u9ed8\u8ba4\u7ed1\u5b9a\u6240\u6709\u5730\u5740 \r\n  if (!address) { \r\n    if (this.type === 'udp4') \r\n      address = '0.0.0.0'; \r\n    else \r\n      address = '::'; \r\n  } \r\n  \/\/ dns\u89e3\u6790\u540e\u5728\u7ed1\u5b9a\uff0c\u5982\u679c\u9700\u8981\u7684\u8bdd \r\n  state.handle.lookup(address, (err, ip) => { \r\n    if (err) { \r\n      state.bindState = BIND_STATE_UNBOUND; \r\n      this.emit('error', err); \r\n      return; \r\n    } \r\n    const err = state.handle.bind(ip, port || 0, flags); \r\n    if (err) { \r\n       const ex = exceptionWithHostPort(err, 'bind', ip, port); \r\n       state.bindState = BIND_STATE_UNBOUND; \r\n       this.emit('error', ex); \r\n       \/\/ Todo: close? \r\n       return; \r\n     } \r\n \r\n     startListening(this); \r\n  return this;} \r\n<\/pre>\n

bind\u51fd\u6570\u4e3b\u8981\u7684\u903b\u8f91\u662fhandle.bind\u548cstartListening\u3002\u6211\u4eec\u4e00\u4e2a\u4e2a\u770b\u3002\u6211\u4eec\u770b\u4e00\u4e0bc++\u5c42\u7684bind\u3002<\/p>\n

\r\nvoid UDPWrap::DoBind(const FunctionCallbackInfo& args, int family) { \r\n  UDPWrap* wrap; \r\n  ASSIGN_OR_RETURN_UNWRAP(&wrap, \r\n                          args.Holder(), \r\n                          args.GetReturnValue().Set(UV_EBADF)); \r\n \r\n  \/\/ bind(ip, port, flags) \r\n  CHECK_EQ(args.Length(), 3); \r\n  node::Utf8Value address(args.GetIsolate(), args[0]); \r\n  Local ctx = args.GetIsolate()->GetCurrentContext(); \r\n  uint32_t port, flags; \r\n  if (!args[1]->Uint32Value(ctx).To(&port) || \r\n      !args[2]->Uint32Value(ctx).To(&flags)) \r\n    return; \r\n  struct sockaddr_storage addr_storage; \r\n  int err = sockaddr_for_family(family, address.out(), port, &addr_storage); \r\n  if (err == 0) { \r\n    err = uv_udp_bind(&wrap->handle_, \r\n                      reinterpret_cast(&addr_storage), \r\n                      flags); \r\n  } \r\n \r\n  args.GetReturnValue().Set(err);} \r\n<\/const><\/context><\/value><\/pre>\n

\u4e5f\u6ca1\u6709\u592a\u591a\u903b\u8f91\uff0c\u5904\u7406\u53c2\u6570\u7136\u540e\u6267\u884cuv_udp_bind\uff0cuv_udp_bind\u5c31\u4e0d\u5177\u4f53\u5c55\u5f00\u4e86\uff0c\u548ctcp\u7c7b\u4f3c\uff0c\u8bbe\u7f6e\u4e00\u4e9b\u6807\u8bb0\u548c\u5c5e\u6027\uff0c\u7136\u540e\u6267\u884c\u64cd\u4f5c\u7cfb\u7edfbind\u7684\u51fd\u6570\u628a\u672c\u7aef\u7684ip\u548c\u7aef\u53e3\u4fdd\u5b58\u5230socket\u4e2d\u3002\u6211\u4eec\u7ee7\u7eed\u770bstartListening\u3002<\/p>\n

\r\nfunction startListening(socket) { \r\n  const state = socket[kStateSymbol]; \r\n  \/\/ \u6709\u6570\u636e\u65f6\u7684\u56de\u8c03\uff0c\u89e6\u53d1message\u4e8b\u4ef6 \r\n  state.handle.onmessage = onMessage; \r\n  \/\/ \u91cd\u70b9\uff0c\u5f00\u59cb\u76d1\u542c\u6570\u636e \r\n  state.handle.recvStart(); \r\n  state.receiving = true; \r\n  state.bindState = BIND_STATE_BOUND; \r\n \r\n  if (state.recvBufferSize) \r\n    bufferSize(socket, state.recvBufferSize, RECV_BUFFER); \r\n \r\n  if (state.sendBufferSize) \r\n    bufferSize(socket, state.sendBufferSize, SEND_BUFFER); \r\n \r\n  socket.emit('listening');} \r\n<\/pre>\n

\u91cd\u70b9\u662frecvStart\u51fd\u6570\uff0c\u6211\u4eec\u5230c++\u7684\u5b9e\u73b0\u3002<\/p>\n

\r\nvoid UDPWrap::RecvStart(const FunctionCallbackInfo& args) { \r\n  UDPWrap* wrap; \r\n  ASSIGN_OR_RETURN_UNWRAP(&wrap, \r\n                          args.Holder(), \r\n                          args.GetReturnValue().Set(UV_EBADF)); \r\n  int err = uv_udp_recv_start(&wrap->handle_, OnAlloc, OnRecv); \r\n  \/\/ UV_EALREADY means that the socket is already bound but that's okay \r\n  if (err == UV_EALREADY) \r\n    err = 0; \r\n  args.GetReturnValue().Set(err);} \r\n<\/value><\/pre>\n

OnAlloc, OnRecv\u5206\u522b\u662f\u5206\u914d\u5185\u5b58\u63a5\u6536\u6570\u636e\u7684\u51fd\u6570\u548c\u6570\u636e\u5230\u6765\u65f6\u6267\u884c\u7684\u56de\u8c03\u3002\u7ee7\u7eed\u770blibuv<\/p>\n

\r\nint uv__udp_recv_start(uv_udp_t* handle, \r\n                       uv_alloc_cb alloc_cb, \r\n                       uv_udp_recv_cb recv_cb) { \r\n  int err; \r\n \r\n \r\n  err = uv__udp_maybe_deferred_bind(handle, AF_INET, 0); \r\n  if (err) \r\n    return err; \r\n  \/\/ \u4fdd\u5b58\u4e00\u4e9b\u4e0a\u4e0b\u6587 \r\n  handle->alloc_cb = alloc_cb; \r\n  handle->recv_cb = recv_cb; \r\n  \/\/ \u6ce8\u518cio\u89c2\u5bdf\u8005\u5230loop\uff0c\u5982\u679c\u4e8b\u4ef6\u5230\u6765\uff0c\u7b49\u5230poll io\u9636\u6bb5\u5904\u7406 \r\n  uv__io_start(handle->loop, &handle->io_watcher, POLLIN); \r\n  uv__handle_start(handle); \r\n \r\n  return 0;} \r\n<\/pre>\n

uv__udp_recv_start\u4e3b\u8981\u662f\u6ce8\u518cio\u89c2\u5bdf\u8005\u5230loop\uff0c\u7b49\u5f85\u4e8b\u4ef6\u5230\u6765\u7684\u65f6\u5019\uff0c\u5728poll io\u9636\u6bb5\u5904\u7406\u3002\u524d\u9762\u6211\u4eec\u8bb2\u8fc7\uff0c\u56de\u8c03\u51fd\u6570\u662fuv__udp_io\u3002\u6211\u4eec\u770b\u4e00\u4e0b\u4e8b\u4ef6\u89e6\u53d1\u7684\u65f6\u5019\uff0c\u8be5\u51fd\u6570\u600e\u4e48\u5904\u7406\u7684\u3002<\/p>\n

\r\nstatic void uv__udp_io(uv_loop_t* loop, uv__io_t* w, unsigned int revents) { \r\n  uv_udp_t* handle; \r\n \r\n  handle = container_of(w, uv_udp_t, io_watcher); \r\n  \/\/ \u53ef\u8bfb\u4e8b\u4ef6\u89e6\u53d1 \r\n  if (revents & POLLIN) \r\n    uv__udp_recvmsg(handle); \r\n  \/\/ \u53ef\u5199\u4e8b\u4ef6\u89e6\u53d1 \r\n  if (revents & POLLOUT) { \r\n    uv__udp_sendmsg(handle); \r\n    uv__udp_run_completed(handle); \r\n  }} \r\n<\/pre>\n

\u6211\u4eec\u8fd9\u91cc\u5148\u5206\u6790\u53ef\u8bfb\u4e8b\u4ef6\u7684\u903b\u8f91\u3002\u6211\u4eec\u770buv__udp_recvmsg\u3002<\/p>\n

\r\nstatic void uv__udp_recvmsg(uv_udp_t* handle) { \r\n  struct sockaddr_storage peer; \r\n  struct msghdr h; \r\n  ssize_t nread; \r\n  uv_buf_t buf; \r\n  int flags; \r\n  int count; \r\n \r\n  count = 32; \r\n \r\n  do { \r\n    \/\/ \u5206\u914d\u5185\u5b58\u63a5\u6536\u6570\u636e\uff0cc++\u5c42\u8bbe\u7f6e\u7684 \r\n    buf = uv_buf_init(NULL, 0); \r\n    handle->alloc_cb((uv_handle_t*) handle, 64 * 1024, &buf); \r\n    memset(&h, 0, sizeof(h)); \r\n    memset(&peer, 0, sizeof(peer)); \r\n    h.msg_name = &peer; \r\n    h.msg_namelen = sizeof(peer); \r\n    h.msg_iov = (void*) &buf; \r\n    h.msg_iovlen = 1; \r\n    \/\/ \u8c03\u64cd\u4f5c\u7cfb\u7edf\u7684\u51fd\u6570\u8bfb\u53d6\u6570\u636e \r\n    do { \r\n      nread = recvmsg(handle->io_watcher.fd, &h, 0); \r\n    } \r\n    while (nread == -1 && errno == EINTR); \r\n    \/\/ \u8c03\u7528c++\u5c42\u56de\u8c03 \r\n    handle->recv_cb(handle, nread, &buf, (const struct sockaddr*) &peer, flags); \r\n  }} \r\n<\/pre>\n

libuv\u4f1a\u56de\u8c03c++\u5c42\uff0c\u7136\u540ec++\u5c42\u56de\u8c03\u5230js\u5c42\uff0c\u6700\u540e\u89e6\u53d1message\u4e8b\u4ef6\uff0c\u8fd9\u5c31\u662f\u5bf9\u5e94\u5f00\u59cb\u90a3\u6bb5\u4ee3\u7801\u7684message\u4e8b\u4ef6\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"

\u6211\u4eec\u4ece\u4e00\u4e2a\u4f7f\u7528\u4f8b\u5b50\u5f00\u59cb\u770b\u770budp\u6a21\u5757\u7684\u5b9e\u73b0\u3002 const dgram = require(‘dgram’); […]<\/p>\n","protected":false},"author":668,"featured_media":85409,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[55],"tags":[],"class_list":["post-201344","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-thread"],"acf":[],"_links":{"self":[{"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/posts\/201344","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/users\/668"}],"replies":[{"embeddable":true,"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/comments?post=201344"}],"version-history":[{"count":4,"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/posts\/201344\/revisions"}],"predecessor-version":[{"id":201348,"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/posts\/201344\/revisions\/201348"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/media\/85409"}],"wp:attachment":[{"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/media?parent=201344"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/categories?post=201344"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lrxjmw.cn\/wp-json\/wp\/v2\/tags?post=201344"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}