xml地图|网站地图|网站标签 [设为首页] [加入收藏]
Python之mmap内存映射模块,网站部署
分类:编程

这篇文章首发于我的个人网站:听说 - https://tasaid.com/,建议在我的个人网站阅读,拥有更好的阅读体验。

背景:

      通常在UNIX下面处理文本文件的方法是sed、awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力。关于sed的说明可以看了解sed的工作原理,本文将介绍通过python的mmap模块来实现对大文件的处理,来对比看他们的差异。

 

这篇文章与 博客园 和 Segmentfault 共享。

说明:

     mmap是一种虚拟内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。关于系统中mmap的理论说明可以看百度百科和维基百科说明以及mmap函数介绍,这里的说明是针对在Python下mmap模块的使用说明。

使用:
**
1,创建:创建并返回一个 mmap 对象m**

m=mmap.mmap(fileno, length[, flags[, prot[, access[, offset]]]])

fileno: 文件描述符,可以是file对象的fileno()方法,或者来自os.open(),在调用mmap()之前打开文件,不再需要文件时要关闭。

图片 1图片 2

os.O_RDONLY   以只读的方式打开 Read only
os.O_WRONLY   以只写的方式打开 Write only
os.O_RDWR     以读写的方式打开 Read and write
os.O_APPEND  以追加的方式打开  
os.O_CREAT   创建并打开一个新文件
os.O_EXCL     os.O_CREAT| os.O_EXCL 如果指定的文件存在,返回错误
os.O_TRUNC    打开一个文件并截断它的长度为零(必须有写权限)
os.O_BINARY          以二进制模式打开文件(不转换)
os.O_NOINHERIT        阻止创建一个共享的文件描述符
os.O_SHORT_LIVED
os.O_TEMPORARY        与O_CREAT一起创建临时文件
os.O_RANDOM         缓存优化,但不限制从磁盘中随机存取
os.O_SEQUENTIAL   缓存优化,但不限制从磁盘中序列存取
os.O_TEXT           以文本的模式打开文件(转换)

View Code

length:要映射文件部分的大小(以字节为单位),这个值为0,则映射整个文件,如果大小大于文件当前大小,则扩展这个文件。

flags:MAP_PRIVATE:这段内存映射只有本进程可用;mmap.MAP_SHARED:将内存映射和其他进程共享,所有映射了同一文件的进程,都能够看到其中一个所做的更改;
prot:mmap.PROT_READ, mmap.PROT_WRITE 和 mmap.PROT_WRITE | mmap.PROT_READ。最后一者的含义是同时可读可写。

access:在mmap中有可选参数access的值有

ACCESS_READ:读访问。

ACCESS_WRITE:写访问,默认。

ACCESS_COPY:拷贝访问,不会把更改写入到文件,使用flush把更改写到文件。

2,方法:mmap 对象的方法,对象m**

m.close()
关闭 m 对应的文件;

m.find(str, start=0)
从 start 下标开始,在 m 中从左往右寻找子串 str 最早出现的下标;

m.flush([offset, n])
把 m 中从offset开始的n个字节刷到对应的文件中;

m.move(dstoff, srcoff, n)
等于 m[dstoff:dstoff+n] = m[srcoff:srcoff+n],把从 srcoff 开始的 n 个字节复制到从 dstoff 开始的n个字节,可能会覆盖重叠的部分。

m.read(n)
返回一个字符串,从 m 对应的文件中最多读取 n 个字节,将会把 m 对应文件的位置指针向后移动;

m.read_byte() 
返回一个1字节长的字符串,从 m 对应的文件中读1个字节,要是已经到了EOF还调用 read_byte(),则抛出异常 ValueError;

m.readline()
返回一个字符串,从 m 对应文件的当前位置到下一个'n',当调用 readline() 时文件位于 EOF,则返回空字符串;

m.resize(n) ***有问题,执行不了***
把 m 的长度改为 n,m 的长度和 m 对应文件的长度是独立的;

m.seek(pos, how=0)
同 file 对象的 seek 操作,改变 m 对应的文件的当前位置;

m.size()
返回 m 对应文件的长度(不是 m 对象的长度len(m));

m.tell()
返回 m 对应文件的当前位置;

m.write(str)
把 str 写到 m 对应文件的当前位置,如果从 m 对应文件的当前位置到 m 结尾剩余的空间不足len(str),则抛出 ValueError;

m.write_byte(byte)
把1个字节(对应一个字符)写到 m 对应文件的当前位置,实际上 m.write_byte(ch) 等于 m.write(ch)。如果 m 对应文件的当前位置在 m 的结尾,也就是 m 对应文件的当前位置到 m 结尾剩余的空间不足1个字节,write() 抛出异常ValueError,而 write_byte() 什么都不做。

方法的使用说明:介绍上面常用的方法

测试文本:test.txt,mmap对象m

图片 3图片 4

-- MySQL dump 10.13  Distrib 5.6.19, for osx10.7 (x86_64)
--
-- Host: localhost    Database: test
-- ------------------------------------------------------
-- Server version       5.6.19

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

View Code

①: m.close(),关闭对象

>>> import os,mmap
>>> m=mmap.mmap(os.open('test.txt',os.O_RDWR),0)  #创业内存映射对象,
>>> m.read(10)                                    #可以使用方法
'-- MySQL d'
>>> m.close()                                     #关闭对象
>>> m.read(10)                                    #方法不可用
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: mmap closed or invalid

②:m.find(str, start=0),从start的位置开始寻找第一次出现的str。

>>> m.find('SET',0)      #从头开始查找第一次出现SET的字符串
197

③:m.read(n),返回一个从 m对象文件中读取的n个字节的字符串,将会把 m 对象的位置指针向后移动,后续读取会继续往下读。

