node学习二 (url、http、事件循环和回调函数)

http模块

  1. 创建服务器两种方式
// 方式一 http.createServer([requestListener])
/*
 * [requestListener(req, res)] 用户请求后回调函数,有两个参数,req请求;res响应
 * @return http.Server 返回一个http.Server实例
 */
var server = http.createServer((req, res) => {
    res.writeHeader(200,{'Content-Type':'text/plain'});
    res.end('hello world');
})
 // 方式二 new http.Server()
 var server = new http.Server();
 server.on('request', (req, res){
    res.writeHeader(200,{'Content-Type':'text/plain'});
        res.end('hello world');
 })

采用上面的方式并不能创建一个完成的服务器,还必须监听对应的端口,比如采用下面这种方式。

// 表示引入http模块
const http = require('http');

// 创建一个http服务
/*
   request: 获取url传过来的信息  请求头
   response: 像浏览器响应信息  响应头
*/
http.createServer(function(request,response){
    // 设置响应头
    response.writeHead(200,{'Content-Type': 'text/plain'});

    response.write('1111');// 像页面返回的内容
    response.write('222');
    // 像浏览器输出一句话 并结束响应
    response.end('1123122'); // 

}).listen(8081);// 端口

为啥要监听端口(listen(8080))?

端口的功能
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区 分不同的服务的。

客户端通常对它所使用的端口号并不关心,只需保证该端口号在本机上是唯一的就可以了。客户端口号又称作临时端口号(即存在时间很短暂)。这是因为它通常只是在用户运行该客户程序时才存在,而服务器则只要主机开着的,其服务就运行。

简单理解:IP就是一个电脑节点的网络物理地址,就像你的家住的那个地址;端口是该计算机逻辑通讯接口,不同的应用程序用不同的端口,就像你家里的各个不同的房间,卧室用来睡觉,餐厅用来吃饭。
理解为 node启动一个服务就是将本机当做了服务器,所以它的ip地址是127.0.0.1 ,其中端口8080端口里面是提供你要的服务。

Url模块

在上面创建的那个服务器里面,页面所有的请求都会执行createServer中传入的方法。但是不同的请求,需要做不同的处理,所以我们需求判断请求路径,以便我们做出不同的响应。

我们可以很简单的获取到请求的url,但有数据提交上来的url是十分复杂和不确定的。很不利于编写业务逻辑。所以我们就要将url拆分成我们能用的数据。node的url模块就是帮助我们对提交上来的url进行解析处理

常用方法:

parse(urlStr,queryString,AnalysisHost)

解析url,返回一个url属性对象
urlStr: 要解析的url地址
queryString: 解析出来的query是字符串还是查询对象,true是对象 false是字符串
AnalysisHost: 是否要解析出来主机名

实例代码

  var url = require('url')
  var obj = url.parse('http://www.baidu.com/vdsa?ie=utf-8&word=sad',true,true)
  console.log(obj);

结果:


Url组成部分:

  • protocol:url的通信协议(http/https)
  • slashes:如果协议protocol冒号后跟的是两个斜杠字符(/),那么值为true
  • auth:URL的用户名与密码部分
  • host:url的主机名 “baidu.com”
  • port: 端口号
  • hostname: hostname是host属性排除端口port之后的小写的主机名部分
  • hash:哈希#后面字符串包括#
  • search:URL的查询字符串部分,包括开头的问号字符(?)
  • query: 不包含问号(?)的search字符串
  • pathname:URL的整个路径部分。跟在host后面,截止问号(?)或者哈希字符(#)分隔
  • path:由pathname与search组成的串接,不包含hash字符后面的东西
  • href:解析后的完整的URL字符串,protocol和host都会被转换成小写。

事件循环和回调函数

我们都知道,不同的请求对应的Content-type是不一样的,比如一个页面的加载html和css的Content-type是不一样的。

文件目录为:


image.png

mime.json文件的内容为:

{ ".323":"text/h323" ,
  ".3gp":"video/3gpp" ,
  ".asc":"text/plain" ,
  ".htm":"text/html" ,
  ".html":"text/html" 
.....
}

我们现在通过getTypeFile方法来获取不同文件后缀对应的Content-type。

const http = require('http');
const fs = require('fs');
const path = require('path');
const URL = require('url');
const utils = require('./utils/getType');
const EventEmitter = require('./utils/Listener');
http.createServer(function(request,response){
    const pathname = request.url;
    let extname = path.extname(URL.parse(pathname).pathname); // 获取文件后缀名
 if(pathname !== '/favicon.ico'){
         返回对应的页面
        response.writeHead(200,{
            'Content-Type': `${utils.getTypeFile(extname)};charset='utf-8'`
         });
            fs.readFile('./static/'+URL.parse(pathname).pathname,    (err,data)=> {
                    response.end(data);
                }
            })
    }else {
        response.end()
    }        
}).listen(8081)
console.log('服务启动了')

