服务端编程(一)
一.node基础
1.学习NodeJS的意义
-
了解客户端浏览器与服务端后台的交互过程
,可以在以后的前端开发工作中与后台人员之间的沟通更加容易理解- 虽然以后工作中不一定用的上nodejs,能够在日常工作中与公司后台人员之间的沟通变得更加轻松
-
了解服务端开发的一些特性,当我们前端与后台交互出现bug问题时,能够更快速的定位bug是出现在自己的客户端还是别人的服务端。
-
了解服务端开发的过程,可以为以后的职业发展打下一定的基础(全栈工程师)
2.什么是nodejs
- Node.js官网地址:https://nodejs.org/en/
- 中文:http://nodejs.cn/api/
- Node 是一个构建于 Chrome V8引擎之上的一个Javascript 运行环境
- Node
是
一个运行环境
,作用是让js拥有开发服务端的功能
- Node
- *Node使用事件驱动 、 非阻塞IO模型 异步读写–异步非阻塞)使得它非常的轻量级和高效
- Node中绝大多数API都是异步(类似于ajax),目的是提高性能
- Node中的
NPM
是世界上最大的开源库生态系统(类似于github)- NMP官网:https://www.npmjs.com
1. 事件驱动:通过事件或状态的变化来进行应用程序的流程控制,一般通过事件监听完成,一旦事件被检测到,则调用相应的回调函数。
2. 非阻塞IO模型:非阻塞,意味着不用等待前一个IO操作返回就可以直接执行其他的IO操作。
3. 异步操作的两大特点
3.1 异步非阻塞:异步操作不会阻塞后面代码的执行
3.2 异步操作的执行顺序不依赖于异步操作代码的书写顺序
3. Node.js环境安装
3.1如何确认当前电脑是否已经安装了Node环境
- 打开终端,输入
node -v
,如果能看到版本号则说明当前电脑已经安装Node环境,如果提示Node不是内部或外部命令
,则表示未安装- 一旦安装了node,则会自动一并安装npm
4. 服务端js与客户端js区别
-
客户端JavaScript由三部分组成
- ECMAScript:确定js的语法规范
- DOM:js操作网页内容
- BOM:js操作浏览器窗口
-
node中的JavaScript
- 组成
- ECMAScript
- 核心模块
- 第三方模块
- 基本的语法和写法和之前的js没有本质的区别
- 在nodejs中使用dom与bom的api程序会报错
- 服务器端没有界面
- 压根不需要操作浏览器和页面元素
- 同样的,在html中写node代码也会报错
- 后台代码的运行需要后台的运行环境
- 组成
-
第一个node.js代码
let arr = [1, 3, 5, 7, 9] for (let i = 0; i < arr.length; i++) { console.log(i) } // 浏览器中的js的组成: // ECMAScript + dom + bom // alert('你好呀') // console.log(window.name) let a = document.querySelector('a') //显示错误:ReferenceError: document is not defined // node中的js的组成: // ECMAScript 核心模块 第三方模块 // 意味着在node环境中运行dom或者bom相关的代码会报错 let fs = require('fs') console.log(fs) //0 1 2 3 4
5. 运行Node.js程序
1.REPL:交互解释器
- Node运行环境的另一种叫法,作用是解析执行js代码
- 用法
- 第一种方式:直接双击打开 node.exe,然后写js代码
- 第二种方式:
- 先在终端先执行node,进入node环境
- 然后写js代码
2.使用终端命令node [js文件路径]
开始运行js文件
- 文件上右键>终端中打开
- 其实当我们在终端执行Node命令时,并不是我们终端去编译解释js代码,而是电脑会自动打开Node安装包中Node.exe应用程序来打开js文件
- Node.exe是一个类似于终端的应用程序,没有界面(CLI程序:command-line interface,命令行界面)
- Node.exe工作环境称之为REPL环境,也就是交互式解释器
- REPL才是真正解释执行我们js代码的解释器
3.nodemon
-
自己决定是否需要使用
-
node开发之友,当你的js文件发生变化的时候,nodemon会自动帮你启动node程序(自动重启并刷新)
-
安装:
npm install -g nodemon
- 使用:
nodemon [js文件名]
- 使用:
二.nodejs核心模块
1.fs文件模块(读写文件)
fs模块是node提供的核心模块–装好了node就能直接使用这个核心模块
fs核心模块可以进行文件(目录)的相关操作require方法可以引入模块,并返回一个对象,通过返回的对象可以调用模块中提供的api ---- require(‘fs’)
1.1 readFile读取文件
readFile(你想读取的文件路径,[选项,如编码],读取之后调用的回调函数)
// 1.引入你想使用的核心模块
const myfs = require('fs')
// 2.调用模块中的api进行功能的实现
// readFile可以以异步的方式读取指定的文件,并对读取结果进行相应的处理,如果文件没有存在则会报错
// 回调函数:有两个参数 err | data
// 当读取完成之后,会调用这个回调函数,并将err|data传递给函数的两个参数
myfs.readFile('./data/a.txt', 'utf-8', (err, data) => {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
myfs.readFile('./data/b.txt', 'utf-8', (err, data) => {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
// 异步操作的两大特点
// 1.异步非阻塞:异步操作不会阻塞后面代码的执行
// 2.异步操作的执行顺序不依赖于异步操作代码的书写顺序
//就是说返回结果的顺序是不确定的,先执行不一定先返回
读取html页面文件:
const fs = require('fs')
fs.readFile('./views/login.html', 'utf8', (err, data) => {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
常见错误信息
1.require('fs'):如果想引入fs,这是固定写法
2.myfs.readfile is not a function:api调用错误了,应该是readFile,注意是小驼峰命名法
3.no such file or directory, open '*****':一定是路径有问题,一定要注意当前需要运行文件的目录,将终端切换到当前目录下再输入命令
1.2 writeFile写入文件
writeFile:可以将内容写入到文件
writeFile(文件路径,写入的数据,选项,写入完成之后的回调函数)
// 1.引入核心模块
const fs = require('fs')
// 2.调用核心模块的方法实现文件的写入操作
// 特性:
// 1.默认会覆盖原文件的内容
// 2.如果指定的源文件不存在,则会先创建再写入
// 3.如果指定的目录不存在,就真的报错
fs.writeFile('./data/aa.txt', '写个内容', (err) => {
if (err) {
console.log(err)
} else {
console.log('ok')
}
})
1.3appendFile追加内容
appendFile:可以往指定的文件中追加内容–不会覆盖原文件的内容
// 1.引入核心模块
const fs = require('fs')
fs.appendFile('./data/a.txt', '\r\n再搞个内容', (err) => {
if (err) {
console.log(err)
} else {
console.log('ok')
}
})
2.path路径模块
2.1 nodejs中的相对路径介绍
-
node中的相对路径: ./ 不是相对于当前文件所在路径,而是相对于执行node命令的文件夹路径(当前被执行的文件所在的文件夹路径).
*在服务端开发中,一般不要使用相对路径,而使用绝对路径
-
解决方案:在nodejs中,每一个js文件都有两个全局属性,它可以帮助我们获取到文件的绝对路径
- __filename:当前js文件所在目录的绝对路径(目录+文件名称)
- __dirmame:当前js文件的绝对路径(当前文件所在的目录)
2.2 path路径模块拼接路径
如果想要获取当前文件夹下其他文件绝对路径,可以使用 __dirname属性来拼接
使用path模块拼接文件路径与 使用 '+'连接符拼接的好处
1.会自动帮我们正确添加路径分隔符 ‘/’,我们无需手动添加
2.当我们路径格式拼接错误的时候,能自动帮我们转换正确的格式
//1.导入文件模块
const fs = require('fs');
// console.log(__filename) // 当前文件的全路径:目录+文件名称
// console.log(__dirname) // 当前文件所在的目录
//......\code./views/login.html
console.log(__dirname + './views/login.html')
//......\code\views\login.html
console.log(path.join(__dirname, './views/login.html'))
//以后读取文件的时候,一般都会使用绝对路径,通过path.join可以生成规范化的路径
fs.readFile(path.join(__dirname, '/views/login.html'), 'utf8', (err, data) => {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
三.服务器
就是提供资源请求响应的电脑
a.将资源文件放置到一个我们都能访问的机器上,供我们远程访问--托管资源到服务器
b.提供访问的地址,并让资源电脑能够提供资源请求的响应
c.如果资源文件更新了,直接更新资源电脑上的文件,而不用每个请求客户端自己来更新
1.什么是服务器
一个管理资源并为用户提供服务的计算机,通常分为文件服务器(能使用户在其它计算机访问文件,我们平时访问的网页也一个一个的文件),数据库服务器和应用程序服务器;能够提供以上服务的计算机,我们也称其为网络主机(Host)
2.基本访问流程
输入主机地址
指定端口(如果没有指定,则默认为80)
指定需要访问的资源路径(要参照服务器的设定)
发起请求
获取服务器返回的结果并处理
3.具体细节
为什么我们能够顺利的向服务器发起请求并能够成功的获取到返回结果呢?
3.1 HTTP协议
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准
客户端和服务器的通信必须遵守某种协议,http协议就是最常见的一种
- HTTP协议称为超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP),HTTP是万维网的数据通信的基础
- HTTP协议规范了客户端如何向服务器发送请求以及服务器端如何响应数据回客户端,只有客户端和服务器遵守一个相同的协议,才能建立正常的通信
- 通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息
3.2 主机地址(域名)
- 主机地址是网络中用来唯一标识每台主机或设备的地址,一般使用ip地址进行标识
- IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,由32位(共四个八位组)的二进制组成,它是一个唯一地址,由于有这种唯一的地址,才保证了用户在连网的计算机上操作时,能够高效而且方便地从千千万万台计算机中选出自己所需的主机
- 如果以本机做为服务器,那么本机服务器的IP地址默认就为:127.0.0.1
- IP地址不好记忆和推广,所以我们使用域名来代码IP地址,方便用户的访问。意味着在访问某一个网站的时候,除了输入熟悉的域名,输入它对应的IP地址也是可以访问的
3.3 端口
端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)
如果把IP地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536(即:2^16)个之多
参考资料:https://baike.baidu.com/item/%E7%AB%AF%E5%8F%A3/103505?fr=aladdin
-
网络中的计算机是通过IP地址来代表其身份的,它只能表示某台特定的计算机。但是一台计算机上可以同时提供很多个服务,如数据库服务、FTP服务、Web服务等,我们就通过端口号来区别相同计算机所提供的这些不同的服务
-
客户端可以通过ip地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过端口号,客户端才能真正的访问到该服务器上的某个提供服务的应用软件。为了对端口进行区分,将每个端口进行了编号,这就是端口号
-
打开cmd后,用“netstat ”查看端口状态,用“netstat -n”命令,以数字格式显示地址和端口信息
-
常见的端口号
- 80:web服务器端口
- 3306:mysql数据服务器端口
-
结论:为了正确的发送请求和获取响应,我们请求的目标地址应该是IP + 端口的组合,如
http://157.122.54.189:9063
3.4 资源url地址(接口地址)
服务器上有很多资源,我们怎么找到我们想要的资源呢?
- 资源url地址由零或多个“/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址
- 一般情况下,我们向服务器发起请求时,需要准确的告诉服务器我们想要什么,这个时候就有必要传入一个资源url地址了
- 如http://157.122.54.189:9063/getUserList,这里面的/getUserList就是资源url地址,这个地址对应着服务器端的一个业务处理或者文件地址,例如/getUserList就能够获取存储在服务器的用户列表数据
3.5 返回数据
返回数据是指客户端发送请求之后,从服务器端返回的数据
常见的从服务器返回的数据有:网页结构,样式,图片,js等
3.5.1 返回状态码
是用以表示网页服务器超文本传输协议响应状态的3位数字代码
常见的状态码
- 200请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态
- 404:请求失败,请求所希望得到的资源未被在服务器上发现
- 500:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现
3.5.2 返回数据的格式
- text/html格式:html代码,浏览器会以html语法解析
- text/css:样式,浏览器会以css语法解析
- application/javascript:js代码,浏览器会以js语法解析
- application/json:json格式字符串,描述从服务器返回的数据
4.搭建自己的web服务器
4.1 创建服务器
-
服务器一定要遵守http协议,所以首先要引入http协议
-
根据http协议创建一个服务器
-
添加指定端口的监听
-
监听用户的请求 响应
// 1.服务器一定要遵守http协议,所以首先要引入http协议
const http = require('http')
// 2.根据http协议创建一个服务器
// 里面有一个参数,当监听到用户请求的时候,就会调用这个函数来处理,同时给这个函数传入两个参数
// req:就是客户端传递给服务器的请求数据(请求报文) -- request,以后与请求相关的操作都通过req来实现
// res:就是服务器响应给客户端的数据 --response,以后与响应相关的操作都通过res来实现
const server = http.createServer()
// 3.添加指定端口的监听
// 127.0.0.1:本机圆环地址,如果以本机做为服务器,默认的地址就是127.0.0.1
server.listen(3003, () => {
console.log('http://127.0.0.1:3003')
})
// 4. 监听用户的请求
// 当用户向这个服务器发起指定端口的请求时,就会自动的触发request事件
// 在事件处理函数中,有两个参数:req,res
// 如果监听到用户的请求,通过设置的资源url用于响应用户特定的请求---路由
server.on('request', (req, res) => {
// writeHead设置响应头
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })
// res.setHeader('Content-Type', 'text/html;charset=utf-8')
// 服务器响应回客户端的内容永远都是字符串
// 但是你需要注意的是,字符串是带格式的
res.end('为什么写个中文我看不懂aabbcc')
})
4.2 响应用户登陆页面
const http = require('http')
const fs = require('fs')
const path = require('path')
// 2.根据http协议创建一个服务器
// 里面有一个参数,当监听到用户请求的时候,就会调用这个函数来处理,同时给这个函数传入两个参数
// req:就是客户端传递给服务器的请求数据(请求报文) -- request,以后与请求相关的操作都通过req来实现
// res:就是服务器响应给客户端的数据 --response,以后与响应相关的操作都通过res来实现
const server = http.createServer()
// 3.添加指定端口的监听
// 127.0.0.1:本机圆环地址,如果以本机做为服务器,默认的地址就是127.0.0.1
server.listen(3003, () => {
console.log('http://127.0.0.1:3003')
})
// 4. 监听用户的请求
// 当用户向这个服务器发起指定端口的请求时,就会自动的触发request事件
// 在事件处理函数中,有两个参数:req,res
// 如果监听到用户的请求,通过设置的资源url用于响应用户特定的请求---路由
// $().on('click',function(e){})
server.on('request', (req, res) => {
// 响应登陆页面
fs.readFile(path.join(__dirname, '/views/login.html'), (err, data) => {
if (err) {
res.end('404')
} else {
res.end(data)
}
})
})
4.3 监听客户端请求并响应不同页面
const http = require('http')
const fs = require('fs')
const path = require('path')
// 2.根据http协议创建一个服务器
const server = http.createServer()
// 3.添加指定端口的监听
// 127.0.0.1:本机圆环地址,如果以本机做为服务器,默认的地址就是127.0.0.1
server.listen(3003, () => {
console.log('http://127.0.0.1:3003')
})
// 4. 监听用户的请求
server.on('request', (req, res) => {
// 根据用户的请求响应不同的结果
// 通过req.url可以获取用户请求url,如果没有输入任何值,默认为/
let myurl = req.url
console.log(myurl)
if (myurl == '/login') {
// 响应登陆页面
fs.readFile(path.join(__dirname, '/views/login.html'), (err, data) => {
if (err) {
res.end('404')
} else {
res.end(data)
}
})
} else if (myurl == '/index') {
// 响应首页
fs.readFile(path.join(__dirname, '/views/index.html'), (err, data) => {
if (err) {
res.end('404')
} else {
res.end(data)
}
})
} else if (myurl == '/userlist') {
// 响应用户数据
fs.readFile(path.join(__dirname, '/data/user.json'), (err, data) => {
if (err) {
res.end('404')
} else {
res.end(data)
}
})
}
})
4.4 响应客户端不同类型的请求
常用的get请求:
1.通过超链接发起请求
2.通过浏览器地址栏发起请求
3.通过ajax发起请求,同时设置type为get
4.常见的get操作有:获取数据,详情,搜索
常见的post请求,一般用于将数据传递到服务器
1.通过表单的默认行为发起请求,同时将method设置为post
2.通过ajax发起请求,同时设置type为post
3.常见的post操作有:新增,编辑,登陆…
1.通过req.method可以获取当前客户端的请求方式。 根据req.method的值的不同,进行相应的处理操作
响应get方式的请求
// 这个服务能够根据用户的请求,响应服务器端的数据到客户端
// 后台模拟数据,一般使用json文件,之后读取json文件返回数据
// 1.引入http协议
const http = require('http')
const fs = require('fs')
const path = require('path')
// 2.根据http协议创建服务器,因为服务器要遵守这个协议
const server = http.createServer()
// 3.添加端口的监听
// 一台电脑可以提供多种不同的服务,通过端口进行区分
server.listen(3004, () => {
console.log('http://127.0.0.1:3004')
})
// 4.监听用户的请求
server.on('request', (req, res) => {
// 由于我们是进行后台开发的,所以我们可以制定前端用户的访问规则
// /getUsers:获取当前所有用户信息
// 通过req的method属性可以获取当前用户的请求方式
let mymethod = req.method // GET或POST
console.log(mymethod)
// 获取用户请求的资源url
let myurl = req.url
// 判断用户请求的url,进行相应的处理
if (myurl == '/getUsers' && mymethod == 'GET') {
fs.readFile(path.join(__dirname, '/data/users.json'), (err, data) => {
if (err) {
res.end('err')
} else {
res.end(data)
}
})
} else {
res.end('404')
}
})
4.5 根据用户请求方式和请求url实现用户注册功能
以后服务器有两种类型的文件
1.在服务器端创建,并且在服务器运行的文件
2.在服务器端创建,但是是返回到前端浏览器运行的文件
需求:
1.能够提供用户注册页面
2.通过接收用户注册请求,并将用户数据存储到服务器的数据文件中
3.响应注册结果
用户注册页面
<body>
<!-- method:指定请求方式,由于现在我要做注册,说明是将客户端的数据传递给服务器,所以是post请求 -->
<!-- action:当前表单提交到的目的地址,如果没有设置,默认就是当前页面 -->
<!-- enctype="application/x-www-form-urlencoded":设置提交数据的编码格式,以后ajax中会看到哦 -->
<form method="post" action="http://127.0.0.1:3004/register">
<!-- name属性的作用就是生成参数传递时的键,如果没有键,是不可能生成参数传递的 -->
<!-- name的属性值不能随意,因为这个参数后期需要在后台进行处理,所以前台页面中的name属性的值一定要参数后台的数据 -->
姓名: <input type="text" id='name' name='name'> <br>
年龄: <input type="number" id="age" name='age'> <br>
<!-- type="submit":submit有默认的提交行为,现在用一用而已,以后避免使用它 -->
<input type="submit" value="注册">
</form>
</body>
服务器的数据文件user.json
[
{
"name": "jack",
"age": 20
},
{
"name": "rose",
"age": 18
}
]
// 创建一个服务器,能够实现两个功能
// 1.响应用户注册页面
// 2.实现用户注册功能
// 1.引入http协议
const http = require('http')
const fs = require('fs')
const path = require('path')
const querystring = require('querystring')
// 2.根据http协议创建服务器,因为服务器要遵守这个协议
const server = http.createServer()
// 3.添加端口的监听
// 一台电脑可以提供多种不同的服务,通过端口进行区分
server.listen(3004, () => {
console.log('http://127.0.0.1:3004')
})
// 4.监听用户请求
server.on('request', (req, res) => {
console.log(__dirname)
res.setHeader('Content-Type', 'text/html;charset=utf-8')
// 约定 用户请求方式 和 用户请求url。后台是我们自己写的所以我们可以定制约定,强制规定以后前端请求必须参照我的规定
// 1.响应用户注册页面: get /register
// 2.实现用户注册功能: post /register
// 获取请求方式
let mymethod = req.method
// 获取用户请求url
let myurl = req.url
if (myurl == '/') {
myurl = '/register'
}
// 1.响应用户注册页面
if (mymethod == 'GET' && myurl == '/register') {
fs.readFile(path.join(__dirname, '/views/register.html'), (err, data) => {
if (err) {
console.log(err)
res.end('404')
} else {
res.end(data)
}
})
}
// 2.实现用户注册功能
else if (mymethod == 'POST' && myurl == '/register') {
// 接收用户参数--重点
//客户端传递给服务器的参数本质上都是键值对 key=value&key1=value1,接收参数为字符串形式,再转换为对象 {key:value,key1:value1} ,存入json文件中。
let str = ''
// 1.node支持大容量的参数传递,它会分批接收参数,接收参数会触发两个事件
// req.on('data',(chunk)=>{}):每次接收到一部分参数就会触发一次,接收到的chunk是字符串格式
// req.on('end',()=>{}):当所有参数接收完毕就会触发end事件,在end就可以进行下一步的处理
req.on('data', (chunk) => {
// 1.1 拼接参数
str += chunk
})
req.on('end', () => {
// name=aa&age=20 >> {name:'aa',age:20}
console.log(str)
// 1.2 将参数转换为对象
let obj = querystring.parse(str)
console.log(obj)
// 1.3 将对象存储到json文件中
// 1.3.1 读取json文件 -- 获取字符串
fs.readFile(path.join(__dirname, '/data/users.json'), 'utf-8', (err, data) => {
if (err) {
res.end('注册失败')
} else {
// 1.3.2 将字符串转换为js数组
let arr = JSON.parse(data)
console.log(arr)
// 1.3.3 将对象存储到数组
arr.push(obj)
// 1.3.4 将数组生成转换为字符串
// 1.3.5 将字符串重新写入到文件
fs.writeFile(path.join(__dirname, '/data/users.json'), JSON.stringify(arr, null, ' '), (err1) => {
// 返回注册结果
if (err1) {
res.end('注册失败')
} else {
res.end('注册成功')
}
})
}
})
})
} else {
res.end('404')
}
})
常见错误
1.listen EADDRINUSE: address already in use :::3003
说明当前的3003端口已经被占用了,端口是唯一的,不能重复
2.TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or
an instance of Buffer. Received an instance of Error
服务器响应回客户端的数据类型必须是字符串,同时,客户端传递给服务器的也只能是字符串