分类 随笔 下的文章

Django 源码阅读(六):深入理解WSGI协议


起步

惭愧啊,惭愧啊,距离上一篇这个系列的文章已经是半年前的了,随着 Django2.0 的发布,感觉之前分析的 1.10.5 版本似乎有点老了,我看了一下,好在和我前面文章分析的内容差异不大,基本上也是可以就着前面的分析内容来品尝最新的 django 代码。

那我接下来阅读的版本就从当前能获取的 2.0.6 来分析了。不过呢,本章要将的内容,可能和 django 代码本身没太多关系。本章来理解一下 WSGI 协议,django 就是遵守这个协议的web开发框架,本章重点是协议方面的说明,顶多会讲讲django里相应的 wsgi 的代码,而不对 django 代码做分析。


接口中如何定义空数据


提出问题

前后端常用的 json 格式进行传输,那么怎样来表示空数据呢?空字符串返回 null 还是 "" ,数字返回 null 还是 0 ,列表呢?

这种需要前后端协商一致,一百个项目会有一百个规范,也没有人教你怎样定义更合理,都是通过经验,协商磨合和妥协中产生的。我这边说说按我的经验空数据的定义方式。


Node子进程执行ping操作,获取统计信息


需求:采用ping -t方式不断进行ping操作,直到收到关闭信号or某个超时时间时结束操作,获取统计信息。

分析:

在cmd窗口进行ping -t操作,会一直进行ping,直到输入ctrl+C会输出ping统计信息。

kill('SIGINT') 即模拟ctrl+C终止进程

编码:

这里我自己手动进行统计信息,原因见下面分析。

var exec = require('child_process').exec;
var iconv = require('iconv-lite');
let ping = exec(`ping www.google.com.hk -t`, { encoding: 'binary'}, function (err, stdout, stderr) {
    let send = 0
    let accept = 0
    let lost = 0
    let min = Infinity
    let max = -Infinity
    let avg = 0
    let Min = (a,b)=>a<b?a:b;
    let Max = (a,b)=>a>b?a:b;

    let str = iconv.decode(new Buffer(stdout, 'binary'), 'GBK')
    console.log(str);
    console.log('=========')
    let regAccept = /来自 .*的回复: 字节=(\d+) 时间=(\d+)ms TTL=(\d+)/g
    let regAll = /\n/g
    send = str.match(regAll).length - 2
    send=send<0?0:send
    let res
    while (res = regAccept.exec(str)) {
        accept++
        let tim = Number(res[2])
        min=Min(tim,min)
        max=Max(tim,max)
        avg= (avg*(accept-1)+tim)/accept
        console.log(res[1], tim, res[3],avg)
    }
    console.log('=========')
    console.log(`发送:${send};接收:${accept};丢失:${send-accept};${(1-(accept/send))*100}%丢失`)
    console.log(`最短:${min}ms;最长:${max}ms;平均:${avg}ms`)
});
ping.on('close', (code) => { console.log('close by', code) })
setTimeout(function () {
    ping.kill('SIGINT')
}, 5 * 1000);

想通过ping.kill('SIGINT')去关闭exec子进程。

测试结果是:输出了close by null后,程序依然再运行,并且没有输出统计信息。


setInterval与settimeout模拟的区别


首先先说明下,node里面的事件循环和浏览器中的是不一致的。

这边浏览器用的是chrome 64

问题1:setInterval(fn,ms)过程,是先把fn放入timer堆,还是先执行?

问题2:setInterval 和 settimeout模拟定时 的使用场景都有哪些?

A1

看了一篇文章,里面讲到node中timers阶段的源码为

void uv__run_timers(uv_loop_t* loop) {
  struct heap_node* heap_node;
  uv_timer_t* handle;

  for (;;) {
    heap_node = heap_min((struct heap*) &loop->timer_heap);//取出timer堆上超时时间最小的元素
    if (heap_node == NULL)
      break;
    //根据上面的元素,计算出handle的地址,head_node结构体和container_of的结合非常巧妙,值得学习
    handle = container_of(heap_node, uv_timer_t, heap_node);
    if (handle->timeout > loop->time)//如果最小的超时时间比循环运行的时间还要小,则表示没有到期的callback需要执行,此时退出timer阶段
      break;

    uv_timer_stop(handle);//将这个handle移除
    uv_timer_again(handle);//如果handle是repeat类型的,重新插入堆里
    handle->timer_cb(handle);//执行handle上的callback
  }
}

里面说到对于repeat类型的handle(setInterval设置的),是先重新插入再执行callback

( 我这边测试发现chrome 64是这样的,node相反。可能node修改过实现?

测试代码

var speed = 1000
var start = Date.now()
var icounter = 0
var tcounter = 0
//t:ms
function sleep(t) {
    let d = Date.now()
    while (Date.now() - d < t) { }
}
setInterval(function () {
    var time = (Date.now() - start) / 1000
    var avg = ++icounter / time
    console.log('<td>setInterval</td><td>' + icounter + '</td><td>' + time.toFixed(3) + '</td><td>' + avg.toFixed(6) + '</td>')
    sleep(50)
}, speed)

浏览器运行效果:

setInterval=>次数:1   所用时间:1.002
setInterval=>次数:2   所用时间:2.001
setInterval=>次数:3   所用时间:3.000
setInterval=>次数:4   所用时间:4.002
setInterval=>次数:5   所用时间:5.002

node运行效果:

setInterval=>次数:1   所用时间:1.003
setInterval=>次数:2   所用时间:2.060
setInterval=>次数:3   所用时间:3.114
setInterval=>次数:4   所用时间:4.164
setInterval=>次数:5   所用时间:5.215

结果分析:

  1. 浏览器先把fn放入timer堆,再执行

  2. node先执行,再把fn放入timer堆