>>> m.read(10)         #读取10字节的字符串
'-- MySQL d'
>>> m.read(10)         #读取上面10字节后,再往后的10字节数据
'ump 10.13 '

④:m.read_byte(),返回一个1字节长的字符串,从 m 对应的文件中读1个字节

>>> m.read_byte()   #读取第一个字节
'-'
>>> m.read_byte()   #读取第二个字节
'-'
>>> m.read_byte()   #读取第三个字节
' '

⑤:m.readline():返回一个字符串,从 m 对应文件的当前位置到下一个'n',当调用 readline() 时文件位于 EOF,则返回空字符串

>>> m.readline()             #读取一正行
'-- MySQL dump 10.13  Distrib 5.6.19, for osx10.7 (x86_64)n'
>>> m.readline()             #读取下一正行
'--n'

⑥:m.size():返回 m 对应文件的长度(不是 m 对象的长度len(m))

>>> m.size()            #整个文件的大小
782

⑦:m.tell():返回 m 对应文件的当前光标位置

>>> m.tell()        #当前光标的位置0
0
>>> m.read(10)      #读取10个字节
'-- MySQL d'
>>> m.tell()        #当前光标位置10
10

⑧:m.seek(pos, how=0),改变 m 对应的文件的当前位置

>>> m.seek(10)        #当前光标定位到10
>>> m.tell()          #读取当前光标的位置
10
>>> m.read(10)        #读取当前光标之后的10字节内容
'ump 10.13 '

⑨:m.move(dstoff, srcoff, n):等于 m[dstoff:dstoff+n] = m[srcoff:srcoff+n],把从 srcoff 开始的 n 个字节复制到从 dstoff 开始的n个字节

>>> m[101:108]            #切片101到108的值
'-------'
>>> m[1:8]                #切片1到8的值
'- MySQL'
>>> m.move(1,101,8)       #从101开始到后面的8字节(108),替换从1开始到后面的8字节(8)效果:m[1:8]=m[101:108] 
>>> m[1:8]                #被替换后
'-------'

⑩:m.write(str):把 str 写到 m 对应文件的当前光标位置(覆盖对应长度),如果从 m 对应文件的当前光标位置到 m 结尾剩余的空间不足len(str),则抛出 ValueError

>>> m.tell()                #当前光标位置
0 
>>> m.write('zhoujy')       #写入str,要是写入的大小大于原本的文件,会报错。m.write_byte(byte)不会报错。

>>> m.tell()                #写入后光标位置 
6 

>>> m.seek(0)               #重置,光标从头开始 

>>> m.read(10)              #查看10个字节,确定是否被修改成功 
'zhoujy---d'

⑪:m.flush():把 m 中从offset开始的n个字节刷到对应的文件中

注意:对于m的修改操作,可以当成一个列表进行切片操作,但是对于切片操作的修改需要改成同样长度的字符串,否则都会报错。如m中的10个字符串进行修改,必须改成10个字符的长度。

3,应用说明: 

1):读文件,ACCESS_READ

①:读取整个文件

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib

f = open('test.txt', 'r')
with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m:
#readline需要循环才能读取整个文件
    while True:
        line = m.readline().strip()
        print line
        #光标到最后位置(读完),就退出
        if m.tell()==m.size(): 
            break

效果:

图片 5图片 6

~$ python untitled.py                                                                                                                                1 ↵
-- ZHOUJY  ---dump 10.13  Distrib 5.6.19, for osx10.7 (x86_64)
--
-- Host: localhost    Database: test
-- ------------------------------------------------------
-- Server version       5.6.19

/*!40101 ZHOUJY SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */ZHOUJY;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */ ZHOUJY;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

View Code

②:逐步读取指定字节数文件

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib

with open('test.txt', 'r') as f:
    with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m:
        print '读取10个字节的字符串 :', m.read(10)
        print '支持切片,对读取到的字符串进行切片操作:', m[2:10]
        print '读取之前光标后的10个字符串', m.read(10)

效果:

图片 7图片 8

 ~$ python untitled.py
读取10个字节的字符串 : -- ZHOUJY 
支持切片,对读取到的字符串进行切片操作:  ZHOUJY 
读取之前光标后的10个字符串  ---dump 1

View Code

2):查找文件,ACCESS_READ

①:从整个文件查找所有匹配的

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib

word = 'ZHOUJY'
print '查找:', word

f = open('test.txt', 'r')
with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m:
    #也可以通过find(str,pos)来处理
    while True: 
        line = m.readline().strip()
        if line.find(word)>=0:
            print "结果:"
            print line
        elif m.tell()==m.size():
            break
        else:
            pass

效果:

图片 9图片 10

~$ python untitled.py
查找: ZHOUJY
结果:
-- ZHOUJY  ---dump 10.13  Distrib 5.6.19, for osx10.7 (x86_64)
结果:
/*!40101 ZHOUJY SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */ZHOUJY;
结果:
/*!40103 SET TIME_ZONE='+00:00' */ ZHOUJY;

View Code

②:从整个文件里查找,找到就退出(确认到底是否存在)

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib

word = 'ZHOUJY'
print '查找:', word

f = open('test.txt', 'r')
with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m:
    #不需要循环,只要找到一个就可以了
    loc = m.find(word)
    if loc >= 0:
        print loc
        print m[loc:loc+len(word)]

效果:

图片 11图片 12

~$ python untitled.py
查找: ZHOUJY
194
ZHOUJY

View Code

③:通过正则查找,(找出40开头的数字)

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import re
import contextlib

pattern = re.compile(r'(40d*)')

with open('test.txt', 'r') as f:
    with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m:
        print pattern.findall(m)

