• Posts tagged "nodejs"

Blog Archives

Truffle以太坊DApp开发框架

比特币吸金之道系列文章,由计算机黑客发明的网络货币,无国界,无政府,无中心。没有政府滥发货币,没有通货膨胀。在全球计算机网络中,自由的实现货币兑换和流通。

本系列文章只讲程序和策略,不谈挖矿…

关于作者:

  • 张丹(Conan), 程序员/Quant: Java,R,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/bitcoin-eth-truffle

前言

区块链的开发对于大多数的人来说,都是一件很新、很难的事情,有太多不一样的技术要学习。区块链有自己的设计理念,不同于传统分布式系统架构,数据同步机制,共识算法等。以太坊作为区块链2.0的产品,最独特是智能合约的设计,是超脱于技术的思维体系。

通过 Truffle 把这些不同的理念和思路进行整合,转换为开发人员能明白的一种编程方法。本文中的所有源代码已经上传到github,请有需要的同学下载使用: https://github.com/bsspirit/truffle-demo

目录

  1. Truffle安装
  2. 初始化项目
  3. 启动测试节点
  4. 部署合约
  5. 自定义的智能合约
  6. 交互的控制台
  7. 启动合约服务

1. Truffle安装

Truffle是Dapp开发框架,他可以帮我们处理掉大量无关紧要的小事情,让我们可以迅速开始写代码-编译-部署-测试-打包DApp这个流程。Truffle是使用Nodejs开发的,我们首先需要安装Nodejs运行环境。关于Nodejs的详细使用,请参考系列文章从零开始nodejs系列文章

DApp是什么?

App我们都知道是客户端应用,DApp就是D+App,D是英文单词decentralization的缩写,即DApp为去中心化应用。

检查操作系统版本和Nodejs版本。


> cat /etc/issue
Ubuntu 16.04 LTS \n \l

# Nodejs版本
> npm -v
6.0.0
> node -v
v8.9.4

全局安装Truffle工具。


# 安装truffle工具
> npm install -g truffle
/usr/local/bin/truffle -> /usr/local/lib/node_modules/truffle/build/cli.bundled.js
+ truffle@4.1.12
added 81 packages from 309 contributors in 2.571s

查看命令行帮助


> truffle
Truffle v4.1.12 - a development framework for Ethereum

Usage: truffle  [options]

Commands:
  init      Initialize new and empty Ethereum project
  compile   Compile contract source files
  migrate   Run migrations to deploy contracts
  deploy    (alias for migrate)
  build     Execute build pipeline (if configuration present)
  test      Run JavaScript and Solidity tests
  debug     Interactively debug any transaction on the blockchain (experimental)
  opcode    Print the compiled opcodes for a given contract
  console   Run a console with contract abstractions and commands available
  develop   Open a console with a local development blockchain
  create    Helper to create new contracts, migrations and tests
  install   Install a package from the Ethereum Package Registry
  publish   Publish a package to the Ethereum Package Registry
  networks  Show addresses for deployed contracts on each network
  watch     Watch filesystem for changes and rebuild the project automatically
  serve     Serve the build directory on localhost and watch for changes
  exec      Execute a JS module within this Truffle environment
  unbox     Download a Truffle Box, a pre-built Truffle project
  version   Show version number and exit

See more at http://truffleframework.com/docs

2. 初始化项目

新建工程目录,然后用truffle初始化项目。


> cd /root/workspace
> mkdir truffle01
> cd truffle01/

# 初始化项目
> truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test

查看项目目录,目录下会生成下面的文件和目录。


> tree
.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js
  • contracts/ , Truffle默认的合约文件存放地址。
  • migrations/ , 存放发布脚本文件
  • test/ , 用来测试应用和合约的测试文件
  • truffle-config.js, 配置文件
  • truffle.js, 配置文件

在contracts目录下,默认生成了一个合约文件Migrations.sol,执行编译合约。


> truffle compile
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

成功编译后,会在build/contracts目录下,生成对于合约的Migrations.json文件,这个JSON就是

3. 启动测试节点

接下来,我们用testrpc搭建一个本地的简单的测试网络,相当于是一个mock,这样操作比较直接接入以太坊网络环境要容易的多。

安装测试网络工具testrpc


# 安装testrpc工具
> npm install -g ethereumjs-testrpc
npm WARN deprecated ethereumjs-testrpc@6.0.3: ethereumjs-testrpc has been renamed to ganache-cli, please use this package from now on.
/usr/local/bin/testrpc -> /usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js

> uglifyjs-webpack-plugin@0.4.6 postinstall /usr/local/lib/node_modules/ethereumjs-testrpc/node_modules/uglifyjs-webpack-plugin
> node lib/post_install.js

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules/ethereumjs-testrpc/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

+ ethereumjs-testrpc@6.0.3
added 339 packages from 279 contributors in 42.215s

启动testrpc测试网络。


> testrpc
EthereumJS TestRPC v6.0.3 (ganache-core: 2.0.2)

Available Accounts
==================
(0) 0x8dac051e949fdb323ff963f37de37345ac5a2de1
(1) 0x7ca2561b16a4181455537299ff766d4cec7cf6c3
(2) 0x7d1d40b9a015ff42d19cde1f95c0041ab1fac155
(3) 0x42730fd585a29029667274f0443ac0bb4830cb20
(4) 0xe3225679925b3790c850ec3560156aeff3fea1c2
(5) 0xd9f18fb4aa6ed92279136ddcdad73ad516fa7f7d
(6) 0xe9617966b21f20868a35d97e4abbb979f6b32431
(7) 0x15f1e3f6b1caa047281f91834530f14780b9adf7
(8) 0x53c049daad9338db54960e8620fefd3829590754
(9) 0xf6df046b0ce0d12bc978067bfcd0be209ee0b93c

Private Keys
==================
(0) 0c4cb520a02b1ea7c477e5ef028fef2da22be8589a08b5989fae5403c4fec21e
(1) 667adbcd821e183809bdf2d08cedbcd233741670cb61775ea491dd0ef862bf1f
(2) a5b0a337a7185544300f4d24e78b8a3f9d797280dc2ced40c7f3fe60ab943aa5
(3) 3e8f665924d45bcb013bff6111f3be557703ca0798ce824b14e84f5d7d2294e9
(4) 1620759f80e28a1258c18578253cade445d5d5e380a6fc12ed7cfe7e7f9ec408
(5) 60963d8c56c74d4f0cd460b7d9461581815153559ddb2eee9101eb3d2731fba9
(6) e20a5d21d0b6bdd88b143bedaab4ac94f7d1f2178236021b21991efea1ab6ee3
(7) d4d841c0430a9781bf86132db841364e92432d3c3d6ea25b5178e8a6d4c56984
(8) a98f4c3609d2c8e989a0f9bf86acdd4035bf0eac4de6b6c61545a8674f269b82
(9) d5ea07abc4cad2c9d26ebea32c67251ba7fcc3ab317739ec0a13a84cc1ccad47

HD Wallet
==================
Mnemonic:      enforce trust bridge guard memory stadium polar dignity provide alley embrace machine
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545

这里生成了10个账号,和10个私钥,并模拟测试网络的打开了HTTP-RPC服务,这样就可以让智能合约的程序,基于这个测试网络进行开发了。

随着时间的,发现这个模拟的程序,还会自己模拟一些交易。


HD Wallet
==================
Mnemonic:      away lecture stuff weapon market spot infant solid capital monkey claw siege
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545

