07月10, 2016

node学习笔记(2)

node学习笔记第二篇。

1.stream

首先要解释一下为什么不使用readFile/readFileSync,理由是它会一次性把文件内容全部读入内存,当文件过于庞大时,比如音频、视频文件,动辄几个GB大小,如果使用这种方法,很容易使内存“爆仓”。理想的方法应该是读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完成,这里就需要用到流的概念。

另外比较有名的gulp构建工具的核心就是流。

  • 流的概念

a. 流是一组有序的,有起点和终点的字节数据传输手段

b. 不关心文件的整体内容,只关注是否从文件中 读 到了数据,以及读到数据之后的处理

c. 流是一个抽象接口,被Node中的很多对象所实现。比如对一个 HTTP 服务器的请求对象request是一个流,stdout也是一个流。

d. 流是可读、可写或兼具两者的。所有流都是 EventEmitter 的实例

  • 可读流的各种事件
var fs = require("fs");

var readStream = fs.createReadStream("myfile.txt");
readStream
  .on("data", function (chunk) {
    console.log("emit data")
    console.log(chunk.toString("utf8"))
  })
  .on("end", function () {
    console.log("emit end");
  })
  .on("close", function () {
    console.log("emit close");
  })
  .on("readable", function(){  //监听 readable 会使数据从底层读到系统缓存区,读到数据后或者排空后如果再读到数据,会触发 readable 事件
     console.log("emit readable")
  })
  .on("error", function(e){
     console.log("emit error")
  })
  • 简单地实现文件复制
var fs = require("fs");
var readStream = fs.createReadStream("/path/to/source");
var writeStream = fs.createWriteStream("/path/to/dest");

readStream.on("data", function(chunk) { // 当有数据流出时,写入数据
    writeStream.write(chunk);
});

readStream.on("end", function() { // 当没有数据时,关闭数据流
    writeStream.end();
});

一般来说是OK了,但是会有一些问题,如果写入的速度跟不上读取的速度,有可能导致数据丢失。正常的情况应该是,写完一段,再读取下一段,如果没有写完的话,就让读取流先暂停,等写完再继续。

var fs = require("fs");
var readStream = fs.createReadStream("/path/to/source");
var writeStream = fs.createWriteStream("/path/to/dest");

readStream.on("data", function(chunk) { // 当有数据流出时,写入数据
    if (writeStream.write(chunk) === false) { // 如果没有写完,暂停读取流
        readStream.pause();
    }
});

writeStream.on("drain", function() { // 写完后,继续读取
    readStream.resume();
});

readStream.on("end", function() { // 当没有数据时,关闭数据流
    writeStream.end();
});

关于drain这事事件何时触发:当要写的数据的长度大于 highWaterMark (字面理解:高水位线,默认是16KB)的时候,那么 Stream.write 就会返回 false,也就会触发 drain 事件了。更多请参考:探究 Node.js 中的 drain 事件

更完整的文件复制,请参考这里

  • pipe

像gulp的实现就是通过pipe的流机制。简单代码如下:

var fs = require("fs");
var src = fs.createReadStream("a.txt");
var dest = fs.createWriteStream("b.txt");
src.pipe(dest);

在http中,我们简单地写出一个文件(如下载功能等),不再需要说使用fs.readFile的形式,只需要写成如下的写法即可:

var http = require("http");
var fs = require("fs");

http.createServer(function(req, res){
    fs.createReadStream("./app-group-list.png").pipe(res);
})
.listen(8090);

当我们访问localhost:8090时就能看到app-group-list.png这张图片。

需要注意的是createReadStream接收的文件路径不能是远程地址(即http或者https的),非要使用的话,可以用request这个npm模块:

request("http://google.com/doodle.png").pipe(res)
  • stream种类
使用情景 需要重写的方法
只读 Readable _read
只写 Writable _write
双工 Duplex _read, _write
操作被写入数据,然后读出结果 Transform _transform, _flush

一般用的最多是可读流、可写流和传输流。

我们来看几个例子吧:

var Readable = require("stream").Readable;
var Writable = require("stream").Writable;

var readStream = new Readable();
var writStream = new Writable();

readStream._read = function(){
    this.push("here ");
    this.push("is ");
    this.push("something ");
    this.push(null); //不加这一句,它不断地进行读取的操作
}

writStream._write = function(chunk, encode, cb){
    console.log(chunk.toString());
    cb()
}

readStream.pipe(writStream);

下面这张图很好地诠释了上面的整个过程:

输入输出流

