理解网络编程-2

该文章阅读须要5分钟,更多文章请点击本人博客halu886node

前言

Node是一个面向网络而生的平台,具备事件驱动,无阻塞,单线程等特性。具备良好的可伸缩性,适合在分布式式网络中扮演各式各样的角色。同时提供的网络基础API很是贴合网络,很是适合基于基础API构建灵活的网络服务。算法

利用Node能够很容易的搭建Web服务。像其余语言都须要Web服务器做为容器。如ASP,ASP.NET须要IIS做为服务器。PHP须要搭建Apache或Nginx环境等。JSP须要Tomcat等等。可是对于Node来讲,只须要仅仅几行代码就能够构建服务器,不须要额外的容器。shell

Node提供了net,dgram,http,https这4个模块,分别用来处理TCP,UDP,HTTP,HTTPS,适用于服务端和客户端。浏览器

构建TCP服务

TCP在网络中十分常见,目前大部分应用都是构建在TCP上的。缓存

TCP

TCP全称是传输控制协议,在OSI模型(分为七层,物理层,数据链路层,网络层,传输层,会话层,表现层,应用层)位于传输层协议,典型的HTTP,SMPT,IMAP等就是构建在TCP上。bash

1

TCP是典型的面向链接的协议,特征是在传输以前须要3次握手造成会话。服务器

2

只有会话造成,客户端和服务端才能开始传输数据。建立过程当中,服务端和客户端分别提供一个的套接字共同造成一个连接。服务端和客户端经过套接字造成二者连接的操做。网络

建立TCP服务器端

如今咱们来建立一个TCP服务端接受网络请求。并发

var net = require('net');

var server = net.createServer(function(socket){
     socket.on('data',function(data){
          socket.write("你好");
     })

     socket.on('end',function(){
          console.log('链接断开');
     })
     socket.write("hello world!\n");
});

server.listen(8124function(){
     console.log('server bound');
})
复制代码

咱们经过net.createServer()建立一个TCP服务器,server.listen表示connection的侦听器,也能够用以下方式:app

var server = net.createServer();

server.on('connection',function(socket){
     // 新的链接
})

server.listen(8124);
复制代码

咱们能够用telnet做为客户端和刚刚建立的简易服务器进行会话交流。

$ telnet 127.0.0.1 8124
Trying 127.0.0.1
Connected to localhost.
Escape character is '^]'.
hello world
hi
复制代码

除了端口外,咱们还能够对Domain Socket进行监听。

server.listen('/tmp/echo.sock');
复制代码

经过nc工具进行上述的构建的TCP服务的会话测试。

$ nc -U /tmp/echo.sock
hello world
hi
复制代码

也能经过net模块自行构建客户端进行会话。

var net = require('net')

var client = net.connect({port:8124},function(){
     console.log('client connected');
     client.write('world!\r\n');
});

client.on('data',function(data){
     console.log(data.toString());
     client.end();
})

client.on('end',function(data){
     console.log('client disconnected');
})
复制代码

将以上客户端存为client.js,执行以下

$ node client.js
client connected
hello world!

你好
client disconnected
复制代码

其结果与使用nc和Telnet的结果并没有区别。若是是Domain Socket,填写path便可。

var client = net.nonnect({path:'/tmp/echo.sock'});
复制代码

TCP服务的事件

以上例子中,事件分为服务器事件和链接事件

服务器事件

对于经过net.createServer()建立的服务器而言,它是一个EventEmitter实例,它的自定义事件以下几种。

  • listening:再调用server.listen()绑定端口或者Domain Socket后触发。
  • connect:每一个客户端链接到服务端时触发,简洁用法能够用net.createServer(),最后一个参数传递。
  • close:当服务端关闭时触发,当调用sever.close()后,中止接受新的客户端套接字链接,同时等待链接断开后触发该事件。
  • error:当服务器发生异常,则会触发这个事件。例如侦听一个正在使用中的端口,则会触发这个事件。若是没有侦听error事件,服务器则会抛出异常。

链接事件