eth_getBlockByNumber
eth_accounts
web3_clientVersion
net_version
eth_accounts
eth_accounts
eth_accounts
net_version
net_version
eth_sendTransaction

  Transaction: 0xd8e4638d4d2d95b2e2894fa724249cea81bdb539cf4c7b111dbefe0ea321b9eb
  Contract created: 0x0a2816a1c1ad71cda4843f57ab2c0a4a80cdfef9
  Gas usage: 277462
  Block Number: 1
  Block Time: Tue Jun 26 2018 18:49:33 GMT+0800 (CST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x27bda84efcb9a9677c17269919a00f01d7bd2c88b458bd20071c51e6e58dbd48
  Gas usage: 42008
  Block Number: 2
  Block Time: Tue Jun 26 2018 18:49:33 GMT+0800 (CST)

eth_getTransactionReceipt
eth_getBlockByNumber
eth_accounts
web3_clientVersion
eth_getBlockByNumber
eth_accounts
web3_clientVersion
eth_accounts

4. 部署合约

运行truffle migrate命令部署智能合约到测试网络上,第一次执行时出现错误Error: No network specified. Cannot determine current network,是因为没有连接到测试网络。

修改文件truffle.js,连接到测试网络上。


> vi truffle.js

module.exports = {
  networks: {
    development: {
      host: '127.0.0.1',
      port: 8545,
      network_id: '*'
    }
  }
};

再次启动truffle,完成部署的过程。


> truffle migrate
Using network 'development'.

Network up to date.

5. 自定义的智能合约

接下来,我们开始编写一个自己的智能合约,需要编写4个文件。

  • contracts/Hello.sol,合约文件
  • migrations/2_hello.js,部署文件
  • test/Hello.js,js单元测试文件
  • test/TestHello.sol,solidity单元测试文件

在contracts目录下,编写合约文件Hello.sol,提供2个函数,say()用来返回一个固定的字符串,sum()用来计算2个整书之和。


> vi ./contracts/Hello.sol

pragma solidity ^0.4.23;

contract Hello {

  function say() pure public returns (string) {
    return "Hello world";
  }


  function sum(uint a, uint b) pure public returns (uint val) {
    val = a + b ;
    return val;
  }

}

编写Hello.sol合约的部署脚本,放到migrations目录下面,文件名为2_hello.js。


> vi migrations/2_hello.js

var MyContract = artifacts.require("Hello");

module.exports = function(deployer) {
  deployer.deploy(MyContract);
};

单元测试有2种写法,一种是基于nodejs的Mocha库的写法,另一种是基本solidity的写法。

按Nodejs写法的测试用例,放到test目录下面,文件名为Hello.js。


> vi ./test/Hello.js

const Hello = artifacts.require("Hello");

contract('Hello test', async (accounts) => {

  it("say", async () => {
     let obj = await Hello.deployed();
     let val = await obj.say();
     assert.equal(val, "Hello world");
  })

  it("sum", async () => {
    let obj = await Hello.deployed();
    let val = await obj.sum(10,15);
    assert.equal(val, 25);

  });
})

按solidity写法的测试用例,放到test目录下面,文件名为TestHello.sol。


> vi ./test/TestHello.sol

pragma solidity ^0.4.24;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Hello.sol";

contract TestHello {

  function test_say() public {
    Hello obj = Hello(DeployedAddresses.Hello());
    Assert.equal(obj.say(), "Hello world","test say");
  }

  function test_sum() public {
    Hello obj = Hello(DeployedAddresses.Hello());
    Assert.equal(obj.sum(10,15), 25, "test sum");
  }
}

编译Hello.sol合约,成功通过。


> truffle compile
Compiling ./contracts/Hello.sol...
Writing artifacts to ./build/contracts

把合约再次部署到testrpc的测试网络上面,这时需要用–reset参数。


> truffle migrate --reset
Using network 'development'.

Running migration: 1_initial_migration.js
  Replacing Migrations...
  ... 0x011256fee23fe4e633c86411f35e31f539e9026302495c3d824fca6b314ae92c
  Migrations: 0x298afbabd16ca14ec870377a61f983203ac69536
Saving successful migration to network...
  ... 0x59b23e1efc5f1bfb09af05ed3c26b7338573834394a61adea4bfc69775dcbae8
Saving artifacts...
Running migration: 2_hello.js
  Replacing Hello...                                                       # 创建合约
  ... 0x0aab9dd6c5781b899b60c7fc1190d3aefaa2af68363af386a8c473d40bc9f20f   # 交易hash
  Hello: 0x98ce096564f6b459b4a09b1b204ad6e362d384b6                        # 合约地址
Saving successful migration to network...
  ... 0x164b11ea951882cf5d374c2bdb979dac9586a87d0db4b8c6c8561d4cc7a9d5ca
Saving artifacts...

部署成功之后,我们可以看到testrpc的测试网络中,也有一些对应的更新。



eth_getTransactionReceipt
eth_accounts
eth_sendTransaction

  Transaction: 0x0aab9dd6c5781b899b60c7fc1190d3aefaa2af68363af386a8c473d40bc9f20f    # 交易hash
  Contract created: 0x98ce096564f6b459b4a09b1b204ad6e362d384b6                       # 合约地址
  Gas usage: 162663
  Block Number: 63
  Block Time: Wed Jun 27 2018 23:24:14 GMT+0800 (CST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x164b11ea951882cf5d374c2bdb979dac9586a87d0db4b8c6c8561d4cc7a9d5ca
  Gas usage: 27008
  Block Number: 64
  Block Time: Wed Jun 27 2018 23:24:14 GMT+0800 (CST)

eth_getTransactionReceipt

接下来,我们就是可以运行test的命令,来测试合约的正确性。


> truffle test
Using network 'development'.

Compiling ./contracts/Hello.sol...
Compiling ./test/TestHello.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...

  TestHello
    ✓ test_say (119ms)
    ✓ test_sum (70ms)

  Contract: Hello test
    ✓ say
    ✓ sum

  4 passing (1s)

2种写法的单元测试文件,都通过的测试。2种写法的区别在于,Nodejs当中是异步执行测试的,solidity是同步的。

Nodejs的优势是测试与前端测试相似,可以模拟前端测试,称为整合测试,可以有更强大的语法支持。js的另一大优势可以比较简单地实现异常捕捉。

solidity测试写法简洁,适用于单元测试,另一大优势是js只能测试public的函数,soli可以测试内部function,internal的,通过继承被测试contract来获得internal function的访问权限。

6. 交互的控制台

接下来,我们在网络执行合约,可以通过控制台的交互的命令来完成,启动控制台 truffle console。


> truffle console

# 执行合约函数
truffle(development)> var contract;
undefined
truffle(development)> Hello.deployed().then(function(instance){contract= instance;});
undefined
truffle(development)> contract.say()
'Hello world'
truffle(development)> contract.sum(1,21)
BigNumber { s: 1, e: 1, c: [ 22 ] }

# 查看合约地址
truffle(development)> Hello.address
'0x98ce096564f6b459b4a09b1b204ad6e362d384b6'

# 查看合约abi
truffle(development)> JSON.stringify(Hello.abi)
'[{"constant":true,"inputs":[],"name":"say","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"sum","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"}]'

7. 启动合约服务

最后,启动服务程序,开放一个HTTP的端口,允许通过Http访问JSON ABI(Application Binary Interface),ABC指定了合约接口,包括可调用的合约方法、变量、事件等。


> truffle serve
Serving static assets in ./build on port 8080...

启动truffle serve时,一直会有一个报错,TypeError: fsevents is not a constructor


> truffle serve
Serving static assets in ./build on port 8080...

/usr/local/lib/node_modules/truffle/build/webpack:/Users/gnidan/src/work/truffle/~/chokidar/lib/fsevents-handler.js:26
  return (new fsevents(path)).on('fsevent', callback).start();
^
TypeError: fsevents is not a constructor
    at createFSEventsInstance (/usr/local/lib/node_modules/truffle/build/webpack:/Users/gnidan/src/work/truffle/~/chokidar/lib/fsevents-handler.js:26:1)
    at setFSEventsListener (/usr/local/lib/node_modules/truffle/build/webpack:/Users/gnidan/src/work/truffle/~/chokidar/lib/fsevents-handler.js:80:1)
    at FSWatcher.FsEventsHandler._watchWithFsEvents (/usr/local/lib/node_modules/truffle/build/webpack:/Users/gnidan/src/work/truffle/~/chokidar/lib/fsevents-handler.js:244:1)
    at FSWatcher. (/usr/local/lib/node_modules/truffle/build/webpack:/Users/gnidan/src/work/truffle/~/chokidar/lib/fsevents-handler.js:378:1)
    at gotStat (fs.js:1775:21)
    at FSReqWrap.oncomplete (fs.js:152:21)

只有修改truffle的源代码文件中,把useFsEvents 检查项去掉就可以了。


> vi /usr/local/lib/node_modules/truffle/build/cli.bundled.js

// Enable fsevents on OS X when polling isn't explicitly enabled.
//if (undef('useFsEvents')) opts.useFsEvents = !opts.usePolling;

// If we can't use fsevents, ensure the options reflect it's disabled.
//if (!FsEventsHandler.canUse()) opts.useFsEvents = false;
opts.useFsEvents = false;

最后,用浏览器访问HTTP服务,http://103.211.167.71:8080/contracts/Hello.json

如下图所示:

总结一下,在本文中我们使用了trffule工具,完成了智能合约的 代码-编译-部署-测试-打包的完事流程,操作起来还是很方便的。

本文中的所有源代码已经上传到github,请有需要的同学下载使用: https://github.com/bsspirit/truffle-demo

接下来的步骤,就是把我们自定义的Hello.sol部署到Geth的私有网络中,等下篇文章再具体说明。Geth的私有网络环境搭建,请参考文章以太坊测试区块链环境搭建

转载请注明出处:
http://blog.fens.me/bitcoin-eth-truffle

打赏作者

新一代Node.js的Web开发框架Koa2

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者:

  • 张丹(Conan), 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-koa2

前言

Nodejs是一个年轻的编程框架,充满了活力和无限激情,一直都在保持着快速更新,甚至你都追不上他的更新速度。我写的“从零开始nodejs系列文章”,基本把Nodejs的应用领域都涵盖了,但是放下了1年半后,再重新用Nodejs做开发,发现都变了,还要再重新再学一遍。我花了3周的时间,重学了ES6, KOA2, VUE2, VUX, Angular2, Webpack等的框架,终于找回了之前用Nodejs的时候的一些感觉,所以就有了接来的一系列的文章,希望能把我的踩坑经验分享给大家。

本文标题为“新一代Node.js的Web开发框架Koa2”,那么上一代是什么呢?就是Express,我写过2篇文章来介绍Express的,请参考文章:Node.js开发框架Express4.xNodejs开发框架Express3.0开发手记–从零开始。同时,Koa也从Koa的1.x升级了Koa的2.x,一切都在加速,希望大家能够体会到加速世界,给开发带来的快感,这就是风口。

程序代码已经上传到github有需要的同学,自行下载。
https://github.com/bsspirit/koa2-sample

目录

  1. 安装Node环境
  2. 建立项目
  3. 通过koa-generator来建立项目
  4. 目录结构
  5. app.js 文件分析
  6. 路由管理
  7. 页面渲染
  8. 日志分析

1. 安装Node环境

让我们从头开始 Koa2 的安装和使用吧,第一步,就是安装Node和NPM环境。在Window上面安装,就直接从官方网站下载一个可执行安装文件,执行安装即可。

在Linux Ubuntu上安装过程也是类似,本机的系统环境为:Linux Ubuntu 16.04 LTS,然后下载Node工程的源文件,编译,安装。


# 切换到root用户
~ sudo -i      

# 下载nodejs最新版本,源代码       
~ wget https://nodejs.org/dist/v8.4.0/node-v8.4.0.tar.gz
~ tar xvzf node-v8.4.0.tar.gz
~ cd node-v8.4.0

 # 编译、安装
~ ./configure
~ make
~ make install

运行node命令和npm命令


~ node -v
v8.4.0

~ npm -v
5.4.1

2. 建立项目

下面我们就开始创建Koa2的项目,接下来的操作在window与linux通用,只是编辑器不同而已。

进入window的开发目录,新建项目koa2-demo1。


# 进入开发目录
~ cd d:\workspace\js

# 新建项目、并进入项目目录
~ mkdir koa2-demo1 && cd koa2-demo1

创建一个node项目


~ d:\workspace\js\koa2-demo1>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (koa2-demo1)
version: (1.0.0)
description: koa2 demo
entry point: (index.js)
test command:
git repository:
keywords:
author: DanZhang
license: (ISC) MIT
About to write to d:\workspace\js\koa2-demo1\package.json:

{
  "name": "koa2-demo1",
  "version": "1.0.0",
  "description": "koa2 demo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "DanZhang ",
  "license": "MIT"
}

Is this ok? (yes) yes

这样我们就完成了一个最简单的nodejs项目,在当前的目录下面生成了package.json的文件。

接下来,我们来安装koa2的库,到当前的项目中。


~ npm install koa -s
+ koa@2.3.0
added 37 packages in 12.462s

查看当前目录,发现生成了一个node_modules的目录,这个目录存放了koa包所有的依赖的库。

然后,我们新建一个启动文件:index.js,用来启动Koa的项目。


~ notepad index.js

var koa = require('koa');
var app = new koa();

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

启动项目


~ node index.js

打开浏览器: http://localhost:3000/

看到这界面,那么恭喜你,第一步成功了。

3. 通过koa-generator来建立项目

Koa和Express项目一样,都提供了一种脚手架的方式来创建项目,使用这么方式会极大地提高开发的效率,但不建议初学者直接使用,高度自动化的生成器,可能会让你不理解实现的原理。

接下来,我们用koa-generator来生成一个koa的项目。

安装koa-generator,这是一个命令工具,所以需要全局安装。


~ npm install -g koa-generator

然后,我们创建koa2的项目时,就可以直接使用koa2的命令了。


# 进入开发目录
~ cd d:\workspace\js

# 生成koa2项目
~ koa2 koa2-demo2

   create : koa2-demo2
   create : koa2-demo2/package.json
   create : koa2-demo2/app.js
   create : koa2-demo2/public
   create : koa2-demo2/public/stylesheets
   create : koa2-demo2/public/stylesheets/style.css
   create : koa2-demo2/public/javascripts
   create : koa2-demo2/public/images
   create : koa2-demo2/routes
   create : koa2-demo2/routes/index.js
   create : koa2-demo2/routes/users.js
   create : koa2-demo2/views
   create : koa2-demo2/views/index.pug
   create : koa2-demo2/views/layout.pug
   create : koa2-demo2/views/error.pug
   create : koa2-demo2/bin
   create : koa2-demo2/bin/www

   install dependencies:
     > cd koa2-demo2 && npm install

   run the app:
     > SET DEBUG=koa* & npm start koa2-demo2

进入项目,并安装依赖库


~ cd koa2-demo2 && npm install

启动项目


~ npm run start

> koa2-demo2@0.1.0 start d:\workspace\js\koa2-demo2
> node bin/www

打开浏览器

如果你看到的界面,和上面的一样,那么恭喜你,用koa-generator生成的koa2的项目,也能正常工作了。这里你可以发现,用koa-generator时,只要一条命令就够了,程序会给你生成很多文件,直接就构建好了一个项目的基础。

4. 目录结构

接下来,我们就基于koa2-demo2的项目来分析,看一下生成出来的项目目录结构。我们用一个文本编辑器来打开这个项目,这样可以看得更清楚一些。

  • bin, 存放启动项目的脚本文件
  • node_modules, 存放所有的项目依赖库。
  • public,静态文件(css,js,img)
  • routes,路由文件(MVC中的C,controller)
  • views,页面文件(pug模板)
  • package.json,项目依赖配置及开发者信息
  • app.js,应用核心配置文件
  • package.json,node项目配置文件
  • package-lock.json,node项目锁定的配置文件

5. app.js 文件分析

app.js 是应用核心配置文件,我们把这个文件能够看明白,整理koa就非常容易了。

app.js文件,我们可以分割为X个部分进行解读:依赖包加载、错误处理、中间件加载、web界面渲染模板、自定义日志、自己定义路由、外部调用接口。


// 依赖包加载
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

const index = require('./routes/index')
const users = require('./routes/users')

// 错误处理
onerror(app)

// 中间件加载
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

// web界面渲染模板
app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// 自定义日志
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// 自己定义路由
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

// 外部调用接口
module.exports = app

麻雀虽小,五脏俱全!通过app.js的配置,就可以把一个web项目给组装起来了。

6. 路由管理

普通的web应用,通常都是多个页面组成,每个页面会对应一个URL的地址,用户在浏览器输入URL可以打开不同的页面。路由管理,就是把URL和页面的关系对应起来的。

在 app.js 中,路由来提取到 ./routes/index.js 和 ./routes/users.js 两个文件中进行实现了。

以 ./routes/index.js 文件来举例说明。


~ notepad ./routes/index.js

const router = require('koa-router')()

// 解析'/'
router.get('/', async (ctx, next) => {  
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})

// 解析 '/string'
router.get('/string', async (ctx, next) => {
  ctx.body = 'koa2 string'
})

// 解析 '/json'
router.get('/json', async (ctx, next) => {
  ctx.body = {
    title: 'koa2 json'
  }
})

module.exports = router

从代码中分析,当路径为’/’时,网页会返回Hello Koa 2!;当路径为’/string’时,网页会返回koa2 string;哪路径是/json时,网页会返回koa2 json。

7. 页面渲染

从上面路由中,我们看到路径’/’的输出,是一个被渲染为网页输出,而’/string’和’/json’的输出是直接字符的输出。传统的web应用,大都是后台渲染的机制。新型的单面应用来说,前后端分离的设计,根本不需要后端来渲染,直接输出json就可以了。

对后台渲染的实现,我们可以参考’/’的实现。


router.get('/', async (ctx, next) => {
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})

上面代码中,通过 await ctx.render(‘index’,{}) 这样的语法,就可以加载pug的模板引擎。

  • await是ES6的关键字,用于把异步代码同步化,就不再写回调函数了(callback)。
  • ctx.render()函数,用于加载渲染引擎。

然后,我们找到views中的index.pug文件。


~ notepad ./views/index.pug

extends layout

block content
  h1= title
  p Welcome to #{title}

在index.pug文件中,有一个参数是后台传过来的,就是title,同时index.pug继承了layout.pug。

再打开layout.pug文件。


doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content

layout.pug文件,是整个html的骨架,这样就可以通过后台要渲染为一个HTML的网页了。打开chrome浏览品质开发人员工具,看到HTML的代码的结构,与layout.pug和index.pug结构是一致的。

8. 日志分析

最后要说的就是服务器日志了,每一次的浏览行为,都会产生一条服务器日志,用于记录用户的访问情况。我们后台通过命令行启动后,后台的服务器就会一直存活着,接收浏览器的请求,同时产生日志。

日志中,200表示正常访问,404是没有对应的URL错误。你会看到每次访问的路径都被记录了,包括后台的路径和css的文件路径,还包括了访问协议,响应时间,页面大小等。

我们可以自定义日志格式,记录更多的信息,也可以记录对自己有用的信息。这样我们就构建出一个最小化的web应用了。

Nodejs的发展速度,远远超越了其他语言的发展,我看好Nodejs,希望能给入门Nodejs的朋友一些帮助。加油!!

程序代码已经上传到github有需要的同学,自行下载。
https://github.com/bsspirit/koa2-sample

转载请注明出处:
http://blog.fens.me/nodejs-koa2

打赏作者

图片延迟加载库Layzr

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-layzr/

js-layzr

前言

延迟加载技术,普遍应用于现在大部分的互联网的Web应用上。通过对图像的延迟加载,等到用户浏览区域接近时才把图片进行加载,可以有效地提升网页打开的速度,进一步改进用户的体验。

Layzr.js库就可以完成这个功能,150多行代码,很小很快很方便。

目录

  1. layzr介绍
  2. layzr的基本使用
  3. Layzr的配置

1. layzr介绍

layzr.js 是一个很小、速度快、无依赖的,用于浏览器图片延迟加载的库。

我们找到Layzr.js官方的Github上面,dist目录发布的 layzr.min.js 仅有 2.2 KB。同时,发现 package.json 文件,没有任何的dependencies依赖。

用layzr.js进行图片延迟加载,是非常方便的。通过配置选项,实现最大化的加载速度。layzr.js对于滚动事件已去抖,以尽量减少对浏览器的压力,以确保最佳的渲染。

项目官方网站:http://callmecavs.github.io/layzr.js/

2. layzr的基本使用

layzr.js是在浏览器端运行的Javascript库,但是他是用于NPM管理的,还在没有bower中发布。关于NPM和Bower的介绍,请分别查看文章:快速创建基于npm的nodejs库bower解决js的依赖管理

下面我们用npm的方式,下载layzr库。

首先,创建项目目录。


~  D:\workspace\javascript> mkdir js-layzr && cd js-layzr 

新建NPM项目文件package.json。


~ D:\workspace\javascript\js-layzr> vi package.json
{
    "name": "js-layzr",
    "version": "0.0.1",
    "description": "a demo for layzr",
    "dependencies": {}
}

通过NPM下载layzr.js包


~ D:\workspace\javascript\js-layzr> npm install layzr.js --save
npm WARN package.json js-layzr@0.0.1 No repository field.
npm WARN package.json js-layzr@0.0.1 No README data
layzr.js@1.1.4 node_modules\layzr.js

layzr1

接下来,我们打开layzr.js项目提供的Demo的文件,在layzr.js/demo.html。


<!DOCTYPE html>

<!--[if IE 9]>         <html class="ie9" lang="en-US"> <![endif]-->
<!--[if gt IE 9]><!--> <html lang="en-US"> <!--<![endif]-->
  <head>
    <title></title>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
      .demo-images {
        width: 300px;
        margin-right: auto;
        margin-left: auto;
        padding-top: 50vh;
      }

      .demo-image,
      iframe {
        display: block;
        margin-bottom: 100px;
      }
    </style>
  </head>
  <body>
    <div class="wrapper">

      <div class="demo-images">
        <img class="demo-image" src="http://placehold.it/300">
        <img class="demo-image" data-layzr="http://placehold.it/300">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina" data-layzr-bg>
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <iframe width="300" height="169" data-layzr="https://www.youtube.com/embed/2a01Rg2g2Z8"></iframe>
      </div>

    </div><!-- END wrapper -->

    <script src="dist/layzr.js"></script>
    <script>
      // create new instance, defaults shown below
      var layzr = new Layzr({
        selector: '[data-layzr]',
        attr: 'data-layzr',
        retinaAttr: 'data-layzr-retina',
        bgAttr: 'data-layzr-bg',
        threshold: 0,
        callback: null
      });
    </script>
  </body>
</html>

在浏览型预览效果,如下图所示。

layzr2

左边为浏览器页面,右边为Chrome的开发者工具的Elements界面,红色直线为显示和代码的对应关系。当页面向下滚动时候,图片会一张一张的加载。同时代码中,原代码为


<img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">

的部分,会被重新翻译为


<img class="demo-image" src="http://placehold.it/300">

从本Demo中,我们可以猜出layzr库应该有2个组成部分,监听页面事件 和 更新img标签的DOM元素。

3. Layzr的配置选择

在HTML中,图片是用img标签在控制的,要用到Layzr.js库,需要在img标签中增加属性。


<img src="optional/placeholder" data-layzr="normal/image" data-layzr-retina="optional/retina/image" data-layzr-bg >
  • src: 用于定义图像占位符,如果没有定义图像占位符,那么在图像载入前,可能会显示异常。
  • data-layzr: 用于显示的图像
  • data-layzr-retina: 用于图像的延迟加载,测试发现没效果。
  • data-layzr-bg: 图像用于做成背景,测试发现直接被隐藏了。

对应地,我们还需要定义Javascript的实例,用来启动layzr的事件。


var layzr = new Layzr({ 
  selector: '[data-layzr]', 
  attr: 'data-layzr', 
  retinaAttr: 'data-layzr-retina', 
  bgAttr: 'data-layzr-bg', 
  threshold: 0, 
  callback: null 
});

Javascript实例属性:

  • selector: 用于选定图像标签。
  • attr: 用于指定data-layzr的属性
  • retinaAttr: 用于指定data-layzr-retina属性
  • bgAttr: 用于指定data-layzr-bg的属性
  • threshold: 用于定义图像加载参数,通过屏幕高度来控制。
  • callback: 当加载完成,触发事件回调。

接下来,我们新建一个layzr.html的文件,用于测试 layzr 的库各种功能,会比官方提供的Demo更易于理解。

新建文件layzr.html。


<!DOCTYPE html>
<html>
<head>
    <title>Layzr Demo</title>
    <script src="node_modules/layzr.js/dist/layzr.js"></script>
    <style>
        .row {
            margin: 50px 0;
            height: 500px;
        }
        .border {
            padding: 10px;
            border-style: solid;
            border-width: 3px;
            border-color: blue;
        }
    </style>
</head>
<body>
<div class="wrapper">
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/02/marked.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/buffer.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/02/express4.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/child_process.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/crypto.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2014/02/architect.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr-retina="./bg2.png" data-layzr="http://blog.fens.me/wp-content/uploads/2013/08/seo-title.png">
        </div>
</div>

<script>
    var layzr = new Layzr({
        selector: '[data-layzr]',
        attr: 'data-layzr',
        retinaAttr: 'data-layzr-retina',
        threshold: 10,
        callback: function () {
            console.log(this);
            this.classList.add('border');
        }
    });
</script>

</body>
</html>

用浏览器找开页面,我们可以看到展示效果。

layzr3

当浏览器向下滑动的过程中,图片由原来黑色的占位图,会自动替换为我们需要显示的图片。

layzr4

本文测试的代码,已上传到github:https://github.com/bsspirit/js-layzr

这样,我们就实现图像的延迟加载技术。对于有大量图片的网站来说,尝试一下图像的延迟加载技术,肯定会给网站带来大幅度的性能改进的。

转载请注明出处:
http://blog.fens.me/nodejs-layzr/

打赏作者

Node.js进程通信模块child_process

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-child-process/

child_process

前言

Node.js是一种单线程的编程模型,对Node.js的赞美和诟病的也都是因为它的单线程模型,所有的任务都在一个线程中完成(I/O等例外)。单线程模型,不仅让代码非常简洁,更是直接避免了线程调度的复杂性;同样也是因为单线程,让CPU密集型计算应用,完全不适用。

在Node.js的内核中,给了我们一种新的选择,通过child_process模块创建新进程,从而实现多核并行计算。

目录

  1. child_process介绍
  2. child_process的基本使用:spawn, exec, execFile, fork

1. child_process介绍

child_process是Node.js的一个十分重要的模块,通过它可以实现创建多进程,以利用单机的多核计算资源。虽然,Nodejs天生是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信,等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

本文仅从使用上对child_process模块进行介绍,对于深入的内容请参考官方文档,https://nodejs.org/api/child_process.html

关于Node.js线程不错的文章:https://cnodejs.org/topic/518b679763e9f8a5424406e9

2. child_process的基本使用

child_process模块,在v0.12.0版本主要包括4个异步进程函数(spawn,exec,execFile,fork)和3个同步进程函数(spawnSync,execFileSync,execSync)。以异步函数中spawn是最基本的创建子进程的函数,其他三个异步函数都是对spawn不同程度的封装。spawn只能运行指定的程序,参数需要在列表中给出,而exec可以直接运行复杂的命令。

比如要运行 du -sh /disk1 命令, 使用spawn函数需要写成spawn(‘du ‘, [‘-sh ‘, ‘/disk1’]),而使用exec函数时,可以直接写成exec(‘du -sh /disk1’)。exec是会先进行Shell语法解析,因此用exec函数可以更方便的使用复杂的Shell命令,包括管道、重定向等。下面我们就针对每个异步函数,进行测试一下。

系统环境

  • Linux Ubuntu 14.04.1 LTS 64bit
  • Nodejs:v0.13.0-pre
  • Npm:1.4.28

创建项目


~ cd /disk1/demo
~ mkdir nodejs-childprocess && cd nodejs-childprocess

2.1 spawn函数

spawn从定义来看,有3个参数。

child_process.spawn(command[, args][, options])
  • command: 只执行的命令
  • args: 参数列表,可输入多的参数
  • options: 环境变量对象

其中环境变量对象包括7个属性:

  • cwd: 子进程的当前工作目录
  • env: 环境变量键值对
  • stdio: 子进程 stdio 配置
  • customFds: 作为子进程 stdio 使用的文件标示符
  • detached: 进程组的主控制
  • uid: 用户进程的ID.
  • gid: 进程组的ID.

首先,我们运行一下,上文提到的du的命令。直接在命令行,运行的结果。


~ du -sh /disk1
582M    /disk1

新建文件spawn.js


~ vi spawn.js

var child = require('child_process');
var du = child.spawn('du', ['-sh', '/disk1']);
du.stdout.on('data', function (data) {
    console.log('stdout: ' + data);
});
du.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
du.on('exit', function (code) {
    console.log('child process exited with code ' + code);
});

通过node运行的结果


~ node spawn.js

stdout: 582M    /disk1
child process exited with code 0

输出的结果是一样的,这样我们就可以很方便地以异步的方式调用系统命令了,spawn是不支持callback函数的,通过stream的方式发数据传给主进程,从而实现了多进程之间的数据交换。

这个功能的直接用应用场景就是“系统监控”。在Linux下,我们有很多命令行工具,可以实时监控CPU,内存,IO,网络等数据,那么用Node的child_process模块可以很容易地把这些数据采集的我们自己的应用中。

比如,我们用mpstat命令,监控用户CPU的使用情况。先看看mpstat命令直接使用的效果。


~ mpstat 1
Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
11:45:56 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
11:45:57 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:45:58 AM  all   96.50    0.00    3.25    0.00    0.25    0.00    0.00    0.00    0.00    0.00
11:45:59 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:00 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:01 AM  all   96.26    0.00    3.24    0.00    0.25    0.00    0.25    0.00    0.00    0.00
11:46:02 AM  all   96.75    0.00    3.25    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:03 AM  all   96.51    0.00    3.24    0.00    0.25    0.00    0.00    0.00    0.00    0.00
^C
Average:     all   96.50    0.00    3.35    0.00    0.11    0.00    0.04    0.00    0.00    0.00

我们新建文件mpstat.js,读取mpstat命令的数据,然后只输出%usr的数据。


~ vi mpstat.js

var child = require('child_process');
var mpstat = child.spawn('mpstat', ['1']);

//Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
//11:27:12 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
//11:27:13 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00

var line = 0;
var cols = ["time","day","CPU","%usr","%nice","%sys","%iowait","%irq","%soft","%steal","%guest","%gnice","%idle"];
mpstat.stdout.on('data', function (data) {
    var str = data.toString();
    if(line > 2) {
        var arr = str.split(/\s+/);
        console.log(arr[0]+" "+cols[3]+" "+arr[3]);
    }else{
       line++;
    }
});
mpstat.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
mpstat.on('exit', function (code) {
    console.log('child process exited with code ' + code);
});

运行程序:


~ node mpstat.js
11:47:57 %usr 96.75
11:47:58 %usr 96.52
11:47:59 %usr 96.75
11:48:00 %usr 96.25
11:48:01 %usr 96.74
11:48:02 %usr 96.51
11:48:03 %usr 96.74
11:48:04 %usr 96.51

这样就完成系统数据的采集,我们可以把采集到的数据存储到数据库中,通过websocket等协议直接输出的浏览器前端进行数据的展示。监控系统的例子,可以参考我之前写过的一篇文章:websocket服务器监控

2.2 exec函数

exec函数是对spawn的一种友好封装,增加Shell命令解析,可以直接嵌入复杂的命令,比如,管道用法 cat spawn.js exec.js | wc。

我先试用wc命令来统计一下当前目录的文件字数,分别对应3列为 字节数、字数、行数。


~ wc *
   9   29  275 exec.js
  25   95  878 mpstat.js
  16   41  343 spawn.js
  50  165 1496 total

~ cat *.js | wc
     50     165    1496

接下来,我们使用exec函数,来使用Linux管道命令。


~ vi exec.js

var childProcess = require('child_process');

var ls = childProcess.exec('cat *.js | wc', function (error, stdout, stderr) {
   if (error) {
     console.log(error.stack);
     console.log('Error code: '+error.code);
   }
   console.log('Child Process STDOUT: '+stdout);
});

运行程序:


~ node exec.js
Child Process STDOUT:      50     165    1496

输出结果与Linux命令类似,而且用exec时,命令可以写成一个完整的字符串了,不用像spawn函数时分开写成多个参数数组的形式。最后通过一个callback函数来返回,更符合JavaScript的函数调用习惯,通常情况我们可以用exec来替换spawn的使用。

2.3 execFile函数

execFile函数会直接执行特定的程序,参数作为数组传入,不会被bash解释,因此具有较高的安全性。execFile与spawn的参数相似,也需要分别指定执行的命令和参数,但可以接受一个回调函数,与exec的回调函数相同。

我们看一个execFile函数的例子。


~ vi execFile.js

var childProcess = require('child_process');
var path = ".";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    console.log(result)
});

