前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Node理论笔记:网络编程

Node理论笔记:网络编程

原创
作者头像
Ashen
修改2020-06-01 14:41:46
1.2K0
修改2020-06-01 14:41:46
举报
文章被收录于专栏:Ashenの前端技术Ashenの前端技术

一、构建TCP服务

1.1 TCP

TCP全名为传输控制协议,在OSI(由七层组成:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层)中属于传输层协议。HTTP、SMTP、IMAP协议都是基于TCP构建的。

TCP是面向连接的协议,特点是在传输之前需要3次握手(请求连接、响应、开始传输)形成会话。在创建会话的过程中,服务器端和客户端分别提供一个套接字,这两个套接字共同形成一个连接,服务端与客户端则通过套接字实现两者之间连接的操作。

1.2 创建TCP服务器端

node内置了net模块用于创建TCP连接。

代码语言:javascript
复制
const net = require("net");
const server = net.createServer(socket=>{
  socket.on("data",data=>{
    socket.write("你好\n");
  });
  socket.on("end",()=>{
    console.log("连接断开\n");
  });
  socket.write("你好nodeJs\n");
});
server.listen(8080,()=>{
  console.log("server is running~");
});

createServer()用于创建一个TCP服务器。然后利用telnet客户端来创建会话,这里用的是MobaXterm,因为windows的cmd每次需要设置编码比较麻烦:

代码语言:javascript
复制
telnet> open localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
你好nodeJs

键入内容并回车,会在TCP服务端触发data事件,内容作为参数传递给回调。执行quit退出telnet则会触发end事件,标识结束该TCP连接。

1.3 TCP服务的事件

上述代码中,分为服务器事件和连接事件。

1、服务器事件

调用createServer()创建的服务,本身是一个EventEmitter实例,包含以下几种自定义事件:

1 )  listening事件

在调用listen绑定端口或Domain socket触发,简单写法就是listen的第二个回调函数。

代码语言:javascript
复制
server.listen(8080,()=>{
  console.log("server is running~");
});
server.on("listening",()=>{
  console.log("listening事件触发");
});

触发的顺序取决于注册的顺序,所以这里listen回调会先执行。

2 ) connection事件

TCP连接是可以一对多的,所以每个客户端连接到服务端会触发connection事件。

代码语言:javascript
复制
server.on("connection",()=>{
  console.log("connection事件触发");
});

3 ) close事件

服务器关闭时触发。当调用server.close()方法后,将拒绝新的连接请求,原有的连接依然保持,当所有的连接中断后,触发close事件。

代码语言:javascript
复制
server.on("close",()=>{
  console.log("所有会话已关闭");
});

4 ) error事件

服务器发生异常会触发该事件,比如侦听一个已使用的端口,如果没有注册该事件,服务器会抛出异常。

代码语言:javascript
复制
server.on("error",()=>{
  console.log("error事件触发");
});

2、连接事件

服务器端可以与多个多户端保持连接,对每个连接而言是典型的可写可读Stream对象,该对象可用于服务器端与客户端的通信,可以通过data事件从一端读取另一端发来的数据,反之也可以通过write方法从一端向令一端发送数据。

1 )  data事件

一端调用write发送数据会触发另一端的data事件,事件传递的数据就是data发送的数据。

代码语言:javascript
复制
socket.on("data",(chunk)=>{
  console.log("data事件触发",chunk);
});

2 )  end事件

当连接中的任意一端发送了FIN数据时,将会触发该事件。

客户端退出连接会触发该事件。

代码语言:javascript
复制
socket.on("end",()=>{
  console.log("end事件触发");
});

3 )  connect事件

该事件用于客户端,当套接字与服务器连接成功时触发。

暂时不知道如何触发,待定。

4 )  drain事件

任意一端调用write()方法会触发该事件。

telnet下直接回车并不会触发,待定。

5 )  error事件

异常发生时触发该事件。

代码语言:javascript
复制
socket.on("error",()=>{
  console.log("error事件触发");
});

6 )  close事件

