<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Node.js</title>
	<atom:link href="https://blog.gmem.cc/tag/node-js/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Sun, 12 Apr 2026 02:07:19 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Socket.io学习笔记</title>
		<link>https://blog.gmem.cc/socket-io-study-note</link>
		<comments>https://blog.gmem.cc/socket-io-study-note#comments</comments>
		<pubDate>Tue, 29 Mar 2016 09:09:16 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Network]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[WebSocket]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=14330</guid>
		<description><![CDATA[<p>简介 Socket.io是一个Web通信框架框架，同时支持基于浏览器环境的客户端、基于Node.js的服务器端。它实现了实时的、事件驱动的双向通信。使用Socket.io，你可以： 推送数据，让客户端展示实时更新的仪表/图表、文本信息 推送二进制流，从1.0版本开始，Socket.io支持推送图片、音频、视频 实现多用户协作，例如联网游戏、共同编辑文档 Socket.io主要由两个部分组成： socket.io模块，集成到Node.js的http模块的服务器 socket.io-client，在浏览器中运行的客户端 Socket.io支持多种传输机制，例如WebSocket、Adobe Flash Sockets、XHR轮询、JsonP轮询，它们被隔离在统一的接口之下，这意味着任何浏览器都可以作为客户端。 标准的WebSocket服务器并不能和Socket.io客户端进行直接通信，需要注意这一点。  入门 聊天室应用 我们以一个简单的聊天室应用作为入门教程，你会发现利用Socket.io编写这类应用有多简单。特别是前后端都是基于Socket.io实现的时候，连API接口都一样，太方便了。 首先，为工程添加依赖： [crayon-69db8ad173829704846781/] 服务器代码 [crayon-69db8ad173830819527393/] 客户端代码 [crayon-69db8ad173833102939236/] 运行代码 <a class="read-more" href="https://blog.gmem.cc/socket-io-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/socket-io-study-note">Socket.io学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Socket.io是一个Web通信框架框架，同时支持基于浏览器环境的客户端、基于Node.js的服务器端。它实现了实时的、事件驱动的双向通信。使用Socket.io，你可以：</p>
<ol>
<li>推送数据，让客户端展示实时更新的仪表/图表、文本信息</li>
<li>推送二进制流，从1.0版本开始，Socket.io支持推送图片、音频、视频</li>
<li>实现多用户协作，例如联网游戏、共同编辑文档</li>
</ol>
<p>Socket.io主要由两个部分组成：</p>
<ol>
<li>socket.io模块，集成到Node.js的http模块的服务器</li>
<li>socket.io-client，在浏览器中运行的客户端</li>
</ol>
<p>Socket.io支持多种传输机制，例如WebSocket、Adobe Flash Sockets、XHR轮询、JsonP轮询，它们被隔离在统一的接口之下，这意味着任何浏览器都可以作为客户端。</p>
<p>标准的WebSocket服务器并不能和Socket.io客户端进行直接通信，需要注意这一点。 </p>
<div class="blog_h1"><span class="graybg">入门</span></div>
<div class="blog_h2"><span class="graybg">聊天室应用</span></div>
<p>我们以一个简单的聊天室应用作为入门教程，你会发现利用Socket.io编写这类应用有多简单。特别是前后端都是基于Socket.io实现的时候，连API接口都一样，太方便了。</p>
<p>首先，为工程添加依赖：</p>
<pre class="crayon-plain-tag">npm install --save express
npm install --save socket.io</pre>
<div class="blog_h3"><span class="graybg">服务器代码</span></div>
<pre class="crayon-plain-tag">// 创建一个Express应用
var app = require('express')();
app.use(function (req, res, next) {
    next();
})
// 创建一个HTTP服务器
var http = require('http').Server(app);
// 创建一个IO实例，可以看到socket.io和Express可以共享一个HTTP服务器套接字
var io = require('socket.io')(http);
// Express代码，首页
app.get('/', function (req, res) {
    res.sendfile('index.html');
});
// 侦听客户端连接事件
io.on('connection', function (socket) {
    console.log('a user connected');
    // socket变量为代表一个客户端连接的套接字
    socket.on('chat message', function (msg) {
        // 用户输入文本，并点击发送后，该事件由客户端发送
        console.log('message: ' + msg);
        // 服务器把可以把消息发送给所有人：
        io.emit('chat message', msg);
    });
    socket.on('disconnect', function () {
        // 用户刷新浏览器时，会触发该事件，因为socket.io-client做了清理工作
        console.log('user disconnected');
    });
});
// 启动HTTP服务器
http.listen(3000, function () {
    console.log('listening on *:3000');
});</pre>
<div class="blog_h3"><span class="graybg">客户端代码</span></div>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;title&gt;Socket.IO chat&lt;/title&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;style&gt;
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font: 13px Helvetica, Arial; }
        form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
        form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
        form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
        #messages { list-style-type: none; margin: 0; padding: 0; }
        #messages li { padding: 5px 10px; }
        #messages li:nth-child(odd) { background: #eee; }
    &lt;/style&gt;
    &lt;!-- 这个URL由Socket.io服务器负责生成 --&gt;
    &lt;script src="/socket.io/socket.io.js"&gt;&lt;/script&gt;
    &lt;script src="http://code.jquery.com/jquery-1.11.1.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;ul id="messages"&gt;&lt;/ul&gt;
&lt;form action=""&gt;
    &lt;input id="m" autocomplete="off"/&gt;
    &lt;button&gt;Send&lt;/button&gt;
&lt;/form&gt;
&lt;script&gt;
    // 暴露了全局函数io，调用它后会尝试基于WebSocket协议连接到服务器，并得到一个客户端套接字对象
    // 此对象就像Node.js中的套接字一样，是一个EventEmitter
    var socket = io();
    $('form').submit(function(){
        // Socket.io的主要思想就是，你可以接收、发送任何事件，事件的载荷可以是任何数据，包括编码为JSON的对象或者二进制数据
        socket.emit('chat message', $('#m').val());
        $('#m').val('');
        return false;
    });
    socket.on('chat message', function(msg){
        // 该事件由服务器发送，它把消息广播到所有客户端
        $('#messages').append($('&lt;li&gt;').text(msg));
    });
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<div class="blog_h3"><span class="graybg">运行代码</span></div>
<p>执行命令<pre class="crayon-plain-tag">node app.js</pre> 启动服务器，然后浏览器打开http://127.0.0.1:3000，多打开几个这样的窗口，就可以聊天了。</p>
<div class="blog_h1"><span class="graybg">基础</span></div>
<div class="blog_h2"><span class="graybg">创建IO实例</span></div>
<p>有几种不同的方法：</p>
<pre class="crayon-plain-tag">// 方法一：配合Node.js的HTTP服务
var app = require( 'http' ).createServer( function ( req, res ) {
    /* */
} );
var io = require('socket.io')(app);
app.listen(80);
// 方法二：配合Express框架
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(80);
// 方法三：隐式HTTP服务创建
var io = require('socket.io')(80); </pre>
<div class="blog_h2"><span class="graybg">名字空间</span></div>
<p>你可以使用名字空间（即端点，对应URL路径）实现单个WebSocket的多路复用（multiplexing），这样多个应用模块可以共享单个TCP连接：</p>
<pre class="crayon-plain-tag">var io = require( 'socket.io' )( 80 );
var chat = io
    // 名字空间一
    .of( '/chat' )
    .on( 'connection', function ( socket ) {
        // 发送消息的两种方式：
        socket.emit( 'a message', {
            that: 'only' , '/chat': 'will get'
        } );
        chat.emit( 'a message', {
            everyone: 'in' , '/chat': 'will get'
        } );
    } );

var news = io
    // 名字空间二
    .of( '/news' )
    .on( 'connection', function ( socket ) {
        socket.emit( 'item', { news: 'item' } );
    } );</pre><br />
<pre class="crayon-plain-tag">&lt;script&gt;
    // 名字空间一
    var chat = io.connect( 'http://localhost/chat' );
    // 名字空间二，如果 协议://主机名:端口 与当前网页的相同，可以省略
    var news = io.connect( '/news' );

    chat.on( 'connect', function () {
        chat.emit( 'hi!' );
    } );

    news.on( 'news', function () {
        news.emit( 'woot' );
    } );
&lt;/script&gt;</pre>
<div class="blog_h3"><span class="graybg">默认名字空间</span></div>
<p>Socket.io客户端默认连接到此名字空间，对应URL路径 <pre class="crayon-plain-tag">/</pre> ，Socket.io服务器默认也在此名字空间上监听。</p>
<p>下面的代码都是在默认名字空间上发送消息：</p>
<pre class="crayon-plain-tag">io.sockets.emit('hi', 'everyone');    // 方式一
io.emit('hi', 'everyone');            // 方式二 </pre>
<p>在默认名字空间上，也可以监听connection事件，来监听新的客户端连接：</p>
<pre class="crayon-plain-tag">io.on( 'connection', function ( socket ) {
    socket.on( 'disconnect', function () { } );
} );</pre>
<div class="blog_h3"><span class="graybg">房间</span></div>
<p>在每个名字空间内部，你还可以定义任意数量的房间（频道，channel）。每个Socket都可以自由的<pre class="crayon-plain-tag">join()</pre> 或者<pre class="crayon-plain-tag">leave()</pre> 房间：</p>
<pre class="crayon-plain-tag">// 在连接后立即加入房间
io.on( 'connection', function ( socket ) {
    socket.join( 'some room' );
} );
// 调用to可以随时加入房间
io.to( 'some room' ).emit( 'some event' );
// 离开房间
io.leave( 'some room' );</pre>
<p> Socket.io中的每一个Socket由一个随机的、唯一的、不可猜测的ID来标识。Socket会自动加入到以此ID来标识的房间：</p>
<pre class="crayon-plain-tag">io.on( 'connection', function ( socket ) {
    socket.on( 'say to someone', function ( id, msg ) {
        // 广播到房间
        socket.broadcast.to( id ).emit( 'my message', msg );
    } );
} );</pre>
<p>在断开连接时，Socket会自动离开所有加入过的房间。</p>
<div class="blog_h2"><span class="graybg">易失消息</span></div>
<p>如果某些客户端没有准备好接收消息，原因可能包括：网络缓慢、客户端基于长轮询协议连接且正处于请求-响应周期中间，则允许消息丢失，可以提高性能。</p>
<p>某些消息即使丢失，也不会对应用造成太大影响。例如频繁更新的温湿度监测值，丢失一两个数值，可能不会影响客户端的曲线图渲染。这种情况下，我们可以声明消息是易失的（volatile）：</p>
<pre class="crayon-plain-tag">io.on('connection', function (socket) {
    // 发送一个易失的消息
    socket.volatile.emit('bieber tweet', tweet);
});</pre>
<div class="blog_h2"><span class="graybg">消息确认</span></div>
<p>某些情况下，你可能需要在客户端确认（acknowledgement）接收到消息之后，执行一个回调。这种情况下，你可以为send/emit方法提供函数作为最后一个参数。<span style="background-color: #c0c0c0;">使用emit时，确认操作由消息接收者手工触发并且可以附带数据</span>：</p>
<pre class="crayon-plain-tag">&lt;script&gt;
  var socket = io(); // 不带参数的调用io()可以进行自动服务发现
  socket.on('connect', function () { // 你可以监听具体事件，也可以监听connect
      // 像服务器发送一个消息，消息确认后，执行回调
      socket.emit('ferret', 'tobi', function (data) {
          console.log(data); // 打印服务器返回的确认消息
      });
  });
&lt;/script&gt;</pre><br />
<pre class="crayon-plain-tag">io.on( 'connection', function ( socket ) {
    // 服务器，接收客户端emit来的消息后，可以手工的进行确认
    socket.on( 'ferret', function ( msg, fn ) {
        // 调用第二个参数即可确认，其入参是返回给客户端的确认消息
        fn( 'woot' );
    } );
} );</pre>
<div class="blog_h2"><span class="graybg">广播消息</span></div>
<p>所谓广播，是指像所有Socket发送消息，除了广播消息的这个Socket：</p>
<pre class="crayon-plain-tag">io.on( 'connection', function ( socket ) {
    // 广播一个消息
    socket.broadcast.emit( 'user connected' );
} );</pre>
<div class="blog_h2"><span class="graybg">作为WebSocket客户端</span></div>
<p>如果你仅仅想使用WebSocket语义，只需要调用send方法，然后监听message事件：</p>
<pre class="crayon-plain-tag">io.on( 'connection', function ( socket ) {
    // 接收到客户端消息时：
    socket.on( 'message', function ( msg ) {
    } );
    socket.on( 'disconnect', function () {
    } );
} );</pre><br />
<pre class="crayon-plain-tag">&lt;script&gt;
var socket = io( 'http://localhost/' );
socket.on( 'connect', function () {
    // 发送一个消息
    socket.send( 'hi' );
    // 接收到服务器消息时：
    socket.on( 'message', function ( msg ) {
    } );
} );
&lt;/script&gt;</pre>
<div class="blog_h2"><span class="graybg">多Node.js实例</span></div>
<p>当利用多个Node.js进程或机器来进行负载均衡时，你需要会话关联性（session affinity）。即，关联到特定Session ID的请求需要发送给最初产生会话的那个Node.js进程。</p>
<p>上述要求的原因是，某些Socket.io传输机制——XHR轮询、JSONP轮询——依赖于在Socket的生命周期内发起的多个HTTP请求。WebSocket传输则不存在这个问题，因为整个会话中它只会使用一个TCP长连接。</p>
<p>使用NginX可以很容易的实现会话关联性。如果使用Node.js的Cluster模块，可以结合<a href="https://github.com/indutny/sticky-session">sticky session</a>。</p>
<div class="blog_h3"><span class="graybg">跨进程事件</span></div>
<p>某些情况下，你可能需要在跨越多个Node.js进程向特定名字空间、房间发送消息，或者广播消息。你可以使用<a href="https://github.com/socketio/socket.io-redis">socket.io-redis</a>、<a href="https://github.com/socketio/socket.io-emitter">socket.io-emitter</a>之类的模块。</p>
<p>socket.io-redis是一个适配器，可以实现跨越多个进程来路由消息。使用它来结合Redis，你可以在多个进程中运行多个Socket.io实例，这些实例之间可以正常收发消息：</p>
<pre class="crayon-plain-tag">var io = require( 'socket.io' )( 3000 );
var redis = require( 'socket.io-redis' );
/**
 * 选项：
 * key 发布/订阅事件的前缀，默认socket.io
 * host Redis所在主机
 * port Redis监听端口
 * subEvent 可选，需要订阅的Redis客户端事件名，默认messageBuffer
 *
 * pubClient 可选，用于发布事件的Redis客户端
 * subClient 可选，用于订阅事件的Redis客户端
 * 如果提供上面两者之一，必须使用node_redis或者相同API的客户端
 *
 * requestsTimeout 可选，适配器等待响应的超时时间，默认1000ms
 * withChannelMultiplexing 可选，是否启用频道复用，默认true
 *
 */
io.adapter( redis( { host: 'localhost', port: 6379 } ) );</pre>
<div class="blog_h2"><span class="graybg">调试</span></div>
<p>在v1.0之前，Socket.io直接把调试信息打印在console，当前版本则基于<a href="https://github.com/visionmedia/debug">debug</a>模块打印调试日志。</p>
<p>要查看服务器端日志，以<pre class="crayon-plain-tag">DEBUG=* node app.js</pre> 启动应用。要查看浏览器日志，使用：<pre class="crayon-plain-tag">localStorage.debug = '*';</pre> </p>
<div class="blog_h2"><span class="graybg">通配符事件</span></div>
<p>Socket.io本身不支持对所有事件进行统一的处理，但是你可以通过中间件<a href="https://github.com/hden/socketio-wildcard">socketio-wildcard</a>满足这一需求：</p>
<pre class="crayon-plain-tag">npm install --save socketio-wildcard</pre>
<p>服务器代码示例：</p>
<pre class="crayon-plain-tag">var io = require( 'socket.io' )();
var middleware = require( 'socketio-wildcard' )();
// 启用中间件
io.use( middleware );

io.on( 'connection', function ( socket ) {
    // 监听通配符事件
    socket.on( '*', function ( packet ) {
        packet.data === [ 'foo', 'bar', 'baz' ]
    } );
} );

io.listen( 3000 );</pre>
<p>客户端代码示例：</p>
<pre class="crayon-plain-tag">var io = require( 'socket.io-client' );
var socket = io( 'http://localhost' );
var patch = require( 'socketio-wildcard' )( io.Manager );
patch( socket );
// 监听通配符事件
socket.on( '*', function () {
} ); </pre>
<div class="blog_h1"><span class="graybg">API</span></div>
<div class="blog_h2"><span class="graybg">服务器</span></div>
<div class="blog_h3"><span class="graybg">Server</span></div>
<p>该函数由<pre class="crayon-plain-tag">require('socket.io')</pre> 暴露：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">调用/属性/方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>( )</td>
<td>创建一个服务器，可以使用new：<br />
<pre class="crayon-plain-tag">var io = require('socket.io')();
// 或者
var Server = require('socket.io');
var io = new Server();</pre>
</td>
</tr>
<tr>
<td>( opts )</td>
<td>创建一个服务器，opts为选项：<br />serveClient  调用serveClient()方法<br />path  调用path()方法</td>
</tr>
<tr>
<td>( http, opts )</td>
<td>创建一个服务器，http为Node.js的HTTP服务器</td>
</tr>
<tr>
<td>( port, opts )</td>
<td>创建一个服务器，在指定端口自动创建Node.js的HTTP服务器</td>
</tr>
<tr>
<td>serveClient( Bool ):Server</td>
<td>如果为true，则关联的HTTP服务器负责给客户端服务静态文件，默认true</td>
</tr>
<tr>
<td>path( String ):Server</td>
<td>在什么路径下为客户端服务静态文件，默认 /socket.io</td>
</tr>
<tr>
<td>adapter( Adapter ):Server</td>
<td>
<p>设置适配器，适配器用于<span style="background-color: #c0c0c0;">实现消息存储</span>，参数为Adapter实例。默认使用的适配器是基于内存的 socket.io-adapter</p>
<p>如果不指定入参，则返回当前使用的适配器</p>
</td>
</tr>
<tr>
<td>origins( String ):Server</td>
<td>设置允许的客户端 Orgin头，如果不指定入参，则返回当前值</td>
</tr>
<tr>
<td>sockets:Namespace</td>
<td>返回默认 / 名字空间对象</td>
</tr>
<tr>
<td>attach( http, opts ):Server<br />attach( port, opts ):Server</td>
<td>
<p>附着到一个engine.io实例，返回得到的Server对象</p>
<p>该函数的别名是listen</p>
</td>
</tr>
<tr>
<td>of( String ):Namespace</td>
<td>根据路径，初始化或者取得一个名字空间对象</td>
</tr>
<tr>
<td>emit()</td>
<td>发送一个消息到所有客户端</td>
</tr>
<tr>
<td>use()</td>
<td>注册中间件</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Namespace</span></div>
<p>该类型代表连接到特定Scope的客户端Socket的池，以路径标识。</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">属性/方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡ connection   <br />⚡ connect</td>
<td>当一个客户端连接到此名字空间时触发，回调函数的入参时客户端Socket对象</td>
</tr>
<tr>
<td>name:String</td>
<td>返回标识符</td>
</tr>
<tr>
<td>connected:Object</td>
<td>连接到此名字空间的Sockets对象的Hash，键为Socket的ID</td>
</tr>
<tr>
<td>use( Function ):Namespace</td>
<td>注册一个中间件，所谓中间件就是一个函数，它会针对每一个接入的Socket调用：<br />
<pre class="crayon-plain-tag">var io = require( 'socket.io' )();
// next代表下一个中间件，用法与Express中间件类似
io.use( function ( socket, next ) {
    // 调用下一个中间件
    if ( socket.request.headers.cookie ) return next();
    // 向客户端发送error包
    next( new Error( 'Authentication error' ) );
} );</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Socket</span></div>
<p>与客户端通信的基础对象。<span style="background-color: #c0c0c0;">Socket属于特定的名字空间</span>，在底层使用Client对象进行通信。</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">属性/方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>rooms:Array</td>
<td>套接字加入的房间列表</td>
</tr>
<tr>
<td>client:Client</td>
<td>底层的Client对象</td>
</tr>
<tr>
<td>conn:Socket</td>
<td>底层Client对象使用的连接对象</td>
</tr>
<tr>
<td>request:Request</td>
<td>产生此Socket的HTTP请求对象，用于访问Cookie、User-Agent等</td>
</tr>
<tr>
<td>id:String</td>
<td>Socket会话的ID，来自底层Client对象</td>
</tr>
<tr>
<td>emit(name[, …]):Socket</td>
<td>发布一个名为name的事件，后么可以跟着事件的内容。任何数据结构都被支持，包括Buffer。注意不能发布函数，因为其不可串行化</td>
</tr>
<tr>
<td>join(name[, fn]):Socket</td>
<td>
<p>加入到一个房间中，fn(err)为可选的回调。Socket会自动加入到以其ID命名的房间</p>
<p>加入房间的机制由Adapter实现</p>
</td>
</tr>
<tr>
<td>leave(name[, fn]):Socket</td>
<td>
<p>从房间中移除一个Socket，fn(err)为可选的回调</p>
<p>当断开连接时，Socket自动离开所有房间</p>
</td>
</tr>
<tr>
<td>to(room):Socket</td>
<td rowspan="2">让后续的事件发布仅仅在room范围内广播</td>
</tr>
<tr>
<td>in(room):Socket</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">其它客户端</span></div>
<div class="blog_h2"><span class="graybg">Java</span></div>
<p><a href="https://github.com/socketio/socket.io-client-java">socket.io-client-java</a>提供了全特性支持的Java的Socket.io客户端。</p>
<div class="blog_h3"><span class="graybg">Maven依赖</span></div>
<pre class="crayon-plain-tag">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.socket&lt;/groupId&gt;
        &lt;artifactId&gt;socket.io-client&lt;/artifactId&gt;
        &lt;version&gt;0.8.3&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</pre>
<div class="blog_h3"><span class="graybg">用法示例</span></div>
<pre class="crayon-plain-tag">// 建立一个客户端套接字对象
IO.Options opts = new IO.Options();
opts.forceNew = true;   // 当查询参数改变后，是否丢弃既有套接字
opts.reconnection = false;  // 
opts.query = "auth_token=" + authToken;  // 指定查询参数
socket = IO.socket( "http://localhost", opts );
// 监听连接事件
socket.on( Socket.EVENT_CONNECT, new Emitter.Listener() {

    public void call( Object... args ) {
        socket.emit( "foo", "hi" ); // 发送消息
        socket.disconnect();
    }

} ).on( "event", new Emitter.Listener() { // 监听一般性事件

    public void call( Object... args ) {
        // Java客户端使用org.json来进行JSON对象的编码、解码
        JSONObject obj = (JSONObject)args[0];

        obj = new JSONObject();
        obj.put("hello", "server");
        obj.put("binary", new byte[42]);
        socket.emit("foo", obj);
    }

} ).on( Socket.EVENT_DISCONNECT, new Emitter.Listener() { // 监听断开事件

    public void call( Object... args ) {
    }

} );
// 发起连接
socket.connect();

// 在服务器确认收到消息后，执行回调
socket.emit("foo", "woot", new Ack() {
   @Override
   public void call(Object... args) {}
});

// 在收到服务器消息后，予以确认
socket.on("foo", new Emitter.Listener() {
   @Override
   public void call(Object... args) {
   Ack ack = (Ack) args[args.length - 1];
       ack.call();
   }
});</pre>
<div class="blog_h2"><span class="graybg">iOS</span></div>
<p>可以使用<a href="https://github.com/MegaBits/SIOSocket">SIOSocket</a></p>
<div class="blog_h2"><span class="graybg">Cordova应用</span></div>
<p>参考：<a href="http://socket.io/socket-io-with-apache-cordova/">Socket.IO with Apache Cordova</a></p>
<div class="blog_h1"><span class="graybg">其它服务器</span></div>
<p>如果服务器的开发平台不是Node.js，你就需要第三方的Socket.io实现了。注意：单纯的WebSocket服务器不能和Socket.io客户端正常通信。</p>
<div class="blog_h2"><span class="graybg">netty-socketio</span></div>
<p><a href="https://github.com/mrniko/netty-socketio">这是</a>基于Netty网络框架，在Java开发环境下实现Socket.io服务器的开源项目。</p>
<div class="blog_h3"><span class="graybg">Maven依赖</span></div>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.corundumstudio.socketio&lt;/groupId&gt;
    &lt;artifactId&gt;netty-socketio&lt;/artifactId&gt;
    &lt;version&gt;1.7.7&lt;/version&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h3"><span class="graybg">服务器示例</span></div>
<pre class="crayon-plain-tag">// 服务器配置，主机名和端口
Configuration config = new Configuration();
config.setHostname( "localhost" );
config.setPort( 9092 );
// SocketIO服务器对象
final SocketIOServer server = new SocketIOServer( config );

// 监听新的客户端连接
server.addConnectListener(new ConnectListener() {
    @Override
    public void onConnect(SocketIOClient client) {
       // 参数为新的客户端
    }
});

// 监听客户端断开
server.addDisconnectListener( new DisconnectListener() {
    @Override
    public void onDisconnect( SocketIOClient socketIOClient ) {
        // 参数为断开的客户端
    }
} );

// 监听一个SocketIO消息事件，把JSON反串行化为ChatObject这个Java类型
server.addEventListener( "chatevent", ChatObject.class, new DataListener&lt;ChatObject&gt;() {
    @Override
    public void onData( SocketIOClient client, ChatObject data, AckRequest ackRequest ) {
        // 广播一个事件到所有客户端
        server.getBroadcastOperations().sendEvent( "chatevent", data );
    }
} );
// 启动服务器
server.start();

Thread.sleep( Integer.MAX_VALUE );

// 停止服务器
server.stop();</pre>
<div class="blog_h3"><span class="graybg">消息确认示例</span></div>
<pre class="crayon-plain-tag">server.addEventListener( "ackevent1", ChatObject.class, new DataListener&lt;ChatObject&gt;() {
    @Override
    public void onData( final SocketIOClient client, ChatObject data, final AckRequest ackRequest ) {

        // 检查客户端是否要求消息确认，可选
        if ( ackRequest.isAckRequested() ) {
            // 发送确认消息给客户端
            ackRequest.sendAckData( "client message was delivered to server!", "yeah!" );
        }

        // 发送一个消息给客户端，要求确认
        ChatObject ackChatObjectData = new ChatObject( data.getUserName(), "message with ack data" );
        client.sendEvent( "ackevent2", new AckCallback&lt;String&gt;( String.class ) {
            @Override
            public void onSuccess( String result ) {
                // 此回调执行时，客户端已经确认，可以检查确认结果
                System.out.println( "ack from client: " + client.getSessionId() + " data: " + result );
            }
        }, ackChatObjectData );

        // 发送一个消息给客户端，要求确认，但是不关心确认后返回的消息
        ChatObject ackChatObjectData1 = new ChatObject( data.getUserName(), "message with void ack" );
        client.sendEvent( "ackevent3", new VoidAckCallback() {

            protected void onSuccess() {
                System.out.println( "void ack from: " + client.getSessionId() );
            }

        }, ackChatObjectData1 );
    }
} );</pre>
<div class="blog_h3"><span class="graybg">二进制消息</span></div>
<pre class="crayon-plain-tag">Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(9092);
// 限制WebSocket帧最大长度
config.setMaxFramePayloadLength(1024 * 1024);
// 限制HTTP报文最大长度
config.setMaxHttpContentLength(1024 * 1024);

final SocketIOServer server = new SocketIOServer(config);
// 把二进制消息转换为数组
server.addEventListener("msg", byte[].class, new DataListener&lt;byte[]&gt;() {
    @Override
    public void onData(SocketIOClient client, byte[] data, AckRequest ackRequest) {
        client.sendEvent("msg", data);
    }
});</pre>
<div class="blog_h3"><span class="graybg">使用名字空间</span></div>
<pre class="crayon-plain-tag">final SocketIOServer server = new SocketIOServer( config );
// 创建一个新的名字空间
final SocketIONamespace chat1namespace = server.addNamespace( "/chat1" );
// 在此名字空间上监听
chat1namespace.addEventListener( "message", ChatObject.class, new DataListener&lt;ChatObject&gt;() {
    @Override
    public void onData( SocketIOClient client, ChatObject data, AckRequest ackRequest ) {
    }
} );</pre>
<div class="blog_h3"><span class="graybg">SSL支持</span></div>
<pre class="crayon-plain-tag">Configuration config = new Configuration();
config.setHostname( "localhost" );
config.setPort( 10443 );

config.setKeyStorePassword( "test1234" );
InputStream stream = SslChatLauncher.class.getResourceAsStream( "/keystore.jks" );
// 设置密钥存储库
config.setKeyStore( stream );

final SocketIOServer server = new SocketIOServer( config ); </pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/socket-io-study-note">Socket.io学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/socket-io-study-note/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Node.js学习笔记</title>
		<link>https://blog.gmem.cc/node-js-study-note</link>
		<comments>https://blog.gmem.cc/node-js-study-note#comments</comments>
		<pubDate>Wed, 25 Mar 2015 09:24:04 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=14205</guid>
		<description><![CDATA[<p>简介 Node.js是一个基于Chrome V8 JavaScript引擎的脚本运行环境，它提供了一个事件驱动的、非阻塞的I/O模型，适合服务器端编程。 JavaScript语言标准的API在Node.js中均可用，此外后者提供了大量的其它API，这些API提供了服务器端编程的支持。目前版本的Node.js已经原生支持大量ES6特性。 使用Node.js可以很方便的开发Web应用的服务器端，比起其它动态语言的Web框架，Node.js的最大优势是前后端语言统一。 Node.js的包管理系统——npm，已经成为世界上最大的开源项目生态系统，其中包含了大量适用于前、后端的优秀的库。 Node.js的特点 异步I/O：网络通信、本地文件系统读写，都采用异步方式，不会导致阻塞 单线程：和浏览器中运行的JavaScript一样，Node.js中的你编写的代码也是在单线程中执行的。这意味着你不需要考虑状态共享与同步的问题。如果遇到计算密集型任务，你需要使用子进程的方式来处理，而不能让当前Node.js实例阻塞 跨平台：Node.js可以在主流的操作系统上运行 支持C/C++扩展。与Python、Perl之类的脚本语言类似，Node.js也支持Native扩展 由于Node.js提供了大量的异步API，因此它非常使用I/O密集型的应用，典型的例子是Web服务器。 模块系统 Node.js内置了CommonJS规范的实现，作为自己的模块管理系统。Node.js对CommonJS规范进行了一些扩展。 要让一个模块在运行时可用，需要进行：路径分析、文件定位、编译执行这三个步骤。Node.js把模块分为两类： 核心模块：随Node运行时一起分发的内置模块。这种模块已经内置在Node.js的二进制文件中，因此不需要路径分析和文件定位。Node.js实例启动时，部分核心模块会被直接加载 文件模块：用户编写的第三方模块。需要完整的分析、定位、编译执行步骤，速度相对慢 依据CommonJS规范，要导入一个模块给当前代码使用，需要调用[crayon-69db8ad1747f0058782294-i/] 函数。 缓存机制 就像浏览器缓存那样，Node.js对所有导入过的模块都会进行缓存，但是缓存的是编译、执行过后的对象而不是源码。不管是核心、文件模块，第二导入时，都会优先使用缓存。 模块定位 <a class="read-more" href="https://blog.gmem.cc/node-js-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/node-js-study-note">Node.js学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Node.js是一个基于Chrome V8 JavaScript引擎的脚本运行环境，它提供了一个事件驱动的、非阻塞的I/O模型，适合服务器端编程。</p>
<p>JavaScript语言标准的API在Node.js中均可用，此外后者提供了大量的其它API，这些API提供了服务器端编程的支持。目前版本的Node.js已经原生支持大量ES6特性。</p>
<p>使用Node.js可以很方便的开发Web应用的服务器端，比起其它动态语言的Web框架，Node.js的最大优势是前后端语言统一。</p>
<p>Node.js的包管理系统——npm，已经成为世界上最大的开源项目生态系统，其中包含了大量适用于前、后端的优秀的库。</p>
<div class="blog_h2"><span class="graybg">Node.js的特点</span></div>
<ol>
<li>异步I/O：网络通信、本地文件系统读写，都采用异步方式，不会导致阻塞</li>
<li>单线程：和浏览器中运行的JavaScript一样，Node.js中的你编写的代码也是在单线程中执行的。这意味着你不需要考虑状态共享与同步的问题。如果遇到计算密集型任务，你需要使用子进程的方式来处理，而不能让当前Node.js实例阻塞</li>
<li>跨平台：Node.js可以在主流的操作系统上运行</li>
<li>支持C/C++扩展。与Python、Perl之类的脚本语言类似，Node.js也支持Native扩展</li>
</ol>
<p>由于Node.js提供了大量的异步API，因此它非常使用I/O密集型的应用，典型的例子是Web服务器。</p>
<div class="blog_h1"><span class="graybg">模块系统</span></div>
<p>Node.js内置了<a href="https://blog.gmem.cc/intro-to-commonjs-spec">CommonJS</a>规范的实现，作为自己的模块管理系统。Node.js对CommonJS规范进行了一些扩展。</p>
<p>要让一个模块在运行时可用，需要进行：路径分析、文件定位、编译执行这三个步骤。Node.js把模块分为两类：</p>
<ol>
<li>核心模块：随Node运行时一起分发的内置模块。这种模块已经内置在Node.js的二进制文件中，因此不需要路径分析和文件定位。Node.js实例启动时，部分核心模块会被直接加载</li>
<li>文件模块：用户编写的第三方模块。需要完整的分析、定位、编译执行步骤，速度相对慢</li>
</ol>
<p>依据CommonJS规范，要导入一个模块给当前代码使用，需要调用<pre class="crayon-plain-tag">require()</pre> 函数。</p>
<div class="blog_h2"><span class="graybg">缓存机制</span></div>
<p>就像浏览器缓存那样，Node.js对所有导入过的模块都会进行缓存，但是缓存的是编译、执行过后的对象而不是源码。不管是核心、文件模块，第二导入时，都会优先使用缓存。</p>
<div class="blog_h2"><span class="graybg">模块定位</span></div>
<div class="blog_h3"><span class="graybg">模块标识符</span></div>
<p>模块标识符可以分为几种不同类型：</p>
<ol>
<li>核心模块标识符：无前缀，例如http 、 fs 、 path</li>
<li>基于相对路径的模块标识符：<pre class="crayon-plain-tag">.</pre>  或者<pre class="crayon-plain-tag">..</pre> 开头。相对路径会被转换为绝对路径并定位到文件</li>
<li>基于绝对路径的模块标识符：<pre class="crayon-plain-tag">/</pre> 开头</li>
<li>非路径形式的模块标识符，例如自定义的connect模块</li>
</ol>
<p>模块标识符可以附带扩展名以体现其类型，如果不包含扩展每，Node.js会依次查找.js ⇨ .json ⇨ .node扩展名。在查找过程中，需要阻塞的调用fs模块来判断文件是否存在，这是潜在的性能风险点。</p>
<div class="blog_h3"><span class="graybg">模块路径</span></div>
<p>所谓模块路径，是Node.js用来定位文件模块的路径列表，其作用类似于PATH环境变量。这些路径的列表由一系列<pre class="crayon-plain-tag">node_modules</pre> 目录构成，第一条为当前目录下的node_modules子目录，第二条为当前目录父目录下的node_modules子目录……类推。执行下面的代码可以清楚的看到：</p>
<pre class="crayon-plain-tag">console.log( module.paths );

// [ '/home/alex/JavaScript/projects/webstorm/NodeStudy/node_modules',
//  '/home/alex/JavaScript/projects/webstorm/node_modules',
//  '/home/alex/JavaScript/projects/node_modules',
//  '/home/alex/JavaScript/node_modules',
//  '/home/alex/node_modules',
//  '/home/node_modules',
//  '/node_modules' ]</pre>
<p>越是靠前的条目优先级越高，查找模块时越优先使用。如果最终找不到想require的模块，一个异常被抛出。</p>
<div class="blog_h2"><span class="graybg">模块编译</span></div>
<p>Node.js中每个模块都是一个对象，其构造函数类似于：</p>
<pre class="crayon-plain-tag">function Module( id, parent ) {
    // 模块标识符
    this.id = id;
    // 导出的API
    this.exports = {};
    // 父模块对象
    this.parent = parent;
    if ( parent &amp;&amp; parent.children ) {
        parent.children.push( this );
    }
    this.filename = null;
    this.loaded = false;
    // 子模块列表
    this.children = [];
}</pre>
<p>在定位到模块后，Node.js在内存中创建对应的模块对象，并编译、执行模块的代码。不同类型模块的执行方式不同：</p>
<ol>
<li>对于.js，通过fs模块同步读取、编译执行</li>
<li>对.node这类C/C++扩展，通过dlopen()加载已经编译好的文件</li>
<li>对于.json，通过fs模块同步读取，然后调用JSON.parse()解码为JavaScript对象 </li>
</ol>
<div class="blog_h1"><span class="graybg">ES6支持</span></div>
<p>Node.js对ES6特性的支持被分为三组：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 20%;">Shipping</td>
<td>这些特性被V8引擎看做是稳定的，并且在Node.js中自动开启</td>
</tr>
<tr>
<td>Staged</td>
<td>这些特性基本完成，但是上不稳定，需要通过运行时标记<pre class="crayon-plain-tag">--harmony</pre> 启用</td>
</tr>
<tr>
<td>In progress</td>
<td>这些特性可以被各自的harmony标记启用，不推荐在生产环境下使用</td>
</tr>
</tbody>
</table>
<p>查询<a href="http://node.green/">node.green</a>可以了解Node.js各版本对ES6的支持情况。</p>
<p>执行下面的命令，可以查看哪些特性属于In progress分组：</p>
<pre class="crayon-plain-tag">node --V8-options | grep "in progress"</pre>
<div class="blog_h2"><span class="graybg">ES6模块语法</span></div>
<p>从14版本开始，可以原生支持ES6的import语句，需要配置</p>
<pre class="crayon-plain-tag">"type": "module"</pre><br />
<pre class="crayon-plain-tag">import { sep } from 'path'
​
console.log('print: ', sep); // /</pre>
<p>低版本则需要使用Babel。 </p>
<div class="blog_h1"><span class="graybg">node命令</span></div>
<p>Node.js提供了一个命令行工具node，用于执行Node.js应用程序。该命令行的调用格式如下：</p>
<pre class="crayon-plain-tag">node [options] [V8 options] [script.js | -e "script"] [arguments]</pre>
<p>下面这段Node.js代码启动一个HTTP服务器：</p>
<pre class="crayon-plain-tag">const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;
// 以一个回调函数来注册HTTP服务器，当请求到达时，自动调用该回调处理
const server = http.createServer((req, res) =&gt; {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello Node!');
});
// 在指定的地址和端口上监听请求
server.listen(port, hostname, () =&gt; {
    console.log(`Server running at http://${hostname}:${port}/`);
});</pre>
<p>要调用这段代码，只需要：</p>
<pre class="crayon-plain-tag">node index.js</pre>
<div class="blog_h2"><span class="graybg">常用选项</span></div>
<p>下表列出node命令的常用选项：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-e, --eval</td>
<td>将参数作为JavaScript进行估算</td>
</tr>
<tr>
<td>-p, --print</td>
<td>与上面类似，并且打印估算结果</td>
</tr>
<tr>
<td>-c, --check</td>
<td>检查脚本的语法但不运行</td>
</tr>
<tr>
<td>-r, --require</td>
<td>在运行时启动时，预加载指定的模块</td>
</tr>
<tr>
<td>--no-deprecation</td>
<td>禁止废弃（deprecation）用法的警告</td>
</tr>
<tr>
<td>--trace-deprecation</td>
<td>打印废弃用法的调用栈</td>
</tr>
<tr>
<td>--throw-deprecation</td>
<td>废弃用法将导致错误抛出</td>
</tr>
<tr>
<td>--no-warnings</td>
<td>禁止警告信息</td>
</tr>
<tr>
<td>--trace-sync-io</td>
<td>在第一个事件循环之后，一旦出现同步I/O操作，即打印其调用栈</td>
</tr>
<tr>
<td>--zero-fill-buffers</td>
<td>自动以\0填充Buffer、SlowBuffer实例</td>
</tr>
<tr>
<td>--track-heap-objects</td>
<td>跟踪堆中的对象分配，用于堆快照分析</td>
</tr>
<tr>
<td>--prof-process</td>
<td>处理基于V8选项 --prof生成的剖析结果</td>
</tr>
<tr>
<td>--V8-options</td>
<td>打印可用的V8命令行选项。注意V8选项中的 _ 和 - 可以替换</td>
</tr>
<tr>
<td>--V8-pool-size</td>
<td>指定用于执行后台任务的V8线程池的大小，设置为0则自动依据当前处理器数量设置</td>
</tr>
<tr>
<td>--tls-cipher-list=list</td>
<td>指定备选的TLS密码算法</td>
</tr>
<tr>
<td>--openssl-config=file</td>
<td>在启动时加载OpenSSL配置</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">环境变量</span></div>
<p>下表列出node命令读取的环境变量：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">环境变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>NODE_DEBUG=module[,…]</td>
<td>需要打印调试信息的核心模块列表</td>
</tr>
<tr>
<td>NODE_PATH=path[:…]</td>
<td>从中寻找模块的目录前缀</td>
</tr>
<tr>
<td>NODE_DISABLE_COLORS=1</td>
<td>是否在REPL环境中使用语法高亮</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">API简介</span></div>
<p>Node.js官方提供的API，包括以下大类：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">API类别</td>
<td style="text-align: center;">简介</td>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/assert.html">Assert</a></td>
<td>该模块提供一些断言类功能，断言失败会导致AssertionError被抛出</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/buffer.html">Buffer</a></td>
<td>读写二进制数据流的容器</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html">Child Processes</a></td>
<td>创建和管理子进程</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/process.html">Process</a></td>
<td>
<p>操控进程，支持的操作包括：</p>
<ol>
<li>读取命令参数、环境变量、PID、启动时间、内存用量等信息</li>
<li>注册进程生命周期的钩子</li>
<li>停止、杀死进程</li>
</ol>
<p>注意：当前的全局对象——process，代表当前Node.js进程</p>
</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/cluster.html">Cluster</a></td>
<td>一个Node.js实例仅仅运行在单个线程中，要想利用多核系统的优势，可以使用Cluster来启动多个Node.js进程</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/console.html">Console</a></td>
<td>提供一个简单的调试控制台程序 </td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/crypto.html">Crypto</a> </td>
<td>一个密码算法库，提供对OpenSSL Hash、HMAC、加解密、签名/验证等功能的封装接口</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/dns.html">DNS</a> </td>
<td>调用操作系统进行DNS查找，或者直接联系DNS服务器进行查找</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/errors.html">Errors</a> </td>
<td>异常/错误类型层次</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/events.html">Events</a> </td>
<td>内置的观察者模式 </td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/fs.html">File System</a></td>
<td>文件系统API</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/http.html">HTTP</a></td>
<td>提供对HTTP客户端、服务器的支持</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/https.html">HTTPS</a></td>
<td>Node.js为HTTPS支持提供的单独模块</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/modules.html">Modules</a></td>
<td>内置的、基于CommonJS规范的模块加载系统</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/net.html">net</a></td>
<td>异步网络包装器，同时包括创建服务器/客户端（所谓Streams）的功能</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/os.html">OS</a></td>
<td>包含一系列操作系统相关的实用方法。例如获取体系结构、内存容量、临时目录、主机名、负载、网络接口……等信息</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/path.html">Path</a></td>
<td>处理目录、文件的路径</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/querystring.html">Querystring</a></td>
<td>解析URL查询串</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/url.html">URL</a></td>
<td>支持URL的解析</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/readline.html">Readline</a></td>
<td>支持从Readable的流中读取数据</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/repl.html">REPL</a></td>
<td>提供一个REPL（Read-Eval-Print-Loop）实现，可以嵌入到其它程序中使用</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/stream.html">Stream</a></td>
<td>提供操控流式数据的抽象接口</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/string_decoder.html">String Decoder</a></td>
<td>把Buffer解码为字符串</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/timers.html">Timers</a></td>
<td>定时器API支持</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/tls.html">TLS</a></td>
<td>基于OpenSSL构建的TLS、SSL层</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/tty.html">TTY</a></td>
<td>支持读写TTY</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/dgram.html">UDP</a></td>
<td>支持UDP数据报的收发</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/vm.html">VM</a></td>
<td>在一个V8虚拟机上下文中编译、执行JavaScript代码</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/v8.html">V8</a></td>
<td>获得V8引擎的堆统计信息，或者编程式的设置V8引擎的标记</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/zlib.html">Zlib</a></td>
<td>支持基于Gzip的压缩/解压缩</td>
</tr>
<tr>
<td><a href="https://nodejs.org/dist/latest-v6.x/docs/api/globals.html">全局对象</a></td>
<td>
<p>Node.js提供了少量在任何模块中可用的全局对象：</p>
<ol>
<li><pre class="crayon-plain-tag">Buffer</pre> 类用于处理二进制对象</li>
<li><pre class="crayon-plain-tag">__dirname</pre> 当前正在被执行的脚本所在的目录</li>
<li><pre class="crayon-plain-tag">__filename</pre> 当前正在被执行的脚本的文件名</li>
<li><pre class="crayon-plain-tag">process</pre> 当前Node.js进程对象</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">异步</span></div>
<div class="blog_h2"><span class="graybg">异步I/O</span></div>
<p>Web开发人员一直在使用异步I/O，这是因为浏览器中UI渲染、JavaScript执行互斥——它们不能同时进行，它们可能由单一线程负责完成。这意味着JavaScript执行稍微耗时（比如100ms+）就会让用户觉得卡顿。网络操作通常都比较耗时 ，因此不适合同步的完成。同步操作的劣势在同时执行多个操作的场景下更加明显，用户等待时间可能成倍的增加，而多个异步操作消耗的总时间仅仅和最耗时的那个操作消耗的时间接近。</p>
<p>尽管Ajax支持同步方式，但是基本没人使用，因为等待响应到达的过程中，同步Ajax会导致页面失去响应。</p>
<p>除了用户体验，异步I/O在资源占用方面也具有优势。它避免了不必要的线程，从而减少了上下文切换的开销。</p>
<p>从底层实现的角度来说，Node.js的异步I/O与很多主流框架无异：专门的线程负责select/poll之类的系统调用来发现I/O事件，并在事件发生时由其它线程执行回调函数。Node.js中的“其它”线程就是执行用户代码的JavaScript线程。</p>
<div class="blog_h3"><span class="graybg">事件循环</span></div>
<p>Node.js的执行模型与浏览器环境下的事件循环差不多。在启动后，一个无限循环被创建。每一次处理循环体的过程称为一个Tick。一个Tick的流程如下：</p>
<ol>
<li>取出下一个事件，如果没有更多的事件，退出本次循环</li>
<li>判断该事件是否关联了回调，如果是，执行回调</li>
<li>跳转到第1步</li>
</ol>
<p>其中回调是通过Node.js提供的API注册的，回调相当于观察者模式中的观察者。</p>
<div class="blog_h3"><span class="graybg">非I/O异步API</span></div>
<p>Node.js提供了一些与I/O编程无直接关系的API：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">API</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>setTimeout()</td>
<td rowspan="2">
<p>与浏览器中同名API语义一致。</p>
<p>调用这些API后，一个定时器对象被放入一棵红黑树中，每当Tick开始执行时，Node.js检查红黑树中已经超时的定时器，并调用其回调函数</p>
<p>需要注意的是，这些函数无法在时间精度上完全满足需求。如果某次事件循环消耗的时间过多，则回调可能被延迟执行</p>
</td>
</tr>
<tr>
<td>setInterval()</td>
</tr>
<tr>
<td>process.nextTick()</td>
<td>安排一个任务，在下一个Tick取出执行</td>
</tr>
<tr>
<td>setImmediate()</td>
<td>与nextTick()类似，但是nextTick在下一个事件循环中的执行优先级比setImmediate高</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">异步I/O与服务器</span></div>
<p>几种典型的服务器模型比较如下：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 20%;">同步式</td>
<td>同时仅处理一个请求，其它请求必须排队等候。这种模型仅仅用于测试目的</td>
</tr>
<tr>
<td>每进程/每请求</td>
<td>为每个新到达的请求创建/使用一个进程。但是进程的创建、维护开销是比较大的，因此Scalability很差</td>
</tr>
<tr>
<td>每线程/每请求</td>
<td>
<p>为每个新到达的请求创建/使用一个线程。Apache服务器仍然在使用这种模型</p>
<p>不管是每进程、每线程，都具有如下缺点：</p>
<ol>
<li>进、线程创建的开销，Linux下进程比较轻量，二者差别不大</li>
<li>线程栈的内存消耗</li>
<li>线程的上下文切换开销，随着数量的增加越发严重</li>
</ol>
</td>
</tr>
<tr>
<td>事件驱动</td>
<td>
<p>避免了进程、线程的开销，可以应对高并发场景。</p>
<p>Nginx是一个例子，它基于C编写，性能优异，除了用作Web服务器以外，还可以用于负载均衡、反向代理</p>
<p>基于Node.js的服务器也是这种类型，在Web服务器领域没有Nginx性能好，但是用途更广</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">异步编程技术</span></div>
<div class="blog_h3"><span class="graybg">观察者模式</span></div>
<p>尽管该模式可以用于同步编程，在异步编程领域其价值更大。Node.js模块events提供完整的观察者模式支持：</p>
<pre class="crayon-plain-tag">const EventEmitter = require( 'events' );
let ee = new EventEmitter();

const EVENT_GREETING = 'greeting';
// 订阅事件
ee.on( EVENT_GREETING, ( who ) =&gt; {
    console.log( `Hello ${who}!` ); // 打印 Hello Alex!
} );
// 发布事件
ee.emit( EVENT_GREETING, 'Alex' ); </pre>
<p>和Web环境下的的DOM事件系统比起来，该模块提供的API要简单的多：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>addListener/on()</td>
<td>注册一个监听器</td>
</tr>
<tr>
<td>once()</td>
<td>注册一个一次性监听器，该监听器在第一次调用后自动移除</td>
</tr>
<tr>
<td>removeListener()</td>
<td>移除一个监听器 </td>
</tr>
<tr>
<td>removeAllListeners() </td>
<td>移除所有监听器</td>
</tr>
<tr>
<td>emit()</td>
<td>发布一个事件</td>
</tr>
</tbody>
</table>
<p>events模块提供了一些额外的逻辑：</p>
<ol>
<li>如果为一个EventEmitter添加超过10个监听器，你会得到一个警告，这是因为Node.js的单线程特征。设计者认为过多的监听器可能存在潜在的内存泄漏、CPU占有。调用<pre class="crayon-plain-tag">emitter.setMaxListeners(0)</pre> 可以去除该警告</li>
<li>为了异常处理的考虑，<pre class="crayon-plain-tag">error</pre> 事件被特殊对待。如果没有针对这种事件的监听器，将会抛出异常</li>
</ol>
<div class="blog_h3"><span class="graybg">事件队列</span></div>
<p>所谓雪崩问题，是指高并发访问情况下，缓存失效导致大量请求需要进入下层处理，从而拖慢应用整体速度的情况。这种情况下，可以使用事件队列和once()来处理：</p>
<pre class="crayon-plain-tag">var proxy = new events.EventEmitter();
var status = "ready";
// callback为请求处理函数
var select = function ( callback ) {
    // 使用一次性监听器，加入队列
    proxy.once( "selected", callback );
    // 只有第一个请求才会触发下层处理 —— 数据库I/O
    if ( status === "ready" ) {
        status = "pending";
        db.select( "SQL", function ( results ) {
            // 唤醒所有队列中的请求
            proxy.emit( "selected", results );
            status = "ready";
        } );
    }
};</pre>
<div class="blog_h2"><span class="graybg">Promise/Deferred模式</span></div>
<p>观察者模式的缺点是，你必须预先把处理函数注册上去，在<span style="background-color: #c0c0c0;">emit调用之后注册的监听函数不会被调用</span>。例如下面这个Ajax调用：</p>
<pre class="crayon-plain-tag">$.get( '/api', {
    success: onSuccess,
    error: onError, 
    complete: onComplete
} ); </pre>
<p>Deferred模式则允许你<span style="background-color: #c0c0c0;">先执行异步调用</span>，随后延迟的传递处理函数。这种模式首先由Dojo引入，在jQuery 1.5中被广为人知：</p>
<pre class="crayon-plain-tag">// 处理函数被后续传入
$.get( '/api' ).success( onSuccess ).error( onError ).complete( onComplete );</pre>
<p>这种方式看起来更加舒服，还可以避免嵌套的Callback地狱。</p>
<p>与Deferred类似的<a href="/es6-study-note#promise">Promise模式</a>已经成为ES6的一部分，因此推荐使用。</p>
<div class="blog_h2"><span class="graybg">流程控制库</span></div>
<div class="blog_h3"><span class="graybg">async</span></div>
<pre class="crayon-plain-tag">async.map( [ 'file1', 'file2', 'file3' ], fs.stat, function ( err, results ) {
    // 对三个文件分别调用fs.stat方法，结果以数组形式存放到results
} );

// 类似的parallelLimit可以限制并发数量
async.parallel( [
    function ( callback ) {
    },
    function ( callback ) {
    }
], function ( err, results ) {
    // 并行执行，结果以数组形式存放到results
} );

async.series( [
    function ( callback ) {
    },
    function ( callback ) {
    }
], function ( err, results ) {
    // 串行执行，结果以数组形式存放到results
} );</pre>
<div class="blog_h1"><span class="graybg">内存</span></div>
<p>Node.js主要用于服务器端，需要面对大量并发的场景，因此高效、合理使用内存资源非常重要。</p>
<div class="blog_h2"><span class="graybg">V8内存管理</span></div>
<p>JavaScript属于自动内存管理类的编程语言，开发人员不能严格控制内存的分配和释放。内存管理工作由垃圾回收器负责，对于Node.js来说，垃圾回收器由V8引擎提供。</p>
<p>V8在<span style="background-color: #c0c0c0;">堆内存用量上有限制</span>，在64bit系统中最大上限约为1.7GB左右，32bit系统则为1GB左右，默认上限为1.0GB / 0.5GB。使用V8V8选项可以微调，但是不能超过最大上限：</p>
<pre class="crayon-plain-tag">node --max-old-space-size=1024 --max-new-space-size=1024</pre>
<p>V8采用分代垃圾回收算法，与JVM经典垃圾回收算法类似，V8将堆内存划分为新生代、年老代两部分，不过V8不支持动态调整两代的比例。</p>
<p>对于新生代对象的垃圾回收，主要采用Scavenge算法轻量，此算法效率较高但是内存浪费较大。对于年老代，则结合使用标记-清除、标记-压缩算法。 这三种算法在标记阶段都需要Stop the world以确保数据一致性。为了避免Stop the world引入过长停顿，V8使用了增量标记的方法——让标记与JavaScript逻辑交替执行，最大停顿时间可以减少为1/6。后续版本的V8对清理、压缩算法也进行了优化，都是为了缩短停顿时间。</p>
<div class="blog_h2"><span class="graybg">内存使用注意点</span></div>
<div class="blog_h3"><span class="graybg">变量作用域</span></div>
<p>JavaScript中的变量可以定义在全局作用域、函数作用域、块作用域（let/const）中。你应该尽可能缩小作用域范围，便于垃圾回收器回收。</p>
<div class="blog_h3"><span class="graybg">主动释放</span></div>
<p>全局变量会常驻内存，知道Node.js实例退出。你可以显式的<pre class="crayon-plain-tag">delete global.varname</pre> 删除之，或者重新赋值。</p>
<div class="blog_h3"><span class="graybg">闭包</span></div>
<p>闭包会导致外层作用域中的变量驻留内存，要特别小心。</p>
<div class="blog_h2"><span class="graybg">查看内存占用情况</span></div>
<p>调用<pre class="crayon-plain-tag">process.memoryUsage()</pre> 可以查看Node.js实例的内存占用情况：</p>
<pre class="crayon-plain-tag">// 单位都是字节
{
    // 驻留集尺寸（resident set size），即进程的位于物理内存中的页的总大小
    rss: 16580608,
    // 堆总大小
    heapTotal: 8425472,
    // 堆已使用大小
    heapUsed: 4030376
}</pre>
<p>调用os模块的<pre class="crayon-plain-tag">totalmem()</pre> 和<pre class="crayon-plain-tag">freemem()</pre> 则可以看到操作系统的总内存、空闲内存数量。</p>
<div class="blog_h3"><span class="graybg">堆外内存</span></div>
<p>你可能注意到rss总是大于heapTotal，这说明V8并不总是在堆中分配内存的。Buffer对象在堆外内存中分配，因而也就可以突破堆的大小限制。</p>
<div class="blog_h2"><span class="graybg">操控大内存</span></div>
<p>由于V8的内存限制，你不能调用<pre class="crayon-plain-tag">fs.readFile()</pre> 、<pre class="crayon-plain-tag">fs.writeFile()</pre> 直接读写超大文件，而应该可以使用Node.js内置的stream模块。类似于Java的输入/输出流，strem也分为可读、可写、双向流。</p>
<p>示例代码：</p>
<pre class="crayon-plain-tag">var reader = fs.createReadStream( 'in.txt' );
var writer = fs.createWriteStream( 'out.txt' );
// stream继承了EventEmitter
reader.on( 'data', function ( chunk ) {
    // 当读取到数据块后
    writer.write( chunk );
} );
reader.on( 'end', function () {
    // 当读取完毕后
    writer.end();
} );

// 对于这种读出再写入的场景，可以使用管道，更加简化
var reader = fs.createReadStream('in.txt');
var writer = fs.createWriteStream('out.txt');
reader.pipe(writer);</pre>
<div class="blog_h1"><span class="graybg">Buffer</span></div>
<p>由于JavaScript最初主要用于前端开发，很少需要操纵二进制数据，因此也缺乏对应的API。Node.js在这方面做了很大改进，提供了高性能的Buffer接口来处理图片、上传文件之类的二进制文件。</p>
<p>Buffer类似于数组，但是主要用来操作二进制数据——字节。Buffer的性能关键部分基于C++实现，它的内存在V8的堆外分配。</p>
<p>由于Buffer太常用，因此Node.js将其作为Global对象的属性，可以直接使用，不需要require。</p>
<div class="blog_h2"><span class="graybg">Buffer对象</span></div>
<p>下面的代码创建一个Buffer对象：</p>
<pre class="crayon-plain-tag">let greeting = '你好';
let buf = new Buffer( greeting ,'UTF-8');
console.log( buf );    // &lt;Buffer e4 bd a0 e5 a5 bd&gt;
let len =  buf.length; // 访问长度
buf[0];                // 下标访问元素
buf[len-1] = 0; 

// 分配Buffer时可以显式指定长度
new Buffer(size);</pre>
<p>可以看到Buffer的接口类似于数组，但是Buffer的元素是一个字节大小的数字，打印控制台的时候每个元素表示为两位十六进制数。 </p>
<div class="blog_h2"><span class="graybg">Buffer内存分配</span></div>
<p>Node.js直接向操作系统申请Buffer所需的内存。为了提高使用效率，Node.js使用Slab分配机制。Slab最初在Solairs中使用，后来被Linux系统引入。Slab代表<span style="background-color: #c0c0c0;">一块申请好的、固定大小的内存</span>，处于三种状态之一：full，完全分配；partial，部分分配；empty，没有被分配。</p>
<p>Node.js以8KB为界限，来区分Buffer是大对象还是小对象，8KB也是单个Slab的大小。这两种对象的分配方式有所不同。</p>
<div class="blog_h2"><span class="graybg">转换Buffer</span></div>
<p>Buffer可以和字符串相互转换，转换为字符串时，可以使用ASCII、UTF-8、UTF-16LE/UCS-2、Base64、Binary、HEX等编码方式。转换示例：</p>
<pre class="crayon-plain-tag">// 字符串转换为Buffer
new Buffer(str, [encoding]);
// Buffer可以后续的写入字符串
buf.write(string, [offset], [length], [encoding]);
// Buffer转换为字符串
buf.toString([encoding], [start], [end])</pre>
<p>Node.js官方不支持中国常用的GB2312、GBK编码方式，可以通过第三方库解决：</p>
<pre class="crayon-plain-tag">var iconv = require('iconv-lite');
// 转换为字符串
var str = iconv.decode(buf, 'win1251');
// 转换为Buffer
var buf = iconv.encode("Sample input string", 'win1251');</pre>
<div class="blog_h2"><span class="graybg">Buffer拼接</span></div>
<p>通常的I/O应用场景中，Buffer是逐段传输给回调函数的。下面的代码展示了如何拼接出完整的内容：</p>
<pre class="crayon-plain-tag">var fs = require('fs');
var rs = fs.createReadStream('Readme.md');
var data = '';
// chunk是一个Buffer对象
rs.on("data", function (chunk) {
    data += chunk;  // 实际上是调用Buffer.toString()完成操作
});
rs.on("end", function () {
    console.log(data);
});</pre>
<p>上面的代码把Buffer当做字符串处理，这在西方单字节编码方案里没有问题，但是遇到宽字符就会出现乱码。可以通过设置编码方式来解决：</p>
<pre class="crayon-plain-tag">var rs = fs.createReadStream('test.md', { highWaterMark: 11});
rs.setEncoding('utf-8');</pre>
<p>这样设置后，回调函数接收到的就是解码后的字符串了。但是这种设置只能支持UTF-8等几种内置的编码方式。</p>
<p>要支持任意方式编码的字符串，还是要使用iconv-lite之类的第三方库：</p>
<pre class="crayon-plain-tag">var chunks = [];
var size = 0;
res.on('data', function (chunk) {
    chunks.push(chunk);
    size += chunk.length;
});
res.on('end', function () {
    // 调用concat把Buffer的数组连接为一个完整的新的Buffer
    var buf = Buffer.concat(chunks, size);
    var str = iconv.decode(buf, 'utf-8');
    console.log(str);
});</pre>
<div class="blog_h2"><span class="graybg">Buffer的性能</span></div>
<p>Buffer在文件、网络I/O中被广泛使用，其性能很重要。我们常常需要把字符串转换为Buffer，以二进制的方式在网络上传输，在Web应用中这种转换发生的非常频繁，因此提高转换效率很重要。 </p>
<p>可以考虑把静态内容预先转换为Buffer对象，以减少反复的转换带来的CPU消耗，提高性能。</p>
<div class="blog_h3"><span class="graybg">文件读取性能</span></div>
<p>读取文件系统时，选项highWaterMark对性能有很大影响：</p>
<pre class="crayon-plain-tag">fs.createReadStream(path, {
    flags: 'r',
    encoding: null,
    fd: null,
    mode: 0666,
    start: 0, end: 10000,     // 文件读取的起止位置
    highWaterMark: 64 * 1024  // 高水位设置
});</pre>
<p>highWaterMark如果设置的过小，则可能导致系统调用次数过多。highWaterMark设置的越大，读取大文件的速度也就越大，但是设置的过大则是浪费。</p>
<div class="blog_h1"><span class="graybg">网络编程</span></div>
<p>Node.js是一个为网络而生的平台，利用它搭建网络服务非常的简单，只需要几行代码即可开启简单的网络服务。Node.js提供了net、dgram、http、https这几个模块，分别用于处理TCP、UDP、HTTP、HTTPS协议。</p>
<div class="blog_h2"><span class="graybg">TCP</span></div>
<div class="blog_h3"><span class="graybg">服务器端</span></div>
<p>一个简单的示例：</p>
<pre class="crayon-plain-tag">let net = require('net');
let server = net.createServer(
    // 当一个新的套接字连接建立后，执行的回调
    function (socket) {
        // 当对方发来数据后，执行的回调
        socket.on('data', function (data) {
            socket.write(`Hello ${data} !`);
        });
        // 当FIN时，执行的回调
        socket.on('end', function () {
            console.log('Good bye !');
        });
        // 立即向对方发送的内容
        socket.write("Who are you ?");
    }
);
server.listen(8088, function () {
    // 服务器启动后执行的回调
    console.log('Server started.');
});


// 也可以这样编写：
let server = net.createServer();
server.on('connection', function (socket) {
    // 新连接
});
server.listen(8088);</pre>
<p>监听UNIX Domain Socket也是支持的：</p>
<pre class="crayon-plain-tag">server.listen('/tmp/echo.sock');</pre>
<p>由于Socket是一个可读写的Stream对象，因此可以使用管道，实现Echo服务：</p>
<pre class="crayon-plain-tag">let server = net.createServer(function (socket) {
    // 从此Socket读到的内容，原样写回去
    socket.pipe(socket);
});</pre>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<pre class="crayon-plain-tag">let net = require('net');
let client = net.connect({host: '127.0.0.1', port: 8088}, function () {
    console.log('Connection opened.');
    // 立即向对方发送的内容
    client.write('Alex');
});
client.on('data', function (data) {
    // 接收到对方发送来的数据时
    console.log(data.toString());
    client.end();
});
client.on('end', function () {
    // FIN时
    console.log('Connection closed.');
});</pre>
<div class="blog_h3"><span class="graybg">相关事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;" colspan="2"><em>服务器事件</em></td>
</tr>
<tr>
<td>listening</td>
<td>服务器绑定端口或者UNIX Domain Socket并开始监听连接时，触发此事件。可以通过<pre class="crayon-plain-tag">server.listen(port,listeningListener)</pre> 的第二参数传入事件处理函数</td>
</tr>
<tr>
<td>connection</td>
<td>单新的客户端套接字连接到服务器时触发。可以通过<pre class="crayon-plain-tag">net.createServer()</pre> 的入参传入事件处理函数</td>
</tr>
<tr>
<td>close</td>
<td>当服务器关闭后触发。调用<pre class="crayon-plain-tag">server.close()</pre> 后服务器不再接受新连接，现有连接处理完毕后，该事件触发</td>
</tr>
<tr>
<td>error</td>
<td>当服务器遇到错误，例如尝试监听已经被占用的端口时触发。如果不侦听此事件，异常被抛出</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2">
<p><em>连接事件</em></p>
<p><em>由代表一个连接的Socket对象发布</em></p>
</td>
</tr>
<tr>
<td>data</td>
<td>
<p>当一端调用<pre class="crayon-plain-tag">write()</pre> 发送数据时，另外一端会触发该事件，事件的参数就是write的数据</p>
<p>注意：TCP针对网络中的小报文就有优化算法Nagle，仅当缓冲区数据到达一定量、或者超过某个时间阈值，才会发送TCP报文。该算法默认启用以提高性能，要关闭可以：<pre class="crayon-plain-tag">用socket.setNoDelay(true)</pre> </p>
</td>
</tr>
<tr>
<td>end</td>
<td>连接中的任意一端发送FIN报文时，触发该事件</td>
</tr>
<tr>
<td>connect</td>
<td>仅用于客户端，当成功连接到服务器时触发该事件</td>
</tr>
<tr>
<td>error</td>
<td>当发生错误时，触发该事件</td>
</tr>
<tr>
<td>close</td>
<td>当套接字完全关闭时，触发该事件</td>
</tr>
<tr>
<td>timeout</td>
<td>当连接不活动超过一定时间后触发该事件，通知连接被闲置</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">UDP</span></div>
<div class="blog_h3"><span class="graybg">服务器端</span></div>
<pre class="crayon-plain-tag">var dgram = require( "dgram" );
var server = dgram.createSocket( "udp4" );
// 接收到客户端消息时触发
server.on( "message", function ( msg, rinfo ) {
    console.log( "Server got: " + msg + " from " + rinfo.address + ":" + rinfo.port );
} );
// 开始监听时触发
server.on( "listening", function () {
    var address = server.address();
    console.log( "Server listening " + address.address + ":" + address.port );
} );
server.bind( 8088 ); </pre>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<pre class="crayon-plain-tag">var dgram = require( 'dgram' );
var message = new Buffer( "Hello Node!" );
var client = dgram.createSocket( "udp4" );
client.send( message, 0, message.length, 8088, "127.0.0.1", function ( err, bytes ) {
    client.close();
} );</pre>
<div class="blog_h3"><span class="graybg">相关事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>message</td>
<td>接收到UDP数据报后触发该事件，第一个参数是Buffer，第二个参数是发送者的信息</td>
</tr>
<tr>
<td>listening</td>
<td>套接字开始监听时触发该事件</td>
</tr>
<tr>
<td>close</td>
<td>调用close()方法时触发该事件</td>
</tr>
<tr>
<td>error</td>
<td>发生错误的时触发该事件，如果不监听该事件，异常被抛出</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">HTTP</span></div>
<p>Node.js提供的http模块，继承自net模块，由于支持HTTP协议的处理。http将对Stream的读写操作封装到ImcomeMessage、ServerResponse对象中，这两个对象分别代表请求、应答。</p>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<pre class="crayon-plain-tag">let http = require( 'http' );
let srv = http.createServer( function ( req, resp ) { // req为IncomeMessage，resp为ServerResponse
    console.log( req.method );   // 请求方法
    console.log( req.url );      // 请求的URL，相对于服务器。访问http://localhost:8088/时值为 /
    console.log( req.headers );  // 请求头
    // 写入响应头
    resp.writeHead( 200, { 'Content-Type': 'text/plain' } );
    // end与write的区别是，后者在写入数据的同时，还结束本次请求处理
    resp.write( 'Hello ' )
    resp.end( 'Node!' );
} );
srv.listen( 8088, '127.0.0.1' );
srv.on( 'connection', function ( socket ) {
    console.log(socket.remoteAddress); // 127.0.0.1
} );</pre>
<p>注意：</p>
<ol>
<li>响应头的设置必须在发送响应体之前进行</li>
<li>确保调用end()来结束请求处理，否则客户端一直处于等待</li>
<li>end()之后，底层连接可能被关闭，也可能用于处理下一个请求</li>
</ol>
<div class="blog_h3"><span class="graybg">服务器事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>connection</td>
<td>当底层TCP连接建立时触发该事件。注意连接可能开启了keep-alive，因而可能被多个请求使用</td>
</tr>
<tr>
<td>request</td>
<td>http模块从底层数据流中抽取出请求、响应。解析完请求头后，触发该事件。调用resp.end()后，当前连接可以用于下一个请求</td>
</tr>
<tr>
<td>close</td>
<td>调用server.close()后，新连接不再被接收，现有请求处理完毕后触发该事件</td>
</tr>
<tr>
<td>checkContinue</td>
<td>
<p>客户端上传大块数据时，不会直接发送。客户端会先发送一个带有Expect: 100-continue头的请求到服务器，此时该事件被触发。如果不监听该事件，服务器自动发送100 Continue状态码，客户端开始上传数据。你可以响应400 Bad Request拒绝上传</p>
<p>同一个请求中，request与该事件不会同时触发。客户端真正上传数据时，发送的是另外一个请求</p>
</td>
</tr>
<tr>
<td>connect</td>
<td>客户端发起连接请求时触发</td>
</tr>
<tr>
<td>upgrade</td>
<td>
<p>客户端需要升级所使用的协议时，需要与服务器协商。此时客户端发送带有Upgrade头的请求，服务器在接收到这种请求后触发该事件。与WebSocket有关</p>
<p>如果不监听该事件，发起请求的连接被关闭</p>
</td>
</tr>
<tr>
<td>clientError</td>
<td>连接的客户端触发error事件时，该错误会传播到服务器，并触发该事件</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<pre class="crayon-plain-tag">var http = require( "http" );
var options = {
    // 服务器主机和端口
    hostname: '127.0.0.1',
    port: 8088,
    // 请求的URL
    path: '/',
    // 请求方法
    method: 'GET',
    // 使用哪个本地网络接口
    localAddress: '127.0.0.1',
    // 使用UNIX Domain Socket时指定：
    socketPath: undefined,
    // 指定请求头
    headers: undefined,
    // HTTP基本认证，自动作为Authorization请求头
    auth: undefined
};
// 创建一个ClientRequest对象
var req = http.request( options, function ( resp ) { // resp为IncomeMessage
    // 响应状态码
    console.log( resp.statusCode );
    // 响应头
    console.log( JSON.stringify( resp.headers ) );
    resp.setEncoding( 'utf8' );
    // 接收到数据后执行的回调
    resp.on( 'data', function ( chunk ) {
        console.log( chunk );
    } );
} );
req.end();</pre>
<p>客户端默认使用<pre class="crayon-plain-tag">http.globalAgent</pre> 作为代理对象，此代理为每个服务器维护最多5个TCP连接池。池中连接可以用于重复的请求发送。你可以自己指定连接池大小：</p>
<pre class="crayon-plain-tag">var agent = new http.Agent( {
    maxSockets: 10
} );
var options = {
    method: 'GET',
    agent: agent
};</pre>
<p>Agent对象的sockets、requests分别表示连接池中的连接数量、等待处理的请求数量。 </p>
<p>可以直接设置<pre class="crayon-plain-tag">agent:fasle</pre> ，这样，客户端的<span style="background-color: #c0c0c0;">并发连接数不受限制</span>。</p>
<div class="blog_h3"><span class="graybg">客户端事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>response</td>
<td>对应服务器端的request事件。当服务器响应到达时，触发该事件</td>
</tr>
<tr>
<td>socket</td>
<td>当连接池中的TCP连接分配给当前请求时，触发该事件</td>
</tr>
<tr>
<td>connect</td>
<td>客户端发起CONNECT请求时，如果服务器响应了200状态码，触发该事件</td>
</tr>
<tr>
<td>upgrade</td>
<td>客户端发起Upgrade请求时，如果服务器响应了101 Switching Protocols状态码，触发该事件</td>
</tr>
<tr>
<td>continue</td>
<td>客户的发起Expect: 100-continue请求时，如果服务器响应了100 Continue，触发该事件</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">WebSocket</span></div>
<p><a href="/html5-study-note#websockets">WebSocket</a>是HTML5的重要特性，Node.js可以完美支持WebSocket。两者的编程模型几乎一致，而Node.js的事件驱动机制特别适合高并发的长连接。</p>
<p>WebSocket不是堆HTTP协议的再次封装，它是独立的协议。它主要由两部分组成：握手、数据传输。</p>
<div class="blog_h3"><span class="graybg">WebSocket握手</span></div>
<p>握手是基于HTTP协议进行的，客户端发出具有特殊头的请求：</p>
<pre class="crayon-plain-tag"># 下面两个头表示将连接升级为WebSocket协议
Upgrade: websocket
Connection: Upgrade
# 这个Key用于安全性校验
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
# 子协议和版本号
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13</pre>
<p>服务器收到上述请求后，如果同意切换为WebSocket协议，则作出如下应答：</p>
<pre class="crayon-plain-tag"># 101响应，表示同意协议切换
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
# 用于安全性校验
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# 使用的子协议
Sec-WebSocket-Protocol: chat</pre>
<div class="blog_h3"><span class="graybg">WebSocket数据传输</span></div>
<p>一旦握手成功，服务器 - 客户端将成为对等实体，双方都可以主动发送数据。发送的数据变为WebSocket数据帧，而不再是HTTP报文。</p>
<p>Node.js没有内置WebSocket库，可以使用社区提供的ws库。框架Socket.io就是基于ws库实现的。</p>
<div class="blog_h2"><span class="graybg">网络安全支持</span></div>
<p>为了应对HTTP、FTP等协议明文传输导致的安全性问题，网景公司引入了SSL（安全套接字层，Secure Sockets Layer）协议。SSL在传输层上方完成加密、解密工作，对于应用层是透明的。后来IETF将SSL标准化，成为TLS（Transport Layer Security，传输层安全）协议。</p>
<p>Node.js在网络安全方面提供了三个模块：crypto 、 tls 、 https。其中crypto用于加解密，SHA1、MD5等经典算法都包含在其中；tls模块类似于net，可以建立基于TLS/SSL加密的TCP协议；https模块则提供https协议的支持。</p>
<div class="blog_h2"><span class="graybg">TLS/SSL</span></div>
<div class="blog_h3"><span class="graybg">密钥和证书</span></div>
<p>Node.js基于OpenSSL实现TLS/SSL，你可以利用openssl命令来生成通信需要的密钥对：</p>
<pre class="crayon-plain-tag">// 生成服务器密钥对
openssl genrsa -out server.key 1024
// 客户端密钥对类似
openssl genrsa -out client.key 1024</pre>
<p>为了防止中间人攻击，公钥需要被CA签名，形成证书。我们可以使用自签名证书，即自己扮演CA角色：</p>
<pre class="crayon-plain-tag"># 生成CA密钥对
openssl genrsa -out ca.key 1024
# 生成CA根证书的请求，需要填写一系列表明身份的字段，Common Name必须和服务器域名匹配，否则无法正常使用
openssl req -new -key ca.key -out ca.csr
# 使用CA私钥对CA公钥进行自签名，得到CA根证书
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt


# 生成服务器证书请求
openssl req -new -key server.key -out server.csr
# 生成服务器证书
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt

# 客户端证书类似
openssl req -new -key client.key -out client.csr
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt</pre>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<pre class="crayon-plain-tag">var tls = require( 'tls' );
var fs = require( 'fs' );
var options = {
    // 服务器私钥、服务器证书
    key: fs.readFileSync( './keys/server.key' ),
    cert: fs.readFileSync( './keys/server.crt' ),
    // 是否要求客户端提供证书，注意客户端总是要求服务器提供证书
    requestCert: true,
    // CA的根证书，用于验证客户端证书的合法性
    ca: [ fs.readFileSync( './keys/ca.crt' ) ]
};
var server = tls.createServer( options, function ( stream ) {
    // 如果客户端证书被CA签名，则stream.authorized为true
    console.log( 'Client connected ', stream.authorized ? 'authorized' : 'unauthorized' );
    stream.write( "Welcome!" );
    stream.setEncoding( 'utf-8' );
    stream.pipe( stream );
} );
server.listen( 8088, function () {
    console.log( 'Server bound' );
} );</pre>
<p>可以使用下面的命令验证上述服务器能否正常工作：</p>
<pre class="crayon-plain-tag">openssl s_client -connect 127.0.0.1:8088</pre>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<pre class="crayon-plain-tag">var tls = require( 'tls' );
var fs = require( 'fs' );
var options = {
    key: fs.readFileSync( './keys/client.key' ),
    cert: fs.readFileSync( './keys/client.crt' ),
    ca: [ fs.readFileSync( './keys/ca.crt' ) ]
};
var stream = tls.connect( 8088, options, function () {
    process.stdin.pipe( stream );
} );
stream.setEncoding( 'utf8' );
stream.on( 'data', function ( data ) {
    console.log( data );
} );
stream.on( 'end', function () {
    stream.close();
} );</pre>
<div class="blog_h2"><span class="graybg">HTTPS</span></div>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<pre class="crayon-plain-tag">var https = require( 'https' );
var fs = require( 'fs' );
var options = {
    key: fs.readFileSync( './keys/server.key' ),
    cert: fs.readFileSync( './keys/server.crt' )
};
https.createServer( options, function ( req, res ) {
    res.writeHead( 200 );
    res.end( "Hello Node!" );
} ).listen( 8088 );</pre>
<div class="blog_h3"><span class="graybg">客户端</span> </div>
<pre class="crayon-plain-tag">var https = require( 'https' );
var fs = require( 'fs' );
var options = {
    hostname: '127.0.0.1',
    port: 8088,
    path: '/',
    method: 'GET',
    key: fs.readFileSync( './keys/client.key' ),
    cert: fs.readFileSync( './keys/client.crt' ),
    ca: [ fs.readFileSync( './keys/ca.crt' ) ]
};
options.agent = new https.Agent( options );
var req = https.request( options, function ( res ) {
    res.setEncoding( 'utf-8' );
    res.on( 'data', function ( d ) {
        console.log( d );
    } );
} );
req.end();
req.on( 'error', function ( e ) {
    console.log( e );
} );</pre>
<div class="blog_h1"><span class="graybg">进程管理</span></div>
<p>Node.js是在单个线程中执行JavaScript的，要想利用多核CPU的优势，必须创建额外的进程。</p>
<p>Node.js中未捕获的异常会导致进程直接退出，这影响了其健壮性，应该使用额外的进程防止应用程序崩溃。</p>
<div class="blog_h2"><span class="graybg">多进程</span></div>
<p>利用child_process模块，利用创建子进程，fork()函数可以复制当前进程：</p>
<pre class="crayon-plain-tag">#!/usr/bin/env node

var http = require( 'http' );
http.createServer( function ( req, res ) {
    res.writeHead( 200, { 'Content-Type': 'text/plain' } );
    res.end( 'Hello World\n' );
} ).listen( Math.round( (1 + Math.random()) * 10000 ), '127.0.0.1' );</pre><br />
<pre class="crayon-plain-tag">var fork = require('child_process').fork;
var cpus = require('os').cpus();
for (var i = 0; i &lt; cpus.length; i++) {
    fork('./worker.js'); // 复制当前进程，然后执行指定的脚本
}</pre>
<p>执行<pre class="crayon-plain-tag">node master.js</pre> 后，会创建额外的子进程，其数量等于CPU核心数。</p>
<p>这种模式被称为主从模式，主进程不负责具体的业务逻辑，仅仅完成工作进程的调度和管理，它的功能单一，容易达到稳定状态，一般不会崩溃。从进程负责各种业务的处理，可能因为意外崩溃，但是不会导致整个应用程序宕机。</p>
<p>除了增强健壮性之外，主进程还可以根据需要增加子进程的数量，因而具有可伸缩性。</p>
<div class="blog_h3"><span class="graybg">创建子进程</span></div>
<p>child_process提供多种创建子进程的方法。它们均<span style="background-color: #c0c0c0;">返回子进程对象</span>：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>spawn()</td>
<td>
<p>启动一个子进程来执行命令</p>
<p>与后两者不同的是：exec、execFile可以指定timeout，超时后子进程被杀死</p>
</td>
</tr>
<tr>
<td>exec()</td>
<td>启动一个子进程来执行命令，可以通过回调函数获得子进程的状态</td>
</tr>
<tr>
<td>execFile()</td>
<td>启动一个子进程来执行可执行文件</td>
</tr>
<tr>
<td>fork()</td>
<td>类似于spawn()，但是执行的目标是JavaScript模块</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">进程事件</span></div>
<p>父进程可以在子进程对象上监听以下事件：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>message</td>
<td>进程间通信时发布的事件</td>
</tr>
<tr>
<td>error</td>
<td>如果子进程无法被创建、杀死、发送消息时触发该事件</td>
</tr>
<tr>
<td>exit</td>
<td>
<p>子进程退出时触发该事件。监听器接受两个参数：</p>
<ol>
<li>退出码，如果进程是被kill()的，该参数为null</li>
<li>kill()时第信号代码</li>
</ol>
</td>
</tr>
<tr>
<td>close</td>
<td>子进程的标准输入/输出流关闭时触发，参数与exit同</td>
</tr>
<tr>
<td>disconnect</td>
<td>在父进程或者子进程中调用disconnect()时触发，导致IPC通道关闭</td>
</tr>
</tbody>
</table>
<p>父进程调用子进程的<pre class="crayon-plain-tag">kill([signal])</pre> 方法时，子进程将收到以POSIX信号为名的事件：</p>
<pre class="crayon-plain-tag">// 父进程不指定具体信号时，默认发送SIGTERM信号，子进程的正常行为应该是退出
process.on( 'SIGTERM', function () {
    console.log( 'Got a SIGTERM, exiting...' );
    process.exit( 1 );
} );</pre>
<div class="blog_h3"><span class="graybg">进程间通信</span></div>
<p>Node.js中父子进程之间的通信方式，类似于HTML5中Web Worker与JavaScript主线程之间的通信方式：</p>
<pre class="crayon-plain-tag">var cp = require( 'child_process' );
var cp = cp.fork( __dirname + '/worker.js' );
// 在子进程上注册监听器，以获得子进程发来的消息
cp.on( 'message', function ( m ) {
    console.log( 'Message from child: ', m );
} );
cp.send( { hello: 'world' } );</pre><br />
<pre class="crayon-plain-tag">// 在当前进程对象上注册监听器，以获得父进程发来的消息
process.on( 'message', function ( m ) {
    console.log( 'Message from parent:', m );
} );
process.send( { foo: 'bar' } );</pre>
<div class="blog_h3"><span class="graybg">共享套接字</span></div>
<p>上面的例子中我们让多个子进程分别监听不同的端口，这不符合Web服务器应用场景。我们需要的是，多个子进程都能处理同一端口监听到的新请求。</p>
<p>解决此问题的一种方式是，添加一个前端代理，把监听到的请求转发给某一个子进程处理。</p>
<p>Node.js从v0.5.9版本开始，支持通过进程间通信发送句柄（Handle）。句柄是执行某种资源的引用，这些资源包括：Socket套接字、管道等。考虑下面的代码：</p>
<pre class="crayon-plain-tag">var child = require( 'child_process' ).fork( 'child.js' );
var server = require( 'net' ).createServer();
server.on( 'connection', function ( socket ) {
    socket.end( 'Handled by parent\n' );
} );
server.listen( 8088, function () {
    // 向子进程发送句柄
    child.send( 'server', server );
} );</pre><br />
<pre class="crayon-plain-tag">process.on( 'message', function ( type, server ) {
    if ( type === 'server' ) {
        server.on( 'connection', function ( socket ) {
            socket.end( 'Handled by child' );
        } );
    }
} );</pre>
<p>启动服务后，多次发起连接，可以看到，请求随机的被父、子进程处理。由于主进程不负责业务处理，我们可以在发送句柄后，关闭主进程中的套接字：</p>
<pre class="crayon-plain-tag">server.listen( 8088, function () {
    // 向子进程发送句柄
    child1.send( 'server', server );
    child2.send( 'server', server );
    // 关闭主进程中的套接字
    server.close();
} );</pre>
<p>为何套接字可以在多个进程中共享，甚至在父进程中关闭套接字后，子进程居然不受影响呢？</p>
<p>实际上，Node.js在底层使用了prefork技术。当你调用send()发送句柄时，Node.js自动通过IPC通道发送服务器套接字的文件描述符。此描述符只是一个整数值，但是不能直接传送，必须基于特殊的IPC通道（Linux下是UNIX domain sockets）才能有效的传递给其它进程。</p>
<p>只要得到代表服务器套接字的文件描述符，任何进程都可以针对它调用<pre class="crayon-plain-tag">accept()</pre> 来等待连接请求。至于连接到底发送给哪个进程处理，取决于操作系统的调度，每个进程都有抢占文件描述符的机会。</p>
<div class="blog_h3"><span class="graybg">自动重启</span></div>
<p>我们利用进程相关的事件机制，可以实现：</p>
<ol>
<li>worker退出后，由master自动重新创建新的worker代替它</li>
<li>worker遇到未捕获异常时，自动退出，因为数据状态可能已经被破坏</li>
</ol>
<p>实现这两点，可以提高健壮性。示例代码：</p>
<pre class="crayon-plain-tag">var fork = require( 'child_process' ).fork;
var cpus = require( 'os' ).cpus();
var server = require( 'net' ).createServer();
server.listen( 8088 );
// 持有子进程的集合
var workers = {};
var createWorker = function () {
    var worker = fork( __dirname + '/worker.js' );
    // 监听子进程的退出事件
    worker.on( 'exit', function () {
        // 重新创建一个子进程
        delete workers[ worker.pid ];
        createWorker();
    } );
    // 发送句柄
    worker.send( 'server', server );
    workers[ worker.pid ] = worker;
};
for ( var i = 0; i &lt; cpus.length; i++ ) {
    createWorker();
} </pre><br />
<pre class="crayon-plain-tag">var http = require( 'http' );
var server = http.createServer( function ( req, res ) {
    /* Web服务器逻辑 */
} );
// 捕获未被处理的异常
process.on( 'uncaughtException', function () {
    // 执行资源清理
    // 如果资源清理比较耗时，可以提前通知master当前进程即将自杀，让父进程尽快创建新的worker
    // 然后，自杀
    process.exit( 1 );
} );</pre>
<div class="blog_h3"><span class="graybg">负载均衡</span></div>
<p>使用操作系统默认调度机制，让多个worker去抢占请求，具有缺陷。因为抢占是和CPU相关的，缺乏计算压力的worker会更容易抢占到请求，但是这种worker在I/O方便压力可能反而很大。这导致了负载不均衡。</p>
<p>Node引入了一种新的负载均衡机制——循环调度（Round-Robin）。其工作方式是，让master接受连接，然后依次分发给子进程。</p>
<p>在cluster模块中，启用此负载均衡机制的方法是：</p>
<pre class="crayon-plain-tag">// 启用Round-Robin
cluster.schedulingPolicy = cluster.SCHED_RR;
// 不启用Round-Robin
cluster.schedulingPolicy = cluster.SCHED_NONE;</pre>
<p> 你也可以设置环境变量：</p>
<pre class="crayon-plain-tag">export NODE_CLUSTER_SCHED_POLICY=rr
export NODE_CLUSTER_SCHED_POLICY=none</pre>
<p>RR策略也可以通过前置的代理服务器实现，缺点是消耗两倍的文件描述符。操作系统支持的最大打开文件总数量、进程打开文件总数量是有限制的。</p>
<div class="blog_h3"><span class="graybg">状态共享</span></div>
<p>Node.js中不适合存放太多数据，因为V8引擎本身不是为服务器场景设计的，它不适合管理太大内存。</p>
<p>我们可以使用第三方数据存储方案，例如RDBMS、文件系统、缓存服务（Redis等）。</p>
<div class="blog_h2"><span class="graybg">Cluster模块</span></div>
<p>Node.js从v.08版本开始引入Cluster模块，内置了master/worker模式的支持。你不再需要通过child_process自己实现集群了。</p>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var cluster = require( 'cluster' );
var http = require( 'http' );
var numCPUs = require( 'os' ).cpus().length;
if ( cluster.isMaster ) {
    // master进程的逻辑
    for ( var i = 0; i &lt; numCPUs; i++ ) {
        cluster.fork();
    }
    cluster.on( 'exit', function ( worker, code, signal ) {
        // worker进程退出时的回调
    } );
}
else {
    // 子进程可以共享任何TCP监听
    http.createServer( function ( req, res ) {
        
    } ).listen( 8088 );
}</pre>
<p>判断一个进程是master还是worker，主要依赖于环境变量：</p>
<pre class="crayon-plain-tag">// 判断当前进程
cluster.isWorker = ('NODE_UNIQUE_ID' in process.env);
cluster.isMaster = (cluster.isWorker === false);</pre>
<p>可以使用<pre class="crayon-plain-tag">cluster.setupMaster()</pre>  调用，将master/worker的代码分离：</p>
<pre class="crayon-plain-tag">var cluster = require( 'cluster' );
cluster.setupMaster( {
    exec: "worker.js" // worker的代码分离出去
} );
var cpus = require( 'os' ).cpus();
for ( var i = 0; i &lt; cpus.length; i++ ) {
    cluster.fork();
}</pre>
<div class="blog_h3"><span class="graybg">cluster事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>fork</td>
<td>复制一个worker进程后触发</td>
</tr>
<tr>
<td>online</td>
<td>复制好worker后，worker主动发一条online消息给master，master接收到该消息后触发online事件</td>
</tr>
<tr>
<td>listening</td>
<td>worker中调用listen()对共享的服务器套接字进行监听后，发送一条listening消息给master，master接收到该消息后触发listening事件</td>
</tr>
<tr>
<td>disconnect</td>
<td>master - worker之间的IPC通道断开后触发</td>
</tr>
<tr>
<td>exit</td>
<td>worker退出后触发</td>
</tr>
<tr>
<td>setup</td>
<td>调用cluster.setupMaster()后触发该事件</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">测试</span></div>
<div class="blog_h2"><span class="graybg">断言</span></div>
<p>Node.js提供了一个assert模块，可以进行基本的断言测试，如果测试失败会导致异常抛出。示例代码：</p>
<pre class="crayon-plain-tag">var assert = require( 'assert' );
assert.equal( Math.max( 1, 100 ), 100 );</pre>
<p>常用的断言方法包括：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ok()</td>
<td>断言结果为真</td>
</tr>
<tr>
<td>equal()</td>
<td>断言实际指等于期望值</td>
</tr>
<tr>
<td>notEqual()</td>
<td>断言实际指不等于期望值</td>
</tr>
<tr>
<td>deepEqual()</td>
<td rowspan="2">深度相等性比较，会比较对象的属性、数组的元素</td>
</tr>
<tr>
<td>notDeepEqual()</td>
</tr>
<tr>
<td>strictEqual()</td>
<td rowspan="2">使用 === 而不是 == 比较</td>
</tr>
<tr>
<td>notStrictEqual()</td>
</tr>
<tr>
<td>throws()</td>
<td>断言代码块抛出异常</td>
</tr>
<tr>
<td>doesNotThrow()</td>
<td>断言代码块不抛出异常</td>
</tr>
<tr>
<td>ifError()</td>
<td>断言结果为假—— null 、 undefined 、 0 、 '' 、 false</td>
</tr>
</tbody>
</table>
<p>第三方的断言框架包括：<a href="https://shouldjs.github.io/">should.js</a>。</p>
<div class="blog_h2"><span class="graybg">mocha</span></div>
<p>参考：<a href="/mocha-study-note">mocha学习笔记</a></p>
<div class="blog_h1"><span class="graybg">最佳实践</span></div>
<div class="blog_h2"><span class="graybg">性能优化</span></div>
<div class="blog_h3"><span class="graybg">静态文件处理</span></div>
<p>Node.js在处理静态文件方面没有优势，可以把图片、样式表、多媒体文件都交由静态文件服务器处理，Node.js仅负责处理动态请求。可以利用Nginx来搭建静态文件服务器，或者使用CDN。</p>
<p>如果Node.js需要反复发送某些静态的部分，可以将其存放在Buffer中，不要重复字符串与Buffer之间的转换。</p>
<div class="blog_h3"><span class="graybg">使用缓存</span></div>
<p>考虑使用Redis、Memcached等缓存框架，避免不必要的底层数据访问。</p>
<div class="blog_h3"><span class="graybg">多进程架构</span></div>
<p>利用官方的cluster模块，或者第三方的pm、forever、pm2等模块，可以利用多核CPU的优势、增强应用健壮性。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/node-js-study-note">Node.js学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/node-js-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>npm学习笔记</title>
		<link>https://blog.gmem.cc/npm-study-note</link>
		<comments>https://blog.gmem.cc/npm-study-note#comments</comments>
		<pubDate>Fri, 05 Dec 2014 09:52:31 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=13864</guid>
		<description><![CDATA[<p>基础知识 简介 npm是一个JavaScript软件包的管理工具，让代码共享、重用更加简单。  术语列表 术语 说明 package 所谓包，就是一个文件或者目录，它由一个名为[crayon-69db8ad175ccf454745304-i/] 的元数据文件来描述。包和模块有交集但互不包含 module 是指任何可以通过Node.js的[crayon-69db8ad175cd3613385838-i/] 来加载的文件或者目录，例如： 一个包含了package.json的目录，package.json中包含main字段 一个包含了index.js的目录 一个JavaScript文件 大部分的npm包都属于模块，因为一般情况下，npm包都是供Node.js程序通过require()来加载的 几乎所有的npm包，都包含多个模块，这是因为其中JS文件可以通过require()来加载 在Node.js程序中，模块还可能指通过require()调用得到的变量： [crayon-69db8ad175cd6400017390/] 之所以node_modules目录如此命名，是因为它是用来寻找模块（而不是包） 的，对于文件node_modules/request.js，可以通过上面的require()代码加载 入门 安装Node.js和npm 可以到Node.js官网下载Node.js运行时，完毕后解压到某个目录，设置好环境变量。然后，执行： <a class="read-more" href="https://blog.gmem.cc/npm-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/npm-study-note">npm学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>npm是一个JavaScript软件包的管理工具，让代码共享、重用更加简单。 </p>
<div class="blog_h2"><span class="graybg">术语列表</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>package</td>
<td>所谓包，就是<span style="background-color: #c0c0c0;">一个文件或者目录</span>，它由一个名为<pre class="crayon-plain-tag">package.json</pre> 的元数据文件来描述。包和模块有交集但互不包含</td>
</tr>
<tr>
<td>module</td>
<td>
<p>是指任何可以通过Node.js的<pre class="crayon-plain-tag">require()</pre> 来加载的文件或者目录，例如：</p>
<ol>
<li>一个包含了package.json的目录，package.json中包含main字段</li>
<li>一个包含了index.js的目录</li>
<li>一个JavaScript文件</li>
</ol>
<p><span style="background-color: #c0c0c0;">大部分的npm包都属于模块</span>，因为一般情况下，npm包都是供Node.js程序通过require()来加载的</p>
<p><span style="background-color: #c0c0c0;">几乎所有的npm包，都包含多个模块</span>，这是因为其中JS文件可以通过require()来加载</p>
<p>在Node.js程序中，模块还可能指通过require()调用得到的变量：</p>
<pre class="crayon-plain-tag">// req是模块
var req = require('request')</pre>
<p>之所以node_modules目录如此命名，是因为它是用来寻找模块（而不是包） 的，对于文件node_modules/request.js，可以通过上面的require()代码加载</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">入门</span></div>
<div class="blog_h2"><span class="graybg">安装Node.js和npm</span></div>
<p>可以到Node.js官网下载Node.js运行时，完毕后解压到某个目录，设置好环境变量。然后，执行：</p>
<pre class="crayon-plain-tag">npm install npm@latest -g</pre>
<p>安装最新版本npm。</p>
<div class="blog_h2"><span class="graybg">解决权限问题</span></div>
<p>尝试安装全局模块时，可能会遇到<pre class="crayon-plain-tag">EACCES</pre> 错误，这意味着你没有权限对npm用来存放全局模块/命令的目录进行写操作。此时你可以设置默认全局模块目录的权限，或者转移全局模块存放目录。</p>
<div class="blog_h3"><span class="graybg">修改权限</span></div>
<p>执行下面的命令获取npm安装位置的前缀：<pre class="crayon-plain-tag">npm config get prefix</pre> ，如果输出的位置是/user/local，你可以选择修改文件权限：</p>
<pre class="crayon-plain-tag"># 获取几个目录的所有权
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}</pre>
<div class="blog_h3"><span class="graybg">转移目录</span></div>
<p>如果你不希望改变文件所有权，可以转移npm全局模块安装目录：</p>
<pre class="crayon-plain-tag"># 创建一个作为npm全局目录
mkdir ~/.npm-global
# 设置npm前缀
npm config set prefix '~/.npm-global'</pre>
<p>然后，修改你的环境变量，添加PATH：<pre class="crayon-plain-tag">export PATH=~/.npm-global/bin:$PATH</pre>  </p>
<p>现在你可以通过npm安装软件包，不需要sudo前缀了。</p>
<div class="blog_h2"><span class="graybg">安装模块</span></div>
<p>安装npm包有两种方式：全局安装、本地安传。根据需要，选择其中的一种：</p>
<ol>
<li>如果你希望在自己正在开发的工程中依赖（例如通过Node.js的require）一个包，使用本地安装，本地安装是npm的默认行为</li>
<li>如果你希望将npm作为<span style="background-color: #c0c0c0;">某种命令行工具</span>，例如cordova，则应该进行全局安装</li>
</ol>
<div class="blog_h3"><span class="graybg">本地安装</span></div>
<p>执行下面的命令进行本地安装：</p>
<pre class="crayon-plain-tag">npm install package_name</pre>
<p>上述命令会在当前目录下，创建一个<pre class="crayon-plain-tag">node_modules</pre>  子目录，然后下载软件包到此目录中。</p>
<p>被安装的软件包的版本，按如下规则确定：</p>
<ol>
<li>如果本地目录中存在package.json，则根据其中声明的semver规则来确定安装的版本，例如：<br />
<pre class="crayon-plain-tag">{
  "name": "package_name",
  "version": "1.0.0"  // 该字段支持semver规范
} </pre>
</li>
<li>如果没有package.json文件，则安装最新版本</li>
</ol>
<p>一旦安装，你就可以在工程中使用这些软件包了：</p>
<pre class="crayon-plain-tag">var pkg = require('package_name');</pre>
<div class="blog_h3"><span class="graybg">全局安装</span></div>
<p>添加-g选项，则自动执行全局安装：</p>
<pre class="crayon-plain-tag"># 安装
npm install -g package_name
# 更新
npm update -g package_name
# 更新所有全局包
npm update -g
# 查找过期的软件包
npm outdated -g --depth=0
# 移除
npm uninstall -g package_name</pre>
<div class="blog_h2"><span class="graybg">使用package.json</span></div>
<p>要为你自己编写的包初始化包描述符，可以在包的根目录下执行：</p>
<pre class="crayon-plain-tag"># 你可以设置字段默认值：
npm set init.author.email "i@gmem.cc"
npm set init.author.name "i"
npm set init.license "MIT"
# 指定 --yes 则仅仅提问author字段
npm init --yes</pre>
<p>生成的package.json内容如下：</p>
<pre class="crayon-plain-tag">{
    "name": "包名称",
    "version": "版本号，默认1.0.0",
    "main": "入口点，默认index.js",
    // 脚本文件列表
    "scripts": {
        // 测试脚本
        "test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
        // 构建脚本
        "build": "babel src -d lib"
    },
    // 关键字，用于搜索
    "keywords": [],
    "author": "包的作者",
    "license": "许可证类型，默认ISC",
    "repository": {
        "type": "仓库类型，例如git",
        "url": "仓库URL，例如https://github.com/author/name"
    },
    "bugs": {
        "url": "https://github.com/author/name/issues"
    },
    "homepage": "https://github.com/author/name"
}</pre>
<div class="blog_h3"><span class="graybg">指定依赖</span></div>
<p>你可以在包描述符中指定当前包的依赖项：</p>
<ol>
<li>dependencies，在产品环境下，依赖的包</li>
<li>devDependencies，在开发/测试时，依赖的包</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">{
    "name": "my_package",
    "version": "1.0.0",
    "dependencies": {
        "my_dep": "^1.0.0"
    },
    "devDependencies": {
        "my_test_framework": "^3.1.0"
    }
}</pre>
<p>你可以在本地安装软件包的同时，将其自动添加为当前工程的依赖： </p>
<pre class="crayon-plain-tag"># 安装包，并将其作为当前工程的dependencies
npm install  --save      package_name
# 安装包，并将其作为当前工程的devDependencies
npm install  --save-dev  package_name</pre>
<div class="blog_h3"><span class="graybg">更新依赖</span></div>
<p>在当前工程根目录下执行下面的命令，即可更新依赖到最新的、匹配semver的版本：</p>
<pre class="crayon-plain-tag">npm update</pre>
<div class="blog_h3"><span class="graybg">删除依赖</span></div>
<pre class="crayon-plain-tag"># 从本地目录删除一个依赖
npm uninstall package_name
# 删除的同时，从dependencies或者devDependencies移除：
npm uninstall --save     package_name
npm uninstall --save-dev package_name </pre>
<div class="blog_h2"><span class="graybg">发布模块</span></div>
<p>只要是具有合法包描述符（package.json）的目录，都可以发布到npm官方仓库中：</p>
<pre class="crayon-plain-tag"># 如果没有npm仓库的账号，需要先创建账号
npm adduser
# 执行登录，并把身份信息保存在客户端
npm login

# 发布当前包
npm publish</pre>
<p>注意，任何文件都作为包的内容被发布，除非在.gitignore或者.npmignore中声明排除模式。</p>
<p>每当你修改了包的版本号，都可以再次进行发布。</p>
<div class="blog_h2"><span class="graybg">Semver</span></div>
<p>所谓语义版本控制（Semantic versioning），是一种被广泛的使用的版本号规则 ，用来向软件包的用户传递软件包的更新情况。</p>
<div class="blog_h3"><span class="graybg">包发布者</span></div>
<p>版本号必须符合<pre class="crayon-plain-tag">1.0.0</pre> 这样的模式，即三段版本号：</p>
<ol>
<li>补丁版本：当进行缺陷修复、其它小的改动时，递增最低位</li>
<li>小版本：当引入了新的特性，但是不会影响既有的特性时，递增中间位</li>
<li>大版本：当破坏了向后兼容性时，递增第一位</li>
</ol>
<div class="blog_h3"><span class="graybg">包使用者</span></div>
<p>作为包的使用者，你可以在package.json中声明，那样的版本更新是可以被接受的。假设当前依赖版本是1.0.4，那么：</p>
<ol>
<li>允许补丁版本：<pre class="crayon-plain-tag">1.0</pre> 、<pre class="crayon-plain-tag">1.0.x</pre> 或者<pre class="crayon-plain-tag">~1.0.4</pre> </li>
<li>允许小版本：<pre class="crayon-plain-tag">1</pre> 、<pre class="crayon-plain-tag">1.x</pre> 或者<pre class="crayon-plain-tag">^1.0.4</pre> </li>
<li>允许大版本：<pre class="crayon-plain-tag">*</pre> 或者<pre class="crayon-plain-tag">x</pre> </li>
</ol>
<div class="blog_h2"><span class="graybg">使用标签</span></div>
<p>标签（Tag）是对Semver的补充，用于组织包的不同版本。</p>
<p>标签可以作为版本号的别名，例如某个项目可能具有多个开发流，并以不同的Tag区分它们——stable、beta、dev、canary等。</p>
<div class="blog_h3"><span class="graybg">添加标签</span></div>
<p>执行下面的命令，可以为你的包的特定版本添加一个标签：</p>
<pre class="crayon-plain-tag">npm dist-tag add &lt;pkg&gt;@&lt;version&gt; [&lt;tag&gt;]</pre>
<div class="blog_h3"><span class="graybg">发布时指定标签</span></div>
<p>在发布包到npm仓库时，可以指定标签： </p>
<pre class="crayon-plain-tag">npm publish --tag beta</pre>
<p>如果不指定标签，publish子命令自动添加<pre class="crayon-plain-tag">latest</pre> 标签。</p>
<div class="blog_h3"><span class="graybg">安装时指定标签</span></div>
<p>在安装包时，也可以指定标签：<pre class="crayon-plain-tag">npm install &lt;pkg&gt;@&lt;tag&gt;</pre> ，如果不指定，默认使用latest标签。</p>
<div class="blog_h2"><span class="graybg">使用Scoped包</span></div>
<p>对于npm模块来说，范围（Scope）类似于名字空间。当指定一个包名时，以@开头，则它是一个Scoped包。Scope由@与/之间的字符串限定：<pre class="crayon-plain-tag">@scope/project-name</pre> ，每个npm仓库用户，都具有自己的Scope：<pre class="crayon-plain-tag">@username/project-name</pre> 。</p>
<div class="blog_h3"><span class="graybg">创建Scoped包</span></div>
<p>只需要在包描述符中修改包名称，即可创建一个Scoped包：</p>
<pre class="crayon-plain-tag">{
    "name": "@username/project-name"
}</pre>
<p>使用npm init生成package.json时，你可以指定<pre class="crayon-plain-tag">npm init --scope=username</pre>  。</p>
<div class="blog_h3"><span class="graybg">发布Scoped包</span></div>
<p>默认情况下，Scoped包是私有的，你需要付费帐户。如果想将其发布为公共包，可以：</p>
<pre class="crayon-plain-tag">npm publish --access=public</pre>
<div class="blog_h3"><span class="graybg">依赖Scoped包</span></div>
<p>在package.json中：</p>
<pre class="crayon-plain-tag">{
    "dependencies": {
        "@username/project-name": "^1.0.0"
    }
}</pre>
<p>在命令行中：</p>
<pre class="crayon-plain-tag">npm install @username/project-name --save</pre>
<p>在require调用中：</p>
<pre class="crayon-plain-tag">var projectName = require("@username/project-name");  </pre>
<div class="blog_h1"><span class="graybg">npm-scripts</span></div>
<p>npm允许在package.json的scripts字段，以字典的形式声明多个脚本。这些脚本会在包生命周期的不同阶段自动调用：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">脚本键（Stage）</td>
<td style="text-align: center;">何时调用</td>
</tr>
</thead>
<tbody>
<tr>
<td>prepublish</td>
<td>
<p>在发布包到仓库之前调用<br />在进行本地 npm install，不带任何参数时调用</p>
<p>如果你希望在包被使用之前，执行不依赖于目标（安装包的）平台的动作，可以使用该Stage。示例场景包括：</p>
<ol>
<li>编译CoffeeScript为JavaScript</li>
<li>创建JavaScript文件的最小化版本</li>
<li>下载包需要使用的远程资源</li>
</ol>
<p>使用该Stage的好处是，目标动作可以在单个地方、一次性的被执行，因此可以降低复杂性和不确定性。使用该Stage还意味着：</p>
<ol>
<li>你可以将coffee-script声明为devDependency</li>
<li>不需要在包中包含JS压缩器（minifiers）</li>
<li>你不需要在目标系统上调用curl或者wget来下载东西</li>
</ol>
</td>
</tr>
<tr>
<td>publish,postpublish</td>
<td>在发布包到仓库之后调用</td>
</tr>
<tr>
<td>preinstall</td>
<td>在包被安装之前调用</td>
</tr>
<tr>
<td>install, postinstall</td>
<td>在包被安装之后调用</td>
</tr>
<tr>
<td>preuninstall, uninstall</td>
<td>在包被删除之前调用</td>
</tr>
<tr>
<td>postuninstall</td>
<td>在包被删除之后调用</td>
</tr>
<tr>
<td>preversion, version</td>
<td>在Bump包的版本之前调用</td>
</tr>
<tr>
<td>postversion</td>
<td>在Bump包的版本之后调用</td>
</tr>
<tr>
<td>pretest, test, posttest</td>
<td>由npm test命令调用</td>
</tr>
<tr>
<td>prestop, stop, poststop</td>
<td>由npm stop命令调用</td>
</tr>
<tr>
<td>prestart, start, poststart</td>
<td>由npm start命令调用</td>
</tr>
<tr>
<td>prerestart, restart, postrestart</td>
<td>由 npm restart调用。如果不指定该脚本，npm restart会先调用stop再调用start</td>
</tr>
</tbody>
</table>
<p>此外，不在上述列表的任何Stage，都可以通过<pre class="crayon-plain-tag">npm run-script &lt;pkg&gt; &lt;stage&gt;</pre> 命令来调用。<pre class="crayon-plain-tag">pre&lt;stage&gt;</pre> 、<pre class="crayon-plain-tag">post&lt;stage&gt;</pre> 脚本则会在该命令之前、之后被调用。</p>
<p>注意：</p>
<ol>
<li>这些脚本被传递给Shell执行，如果退出码不是0，则会中断当前npm命令</li>
<li>这些脚本不一定必须是Node.js程序或者JavaScript程序，任意可执行文件都是支持的</li>
<li>
<p><pre class="crayon-plain-tag">--</pre> 可以用来中止从命令行读取npm选项，这样，后续命令行参数被传递给脚本</p>
</li>
</ol>
<div class="blog_h2"><span class="graybg">运行环境</span></div>
<p>Stage脚本在一个特定的环境中运行，npm的配置、当前进程的状态会影响到该环境</p>
<div class="blog_h3"><span class="graybg">PATH</span></div>
<p>如果你依赖于定义了Stage脚本的模块，可以：</p>
<pre class="crayon-plain-tag">{
    "name": "foo",
    "dependencies": {
        "bar": "0.1.x"
    },
    "scripts": {
        "start": "bar ./test"
    }
}</pre>
<p>由于依赖了bar，因此bar包提供的可执行文件bar被自动添加到node_modules/.bin目录，因此可以直接调用。</p>
<div class="blog_h3"><span class="graybg">package.json变量</span></div>
<p>package.json的属性被映射到<pre class="crayon-plain-tag">npm_package_</pre>  前缀的环境变量，Stage脚本可以使用npm_package_name来获得当前包的name。</p>
<p>package.json的config属性被映射到<pre class="crayon-plain-tag">npm_package_config_</pre> 前缀的环境变量</p>
<div class="blog_h3"><span class="graybg">配置参数</span></div>
<p>npm配置参数被映射到<pre class="crayon-plain-tag">npm_config_</pre> 前缀的环境变量，Stage脚本可以访问这些变量。例如：</p>
<pre class="crayon-plain-tag">{
    "name": "foo",
    "config": {
        "port": "8080"
    },
    "scripts": {
        "start": "node server.js"
    }
}</pre>
<p>声明了一个port变量，Stage脚本可以这样访问：</p>
<pre class="crayon-plain-tag">http.createServer(...).listen(process.env.npm_package_config_port)</pre>
<p>而你可以改变port的当前值：</p>
<pre class="crayon-plain-tag">npm config set foo:port 80 </pre>
<div class="blog_h2"><span class="graybg">钩子脚本</span></div>
<p>如果任意包的某个Stage都需要执行同一脚本，可以使用钩子。</p>
<p>在<pre class="crayon-plain-tag">node_modules/.hooks/{stage}</pre> 目录下放置一个可执行文件，它会在所有包的stage阶段自动被执行。</p>
<div class="blog_h2"><span class="graybg">最佳实践</span></div>
<ol>
<li>除非你真的愿意，不要让Stage脚本以非0退出。例外是uninstall脚本。非0会导致npm操作失败，可能导致回滚</li>
<li>对于npm本身就能够为你做的事情，不要用Stage脚本完成</li>
<li>根据环境变量来决定安装文件到什么位置，例如npm_config_binroot为/home/user/bin时，你不应该把文件安装到/usr/local/bin</li>
<li>不要为你的脚本添加sudo前缀</li>
</ol>
<div class="blog_h1"><span class="graybg">npm命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>access</td>
<td>
<p>对公共包进行访问控制：</p>
<pre class="crayon-plain-tag"># 设置某个包为公共可见或者受限的
npm access public [&lt;package&gt;]
npm access restricted [&lt;package&gt;]

# 授予特定团队/用户以权限
npm access grant &lt;read-only|read-write&gt; &lt;scope:team&gt; [&lt;package&gt;]
npm access revoke &lt;scope:team&gt; [&lt;package&gt;]

# 显示一个用户或者团队能够访问的包的列表
npm access ls-packages [&lt;user&gt;|&lt;scope&gt;|&lt;scope:team&gt;]</pre>
</td>
</tr>
<tr>
<td>adduser</td>
<td>
<p>为仓库添加一个用户：
<pre class="crayon-plain-tag">npm adduser [--registry=url] [--scope=@orgname] [--always-auth]</pre>
</td>
</tr>
<tr>
<td>bin</td>
<td>
<p>打印npm安装可执行文件的目标目录：
<pre class="crayon-plain-tag">npm bin [-g|--global]</pre>
</td>
</tr>
<tr>
<td>bugs</td>
<td>
<p>猜测包的缺陷跟踪URL，并用浏览器打开它：
<p><pre class="crayon-plain-tag">npm bugs [&lt;pkgname&gt;]</pre>
</td>
</tr>
<tr>
<td>build</td>
<td>
<p>构建一个包：<pre class="crayon-plain-tag">npm build [&lt;package-folder&gt;]</pre> </p>
<p>该命令是一个底层（Plumbing）的命令，通常你不应该直接调用。它被link或者install子命令调用</p>
<p>如果你需要调用build脚本，可以：</p>
<pre class="crayon-plain-tag">npm run-script build</pre>
</td>
</tr>
<tr>
<td>cache</td>
<td>
<p>操控包缓存：
<pre class="crayon-plain-tag"># 将指定的包添加到本地缓存
npm cache add &lt;tarball file&gt;
npm cache add &lt;folder&gt;
npm cache add &lt;tarball url&gt;
npm cache add &lt;name&gt;@&lt;version&gt;

# 显示缓存中的数据
npm cache ls [&lt;path&gt;]

# 清空缓存
npm cache clean [&lt;path&gt;]</pre>
</td>
</tr>
<tr>
<td>config</td>
<td>
<p>管理npm的配置文件：
<pre class="crayon-plain-tag"># 设置配置参数
npm config set &lt;key&gt; &lt;value&gt; [-g|--global]
# 读取配置参数
npm config get &lt;key&gt;
# 删除配置参数
npm config delete &lt;key&gt;
# 列出所有配置参数
npm config list
# 用默认编辑器编辑配置文件，-g则编辑全局配置文件
npm config edit  [-g|--global]
# 别名
npm get &lt;key&gt;
npm set &lt;key&gt; &lt;value&gt; [-g|--global]</pre>
<p>npm可以从命令行参数、环境变量、npmrc文件读取配置参数，某些情况下还可以从package.json中读取 </p>
</td>
</tr>
<tr>
<td>dedupe</td>
<td>
<p>减少重复依赖：</p>
<pre class="crayon-plain-tag">npm dedupe
npm ddp</pre>
<p>该子命令会搜索本地包的依赖树，并尝试像树的根部移动依赖，以便在总体上简化其结构。例如：</p>
<pre class="crayon-plain-tag">a
+-- b &lt;-- depends on c@1.0.x
|   `-- c@1.0.3
`-- d &lt;-- depends on c@~1.0.9
    `-- c@1.0.10</pre>
<p>在执行该子命令后，会被简化为：</p>
<pre class="crayon-plain-tag">a
+-- b
+-- d
`-- c@1.0.10</pre>
</td>
</tr>
<tr>
<td>deprecate  </td>
<td>
<p>更新包在仓库中的条目，标记某个版本为废弃的：
<pre class="crayon-plain-tag">npm deprecate &lt;pkg&gt;[@&lt;version&gt;] &lt;message&gt;
# 示例
npm deprecate my-thing@"&lt; 0.2.3" "critical bug fixed in v0.2.3"</pre>
</td>
</tr>
<tr>
<td>dist-tag</td>
<td>
<p>修改包的分发标签（distribution tags）：
<pre class="crayon-plain-tag"># 为指定版本的包设置标签
npm dist-tag add &lt;pkg&gt;@&lt;version&gt; [&lt;tag&gt;]
# 删除一个标签
npm dist-tag rm &lt;pkg&gt; &lt;tag&gt;
# 列出所有标签
npm dist-tag ls [&lt;pkg&gt;]</pre>
</td>
</tr>
<tr>
<td>docs</td>
<td>
<p>尝试猜测包的文档地址，并在浏览器中打开：
<p><pre class="crayon-plain-tag">npm docs [&lt;pkgname&gt; [&lt;pkgname&gt; ...]]
npm docs .
npm home [&lt;pkgname&gt; [&lt;pkgname&gt; ...]]
npm home .</pre>
</td>
</tr>
<tr>
<td>edit</td>
<td>编辑一个已经安装的包</td>
</tr>
<tr>
<td>explore</td>
<td>浏览一个已经安装的包</td>
</tr>
<tr>
<td>help</td>
<td>显示帮助信息：<pre class="crayon-plain-tag">npm help &lt;term&gt; [&lt;terms..&gt;]</pre></td>
</tr>
<tr>
<td>help-search</td>
<td>搜索npm帮助文档：<pre class="crayon-plain-tag">npm help-search &lt;text&gt;</pre> </td>
</tr>
<tr>
<td>init</td>
<td>交互式的初始化package.json文件：<pre class="crayon-plain-tag">npm init [-f|--force|-y|--yes]</pre> </td>
</tr>
<tr>
<td>install</td>
<td>
<p>安装一个包：</p>
<pre class="crayon-plain-tag"># 子命令别名
npm i ...

# 安装依赖到本地的node_modules目录
# 在全局模式下，该命令把当前目录安装为全局包，默认的，所有在package.json中声明的依赖都被安装
# 如果指定--production则不安装devDependencies中的依赖
npm install (with no args, in package dir)
# 从仓库安装指定的包、版本
npm install [&lt;@scope&gt;/]&lt;name&gt;
npm install [&lt;@scope&gt;/]&lt;name&gt;@&lt;tag&gt;
npm install [&lt;@scope&gt;/]&lt;name&gt;@&lt;version&gt;
npm install [&lt;@scope&gt;/]&lt;name&gt;@&lt;version range&gt;
# 安装位于压缩文件中的包
npm install &lt;tarball file&gt;
npm install &lt;tarball url&gt;
# 安装位于本地文件系统中的包
npm install &lt;folder&gt;</pre>
<p>该子命令用于安装一个包，以及所有该包的依赖。如果包提供了shrinkwrap文件则依赖的安装由shrinkwrap驱动 </p>
<p>下面是三个常用的、互斥的选项：<br />-S, --save  把安装的包加入到dependencies<br />-D, --save-dev  把安装的包加入到devDependencies<br />-O, --save-optional  把安装的包加入到optionalDependencies</p>
<p>当使用上面三个选项之一时，可以指定：<br />-E, --save-exact  在package.json的dependencies/devDependencies/optionalDependencies中限定使用精确的依赖版本</p>
</td>
</tr>
<tr>
<td>install-test</td>
<td>连续执行install、test子命令</td>
</tr>
<tr>
<td>link</td>
<td>
<p>为一个包的目录创建符号链接：</p>
<pre class="crayon-plain-tag"># 子命令别名
npm ln ...

npm link (in package dir)
npm link [&lt;@scope&gt;/]&lt;pkg&gt;[@&lt;version&gt;]</pre>
<p>从全局安装目录中定位到目标包，并在当前目录中创建该包的符号链接</p>
</td>
</tr>
<tr>
<td>logout</td>
<td>从仓库退出登录：<pre class="crayon-plain-tag">npm logout [--registry=&lt;url&gt;] [--scope=&lt;@scope&gt;]</pre> </td>
</tr>
<tr>
<td>ls</td>
<td>
<p>列出已经安装的包：<pre class="crayon-plain-tag">npm ls [[&lt;@scope&gt;/]&lt;pkg&gt; ...]</pre> </p>
<p>可以显示当前工程的依赖树</p>
</td>
</tr>
<tr>
<td>outdated</td>
<td>
<p>检查已经过期的包：<pre class="crayon-plain-tag">npm outdated [[&lt;@scope&gt;/]&lt;pkg&gt; ...]</pre> </p>
<p>输出列如下：<br />wanted   最高的满足package.json需求的包版本<br />latest   仓库中被标记为latest的包版本<br />location  在依赖树的何处定位到此包</p>
<p>注意：该子命令默认使用depth为0，因此只有直接依赖的包过期了才会被检测到</p>
</td>
</tr>
<tr>
<td>owner</td>
<td>
<p>管理包的所有者信息：</p>
<pre class="crayon-plain-tag">npm owner add &lt;user&gt; [&lt;@scope&gt;/]&lt;pkg&gt;
npm owner rm &lt;user&gt; [&lt;@scope&gt;/]&lt;pkg&gt;
npm owner ls [&lt;@scope&gt;/]&lt;pkg&gt;</pre>
</td>
</tr>
<tr>
<td>pack</td>
<td>
<p>为一个包创建压缩文件：
<pre class="crayon-plain-tag">npm pack [[&lt;@scope&gt;/]&lt;pkg&gt;...]</pre>
<p>压缩文件会被拷贝到当前工作目录，命名为：<pre class="crayon-plain-tag">&lt;name&gt;-&lt;version&gt;.tgz</pre> </p>
</td>
</tr>
<tr>
<td>ping</td>
<td>Ping指定的仓库并验证身份信息：<pre class="crayon-plain-tag">npm ping [--registry &lt;registry&gt;]</pre> </td>
</tr>
<tr>
<td>prefix</td>
<td>
<p>打印本地前缀到标准输出——最接近工作目录的、包含package.json的祖代目录</p>
<p>如果指定全局模式，则打印全局安装目录前缀</p>
</td>
</tr>
<tr>
<td>prune</td>
<td>
<p>移除没有使用的依赖：<pre class="crayon-plain-tag">npm prune [[&lt;@scope&gt;/]&lt;pkg&gt;...] [--production]</pre> </p>
<p>该子命令移除没有在父包依赖列表里出现的那些包</p>
<p>如果指定--production，则devDependencies中指定的依赖被移除</p>
</td>
</tr>
<tr>
<td>publish</td>
<td>
<p>发布一个包到仓库：</p>
<p><pre class="crayon-plain-tag">npm publish [&lt;tarball&gt;|&lt;folder&gt;] [--tag &lt;tag&gt;] [--access &lt;public|restricted&gt;]</pre>
</td>
</tr>
<tr>
<td>rebuild</td>
<td>
<p>重新构建一个包：<pre class="crayon-plain-tag">npm rebuild [[&lt;@scope&gt;/&lt;name&gt;]...]</pre>  </p>
<p>当你安装了新版本的Node.js，并且需要重新编译所有的C++加载项时，可以使用该命令</p>
</td>
</tr>
<tr>
<td> repo</td>
<td>在浏览器中打开包的仓库页面：<pre class="crayon-plain-tag">npm repo [&lt;pkg&gt;]</pre> </td>
</tr>
<tr>
<td>restart</td>
<td>
<p>重启启动一个包：<pre class="crayon-plain-tag">npm restart [-- &lt;args&gt;]</pre>  </p>
<p>该命令会依次执行stop、restart、start脚本，以及相关的pre-*、post-*脚本。具体顺序如下：<br />prerestart ⇨ prestop ⇨ stop ⇨ poststop ⇨ restart ⇨ prestart ⇨ start ⇨ poststart ⇨ postrestart</p>
</td>
</tr>
<tr>
<td>root</td>
<td>
<p>打印node_modules目录所在位置到标准输出：<pre class="crayon-plain-tag">npm root [-g]</pre> </p>
</td>
</tr>
<tr>
<td>run-script</td>
<td>
<p>运行任意的包脚本：</p>
<pre class="crayon-plain-tag"># 子命令别名
npm run ...
# 格式：
npm run-script &lt;command&gt; [-- &lt;args&gt;...]</pre>
<p>这里的command是定义在package.json的scripts属性中的一个键</p>
</td>
</tr>
<tr>
<td>search</td>
<td>
<p>在仓库中搜索指定的包：</p>
<p><pre class="crayon-plain-tag"># 子命令别名
npm s ...
npm se ...
# 格式：
npm search [-l|--long] [search terms ...]</pre>
</td>
</tr>
<tr>
<td>shrinkwrap</td>
<td>
<p>锁定依赖的版本：<pre class="crayon-plain-tag">npm shrinkwrap</pre> </p>
<p>该子命令锁定包的依赖的版本，当包被安装时，其依赖的精确版本会被安装</p>
<p>默认情况下，npm install会递归的安装目标包package.json中指定的依赖项，选择满足依赖semver的最新版本。但是有些时候需要避免此行为</p>
<p>该子命令会生成一个<pre class="crayon-plain-tag">npm-shrinkwrap.json</pre> 文件，其中记录依赖的精确版本</p>
</td>
</tr>
<tr>
<td>star<br />unstar</td>
<td>
<p>标记你最喜欢的包：</p>
<p><pre class="crayon-plain-tag">npm star [&lt;pkg&gt;...]
npm unstar [&lt;pkg&gt;...]</pre>
</td>
</tr>
<tr>
<td>stars</td>
<td>列出被标记为喜欢的包：<pre class="crayon-plain-tag">npm stars [&lt;user&gt;]</pre> </td>
</tr>
<tr>
<td>stop</td>
<td>执行包的stop脚本：<pre class="crayon-plain-tag">npm stop [-- &lt;args&gt;]</pre> </td>
</tr>
<tr>
<td>team</td>
<td>
<p>管理团队和团队成员：</p>
<pre class="crayon-plain-tag"># 创建或者删除团队
npm team create &lt;scope:team&gt;
npm team destroy &lt;scope:team&gt;

# 添加或者删除成员
npm team add &lt;scope:team&gt; &lt;user&gt;
npm team rm &lt;scope:team&gt; &lt;user&gt;

# 列出组织或者团队的成语
npm team ls &lt;scope&gt;|&lt;scope:team&gt;

# 编辑团队
npm team edit &lt;scope:team&gt;</pre>
</td>
</tr>
<tr>
<td>test</td>
<td>
<p>测试一个包：
<pre class="crayon-plain-tag">npm test [-- &lt;args&gt;]
npm tst [-- &lt;args&gt;]</pre>
<p>该子命令执行package.json中的test脚本</p>
</td>
</tr>
<tr>
<td>uninstall</td>
<td>
<p>删除一个包：</p>
<p><pre class="crayon-plain-tag">npm uninstall [&lt;@scope&gt;/]&lt;pkg&gt;[@&lt;version&gt;]... [-S|--save|-D|--save-dev|-O|--save-optional]</pre>
</td>
</tr>
<tr>
<td>unpublish</td>
<td>
<p>从仓库中删除一个包：<pre class="crayon-plain-tag">npm unpublish [&lt;@scope&gt;/]&lt;pkg&gt;[@&lt;version&gt;]</pre> </p>
<p>通常不应该删除已经发布的包版本，因为其它用户可能依赖它。可以使用deprecate命令代替</p>
</td>
</tr>
<tr>
<td>update</td>
<td>
<p>更新一个包：<pre class="crayon-plain-tag">npm update [-g] [&lt;pkg&gt;...]</pre> </p>
<p>该命令会更新所有指定的包到尽可能新（semver允许的）的版本</p>
<p>该命令也会安装缺失的软件包，如果指定--dev则devDependencies中的依赖也被处理</p>
</td>
</tr>
<tr>
<td>whoami</td>
<td>
<p>显示当前用户名：<pre class="crayon-plain-tag">npm whoami [--registry &lt;registry&gt;]</pre> </p>
<p>该命令显示username配置参数</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">package.json</span></div>
<p>包的根目录下的package.json文件，是包的描述符，它必须标准化的JSON格式。</p>
<div class="blog_h2"><span class="graybg">属性列表</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>
<p>包的名称，该字段与version一起，唯一的标识一个包。该字段必须：</p>
<ol>
<li>不大于214字符</li>
<li>不能以点号、下划线开头</li>
<li>新创建的包，包名不得包含大写字母</li>
<li>不得包含URL不安全字符</li>
</ol>
</td>
</tr>
<tr>
<td>version</td>
<td>包的版本号</td>
</tr>
<tr>
<td>description</td>
<td>包的描述信息，该信息显示在npm search的结果中</td>
</tr>
<tr>
<td>keywords</td>
<td>包的搜索关键字</td>
</tr>
<tr>
<td>homepage</td>
<td>此包的项目主页</td>
</tr>
<tr>
<td>bugs</td>
<td>
<p>此包的缺陷跟踪地址、电子邮件地址，格式：</p>
<pre class="crayon-plain-tag">{
    "url": "https://github.com/owner/project/issues",
    "email": "project@hostname.com"
}</pre>
</td>
</tr>
<tr>
<td>license</td>
<td>包的授权类型</td>
</tr>
<tr>
<td>author</td>
<td>包的作者</td>
</tr>
<tr>
<td>contributors</td>
<td>包的贡献者列表</td>
</tr>
<tr>
<td>files</td>
<td>
<p>工程中需要包含在包中的目录、文件列表
<p>如果不指定此字段，可以使用.npmignore、.gitignore设置排除规则</p>
<p>文件package.json、README、CHANGELOG、LICENSE、LICENCE总是包含</p>
<p>文件.git、.svn等总是排除</p>
</td>
</tr>
<tr>
<td>main</td>
<td>作为程序主入口点的模块的ID（相对于包根目录），当require('pkg')时，该模块的exports对象作为返回值</td>
</tr>
<tr>
<td>bin</td>
<td>
<p>可以指定一个或者多个需要安装到PATH下的可执行文件，示例：</p>
<pre class="crayon-plain-tag">{
    "bin": {
        // 键为可执行文件名称，值为相对于包根目录的JS文件路径
        "myapp": "./cli.js"
    }
}</pre>
</td>
</tr>
<tr>
<td>man</td>
<td>指定一个文件、或者多个文件的数组，这些文件会被放置到合适的地方，供man程序查看</td>
</tr>
<tr>
<td>directories</td>
<td>CommonJS包允许通过使用一个directories对象来指定包的详细结构</td>
</tr>
<tr>
<td>repository</td>
<td>
<p>设置此包的CVS仓库：
<pre class="crayon-plain-tag">"repository": {
    "type": "git",
    "url": "https://github.com/npm/npm.git"
}

"repository": {
    "type": "svn",
    "url": "https://v8.googlecode.com/svn/trunk/"
} </pre>
</td>
</tr>
<tr>
<td>scripts</td>
<td>
<p>一个字典，指定在包的生命周期的不同阶段需要被执行的脚本
<p>如果包的根目录下存在一个server.js文件，则默认值为：</p>
<pre class="crayon-plain-tag">"scripts": {"start": "node server.js"}</pre>
<p>如果包的根目录下存在一个binding.gyp文件，则默认值为：</p>
<pre class="crayon-plain-tag">"scripts":{"preinstall": "node-gyp rebuild"}</pre>
</td>
</tr>
<tr>
<td>config</td>
<td>设置包脚本中可以访问的参数</td>
</tr>
<tr>
<td>dependencies</td>
<td>
<p>指定包的依赖项。这些依赖项必须随着当前包一起安装
<p>一个字典，键为依赖的包的名称，值为包的版本：<br />version    精确版本<br />&gt;version  最小版本（不包含）<br />&gt;=version 最小版本<br />&lt;version 最大版本（不包含）<br />&lt;=version 最大版本<br />~version  允许BUG修复版本变动<br />^version  允许兼容版本变动<br />1.2.x  允许1.2下任意BUG修复版本<br />*  允许所有版本，""等价于"*"<br />version1 - version2  在两个版本之间（包含）<br />range1 || range2   匹配range1或者range2<br />tag  匹配标记了tag的版本</p>
</td>
</tr>
<tr>
<td>devDependencies</td>
<td>指定包的开发依赖项。当在一个包的根目录下执行npm link、npm install时，这些依赖会被一并安装</td>
</tr>
<tr>
<td>bundledDependencies</td>
<td>指定当发布当前包时，需要捆绑到其中的包的名称</td>
</tr>
<tr>
<td>optionalDependencies</td>
<td>指定可选依赖，这些依赖会被安装，但是安装失败的情况下，整体安装不会因而失败</td>
</tr>
<tr>
<td>peerDependencies</td>
<td>
<p>当前包的一个依赖，此依赖也会安装该包的用户所依赖。假设当前包是grunt插件，则它依赖于grunt，而此插件的用户也必然依赖于grunt。使用该配置，可以声明插件所要求的宿主包的版本</p>
<p>如果当前包依赖于request 2.x的同时依赖于some-other-library，而some-other-library依赖于request 1.x，则当前包安装后的依赖树如下：</p>
<pre class="crayon-plain-tag">├── request@2.12.0
└─┬ some-other-library@1.2.3
  └── request@1.9.9</pre>
<p>感谢模块化机制，通常情况下这不会造成问题， some-other-library使用自己的1.9.9版本，对当前包不会造成冲突。但是在使用插件时，会出现问题，这与插件的运行方式有关</p>
<p>从npm 3.x开始peerDependencies不会自动安装，出现如下警告：</p>
<pre class="crayon-plain-tag">npm WARN peerDependencies The peer dependency mocha@&gt;=1.x.x included from grunt-mocha-istanbul will no
npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency
npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly.</pre>
</td>
</tr>
<tr>
<td>engines</td>
<td>
<p>指定兼容的Node.js引擎、npm版本：
<pre class="crayon-plain-tag">"engines": {
    "node": "&gt;=0.10.3 &lt;0.12",
    "npm": "~1.0.20"
}</pre>
</td>
</tr>
<tr>
<td>os</td>
<td>
<p>指定支持的操作系统，示例：
<pre class="crayon-plain-tag">"os" : [ "darwin", "linux" ]
"os" : [ "!win32" ] </pre>
</td>
</tr>
<tr>
<td>cpu</td>
<td>
<p>指定支持的CPU架构，示例：
<pre class="crayon-plain-tag">"cpu" : [ "x64", "ia32" ]
"cpu" : [ "!arm", "!mips" ]</pre>
</td>
</tr>
<tr>
<td>preferGlobal</td>
<td>如果当前包仅仅是为了提供命令行程序，可以设置为true。这样，用户在尝试本地安装时会得到一个警告</td>
</tr>
<tr>
<td>private</td>
<td>如果设置为true，则npm拒绝发布此包</td>
</tr>
<tr>
<td>publishConfig</td>
<td>指定一系列的nmp配置项，这些配置项在发布包的时候使用</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">npm配置</span></div>
<div class="blog_h2"><span class="graybg">配置来源</span></div>
<p>npm的配置信息，按优先级从高到低，有以下来源：
<ol>
<li>命令行标记，例如<pre class="crayon-plain-tag">--name value</pre> 设置配置参数name为value，<pre class="crayon-plain-tag">--</pre> 表示停止继续读取命令行标记，最后一个标记<pre class="crayon-plain-tag">--flag</pre> 如果不提供值则值默认为true</li>
<li>环境变量，以<pre class="crayon-plain-tag">npm_config_</pre> 开头的环境变量作为配置参数解析，例如<pre class="crayon-plain-tag">npm_config_name=value</pre> ，这类配置参数的名字，大小写不敏感</li>
<li>npmrc文件，包括：
<ol>
<li>工程根目录下的.npmrc文件</li>
<li>用户的~/.npmrc文件</li>
<li>全局的$PREFIX/etc/npmrc文件</li>
<li>npm配置的/path/to/npm/npmrc文件</li>
</ol>
</li>
</ol>
<div class="blog_h2"><span class="graybg">配置项列表</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>access</td>
<td>默认restricted，当发布scoped包时，默认访问限制为restricted，如果要改为允许公共访问，设置为public</td>
</tr>
<tr>
<td>always-auth</td>
<td>默认false，是否在访问仓库（Regisry）时总是要求身份验证</td>
</tr>
<tr>
<td>bin-links</td>
<td>默认true，是否为包中的可执行文件创建符号链接</td>
</tr>
<tr>
<td>cache</td>
<td>npm缓存位置，默认Windows下为%AppData%\npm-cache，Linux下为~/.npm</td>
</tr>
<tr>
<td>cache-max</td>
<td>
<p>默认Infinity，再次访问仓库检查之前，缓存有效的最大时间</p>
<p>注意：除非调用<pre class="crayon-plain-tag">npm cache clean</pre> 否则缓存不会消失，缓存仅仅影响GET请求</p>
</td>
</tr>
<tr>
<td>cache-min</td>
<td>默认10，在此秒数之前，绝不重新访问仓库检查缓存有效性</td>
</tr>
<tr>
<td>depth</td>
<td>默认Infinity，执行npm ls、npm cache ls、npm outdated等命令时递归低最大深度</td>
</tr>
<tr>
<td>description</td>
<td>默认true，是否显示 npm search结果的描述字段</td>
</tr>
<tr>
<td>dry-run</td>
<td>默认false，仅仅查看命令执行结果，不改变现有文件系统</td>
</tr>
<tr>
<td>editor</td>
<td>npm edit 或者 npm config edit使用的文本编辑器</td>
</tr>
<tr>
<td>engine-strict</td>
<td>默认false，如果设置为true则npm拒绝安装声明和当前Node.js版本不兼容的包</td>
</tr>
<tr>
<td>force</td>
<td>
<p>默认false，有多种效果：</p>
<ol>
<li>生命周期脚本执行失败，不影响构建进程</li>
<li>不使用缓存</li>
</ol>
</td>
</tr>
<tr>
<td>fetch-retries</td>
<td>默认2，尝试从仓库获取同一包的次数</td>
</tr>
<tr>
<td>fetch-retry-mintimeout</td>
<td rowspan="2">尝试从仓库获取包，超时的最小、最大时间，默认10000、60000</td>
</tr>
<tr>
<td>fetch-retry-maxtimeout</td>
</tr>
<tr>
<td>git</td>
<td>指定Git命令行所在路径</td>
</tr>
<tr>
<td>global</td>
<td>
<p>是否在全局模式下运行，全局模式下，包被安装到prefix目录而不是当前目录：</p>
<ol>
<li>包被安装到{prefix}/lib/node_modules</li>
<li>可执行文件被链接到{prefix}/bin</li>
<li>Man手册被连接到{prefix}/share/man</li>
</ol>
</td>
</tr>
<tr>
<td>globalconfig</td>
<td>全局配置的位置，默认{prefix}/etc/npmrc</td>
</tr>
<tr>
<td>group</td>
<td>默认为当前进程的GID，执行包脚本时的使用的GID</td>
</tr>
<tr>
<td>heading</td>
<td>默认npm，打印调试信息时的前缀信息</td>
</tr>
<tr>
<td>https-proxy</td>
<td>指定对外执行HTTPS请求时使用的代理服务器</td>
</tr>
<tr>
<td>if-present</td>
<td>默认false，如果设置为true，则npm run-script调用不会以错误码退出，只要执行的脚本不是在package.json的scripts段定义</td>
</tr>
<tr>
<td>ignore-scripts</td>
<td>默认false，如果设置为true，则npm不会运行package.json中声明的脚本</td>
</tr>
<tr>
<td>init-module</td>
<td>默认~/.npm-init.js，指定npm init命令需要加载的模块</td>
</tr>
<tr>
<td>init-author-name</td>
<td rowspan="5">为npm init命令提供默认值</td>
</tr>
<tr>
<td>init-author-email</td>
</tr>
<tr>
<td>init-author-url</td>
</tr>
<tr>
<td>init-license</td>
</tr>
<tr>
<td>init-version</td>
</tr>
<tr>
<td>json</td>
<td>默认false，命令是否输出JSON格式的数据</td>
</tr>
<tr>
<td>key</td>
<td>访问仓库时使用的客户端Key</td>
</tr>
<tr>
<td>link</td>
<td>默认false，如果设置为true，当对应的全局包存在时，本地包仅仅是一个链接</td>
</tr>
<tr>
<td>local-address</td>
<td>连接到仓库时，使用哪个本地IP地址</td>
</tr>
<tr>
<td>loglevel</td>
<td>
<p>默认warn，设置日志级别："silent", "error", "warn", "http", "info", "verbose", "silly"</p>
<p>日志被输出到当前目录的npm-debug.log文件中</p>
</td>
</tr>
<tr>
<td>long</td>
<td>让npm ls、npm search命令输出扩展的信息</td>
</tr>
<tr>
<td>npat</td>
<td>默认false，是否在安装时执行测试</td>
</tr>
<tr>
<td>onload-script</td>
<td>当npm模块加载时，require()的Node.js模块</td>
</tr>
<tr>
<td>optional</td>
<td>尝试在optionalDependencies对象中安装包，如果失败，整体的安装过程不会中断</td>
</tr>
<tr>
<td>prefix</td>
<td>
<p>设置全局安装目录的前缀</p>
<p>如果在命令行参数中指定，用于强制非全局命令在指定的目录中运行</p>
</td>
</tr>
<tr>
<td>production</td>
<td>
<p>默认false，设置为true则运行在产品模式：</p>
<ol>
<li>不带任何参数运行npm install时devDependencies不会在顶级安装</li>
<li>为生命周期脚本设置NODE_ENV="production"</li>
</ol>
</td>
</tr>
<tr>
<td>progress</td>
<td>默认true，如果设置为true，执行时间密集型操作时显示进度条</td>
</tr>
<tr>
<td>proxy</td>
<td>指定对外执行HTTP请求时使用的代理服务器</td>
</tr>
<tr>
<td>registry</td>
<td>使用的仓库的BaseUrl，默认https://registry.npmjs.org/</td>
</tr>
<tr>
<td>rollback</td>
<td>默认true，是否移除失败的安装</td>
</tr>
<tr>
<td>save</td>
<td>默认false，将安装的包作为 package.json中的依赖项（dependencies）</td>
</tr>
<tr>
<td>save-bundle</td>
<td>默认false，如果一个包利用--save, --save-dev, --save-optional来安装时，是否也将其添加到bundleDependencies列表</td>
</tr>
<tr>
<td>save-dev</td>
<td>默认false，将安装的包作为 package.json中的开发依赖项（devDependencies）</td>
</tr>
<tr>
<td>save-exact</td>
<td>默认false，如果一个包利用--save, --save-dev, --save-optional来安装时，是否在package.json中配置为精确的依赖版本而不是使用Semver</td>
</tr>
<tr>
<td>save-optional</td>
<td>默认false，将安装的包作为 package.json中的可选依赖项（optionalDependencies）</td>
</tr>
<tr>
<td>save-prefix</td>
<td>
<p>默认^，在保存到package.json的某个依赖列表时，被依赖包的版本使用的Semver前缀</p>
</td>
</tr>
<tr>
<td>scope</td>
<td>对Scoped包进行操作时，使用的scope</td>
</tr>
<tr>
<td>searchopts</td>
<td>空格分隔的选项，这些选项总是传递给搜索命令</td>
</tr>
<tr>
<td>searchsort</td>
<td>搜索结果排序方式，默认name，可用值"name", "date","description", "keywords"，这些值都可以前缀 - 表示逆序排列</td>
</tr>
<tr>
<td>tag</td>
<td>默认值latest，当安装包时不指定版本时，默认使用的版本</td>
</tr>
<tr>
<td>user</td>
<td>运行包脚本时设置UID为</td>
</tr>
<tr>
<td>userconfig</td>
<td>用户配置位置，默认~/.npmrc</td>
</tr>
<tr>
<td>umask</td>
<td>创建文件/目录时使用的文件模式</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">命令行捷径</span></div>
<p>很多配置项具有对应低命令行捷径：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">配置项</td>
<td style="text-align: center;">捷径</td>
</tr>
</thead>
<tbody>
<tr>
<td>--version</td>
<td>-v</td>
</tr>
<tr>
<td>--help</td>
<td>-h, -?</td>
</tr>
<tr>
<td>--usage</td>
<td>-H</td>
</tr>
<tr>
<td>--loglevel silent</td>
<td>-s, --silent</td>
</tr>
<tr>
<td>--loglevel warn</td>
<td>-q, --quiet</td>
</tr>
<tr>
<td>--loglevel info</td>
<td>-d</td>
</tr>
<tr>
<td>--loglevel verbose</td>
<td>-dd, --verbose</td>
</tr>
<tr>
<td>--loglevel silly</td>
<td>-ddd</td>
</tr>
<tr>
<td>--global</td>
<td>-g</td>
</tr>
<tr>
<td>--prefix</td>
<td>-C</td>
</tr>
<tr>
<td>--long</td>
<td>-l</td>
</tr>
<tr>
<td>--message</td>
<td>-m</td>
</tr>
<tr>
<td>--parseable</td>
<td>-p, --porcelain</td>
</tr>
<tr>
<td>--registry</td>
<td>-reg</td>
</tr>
<tr>
<td>--force</td>
<td>-f</td>
</tr>
<tr>
<td>--description</td>
<td>-desc</td>
</tr>
<tr>
<td>--save</td>
<td>-D</td>
</tr>
<tr>
<td>--save-optional</td>
<td>-O</td>
</tr>
<tr>
<td>--save-bundle</td>
<td>-B</td>
</tr>
<tr>
<td>--save-exact</td>
<td>-E</td>
</tr>
<tr>
<td>--yes </td>
<td>-y</td>
</tr>
<tr>
<td>--yes false</td>
<td>-n</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">~/.npm目录是什么</span></div>
<p>该目录作为包缓存，可以避免反复下载同一个包。</p>
<div class="blog_h3"><span class="graybg">如何修改npm的全局模块安装目录？</span></div>
<p>Windows下，修改文件<pre class="crayon-plain-tag">%NODEJS_HOME%/node_modules/npm/npmrc</pre> 中的prefix为期望的安装目录。完毕后修改环境变量PATH，确保其中包含了新设置的全局安装目录。Linux下类似。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/npm-study-note">npm学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/npm-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于Eclipse的Node.js开发</title>
		<link>https://blog.gmem.cc/eclipse-based-node-js-dev</link>
		<comments>https://blog.gmem.cc/eclipse-based-node-js-dev#comments</comments>
		<pubDate>Sat, 30 Nov 2013 02:49:05 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Node.js]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=9485</guid>
		<description><![CDATA[<p>基础知识 名词术语 Node.js：一个开放源代码、跨平台的、可用于服务器端和网络应用的运行环境。Node.js应用程序JavaScript语言写成，在Node.js运行时环境运行。Node.js使用Google的V8引擎来执行JavaScript代码 NPM：即Node包管理器（Node Package Manager），是一个JavaScript软件包管理系统，默认运行环境是Node.js，从Node.js 0.6.3开始内置了NPM 安装Node.js以及Eclipse插件 安装Node.js运行时 Windows安装步骤 到Node.js官网下载安装文件，根据提示安装，设置安装目录为环境变量[crayon-69db8ad17716a754224603-i/]  修改NPM默认全局模块安装目录 将[crayon-69db8ad17716f163393919-i/] 和[crayon-69db8ad177171802080130-i/] 添加到[crayon-69db8ad177173919745480-i/] 环境变量 OS X 安装步骤 [crayon-69db8ad177175037533387/] Ubuntu安装步骤 [crayon-69db8ad177177882912591/] 安装Nodeclipse Update Site：http://www.nodeclipse.org/updates/ <a class="read-more" href="https://blog.gmem.cc/eclipse-based-node-js-dev">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/eclipse-based-node-js-dev">基于Eclipse的Node.js开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">名词术语</span></div>
<ol>
<li>Node.js：一个开放源代码、跨平台的、可用于<span style="background-color: #c0c0c0;">服务器端和网络应用</span>的运行环境。Node.js应用程序JavaScript语言写成，在Node.js运行时环境运行。Node.js使用<span style="background-color: #c0c0c0;">Google的V8引擎</span>来执行JavaScript代码</li>
<li>NPM：即Node包管理器（Node Package Manager），是一个JavaScript软件包管理系统，默认运行环境是Node.js，从Node.js 0.6.3开始内置了NPM</li>
</ol>
<div class="blog_h1"><span class="graybg">安装Node.js以及Eclipse插件</span></div>
<div class="blog_h2">安装Node.js运行时</div>
<div class="blog_h3"><span class="graybg">Windows安装步骤</span></div>
<ol>
<li>到<a href="https://nodejs.org/en/">Node.js官网</a>下载安装文件，根据提示安装，设置安装目录为环境变量<pre class="crayon-plain-tag">%NODE_HOME%</pre> </li>
<li>修改NPM默认全局模块安装目录</li>
<li>将<pre class="crayon-plain-tag">%NODE_HOME%</pre> 和<pre class="crayon-plain-tag">%NODE_HOME%\node_modules\npm</pre> 添加到<pre class="crayon-plain-tag">Path</pre> 环境变量</li>
</ol>
<div class="blog_h3"><span class="graybg">OS X 安装步骤</span></div>
<pre class="crayon-plain-tag">curl https://nodejs.org/dist/v4.4.5/node-v4.4.5-darwin-x64.tar.gz -o node-v4.4.5-darwin-x64.tar.gz
tar zxf node-v4.4.5-darwin-x64.tar.gz 
rm node-v4.4.5-darwin-x64.tar.gz
mkdir -p ~/JavaScript/nodejs
mv node-v4.4.5-darwin-x64/ ~/JavaScript/nodejs/4.4.5
# 参考Windows，添加环境变量</pre>
<div class="blog_h3"><span class="graybg">Ubuntu安装步骤</span></div>
<pre class="crayon-plain-tag">wget https://nodejs.org/dist/v4.4.1/node-v4.4.1-linux-x64.tar.xz
tar xf node-v4.4.1-linux-x64.tar.xz
rm node-v4.4.1-linux-x64.tar.xz
mkdir -p  ~/JavaScript/nodejs
mv node-v4.4.1-linux-x64  ~/JavaScript/nodejs/4.4.1
# 参考Wndows，添加环境变量</pre>
<div class="blog_h2"><span class="graybg"><span style="color: #007755;"><b>安装Nodeclipse</b></span></span></div>
<p>Update Site：http://www.nodeclipse.org/updates/</p>
<p>此站点中杂项内容非常多，可以根据需要，选择性的安装：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">插件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Nodeclipse ChromeDevTools SDK</td>
<td>Google Chrome Developer Tools SDK</td>
</tr>
<tr>
<td>Nodeclipse Chromium JavaScript Remote Debugger</td>
<td>允许使用Eclipse远程Debug正在Chromium中运行的JavaScript代码</td>
</tr>
<tr>
<td>Nodeclipse Core and Node.js</td>
<td>Node.js的Eclipse核心插件</td>
</tr>
<tr>
<td>ShellEd</td>
<td>支持在Eclipse中编辑Shell脚本</td>
</tr>
<tr>
<td>StartExplorer Feature</td>
<td>在Eclipse上下文菜单中集成：打开资源管理器、打开命令行等功能</td>
</tr>
<tr>
<td>ZipEditor</td>
<td>直接在Eclipse中查看压缩文件</td>
</tr>
</tbody>
</table>
<div class="blog_h1"> </div>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/eclipse-based-node-js-dev">基于Eclipse的Node.js开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/eclipse-based-node-js-dev/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