运行程序


~ node execFile.js
total 16
-rw-r--r-- 1 root root 527 Mar 20 13:23 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js

那么,什么时候使用exec,什么时候使用execFile呢?

如果命令参数是由用户来输入的,对于exec函数来说是有安全性风险的,因为Shell会运行多行命令,比如’ls -l .;pwd,如逗号分隔,之后的命令也会被系统运行。但使用exeFile命令时,命令和参数分来,防止了参数注入的安全风险。

我们用程序测试一下。


~ vi execFile.js

// exec
var cmd = 'ls -l .;pwd'
var ls = childProcess.exec(cmd, function (error, stdout, stderr) {
   if (error) {
     console.log(error.stack);
     console.log('Error code: '+error.code);
   }
   console.log('Child Process STDOUT: '+stdout);
});

// execFile
var path = ".;pwd";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    console.log(result)
});

运行程序


~  node execFile.js

{ [Error: Command failed: /bin/ls -l .;pwd
/bin/ls: cannot access .;pwd: No such file or directory
] killed: false, code: 2, signal: null, cmd: '/bin/ls -l .;pwd' }

Child Process STDOUT: total 16
-rw-r--r-- 1 root root 547 Mar 20 13:31 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js
/disk1/demo/nodejs-childprocess

从输出结果看到,exec函数被正常执行,而execFile函数,则提示参数错误。

