物联网服务NodeJs-5天学习第二天篇② —— 网络编程(TCPHTTPWeb应用服务)
Posted 单片机菜鸟哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了物联网服务NodeJs-5天学习第二天篇② —— 网络编程(TCPHTTPWeb应用服务)相关的知识,希望对你有一定的参考价值。
【NodeJs-5天学习】第二天篇② ——网络编程
面向读者群体
- ❤️ 电子物联网专业同学,想针对硬件功能构造简单的服务器,不需要学习专业的服务器开发知识 ❤️
- ❤️ 业余爱好物联网开发者,有简单技术基础,想针对硬件功能构造简单的服务器❤️
- ❤️ 本篇创建记录 2023-03-12 ❤️
- ❤️ 本篇更新记录 2023-03-12 ❤️
技术要求
- 有HTML、CSS、JavaScript基础更好,当然也没事,就直接运行实例代码学习
专栏介绍
- 通过简短5天时间的渐进式学习NodeJs,可以了解到基本的服务开发概念,同时可以学习到npm、内置核心API(FS文件系统操作、HTTP服务器、Express框架等等),最终能够完成基本的物联网web开发,而且能够部署到公网访问。
🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
NodeJs是一个面向网络而生的平台,具有事件驱动
、无阻塞
、单线程
等特性,十分轻量,适合在分布式网络中扮演各种各样的角色。同时Node提供的API可以构建灵活的网络服务。
利用Node可以十分方便地搭建网络服务器。在web领域,大多数的编程语言需要专门的web服务器来做容器,如ASP、ASP.NET需要IIS作为服务器,php需要搭载Apache或者nginx环境等,JSP需要Tomcat服务器等。但Node就相对简单,只需要几行代码即可构建服务器,无需额外容器。
Node提供了net
、dgram
、http
、https
这4个模块,分别用于处理TCP
、UDP
、HTTP
、HTTPS
,适用于服务器和客户端。
1、client/server结构
说到服务器以及客户端,我们先来了解一下c/s结构。
C/S是Client/Server的缩写。
- 服务器通常采用高性能的
PC
、工作站
或小型机
,并采用大型数据库
系统,如Oracle、Sybase、Informix或 SQL Server。 - 客户端需要安装专用的
客户端软件
,比如浏览器
、App
、小程序
等等。当然从广义上来说,也有可能是一台服务器,比如一台服务器向另外一台服务器请求数据。
客户端发起一个请求内容给到服务器,服务器收到请求之后再解析请求并对请求内容作出响应,最终把响应内容返回给到客户端。
比如:
- 我们在浏览器上访问百度,本质上就是
浏览器client
向百度服务器server
发起请求百度网页内容,百度服务器收到请求
后解析成功并把百度首页页面内容响应
给回到浏览器。- 我们在b站上搜索某一个学习视频,本质上就是
app
向b站服务器
发起请求搜索请求,请求数据会带上要搜索的关键字
,b站服务器收到请求后会把关键字解析出来并且发起搜索服务
(搜索数据库等等),拿到搜索结果后再响应给回到app,这样app就可以展示我们要搜索的内容。
2. 服务器相关概念
在讲解 网络模块使用之前,我们需要先了解几个服务器概念。
- IP地址 和 端口号,表示一个web服务的唯一响应位置。
- DNS域名服务
2.1 IP地址 和 端口号
- IP地址
IP 地址就是互联网上每台计算机的唯一地址
,因此 IP 地址具有唯一性(这个是相对,比如我设置一个静态IP地址)。如果把“个人电脑”比作“一台电话”,那么“IP地址”就相当于“电话号码”,只有在知道对方 IP 地址的前提下,才能与对应的电脑之间进行数据通信。
IP 地址的格式:通常用“点分十进制
”表示成(a.b.c.d)的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。例如:用点分十进表示的 IP地址(192.168.1.1)
注意:
- 互联网中每台 Web 服务器,都有自己的 IP 地址,例如:大家可以在 Windows 的终端中运行
ping www.baidu.com
命令,即可查看到百度服务器的 IP 地址。
- 自己的电脑作为一个本地服务器时,可以在自己的浏览器中输入
127.0.0.1
(或者输入localhost
)这个IP 地址,就能把自己的电脑当做一台服务器进行访问了。思考:为啥不用Mac地址?
- 端口号
计算机中的端口号,就好像是现实生活中的门牌号
一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。
同样的道理,在一台电脑中,可以运行成百上千个 web 服务。每个 web 服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的 web 服务进行处理。
注意:
唯一性
决定了一个端口号
不能同时被多个服务占用- 默认情况下,当不填端口是默认是
80
。比如 http://www.baidu.com:80/index.html 等同于 http://www.baidu.com/index.html
2.2 DNS域名服务
尽管 IP 地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名
(Domain Name)地址。
IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器
(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供 IP 地址和域名之间的转换服务的服务器。
当我们用域名访问服务器时,比如访问
http://www.baidu.com/index.html
它的步骤是:
- 客户端向DNS服务器发起获取IP地址请求
- DNS服务器向客户端响应具体对应的IP地址
- 客户端向IP地址和端口号的服务器发起请求
当我们直接用IP地址和端口号访问服务器时,比如访问
http://xxxxxx:80/index.html
它的步骤是
- 客户端向IP地址和端口号的服务器发起请求
- 单纯使用 IP 地址,互联网中的电脑也能够正常工作。但是有了域名的加持,能让互联网的世界变得更加方便。
- 在开发测试期间, 127.0.0.1 对应的域名是 localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别。
如何设置本地DNS:
在windows
”→“System32
”→“drivers
”→“etc
" 目录下有一个hosts
文件 用记事本打开。hosts文件相当于一个本地的DNS,当你访问某个域名时,是先通过hosts进行解析的,没有找到才进一步通过外网DNS服务器进行解析,所以我们只需要在该文件中设置好,就不怕外网DNS解析不了了。
我们可以在这个文件上加上一些域名映射思考:
- 是不是直接IP地址和端口号速度更快?是否是一个优化速度方向?
3. 构建TCP
可以说,互联网上绝大部分的应用服务都是基于TCP的服务,TCP服务在网络应用中十分常见。
3.1 TCP
TCP又叫做传输控制协议
,在网络模型中属于传输层协议。很多应用层协议都是基于TCP构建,比较典型就是我们HTTP协议。
TCP是面向连接的协议,在传输之前需要3次握手形成会话。
只有会话形成之后,服务器端和客户端之间才能互相发送数据。在创建会话的过程中,服务器端和客户端分别提供一个套接字socket
,这两个套接字共同形成一个连接。服务器端与客户端则通过套接字实现两者之间连接的操作。
所以我们接下来需要分别学习 服务器端
和客户端
内容。
3.2 创建TCP服务器端
-
① 导入
net
模块 -
② 创建
TCP服务器实例
,监听socket事件 -
③ 配置服务器事件,启动服务器,监听客户端的请求
3.2.1 导入 net
模块
创建一个 TCP 服务器,对外提供 tcp 服务,需要导入 net 模块:
// 1.导入 net 模块
const net = require ('net')
推荐API学习:
在net模块中,需要了解到三个概念:
-
net 模块
net模块下包含了server、client、socket,它们三者的关系。
-
net.Server
用于创建一个 TCP 服务器。内部通过socket来实现与客户端的通信。
-
net.Socket
TCP Socket 的抽象。net.Socket 实例实现了一个双工流接口。 他们可以在用户创建客户端(使用 connect())时使用, 或者由 Node 创建它们,并通过connection
服务器事件传递给用户。
也就是说我们需要区分server和socket,从而区分服务器事件和socket事件。一个server上根据客户端连接的多少决定了socket数量的多少。
3.2.2创建TCP服务器实例
,监听socket事件
调用 http.createServer()
方法,即可快速创建一个 TCP 服务器实例:
// 2. 创建 TCP 服务器实例,回调方法表示有新的socket连接进来。
const server = net.createServer(function(socket)
// 5. 表示一个新的socket连接进来
var helloContent = `你好,我是服务端 $socket.remoteAddress:$socket.remotePort`
socket.write(helloContent);
// 以下是配置
// 闲置超时时间 2s
socket.setTimeout(2000)
// 禁用Nagle算法, socket.write立即发送数据
socket.setNoDelay(true)
// 启用长连接
socket.setKeepAlive(true)
console.log("服务端:收到来自客户端的请求 %j:%j",socket.remoteAddress,socket.remotePort);
// 以下是事件监听
// 5.1 data事件:当收到另一侧传来的数据时触发
// 事件传递的数据就是发送的数据
socket.on('data', function(data)
console.log("服务端:收到客户端数据,内容为",data.toString());
)
// 5.2 end事件:当连接中的任意一端发送了FIN数据时,将会触发此事件(了解TCP四次挥手)
socket.on('end', function(data)
console.log('服务端:客户端连接断开,FIN数据')
)
// 5.3 error事件:当有错误发生时,就会触发,参数为error
socket.on("error",function(err)
console.log("服务端:客户端异常关闭",err)
)
// 5.4 timeout事件:当 socket 空闲超时时触发,仅是表明 socket 已经空闲。用户必须手动关闭连接。
socket.on("timeout",function()
console.log("服务端:socket已经超时,手动关闭连接")
socket.end()
)
// 5.4 close事件:连接断开时触发。如果是因为传输错误导致的连接断开,则参数为error。
socket.on('close', function(error)
if(!error)
console.log("服务端:客户端连接断开 success! %j:%j",socket.remoteAddress,socket.remotePort)
else
console.log("服务端:客户端连接断开 error! %j:%j",socket.remoteAddress,socket.remotePort)
)
)
服务器可以同时与多个客户端保持连接(连接状态本质上来说就是socket的状态),
对于每个连接来说就是典型的可写可读Stream
流对象。Stream流对象可以用于服务器端和客户端之间的通信,也就是说通过data事件可以从一端读取另外一端发过来的数据,也可以通过write() 方法从一端向另外一端发送数据。同时我们可以通过事件来监听socket的状态:
- data事件
- end事件
- connect事件
- error事件
- close事件
- timeout事件
关于Socket的api可以参考:
我们下面重点讲解socket事件,从事件来理解socket状态。
3.2.2.1 connect 事件
成功建立 socket 连接时触发。
一般是在 net.createServer(callback) 中,回调函数被执行了表示触发了connect事件。
// 2. 创建 TCP 服务器实例,回调方法表示有新的socket连接进来。
const server = net.createServer(function(socket)
// 5. 表示一个新的socket连接进来
socket.write("你好,我是服务端");
console.log("服务端:收到来自客户端的请求 %j:%j",socket.remoteAddress,socket.remotePort);
)
3.2.2.2 data 事件
当收到另一侧传来的数据时触发,事件传递的数据就是发送的数据。这个就是最重要事件,处理数据。主要关联 socket.write
方法
// 2. 创建 TCP 服务器实例,回调方法表示有新的socket连接进来。
const server = net.createServer(function(socket)
// 5. 表示一个新的socket连接进来
socket.write("你好,我是服务端");
console.log("服务端:收到来自客户端的请求 %j:%j",socket.remoteAddress,socket.remotePort);
// 5.1 data事件:当收到另一侧传来的数据时触发
// 事件传递的数据就是发送的数据
socket.on('data', function(data)
console.log("服务端:收到客户端数据,内容为",data.toString());
)
)
3.2.2.3 error 事件
当有错误发生时,就会触发,参数为error。比如传输连接突然断开,连接异常等等,一般这种情况下我们需要做一些纠错处理,比如重连、关闭连接等等
// 2. 创建 TCP 服务器实例,回调方法表示有新的socket连接进来。
const server = net.createServer(function(socket)
// 5. 表示一个新的socket连接进来
socket.write("你好,我是服务端");
console.log("服务端:收到来自客户端的请求 %j:%j",socket.remoteAddress,socket.remotePort);
// 5.1 data事件:当收到另一侧传来的数据时触发
// 事件传递的数据就是发送的数据
socket.on('data', function(data)
console.log("服务端:收到客户端数据,内容为",data.toString());
)
// 5.3 error事件:当有错误发生时,就会触发,参数为error
socket.on("error",function(err)
console.log("服务端:客户端异常关闭",err)
)
)
3.2.2.4 end 事件
当数据传输准备结束时,连接中的任意一端发送了FIN数据,将会触发此事件(了解TCP四次挥手
)。此事件会比close事件更早。
半关闭 socket。它发送一个
FIN
包。可能服务器仍在发送数据。可以调用socket.end方法。
// 2. 创建 TCP 服务器实例,回调方法表示有新的socket连接进来。
const server = net.createServer(function(socket)
// 5. 表示一个新的socket连接进来
socket.write("你好,我是服务端");
console.log("服务端:收到来自客户端的请求 %j:%j",socket.remoteAddress,socket.remotePort);
// 5.1 data事件:当收到另一侧传来的数据时触发
// 事件传递的数据就是发送的数据
socket.on('data', function(data)
console.log("服务端:收到客户端数据,内容为",data.toString());
)
// 5.2 end事件:当连接中的任意一端发送了FIN数据时,将会触发此事件(了解TCP四次挥手)
socket.on('end', function(data)
console.log('服务端:客户端连接断开,FIN数据')
)
)
3.2.2.5 close 事件
当socket连接真正断开时触发。如果是因为传输错误导致的连接断开,则参数为error。
// 2. 创建 TCP 服务器实例,回调方法表示有新的socket连接进来。
const server = net.createServer(function(socket)
// 5. 表示一个新的socket连接进来
socket.write("你好,我是服务端");
console.log("服务端:收到来自客户端的请求 %j:%j",socket.remoteAddress,socket.remotePort);
// 5.1 data事件:当收到另一侧传来的数据时触发
// 事件传递的数据就是发送的数据
socket.on('data', function(data)
console.log("服务端:收到客户端数据,内容为",data.toString());
)
// 5.2 end事件:当连接中的任意一端发送了FIN数据时,将会触发此事件(了解TCP四次挥手)
socket.on('end', function(data)
console.log('服务端:客户端连接断开,FIN数据')
)
// 5.4 close事件:连接断开时触发。如果是因为传输错误导致的连接断开,则参数为error。
socket.on('close', function(error)
if(!error)
console.log("服务端:客户端连接断开 success! %j:%j",socket.remoteAddress,socket.remotePort)
else
console.log("服务端:客户端连接断开 error! %j:%j",socket.remoteAddress,socket.remotePort)
)
)
3.2.2.6 timeout 事件
当 socket 空闲超时时触发,仅是表明 socket 已经空闲。用户必须手动关闭连接。这里会受到 socket.setTimeout
方法影响.
// 以下是配置
// 闲置超时时间 2s
socket.setTimeout(2000)
// 5.4 timeout事件:当 socket 空闲超时时触发,仅是表明 socket 已经空闲。用户必须手动关闭连接。
socket.on("timeout",function()
console.log("服务端:socket已经超时,需要手动关闭连接")
socket.end()
)
3.2.3 配置服务器事件,启动服务器,监听客户端的请求
服务器实例启动监听客户端请求,同时监控一些服务器事件:
// 3. 捕获server对象发生错误
server.on("error",()=>
console.log("server对象发生error事件")
)
// 4. 启动服务器,开始监听
server.listen(8266, function()
var address=server.address()
console.log("服务端:开始监听来自客户端的请求 %j ",address)
)
3.2.4 完整步骤代码
创建一个test_tcp_server.js文件
写入以下代码:
// 1.导入 net 模块
const net = require ('net')
// 2. 创建 TCP 服务器实例,回调方法表示有新的socket连接进来。
const server = net.createServer(function(socket)
// 5. 表示一个新的socket连接进来
var helloContent = `你好,我是服务端 $socket.remoteAddress:$socket.remotePort`
socket.write(helloContent);
// 以下是配置
// 闲置超时时间 2s
socket.setTimeout(2000)
// 禁用Nagle算法, socket.write立即发送数据
socket.setNoDelay(true)
// 启用长连接
socket.setKeepAlive(true)
console.log("服务端:收到来自客户端的请求 %j:%j",socket.remoteAddress,socket.remotePort);
// 以下是事件监听
// 5.1 data事件:当收到另一侧传来的数据时触发
// 事件传递的数据就是发送的数据
socket.on('data', function(data)
console.log("服务端:收到客户端数据,内容为",data.toString());
)
// 5.2 end事件:当连接中的任意一端发送了FIN数据时,将会触发此事件(了解TCP四次挥手)
socket.on('end', function(data)
console.log('服务端:客户端连接断开,FIN数据')
)
// 5.3 error事件:当有错误发生时,就会触发,参数为error
socket.on("error",function(err)
console.log("服务端:客户端异常关闭",err)
)
// 5.4 timeout事件:当 socket 空闲超时时触发,仅是表明 socket 已经空闲。用户必须手动关闭连接。
socket.on("timeout",function()
console.log("服务端:socket已经超时,手动关闭连接")
socket.end()
)
// 5.4 close事件:连接断开时触发。如果是因为传输错误导致的连接断开,则参数为error。
socket.on('close', function(error)
if(!error)
console.log("服务端:客户端连接断开 success! %j:%j",socket.remoteAddress,socket.remotePort)
else
console.log("服务端:客户端连接断开 error! %j:%j",socket.remoteAddress,socket.remotePort)
)
)
// 3. 捕获server对象发生错误
server.on("error",()=>
console.log("server对象发生error事件")
)
// 4. 启动服务器,开始监听
server.listen(8266, function()
var address=server.address()
console.log("服务端:开始监听来自客户端的请求 %j ",address)
)
执行一下test_tcp_server.js 看看。
这里表示已经开始监听TCP客户端请求,我们使用Tcpudp一些工具来测试一下。这里我使用 NetAssist 网络调试助手。
然后,调试助手作为TCP Client访问一下Node TCP Server,观察一下打印信息:
因为我们配置了setTimeout,所以这里会发现如果不发送数据,server会主动断开连接。剩下就是一个socket的生命周期事件(理解socket生命周期就意味着我们需要了解TCP状态机
)。
上面有一个方法需要注意:
socket.setNoDelay(true)
TCP针对网络中的小数据包有一定的优化策略:Nagle算法
。如果每次只发送一个字节的内容而不优化,网络中将充满只有极少数有效数据的数据包,将十分浪费网络资源。Nagle算法针对这种情况,要求缓冲区的数据达到一定数量或者一定时间后才将其发出,所以小数据包将会被Nagle算法合并,以此来优化网络。这种优化虽然使网络宽带被有效地使用,但是数据有可能被延迟发送。
在Node中,TCP默认启用了Nagle算法,可以通过调用 socket.setNoDelay(true)
去掉Nagle算法,使得write可以立即发送数据到网络中。但是,尽管在网络的一端调用write会触发另外一端的data事件,但是并不意味着每次write都会触发一次data事件,在关掉Nagle算法后,另一端可能会将接收到的多个小数据包合并,然后只触发一次data事件。
3.3 创建TCP客户端
① 导入 net 模块
② 创建TCP客户端实例,监听socket事件
③发起客户端的请求
3.3.1 导入 net
模块
创建一个 TCP 客户端,需要导入 net 模块:
// 1.导入 net 模块
const net = require ('net')
3.3.2 创建TCP客户端实例,监听socket事件
var PORT = 8266;
var HOST = '127.0.0.1';
// 2. 创建 TCP 客户端实例
const client = net.createConnection(PORT, HOST);
client.on('connect', function(socket)
console.log(`客户端:已经与服务端建立连接`);
)
client.on('data', function(data)
console.物联网服务NodeJs-5天学习第二天篇① ——fs文件系统
物联网服务NodeJs-5天学习第三天实战篇② ——基于物联网的WiFi自动打卡考勤系统
物联网服务NodeJs-5天学习第四天存储篇② ——NodeJs连接操作mysql 8.0