服务器能够和多个客户端保持链接,对于每一个链接来讲都存在一个可读可写Stream流用于服务端到客户端的通讯。能够侦听data事件进行读取另外一端的数据,经过write()像另外一端发送数据。

  • data:当一端经过write发送数据,另外一端则会触发data事件。接受到数据则是write传入的数据。
  • end:当任何一段发送Fin数据包,则会触发这个事件。
  • connect:用于客户端链接服务端,当套接字与服务端链接成功时触发。
  • drain:当任意一端调用write()时,当前这端触发该事件。
  • error:异常发生时,触发该事件。
  • close:当套接字彻底关闭时,触发该事件。
  • timeout:当链接闲置了一段时间后,触发该事件。

另外,TCP套接字是一个可读可写的Stream流,能够经过pipe()实现管道操做。

var net = require('net');
var server = net.createServe(function(socket){
     socket.write('Echo server\r\n');
     socket.pipe(socket);
})

server.listen(1337,'127.0.0.1');
复制代码

不过,TCP在传输小数据包时存在一个优化策略。Nagle算法,若是不存在这个算法的话,网络传输中全是相同的小数据包,十分浪费网络资源。这个算法是将数据缓存在一个缓冲区到必定数据量或必定时间量时再一块儿发出去,因此小数据包会被Nagle算法合并,可以起到节省宽带的做用。不过这样的反作用则是有可能数据包会被延迟发送。

在Node中,TCP默认采用了启用Nagle算法,能够经过Socket.setNotDaley(true)去掉Nagle算法。使得write()后当即将数据发出到网络中。

不过,经过write()写入数据后会触发data事件,关闭Nagle算法后,也不意味这每次执行write方法都会触发data事件。另外一端将多个小数据包合并,而后只触发一次data事件。

构建UDP服务

UDP称为用户数据包协议,和TCP同属于网络传输层。和TCP最大的不一样是UDP不是面向链接。TCP链接一旦创建,会话都是基于链接完成,而且每一个不一样的链接都须要不一样的套接字。但在UDP中,一个套接字能够和多个UPD服务端会话。虽然提供的是面向事务的简单不可靠传输服务,在网络状况差的状况下丢包严重,可是无需链接,资源消耗低,处理快速且灵活。因此经常应用于丢一两个包也不影响的场景,例如音频,视频。UDP目前引用很是普遍,CDN服务就是基于UDP实现的。

建立UPD套接字

建立UPD套接字十分简单,UDP套接字一旦创建,既能够做为客户端发送数据,也能够做为服务端接收数据。

var dgram = require('dgram');
var socket = dgram.createSocket('udp4');
复制代码

建立UDP服务端

若想让UPD套接字接受网络消息,只须要用dgram.bind(port,address)对网卡和端口进行绑定便可。

var dgram = require("dgram");

var server = dgram.createSocket("udp4");

server.on("message",function(msg,rinfo){
     console.log(msg +" from " + rinfo.address + ":" +rinfo.port);
})

server.on("listening",function(){
     var address = server.address();
     console.log(address.address+ ":" + address.port);
})

server.bind(41234);
复制代码

建立UDP客户端

建立一个UDP客户端与客户端通讯。

var dgram = require('dgram');

var message = new Buffer('hello world');

var client = dgram.createSocket('udp4');

client.send(message,0,message.length,41234,"localhost",function(err,bytes){
     client.close();
})
复制代码

保存client.js并执行。

$ node server.js
server listening 0.0.0.0:41234
server got:hello world from 127.0.0.1:58682
复制代码

当套接字用在客户端时,使用send()方法发送消息到网络中。send()方法参数以下:

socket.send(buf,offset,length,port,address,[callback])
复制代码

这些参数分别为要发送的Buffer,偏移位,长度,端口,地址以及回调。它与TCP相比,参数相对复杂一点,可是能够随意发送到网络中服务端,可是若是TCP须要从新发送数据另外一个服务端的话,则须要经过套接字构造一个新的链接了。

UDP套接字事件

UDP套接字使用起来相对容易一点,只是一个EventEmitter的实例,而非Steam的实例。

  • message:当UDP套接字侦听网口端口后,接收到消息后触发该事件,携带的数据为Buffer数据和和一个远程地址信息。
  • listening:当UDP套接字开始侦听时触发该事件。
  • close:调用close时触发该事件,不在触发message事件,从新侦听地址和端口则可从新触发。
  • error:当异常发生时触发该事件,若是不侦听该事件,则异常抛出,使进程退出。

以上知识点均来自<<深刻浅出Node.js>>,更多细节建议阅读书籍:-)

