抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文主要解释了网络通信的背景知识,以及如何在Node项目中使用网络通信功能。

环境

Windows 10 企业版 LTSC 21H2
Node 18.14.0
NPM 9.3.1
NVM 1.1.12

1 概念

1.1 网络通信

网络通信就是在两个或两个以上的设备之间传输数据。

计算机网络将分布在不同地理区域的计算机与外部设备连成一个巨大的网络系统,从而使众多的计算机可以方便地互相传递信息。

实现网络通信需要解决两个问题:

  1. 如何准确地定位网络上一台或多台主机,如何定位主机上的特定的应用。
  2. 找到主机后如何可靠高效地进行数据传输。

解决办法:

  1. 使用IP地址和端口号识别网络上的每个设备。
  2. 使用网络通讯协议保证设备之间数据传输的安全可靠。

说明:

  • IP地址:网络设备唯一的数字标识,解决了设备在网络上的识别问题。
  • 域名:为了方便记忆IP地址而创建的概念,一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。
  • 域名解析服务(DNS,Domain Name Service):在实际传输数据前将域名解析为IP地址的功能。
  • 域名解析服务器(DNS,Domain Name Server):实现域名解析的服务器,当用户在浏览器输入域名时,浏览器首先通过DNS服务器将域名转换为IP地址,然后在浏览器根据IP地址进行实际的数据传输。
  • 端口:计算机上每个应用进行网络通信的唯一标识,解决了应用在计算机上的识别问题。
  • 网络通讯协议:规定了网络上的设备传输数据的格式。

1.2 网络模型

网络模型规定了网络上的计算机之间以何种规则进行通信。

常见的网络模型对比如下:

OSI七层网络模型 TCP/IP四层网络模型 TCP/IP五层网络模型 网络协议 工作设备
应用层 应用层 应用层 HTTP HTTPS FTP SMTP POP3 计算机及应用
表示层
会话层
传输层 传输层 传输层 TCP UDP 四层交换机 四层路由器
网络层 网络层 网络层 IP ICMP ARP RARP 三层交换机 路由器 网关
数据链路层 网络接口层 数据链路层 Ethernet PPP 交换机 网桥
物理层 物理层 USB 中继器 集线器

1.3 HTTP协议

HTTP全称为Hyper Text Transport Protocol,中文名称为超文本传输协议。

HTTP是一种基于TCP/IP的应用层通信协议,协议详细规定了浏览器和万维网服务器之间互相通信的规则。

协议中主要规定了两个方面的内容:

  • 客户端:用来向服务器发送数据,可以将发送的数据称为请求报文。
  • 服务端:向客户端返回数据,可以将返回的数据称为响应报文。

1.4 请求报文

请求报文是客户端向服务器发送的请求信息,用于在客户端和服务器之间进行通信,是HTTP协议的重要组成部分。

组成部分:

  1. 请求行:包含请求方法、请求的资源路径和HTTP协议版本。
  2. 请求头:包含客户端发送给服务器的额外信息,如客户端类型、接受的媒体类型等。
  3. 空行:用于分隔请求头和请求体。
  4. 请求体:可选部分,用于包含要发送给服务器的数据,如表单数据、JSON数据等。

示例:

txt
1
2
3
4
5
6
7
8
9
10
11
POST /login HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 29

username=JohnDoe&password=12345

说明:

  1. 请求行POST /login HTTP/1.1由三部分组成:
  • POST是请求方法,表示以何种方法请求获取指定的资源,比如GET/POST/PUT/DELETE等。
  • /login是URL统一资源定位符,表示请求资源的位置。
  • HTTP/1.1是协议版本,表示使用的协议版本。
  1. 请求头:
  • Host:表示请求的主机名,即服务器的域名。
  • User-Agent:表示客户端的用户代理信息,包括浏览器类型、版本、操作系统等,用于服务器识别客户端的环境。
  • Accept:表示客户端可以接受的媒体类型。
  • Accept-Language:表示客户端可以接受的语言。
  • Accept-Encoding:表示客户端可以接受的编码方式。
  • Connection:表示客户端希望在请求完成后的连接方式。使用keep-alive表示保持连接,使用close表示关闭连接。
  • Content-Type:表示请求体中的数据类型是表单数据。
  • Content-Length:表示请求体的长度,使用字节作为单位。
  1. 空行:
  • 分隔请求头和请求体。
  1. 请求体:
  • 如果是GET请求,通常不包含请求体。如果是POST或PUT请求,可能会包含表单数据、JSON数据等,用于向服务器提交信息。

常见的请求方法:

请求方法 功能 传参位置 安全性 使用场景
GET 获取资源 参数在URL中 较低 获取数据,如搜索用户
POST 提交数据创建或更新资源 参数在请求体中 较高 提交数据,如表单提交用户信息
PUT 上传全部数据更新资源 参数在请求体中 较高 更新整个资源,如更新用户信息
DELETE 删除资源 参数在URL中或请求体中 较低 删除资源,如删除用户