getTypeFile方法为:

exports.getTypeFile = function(exname){
    const obj = fs.readFileSync('./mime.json').toString();
    return JSON.parse(obj)[exname];
}

当getTypeFile是一个同步方法的时候,上面的代码是没有任何问题的。但是如果使用fs.readFile这个异步方法的时候就会出现问题。因为外面得不到返回的值。这里readFildSync是一个同步的方式 ,如果采用异步的方法 readFile 在app.js 如何得到想要值呢 。

一般解决这种问题的有事件循环、和回调函数的解决方式。

回调函数

// app.js
const utils = require('./utils/getType');
...code
utils.getTypeFild(exname,function(str){
    response.writeHead(200,{
            'Content-Type': `${str};charset='utf-8'`
        });
    ... code    
})

// getType.js
exports.getTypeFile = function(exname,callback){
    fs.readFile('./mime.json',(err,data) =>{
        callback(data.toString);
    })
}

通过在给getTypeFile传入一个回调方法,在readFile执行完成后,在执行回调方法。Node 使用了大量的回调函数,Node 所有 API 都支持回调函数

事件驱动

event 模块只提供了一个对象 events.EventEmitter,
核心就是事件触发和事件监听功能,原生使用原理类似于观察者模式。

创建 eventEmitter 对象

// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();

以下程序绑定事件处理程序:

// 绑定事件及事件的处理程序
eventEmitter.on('eventName', eventHandler);

我们可以通过程序触发事件:

// 触发事件
eventEmitter.emit('eventName');

所以如果采用事件驱动驱动的方式我们的第二种实现方式为:

1. getType.js
const fs = require('fs');
const events= = require('events');
const EventEmitter = new events.EventEmitter();// 实例化事件对象
exports.getTypeFile = function(exname){
    fs.readFile('./mime.json',(err,data) =>{
       EventEmitter.emit('exname', data.toString()); // 发送一个名为exname 的广播
    })
}

2. app.js
const fs = require('fs');
const events= = require('events');
const EventEmitter = new events.EventEmitter();
...code

if(pathname !== '/favicon.ico'){
        // 接受一个名为 exname的广播
        EventEmitter.on('exname',(data) =>{
            response.writeHead(200,{
            'Content-Type': `${data};charset='utf-8'`
            });
            ...code;
        })        
}

这里有一个错误的地方是,两个文件中都通过new events.EventEmitter()来生成了一个EventEmitter对象,尽管他们两个的名字一样,但是他们两个是不同的实例。所以会发现在app.js中并没有接受到一个名为 exname的广播。

为了保证是一个EventEmitter对象,我们采用这种方法,将app.js、和getType.js中的

const events= = require('events');
const EventEmitter = new events.EventEmitter();// 实例化事件对象

替换为

const EventEmitter = require('./Listener');

// Listenser.js
const events = require('events');
const EventEmitter = new events.EventEmitter();
module.exports = EventEmitter;

这样两个文件使用的就是同一个EventEmitter

参考文章:

  • https://www.runoob.com/nodejs/nodejs-event-loop.html
  • https://www.jianshu.com/p/be67d4419392

你可能感兴趣的:(node学习二 (url、http、事件循环和回调函数))