下面再来看一个例子,它自定义实现了read transform write的整个过程

var stream = require("stream");
var util = require("util");

function ReadStream(){
    stream.Readable.call(this);
}

util.inherits(ReadStream, stream.Readable);

ReadStream.prototype._read = function() {
    this.push("here ");
    this.push("is ");
    this.push("something ");
    this.push(null);
}

function WritStream(){
    stream.Writable.call(this);
}

util.inherits(WritStream, stream.Writable);

WritStream.prototype._write = function(chunk, encode, cb){
    console.log(chunk.toString());
    cb();
}

function TransformStream(){
    stream.Transform.call(this);
}

util.inherits(TransformStream, stream.Transform);

TransformStream.prototype._transform = function(chunk, encode, cb){
    this.push(chunk);
    cb();
}

TransformStream.prototype._flush = function(cb){
    this.push("add something");
    cb();
}

var rs = new ReadStream();
var ws = new WritStream();
var ts = new TransformStream();

rs.pipe(ts).pipe(ws);

有了上面的例子,对于像gulp的插件如何实现,有没有点豁然开朗的感觉:

fs.createReadStream("./1.txt").pipe(ts).pipe(ws);

中间自己实现一个transform流,在得到文件内容之后,对文件内容进行处理,然后输出就行了。

相关阅读:

2.es6

  • jspm

在学习的过程中发现了这货,本想深入了解一下的。

然后又在知乎上看到如何评价jspm.io?,觉得有些道理,与其学这个,还不如学webpack全家桶。

不过知道一下是干嘛的,有什么用,也算增长一些见识吧。

  • es6的基本常见类型如下

a. 块级作用域(let)

b. 恒量(const)

c. 解构(数组、对象,个人觉得非常重要)

d. 模板字符串

e. 字符串的新方法(startsWith、endsWith、includes)

f. 函数参数默认值

g.展示操作符(...)

h. 剩余操作符(同上)

i. 箭头函数

j. Object的新方法(is、assign)

3.tcp

TCP是一种传输层协议,它可以让你将数据从一台计算机完整有序地传输到另外一台计算机。

我们先来一个简单的telnet连接到web的例子。

web服务器(node):

var http = require("http");

http.createServer(function(req, res){
    res.writeHead(200, {"Content-Type": "text/html"});
    res.end("hello world");
}).listen(8090, "127.0.0.1");

打开命令行,输入: telnet 127.0.0.1 8090

输入:GET / HTTP/1.1,回车两次,即可看到下面的结果

1.1是表示HTTP的协议,实际输入2.0也可以的,注意HTTP必须是大写,不然得不到结果。

退出telnet,在mac下的命令是alt + [

在实际中,一般是会使用node的net模块来实现tcp的功能

//服务器端
var net = require("net");

const PORT = 18001;
const HOST = "127.0.0.1";

var clientHandler =  function(socket){
   console.log("someone connected"); //当有客户端连接时,就会打印
   socket.on("data",function dataHandler(data){
      console.log(socket.remoteAddress,socket.remotePort,"send",data.toString()); //从客户端接收信息
      socket.write("receive data\n"); //向客户端写出信息
   })
   socket.on("close",function(){
      console.log(socket.remoteAddress,socket.remotePort,"disconnected");
   })
}

var app = net.createServer(clientHandler);

app.listen(PORT, HOST);

console.log("tcp server running on tcp://", HOST, ": ", PORT);

简单来说,服务端就是创建一个server,然后监听socket的data和close等事件,这些事件的触发点是当有客户端连接,并向服务端write一些信息。

//客户端
var net = require("net");

const HOST = "127.0.0.1";
const PORT = 18001;

var tcpClient = net.Socket();

tcpClient.connect(PORT,HOST,function(){
   console.log("connect success");
   tcpClient.write("this is tcp client by Node.js"); //write时会触发服务器端的data事件
})

tcpClient.on("data",function(data){
   console.log("received: ",data.toString());
})

4.udp

Node.js也提供了UDP编程的能力,相关类库在“dgram”模块里。

与TCP不同,UDP是无连接的,不保障数据的可靠性,不过它的编程更为简单,有时候我们也需要它。比如做APP的统计或者日志或者流媒体,很多流媒体协议都会用到UDP,网上一搜一大堆。

使用UDP,如果你要发送数据,只需要知道对方的主机名(地址)和端口号,扔一消息过去即可。至于对方收不收得到,听天由命了。这就是数据报服务,类似快递或邮件。

本文链接:www.my-fe.pub/post/node-study-part2.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。