2.4 fork

fork函数,用于在子进程中运行的模块,如 fork(‘./son.js’) 相当于 spawn(‘node’, [‘./son.js’]) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

我们一个主进程和子进程通信的例子,主进程文件main.js和子进程文件son.js。

新建主进程文件。


~ main.js

var childProcess = require('child_process');
var n = childProcess.fork('./son.js');

n.on('message', function(m) {
  console.log('Main Listen: ', m);
});
n.send({ hello: 'son' });

新建子进程文件。


~ vi son.js

process.on('message', function(m) {
  console.log('Son Listen:', m);
});
process.send({ Hello: 'conan' });

运行程序:


~ node main.js
Main Listen:  { Hello: 'conan' }
Son Listen: { hello: 'son' }

通过main.js启动子进程son.js,通过process在两个进程之间传递数据。我们对系统进程进行检查,看看是否确实有两个进程。


~ ps -aux|grep node
root     22777  0.2  0.1 603240 13252 pts/3    Sl+  14:25   0:00 node main.js
root     22782  0.2  0.1 603540 13516 pts/3    Sl+  14:25   0:00 /usr/local/bin/node ./son.js

有2个node进程分别是node main.js和node ./son.js。

掌握了进程之间的通信,我们可以做的事情就比较多了,比如自己做一个Node.js多进程管理器,调度器之类。相对于Java的多线程管理或者进程调度来说,Node程序是如此简单明了的。我已经非常明显的感觉到了编程语言在进步!!

