分类目录归档:Node.js

Node.js 内存泄露

#监控日志输出
Process: heapTotal 606.50 MB heapUsed 604.66 MB rss 642.38 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 604.72 MB rss 642.36 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 604.73 MB rss 642.36 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.01 MB rss 642.71 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.03 MB rss 642.71 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.64 MB rss 643.43 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.68 MB rss 643.43 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.70 MB rss 643.39 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.72 MB rss 643.39 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.82 MB rss 643.56 MB
-----------------------------------------------------------

可以看到,RSS一直不断变大,主要原因是堆内存持续增加。在Node中,堆内存是V8管理的,那么必定是某变量无法被回收导致。

代码如下:

var config = require('./config');

var http = require('http');
var cheerio = require('cheerio');

// 如果没有输出则list为空数组
var parseHtml = function(html, itemid) {
	$ = cheerio.load(html);
	// ...
};

var showMem = function () {
        var mem = process.memoryUsage();
        var format = function (bytes) {
            return (bytes / 1024 / 1024).toFixed(2) + ' MB';
        };

        console.log('Process: heapTotal ' + format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss));
        console.log('-----------------------------------------------------------');
};

var server = http.createServer(function (req, res) {
	//showMem();
        //调用parseHtml 
});

Http内部调用parseHtml, 所以问题必定出现在parseHtml()函数内,经过一番测试最终发现问题出现在$ = cheerio.load(html)这个语句,由于没有使用var定义变量,导致每次调用都有一份变量挂到global.$上,在进程周期内,全局变量无法被垃圾回收器回收,所以堆内存持续增长。

把var添加到$前,内存终于开始回收了。从测试来看,内存上涨,然后在某个点下降一截,故而可以推测,V8的垃圾回收器视乎不太勤快。

对于小内存机器,V8的垃圾回收内存太慢,短时间过多并发也会导致大量内存占用,为了保护系统安全,有必要设置一个阀值,一旦超越则重启进程(重启必定所有内存均被回收):

#!/bin/bash