效果:

图片 13图片 14

 ~$ python untitled.py
['40101', '40101', '40101', '40101', '40103', '40103', '40014', '40014', '40101', '40111']

View Code 

3):处理文本,只能等长处理(通过上面的查找方法,来替换查找出的内容),模式:ACCESS_WRITE、ACCESS_COPY

经过上面对mmap方法的介绍和使用说明,大致了解了mmap的特点。这里通过对比sed的方法,来看看到底处理大文件使用哪种方法更高效。 

①:替换文本中出现一次的内容。比如想把A库的备份文件(9G)还原到B库,需要把里面的USE `A`改成USE `B`。

1> sed处理:时间消耗近105s;磁盘IO几乎跑满;内存几乎没消耗、CPU消耗10~20%之间。

1:替换文本中第一次出现的内容
~$ date && sed -i '0,/USE `edcba`;/s//USE `ABCDE`;/' test.sql && date
2016年 11月 16日 星期三 12:04:17 CST
2016年 11月 16日 星期三 12:06:02 CST

2:替换文本中指定行的内容
~$ date && sed -i '24s/USE `ABCDE`;/USE `edcba`;/' test.sql && date
2016年 11月 16日 星期三 12:09:05 CST
2016年 11月 16日 星期三 12:10:50 CST

IO消耗:

图片 15图片 16

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               1.00     7.00  772.00  105.00    87.22    92.06   418.65    27.90   31.35    2.21  245.56   1.14 100.00

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               1.00     4.00  778.00  102.00    87.59    90.03   413.36    25.08   30.30    2.59  241.65   1.13  99.60

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               2.00     5.00  771.00  101.00    87.48    88.04   412.22    29.80   30.24    2.34  243.21   1.14  99.60

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               1.00    18.00  431.00  137.00    49.08   122.04   616.99    66.20   70.25    3.02  281.75   1.75  99.60

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00     1.00    1.00  248.00     0.00   177.04  1456.16   105.24  416.53   24.00  418.11   4.02 100.00

View Code

2> python处理:时间消耗是毫秒级别的,几乎是秒级别完成,该情况比较特别:搜索的关键词在大文本里比较靠前的位置,这样处理上T的大文件也是非常快的,要是搜索的关键词靠后怎会怎么样呢?后面会说明。

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib
import re


word = 'USE `EDCBA`;'
replace = 'USE `ABCDE`;'
print '查找:', word
print'替换:', replace

f = open('test.sql', 'r+')
with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:
    loc = m.find(word)
    if loc >=0:
        print loc
        m[loc:loc + len(word)] = replace

执行:

~$ date && python mmap_python.py && date
2016年 11月 16日 星期三 12:14:19 CST
查找: USE `EDCBA`;
替换: USE `ABCDE`;
929
2016年 11月 16日 星期三 12:14:19 CST

②:替换文本中所有匹配的关键词。比如想把备份文件里的ENGINE=MYISAM改成ENGINE=InnoDB,看看性能如何。

1> sed处理:时间消耗110s;磁盘IO几乎跑满(读写IO高);内存几乎没消耗、CPU消耗10~30%之间。

~$ date && sed -i 's/ENGINE=InnoDB/ENGINE=MyISAM/g' test.sql && date
2016年 11月 16日 星期三 12:19:30 CST
2016年 11月 16日 星期三 12:21:20 CST 

和①中sed的执行效果差不多,其实对于处理一条还是多条记录,sed都是做同样工作量的事情,至于原因可以看了解sed的工作原理说明,个人理解大致意思就是:sed是1行1行读取(所以内存消耗很小),放入到自己设置的缓冲区里,替换完之后再写入(所以IO很高),处理速度受限于CPU和IO。

2> python处理:时间消耗20多秒,比sed少。因为不用重写所有内容,只需要替换指定的内容即可,并且是在内存中处理的,所以写IO的压力几乎没有。当关键词比较靠后,其读入的数据就比较大,文件需要从磁盘读入到内存,这时磁盘的读IO也很高,写IO还是没有。因为是虚拟内存映射文件,所以占用的物理内存不多,虽然通过TOP看到的内存使用率%mem很高,这里可以不用管,因为大部分都是在SHR列里的消耗,真正使用掉的内存可以通过RES-SHR来计算。关于top中SHR的意思,可以去看相关文章说明。

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib

word    = 'ENGINE=MyISAM'
replace = 'ENGINE=InnoDB'
print '查找:', word
print'替换:', replace

loc = 0
f = open('test.sql', 'r+')
with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:
    while True:
        loc = m.find(word,loc)
        if loc >=0:
            print loc
            m[loc:loc + len(word)] = replace
            #要是access=mmap.ACCESS_COPY需要执行flush
            #m.flush()
        elif loc == -1:
            break
        else:
            pass

效果:

~$ date && python mmap_python.py && date
2016年 11月 16日 星期三 13:19:30 CST
查找: ENGINE=MyISAM
替换: ENGINE=InnoDB
1663
5884938
11941259
12630481
12904261
64852169
64859312
65018692
65179617
65181544
65709930
149571849
3592900115
5874952354
7998151839
2016年 11月 16日 星期三 13:19:55 CST

③:正则匹配修改,这个可以通过上面介绍的查找方法,做下修改即可,就不再做说明。

小结:

      对比sed和python处理文件的方法,这里来小结下:对于sed不管修改的关键字在文本中的任意位置、次数,修改的工作量都一样(全文的读写IO),差距不大;对于python mmap的修改,要是关键字出现在比较靠前的地方,修改起来速度非常快,否则修改也会有大量的读IO,写IO没有。通过上面的对比分析来看,mmap的修改要比sed修改性能高。

      Python还有另一个读取操作的方法:open中的read、readline、readlines,这个方法是把文件全部载入内存,再进行操作。若内存不足直接用swap或则报错退出,内存消耗和文本大小成正比,而通过mmap模块的方法可以很好的避免了这个问题。

