在用 Node.js 起服务以前,咱们应该知道这些

网络分层

了解计算机网络的同窗都知道 OSI 七层网络模型TCP/IP 模型。OSI 七层模型是理论上的网络通讯模型,而 TCP/IP 是现实中的网络通讯概念模型。它们之间的对比关系参考下图。html

image

本文主旨不在于解释网络通讯模型,所以此处略去各层的介绍,只关注应用层与传输层,即 TCP/IP 模型中的应用层和传输层。前端

咱们知道 HTTP 协议工做在应用层,属于应用层协议,TCP/IP 协议工做在传输层,属于传输层协议。在实际的网络通讯中,一端的数据以报文的形式,从上到下通过层层封装发送给另外一端,而接收数据的一端则从下到上层层解析数据报文,最后将数据传递给应用层。
从编程的角度来看,上面的数据报文的传输是如何实现的呢?或者说,应用层是如何与传输层通讯的呢?这就须要说到 socket 了。node

socket

所谓 socket,也就是俗称的套接字,是计算机网络中能够发送和接收数据的一个端点,包含源 IP 地址和目的 IP 地址以及源端口号和目的端口号。
其实,socket 相似于插排上的插孔,当插头插入插孔时,即可实现通电。对于 socket 来讲,当链接创建以后,至关于一条通讯线路链接了通讯双方的 socket,通讯双方就能够经过 socket 进行通讯了。git

Node.js 中的 socket 被封装在了 net 模块和 dgram 模块中,分别对应与 TCP 协议UDP 协议。下面以 net 模块为例,说明 socket 的使用。github

先看服务端代码:express

'use strict';

const net = require('net');

const HOST = '127.0.0.1';
const PORT = 9999;

net.createServer(function(socket) {
  console.log(`接收到远端访问: ${socket.remoteAddress}:${socket.remotePort}`);
  
  // 接收客户端发送的数据
  socket.on('data', function(data) {
    console.log(`from client: ${data}`);
    
    // 将接收到的数据返回给客户端
    socket.write(data);
  });

  // 为这个socketet实例添加一个"close"事件处理函数
  socket.on('close', function(data) {
      console.log(`来自 ${socket.remoteAddress}:${socket.remotePort} 的链接已经关闭。`);
  });
}).listen(PORT, HOST);

console.log('Server listening on ' + HOST +':'+ PORT);

在服务端,经过 net.createServer 添加对 connection 事件的监听,获取 socket 实例,并在 socket 实例上作数据收发操做。编程

如下是客户端代码:api

'use strict';

const net = require('net');

const HOST = '127.0.0.1';
const PORT = 9999;

let talk = 0;

const client = new net.Socket();
client.connect(PORT, HOST, function() {
    console.log(`已经创建与 ${HOST}:${PORT} 的链接`);
    // 创建链接后当即向服务器发送数据,服务器将收到这些数据 
    client.write('hello, I am client!');
});

// 为客户端添加“data”事件处理函数,接收服务端返回的数据
client.on('data', function(data) {
    console.log(`from server: ${data}`);
    talk++;
    if (talk < 3) {
      // 接受到数据后,再次发送数据给客户端
      client.write(`talk: ${talk}`);
    } else {
      client.end();
    }
});

// 为客户端添加“close”事件处理函数
client.on('close', function() {
    console.log('链接已经关闭!');
});

客户端直接建立 socket 实例,收发数据。服务器

分别执行服务端和客户端代码,分别获取命令行输出以下。网络

image

服务端每次会将接收到的客户端数据回发给客户端,客户端接收到三次数据后,主动关闭 socket 链接。

整个 net 模块,提供了两个类: net.Servernet.Socketnet.Socket 负责通讯双方之间的通讯,而 net.Server 负责创建本地服务器,提供了对本地服务器的管理,每接收到一个链接就会创建一个 socket 与对端实现通讯。

http

http 协议前端同窗应该都很熟悉。在 Node.js 中,http 协议由 http 模块(底层基于 net 模块)实现。
http 模块提供了以下几个类:

  • http.Agent
  • http.ClientRequest
  • http.Server
  • http.ServerResponse
  • http.IncomingMessage

咱们经过一段代码,简述每一个类的做用,以及内部实现原理。

先看服务端代码:

'use strict';

const http = require('http');

const server = http.createServer((req, res) => {
  req.on('data', (data) => {
    console.log(`接收到远端请求数据: ${data}`);
  });
  req.on('end', () => {
    res.write('请求已收到');
    res.statusCode = 200;
    res.end();
  });
});

server.listen(9999);
console.log('server listening on 9999');

服务端经过 http.createServer 建立服务器并监听 request 事件,在事件处理函数中获取 reqres作请求处理和响应。其中 req 就是 http.IncomingMessage,用于描述远端过来的请求,而res 就是 http.ServerResponse,用于描述服务端作出的响应。

在上面的代码中,咱们经过 req 监听 data 事件获取请求的数据,并经过 res 向客户端发送响应数据。一读一写,不由让人联想到 reqres 是否就对应于 socket 的读和写呢?查了下 Node.js 的源代码,http.IncomingMessagehttp.ServerResponse 的确是共用了一个 socket,而这个 socket 正是 net.Server 在接收到请求链接时所建立。

再看客户端代码:

'use strict';

const http = require('http');

const postData = 'post_data';
const options = {
  hostname: 'localhost',
  port: 9999,
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(postData)
  }
};
const req = http.request(options, (res) => {
  console.log('获取到服务端响应');
  res.on('data', (data) => {
    console.log(`服务端响应: ${data}`);
  });
});
req.write(postData);
req.end();

在上面代码中,经过 http.request 建立 http.ClientRequest,用于描述一个进行中的请求,在 http.ClientRequest 之上添加 response 事件监听,能够获取到服务端的响应描述 http.IncomingMessage,从而获取服务端响应数据。

除以上各种之外,http 模块还提供了一个 http.Agent 类,该类用于管理客户端的链接,必要时会重用 socket。此处不作详述。

小结

在平常开发过程当中,咱们不多直接使用 Node.js 原生的类,更多的是使用成熟的框架,好比 expresskoa,阿里集团内部也有成熟的框架,好比 midwayegg。这些框架或多或少的都对 Node.js 提供的原生能力进行了封装,咱们在使用这些框架的同时,也应该清楚框架背后 Node.js 的运行机制以及能提供的能力。

相关文章
相关标签/搜索