该文章阅读须要5分钟,更多文章请点击本人博客halu886

构建HTTP服务

TCP和UDP都属于传输层的协议,若是须要构想高性能的网络传输,就应该从传输层入手。 若是对于经典的业务场景,应用层协议远远够了,例如http和smtp等。

用Node构建一个http服务经过几行代码就够了。

var http = require('http');
http.createServer(function(req,res){
    res.writeHead(200,{'Content-Type':'text/plain'});
    res.end('Hello World\n');
}).listen(1337,'127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
复制代码

虽然只能返回hello world,可是维持的并发量和QPS都不容小觑的。

Http

初识HTTP

HTTP称为超文本传输协议,写做HyperText Transfer Protocol。构建在TCP协议上。属于应用层协议。 用于服务端和客户端通讯,著名的B/S模式,

HTTP的发展是W3C和IETF的合做结果,他们最终发布了一系列RFC标准,目前最著名的HTTP标准就是RFC2612。

Http报文

咱们经过curl调用上述代码启动的本地http服务器的接口,查看http报文。

$ curl -v http://127.0.0.1:1337
* About to connect() to 127.0.0.1 port 1337 (#0)
* Trying 127.0.0.1...
* connected
* Connected to 127.0.0.1(127.0.0.1) port 1337 (#0)
> Get / HTTP/1.1
> Uesr-Agent: curl/7.24.0(x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.0r zlib/1.2.5
> Host:127.0.0.1:1337
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat,06 Apr 2013 08:01:44 GMT
< Connection:keep-alive
< Transfer-Encoding: chunked
<
Hello World
* Connection #0 to host 127.0.0.1 left intact
* Cloing connection #0
复制代码

上述的报文分为几个阶段,最经典的就是三次TCP三次握手。

* About to connect() to 127.0.0.1 port 1337(#0)
*   Trying 127.0.0.1..
* connected
* Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)
复制代码

第二部分是向服务端发送请求报头

> GET / HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 openSSL/0.9.8r zlib/1.2.5
> Host: 127.0.0.1:1337
> Accept: */*
>
复制代码

第三部分是服务器端向客户端发送返回内容,包括响应头和响应体。

< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 06 Apr 2013 08:01:44 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello World
复制代码

最后是结束会话的阶段

* Connection #0 to host 127.0.0.1 left intact
* Closing connetion #0
复制代码

从上述报文能够看出,http是基于请求响应式,一问一答实现服务的。虽然基于TCP会话,可是不存在会话的特色。

从协议的角度上来讲,浏览器就是一个HTTP代理,将用户的行为转化为请求报文发送给服务端,服务端处理请求,返回响应报文给http代理,代理解析响应报文,将内容显示在界面上。以查看图片为例子,浏览器发送请求报文,服务器解析图片路径,将磁盘中图片文件以报文的形式发送给客户端。浏览器接收到报文后,调用渲染引擎将内容显示给用户。http服务只有两件事,处理http请求,发送http响应。

不管是http请求体仍是响应体,都只包含两件部分,报文头和报文体。

上述部分<和>分别为请求头和响应头,因为是get请求,因此没有报文体,hello world是响应的报文体。

http模块

Node的Http模块是继承自TCP模块,基于事件驱动,不会为每个请求就生成额外线程或者进程,内存占用比很是底,因此能实现高并发。Http模块与TCP模块的区别在于,启动了keepalive后,tcp可以用于屡次请求和响应。HTTP服务是以Request为单位进行服务,Tcp服务是以Connection为单位进行服务,http模块即将connection到request进行封装。

1

HTTP模块将嵌套字的读写抽象为ServerRequest对象和Respond对象,分别对应请求和响应操做。当请求产生时,http拿到链接中的数据,调用二进制模块http_parse解析完报头后,触发Request事件,而后调用用户的业务逻辑。

2

HTTP请求

对于TCP的链接的读操做,http模块将其封装为ServerRequest对象,以下是通过http_parse的请求报文

> GET / HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: 127.0.0.1:1337
> Accept: */*
>
复制代码

报文头 GET / HTTP/1.1 会被解析为以下属性

  • req.method熟悉为请求方法,值为GET,经常使用的有GET,PUT,POST,DELETE,CONNECT几种。
  • req.url属性,值为/
  • req.httpVersion熟悉,值为 1.1;

其余报头主要是Key:Value格式,解析完后给业务逻辑处理。

报文体被抽象为一个流对象,只有在报文接收完毕后业务逻辑才能够进行读操做。

function(req,res){
    //console.log(req.headers);
    var buffers = [];
    req.on('data',function(trunk){
        buffers.push(trunk);
    }).on('end',function(){
        var buffer = Buffer.concat(buffers);
        res.end('Hello world')
    })
}
复制代码

HTTP响应

HTTP响应是封装流中的可写操做,能够当作一个可写的流对象。

在编辑响应体的头部主要分两步setHeader()和writeHeader(),能够经过setHeader()设置多个参数,可是只有经过writeHeader后才会写入到链接。

而且http模块会自动设置一些头信息。

< Date: Sat, 06 Apr 2013 08:01:44 GMT
< Connection: keep-alive
< Transfer-Encoding:chunked
复制代码

这里的keep-alive表示的是当前的链接不关闭,能够用于下一个请求。chunked表示的则是默认使用分块传输协议(chunked transfer encoding),放弃采用缓存的方式传输返回体,并且分块传输从而提高效率,最后一个数据块用0表示传输完毕。

写入响应体的内容则是经过write()和end(),二者的区别在于end()会调用write()将数据写入链接中,而后告诉服务端响应结束。

一旦开始发送报文则writeHeader()和setHeader()不在生效,由于报头在报文发送前发送,这是因为http协议特性决定的。

HTTP事件

HTTP服务如TCP服务同样抽象了一些事件,而且HTTP服务器也是一个EventEmiter实例。

  • connection:当开始发送请求和响应时,须要创建底层的TCP链接,有些时候也会开启keep-live用以保持链接。当连接创建完成时触发该事件。
  • request:当连接创建完成,请求的报头发送完毕解析完成时触发该事件。
  • close:与TCP模块服务相似,调用server.close再也不接收新的连接,当全部连接都断开时触发该事件,也能够经过server.close()传入回调快速触发该事件。
  • checkContinue:当客户端发送一个较大数据的请求时,会先发送一个报头带有expect:100-continue进行确认,当接收到请求报头时触发该事件。若是没有监听该事件,则默认返回状态码为100 continue表示赞成接收,不然返回400 Bad Request拒绝接受。该事件与reqeust事件互斥,当监听了该事件,request事件不会被触发。当客户端接收到100 continue从新发起请求,下一个请求才会触发request事件。
  • connect:当客户端发起CONNECT请求时(一般用于代理时发起),触发该事件,当没有监听该事件时,自动关闭该链接。
  • upgrade:当请求报文中携带这个字段(用于与服务器端协商升级协议),若是没有监听该事件,自动断开连接
  • clientError:当客户端触发error时,会将error传递给服务端,触发该事件。

HTTP客户端

HTTP模块提供了一个底层API http.request(options,connect)用来实例化一个http客户端。

options有以下选项

  • host:服务器端口和域名,默认为127.0.0.1
  • hostname:服务器名称
  • port:服务器端口,默认为80
  • localAdress:创建链接的本地网卡
  • socketPath:domain套接字路径
  • method:http请求方法,默认为Get
  • path:http请求路径,默认为/
  • header:请求头
  • auth:basic认证,用于headers中的Authorization部分

HTTP代理

HTTP模块的ClientReqeust封装的模块相似于服务端。在keep-live的状况下可复用链接,默认最大链接数是5。封装了一个http.globalAgent进行管理链接池。

3

若是须要修改限制,则须要声明一个agent配置maxSockets配置进行传递,false表示不进行配置

const agent = http.agent({
    maxSockets:20
});

http.request({
    agent
});
复制代码

Agent对象中sockets和request表示当前的链接数和等待链接的请求,能够经过观察这两个值进行判断业务繁忙程度。

HTTP客户端事件

  • response:在获得服务端端响应时触发该事件
  • socket:当底层的链接池分配链接给当前请求时出发该请求
  • connect:当发送connect请求时服务端返回200状态码时触发该事件
  • upgrede:当客户端发送upgrade请求时,服务端发送101 Switching Protocols时触发该事件
  • continue:当客户端发送expect:100-continue时,服务端返回100 continue触发该事件

以上知识点均来自<<深刻浅出Node.js>>,更多细节建议阅读书籍:-)

相关文章
相关标签/搜索