创建nodejs服务器的步骤:

1、导入http模块

2、创建http服务器对象
3、给服务器对象注册(绑定)request事件
        (1)对不同请求方式进行处理
        (2)路由:对不同的请求路径进行处理
        (3)处理请求参数
        (4)给客户端发送响应信息
        app.on('request',(req,res)=>{   
        })
4、开启端口监听

我们今天就按照这个步骤来学习nodejs服务器的知识。

一、NodeJS服务器开发

1、学习服务器开发的目的

  • 可以和后端程序员进行紧密的配合
  • 让业务前置
  • 扩展知识面

2、服务器的主要工作

  • 实现项目的业务逻辑
  • 实现数据的增删改查(CRUD)

3、使用Node创建服务器示例

  • 引入http模块
  • 创建服务器对象
  • 定义请求和响应
  • 监听端口号    本地机的域名:localhost       本地机的IP地址:127.0.0.1
//1、引入http模块
const http= require('http');
//2、创建Web服务器对象
const  app=http.createServer()
//3、定义请求和响应
/*
回调函数中的参数
req(request):表示请求对象,存放了客户端的所有请求信息
res(respone):表示响应对象,服务器端通过该对象向客户端发送响应信息
*/
app.on('request',(req,res)=>{res.end('Hello Word')res.writeHead(200,{// 'content-type':'text/plain'//相应信息是纯文本格式'content-type':'text/html;charset=utf8'//相应信息是纯文本格式})
});
//4、监听端口号:指定服务器所使用的端口号
app.listen(8080)//监听3000端口
console.log('服务器已启动,监听8080端口,请访问localhost:8080');
//127.0.0.1  本地机的IP地址

4、http协议

HTTP(Hyper Text Transfer Protocol)超文本传输协议

  • HTTP协议规定了如何从网站服务器传输超文本到本地浏览器
  • HTTP协议基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准
  • HTTP协议可以使浏览器更加高效,使网络传输减少
  • HTTP协议不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。

5、关于http请求和响应

  • 请求消息:客户端向服务器端发送请求时所携带的数据块
  • 响应消息:服务器端响应客户端请求时所携带的数据块
  • 客户端向服务器端发送请求的类型

⭐get请求:在浏览器的地址栏直接输入请求地址或在页面使用超链接(a标签)发起的请求,默认都是 get请求
⭐post请求:在<form>标签的method属性中指定为post时,请求方式就是post

get请求 :

post请求:

  • get请求和post请求的区别

⭐get请求的参数是通过URL进行传递。post请求的参数包含在请求体中
⭐get请求不安全,不能用来传输敏感信息;post请求相对比较安全
⭐get请求在url中传递的参数是有长度限制的(不同的浏览器限制的长度不同),post请求对长度 没有限制
⭐get请求参数会被完整的保留在浏览器的历史记录中;post请求参数不会再浏览器中保留
⭐get请求使用url编码(百分号编码);post请求支持多种编码方式。
⭐get请求在浏览器中可以被主动缓存(cache);post请求不会
⭐get请求在浏览器回退时是无害的;post请求在浏览器回退时会再次向服务器提交数据
⭐get请求时浏览器会把请求头(http header)和数据(data)一起发送出去;
⭐post请求时浏览器会先发送请求头(http header),服务器响应后(状态码100),浏览器会再次发送数据(data),服务器器会给客户端返回状态码200

  • 服务器收到用不同请求方式发送的请求时,如何分开处理(服务器如何区分get请求和post请求)通过对请求对象(request)的method属性进行判断,来获取用户的请求方式

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥服务器端代码

//1、引入http模块
const http= require('http');
const { homedir, hostname } = require('os');
//2、创建Web服务器对象
const  app=http.createServer()
//3、定义请求和响应
/*
回调函数中的参数
req(request):表示请求对象,存放了客户端的所有请求信息
res(respone):表示响应对象,服务器端通过该对象向客户端发送响应信息
*/
app.on('request',(req,res)=>{if(req.method == 'POST'){res.end('post')}else if(req.method == 'GET'){res.end('get')}console.log('请求方式:',req.method);console.log(req.headers['host']);//设置响应头信息res.writeHead(200,{// 'content-type':'text/plain'//相应信息是纯文本格式'content-type':'text/html;charset=utf8'//相应信息是纯文本格式})res.end('<h1>你好我是服务器端</h1>')//end()方法:断开与客户端的联系同时向客户端发送信息
});
//4、监听端口号:指定服务器所使用的端口号
app.listen(8080)//监听3000端口
console.log('服务器已启动,监听8080端口,请访问localhost:8080');
//127.0.0.1  本地机的IP地址

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥HTML页面(使用get请求)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="http://localhost:8080" method="get"><label>用户名:<input type="text" name="username"></label><br><br><label>密码:<input type="password" name="pwd"></label><br><br><button type="submit">登录</button></form>
</body>
</html>

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥HTML页面(使用post请求)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="http://localhost:8080" method="post"><label>用户名:<input type="text" name="username"></label><br><br><label>密码:<input type="password" name="pwd"></label><br><br><button type="submit">登录</button></form>
</body>
</html>

  • 根据客户端的请求地址访问不同的内容,通过对请求对象(request)的url属性来获取用户的请求地址
//1、引入http模块
const http= require('http');
//2、创建Web服务器对象
const  app=http.createServer()
//3、定义请求和响应
app.on('request',(req,res)=>{let url = req.url //获取客户端的请求地址if(url == '/index'||url=='/'){//'/'表示服务器的根目录res.end('<h1>Home Page</h1>')}else if(url=='/list'){res.end('<h1>List Page</h1>')}else{res.end('<h1>Not Found</h1>')}
});
//4、监听端口号:指定服务器所使用的端口号
app.listen(8080)//监听3000端口
console.log('服务器已启动,监听8080端口,请访问localhost:8080');

  • 获取请求头信息

⭐请求头是请求信息的一部分,是由客户端浏览器自动发送给服务器的
⭐请求头的详细信息

  • 格式:以键值对的方式保存
  • 属性

User-Agent:发送请求的浏览器的类型

Accept:服务器响应消息的格式

Accept-Language:客户端可以接收的语言

Accept-Encoding:客户端可以接收的编码压缩格式

Accept-Charset:可接收的应答的字符集

Host:请求的主机名

connection:连接方式(close 或keepalive)

  • 获取请求头信息的方法  req.headers
  • 响应消息

⭐状态码

  • 1**:请求已接收,需要继续处理
  • 2**:请求已成功被服务器接收、理解  (200:表示请求响应成功)
  • 3**:为完成请求,客户端需进一步细化请求
  • 4**:客户端的请求有错误    (404:请求的服务器资源不存在)
  • 5**:服务器端出现错误  ( 500:服务器程序有语法错误 )

⭐响应内容的类型(Content-Type)

  • text/plain:返回纯文本格式的内容
  • text/html:返回HTML格式
  • text/css:返回CSS格式
  • application/javascript:返回JavaScript格式
  • image/jpeg:返回JPEG图片格式
  • application/json:返回JSON代码格式

二、HTTP请求和响应的处理

1、get请求参数的处理

  • get参数是放在浏览器的地址栏中发送的(即请求参数和url绑在一起)
  • get参数的获取方式

⭐通过请求对象(req)的url属性获取客户端请求的URL(req.url)
⭐使用Node的内置模块url对请求地址进行解析,获取请求参数
                 let { query,pathname } = url.parse(clientURL,true)

const url =require('url')
const http= require('http')
const app = http.createServer()app.on('request',(req,res)=>{// 1、获取客户端请求let clientURL = req.urlconsole.log(clientURL);//2、解析urllet { query,pathname } = url.parse(clientURL,true)console.log('请求参数:',query);console.log('请求路径:',pathname);res.writeHead(200,{'content-type':'text/html;charset=utf8'})//3、对请求进行判断if(pathname=='/index'||pathname=='/'){res.end('<h1>hello '+query.username+'</h1>')}else if(pathname == '/list'){res.end('<h1>List Page</h1>')}else{res.end('<h1>Not Found</h1>')}
})
app.listen(8089)
console.log('服务器已启动,运行在8089端口上...');

2、post请求参数的处理

  • post请求参数放在请求体中,而不是放在url中

  • post参数的获取方式

⭐导入Node的内置模块querystring,将post请求参数转换成对象
⭐给请求对象(req)注册'data'事件,用于监听参数的传输,将传输的参数读取出来
⭐给请求对象(req)注册'end'事件,用于监听参数传输完毕,该事件被触发表明参数传递完成就可以对参数进行处理
⭐强调:关于querystring被弃用的解决办法

使用querystringify模块替代querystring模块。因为querystringify模块不是Node的内置模块,需要安装  npm install querystringify -g

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥服务器端代码

//导入系统模块querystring用于POST请求参数转化为对象格式
const qs = require('querystringify')
const http = require('http')
const { fstat } = require('fs')
const { error } = require('console')
const app = http.createServer()app.on('request', (req, res) => {let postParams = '' //用来存储post请求参数//给req注册'data'事件,监听参数传递req.on('data',(params)=>{ //params表示请求参数,参数的传递是以字符流的方式进行的postParams += params})//给req注册'end'事件,该事件被触发表明参数传递完成req.on('end',()=>{console.log('获得的参数:',postParams)let temp = qs.parse(postParams)console.log('解析后的参数:',temp)console.log('用户名:',temp.username)console.log('密码:',temp.pwd)})res.end('ok')
})app.listen(8090)
console.log('服务器已启动,运行在8090端口上...');

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥客户端代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<style>div{width:500px ;margin: 100px auto;}
</style>
<body><div><form action="http://localhost:8090" method="post"><label>用户名:<input type="text" name="username"></label><br><br><label>密&nbsp;&nbsp;&nbsp;码:<input type="password" name="pwd"></label><br><br>&nbsp;&nbsp;&nbsp;<button type="submit">提交</button>&nbsp;&nbsp;&nbsp;<button type="reset">重置</button></form></div>
</body>
</html>

3、路由

  • 路由是指客户端请求地址与服务器端程序代码的对应关系。用户在浏览器地址栏中输入不同的请求地址,服务器端会为客户端响应不同的内容。

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥服务器端代码

const url =require('url')
const http= require('http')
const path = require('path')//用于路径的拼接
const fs = require('fs')//用于读取文件
const mime = require('mime') //引入第三方文件
const app = http.createServer()app.on('request',(req,res)=>{//1、获取用户的请求方式const method = req.method.toLowerCase()//2、解析用户的请求地址const pathname = url.parse(req.url).pathname//3、设置响应头信息res.writeHead(200,{'content-type':'text/html;charset=utf8'})//4、进行路由if(method == 'get'){if(pathname == '/'||pathname == '/index'){let msg = '<h1>欢迎来到首页</h1>'+'<p>你的请求方式是:get</p>'res.end(msg)}else if(pathname == '/list'){let msg = '<h1>欢迎来到列表页</h1>'+'<p>你的请求方式是:get</p>'res.end(msg)}else{let msg = '<h1>你访问的页面不存在</h1>'+'<p>你的请求方式是:get</p>'res.end(msg)}}else if(method == 'post'){if(pathname == '/'||pathname == '/index'){let msg = '<h1>欢迎来到首页</h1>'+'<p>你的请求方式是:host</p>'res.end(msg)}else if(pathname == '/list'){let msg = '<h1>欢迎来到列表页</h1>'+'<p>你的请求方式是:host</p>'res.end(msg)}else{let msg = '<h1>你访问的页面不存在</h1>'+'<p>你的请求方式是:host</p>'res.end(msg)}}})
app.listen(8090)
console.log('服务器已启动,运行在8090端口上...');

 ♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥get请求演示

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥客户端代码 (post请求) 

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<style>div{width:500px ;margin: 100px auto;}
</style>
<body><div><form action="http://localhost:8090" method="post"><label>用户名:<input type="text" name="username"></label><br><br><label>密&nbsp;&nbsp;&nbsp;码:<input type="password" name="pwd"></label><br><br>&nbsp;&nbsp;&nbsp;<button type="submit">提交</button>&nbsp;&nbsp;&nbsp;<button type="reset">重置</button></form></div>
</body>
</html>

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥ post请求演示

  • 静态资源的访问

⭐静态资源:是指客户端向服务器端请求的资源,服务器端不需要处理,可以直接响应给客户端 的资源。         
⭐静态资源的类型:CSS、JavaScript、image文件,以及HTML文件
⭐动态资源:指的是相同的请求地址可以传递不同的请求参数,得到不同的响应资源
⭐访问过程:

  • 创建一个文件夹用于存放静态资源
  • 获取用户的请求路径
  • 使用path模块对请求路径进行转换(将请求路径转换成静态资源对应的硬盘路径)
  • 使用mime模块获取静态资源的类型
  • 使用fs模块读取静态资源文件
  • 将读取的静态资源响应给用户
const http = require('http')
const mime =require('mime')
const url = require('url')
const path = require('path')
const fs = require('fs')
const app = http.createServer()app.on('request', (req, res) => {//1、获取用户的请求路径let pathname = url.parse(req.url).pathnamepathname = pathname == '/' ? '/default.html' : pathname //判断用户是否请求了静态资源//2、将用户的请求路径转化成实际服务器硬盘路径let realPath = path.join(__dirname, 'public' + pathname)//3、利用mime模块根据路径返回响应资源的类型let type = mime.getType(realPath)//4、读取文件fs.readFile(realPath, (error, result) => {if (error != null) {  //如果文件读取失败//指定返回资源的文件编码res.writeHead(404, { 'content-type': 'text/html;charset=utf8' })res.end('文件读取失败')} else {  //如果文件读取成功res.writeHead(200, { 'content-type': type })res.end(result)}})})app.listen(8090)
console.log('服务器已启动,运行在8090端口上...');

三、Node.js的异步编程

1、同步API

只有在当前的API执行完成后,才执行下一个API。代码的执行方式是按代码的先后顺序从上到下一行一行的执行

2、异步API

当前API的执行不会阻塞后续代码的执行。代码执行方式是当执行到耗时代码时,不等待,而是往后执行,执行结果由回调函数处理

function getMsg(callback){//函数的定义setTimeout(function(){callback({msg:'hello node.js'})//对callback函数的调用},2000)
}getMsg(function(data){console.log(data);})//getMsg函数的调用

运行后需要等待2秒钟 才会打印结果

 3、回调地狱

如果异步API后面代码的执行依赖当前异步API的执行结果,这就需要把代码写在回调函数中。一旦回调函数的嵌套层次过多,就会导致代码不易维护,我们将这种代码形象地称为回调地狱。

function getMsg(callback){//函数的定义setTimeout(function(){// callback({msg:'hello node.js'})//对callback函数的调用setTimeout(function(){setTimeout(function(){setTimeout(function(){callback({msg:'hello node.js'})},2000)},2000)},2000)},2000)
}getMsg(function(data){console.log(data);})//getMsg函数的调用

运行后需要等待8秒钟 才会打印结果   

4、回调地狱的解决方法

利用Promise解决回调地狱问题

  • Promise本身是一个构造函数,如果要使用Promise解决回调地狱的问题,需要使用new关键字

创建Promise构造函数的实例对象。
// 定义Promise
let promise = new Promise((resolve, reject) => { });
resolve:是一个函数
reject:是一个函数
promise.then(result => console.log(result))
.catch(error => console.log(error));

  • 什么时候用?在程序中出现回调函数嵌套时,建议使用Promise对象
//依次读取a、b、c三个文件
const fs = require('fs')fs.readFile('txt/a.txt','utf8',(err,result1)=>{console.log(result1);fs.readFile('txt/b.txt','utf8',(err,result2)=>{console.log(result2);fs.readFile('txt/c.txt','utf8',(err,result3)=>{console.log(result3);})})
})

const fs = require('fs')function p1() {let promise = new Promise((resolve, reject) => { //创建promise对象fs.readFile('txt/a.txt', 'utf8', (err, result) => {if (err != null) {//若读取文件失败,则通过reject函数将错误信息传递出去reject(err)}resolve(result)})})return promise
}
function p2() {let promise = new Promise((resolve, reject) => { //创建promise对象fs.readFile('txt/b.txt', 'utf8', (err, result) => {if (err != null) {//若读取文件失败,则通过reject函数将错误信息传递出去reject(err)}resolve(result)})})return promise
}
function p3() {let promise = new Promise((resolve, reject) => { //创建promise对象fs.readFile('txt/c.txt', 'utf8', (err, result) => {if (err != null) {//若读取文件失败,则通过reject函数将错误信息传递出去reject(err)}resolve(result)})})return promise
}
//resole函数用来处理回调成功的结果,该函数将结果传递给then
//reject函数用来处理回调失败的结果,该函数将结果传递给catch
p1()
.then((result1) => { console.log(result1); return p2()
})
.then((result2)=>{console.log(result2);return p3()
})
.then((result3)=>{console.log(result3);
})
.catch((err) => { console.log(err); })

5、异步函数

异步函数实际上是在Promise对象的基础上进行了封装,它把一些看起来比较繁琐的代码封装起来, 然后开放一些关键字供开发者来使用.异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。

⭐async关键字:在函数定义放在函数的前面,标识该函数是一个异步调用函数,其返回值是一个Promise对象
⭐await关键字:可以暂停异步函数的执行,等待Promise对象返回结果再向下执行函数只能出现在异步函数中,await后面只能写Promise对象,不能写其他类型API使异步函数调用后,得到一个同步的效果

async function fn(){// throw '发生错误'  //实际等价于返回的是reject(123)return 123  //实际等价于返回的是resolve(123)
}fn()
.then((data)=>{  //调用fn函数,将该函数的返回值传递给then的回调函数console.log(data);
})
.catch((err)=>{//catch的回调函数接受的fn函数中throw抛出的异常信息console.log(err);
})

♥♥♥♥♥♥♥♥对上述依次读取文件案例进行改造

♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥要安装npm模块及promisify模块

安装npm模块  :npm install

安装promisify模块: npm install promisify

const fs = require('fs')
const { promisify } = require('util')
//1、将fs.readFile函数进行转化,使它的返回值是一个Promise对象,需要使用util模块Promiseify函数
const readFile = promisify(fs.readFile)async function p1(){let r1 = await readFile('txt/a.txt','utf8')let r2 = await readFile('txt/b.txt','utf8')let r3 = await readFile('txt/c.txt','utf8')console.log(r1);console.log(r2);console.log(r3);
}
p1()

Node.js服务器端开发相关推荐

  1. 模块加载及第三方包:Node.js模块化开发、系统模块、第三方模块、package.json文件、Node.js中模块的加载机制、开发环境与生产环境、cookie与session

    1.Node.js模块化开发 1.1 JavaScript开发弊端 JavaScript 在使用时存在两大问题,文件依赖和命名冲突. 1.2 软件中的模块化开发 一个功能就是一个模块,多个模块可以组成 ...

  2. node.js的开发流程_Node.js子流程:您需要了解的一切

    node.js的开发流程 by Samer Buna 通过Samer Buna Node.js子流程:您需要了解的一切 (Node.js Child Processes: Everything you ...

  3. 《Node.js入门》Windows 7下Node.js Web开发环境搭建笔记

    最近想尝试一下在IBM Bluemix上使用Node.js创建Web应用程序,所以需要在本地搭建Node.js Web的开发测试环境. 这里讲的是Windows下的搭建方法,使用CentOS 的小伙伴 ...

  4. php微信墙开发,Node.js如何开发微信墙

    这次给大家带来Node.js如何开发微信墙,Node.js开发微信墙的注意事项有哪些,下面就是实战案例,一起来看一下. 验证服务器有效性 接收用户通过微信订阅号发给服务器的消息 解析收到的XML文本消 ...

  5. SAP Spartacus 服务器端渲染模式下,在 Node.js 服务器端渲染视图的单步调试

    入口:在 express-engine.js 文件的 render 函数设置断点: 进入 platform-server.js: 这个文件就是 SAP Spartacus 开启服务器端渲染之后,pac ...

  6. Node.js模块化开发(非常详细,满满的干货)

    下面是对Node.js模块化开发的整理,西洼港可以帮助到有需要的小伙伴~ 文章目录 Node.js模块化开发 JavaScript开发弊端 Node.js模块化开发 模块成员导出的export方法 模 ...

  7. node mysql商城开发_NideShop:基于Node.js+MySQL开发的微信小程序商城开源啦

    NideShop:基于Node.js+MySQL开发的微信小程序商城开源啦 发布时间:2020-04-14 04:23:37 来源:51CTO 阅读:2894 作者:ch10mmt 高仿网易严选的微信 ...

  8. 妙味课堂ajax教程,前后端高级实战 | Node.js 实战开发:博客系统【妙味课堂】

    第一部分:Node.js基础视频内容 1-初识NodeJs 2-webstorm的使用 3-Node和JS的异同 4-模块的使用 5-模块加载机制 6-模块-module和exports 7-glob ...

  9. Node.js模块化开发

    Node.js模块化开发 一.Node.js模块化开发 二.系统模块 1.什么是系统模块 2.系统模块fs文件操作 3.系统模块path路径操作 三.第三方模块 1.什么是第三方模块 2.获取第三方模 ...

最新文章

  1. 三点弯曲弹性模量怎么计算公式_拉力试验机常用力学计算公式
  2. 2019年汽车行业深度投资研究
  3. 使用GitHub免费搭建属于自己的网站
  4. oracle 入参含多个值,oracle几个检查点参数的含义与区别
  5. 【SAP干货】创建Search Helps (Elementary and Secondary)
  6. git分支操作、分支合并冲突解决
  7. 合约 cd 模式_CD的完整形式是什么?
  8. 完善三个数字对象排序程序。MOOC,Java第四章 面向对象和类 第一次作业
  9. android mac转数据格式转换,mac环境下Android 反编译
  10. 商业信息敏感、安全处理(口令、数字证书-U盾-密保卡、指纹识别-虹膜识别)...
  11. Linux meson + ninja编译安装源码(八)
  12. 2017人工智能大会核心技术流出_机器人竟然已经能干这个了?
  13. 如何查看Tomcat版本信息
  14. leetcode 1232. Check If It Is a Straight Line(python)
  15. 2016——大数据版图
  16. liferay开发环境搭建
  17. txt文件批量转换为excel文件
  18. Python中利用openpyxl对Excel的各种相关详细操作(二十一种常用操作<代码+示例>)
  19. oracle基础|oracle多表查询用法|什么是等值连接(inner join)|什么是不等值连接(between)|什么是左连接(left join)|什么是右连接(right join)
  20. 中国股神最新力作——《猎杀黑马》即将上市

热门文章

  1. Unkown entity 错误
  2. 涡轮制冷智能温控,红魔散热背夹体验,告别烫手宝
  3. delphi 编译生成ipa文件 adhoc步骤
  4. hnustOJ - 1693: 原石法阵
  5. python-基础-文件
  6. WSN无线传感网络-节点分簇功能python实现
  7. 联想小新14和air14的区别 哪个好
  8. CreateFontIndirect函数简介
  9. [附源码]PHP计算机毕业设计会议室预约系统(程序+LW)
  10. linux下adb传输文件,使用adb在电脑和手机间传文件,adb手机传文件