1. Hi区块链首页
  2. 资讯
  3. 技术指南

精通IPFS系列之三:IPFS启动之boot函数

上一篇文章中,我们从整体上了解了 IPFS 的启动,今天我们就继续深入看下 boot 函数是怎么真正启动系统的,这个函数位于 core/boot.js 文件中。

上一篇文章中,我们从整体上了解了 IPFS 的启动,今天我们就继续深入看下 boot 函数是怎么真正启动系统的,这个函数位于 core/boot.js 文件中。

在开始看 boot 函数之前,我们先大致讲下 async 类库,Async 是一个实用程序模块,它提供了直接,强大的函数来处理异步 JavaScript。这里简单讲下 waterfall、parallel、series 等3个函数,这3个函数会频繁用到。

l waterfall 函数,接收一个函数数组或对象和一个回调函数,首先调用第一个函数(或第一个 Key 对应的函数),以它的结果为参数调用后一个函数,再以后一个函数返回的结果调用下一个函数,以此类推,当所有函数调用完成后,以最后一个函数返回的结果为参数调用用户指定的回调函数。如果其中某个函数抛出异常,下面的函数不会被执行,会立刻把错误对象传递给指定的回调函数。

l parallel 函数,接收一个函数数组或对象和一个回调函数,数组中的函数会并行执行执行而不用等待前面的函数完成,当所有函数调用完成后,把所有函数的执行结果组成一个数组,传递给最终的回调函数。如果中某个函数抛出异常,会立刻把错误对象传递给指定的回调函数。

l series 函数,接收一个函数数组或对象和一个回调函数,数组中的函数会串行执行,即前一个执行完成之后才会继续执行下一个。如果其中某个函数抛出异常,下面的函数不会被执行,会立刻把错误对象传递给指定的回调函数。

 l5beCsu9mNPogEYbS8l5qEzJA1E6VRMkQjYyX5sV.jpeg

boot 函数执行流程如下

1. 初始化用到的几个变量。

const options = self._options

const doInit = options.init

const doStart = options.start

2. 调用 async 类库的 waterfall 函数。在这里,总共要执行 3 个函数,我们以此来看这 3个函数。

· 首先,执行第 1 个函数。函数首先检查仓库状态是否不是关闭的,如果仓库不是关闭的就直接调用第 2 个函数,否则调用仓库的 open 方法(位于 ipfs-repo 项目 index.js 文件中),打开仓库。

仓库的 open 方法的主体也是一个 waterfall 函数。仓库的 waterfall 函数内部,首先调用 root 对象的 open 方法打开主目录(默认仓库采用的是文件系统保存数据,用的是 datastore-fs 类库),因为主目录在仓库对象初始化时候已经创建好了,所以这个方法什么不做,接下来调用 _isInitialized 方法检查仓库是否已经初始化过,这个方法会检查配置文件、规格文件、版本文件是否存在。对于第一次进来这咱情况,这些文件还不存在,方法直接抛出异常,导致 _isInitialized 下面的所有方法不再执行,流程直接到指定的错误处理中。又因为这个时候锁定文件也不存在,所以直接调用 callback(err) 方法,从而回到 open 方法的回调函数中。而对于不是第一次进来的情况,具体处理详见 init 函数执行分析。

仓库 open 方法代码如下,后面我们还会遇到这个函数的,这里不细说。

open (callback) {

    if (!this.closed) {

      setImmediate(() => callback(new Error(‘repo is already open’)))

      return // early

    }

    waterfall([

      (cb) => this.root.open(ignoringAlreadyOpened(cb)),

      (cb) => this._isInitialized(cb),

      (cb) => this._openLock(this.path, cb),

      (lck, cb) => {

        log(‘aquired repo.lock’)

        this.lockfile = lck

        cb()

      },

      (cb) => {

        this.datastore = backends.create(‘datastore’, path.join(this.path, ‘datastore’), this.options)

        const blocksBaseStore = backends.create(‘blocks’, path.join(this.path, ‘blocks’), this.options)

        blockstore(

          blocksBaseStore,

          this.options.storageBackendOptions.blocks,

          cb)

      },

      (blocks, cb) => {

        this.blocks = blocks

        cb()

      },

      (cb) => {

        log(‘creating keystore’)

        this.keys = backends.create(‘keys’, path.join(this.path, ‘keys’), this.options)

        cb()

      },

      (cb) => {

        this.closed = false

        log(‘all opened’)

        cb()

      }

    ], (err) => {

      if (err && this.lockfile) {

        this._closeLock((err2) => {

          if (!err2) {

            this.lockfile = null

          } else {

            log(‘error removing lock’, err2)

          }

          callback(err)

        })

      } else {

        callback(err)

      }

    })

}