套接字完全关闭时,触发该事件。

代码语言:javascript
复制
socket.on("close",()=>{
  console.log("close事件触发");
});

7 )  timeout事件

当一定时间后连接不再活跃时,该事件会被触发。

暂时不知道如何触发,待定。


TCP针对网络中的小数包有一定的优化策略:Nagle算法。缓冲区的数据达到一定数量或一定时间后才将其发出,所以小数据包会被Nagle算法合并,以此来优化网络。但是带来的问题就是,有时候数据可能被延迟发送。

node中,TCP默认启用了Nagle算法,可以调用socket.setNoDelay(tyrue)来去掉该算法。

值得注意的是,尽管网络的一端调用write()方法会触发另一端的data事件,但并不意味着每次调用write只会触发一次data事件,关闭Nagle算法后,接收端可能会接收到多个小数据包的合并,然后只触发一次data事件。

二、构建UDP服务

UDP全名为用户数据报协议,同TCP一样也属于传输层。

UDP不是面向连接的,在TCP中每一个会话都是基于连接完成的,客户端如果要与另一个TCP服务通信则需要另一个套接字来完成。但在UDP中,一个套接字可以与多个UDP服务器通信,所以UDP是面向不可靠的连接服务,但由于资源消耗少处理速度快且灵活,所以广泛应用于偶尔丢几个包也无重大影响的场景,如音视频等。DNS服务就是基于UDP实现的。

2.1 创建UDP套接字

首先要调用dgram模块,然后调用其createSocket方法。

代码语言:javascript
复制
const dgram = require("dgram");
const socket = dgram.createSocket("udp4");

参数可以是udp4或udp6。

2.2 创建UDP服务器端

只需要调用bind(port,[address])方法绑定网卡和端口即可。

代码语言:javascript
复制
const dgram = require("dgram");
const socket = dgram.createSocket("udp4");
socket.on("message",(msg,info)=>{
  console.log(`server get ${msg} from ${info.address}:${info.port}`);
});
socket.on("listening",()=>{
  const address = socket.address();
  console.log(`server listening ${address.address}:${address.port}`);
});
socket.bind(8080,"127.0.0.1");

绑定完成后将触发listening事件。如果缺省address,将监听所有网卡的8080端口。

2.3 创建UDP客户端

代码语言:javascript
复制
const dgram = require("dgram");
const client = dgram.createSocket("udp4");

const message = new Buffer("hello world");
client.send(message,0,message.length,8080,"192.168.1.1",()=>{
  client.close();
});

保存并执行,服务端会输出:

代码语言:javascript
复制
server get hello world from 127.0.0.1:57367

send()方法参数如下:

代码语言:javascript
复制
send(buf,start,length,port,address,[callback]);

分别指要发送的buffer、buffer的偏移位置,buffer的长度,目标端口,目标地址,完成后的回调函数。

2.4 UDP套接字事件

UDP套接字只是一个EventEmitter实例,而非stream实例。

1、message事件

接收到消息后将触发该事件,传递2个参数:Buffer对象和一个远程地址信息。

代码语言:javascript
复制
socket.on("message",(msg,info)=>{
  console.log(`server get ${msg} from ${info.address}:${info.port}`);
  console.log(msg);
  console.log(info);
});

打印结果:

代码语言:javascript
复制
server get hello world from 127.0.0.1:55649
<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
{ address: '127.0.0.1', family: 'IPv4', port: 55649, size: 11 }

2、listening事件

UDP套接字开始侦听时触发该事件。如上调用bind方法后就会触发listening事件。

代码语言:javascript
复制
socket.on("listening",()=>{
  const address = socket.address();
  console.log(`server listening ${address.address}:${address.port}`);
});

3、close事件

调用close()方法会触发close事件,然后将不再触发message事件,除非再次绑定。

代码语言:javascript
复制
socket.on("close",()=>{
  console.log("close事件被触发");
});

4、error事件

当异常发生时会触发该事件,如果没有监听该事件,异常将直接抛出使进程退出。

