认识node核心模块--网络编程

原文在个人博客,转载请注明出处,谢谢javascript

在构建网络通讯服务方面,相比于其余老牌后端语言,Node.js 一样可以胜任(也许更胜一筹),而且有本身独特的处理方式。node是一个面向网络而生的平台,它的事件驱动、非阻塞、单线程使node应用程序具备低内存、高并发、伸缩性强的优良特性,适合在分布式网络大展身手。Node底层实现了传输层TCP/UDP、应用层HTTP/HTTPS的功能并封装成贴合网络的API,而且能够本身建立服务器而不依赖三方服务,使用起来很是方便、简单、灵活。对于网络编程,node提供了net、dgram、http、https 4个模块,分别用于处理TCP、UDP、HTTP、HTTPS。本文将介绍这些模块并利用这些模块提供的API构建简单的网络服务。html

正文

其实不管什么语言、什么平台,实现网络编程都须要遵循网络标准规范,只不过具体实现或者提供的API不一样而已。所以,在探讨node网络编程以前,咱们须要了解用于网络通讯的网络协议(推荐阅读《图解HTTP》)。理解了网络通讯的规范和机制,再熟悉一下API就能够了。java

图片来源https://yjhjstz.gitbooks.io/deep-into-node/content/chapter9/chapter9-1.html

TCP/UDP

TCP(Transmission Control Protocol)传输控制协议是面向链接的协议,也就是必须创建链接才能发送数据。TCP在传输以前须要与服务器端进行3次握手造成会话(SYN是同步信号,ACK是确认信号):node

TCP传送数据比较可靠,若是丢失数据会重传,而且会对传送数据进行排序。适用于重要、有序数据的传送。git

UDP(User Datagram Protocol)用户数据报协议是无链接协议,不面向链接,面向事务,建立过程相对简单,占用内存底,处理快速且灵活,发送数据不须要与另外一端创建链接,且不分客户端、服务器端,在一端便可以发送数据也能够接收数据。传送的数据是无序的,网络中断会致使丢包。UDP的简单不可靠特性适用于丢失一部分数据不会形成太大影响的场景,如音视频数据传送等。web

socketexpress

网络上的两个程序经过一个双向的通讯链接实现数据的交换,这个链接的一端称为一个socket(套接字),所以创建网络通讯链接至少要一对端口号(socket)。socket本质是对TCP/IP协议栈的封装,它提供了一个针对TCP或者UDP编程的接口,并非另外一种协议。经过socket,你可使用TCP/IP协议。编程

Socket的英文原义是“孔”或“插座”。做为BSD UNIX的进程通讯机制,取后一种意思。一般也称做"套接字",用于描述IP地址和端口,是一个通讯链的句柄,能够用来实现不一样虚拟机或不一样计算机之间的通讯。在Internet上的主机通常运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不一样的端口对应于不一样的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各类插座的房间,每一个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不一样编号的插座,就能够获得不一样的服务。json

​ ——百度百科后端

建立TCP、UDP客户端和服务端

在node中,net模块提供建立基于TCP协议的网络通讯的API,net.Socket类提供了 TCP 或 UNIX Socket 的抽象,net.createServer用于建立服务端,net.Socketnet.connect用于建立客户端。

dgram模块用于建立基于UDP协议的网络服务,建立不分客户端不分客户端、服务器端,在一端使用dgram.createSocket便可发送数据也能够接收数据。

http/https

http是应用层协议,创建在TCP/IP之上,https则创建在TLS、SSL加密层协议之上,现代web基本都是http/https应用。TCP在创建链接要发送报文,http也是,http报文分为请求报文和响应报文,报文格式以下:

HTTP/1.0 200 OK    //起始行

Content-type:text/plain    //头部
Content-length:19            //头部  

Hi I'm a message!    //主体

其中最重要的莫过于头部报文了,它定义了请求或响应的行为方式,是客户端与服务器端交流的重要信息。http报文头部的属性多达几十个,并且愈来愈多,保证客户端与服务器端充分交流。

现代浏览器,集成了HTTP代理功能,用户点击连接等行为会由浏览器生成HTTP请求报文发送给服务器端,收到响应后会解析报文,渲染报文中的主体内容。

node中的http

node中http模块提供建立基于http协议的网络通讯应用的接口,继承于net模块,采用事件驱动机制,能与多个客户端保持链接,并不为每一个链接开启新的进程或线程,低内存、高并发,性能优良。

“http模块将链接所用套接字(socket)的读写抽象为ServerRequest和ServerResponse对象,它们分别对应请求和响应操做。在请求产生的过程当中,http模块拿到链接中传来的数据,调用二进制模块http_parser进行解析,在解析完请求报文的报头后,触发request事件,调用用户的业务逻辑。”

​ ——朴灵《深刻浅出Node.js》