在线预览:http://github.lesschina.com/python/base/concurrency/1.并发编程~进程先导篇.html

前端开发QQ群:377786580

总结:

通过上面的介绍,大致知道如何使用mmap模块了,其大致特点如下:

  • 普通文件被映射到虚拟地址空间后,程序可以向访问普通内存一样对文件进行访问,在有些情况下可以提高IO效率。
  • 它占用物理内存空间少,可以解决内存空间不足的问题,适合处理超大文件。
  • 不同于通常的字符串对象,它是可变的,可以通过切片的方式更改,也可以定位当前文件位置m.tell()m.seek()定位到文件的指定位置,再进行m.write(str)固定长度的修改操作

最后,可以把mmap封装起来进行使用了,脚本信息:

图片 17图片 18

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib
import time
from optparse import OptionParser


def calc_time(func):
    def _deco(*args, **kwargs):
        begin_time = time.time()
        func(*args, **kwargs)
        cost_time = time.time() - begin_time
        print 'cost time: %s' % (cost_time)
    return _deco

@calc_time
def replace_keyword_all(filename,old_word,new_word):
    if len(old_word) == len(new_word):
        loc = 0
        print "%s 替换成 %s " %(new_word,old_word)
        with open(filename,'r+') as f:
            with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:
                while True:
                    loc = m.find(old_word,loc)
                    if loc >= 0:
                        m[loc:loc+len(old_word)] = new_word
                    elif loc == -1:
                        break
                    else:
                        pass
        f.close()
    else:
        print "替换的词要和被替换的词长度一致!"
        exit()


@calc_time
def replace_keyword_once(filename,old_word,new_word):
    if len(old_word) == len(new_word):
        print "%s 替换成 %s " %(new_word,old_word)
        with open(filename,'r+') as f:
            with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:
                loc = m.find(old_word)
                if loc >= 0:
                    m[loc:loc+len(old_word)] = new_word
        f.close()
    else:
        print "替换的词要和被替换的词长度一致!"
        exit()

if __name__ == "__main__":
    parser = OptionParser()
    parser.add_option("-f", "--filename", help="Filename for search", dest="filename")
    parser.add_option("-o", "--oldword", help="the ip to use", dest="old_word")
    parser.add_option("-n", "--newword", help="the ip to use", dest="new_word")

    (options, args) = parser.parse_args()

    if not options.filename:
        print 'params filename need to apply'
        exit()

    if not options.old_word:
        print 'params oldword need to apply'
        exit()

    if not options.new_word:
        print 'params newword need to apply'
        exit()
# 替换文本中第一次出现的内容(查到一个就处理退出,越靠前越快)
#    replace_keyword_once(options.filename,options.old_word,options.new_word)
# 替换文本中出现的内容(查找处理整个文本)
    replace_keyword_all(options.filename,options.old_word,options.new_word)

View Code

方法: 

图片 19图片 20

~$ python mmap_search.py -h
Usage: mmap_search.py [options]

Options:
  -h, --help            show this help message and exit
  -f FILENAME, --filename=FILENAME
                        Filename for search
  -o OLD_WORD, --oldword=OLD_WORD
                        the ip to use
  -n NEW_WORD, --newword=NEW_WORD
                        the ip to use

View Code

脚本处理效果:(40G的文本)

1)sed:替换文本中第一次出现的内容
~$ date && sed -i '0,/USE `EDCBA`;/s//USE `ABCDE`;/' test.sql && date
2016年 11月 17日 星期四 11:15:33 CST
2016年 11月 17日 星期四 11:21:47 CST

2)mmap:替换文本中第一次出现的内容(使用replace_keyword_once方法,查到一个就处理退出,越靠前越快)
~$ python mmap_search.py --filename='test.sql' --oldword="USE `EDCBA`;" --newword="USE `ABCDE`;"
USE `ABCDE`; 替换成 USE `EDCBA`; 
cost time: 0.000128984451294

3)sed:替换文本中出现的内容(查找处理整个文本)
~$ date && sed -i 's/ENGINE=InnoDB/ENGINE=MyISAM/g' test.sql && date
2016年 11月 17日 星期四 10:04:49 CST
2016年 11月 17日 星期四 10:11:34 CST

4)mmap:替换文本中出现的内容(使用replace_keyword_all方法,查找处理整个文本)
~$ python mmap_search.py --filename="test.sql" --oldword="ENGINE=MyISAM" --newword="ENGINE=InnoDB"
ENGINE=InnoDB 替换成 ENGINE=MyISAM 
cost time: 198.471223116

结论:修改大文本文件,通过sed处理,不管被修改的词在哪个位置都需要重写整个文件;而mmap修改文本,被修改的词越靠前性能越好,不需要重写整个文本,只要替换被修改词语的长度即可。

Linux专项¶

先写几个问号来概况下今天准备说的内容:(谜底自己解开,文中都有)

  1. 你知道Ctrl+C终止进程的本质吗?你知道Kill -9 pid的真正含义吗?
  2. 你知道那些跨平台框架(Python,NetCore)在Linux下创建进程干了啥?
  3. 你了解僵尸进程孤儿进程的悲催生产史吗?孤儿找干爹僵尸送往生想知道不?
  4. 想知道创建子进程后怎么李代桃僵吗?ps aux | grep xxx的背后到底隐藏了什么?
  5. 你了解Linux磁盘中p类型的文件到底是个啥吗?
  6. 为什么要realase发布而不用debug直接部署?这背后的性能相差几何?
  7. 还有更多进程间的密密私语等着你来查看哦~