代码语言:javascript
复制
socket.on("error",()=>{
  console.log("error事件被触发");
});

三、构建HTTP服务

3.1 http

HTTP全称为超文本传输协议,构建在TCP上,属于应用层协议。node内置了http和https模块。

创建一个http服务:

代码语言:javascript
复制
const http = require("http");
const server = http.createServer((req,res)=>{
  res.writeHead(200,{
    "Content-Type":"text/plain"
  });
  res.end("Hello World\n");
});
server.listen(8080,"127.0.0.1");

然后通过命令行的curl来访问这个地址:

代码语言:javascript
复制
curl -v http://127.0.0.1:8080

打印结果:

代码语言:javascript
复制
* Rebuilt URL to: http://127.0.0.1:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.50.3
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Tue, 12 Mar 2019 05:55:08 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello World
* Curl_http_done: called premature == 0
* Connection #0 to host 127.0.0.1 left intact

整个请求过程由3部分组成:

1、经典的TCP三次握手

代码语言:javascript
复制
* Rebuilt URL to: http://127.0.0.1:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)

2、客户端向服务器端发送请求数据

代码语言:javascript
复制
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.50.3
> Accept: */*

3、服务器端向客户端返回响应信息,包括响应头和响应体

代码语言:javascript
复制
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Tue, 12 Mar 2019 05:55:08 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello World

最后是结束会话的信息

代码语言:javascript
复制
* Curl_http_done: called premature == 0
* Connection #0 to host 127.0.0.1 left intact

可以看到,http是基于请求响应式的,以一问一答的方式实现服务,虽然是基于TCP的,但本身并无会话的特点。

http服务只做2件事:处理http请求和发送http响应。

3.2 http模块

http模块继承自net模块,http模块将连接所用的套接字的读写抽象成ServerRequest和ServerResponse对象,分别对应请求和响应操作。

1、http请求

请求的报文头会通过http_parser进行解析。比如对于报文头:

代码语言:javascript
复制
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.50.3
> Accept: */*

第一行解析出来会包含如下属性:

  • req.method属性:值为GET,为请求方法。常见请求方法:GET、POST、DELETE、PUT、CONNECT等。
  • req.url属性:值为/。
  • req.httpVersion属性:值为1.1。

其余报头的信息会以key:value的形式存储在req.headers属性中。如:

代码语言:javascript
复制
{ host: '127.0.0.1:8080',
  'user-agent': 'curl/7.50.3',
  accept: '*/*' }

而报文体部分会抽象为一个只读流对象,这个数据流结束后才能进行操作,如:

代码语言:javascript
复制
const buffer = [];
  req.on("data",chunk=>{
    buffer.push(chunk);
  }).on("end",()=>{
    console.log(Buffer.concat(buffer));
    res.end("Hello World\n");
  });

2、http响应

res可以看成一个可写的流对象,可以调用setHeader()和writeHead()方法来设置响应头报文信息。

代码语言:javascript
复制
res.writeHead(200,{
  "Content-Type":"text/plain"
});

可以多次调用setHeader(),但只有调用writeHead()后,报头才会写进连接中。

报文体部分则是通过res.write()和res.end()方法实现,同样res.write()可以多次调用,但只有调用res.end()才会真正结束响应。

代码语言:javascript
复制
res.write("hello\n");
res.write("World\n");
res.end("end\n");

注意,不能在end调用结束后继续调用write,否则会抛出异常。同时,报文头是先于报文体发送的,所以一旦开始了数据的发送(调用write方法),writeHead()和setHeader()将不再生效。

另外,无论如何,结束时一定要调用res.end()结束请求,否则客户端将一直处于等待状态。

3、http服务的事件

1 ) connection事件

当连接建立时会触发一次connection事件。

代码语言:javascript
复制
server.on("connection",()=>{
  console.log("connection事件触发");
});

2 ) request事件

当请求数据发送到服务器,在解析出http请求后,将会触发该事件。

代码语言:javascript
复制
server.on("request",()=>{
  console.log("request事件触发");
});

