1+1=10

扬长避短 vs 取长补短

Node.js 学习笔记(四)

接前面

  • JavaScript基础学习:ECMAScript 基础知识
  • Node.js学习笔记(一):Node.js是什么,如何安装,如何简单使用,工作目录与脚本所在目录获取
  • Node.js学习笔记(二):命令行参数、环境变量、标准输入、标准输出、标准出错、信号处理、程序返回值,打包
  • Node.js学习笔记(三):fs模块API,三种风格——同步、异步回调、异步promise,三种层级——底层、高层、流
  • Node.js学习笔记(四):网络功能:tcp、udp、tls、http、...

继续了解Node.js。看看其内置网络模块,记个流水账:

  • net
  • dgram
  • tls
  • http
  • http/2
  • https

从哪儿开始?

依靠JavaScript起家,网络功能一定是Nodejs强项了,从什么地方开始了解它好一点?

先从熟悉的Qt开始,先列个表格,大致对比一下:

协议/技术 Qt Node.js Python
Deamon Socket/Named Pipe QLocalServer, QLocalSocket net.Server, net.Socket socket, socketserver.UnixStreamServer
TCP QTcpServer, QTcpSocket net.Server, net.Socket socket, socketserver.TCPServer
UDP QUdpSocket dgram.Socket socket, socketserver.UDPServer
SCTP QSctpServer, QSctpSocket sctp(三方) (三方)
SSL/TLS QSslSocket, QSslServer tls.Server ssl.SSLSocket, ssl.SSLServer
HTTP QHttpServer(GPL), QNetworkAccessManager http.Server,http2 http.server, http.client
HTTPS QHttpServer(GPL), QNetworkAccessManager https.Serverhttp2 http.server
WebSocket QWebSocketServer, QWebSocket ws(三方) websockets(三方)
DNS QDnsLookup dns dnspython(三方)

例子?

几个小例子,找找感觉

老规矩,每个.mjs文件都是独立的程序,可以直接运行测试

本地IPC通讯

nodejs的net模块提供了IPC(local server),和TCP功能。

先看看如何使用net模块,通过windows下命名管道或Unix的deamon socket实现的 本机IPC功能。

服务端:

  • 使用 net.createServer() 创建server对象
  • 使用 server.listen() 开启监听【named pipe路径 或 deamon socket路径】
  • 使用server.on() 来响应新的socket连接
// myipcserver.mjs

import * as net from 'node:net';
import * as process from 'node:process';

const server = net.createServer();

let ipcPath;
if (process.platform === 'win32') {
    ipcPath = '\\\\.\\pipe\\debaotest_pipe';
} else {
    ipcPath = '/tmp/debaotest.sock';
}

server.on('connection', (socket) => {
    console.log('Client connected');

    socket.on('data', (data) => {
        console.log(`From Client: ${data}`);
    });

    socket.write('Hello 1+1=10 from server');

    socket.on('end', () => {
        console.log('Client disconnected');
    });
});

server.listen(ipcPath, () => {
    console.log(`Server listening on ${ipcPath}`);
});

客户端:

  • 使用 net.createConnection() 连接服务端并创建client
  • 使用 client.write() 发送数据
  • 使用 client.on() 响应接受数据
  • client收到服务端数据后,调用client.end() 结束连接
// myipcclient.mjs

import * as net from 'node:net';
import * as process from 'node:process';

let ipcPath;
if (process.platform === 'win32') {
    ipcPath = '\\\\.\\pipe\\debaotest_pipe';
} else {
    ipcPath = '/tmp/debaotest.sock';
}

const client = net.createConnection({ path: ipcPath }, () => {
    console.log('Connected to server');

    client.write('Hello 1+1=10 from client');
});

client.on('data', (data) => {
    console.log(`From Server: ${data}`);
    client.end();
});

client.on('end', () => {
    console.log('Disconnected from server');
});

客户端输出结果:

Connected to server
From Server: Hello from server
Disconnected from server

只要不调用client.end(),这个客户端程序就会一直运行。说明nodejs在跑事件循环,client.end()会结束事件循环,这部分功课,后面需要补一补。

TCP通讯

TCP和前面的IPC用起来基本一样。

服务端

只需要把ipcpath去掉,让listen监听TCP端口就可以了:

let port = 3000;
server.listen(port, () => {
    console.log(`Server listening on ${port}`);
});

listen用法

server.listen(3000, callback);
server.listen(3000, "127.0.0.1", callback);
server.listen({ host: '0.0.0.0', port: 3000 }, callback);

客户端

客户端和IPC客户端也一样,只要改一下createConnection(),连接指定的地址和端口就行了:

const client = net.createConnection(3000, () => {
    console.log('Connected to server');

    client.write('Hello from client');
});

createConnection用法:

net.createConnection(3000, callback);
net.createConnection(3000, '127.0.0.1', callback);
net.createConnection({ port: 3000, host: '127.0.0.1' }, callback);

UDP 通讯

udp 在 dgram模块中。

服务端

  • 使用 dgram.createSocket() 创建UDP server
  • 使用 server.bind() 绑定监听端口
  • 使用server.on() 监听数据
  • 使用server.send() 发送数据