关于帮助文档的说明:

  • 所有用到的系统函数你都可以使用man查看,eg:man 2 pipe
  • Python里面的方法你都可以通过help查看,eg:help(os.pipe)

这篇文章是基于我在迁移 的时候,和在公司跟进部署 HTTPS 的一些经验所编写。收录在《Said - 从 HTTP 到 HTTPS 》系列:

参考文档:

Memory-mapped file support

通过mmap库映射文件到内存用法

mmap模块与mmap对象

 

1.概念引入¶

正式讲课之前,先说些基本概念,难理解的用程序跑跑然后再理解:如有错误欢迎批评指正

并发 :一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

并行 :当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。(在同一个时间段内,两个或多个程序执行,有时间上的重叠)


通俗的举个例子:

小明、小潘、小张、小康去食堂打饭,4个小伙子Coding了3天,饿爆了,现在需要1分钟内让他们都吃上饭,不然就有可怕的事情发生。

按照正常的流程,1分钟可能只够他们一个人打饭,这不行啊,于是有了几种处理方法:

并发:快快快,一人先吃一口,轮着来,一直喂到你们都饱了(只有一个食堂打饭的窗口)(单核CPU)

并行

  • 开了2~3个窗口,和上面处理一样,只是竞争的强度没那么大了
  • 开了4个窗口,不着急,一人一个窗口妥妥的

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开两个浏览器就启动了两个浏览器进程。

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)

由于每个进程至少要干一件事,所以,一个进程至少有一个线程。像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。

通俗讲:线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间


PS:进程5态下次正式讲程序的时候会说,然后就是==> 程序实战不会像今天这样繁琐的,Code很简单,但是不懂这些基本概念往后会吃很多亏,逆天遇到太多坑了,所以避免大家入坑,简单说说这些概念和一些偏底层的东西~看不懂没事,有个印象即可,以后遇到问题至少知道从哪个方向去解决

 

  • 从 HTTP 到 HTTPS - 什么是 HTTPS
  • 从 HTTP 到 HTTPS - IIS 部署免费 HTTPS
  • 从 HTTP 到 HTTPS - 网站部署 HTTPS 中需要做的事情

2.进程相关¶

示例代码:

部署到 HTTPS 会发生什么

HTTP 协议和 HTTPS 协议是不兼容的,即 HTTPS 和 HTTP 是不可互相访问的 (混合资源),当 HTTPS 页面中包含 HTTP 内容的时候,浏览器会向用户抛出警告,这个网页是加密的,但是却包含不安全的元素,即混合资源 (Mixed Content)。

图片 21

随着 chrome 的 安全计划,今后以下的 API 只能在 安全环境 中使用:

  • Geolocation - 获取用户地理位置
  • Devicemotion / orientation - 设备方向和运动信息
  • Encrypted Media Extensions/EME - 加密媒体扩展
  • getUserMedia - 采集摄像头/音频/屏幕信息

实测中,当前获取用户地理位置 API navigator.geolocation.getCurrentPosition 已经只能在安全环境 (可以理解为 HTTPS 环境)中使用,在chrome下,非安全环境使用该 API 会显示警告:

getCurrentPosition() and watchPosition() no longer work on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.

2.1.fork引入¶

示例代码:

(linux/unix)操作系统提供了一个fork()系统调用。普通的函数调用,调用一次,返回一次,但是fork()一次调用,两次返回

因为操作系统自动把父进程复制了一份,分别在父进程和子进程内返回。为了便于区分,操作系统是这样做的:子进程永远返回0,而父进程返回子进程的ID

查看下帮助文档:

import os

help(os.fork)
Help on built-in function fork in module posix:

fork()
    Fork a child process.

    Return 0 to child process and PID of child to parent process.

我们来跑个程序验证一下:(PID返回值如果小于0一般都是出错了)

import os