3 ) close事件

同TCP服务器行为一致,调用server.close()会终止新的连接,当已有的连接都中断时,触发该事件。

也可以通过给close()传递一个回调函数来快速注册该事件。

代码语言:javascript
复制
server.close(()=>{
  console.log("close事件触发");
});

4 ) checkContinue事件

某些客户端在发送较大的数据时,会先发送一个头部带Expect:100-continue的请求到服务器,然后触发checkContinue事件。如果没有注册该事件,则默认响应100 Continue状态码表示接受数据上传。如果不接受较多数据响应400拒绝即可。该事件发生不会触发request事件,收到100 Continue再次请求才会触发request事件。

5 ) connect事件

发起connect请求会触发connect事件,通常在http代理时出现。

如果不监听该事件,发起该请求的连接将会关闭。

6 ) upgrade事件

客户端要求升级连接的协议时需要与服务器端协商,客户端会在请求头中携带Upgrade字段,服务端会在接收到这样的请求时触发upgrade事件。在后续的webSocket中会有介绍。

7 ) clientError事件

连接的客户端触发error事件时,这个错误会传递到服务器端,此时触发该事件。

3.3 http客户端

http模块除了创建服务端以外,还可以创建客户端来发起请求。

代码语言:javascript
复制
const http = require("http");
const client = http.request({
  hostname:"127.0.0.1",
  port:8080,
  path:"/",
  method:"GET"
},(res)=>{
  console.log(`Status:${res.statusCode}`);
  console.log(`Headers:${JSON.stringify(res.headers)}`);
  res.setEncoding("utf8");
  res.on("data",chunk=>{
    console.log(chunk);
  })
});
client.end();

回调打印结果:

代码语言:javascript
复制
Status:200
Headers:{"content-type":"text/plain","date":"Tue, 12 Mar 2019 07:42:44 GMT","connection":"close","transfer-encoding":"chunked"}
hello
World
end

request()的第一个参数对象定义了这个http请求头中的内容:

  • host:服务器的IP或域名,默认localhost
  • hostname:服务器名称
  • port:端口
  • localAddress:构建网络连接的本地网卡
  • socketPath:Domain套接字路径
  • method:请求方法,默认GET
  • path:请求路径,默认/
  • headers:请求头对象
  • auth:Basic认证,这个值会被计算成请求头的Authorization

调用http客户端同时对一个服务器发起10次http请求时,实质上只有5个请求处于并发状态,后续的请求会在某个请求结束后才会继续,这与浏览器对同一域名下的连接数的限制时相同的行为。

传递agent参数可以改变这个行为,默认采用的是全局代理,这个连接限制是5。

代码语言:javascript
复制
const options = {
  hostname:"127.0.0.1",
  port:8080,
  path:"/",
  method:"GET",
  agent:new http.Agent({
    maxSockets:10
  })
};

或者直接将agent设为false,脱离连接池的管理,使得请求不受并发的限制。

http客户端事件

  • response:客户端请求发出得到服务器响应时触发该事件
  • socket:当底层连接池中建立的连接分配给当前请求对象时,触发该事件
  • connect:发起connect请求时,如果服务端响应了200状态码,客户端将触发该事件
  • upgrade:客户端发起Upgrade请求时,如果服务端响应了101 Switching Protocols状态,客户端将触发该事件
  • continue:客户端发送大量数据会携带Expect:100-continue头信息,如果服务器端响应100 Continue状态,客户端将触发该事件

四、网络服务与安全

SSL(Secure Sockets Layer)全称安全套接层协议,在传输层提供对网络连接加密的功能。对于应用层而言时透明的。数据在传递到应用层之前就已经完成了加密解密的过程。

随后SSL被标准化,称为TLS(Transport Layer Security)安全传输层协议。

node提供了3个模块:

  • crypto:用于加密解密,包含SHA1、MD5等算法
  • tls:类似于net模块,区别在于是建立在TLS/SSL加密的TCP连接上
  • https:类似于http,区别也在于是建立在安全的连接之上