传参限制:

  • 如果是在URL传参,受URL长度限制,参数长度最大为2K。
  • 如果是在请求体传参,参数长度没有限制,但长度过长会受网速影响导致页面加载异常。

1.5 响应报文

响应报文是服务器在接收到客户端的请求后,向客户端返回的响应信息。

组成部分:

  1. 状态行:包含HTTP协议版本、状态码和状态消息,用于告知客户端请求的处理结果。
  2. 响应头:包含服务器发送给客户端的额外信息,如服务器类型、内容类型、内容长度等。
  3. 空行:用于分隔响应头和响应体。
  4. 响应体:可选部分,包含服务器返回给客户端的资源内容,如HTML页面、JSON数据等。

示例:

txt
1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Date: Tue, 15 Nov 2022 12:34:56 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: application/json; charset=UTF-8
Content-Length: 75
Connection: keep-alive

{"status": "success", "message": "Data retrieved successfully", "data": {"id": 1, "name": "John Doe"}}

说明:

  1. 状态行HTTP/1.1 200 OK由三部分组成:
  • HTTP/1.1是协议版本,表示使用的协议版本。
  • 200是状态码,表示请求的结果,比如200表示成功等。
  • OK是状态消息,表示对状态码的文本描述,比如OK表示成功等。
  1. 请求头:
  • Date:表示服务器发送响应的日期和时间。
  • Server:表示服务器的软件信息。
  • Content-Type:表示响应体的内容类型和编码方式。
  • Content-Length:表示响应体的长度,使用字节作为单位。
  • Connection:表示服务器希望在发送完响应后的连接方式。使用keep-alive表示保持连接,使用close表示关闭连接。
  1. 空行:
  • 分隔响应头和响应体。
  1. 请求体:
  • 如果是HTML页面,就解析并显示页面。如果是JSON数据,就解析并处理数据。

状态码的含义可以在网站上查看:HTTP 响应状态码

2 使用

使用Node中内置的http模块,可以实现网络通信的功能。

2.1 简单使用

示例:

server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 引入模块
const http = require('http');
// 创建服务
let server = http.createServer((req, res) => {
// 忽略特殊请求
if (req.url == '/favicon.ico') {
res.end();
return;
}
// 获得请求方法
console.log(req.method);
// 获得请求URL,只包含路径和查询字符串,不包含协议和域名
console.log(req.url);
// 获得请求头,属性名被转为小写
console.log(req.headers);
// 获得请求头中的属性值
console.log(req.headers.host);
// 实例化URL类
const url = new URL(req.url, 'http://127.0.0.1:9000');
// 获得请求URL
console.log(url);
// 获得请求URL中的路径
console.log(url.pathname);
// 获得请求URL中的查询字符串
console.log(url.search);
// 获得请求URL中的查询字符串中指定属性名对应的属性值
console.log(url.searchParams.get('name'));
// 获得请求体,使用文件流读取
let body = '';
req.on('data', chunk => {
body += chunk;
});
// 获得请求体,读取后的处理
req.on('end', () => {
console.log(body);
// 设置响应状态码,默认是200
res.statusCode = 200;
// 设置响应状态码描述,默认是OK
res.statusMessage = 'OK';
// 设置响应头
res.setHeader('Content-Type', 'text/html;charset=UTF-8');
// 设置响应体,可以不用或使用多次write方法
res.write('hello');
// 设置响应体,可以不用或使用多次write方法
res.write('你好');
// 设置响应体,必须使用并且只能使用一次end方法
res.end();
});
});
// 监听服务
server.listen(9000, () => {
console.log('服务已经启动');
console.log('http://127.0.0.1:9000/');
});

在命令行执行node server.js命令启动服务,在浏览器访问http://127.0.0.1:9000/请求服务。

注意:

  1. 在命令行使用node server.js启动服务。
  2. 在命令行按下Ctrl + C停止服务。
  3. 当服务启动后,更新代码后必须重启服务才能生效。

端口号被占用报错:

bash
1
Error: listen EADDRINUSE: address already in use :::9000

端口号被占用解决办法:

  1. 关闭当前正在运行监听端口的服务,推荐使用这种方式。
  2. 使用其他端口号。

默认端口:

  • HTTP协议默认端口是80,可以省略。
  • HTTPS协议的默认端口是443,可以省略。

2.2 创建服务

语法:

js
1
http.createServer([options][, requestListener])

返回:

  • Object对象,封装了HTTP服务信息。

参数:

  • options:配置对象,封装了多个服务配置,可选。
  • requestListener:请求监听器,封装了请求和响应的方法,可选。

2.3 监听服务

语法:

js
1
server.listen([port[, host[, backlog]]][, callback])