live=`ps -efH | grep 'agent.js' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
	/usr/local/bin/node /var/www/ebay-ap/agent.js 2>&1 >> /var/www/ebay-ap/log.txt &
fi

pid=`ps aux | grep 'agent.js' | grep -v grep | awk '{print $2}'`
rss=`ps aux | grep 'agent.js' | grep -v grep | awk '{print $6}'`

threshold=204800

if [ $rss -gt $threshold ]; then
	cmm="/usr/bin/kill"
	if [ ! -f "$cmm" ]; then
		cmm="/bin/kill"
	fi
        kill=`$cmm -9 $pid`
        if [ $kill ]; then
                echo PID: ${pid} Kill Failed [RSS: ${rss} gt ${threshold}]
        else
                echo PID: ${pid} Kill Success [RSS: ${rss} gt ${threshold}]
		/usr/local/bin/node /var/www/node/agent.js 2>&1 >> /var/www/node.log.txt &
        fi
fi

这段SHELL脚本作用为:
1 如果进程异常退出,自动拉起进程
2 如果进程内存占用超过了阀值,则先杀掉进程,然后启动进程

内存超越阀值,重启进程不但可以保护系统,同时也可以有效防止内存泄露。

Node.js入门基础知识

Node.js基础知识

/////////////////////////////////////////////////////////
Node.js控制台
1 console.log()与console.error()

console.log("字符串");

console.log("%s", "字符串1", "字符串2");
console.log("%d", 10,10.5);

2 console.dir方法(查看一个对象内容并输出到控制台)

var user = {"name": "vfeelit"};
console.dir(user);

3 console.time()和console.timeEnd()
两个方法都接收一个参数,参数值可以为任何字符串,但两个方法所使用的参数字符串必须相同才能正确统计出开始和结束时间之间锁经过的耗秒数。

4 console.trace方法
将当前位置处的栈信息作为标准错误信息进行输出。

5 console.assert方法
对一个表达式的执行结果进行评估。如果结果为false,则输出一个消息字符串并抛出AssertionError异常。

/////////////////////////////////////////////////////////
Node.js全局作用域及全局函数
1 全局作用域
Node.js中,定义了一个global对象,代码Node.js中的全局命名空间,任何全局变量、函数或对象都是该对象的一个属性值。

2 setTimeout与clearTimeout
3 setInterval与clearInterval
4 定时器对象的unref方法与ref方法

5 与模块相关的全局函数与对象
5.1 require函数加载模块

var f = require('../f.js');
var h = require('http');

模块在首次加载后将缓存在内存缓冲区中。这意味对于相同模块的多次引用得到的都是同一个模块对象,对相同模块的多次引用不会引起模块内代码的多次执行(但是引用到同一个模块,可看成是代码空间,第一运行载入内存)。

5.2 require.resolve()查询完整模块名

5.3 require.cache对象
在Node.js中,定义了一个require.cache对象,该对象代码缓存了所有已被加载模块的缓冲区。可用console.log(require.cache)来查看缓冲区中的内容。require.cache对象具有一个“键名/键值”结构,键名为每个模块的完整文件名,键值为各模块对象,可通过键名访问某一个模块:

require.cache["模块文件名"];
console.log(require.cache[require.resolve('./tm.js')]);

可用delete关键字删除缓存区中的缓存的某个模块对象。

delete require.cache[require.resolve('./tm.js')];

6 __filename 与 __dirname变量
用于获取当前模块文件名的__filename变量与用于获取当前目录名的__dirname变量。

7 EventEmitter类
在Node.js的用于实现各种事件处理的event模块中,定义了一个EventEmitter类。所有可能触发事件的对象都是一个继承了EventEmitter类的子类的实例对象(如果一个对象要可以触发事件,那么它应该是EventEmitter的子类,这样才能针对事件绑定函数)。

addListener(event, listener)	
on(event, listener)
once(event, listener)
removeListener(event, listener)
removeAllListeners([event])
setMaxListeners(n)
listeners(event)
emit(event, [arg1], [arg2])

多次调用on来对同一个事件绑定多个处理函数。默认,针对同一个事件,最多可以绑定10个事件处理函数。可以通过setMaxListeners方法修改最多可以绑定的事件处理函数数量:emitter.setMaxListeners(n)

Node.js Http实例

配置文件

module.exports = {
	server: '127.0.0.1',
	port: 9999,
	uas: [
		'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0',
		'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36',
		'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0',
		'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36',
		'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Win64; x64; Trident/4.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)',
		'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
		'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) DumpRenderTree/0.0.0.0 Safari/536.11',
		'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8) Presto/2.10.289 Version/12.00',
		'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
		'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36'
	]
}

以下是主文件:

var config = require('./config');

var http = require('http');
var https = require('https');
var querystring = require('querystring');
var url = require('url');
var zlib = require('zlib');
var cheerio = require('cheerio');

// 如果没有输出则list为空数组
var parseHtml = function(html, itemid) {
	var $ = cheerio.load(html);

	var trs = $("table:contains('Date of Purchase')").last().find("tr");

	var tr = trs.first();
	var hasVar = false;
	if(trs.first().find("th:contains('Variation')").length > 0) {
		hasVar = true;
	}

	var data = [];
	trs.each(function(){
		var row = $(this);
		var colmns = row.find("td");

		if(hasVar) {
			var variation = colmns.eq(2).text().replace(/^\s+|\s+$/,'');
			var price = colmns.eq(3).text().replace(/^\s+|\s+$/,'');
			var qty = colmns.eq(4).text().replace(/^\s+|\s+$/,'');
			var datetime = colmns.eq(5).text().replace(/^\s+|\s+$/,'');
		} else {
			var variation = '-';
			var price = colmns.eq(2).text().replace(/^\s+|\s+$/,'');
			var qty = colmns.eq(3).text().replace(/^\s+|\s+$/,'');
			var datetime = colmns.eq(4).text().replace(/^\s+|\s+$/,'');
		}
        if(!price || (price === 'Price')) {
            return true;
        }
		//console.log(variation + "|" + price.replace(/\s+/, ' ')+"|"+qty+"|"+datetime);
		data.push({'variation': variation, 'price': price, 'qty': qty, 'date_purchase': datetime});
	});

	return {'itemid': itemid, 'list': data};
};

var showMem = function () {
        var mem = process.memoryUsage();
        var format = function (bytes) {
            return (bytes / 1024 / 1024).toFixed(2) + ' MB';
        };

        console.log('Process: heapTotal ' + format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss));
        console.log('-----------------------------------------------------------');
};

var server = http.createServer(function (req, res) {
	//showMem();

	/*
	这里的req是一个IncomingMessage对象,实现了Readable Stream接口(https://nodejs.org/dist/latest-v4.x/docs/api/stream.html#stream_class_stream_readable)

	对于客户端请求服务端,客户端的请求进来的数据是IncomingMessage,在服务端对外发起请求(组装数据),远程的响应类似于远程对服务端发起请求,那么这些响应信息就是一个IncomingMessage。

	从客户端读请求数据,还是从服务端读响应数据,都封装为对Readable Stream的读取。

	console.log(req.headers);
	{ 
		host: '127.0.0.1:9999',
		connection: 'keep-alive',
		'cache-control': 'max-age=0',
		accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*\/*;q=0.8',
		'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/
		'accept-encoding': 'gzip,deflate,sdch',
		'accept-language': 'zh-CN,zh;q=0.8,en;q=0.6' 
	}
	console.log(req.httpVersion);		// http版本
	console.log(req.method);			// 请求方法,GET POST PUT
	console.log(req.rawHeaders);		// 原始头,headers中是去掉了重复头,这里是原始头
	console.log(req.rawTrailers);
	console.log(req.statusCode);		// http.ClientRequest.
	console.log(req.statusMessage);		// http.ClientRequest.
	console.log(req.url);				// http.Server.
	*/

	// 第二参数解析查询字符串
	var ur = url.parse(req.url, true);
	if(typeof ur.query.u !== 'undefined') {
		ur.query.u = req.url.replace(/[^=]+u\=/i, '');
	}
	/*
	{
	    protocol: null,
	    slashes: null,
	    auth: null,
	    host: null,
	    port: null,
	    hostname: null,
	    hash: null,
	    search: '?u=xxxx',
	    query: { u: 'xxxx' },
	    pathname: '/craw',
	    path: '/craw?u=xxxx',
	    href: '/craw?u=xxxx'
	}
	*/
	// 是否在线
    if(ur.pathname === '/ping') {
		res.write("online");
		res.end();
		return;
    }   

	// 只处理/craw
    if(ur.pathname !== '/craw') {
		res.statusCode = 404;
		res.statusMessage = 'Not found';
		res.end();
		return;
    }

	var ghost = '';
	var gpath = '/';
	var protocol = 'http';
	var itemid = '';
	// 传递了u参数,u=http://offer.xxx.com/ws/g.php?a=xxx
	if(typeof ur.query.u !== 'undefined') {
		var m = ur.query.u.match(/item=([0-9]+)/);
		if(typeof m[1] !== 'undefined') {
			itemid = m[1];
		} else {
			res.statusCode = 404;
			res.statusMessage = 'Not found';
			res.end();
			return;
		}

		var furl = url.parse(ur.query.u);

		if(furl.protocol && furl.host) {
			if(furl.protocol === 'https:') {
				protocol = 'https';
			}
			ghost = furl.host;
			gpath = ur.query.u;
		} else {
			res.statusCode = 404;
			res.statusMessage = 'Not found';
			res.end();
			return;
		}
	} else if(typeof ur.query.item !== 'undefined') {
		itemid = ur.query.item;
		var ghost = 'offer.ebay.com';
		var gpath = '/ws/eBayISAPI.dll?ViewBidsLogin&item='+ur.query.item+'&rt=nc&_trksid=p2047675.l2564';
	} else {
		res.statusCode = 404;
		res.statusMessage = 'Not found';
		res.end();
		return;
	}

	// 随机UA
	var ua = '';
	var uidx = Math.floor(Math.random() * config.uas.length + 1)-1;
	if((uidx >= 0) && (typeof config.uas[uidx] !== 'undefined')) {
		ua = config.uas[uidx];	
	}
	if(ua === '') {
		ua = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0';
	}
	//console.log(ua);

	// 发送POST时需要提交的数据
	//var postData = querystring.stringify({
	//	'msg' : 'xxxx'
	//});

	// 这里的options对应HTTP的请求行和请求头信息(请求的cookie是请求头的组成部分)
	// 请求体的设置是调用clientRequest对象的write方法(可写流)
	var options = {
			host: ghost,
			path: gpath,
			method: 'GET',
			headers:{
				'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
				'Accept-Encoding': 'gzip,deflate',
				'Accept-Language': 'en-US,en;q=0.8',
				'Cache-Control': 'max-age=0',
				'Connection': 'keep-alive',
				//'Referer': '',
				'User-Agent': ua
			}
	};
	var q = http;
	if(protocol === 'https') {
		options['port'] = 443;
		q = https;
	}

	//var html = '';
	// 内部对外请求
	// reqc是一个clientRequest对象,它是一个可写流
	// 而resc是一个IncomeMessage对象,它是一个可读流
	var reqc = q.request(options, function(resc){
		//resc.setEncoding('utf-8');
		var chunks = [];
		// 数据可能分块返回
		resc.on('data', function(data){
			//html += data;
			chunks.push(data);
		});

		// 数据返回结束
		// 发起请求时发送了gzip,deflate,服务器端返回压缩数据
		// 由于压缩,网络传输时间明显下降
		resc.on('end', function(){
			// 传递状态码,比如302,404等
			res.statusCode = resc.statusCode;
			var buffer = Buffer.concat(chunks);
			var encoding = resc.headers['content-encoding'];
			if (encoding == 'gzip') {
				zlib.gunzip(buffer, function(err, decoded) {
					if (!err) {
						var json = parseHtml(decoded.toString(), itemid);
						var rej = {"success": 1, "message": '', "data": json};
					} else {
						var rej = {"success": 0, "message": '', "data": {}};
					}
					res.setHeader('Content-Type', 'application/json;charset=UTF-8'); 
					res.write(JSON.stringify(rej));
					res.end();
				});
			} else if (encoding == 'deflate') {
				zlib.inflate(buffer, function(err, decoded) {
					if (!err) {
						var json = parseHtml(decoded.toString(), itemid);
						var rej = {"success": 1, "message": '', "data": json};
					} else {
						var rej = {"success": 0, "message": '', "data": {}};
					}
					res.setHeader('Content-Type', 'application/json;charset=UTF-8');
					res.write(JSON.stringify(rej));
					res.end();
				});
			} else {
				if (!err) {
					var json = parseHtml(decoded.toString(), itemid);
					var rej = {"success": 1, "message": '', "data": json};
				} else {
					var rej = {"success": 0, "message": '', "data": {}};
				}
				res.setHeader('Content-Type', 'application/json;charset=UTF-8');
				res.write(JSON.stringify(rej));
				res.end();
			}

			chunks = null;
		});
	});

	// 设置超时,当超时时执行回调,终止请求
	reqc.setTimeout(10000, function(t) {
		var timeout = (new Date()).toLocaleString() + " Timeout";
		console.log(timeout);

		// 触发error事件 -> socket hang up
		reqc.abort();
	});
	// 发生错误
	reqc.on('error', function(e) {
		var err = (new Date()).toLocaleString() + " Error: "+e.message;
		console.log(err);
		//res.write('error');
		res.statusCode = 599;
		res.statusMessage = err;
		res.end();
	});
	// 发送数据
	//reqc.write(postData);

	//
	reqc.end();

}).listen(config.port, config.server);

/////////////////////////////////////
server.timeout = 20000;
server.on('listening', function() {
    var msg = (new Date()).toLocaleString() + " Server Listening";
	console.log(msg);
});
server.on('close', function() {
	var msg = (new Date()).toLocaleString() + " Server Closed";
	console.log(msg);
});

这个例子中,HTTP模块多数功能多已经使用到。类比其它语言,并不简单多少(甚至更加复杂),JS语法也不见得有何优势。实际上,之所以选择Node.js来做Http代理,原因只有一个,部署非常简单。

Node.js 操作MySQL

var mysql = require("mysql");

var cnn = mysql.createConnection({
    host:'127.0.0.1',
	port:3306,
	database:'test',
	user:'root',
	password:''
});

//连接池
var pool = mysql.createPool({
    host:'127.0.0.1',
	port:3306,
	database:'test',
	user:'root',
	password:''
});
var pcnn = pool.getConnection((err, pcnn) => {
	console.log("使用连接池");
	var query = pcnn.query("SELECT * FROM datatables_demo");
	query.on('error', (err) => {
		console.log("数据读取错误");

		// 释放连接回到池
		pcnn.release();
	}).on('fields', (fields) => {
		//console.log(fields);
		fields.forEach((field) => {
			console.log(field.name);
		});
	}).on('result', (row) => {
		pcnn.pause();
		console.log(row.first_name+" "+row.last_name+" "+row.age);
		pcnn.resume();
	}).on('end', () => {
		console.log("读取完毕");
		// 释放连接回到池
		pcnn.release();
	});	
	console.log("使用连接池结束");
});

var pcnn = pool.getConnection((err, pcnn) => {
	console.log("使用连接池");
	var query = pcnn.query("SELECT * FROM datatables_demo");
	query.on('error', (err) => {
		console.log("数据读取错误");

		// 释放连接回到池
		pcnn.release();
	}).on('fields', (fields) => {
		//console.log(fields);
		fields.forEach((field) => {
			console.log(field.name);
		});
	}).on('result', (row) => {
		pcnn.pause();
		console.log(row.first_name+" "+row.last_name+" "+row.age);
		pcnn.resume();
	}).on('end', () => {
		console.log("读取完毕");
		// 释放连接回到池
		pcnn.release();
	});	
	console.log("使用连接池结束");
});

// 关闭整个池
//pool.end((err) => {
//	console.log("关闭连接池");
//});

///////////////////////////////////////////////////////////
cnn.connect(function(err){
	if(err) {
		console.log("数据库连接错误");
	} else {
		console.log("数据库连接成功");
	}	
});

// 连接丢失重连
cnn.on('error', function(err){
	if(err.code === 'PROTOCOL_CONNECTION_LOST') {
		console.log('连接丢失');
		setTimeout(function(){
			cnn.connetct((err)=>{
				console.log("数据库重连");
			});
		}, 10000);
	}
});

//cnn.query("INSERT INTO posts SET ?", {id:1, title:'Hello MySQL'});
//cnn.query("UPDATE posts SET title = :title", {title:'Hello MySQL'});
//cnn.query("SELECT * FROM users WHERE ID = ?", [userId]);

// 以流的方法读取数据,以行为单位
// 正确姿势
var query = cnn.query("SELECT * FROM datatables_demo");
query.on('error', (err) => {
	console.log("数据读取错误");
}).on('fields', (fields) => {
	//console.log(fields);
	fields.forEach((field) => {
		console.log(field.name);
	});
}).on('result', (row) => {
	cnn.pause();
	console.log(row.first_name+" "+row.last_name+" "+row.age);
	cnn.resume();
}).on('end', () => {
	console.log("读取完毕");
});

// 一次读整个结果,然后对结果循环
cnn.query("SELECT * FROM datatables_demo LIMIT 20", function(err, result){
	//console.log(result);
	for(row in result) {
		//console.log(result[row]);
		console.log(result[row].first_name+" "+result[row].last_name+" "+result[row].age);
	}
	console.log("------------------------------------------");
	result.forEach((row) => {
		 console.log(row.first_name+" "+row.last_name+" "+row.age);
	});
});


cnn.end(function(err){
	console.log("数据库关闭。");
});

Node.js HTTP服务器

> 创建HTTP服务器

var http = require('http');
var server = http.createServer(function(request, reponse){

}).listen('9999','127.0.0.1', function(){
	console.log('开始监听');
});

/////
var server = http.createServer();
server.on('request', function(request, reponse){

});
// listen方法第一参数为端口号,0表示随机端口
// 第三参数默认是511表示最大连接数量
server.listen('9999','127.0.0.1', function(){
	console.log('开始监听');
});
//listen方法将触发listening事件,如果不指定回调
server.on('listening', function(){
	console.log('开始监听');
});

// 显式关闭HTTP服务器,触发close事件,可以监听它做善后处理
server.close()
server.on('close', function(){});

// 如果端口冲突,将触发server的error事件
server.on('error', function(e){
	if(e.code == 'EADDRINUSE') {
	}
});
// 当有客户端连接进来时触发server的connection对象
// 回调参数为服务器端用于监听客户端请求的socket端口对象
server.on('connection', function(socket){
});

方法createServer()的回调中第一参数是http.IncomingMessage对象,此处代表一个客户端请求,第二个参数值为一个http.ServerResponse对象,代表一个服务器端响应对象。

方法createServer方法返回被创建的服务器对象,如果不在createServer方法中使用参数,也可以通过监听该方法创建的服务器对象的request事件(这种方式比较直观)

可以使用HTTP服务器的setTimeout方法来设置服务器的超时时间。当该超时时间超过之后,客户端不可继续利用本次与HTTP服务器建立的连接,下次向该HTTP服务器发出请求时必须重新建立连接

// 设置超时并给出超时时需要执行的回调
server.setTimeout(1000, function(socket) {
});
// 超时时会触发timeout事件 
server.on('timeout', function(socket){

});
// 也可以直接操作server的timeout属性
server.timeout = 1000;
console.log(server.timeout);

HTTP服务器拥有一个timeout属性,属性值为整数值,单位为毫秒,可用于查看或修改服务器的超时时间。

> 获取客户端请求信息
http.IncomingMessage用于读取客户端请求流中的数据,当从客户端请求流中读取到新的数据时触发data事件,当读取完客户端请求流中的数据时触发end事件。当该对象被用于读取客户端请求流中的数据时,有如下属性:

.method		GET POST PUT DELETE
.url		带请求查询的URL
.headers	客户端发送的请求头
.httpVersion
.trailers	客户端附加的HTTP头(end事件触发后才能读取)
.socket		socket对象

例子:

var http = require('http');

var server = http.createServer(function (req, res) {
	 //console.log(req);
	 console.log(req.method);
	 console.log(req.url);
	 console.log(req.headers);
	 // data触发主要用来接收客户端请求体的数据,比如POST的数据
	 req.on('data', function(data) {
		console.log(decodeURIComponent(data));
	 });

	 req.on('end', function(){
		console.log("客户端数据已经全部接收");
	 });
}).listen(9999, '127.0.0.1');

server.on('connection',function(socket){
	console.log('客户端连接已建立');
});

> 转换URL字符串与查询字符串
在Node.js中,提供了一个url模块与一个queryString模块,分别用来转换完整URL字符串与URL中的查询字符串。

querystring.parse('userName=Lulingniu&age=40&sex=male','&','=');
querystring.stringify({username:'vfeelit',age:'18',sex:'male'})
// 第二参数表示是否解析查询字符串为一个对象
// 内部实际使用querystring对象
url.parse('http:// user:pass@host.com:8080/users/user.php?userName=vv&age=18&sex=male#name1')
url.format(urlObj);
url.resolve(from, to); //可以简单看做是字符串替换

> 发送服务器响应
利用http.ServerResponse对象的writeHead方法来发送响应头

var http = require('http');
var server = http.createServer(function(req, res){
	res.writeHead(200, {'Content-Type':'text/plain', 'Access-Control-Allow-Origin':'http://127.0.0.1'});
	//也可以使用setHeader设置响应头 
	res.setHeader("Set-Cookie",["type=xx","language=xxxx"]);
	res.write("Hello");
	res.end();
});

使用了http.ServerResponse对象的setHeader方法设置响应头信息之后,可以使用http.ServerResponse对象的getHeader方法获取响应头中的某个字段值。使用http.ServerResponse对象的removeHeader方法删除一个响应字段。http.ServerResponse对象具有一个headersSent属性,当响应头已发送时,该属性值为true,当响应头未发送时,属性值为false。

HTTP服务器自动将服务器端当前时间作为响应头中的Date字段值发送给客户端。可以通过将http.ServerResponse对象的sendDate属性值设置为false的方法在响应头中删除Date字段。

可以通过http.ServerResponse对象的statusCode属性值获取或设置HTTP服务器返回的状态码。

可以使用http.ServerResponse对象的setTimeout方法设置响应超时时间。如果在指定时间内服务器没有做出响应,则响应超时,同时触发http.ServerResponse对象的timeout事件。

在http.ServerResponse对象的end方法被调用之前,如果连接中断,将触发http.ServerResponse对象的close事件。

> HTTP客户端
request方法返回一个http.ClientRequest对象,代表一个客户端请求。

当客户端请求获取到服务器响应流时,触发http.ClientRequest对象的response事件,可以不在request方法中使用callback参数,而是通过对http.ClientRequest对象的response事件进行监听并指定事件回调函数的方法来指定当获取到其他网站返回的响应流时执行的处理。

var http = require('http');
var option = {
	hostname: 'blog.ifeeline.com',
	port:80,
	path:'/',
	method:'GET'
};
//回调在reponse事件触发时执行
var req = http.request(options, function(res){
});
// http.request()方法返回一个http.clientRequest对象,调用end()方法将发起请求
// 数据返回后触发reponse事件,所以可以如下
req.on('reponse', function(res){
});
// res对象是一个http.IncomingMessage对象,表示发起请求的响应

// 可以调用http.clientRequest对象的write来发送数据,比如上次文件
// 这些内容实际就是HTTP请求中的请求体
req.write(chunk,[encoding])
// 必须调用end方法来结束请求(实际是发出请求)
// end中的参数(可空),类似最后执行一次write方法
req.end([chunk],[encoding])

使用http.ClientRequest对象的abort方法终止本次请求,如果在向目标网站请求数据的过程中发生了错误,将触发http.ClientRequest对象的error事件。在建立连接的过程中,当为该连接分配端口时,触发http.ClientRequest对象的socket事件。

如果使用GET方式向其他网站请求数据,也可以使用http模块中的get方法。

Node.js 操作文件系统

> 文件读写
完整读取一个文件时,可用readFile方法或readFileSync:

fs.readFile(filename, [options], callback)

var fs = require('fs');
fs.readFile('./t.txt', function(err, data){
	if(err) console.log("读文件发送错误")
	slse console.log(data);
});

注意,如果没有指定options,那么data就是二进制数据(Buffer对象,可调用toString()方法获取字符串),options设置:

flag		r, r+, w, w+, a, a+
encoding	utf8, ascii, base64

在options参数值中,可使用encoding属性指定使用何种编码格式来读取该文件,过程就是读取文件,然后转换成指定编码后存入Buffer对象,否则就是直接读取文件的原始二进制内容。如果在readFile函数中使用options参数并将encoding属性值指定为某种编码格式,则回调函数中的第二个参数值返回将文件内容根据指定编码格式进行编码后的字符串。

在使用同步方式读取文件时,使用readFileSync方法:

var data=fs.readFileSync(filename, [options])

在完整写入一个文件时,我们可以使用fs模块中的writeFile方法或writeFileSync方法:

fs.writeFile(filename,data,[options],callback)
fs.writeFileSync(filename,data, [options]);

var fs = require('fs');
fs.writeFile('./t.txt', "数据", function(err){
	if(err) console.log("写文件失败");
	else console.log("写文件成功");
});

// 向文件追加数据
var fs = require('fs');
fs.writeFile('./t.txt', "追加数据", {flag:'a'}, function(err){
	if(err) console.log("写文件失败");
	else console.log("写文件成功");
});

// base64读入图片,base64解码数据写入图片
// 读入的data是一个Buffer对象,toString后就是base64字符串
// 然后写文件时,指定base64解码字符串(得到二进制数据)
var fs=require('fs');
fs.readFile('./a.gif','base64',function(err,data){
	fs.writeFile('./b.gif',data.toString(), "base64", function(err){});
});

产生data可以是字符串也可以是一个Buffer对象。options设置:

flat		默认为w
mode		文件权限,默认0666
encoding	指定使用何种编码来写入文件,data是Buffer时被忽略

将一个字符串或一个缓存区中的数据追加到一个文件底部时,我们可以使用fs模块中的appendFile方法或appendFileSync方法。

>从指定位置开始读写文件

fs.open(filename, flags,[mode],callback)
fs.openSync(filename, flags,[mode])

//回调函数第二参数为打开的文件描述符
var fs=require('fs');
fs.open('./t.txt', 'r', function(err,fd) {

});

// 取到文件描述符后,使用read读取
fs.read(fd, buffer, offset, length, position, callback)

> 创建与读取目录

// mode默认为0777
fs.mkdir(path, [mode], callback);

// 读取目录,files是一个数组
var fs = require('fs');
fs.readdir('D:/', function(err, files){
    console.log(files);
});

> 查看文件或目录的信息

// 当查看连接文件时,必须使用lstat
fs.stat(path,callback);
fs.lstat(path, callback);

// stats是一个fs.Stats对象,有一系列方法和属性
// 比如是isFile isDirectory
fs.stat('./t.txt', function(err, stats){

});

在使用open方法或openSync方法打开文件并返回文件描述符后,可以使用fs模块中的fstat方法查询被打开的文件信息。

> 检查文件或目录是否存在

fs.exists(path, function(exists){});

> 获取文件或目录的绝对路径

fs.realpath(path, function(err, resolvedPath){});

> 修改文件访问时间及修改时间

fs.utime(path, atime, mtime, function(err){});
fs.utime(path, new Date(), new Date(), function(err){});

在使用open方法或openSync方法打开文件并返回文件描述符后,可以使用fs模块中的futimes方法修改文件的访问时间或修改时间。

> 修改文件或目录的读写权限

fs.chmod('./t.txt', 06000, function(err){});

> 文件或目录的其它操作

fs.rename(oldPath,newPath,callback)
fs.link(srcpath,dstpath,callback)
fs.unlink(path,callback)
fs.symlink(srcpath,dstpath,[type],callback)
fs.truncate(filename,len,callback)
fs.rmdir(path,callback)
fs.watchFile('./message.txt',function(curr, prev) {})

Node.js Buffer对象

> Buffer对象创建
在Node.js中,Buffer类是一个可以在任何模块中被利用的全局类,不需要为该类的使用而加载任何模块。

//1 
//size表示多少字节
var buf = new Buffer(size)		

// 2 数组值
var buf = new Buffer([1,2,3]);

// 3 用指定编码的字符串填充,编码默认utf8,一般有ascii base64 hex
var buf = new Buffer(str, [encoding]);

//用value进行填充,指定段
buf.fill(value, [offset], [end]);	

被创建的Buffer对象拥有一个length属性,属性值为缓存区大小。

>字符串长度与缓存区的长度
字符串长度以单个字符作为单位(可能占用多个字节),缓存区以字节为单位。可以使用下标语法取出字符串或缓存区中的内容,比如str[2],buf[2],一个是取字符,一个是取字节。

字符串对象有可用于搜索字符串的indexOf、match、search方法,但是Buffer对象没有,它有一个slice方法(是指字符串和Buffer都有),不过它取的数据是字节,并且不是值拷贝(引用原来数据),所以如果对slice的结果进行修改,对应的Buffer对象的值也会被修改。

> Buffer对象与字符串对象之间的相互转换
可用buf.toString([encoding],[start],[end])来转换为指定编码(默认utf8)的字符串。

要向已经创建的Buffer对象中写入字符串,这时可以使用该Buffer对象的write方法:

buf.write(string, [offset], [length], [encodeing]);

注意和fill方法的区别。

> Buffer类的类方法

// 判断是否是Buffer对象
Buffer.isBuffer(obj);

//使用byteLength方法计算一个指定字符串的字节数
Buffer.byteLength(string, [encode]);

//用于将几个Buffer对象结合创建为一个新的Buffer对象
Buffer.concat(list,[totalLength])

//检测一个字符串是否为一个有效的编码格式字符串
Buffer.isEncoding(encoding)