4.1 TLS/SSL

1、密钥

TLS/SSL是一个公钥/私钥的结构,是非对称的。每个服务器端和客户端都有自己的公私钥,公钥用来加密数据,私钥用来解密数据。公钥和私钥是配对的,公钥加密的数据只有对应的私钥才可以解密。所以在建立安全传输之前,服务器端和客户端需要互换公钥,服务器端用客户端的公钥加密数据然后发给客户端,客户端用服务器端的公钥加密数据发给服务器端。

node底层采用openssl实现TSL/SSL,为此要生成公钥和私钥可以通过openssl完成。

不过首先得安装openssl,并添加进环境变量。这里提供一个不需要编译的方式:

  1. 访问http://slproweb.com/products/Win32OpenSSL.html下载对应的exe或msi安装包,直接安装
  2. 将openssl的bin安装路径添加到系统的环境变量path中

现在就可以通过命令行来生成密钥了。

1 ) 首先生成客户端和服务端私钥(1024位RSA私钥):

代码语言:javascript
复制
openssl genrsa -out server.key 1024
openssl genrsa -out client.key 1024

我本地生成的密钥文件是这样的(server.key):

代码语言:javascript
复制
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC/h9li/0qKRxZfWQObwMG8LxgaVxcjg3YKyMT+eY2tMlODryFg
my7eRF+HidbBU7GM/Z/528MyB7WabmFsF6mdM9+oHLIaC7/H82hOAevC6w/yOSLP
EXkGy3YI6WurooMmghnicWal/Np/U/sl/ofY70pPJHGVQmwYN+s486W/RwIDAQAB
AoGAbzNOcX3LJ1FymdUylSFq2fl1wwVBd+sBg+1hAmZMbXxEpLXvaQlwQrfrxuOu
ffw7n6I5WXXQdKGpPIpNodZzMMJoI1jO9yKvpXxU40G7BX84+MDW66uHNiLOAP3m
pSpOdgyK0m5abn70ToPzwxefgOp3eWGJd8UNomN87uMbUKECQQDu+Afu+YOXzUJP
0zde5wnwymoi9lK/VHj181Ji/0RWZz0tdGxbSEVTUeee6ifnTwRuxV8ry8rlLdnU
+eXhHIofAkEAzS5PxdNI5R7N85pCH1KqMBelmMsMJhhutdIhrPoulmAF8dwxkviM
D9jxG2kiuNZ2l8umSwGGSXQSRS6/nxv12QJAI6zzwkGN28PQ+onV4l0rpr8RSVbs
05OQ22cQDad+VEflYjvXUWlgsCeyJI9gla++QatFogwypjRKKPmF0C2qkQJAMC3o
w34qhsql98bIMgy6M9LJqsg7ERL5pC40hCa3G85udu2KooVEdlAtxY75fUe2z0wd
v00bWFIuHBqvGlB5eQJATl7MHvQ26hDxPZepIOc6Y5d5Gzo+kgHVF7zsb1N46B0h
RgBTkGhrmi6rihxPhBtjisryUfHhoyQRurVdXk3AeA==
-----END RSA PRIVATE KEY-----

2 ) 接着生成公钥:

代码语言:javascript
复制
openssl rsa -in server.key -pubout -out server.pem
openssl rsa -in client.key -pubout -out client.pem

公钥看起来应该是这样子的(server.pem):

代码语言:javascript
复制
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/h9li/0qKRxZfWQObwMG8Lxga
Vxcjg3YKyMT+eY2tMlODryFgmy7eRF+HidbBU7GM/Z/528MyB7WabmFsF6mdM9+o
HLIaC7/H82hOAevC6w/yOSLPEXkGy3YI6WurooMmghnicWal/Np/U/sl/ofY70pP
JHGVQmwYN+s486W/RwIDAQAB
-----END PUBLIC KEY-----

但其实还是有风险,假设有个中间人,中间人对服务器端是客户端,对客户端是服务器端。为了解决这个问题,数据传输过程中还需要对得到的公钥进行认证,以确认得到的公钥来自目标服务器。所以TLS/SSL引入了数字证书来认证。