def main():
    print("准备测试~PID:%d" % os.getpid())
    pid = os.fork()
    if pid == 0:
        print("子进程:PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
    elif pid > 0:
        print("父进程:PID:%d,PPID:%d" % (os.getpid(), os.getppid()))

if __name__ == '__main__':
    main()

结果:

准备测试~PID:11247
父进程:PID:11247,PPID:11229
子进程:PID:11248,PPID:11247

可以查看下这个进程是啥: 图片 22

这个指令如果还不熟悉,Linux基础得好好复习下了:,简单分析下吧:a是查看所有(可以联想ls -a),u是显示详细信息,x是把不依赖终端的进程也显示出来(终端可以理解为:人与机器交互的那些)

技巧:指令学习可以递增式学习:psps a ps au ps aux ps ajx

现在验证一下复制一份是什么意思:(代码原封不动,只是在最下面添加了一行输出)

import os

def main():
    print("准备测试~PID:%d" % os.getpid())
    pid = os.fork() # 子进程被父进程fork出来后,在fork处往下执行

    if pid == 0:
        print("子进程:PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
    elif pid > 0:
        print("父进程:PID:%d,PPID:%d" % (os.getpid(), os.getppid()))

    print("PID:%d,我是卖报的小行家,大风大雨都不怕" % os.getpid())

if __name__ == '__main__':
    main()

输出:(父子进程的执行顺序是系统调度决定的)

准备测试~PID:13081
父进程:PID:13081,PPID:9388
PID:13081,我是卖报的小行家,大风大雨都不怕
子进程:PID:13083,PPID:13081
PID:13083,我是卖报的小行家,大风大雨都不怕

的确是Copy了一份,Code都一样(玩过逆向的应该知道,这份Code其实就放在了.text(代码段)里面

子进程被父进程fork出来后,在fork处往下执行(Code和父进程一样),这时候他们就为了抢CPU各自为战了

最后验证一下:各个进程地址空间中数据是完全独立的(有血缘关系的则是:读时共享,写时复制,比如父子进程等)

import os

def main():
    num = 100
    pid = os.fork()
    # 子进程
    if pid == 0:
        num += 10
    elif pid > 0:
        num += 20

    print("PID:%d,PPID:%d,Num=%d" % (os.getpid(), os.getppid(), num))

if __name__ == '__main__':
    main()

输出:(进程间通信下一节课会系统的讲,今天只谈Linux和概念)

PID:6369,PPID:6332,Num=120
PID:6376,PPID:6369,Num=110

扩展:(简单了解下即可)

  1. 程序:二进制文件(占用磁盘)
  2. 进程:启动的程序(所有数据都在内存中,需要占用CPU、内存等资源)
  3. 进程是CPU、内存、I/0设备的抽象(各个进程地址空间中数据是完全独立的
  4. 0号进程是Linux内核进程(这么理解:初代吸血鬼)
  5. 1号进程是用户进程,所有进程的创建或多或少和它有关系(init or systemd
  6. 2号进程和1号进程一样,都是0号进程创建的,所有线程调度都和他有关系

先看看Linux启动的图示:(图片来自网络) 图片 23

查看一下init进程 图片 24

CentOS进行了优化管理~systemd 图片 25

其实程序开机启动方式也可以知道区别了:systemctl start mongodb.service and sudo /etc/init.d/ssh start

Win系列的0号进程: 图片 26


第5点的说明:(以远程CentOS服务器为例) pstree -ps

systemd(1)─┬─NetworkManager(646)─┬─{NetworkManager}(682)
           │                     └─{NetworkManager}(684)
           ├─agetty(1470)
           ├─auditd(600)───{auditd}(601)
           ├─crond(637)
           ├─dbus-daemon(629)───{dbus-daemon}(634)
           ├─firewalld(645)───{firewalld}(774)
           ├─lvmetad(483)
           ├─master(1059)─┬─pickup(52930)
           │              └─qmgr(1061)
           ├─polkitd(627)─┬─{polkitd}(636)
           │              ├─{polkitd}(639)
           │              ├─{polkitd}(640)
           │              ├─{polkitd}(642)
           │              └─{polkitd}(643)
           ├─rsyslogd(953)─┬─{rsyslogd}(960)
           │               └─{rsyslogd}(961)
           ├─sshd(950)───sshd(50312)───sshd(50325)───bash(50326)───pstree(54258)
           ├─systemd-journal(462)
           ├─systemd-logind(626)
           ├─systemd-udevd(492)
           └─tuned(951)─┬─{tuned}(1005)
                        ├─{tuned}(1006)
                        ├─{tuned}(1007)
                        └─{tuned}(1048)

再看一个例子:

[dnt@localhost ~]$ pstree dnt -ps
sshd(50325)───bash(50326)───pstree(54471)
[dnt@localhost ~]$ pstree 50325 -ps
systemd(1)───sshd(950)───sshd(50312)───sshd(50325)───bash(50326)───pstree(54489)

其实你可以在虚拟机试试干死1号进程,就到了登录页面了【现在大部分系统都不让你这么干了】 kill -9 1

-bash: kill: (1) - 不允许的操作

 

做哪些事

2.2.僵尸进程和孤儿进程¶

示例代码:

先看看定义:

孤儿进程 :一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程 :一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

通俗讲就是:

孤儿进程:你爸在你之前死了,你成了孤儿,然后你被进程1收养,你死后的事宜你干爹帮你解决

僵尸进程:你挂了,你爸忙着干其他事情没有帮你安葬,你变成了孤魂野鬼,你的怨念一直长存世间

举个例子看看:

import os
import time

def main():
    pid = os.fork()
    if pid == 0:
        print("子进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
        time.sleep(1)  # 睡1s
    elif pid > 0:
        print("父进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))

    print("pid=%d,over" % os.getpid())

if __name__ == '__main__':
    main()

输出: 图片 27

import os
import time

def main():
    pid = os.fork()
    if pid == 0:
        print("子进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
    elif pid > 0:
        print("父进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
        while True:
            print("父亲我忙着呢,没时间管你个小屁孩")
            time.sleep(1)

    print("pid=%d,over" % os.getpid())

if __name__ == '__main__':
    main()

输出+测试: 图片 28

其实僵尸进程的危害真的很大,这也就是为什么有些人为了追求效率过度调用底层,不考虑自己实际情况最后发现还不如用自托管的效率高

僵尸进程是杀不死的,必须杀死父类才能彻底解决它们,下面说说怎么让父进程为子进程‘收尸’


自适应协议资源路径

传统的资源路径会一般会写成绝对路径和相对路径:

<img src="/static/bar.jpg"/>
<img src="http://tasaid.com/static/bar.jpg" />

绝对路径的资源建议使用 // 语法让它兼容 HTTP/HTTPS,//语法表示这个资源的访问协议和当前页面保持一致,如果当前页面是 HTTPS 的,则会采用 HTTPS 协议访问,如果是 HTTP 的,则使用 HTTP 协议访问。

<img src="//tasaid.com/static/bar.jpg" /><!--https://tasaid.com 中会访问 https://tasaid.com/static/bar.jpg-->

2.3.父进程回收子进程(wait and waitpid)¶

讲解之前先简单分析一下上面的Linux指令(防止有人不太清楚)

kill -9 pid ==> 以前逆天说过,是无条件杀死进程,其实这种说法不准确,应该是发信号给某个进程

-9指的就是信号道里面的SIGKILL(信号终止),你写成kill -SIGKILL pid也一样

-9只是系统给的一种简化写法(好像记得1~31信号,各个Linux中都差不多,其他的有点不一样)

dnt@MZY-PC:~/桌面/work/PC/python/Thread/Linux kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
 6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

一般搜索进程中的某个程序一般都是用这个:ps -aux | grep xxx|其实就是管道,用于有血缘关系进程间通信,等会讲)

如果安装了pstree就更方便了:pstree 13570 -ps (Ubuntu自带,CentOS要装下yum install psmisc

systemd(1)───systemd(1160)───gnome-terminal-(21604)───bash(8169)───python3(13570)───python3(13571)

扩展:我们平时Ctrl+C其实就是给 2)SIGINT发一个信号


异步请求

Python之mmap内存映射模块,网站部署。相对路径下的异步请求没有问题,绝对路径的请求会有问题:

$.ajax('http://tasaid.com/user/get')

如果请求的 url 是兼容 HTTPS 的话,则可以在 HTTPS 环境下使用 https:// 访问,否则需要服务器做一个 HTTPS包装跳转,将原 url 的请求在自己的服务器做一层转发,表单提交同理。

$.ajax('/httpsRedirect?url=http%3A%2F%2Flinkflys.com%2Fuser%2Fget')

2.3.1.wait¶

代码实例:

步入正题:

Python的Wait和C系列的稍有不同,这边重点说说Python:

help(os.wait)

Help on built-in function wait in module posix:

wait()
    Wait for completion of a child process.

    Returns a tuple of information about the child process:
        (pid, status)

os.wait()返回一个元组,第一个是进程id,第二个是状态,正常退出是0,被九号信号干死就返回9

来个案例:

import os
import time

def main():
    pid = os.fork()
    if pid == 0:
        print("子进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
    elif pid > 0:
        print("父进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
        wpid, status = os.wait()
        print(wpid)
        print(status)

    print("pid=%d,over" % os.getpid())

if __name__ == '__main__':
    main()

输出:

父进程:Pid=22322,PPID=10139
子进程:Pid=22323,PPID=22322
pid=22323,over
22323
0
pid=22322,over

演示一下被9号信号干死的情况:

import os
import time

def main():
    pid = os.fork()
    if pid == 0:
        print("子进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
        while True:
            print("孩子老卵,就是不听话")
            time.sleep(1)
    elif pid > 0:
        print("父进程:Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
        wpid, status = os.wait()  # 调用一次只能回收一次,想都回收,就来个while循环,-1则退出
        print(wpid)
        print(status)
        if status == 0:
            print("正常退出")
        elif status == 9:
            print("被信号9干死了")

    print("pid=%d,over" % os.getpid())

if __name__ == '__main__':
    main()

输出: 图片 29


扩展:(回收所有子进程,status返回-1代表没有子进程了,Python里面没有子进程会触发异常)

import os
import time

def main():
    i = 0
    while i < 3:
        pid = os.fork()
        # 防止产生孙子进程(可以自己思索下)
        if pid == 0:
            break
        i += 1

    if i == 0:
        print("i=%d,子进程:Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
        time.sleep(1)
    elif i == 1:
        print("i=%d,子进程:Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
        time.sleep(1)
    elif i == 2:
        print("i=%d,子进程:Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
        time.sleep(3)
        while True:
            print("(PID=%d)我又老卵了,怎么滴~" % os.getpid())
            time.sleep(3)
    elif i==3: # 循环结束后,父进程才会退出,这时候i=3
        print("i=%d,父进程:Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
        while True:
            print("等待回收子进程")
            try:
                wpid, status = os.wait()
                print(wpid)
                print(status)
                if status == 0:
                    print("正常退出")
                elif status == 9:
                    print("被信号9干死了")
            except OSError as ex:
                print(ex)
                break

    print("pid=%d,over,ppid=%d" % (os.getpid(), os.getppid()))

if __name__ == '__main__':
    main()

演示:看最后一句输出,父进程扫尾工作做完就over了 图片 30


iframe

iframe 只能是被嵌入的 url 也同样支持 HTTPS,目前本人并未找到合适的方案。当然如果你们服务端真心 NB 的话也可以像某大型搜索引擎一样把需要内嵌 iframe 的站点抓到自己的服务器上。

2.3.2.waitpid¶

代码实例:

上面说的wait方法是阻塞进程的一种方式,waitpid可以设置不阻塞进程

help(os.waitpid)

Help on built-in function waitpid in module posix:

waitpid(pid, options, /)
    Wait for completion of a given child process.

    Returns a tuple of information regarding the child process:
        (pid, status)

    The options argument is ignored on Windows.

等待进程id为pid的进程结束,返回一个tuple,包括进程的进程ID和退出信息(和os.wait()一样),参数options会影响该函数的行为。在默认情况下,options的值为0。

  1. 如果pid是一个正数,waitpid()请求获取一个pid指定的进程的退出信息
  2. 如果pid为0,则等待并获取当前进程组中的任何子进程的值
  3. 如果pid为-1,则等待当前进程的任何子进程
  4. 如果pid小于-1,则获取进程组id为pid的绝对值的任何一个进程
  5. 当系统调用返回-1时,抛出一个OSError异常。

官方原话是这样的:(英语好的可以看看我有没有翻译错)

If pid is greater than 0, waitpid() requests status information for that specific process.
If pid is 0, the request is for the status of any child in the process group of the current process. 
If pid is -1, the request pertains to any child of the current process. 
If pid is less than -1, status is requested for any process in the process group -pid (the absolute value of pid).

options:(宏)

os.WNOHANG - 如果没有子进程退出,则不阻塞waitpid()调用
os.WCONTINUED - 如果子进程从stop状态变为继续执行,则返回进程自前一次报告以来的信息。
os.WUNTRACED - 如果子进程被停止过而且其状态信息还没有报告过,则报告子进程的信息。

补充:

  1. 进程组:每一个进程都属于一个“进程组”,当一个进程被创建的时候,它默认是其父进程所在组的成员(你们一家
  2. 会 话:几个进程组又构成一个会话(你们小区

用法和wait差不多,就是多了一个不阻塞线程的方法:

import os
import time

def main():
    pid = os.fork()

    if pid == 0:
        print("[子进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
        time.sleep(2)

    elif pid > 0:
        print("[父进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
        while True:
            try:
                wpid, status = os.waitpid(-1, os.WNOHANG)
                if wpid > 0:
                    print("回收子进程wpid:%d,状态status:%d" % (wpid, status))
            except OSError:
                print("没有子进程了")
                break

            print("父进程忙着挣钱养家呢~")
            time.sleep(3)

    print("[over]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))

if __name__ == '__main__':
    main()

输出:

[父进程]PID:1371,PPID:29604
[子进程]PID:1372,PPID:1371
父进程忙着挣钱养家呢~
[over]PID:1372,PPID:1371
回收子进程wpid:1372,状态status:0
父进程忙着挣钱养家呢~
没有子进程了
[over]PID:1371,PPID:29604

HTTP严格传输安全协议

HTTP 严格传输安全协议( HTTP Strict Transport Security,简称 HSTS )是 互联网工程任务小组 (Internet Engineering Task Force,简称IETF) 发布的互联网安全策略,后者负责互联网标准的开发和推动。网站可以选择使用 HSTS 策略,让浏览器强制使用 HTTPS 协议访问。

为什么要强制访问呢? 因为传统的 HTTP 跳到 HTTPS 都依赖服务端 301/302 跳转,例如访问 http://tasaid.com 跳转到 https://tasaid.com,而这次强制跳转的通信,是基于 HTTP 的,所以是可能被劫持的。

设置 HSTS 之后,浏览器会在本地替换协议为 HTTPS 然后访问服务器,而不用再依赖服务器跳转,可以更多的减少会话劫持攻击。

HSTS 是一个响应头,只能用于 HTTPS 响应,HTTP 环境下会忽略掉这个响应头:

Strict-Transport-Security: max-age=expireTime [; includeSubDomains] [; preload]
参数 释义
max-age 指定的时间内 (单位是秒),网站必须使用 HTTPS 协议来访问
includeSubDomains 子域名也必须通过 HTTPS 协议来访问
preload 让浏览器由安全域名列表 (Preload List) 决定是否本地替换为 HTTPS 请求

最后这个 preload 可能有点抽象,就是各大浏览器厂商 (Chrome/Firefox/IE/Safari/Edge) 共同维护的一个域名列表 (Preload List),你可以 在这里查询 ,chrome 浏览器可以直接在本地访问 chrome://net-internals/#hsts 查询。

设定 preload 参数,浏览器会 根据当前网站满足的条件 尝试把网站加入这个域名列表 (Preload List),其他用户再访问这个网站的时候,如果这个网站域名存在于这个域名列表中,则自动启用 HTTPS 访问。

当用户第一次访问一个从来没访问过的网站时,本地是没有 HSTS 信息的,所以这个第一次的会话仍然是可能被劫持的。preload 就是为了解决这个第一次会话劫持的问题的。

值得注意的是:一旦 HSTS 生效,在 max-age 指定的时间内,你再想把网站重定向为 HTTP,之前的老用户会被无限重定向。而且一旦网站证书错误,用户无法选择忽略。

HSTS 是个大招,不要随便开,不然技能冷却时间的时间内。

2.3.3.wait3 and wait4¶

代码实例:

help(os.wait3)

Help on built-in function wait3 in module posix:

wait3(options)
    Wait for completion of a child process.

    Returns a tuple of information about the child process:
      (pid, status, rusage)

help(os.wait4)

Help on built-in function wait4 in module posix:

wait4(pid, options)
    Wait for completion of a specific child process.

    Returns a tuple of information about the child process:
      (pid, status, rusage)

这个是Python扩展的方法,用法和wait、waitpid差不多,我就不一个个的举例子了,以wait3为例

import os
import time

def main():
    pid = os.fork()

    if pid == 0:
        print("[子进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
        time.sleep(2)

    elif pid > 0:
        print("[父进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
        while True:
            try:
                wpid, status, rusage = os.wait3(os.WNOHANG)
                if wpid > 0:
                    print("回收子进程wpid:%d,状态status:%dn详细信息:%s" % (wpid, status,
                                                                 rusage))
            except OSError:
                print("没有子进程了")
                break

            print("父进程忙着挣钱养家呢~")
            time.sleep(3)

    print("[over]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))

if __name__ == '__main__':
    main()

输出

[父进程]PID:2638,PPID:29604
[子进程]PID:2639,PPID:2638
父进程忙着挣钱养家呢~
[over]PID:2639,PPID:2638
回收子进程wpid:2639,状态status:0
详细信息:resource.struct_rusage(ru_utime=0.0052179999999999995, ru_stime=0.0052179999999999995, ru_maxrss=7032, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=869, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=2, ru_nivcsw=0)
父进程忙着挣钱养家呢~
没有子进程了
[over]PID:2638,PPID:29604

本文由澳门新葡亰手机版发布于编程,转载请注明出处:Python之mmap内存映射模块,网站部署

上一篇:Python2与python3中字符串的区别,开发笔记 下一篇:Python3之多进程,的内部功能介绍
猜你喜欢
热门排行
精彩图文