参数:

  • port:监听端口号。
  • host:监听主机地址。
  • backlog:监听日志。
  • callback:监听回调方法。

2.4 获得请求

获得请求:

js
1
2
3
4
5
6
7
8
// 获得请求方法
console.log(req.method);
// 获得请求URL,只包含路径和查询字符串,不包含协议和域名
console.log(req.url);
// 获得请求头,属性名被转为小写
console.log(req.headers);
// 获得请求头中的属性值
console.log(req.headers.host);

获得请求URL的路径和字符串有两种方式:

使用URL类获得请求URL的信息:

js
1
2
3
4
5
6
7
8
9
10
// 实例化URL类
const url = new URL(req.url, 'http://127.0.0.1:9000');
// 获得请求URL
console.log(url);
// 获得请求URL中的路径
console.log(url.pathname);
// 获得请求URL中的查询字符串
console.log(url.search);
// 获得请求URL中的查询字符串中指定属性名对应的属性值
console.log(url.searchParams.get('name'));

使用url模块获得请求URL的信息:

js
1
2
3
4
5
6
7
8
9
const url = require('url');
// 获得请求URL
console.log(url.parse(req.url));
// 获得请求URL中的路径
console.log(url.parse(req.url).pathname);
// 获得请求URL中的查询字符串
console.log(url.parse(req.url).query);
// 获得请求URL中的查询字符串中指定属性名对应的属性值
console.log(url.parse(req.url, true).query.name);

这种方式已经过时了,不建议使用。

使用文件流读取请求体数据:

js
1
2
3
4
5
6
7
8
9
// 获得请求体,使用文件流读取
let body = '';
req.on('data', chunk => {
body += chunk;
});
// 获得请求体,读取后的处理
req.on('end', () => {
console.log(body);
});

2.5 返回响应

返回响应:

js
1
2
3
4
5
6
7
8
9
10
11
12
// 设置响应状态码,默认是200
res.statusCode = 200;
// 设置响应状态码描述,默认是OK
res.statusMessage = 'OK';
// 设置响应头
res.setHeader('Content-Type', 'text/html;charset=UTF-8');
// 设置响应体,可以不用或使用多次write方法
res.write('hello');
// 设置响应体,可以不用或使用多次write方法
res.write('你好');
// 设置响应体,必须使用并且只能使用一次end方法
res.end();

3 网页加载

3.1 原理

网页资源的加载都是循序渐进的,首先获取HTML的内容, 然后发送获取其他资源的请求,比如CSS、JS、图片等。

资源分类:

  • 静态资源是指那些内容固定不变的文件,如HTML、CSS、JS、图片等。这些文件在创建后,内容不会随着用户请求或时间的变化而改变。
  • 动态资源是指那些内容会根据用户请求、时间或其他条件变化的资源。它们通常由服务器端的程序生成,动态资源的内容在每次请求时可能会不同。

3.2 网站根目录

网站根目录是指在网站文件结构中最高层级的目录,它包含了网站的所有文件和子目录。

当用户在浏览器中输入网站的域名时,服务器会从网站根目录中查找默认的首页文件,然后将其发送给用户。

3.3 URL路径

网址中的URL路径分为绝对路径和相对路径:

  • 绝对路径是指从网站的根目录开始的完整路径,它包含了从根目录到目标资源的所有层级信息。绝对路径以/开头,表示从根目录开始。
  • 相对路径是指相对于当前页面或资源的位置的路径。它不以/开头,而是根据当前页面的位置来确定目标资源的位置。

3.4 媒体类型

媒体类型(MIME,Multipurpose Internet Mail Extensions)是一种标准,用来表示文档、文件或字节流的性质和格式。它在互联网通信中广泛使用,特别是在HTTP协议中,用于指定请求和响应的内容类型。

MIME类型由两个主要部分组成,它们之间用/分隔:

  1. 类型(Type):表示媒体的通用类别,例如文本、图像、音频、视频等。
  2. 子类型(Subtype):指定类型中的具体格式或编码方式,例如html、css、png等。

下面是常见的MIME类型:

类型 子类型 MIME类型
文本 纯文本文件 text/plain
文本 HTML页面 text/html
文本 CSS样式 text/css
文本 JS脚本 text/javascript
application/javascript
图像 JPEG图像 image/jpeg
图像 PNG图像 image/png
图像 GIF图像 image/gif
图像 SVG矢量图像 image/svg+xml
音频 MP3音频 audio/mpeg
音频 WAV音频 audio/wav
音频 AAC音频 audio/aac
视频 MP4视频 video/mp4
视频 MPEG视频 video/mpeg
应用 PDF文档 application/pdf
应用 JSON数据 application/json
应用 XML数据 application/xml
应用 ZIP压缩 application/zip
应用 JS脚本 application/javascript
混合 表单数据,常见于文件上传 multipart/form-data
未知 未知类型,建议独立存储 application/octet-stream

评论