与直接使用公钥不同,数字证书包含了服务器的名称和主机名、服务器的公钥、签名颁发机构的名称、来自签名颁发机构的签名。在连接建立前,会通过证书中的签名确认收到的公钥是来自目标服务器,从而产生信任关系。

2、数字证书

为了确保数据的安全,需要引入一个CA(Certificate Authority,数字证书认证中心)。CA的作用用于给站点颁发证书,且这个证书具有CA通过自己的公钥和私钥实现的签名。

为了得到签名证书,服务器端需要通过自己的私钥生成CSR(Certificate Signing Request,证书签名请求)文件,CA机构通过这个文件颁发属于该服务器端的签名证书,只要通过CA机构就能验证证书是否合法。

通过CA机构颁发证书是个繁琐的过程,需要花钱。所以可以采用自签名证书来构建安全网络,自签名证书就是自己扮演CA机构,给自己服务器端颁发签名证书。

接着还是利用openssl来生成自签名证书。

1 ) 首先生成CA私钥文件:

代码语言:javascript
复制
openssl genrsa -out ca.key 1024

2 ) 接着用私钥生成CSR文件:

代码语言:javascript
复制
openssl req -new -key ca.key -out ca.csr

期间需要填一些东西:

  • Country Name (2 letter code) 使用国际标准组织(ISO)国码格式,填写2个字母的国家代号。中国请填写CN。
  • State or Province Name (full name) 省份,比如填写Shanghai
  • Locality Name (eg, city) 城市,比如填写Shanghai
  • Organization Name (eg, company) 组织单位,比如填写公司名称的拼音
  • Organizational Unit Name (eg, section) 比如填写IT Dept
  • Common Name (eg, your websites domain name): 行使 SSL 加密的网站地址。请注意这里并不是单指您的域名,而是直接使用 SSL 的网站名称 例如:pay.abc.com。 一个网站这里定义是: abc.com 是一个网站; www.abc.com 是另外一个网站; pay.abc.com 又是另外一个网站。
  • Email Address 邮件地址,可以不填
  • A challenge password 可以不填
  • An optional company name 可以不填

3 ) 生成自签名证书

代码语言:javascript
复制
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt

4 ) 回到服务端,先创建自己的CSR文件

代码语言:javascript
复制
openssl req -new -key server.key -out server.csr

5 ) 通过CA的证书和CA的私钥,结合服务端的CSR生成用于CA签名的证书

代码语言:javascript
复制
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt

至此,便完成了自签名证书。

客户端在发起安全连接前会去获取服务器端的证书,并通过CA的证书验证服务器端证书的真伪。同时还含有对服务器名称、IP地址等进行验证的过程。

CA机构将证书颁发给服务器端后,证书在请求的过程中会被发给客户端,客户端需要通过CA的证书验证真伪。对于知名的CA机构,其证书一般会预装到浏览器中,自己扮演CA机构,客户端需要获取该CA证书才能进行验证。

可以看到签名是一环一环颁发的,但是CA的证书是不需要上级证书参与签名的,这个证书称为根证书。

4.2 TLS服务

1、创建服务器端

证书都准备好了,接着通过tls模块来创建一个安全的TCP服务。

代码语言:javascript
复制
const tls = require("tls");
const fs = require("fs");
const options = {
  key:fs.readFileSync("./keys/server.key"),
  cert:fs.readFileSync("./keys/server.crt"),
  requestCert:true,
  ca:[fs.readFileSync("./keys/ca.crt")]
};
const server = tls.createServer(options,function(stream){
  console.log(`server connected`,stream.authorized?"authorized":"unauthoirzed");
  stream.write("welcome\n");
  stream.setEncoding("utf8");
  stream.pipe(stream);
});
server.listen(8080,function(){
  console.log("server is running");
});

启动服务,然后通过以下命令测试证书是否正常:

代码语言:javascript
复制
openssl s_client 127.0.0.1:8080