转载请注明出处:
http://blog.fens.me/nodejs-child-process/

打赏作者

Node.js缓冲模块Buffer

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-buffer/

buffer

前言

Javascript是为浏览器而设计的,能很好的处理unicode编码的字符串,但对于二进制或非unicode编码的数据就显得无能为力。Node.js继承Javascript的语言特性,同时又扩展了Javascript语言,为二进制的数据处理提供了Buffer类,让Node.js可以像其他程序语言一样,能处理各种类型的数据了。

网上有很多讲Buffer的文章,大都讲的是原理,怎么使用几乎找不到,文章将重点介绍Buffer的使用。

目录

  1. Buffer介绍
  2. Buffer的基本使用
  3. Buffer的性能测试

1. Buffer介绍

在Node.js中,Buffer类是随Node内核一起发布的核心库。Buffer库为Node.js带来了一种存储原始数据的方法,可以让Nodejs处理二进制数据,每当需要在Nodejs中处理I/O操作中移动的数据时,就有可能使用Buffer库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。

Buffer 和 Javascript 字符串对象之间的转换需要显式地调用编码方法来完成。以下是几种不同的字符串编码:

  • ‘ascii’ – 仅用于 7 位 ASCII 字符。这种编码方法非常快,并且会丢弃高位数据。
  • ‘utf8’ – 多字节编码的 Unicode 字符。许多网页和其他文件格式使用 UTF-8。
  • ‘ucs2’ – 两个字节,以小尾字节序(little-endian)编码的 Unicode 字符。它只能对 BMP(基本多文种平面,U+0000 – U+FFFF) 范围内的字符编码。
  • ‘base64’ – Base64 字符串编码。
  • ‘binary’ – 一种将原始二进制数据转换成字符串的编码方式,仅使用每个字符的前 8 位。这种编码方法已经过时,应当尽可能地使用 Buffer 对象。Node 的后续版本将会删除这种编码。