从上图能够看到,node中http模块所作的事情就是继承net模块使用TCP协议、封装http请求、产生http事件、响应事件绑定的处理程序。

http代理

node中http模块提供了一个类http.Agent,它称为http代理,它的做用就是为了重用TCP链接,减小资源浪费。那么http代理是如何重用TCP链接呢?http代理维护一个链接池,从客户端发起的http请求都经由代理管理:

它为一个给定的主机与端口维护着一个等待请求的队列,且为每一个请求重复使用一个单一的 socket(TCP) 链接直到队列为空,此时 socket(TCP 链接) 会被销毁或被放入一个链接池中,在链接池中等待被有着相同主机与端口的请求再次使用。 是否被销毁或被放入链接池取决于 keepAlive选项

​ ——Node.js 8.9.0中文文档

链接池如何管理链接还得取决于服务器:

即使链接池中的链接的 TCP Keep-Alive 是开启的,服务器仍然可能关闭闲置的链接,在这种状况下,这些链接会被移出链接池,且当一个新的 HTTP 请求被建立时再为指定的主机与端口建立一个新的链接。 服务器也可能拒绝容许同一链接上有多个请求,在这种状况下,链接会为每一个请求从新建立,且不能被放入链接池。Agent 仍然会建立请求到服务器,但每一个请求会出如今一个新的链接。

但一个链接被客户端或服务器关闭时,它会被移出链接池。 链接池中任何未被使用的 socket 会被释放,从而使 Node.js 进程在没有请求时不用保持运行。

​ ——Node.js 8.9.0中文文档

http模块除了提供代理类,还提供了:

  • http.ClientRequest类—— 表示一个正在处理的请求,这个请求还能设置请求头
  • http.Server类——继承net.Server,并添加了一些事件
  • http.ServerResponse类——表明响应
  • http.createServer方法——建立服务器,返回http.Server实例
  • http.request方法——显式发出请求
  • 各类请求、响应对应的事件

建立http服务器

// node建立服务器很是简单,不须要任何三方代理
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'})
  res.end('Hello World\n');
}).listen(8880, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8880/')

建立http请求

使用http.request便可发送请求。

WebSocket

一问一答是HTTP协议的特色,然而服务器主动向客户端推送数据的场景也是常见的、被须要的。在WebSocket出现以前,实现客户端和服务器端双工通讯通常只能经过多开几个HTTP链接、以轮询方式来实现。因为HTTP一问一答的特色不适合这种场景,就算HTTP1.1新增的Keep-Alive也不能很好的解决这种问题,因而WebSocket协议就出现了。

WebSocket协议可让客户端与服务器端实现双向通讯,服务端能够主动发送数据到客户端。创建WebSocket协议链接时,客户端会发送一条HTTP请求,请求服务器端切换协议为WebSocket,服务器端若是支持WebSocket协议,就会返回一条HTTP响应表示正在切换WebSocket协议并切换。以后就能够互相发送数据了。

使用WebSocket协议构建应用有如下优势:

  • 客户端能够与服务器端实现双向通讯,服务端能够主动发送数据到客户端
  • 经过第一个request创建了TCP链接以后,以后交换的数据都不须要发送 HTTP header就能交换数据

目前大多数浏览器已经实现WebSocket,能够直接使用:

var socket = new WebSocket('ws://localhost:3000/') // 路径中的协议改成ws(WebSocket)
socket.onopen = function () {
  // 链接打开要作的事
};
socket.onmessage = function (event) {
  // 接收到服务端的信息(event.data)
};

调用WebSocket后浏览器会发送一个HTTP请求,请求报文以下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket // 请求协议升级为websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== //校验值
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

在Node原生模块中没有支持WebSocket协议链接功能的模块,但Node有不少三方模块来帮助作这件事,经常使用的ws模块方法以下:

// 导入WebSocket模块:
const WebSocket = require('ws');

// 引用Server类:
const WebSocketServer = WebSocket.Server;

// 实例化:
const wss = new WebSocketServer({     // 在本地3000端口打开一个WebSocket Server
    port: 3000
});

wss.on('connection', function (ws) {
    console.log(`[SERVER] connection()`);
    ws.on('message', function (message) {
        console.log(`[SERVER] Received: ${message}`);
        ws.send(`ECHO: ${message}`, (err) => {
            if (err) {
                console.log(`[SERVER] error: ${err}`);
            }
        });
    })
});

服务器返回的响应报文以下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 返回通过计算得出的校验值 
Sec-WebSocket-Protocol: chat

使用框架进行网络编程

Node网络模块中提供的API较为底层,有时在构建网络应用程序并不须要关心底层实现,这时就能够借助三方框架封装好的API来帮助咱们,经常使用的框架包括express、koa、connect等。

相关文章
相关标签/搜索