NodeJS First Dive

原项目视频地址

https://www.udemy.com/course/complete-nodejs-developer-zero-to-mastery/

相关技术栈

  • NodeJS

  • File I/O

  • Web Servers

  • Express.js

  • Perfermance and Scale

  • Authentication

  • Databases

  • Deployment and CI/CD

  • RESTful APIs

  • Production + the Cloud

  • Sockets

  • TS

  • Deno

主项目Master Project

  • NASA行星

记得开启字幕~

P1-17

Node.js is born

How do you run JavaScript?

我们的计算机可以运行js吗?实际上是浏览器可以运行js。因为浏览器有JS引擎。

JS引擎

JavaScript引擎 - 维基百科,自由的百科全书 (kfd.me)

V8引擎的发布,JavaScript性能得到释放,Google Chrome因此流行

时间线

image

  • 1995 Brenden创造JS

  • 1996 Netscape浏览器可以执行js

  • 2008 Chrome V8新引擎发布

  • 2009 Ryan Dahl创造NodeJS

    • 后来创建了deno

建议观看视频:Ryan Dahl: Original Node.js presentation

Node.js (nodejs.org)

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine.

Node接收js文件,交给V8引擎去编译执行;js引擎遇到不属于js的部分(openfiles),交给libuv

libuv | Cross-platform asynchronous I/O

libuv is a multi-platform support library with a focus on asynchronous I/O.

how work

image

Node.js 安装

Long Term Support (LTS) schedule是稳定版本。关于版本的详细规划可以看Releases | Node.js (nodejs.org),据此选择自己接下来的生产版本所应该使用的,最近的话使用V16。

  • Current 会有比较大的改动
  • Active 可用,小的改动
  • Maintenance 可用,仅修改Bug

First App

// hello.js
const mission = process.argv[2];

if (mission === "learn") {
    console.log("Time to write some node code!")
}
else {
    console.log(`Is ${mission} really more fun?`)
}

process 进程 | Node.js API 文档 (nodejs.cn)

process 对象提供有关当前 Node.js 进程的信息并对其进行控制.

process.argv | Node.js API 文档 (nodejs.cn)

peocess.argv(arg0,arg1,args)

  • agr0 Node.js安装路径;
  • arg[1] 当前文件js路径;
  • args命令行参数

命令行

> node hello.js learn
Time to write some node code!

Node VS JavaScript

image

浏览器js

  • window 全局对象,表示浏览器中打开的窗口
    • alert
    • setTimeout
    • ···
  • document HTML专用的属性和方法
    • activeElement
    • URL
    • ···
  • history
  • location
  • navigator

Node.js

What does Node.js do?

image

  • V8引擎(c++) 这是node能够运行js的原因
  • APIs(js/c++)
    • fs 文件读取
    • http 网络请求
    • path 查找计算机文件路径
    • crypto 密码功能
  • Node.js bindings 将js与C/C++的部分绑定
  • libuv(c) 与计算机对话

image

Deep dive

nodejs/node: Node.js JavaScript runtime (github.com)

  • lib node.js apis
    • console
    • fs
    • http
    • ···
  • src c++部分
    • node_file.cc
    • ···