Buffer官方文档:http://nodejs.org/api/buffer.html

2. Buffer的基本使用

Buffer的基本使用,主要就是API所提供的操作,主要包括3个部分 创建Buffer类、读Buffer、写Buffer。由于基本操作在官方文档中详细的使用介绍,我只是简单列举一下。

系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

创建项目


~ cd D:\workspace\javascript>
~ D:\workspace\javascript>mkdir nodejs-buffer && cd nodejs-buffer

2.1 创建Buffer类

要创建一个Buffer的实例,我们要通过new Buffer来创建。新建文件buffer_new.js。


~ vi buffer_new.js

// 长度为0的Buffer实例
var a = new Buffer(0);
console.log(a);
> <Buffer >

// 长度为0的Buffer实例相同,a1,a2是一个实例
var a2 = new Buffer(0);
console.log(a2);
> <Buffer >

// 长度为10的Buffer实例
var a10 = new Buffer(10);
console.log(a10);
> <Buffer 22 37 02 00 00 00 00 04 00 00>

// 数组
var b = new Buffer(['a','b',12])
console.log(b);
> <Buffer 00 00 0c>

// 字符编码
var b2 = new Buffer('你好','utf-8');
console.log(b2);
> <Buffer e4 bd a0 e5 a5 bd>

Buffer类有5个类方法,用于Buffer类的辅助操作。