在 open 方法的回调函数中,调用 isRepoUninitializedError 方法,检查错误的原因,我们这里的原因是仓库还未初始化,所以这个方法返回真,所以用 false 调用第二个函数。

第 1个函数的代码如下:

(cb) => {

  if (!self._repo.closed) {

    return cb(null, true)

  }

  // 打开仓库

  self._repo.open((err, res) => {

    if (isRepoUninitializedError(err)) return cb(null, false)

    if (err) return cb(err)

    cb(null, true)

  })

}

l 接下来,执行第 2个函数。如果不是第一次进来,那么仓库已经存在,则直接打开仓库。

如果是第一次进来,那么仓库还不存在,所以没办法打开,即 repoOpened 参数为假,所以跳过最上面的初始化。然后,检查 doInit 变量是否为真,如果为真,则根据指定的选项来初始化仓库。在这里 doInit变量的值来自于选项中的 init 属性,这个属性只是一个简单的真值,所以使用默认的配置来初始化。

第 2个函数的代码如下:

(repoOpened, cb) => {

  if (repoOpened) {

    return self.init({ repo: self._repo }, (err) => {

      if (err) return cb(Object.assign(err, { emitted: true }))

      cb()

    })

  }

  // 如果仓库不存在,这里需要进行初始化。

  if (doInit) {

    const initOptions = Object.assign(

      { bits: 2048, pass: self._options.pass },

      typeof options.init === ‘object’ ? options.init : {}

    )

    return self.init(initOptions, (err) => {

      if (err) return cb(Object.assign(err, { emitted: true }))

      cb()

    })

  }

  cb()

}

注意,在 JS 中真值不一定仅仅只一个 true,也可能是一个对象,一个函数,一个数组等,所在这里检测是否为真,只是检测用户有没有指定这个配置而已,并且确保不是 false 而已。

上面 self 指的是 IPFS 对象,init 方法位于 core/components/init.js 文件中。下一篇,我们仔细讲解这个函数的执行过程。

l 接下来,执行第 3个函数。检查是否不需要启动,如果是则直接调用最终的回调函数。

调用 IPFS 对象的 start 方法,启动 IPFS 系统。这个函数我们在分析完初始过程中再来看。

(cb) => {

  if (!doStart) {

    return cb()

  }

  self.start((err) => {

    if (err) return cb(Object.assign(err, { emitted: true }))

    cb()

  })

}

l 接下来,执行最终的回调函数。如果前面 3个函数都没有,则触发 IPFS 对象的 ready 事件;如果有错误,则触发相应的错误。

(err) => {

    if (err) {

      if (!err.emitted) {

        self.emit(‘error’, err)

      }

      return

    }

    self.log(‘booted’)

    self.emit(‘ready’)

}

当 waterfall 函数执行完成后,我们的 IPFS 才真正启动成功,用户可以用它做任何想做的事情。

通过上面的分析,我们发现 IPFS 的启动整体上分为3个步骤,1)打开仓库;2)IPFS 初始化;3)IPFS 启动,而 boot 函数就是一个大总管,控制了 IPFS 系统的启动整个过程。

 

作者:乔疯,加密货币爱好者,ipfs 爱好者,黑萤科技CTO。

声明:登载此文出于传递更多信息之目的,观点仅代表作者本人,绝不代表Hi区块链赞同其观点或证实其描述。
提示:投资有风险,入市须谨慎。本资讯不作为投资理财建议。