打开文件事件

  1. fs 文件系统 | Node.js API 文档 (nodejs.cn)

  2. 在node/lib下找到fs模块,搜索open(

    function open(path, flags, mode, callback) {
      path = getValidatedPath(path);
      if (arguments.length < 3) {
        callback = flags;
        flags = 'r';
        mode = 0o666;
      } else if (typeof mode === 'function') {
        callback = mode;
        mode = 0o666;
      } else {
        mode = parseFileMode(mode, 'mode', 0o666);
      }
      const flagsNumber = stringToFlags(flags);
      callback = makeCallback(callback);
    
      const req = new FSReqCallback();
      req.oncomplete = callback;
    
      binding.open(pathModule.toNamespacedPath(path),
                   flagsNumber,
                   mode,
                   req);
    }
    

    其中binding.open()是绑定部分

  3. 在node/src中node_file.cc

    • 在页面最下方稍微上一点,可以看到方法绑定
    env->SetMethod(target, "access", Access);
      env->SetMethod(target, "close", Close);
      env->SetMethod(target, "open", Open);
      env->SetMethod(target, "openFileHandle", OpenFileHandle);
      env->SetMethod(target, "read", Read);
    
    • 因此我们需要寻找Open函数,搜索Open(
    static void Open(const FunctionCallbackInfo<Value>& args) {
      Environment* env = Environment::GetCurrent(args);
    
      const int argc = args.Length();
      CHECK_GE(argc, 3);
    
      BufferValue path(env->isolate(), args[0]);
      CHECK_NOT_NULL(*path);
    
      CHECK(args[1]->IsInt32());
      const int flags = args[1].As<Int32>()->Value();
    
      CHECK(args[2]->IsInt32());
      const int mode = args[2].As<Int32>()->Value();
    
      FSReqBase* req_wrap_async = GetReqWrap(args, 3);
      if (req_wrap_async != nullptr) {  // open(path, flags, mode, req)
        req_wrap_async->set_is_plain_open(true);
        AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterInteger,
                  uv_fs_open, *path, flags, mode);
      } else {  // open(path, flags, mode, undefined, ctx)
        CHECK_EQ(argc, 5);
        FSReqWrapSync req_wrap_sync;
        FS_SYNC_TRACE_BEGIN(open);
        int result = SyncCall(env, args[4], &req_wrap_sync, "open",
                              uv_fs_open, *path, flags, mode);
        FS_SYNC_TRACE_END(open);
        if (result >= 0) env->AddUnmanagedFd(result);
        args.GetReturnValue().Set(result);
      }
    }
    
    • 可以看到异步调用AsyncCall、同步调用SyncCall,定位到了uv_fs_open函数。我们需要去libuv的代码中寻找.
  4. 在libuv/src/unix中,可以看到unix系统类的相关代码实现

    • 我们可以看到darwin-proctitle.c,darwin是苹果系统的核心组件代号,因此我们来对地方了

    • 进入fs.c中搜索uv_fs_open

      int uv_fs_open(uv_loop_t* loop,
                     uv_fs_t* req,
                     const char* path,
                     int flags,
                     int mode,
                     uv_fs_cb cb) {
        INIT(OPEN);
        PATH;
        req->flags = flags;
        req->mode = mode;
        POST;
      }
      
    • 可以看到实际调用OPEN,在代码最下面稍微上一点的地方,可以看到

      X(MKDIR, mkdir(req->path, req->mode));
          X(MKDTEMP, uv__fs_mkdtemp(req));
          X(MKSTEMP, uv__fs_mkstemp(req));
          X(OPEN, uv__fs_open(req));
          X(READ, uv__fs_read(req));
      
    • 因此实际调用uv__fs_open,可以搜到

      static ssize_t uv__fs_open(uv_fs_t* req) {
      #ifdef O_CLOEXEC
        return open(req->path, req->flags | O_CLOEXEC, req->mode); // unix实际打开文件
      #else  /* O_CLOEXEC */
        int r;
      
        if (req->cb != NULL)
          uv_rwlock_rdlock(&req->loop->cloexec_lock);
      
        r = open(req->path, req->flags, req->mode);
      
        /* In case of failure `uv__cloexec` will leave error in `errno`,
         * so it is enough to just set `r` to `-1`.
         */
        if (r >= 0 && uv__cloexec(r, 1) != 0) {
          r = uv__close(r);
          if (r != 0)
            abort();
          r = -1;
        }
      
        if (req->cb != NULL)
          uv_rwlock_rdunlock(&req->loop->cloexec_lock);
      
        return r; // 返回到js中
      #endif  /* O_CLOEXEC */
      }
      
  5. 在libuv/src/win中,可以看到win系统类的相关代码实现

    • 打开fs.c,搜索uv_fs_open

      int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
          int mode, uv_fs_cb cb) {
        int err;
      
        INIT(UV_FS_OPEN);
        err = fs__capture_path(req, path, NULL, cb != NULL);
        if (err) {
          SET_REQ_WIN32_ERROR(req, err);
          return req->result;
        }
      
        req->fs.info.file_flags = flags;
        req->fs.info.mode = mode;
        POST;
      }
      
    • 实际调用UV_FS_OPEN,像unix一样,在代码底部稍微上一点的地方可以看到

      #define XX(uc, lc)  case UV_FS_##uc: fs__##lc(req); break;
        switch (req->fs_type) {
          XX(OPEN, open)
          XX(CLOSE, close)
          XX(READ, read)
          XX(WRITE, write)
          XX(COPYFILE, copyfile)
      
    • 实际调用fs__open,搜索得到

      void fs__open(uv_fs_t* req) {
        DWORD access;
        DWORD share;
        DWORD disposition;
        DWORD attributes = 0;
        HANDLE file;
        int fd, current_umask;
        int flags = req->fs.info.file_flags;
        struct uv__fd_info_s fd_info;
      
        /* Adjust flags to be compatible with the memory file mapping. Save the
         * original flags to emulate the correct behavior. */
        if (flags & UV_FS_O_FILEMAP) {
          fd_info.flags = flags;
          fd_info.current_pos.QuadPart = 0;
      
          if ((flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) ==
              UV_FS_O_WRONLY) {
            /* CreateFileMapping always needs read access */
            flags = (flags & ~UV_FS_O_WRONLY) | UV_FS_O_RDWR;
          }
      
          if (flags & UV_FS_O_APPEND) {
            /* Clear the append flag and ensure RDRW mode */
            flags &= ~UV_FS_O_APPEND;
            flags &= ~(UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR);
            flags |= UV_FS_O_RDWR;
          }
        }
      
        /* Obtain the active umask. umask() never fails and returns the previous
         * umask. */
        current_umask = umask(0);
        umask(current_umask);
      
        /* convert flags and mode to CreateFile parameters */
        switch (flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) {
        case UV_FS_O_RDONLY:
          access = FILE_GENERIC_READ;
          break;
        case UV_FS_O_WRONLY:
          access = FILE_GENERIC_WRITE;
          break;
        case UV_FS_O_RDWR:
          access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
          break;
        default:
          goto einval;
        }
      
        if (flags & UV_FS_O_APPEND) {
          access &= ~FILE_WRITE_DATA;
          access |= FILE_APPEND_DATA;
        }
      
        /*
         * Here is where we deviate significantly from what CRT's _open()
         * does. We indiscriminately use all the sharing modes, to match
         * UNIX semantics. In particular, this ensures that the file can
         * be deleted even whilst it's open, fixing issue
         * https://github.com/nodejs/node-v0.x-archive/issues/1449.
         * We still support exclusive sharing mode, since it is necessary
         * for opening raw block devices, otherwise Windows will prevent
         * any attempt to write past the master boot record.
         */
        if (flags & UV_FS_O_EXLOCK) {
          share = 0;
        } else {
          share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
        }
      
        switch (flags & (UV_FS_O_CREAT | UV_FS_O_EXCL | UV_FS_O_TRUNC)) {
        case 0:
        case UV_FS_O_EXCL:
          disposition = OPEN_EXISTING;
          break;
        case UV_FS_O_CREAT:
          disposition = OPEN_ALWAYS;
          break;
        case UV_FS_O_CREAT | UV_FS_O_EXCL:
        case UV_FS_O_CREAT | UV_FS_O_TRUNC | UV_FS_O_EXCL:
          disposition = CREATE_NEW;
          break;
        case UV_FS_O_TRUNC:
        case UV_FS_O_TRUNC | UV_FS_O_EXCL:
          disposition = TRUNCATE_EXISTING;
          break;
        case UV_FS_O_CREAT | UV_FS_O_TRUNC:
          disposition = CREATE_ALWAYS;
          break;
        default:
          goto einval;
        }
      
        attributes |= FILE_ATTRIBUTE_NORMAL;
        if (flags & UV_FS_O_CREAT) {
          if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) {
            attributes |= FILE_ATTRIBUTE_READONLY;
          }
        }
      
        if (flags & UV_FS_O_TEMPORARY ) {
          attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
          access |= DELETE;
        }
      
        if (flags & UV_FS_O_SHORT_LIVED) {
          attributes |= FILE_ATTRIBUTE_TEMPORARY;
        }
      
        switch (flags & (UV_FS_O_SEQUENTIAL | UV_FS_O_RANDOM)) {
        case 0:
          break;
        case UV_FS_O_SEQUENTIAL:
          attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
          break;
        case UV_FS_O_RANDOM:
          attributes |= FILE_FLAG_RANDOM_ACCESS;
          break;
        default:
          goto einval;
        }
      
        if (flags & UV_FS_O_DIRECT) {
          /*
           * FILE_APPEND_DATA and FILE_FLAG_NO_BUFFERING are mutually exclusive.
           * Windows returns 87, ERROR_INVALID_PARAMETER if these are combined.
           *
           * FILE_APPEND_DATA is included in FILE_GENERIC_WRITE:
           *
           * FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE |
           *                      FILE_WRITE_DATA |
           *                      FILE_WRITE_ATTRIBUTES |
           *                      FILE_WRITE_EA |
           *                      FILE_APPEND_DATA |
           *                      SYNCHRONIZE
           *
           * Note: Appends are also permitted by FILE_WRITE_DATA.
           *
           * In order for direct writes and direct appends to succeed, we therefore
           * exclude FILE_APPEND_DATA if FILE_WRITE_DATA is specified, and otherwise
           * fail if the user's sole permission is a direct append, since this
           * particular combination is invalid.
           */
          if (access & FILE_APPEND_DATA) {
            if (access & FILE_WRITE_DATA) {
              access &= ~FILE_APPEND_DATA;
            } else {
              goto einval;
            }
          }
          attributes |= FILE_FLAG_NO_BUFFERING;
        }
      
        switch (flags & (UV_FS_O_DSYNC | UV_FS_O_SYNC)) {
        case 0:
          break;
        case UV_FS_O_DSYNC:
        case UV_FS_O_SYNC:
          attributes |= FILE_FLAG_WRITE_THROUGH;
          break;
        default:
          goto einval;
        }
      
        /* Setting this flag makes it possible to open a directory. */
        attributes |= FILE_FLAG_BACKUP_SEMANTICS;
      
        file = CreateFileW(req->file.pathw, // 实际创建文件
                           access,
                           share,
                           NULL,
                           disposition,
                           attributes,
                           NULL);
        if (file == INVALID_HANDLE_VALUE) {
          DWORD error = GetLastError();
          if (error == ERROR_FILE_EXISTS && (flags & UV_FS_O_CREAT) &&
              !(flags & UV_FS_O_EXCL)) {
            /* Special case: when ERROR_FILE_EXISTS happens and UV_FS_O_CREAT was
             * specified, it means the path referred to a directory. */
            SET_REQ_UV_ERROR(req, UV_EISDIR, error);
          } else {
            SET_REQ_WIN32_ERROR(req, GetLastError());
          }
          return;
        }
      
        fd = _open_osfhandle((intptr_t) file, flags); // 把句柄赋给了fd变量
        if (fd < 0) {
          /* The only known failure mode for _open_osfhandle() is EMFILE, in which
           * case GetLastError() will return zero. However we'll try to handle other
           * errors as well, should they ever occur.
           */
          if (errno == EMFILE)
            SET_REQ_UV_ERROR(req, UV_EMFILE, ERROR_TOO_MANY_OPEN_FILES);
          else if (GetLastError() != ERROR_SUCCESS)
            SET_REQ_WIN32_ERROR(req, GetLastError());
          else
            SET_REQ_WIN32_ERROR(req, (DWORD) UV_UNKNOWN);
          CloseHandle(file);
          return;
        }
      
        if (flags & UV_FS_O_FILEMAP) {
          FILE_STANDARD_INFO file_info;
          if (!GetFileInformationByHandleEx(file,
                                            FileStandardInfo,
                                            &file_info,
                                            sizeof file_info)) {
            SET_REQ_WIN32_ERROR(req, GetLastError());
            CloseHandle(file);
            return;
          }
          fd_info.is_directory = file_info.Directory;
      
          if (fd_info.is_directory) {
            fd_info.size.QuadPart = 0;
            fd_info.mapping = INVALID_HANDLE_VALUE;
          } else {
            if (!GetFileSizeEx(file, &fd_info.size)) {
              SET_REQ_WIN32_ERROR(req, GetLastError());
              CloseHandle(file);
              return;
            }
      
            if (fd_info.size.QuadPart == 0) {
              fd_info.mapping = INVALID_HANDLE_VALUE;
            } else {
              DWORD flProtect = (fd_info.flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY |
                UV_FS_O_RDWR)) == UV_FS_O_RDONLY ? PAGE_READONLY : PAGE_READWRITE;
              fd_info.mapping = CreateFileMapping(file,
                                                  NULL,
                                                  flProtect,
                                                  fd_info.size.HighPart,
                                                  fd_info.size.LowPart,
                                                  NULL);
              if (fd_info.mapping == NULL) {
                SET_REQ_WIN32_ERROR(req, GetLastError());
                CloseHandle(file);
                return;
              }
            }
          }
      
          uv__fd_hash_add(fd, &fd_info);
        }
      
        SET_REQ_RESULT(req, fd);  // 设置结果返回给js
        return;
      
       einval:
        SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER);
      }
      
    • 可以看到win的代码有254行,比unix的27行多了一个fr11。这是因为需要处理跨平台的一些逻辑,保证结果在unix上兼容。

posted @ 2022-05-12 13:04  沧浪浊兮  阅读(61)  评论(0编辑  收藏  举报