1) 编码检查,上文中提到Buffer和Javascript字符串转换时,需要显式的设置编码,那么这几种编码类型是Buffer所支持的。像中文处理只能使用utf-8编码,对于几年前常用的gbk,gb2312等编码是无法解析的。


// 支持的编码
console.log(Buffer.isEncoding('utf-8'))
console.log(Buffer.isEncoding('binary'))
console.log(Buffer.isEncoding('ascii'))
console.log(Buffer.isEncoding('ucs2'))
console.log(Buffer.isEncoding('base64'))
console.log(Buffer.isEncoding('hex'))  # 16制进
> true

//不支持的编码
console.log(Buffer.isEncoding('gbk'))
console.log(Buffer.isEncoding('gb2312'))
> false

2) Buffer检查,很多时候我们需要判断数据的类型,对应后续的操作。


// 是Buffer类
console.log(Buffer.isBuffer(new Buffer('a')))
> true

// 不是Buffer
console.log(Buffer.isBuffer('adfd'))
console.log(Buffer.isBuffer('\u00bd\u00bd'))
> false

3) 字符串的字节长度,由于字符串编码不同,所以字符串长度和字节长度有时是不一样的。比如,1个中文字符是3个字节,通过utf-8编码输出就是4个中文字符,占12个字节。


var str2 = '粉丝日志';
console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'utf8') + " bytes");
> 粉丝日志: 4 characters, 12 bytes
console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'ascii') + " bytes");
> 粉丝日志: 4 characters, 4 bytes

4) Buffer的连接,用于连接Buffer的数组。我们可以手动分配Buffer对象合并后的Buffer空间大小,如果Buffer空间不够了,则数据会被截断。


var b1 = new Buffer("abcd");
var b2 = new Buffer("1234");
var b3 = Buffer.concat([b1,b2],8);
console.log(b3.toString());
> abcd1234

var b4 = Buffer.concat([b1,b2],32);
console.log(b4.toString());
console.log(b4.toString('hex'));//16进制输出
> abcd1234 乱码....
> 616263643132333404000000000000000000000000000000082a330200000000

var b5 = Buffer.concat([b1,b2],4);
console.log(b5.toString());
> abcd

程序运行的截图
buffer_concat

5) Buffer的比较,用于Buffer的内容排序,按字符串的顺序。


var a1 = new Buffer('10');
var a2 = new Buffer('50');
var a3 = new Buffer('123');

// a1小于a2
console.log(Buffer.compare(a1,a2));
> -1

// a2小于a3
console.log(Buffer.compare(a2,a3));
> 1

// a1,a2,a3排序输出
console.log([a1,a2,a3].sort(Buffer.compare));
> [ <Buffer 31 30>, <Buffer 31 32 33>, <Buffer 35 30> ]

// a1,a2,a3排序输出,以utf-8的编码输出
console.log([a1,a2,a3].sort(Buffer.compare).toString());
> 10,123,50

2.2 写入Buffer

把数据写入到Buffer的操作,新建文件buffer_write.js。


~ vi buffer_write.js

//////////////////////////////
// Buffer写入
//////////////////////////////

// 创建空间大小为64字节的Buffer
var buf = new Buffer(64);

// 从开始写入Buffer,偏移0
var len1 = buf.write('从开始写入');

// 打印数据的长度,打印Buffer的0到len1位置的数据
console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));

// 重新写入Buffer,偏移0,将覆盖之前的Buffer内存
len1 = buf.write('重新写入');
console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));

// 继续写入Buffer,偏移len1,写入unicode的字符串
var len2 = buf.write('\u00bd + \u00bc = \u00be',len1);
console.log(len2 + " bytes: " + buf.toString('utf8', 0, len1+len2));

// 继续写入Buffer,偏移30
var len3 = buf.write('从第30位写入', 30);
console.log(len3 + " bytes: " + buf.toString('utf8', 0, 30+len3));

// Buffer总长度和数据
console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));

// 继续写入Buffer,偏移30+len3
var len4 = buf.write('写入的数据长度超过Buffer的总长度!',30+len3);

// 超过Buffer空间的数据,没有被写入到Buffer中
console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));

buffer_write

Node.js的节点的缓冲区,根据读写整数的范围,提供了不同宽度的支持,使从1到8个字节(8位、16位、32位)的整数、浮点数(float)、双精度浮点数(double)可以被访问,分别对应不同的writeXXX()函数,使用方法与buf.write()类似。


buf.write(string[, offset][, length][, encoding])
buf.writeUIntLE(value, offset, byteLength[, noAssert])
buf.writeUIntBE(value, offset, byteLength[, noAssert])
buf.writeIntLE(value, offset, byteLength[, noAssert])
buf.writeIntBE(value, offset, byteLength[, noAssert])
buf.writeUInt8(value, offset[, noAssert])
buf.writeUInt16LE(value, offset[, noAssert])
buf.writeUInt16BE(value, offset[, noAssert])
buf.writeUInt32LE(value, offset[, noAssert])
buf.writeUInt32BE(value, offset[, noAssert])
buf.writeInt8(value, offset[, noAssert])
buf.writeInt16LE(value, offset[, noAssert])
buf.writeInt16BE(value, offset[, noAssert])
buf.writeInt32LE(value, offset[, noAssert])
buf.writeInt32BE(value, offset[, noAssert])
buf.writeFloatLE(value, offset[, noAssert])
buf.writeFloatBE(value, offset[, noAssert])
buf.writeDoubleLE(value, offset[, noAssert])
buf.writeDoubleBE(value, offset[, noAssert])

另外,关于Buffer写入操作,还有一些Buffer类的原型函数可以操作。

Buffer复制函数 buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])。


// 新建两个Buffer实例
var buf1 = new Buffer(26);
var buf2 = new Buffer(26);

// 分别向2个实例中写入数据
for (var i = 0 ; i < 26 ; i++) {
    buf1[i] = i + 97; // 97是ASCII的a
    buf2[i] = 50; // 50是ASCII的2
}

// 把buf1的内存复制给buf2
buf1.copy(buf2, 5, 0, 10); // 从buf2的第5个字节位置开始插入,复制buf1的从0-10字节的数据到buf2中
console.log(buf2.toString('ascii', 0, 25)); // 输入buf2的0-25字节
> 22222abcdefghij2222222222

Buffer填充函数 buf.fill(value[, offset][, end])。


// 新建Buffer实例,长度20节节
var buf = new Buffer(20);