2、创建客户端

利用node来模拟客户端,tls模块提供connect()来构建客户端。首先还是按照上述的方法生成客户端自己的签名:

代码语言:javascript
复制
openssl req -new -key client.key -out client.csr
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt

创建客户端:

代码语言:javascript
复制
const tls = require("tls");
const fs = require("fs");
const options = {
  key:fs.readFileSync("./keys/client.key"),
  cert:fs.readFileSync("./keys/client.crt"),
  ca:[fs.readFileSync("./keys/ca.crt")],
  host:"127.0.0.1",
  path:"/"
};
const stream = tls.connect(8080,options,function(){
  console.log(`client connected`,stream.authorized?"authorized":"unauthoirzed");
  process.stdin.pipe(stream);
});
stream.setEncoding("utf8");
stream.on("data",data=>{
  console.log(data);
});
stream.on("error",err=>{
  console.log(err);
});

不幸的是,这个client启动会抛出异常:

代码语言:javascript
复制
{ Error: connect EPERM /
    at Object._errnoException (util.js:1022:11)
    at _exceptionWithHostPort (util.js:1044:20)
    at PipeConnectWrap.afterConnect [as oncomplete] (net.js:1198:14)
  code: 'EPERM',
  errno: 'EPERM',
  syscall: 'connect',
  address: '/' }

哎,暂时不知道为什么,先记录一下。

4.3 HTTPS服务

1、创建https服务器端

代码语言:javascript
复制
const fs = require("fs");
const https =require("https");

const options = {
  key:fs.readFileSync("./keys/server.key"),
  cert:fs.readFileSync("./keys/server.crt")
};
const server = https.createServer(options,function(req,res){
  res.writeHead(200);
  res.end("hello world\n");
});
server.listen(8080,function(){
  console.log("server is running");
});

2、创建https客户端

代码语言:javascript
复制
const fs = require("fs");
const https =require("https");

const options = {
  key:fs.readFileSync("./keys/client.key"),
  cert:fs.readFileSync("./keys/client.crt"),
  ca:[fs.readFileSync("./keys/ca.crt")],
  host:"localhost",
  path:"/",
  port:8080,
  method:"GET"
};
options.agent = new https.Agent(options);
const req = https.request(options,res=>{
  res.setEncoding("utf8");
  res.on("data",chunk=>{
    console.log(chunk);
  })
});
req.end();
req.on("error",err=>{
  console.log(err);
});

再次不幸的是,会抛出异常:

代码语言:javascript
复制
{ Error: self signed certificate
    at TLSSocket.<anonymous> (_tls_wrap.js:1105:38)
    at emitNone (events.js:106:13)
    at TLSSocket.emit (events.js:208:7)
    at TLSSocket._finishInit (_tls_wrap.js:639:8)
    at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:469:38) code: 'DEPTH_ZERO_SELF_SIGNED_CERT' }

究其原因是私有证书的问题,需要加这么一句:

代码语言:javascript
复制
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

让node.js规避非授信证书的问题。


本章End~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、构建TCP服务
    • 1.1 TCP
      • 1.2 创建TCP服务器端
        • 1.3 TCP服务的事件
          • 1、服务器事件
      • 二、构建UDP服务
        • 2.1 创建UDP套接字
          • 2.2 创建UDP服务器端
            • 2.3 创建UDP客户端
              • 2.4 UDP套接字事件
                • 1、message事件
                • 2、listening事件
                • 3、close事件
                • 4、error事件
            • 三、构建HTTP服务
              • 3.1 http
                • 3.2 http模块
                  • 1、http请求
                  • 2、http响应
                  • 3、http服务的事件
                • 3.3 http客户端
                • 四、网络服务与安全
                  • 4.1 TLS/SSL
                    • 1、密钥
                    • 2、数字证书
                  • 4.2 TLS服务
                    • 1、创建服务器端
                    • 2、创建客户端
                  • 4.3 HTTPS服务
                    • 1、创建https服务器端
                    • 2、创建https客户端
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档