import * as dgram from 'node:dgram';

const server = dgram.createSocket('udp4');

server.on('message', (msg, rinfo) => {
    console.log(`Received message from ${rinfo.address}:${rinfo.port} - ${msg}`);
    server.send('Hello 1+1=10 from UDP server', rinfo.port, rinfo.address, (err) => {
        if (err) {
            console.error(err);
            server.close();
        }
    });
});

server.on('listening', () => {
    const address = server.address();
    console.log(`UDP Server listening on ${address.address}:${address.port}`);
});

server.bind(3000); 

客户端

  • 使用 dgram.createSocket() 创建 client
  • 使用client.send() 发送UDP数据
  • 使用client.on() 监听数据
  • 使用client.close() 关闭连接
import * as dgram from 'node:dgram';

const client = dgram.createSocket('udp4');
const message = 'Hello 1+1=10 from UDP client';

client.send(message, 3000, '127.0.0.1', (err) => {
    if (err) {
        console.error(err);
        client.close();
    } else {
        console.log(`Message sent: ${message}`);
    }
});

client.on('message', (msg, rinfo) => {
    console.log(`Received response from ${rinfo.address}:${rinfo.port} - ${msg}`);
    client.close();
});

调用client.close() 之前,进程一直存在。

Http通讯

nodejs中有三个模块:

  • http 用于创建基于 HTTP 协议的服务器和客户端。

  • https 用于创建基于 TLS/SSL 的安全的 HTTP 服务器和客户端。

  • http2 用于创建基于 HTTP/2 协议的服务器和客户端,支持 TLS/SSL 进行加密

先看看http模块

服务端

  • http.createServer() 创建 server
  • server.listen() 启动监听
import * as http from 'node:http';

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello 1+1=10!\n');
});

const PORT = 3000;

server.listen(PORT, () => {
    console.log(`Server running at http://localhost:${PORT}/`);
});

客户端

浏览器是最好的客户端。

不过nodejs写客户端来做些测试也不复杂:

  • http.request() 用于创建请求req和设置响应回调
  • req.on() 用于处理错误
  • req.end() 标志请求结束,开始发送请求
import * as http from 'node:http';

const options = {
    hostname: 'localhost',
    port: 3000,
    path: '/',
    method: 'GET',
};

const req = http.request(options, (res) => {
    let data = '';

    res.o('data', (chunk) => {
        data += chunk;
    });

    res.on('end', () => {
        console.log(`Response from server: ${data}`);
    });
});

req.on('error', (e) => {
    console.error(`Error: ${e.message}`);
});

req.end();

HTTPS

https 和 http2都可以,看看 http2:

  • 准备证书和私钥
  • http2.createSecureServer() 创建server
  • server.listen() 进行监听
import * as fs from 'node:fs';
import * as http2 from 'node:http2';

const options = {
    key: fs.readFileSync('127_0_0_1_ssl_key.pem'),
    cert: fs.readFileSync('127_0_0_1_ssl_cert.pem'),
};

const server = http2.createSecureServer(options, (req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello 1+1=10 (over HTTP/2)!\n');
});

server.listen(3000, () => {
    console.log('HTTP/2 Server running on port 3000');
});

使用浏览器访问:https://127.0.0.1:3000即可。

tls 通讯

服务端

  • 准备私钥和证书
  • 使用tls.createServer() 创建server,设置回调
  • server.listen() 启动监听
import * as fs from 'node:fs';
import * as tls from 'node:tls';

const options = {
    key: fs.readFileSync('127_0_0_1_ssl_key.pem'),
    cert: fs.readFileSync('127_0_0_1_ssl_cert.pem'),
};

const server = tls.createServer(options, (cleartextStream) => {
    console.log('Client connected');

    cleartextStream.write('Hello 1+1=10 from TLS server!\n');
    cleartextStream.setEncoding('utf8');

    cleartextStream.on('data', (data) => {
        console.log(`Received from client: ${data}`);
    });

    cleartextStream.on('end', () => {
        console.log('Client disconnected');
    });
});

const PORT = 3000;

server.listen(PORT, () => {
    console.log(`TLS Server running on port ${PORT}`);
});

客户端

  • tls.connect() 创建client进行连接
  • client.write() 发送数据
  • client.on() 监听
import * as tls from 'node:tls';

const options = {
    host: 'localhost',
    port: 3000,
    rejectUnauthorized: false, // for test only!!!
};

const client = tls.connect(options, () => {
    console.log('Connected to TLS server');

    client.write('Hello from TLS client!\n');
});

client.setEncoding('utf8');

client.on('data', (data) => {
    console.log(`Received from server: ${data}`);
});

client.on('end', () => {
    console.log('Connection closed');
});

参考

  • https://nodejs.org/api/net.html
  • https://nodejs.org/api/dgram.html
  • https://docs.python.org/3/library/socket.html
  • https://docs.python.org/3/library/socketserver.html
  • https://doc.qt.io/qt-6/qthttpserver-index.html

Comments