// 向Buffer中填充数据
buf.fill("h");
console.log(buf)
> <Buffer 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68>
console.log("buf:"+buf.toString())
> buf:hhhhhhhhhhhhhhhhhhhh
// 清空Buffer中的数据
buf.fill();
console.log("buf:"+buf.toString())
> buf:

Buffer裁剪,buf.slice([start][, end])。返回一个新的缓冲区,它和旧缓冲区指向同一块内存,但是从索引 start 到 end 的位置剪裁。


var buf1 = new Buffer(26);
for (var i = 0 ; i < 26 ; i++) {
    buf1[i] = i + 97;
}

// 从剪切buf1中的0-3的位置的字节,新生成的buf2是buf1的一个切片。
var buf2 = buf1.slice(0, 3);
console.log(buf2.toString('ascii', 0, buf2.length));
> abc

// 当修改buf1时,buf2同时也会发生改变
buf1[0] = 33;
console.log(buf2.toString('ascii', 0, buf2.length));
> !bc

2.3 读取Buffer

我们把数据写入Buffer后,我们还需要把数据从Buffer中读出来,新建文件buffer_read.js。我们可以通过readXXX()函数获得对应该写入时编码的索引值,再转换原始值取出,有这种方法操作中文字符就会变得麻烦,最常用的读取Buffer的方法,其实就是toString()。


~ vi buffer_read.js

//////////////////////////////
// Buffer 读取
//////////////////////////////

var buf = new Buffer(10);
for (var i = 0 ; i < 10 ; i++) {
    buf[i] = i + 97;
}
console.log(buf.length + " bytes: " + buf.toString('utf-8'));
> 10 bytes: abcdefghij

// 读取数据
for (ii = 0; ii < buf.length; ii++) {
    var ch = buf.readUInt8(ii); // 获得ASCII索引
    console.log(ch + ":"+ String.fromCharCode(ch));
}
> 97:a
98:b
99:c
100:d
101:e
102:f
103:g
104:h
105:i
106:j

写入中文数据,以readXXX进行读取,会3个字节来表示一个中文字。


var buf = new Buffer(10);
buf.write('abcd')
buf.write('数据',4)
for (var i = 0; i < buf.length; i++) {
    console.log(buf.readUInt8(i));
}

>97
98
99
100
230  // 230,149,176 代表“数”
149
176
230  // 230,141,174 代表“据”
141
174 

如果想输出正确的中文,那么我们可以用toString(‘utf-8’)的函数来操作。


console.log("buffer :"+buf); // 默认调用了toString()的函数
> buffer :abcd数据
console.log("utf-8  :"+buf.toString('utf-8'));
> utf-8  :abcd数据
console.log("ascii  :"+buf.toString('ascii'));//有乱码,中文不能被正确解析
> ascii  :abcdf0f
.
console.log("hex    :"+buf.toString('hex')); //16进制
> hex    :61626364e695b0e68dae

对于Buffer的输出,我们用的最多的操作就是toString(),按照存入的编码进行读取。除了toString()函数,还可以用toJSON()直接Buffer解析成JSON对象。


var buf = new Buffer('test');
console.log(buf.toJSON());
> { type: 'Buffer', data: [ 116, 101, 115, 116 ] }

3. Buffer的性能测试

通过上文中对Buffer的介绍,我们已经了解了Buffer的基本使用,接下来,我们要开始做Buffer做一些测试。

3.1 8K的创建测试

每次我们创建一个新的Buffer实例时,都会检查当前Buffer的内存池是否已经满,当前内存池对于新建的Buffer实例是共享的,内存池的大小为8K。

如果新创建的Buffer实例大于8K时,就把Buffer交给SlowBuffer实例存储;如果新创建的Buffer实例小于8K,同时小于当前内存池的剩余空间,那么这个Buffer存入当前的内存池;如果Buffer实例不大0,则统一返回默认的zerobuffer实例。

下面我们创建2个Buffer实例,第一个是以4k为空间,第二个以4.001k为空间,循环创建10万次。


var num = 100*1000;
console.time("test1");
for(var i=0;i<num;i++){
    new Buffer(1024*4);
}
console.timeEnd("test1");
> test1: 132ms

console.time("test2");
for(var j=0;j<num;j++){
    new Buffer(1024*4+1);
}
console.timeEnd("test2");
> test2: 163ms

第二个以4.001k为空间的耗时多23%,这就意味着第二个,每二次循环就要重新申请一次内存池的空间。这是需要我们非常注意的。

3.2 多Buffer还是单一Buffer

当我们需要对数据进行缓存时,创建多个小的Buffer实例好,还是创建一个大的Buffer实例好?比如我们要创建1万个长度在1-2048之间不等的字符串。


var max = 2048;     //最大长度
var time = 10*1000; //循环1万次

// 根据长度创建字符串
function getString(size){
    var ret = ""
    for(var i=0;i<size;i++) ret += "a";
    return ret;
}

// 生成字符串数组,1万条记录
var arr1=[];
for(var i=0;i<time;i++){
    var size = Math.ceil(Math.random()*max)
    arr1.push(getString(size));
}
//console.log(arr1);

// 创建1万个小Buffer实例
console.time('test3');
var arr_3 = [];
for(var i=0;i<time;i++){
    arr_3.push(new Buffer(arr1[i]));
}
console.timeEnd('test3');
> test3: 217ms

// 创建一个大实例,和一个offset数组用于读取数据。
console.time('test4');
var buf = new Buffer(time*max);
var offset=0;
var arr_4=[];
for(var i=0;i<time;i++){
    arr_4[i]=offset;
    buf.write(arr1[i],offset,arr1[i].length);
    offset=offset+arr1[i].length;
}
console.timeEnd('test4');
> test4: 12ms

读取索引为2的数据。


console.log("src:[2]="+arr1[2]);
console.log("test3:[2]="+arr_3[2].toString());
console.log("test4:[2]="+buf.toString('utf-8',arr_4[2],arr_4[3]));

运行结果如图所示。
buffer_test

对于这类的需求来说,提前生成一个大的Buffer实例进行存储,要比每次生成小的Buffer实例高效的多,能提升一个数量级的计算效率。所以,理解并用好Buffer是非常重要的!!

3.3 string VS Buffer

有了Buffer我们是否需求把所有String的连接,都换成Buffer的连接?那么我们就需要测试一下,String和Buffer做字符串连接时,哪个更快一点?

下面我们进行字符串连接,循环30万次。


//测试三,Buffer VS string
var time = 300*1000;
var txt = "aaa"

var str = "";
console.time('test5')
for(var i=0;i<time;i++){
    str += txt;
}
console.timeEnd('test5')
> test5: 24ms

console.time('test6')
var buf = new Buffer(time * txt.length)
var offset = 0;
for(var i=0;i<time;i++){
    var end = offset + txt.length;
    buf.write(txt,offset,end);
    offset=end;
}
console.timeEnd('test6')
> test6: 85ms

从测试结果,我们可以明显的看到,String对字符串的连接操作,要远快于Buffer的连接操作。所以我们在保存字符串的时候,该用string还是要用string。那么只有在保存非utf-8的字符串以及二进制数据的情况,我们才用Buffer。

6. 程序代码

本文的程序代码,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学习buffer,下载地址:https://github.com/bsspirit/nodejs-buffer

也可以直接用github命令行来下载:


~ git clone git@github.com:bsspirit/nodejs-buffer.git   # 下载github项目
~ cd nodejs-buffer                                      # 进入下载目录

关于Node.js的底层,本人接触并不多,未能从V8(C++)的做更深入的研究,仅仅在使用层次上,写出我的总结。对于文中的错误或描述不清楚的地方,还请大牛予以指正!!

参考文章:

浅析nodejs的buffer类

转载请注明出处:
http://blog.fens.me/nodejs-buffer/

打赏作者