原文在个人博客,转载请注明出处,谢谢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
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.Socket
和net.connect
用于建立客户端。
dgram模块用于建立基于UDP协议的网络服务,建立不分客户端不分客户端、服务器端,在一端使用dgram.createSocket
便可发送数据也能够接收数据。
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模块除了提供代理类,还提供了:
net.Server
,并添加了一些事件建立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
便可发送请求。
一问一答是HTTP协议的特色,然而服务器主动向客户端推送数据的场景也是常见的、被须要的。在WebSocket出现以前,实现客户端和服务器端双工通讯通常只能经过多开几个HTTP链接、以轮询方式来实现。因为HTTP一问一答的特色不适合这种场景,就算HTTP1.1新增的Keep-Alive也不能很好的解决这种问题,因而WebSocket协议就出现了。
WebSocket协议可让客户端与服务器端实现双向通讯,服务端能够主动发送数据到客户端。创建WebSocket协议链接时,客户端会发送一条HTTP请求,请求服务器端切换协议为WebSocket,服务器端若是支持WebSocket协议,就会返回一条HTTP响应表示正在切换WebSocket协议并切换。以后就能够互相发送数据了。
使用WebSocket协议构建应用有如下优势:
目前大多数浏览器已经实现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等。