好库文摘 http://doc.okbase.net/ Percona XtraBackup 关于 MySQL备份还原的详细测试 http://doc.okbase.net/doclist/archive/264657.html doclist 2019/4/20 3:48:10

 一. Percona XtraBackup 的优点。

(1)无需停止数据库进行InnoDB热备;

(2)增量备份MySQL;

(3)流压缩传输到其它服务器;

(4)在线移动表;

(5)能够比较容易地创建主从同步;

(6)备份MySQL时不会增大服务器负载。

 二. Percona XtraBackup 工具集组成

在旧版本中,Percona XtraBackup 工具集里主要包含两个程序:innobackupex 和 xtrabackup。

xtrabackup是C/C++编译的二进制程序,用来备份InnoDB的,并不能备份非InnoDB表,它在内部实现了对InnoDB的热备份。

innobackupex由perl脚本编写,是对xtrabackup的封装,通过调用xtrabackup命令来备份InnoDB表,通过调用mysqldump等命令来实现对非InnoDB表的备份,并且会和MySQL数据库发送命令交互,例如获取Binlog位点、添加锁操作。

因为我们公司,大量使用 innodbbackupex 备份还原,所以,本文的讲解测试仍是以innodbbackupex为主。

 三. innodbbackupex常用的参数选项

参数选项 作用描述 备注
--host 指定数据库实例的IP地址。 备份阶段
--port 连接数据库实例的端口号。 备份阶段
--user 备份时连接数据库实例/数据库的用户名。 备份阶段
--password 连接所用账号的密码。 备份阶段
--copy-back 将准备好的备份文件从备份目录复制到原始位置,其原始位置目录必须为空,否则报错(除非指定--force-non-empty-directories选项)。 恢复阶段
--incremental 该选项表示创建增量备份,而不是全量备份。 备份阶段
--incremental-basedir 该选项接受一个字符串参数,表示包含作为增量备份基本数据集的完整备份的目录。与--incremental一起使用。 备份阶段
--incremental-dir 该选项接受一个字符串参数,表示增量备份与完整备份的目录,以进行新的完整备份。与--incremental一起使用。 备份阶段
--redo-only 在prepare阶段,完整备份合并除最后一个增量之外的所有增量时,使用该选项。迫使innobackupex跳过“回滚”阶段,只做一次“重做”。如果已经在全量备份上重放了提交的事务并回滚了未提交的事务,则无法在此备份上添加增量。同样,如果在增量备份上执行,则无法添加其余的增量备份。 Prepare阶段
--incremental-lsn 增量备份是基于LSN来完成的,所以,增量备份也可以指定LSN值进行。 备份阶段
--apply-log 在创建备份后,备份数据其实处于不可用状态。因为在redo log中可能存在未提交的事务和已提交的事务,需要通过准备阶段来使备份数据达到一致状态。通过此阶段,备份数据就可以用来恢复了。在准备阶段,需要指定的参数选项就包含apply-log。  
--socket 该选项表示mysql.sock所在位置,以便备份进程登录mysql。在MySQL多实例场景下,需指明。  
     

 四. 安装

这个版本是 Percona XtraBackup 2.4 版本。

Step 1 :下载执行文件

wget https://www.percona.com/downloads/XtraBackup/Percona-XtraBackup-2.4.9/binary/redhat/6/x86_64/Percona-XtraBackup-2.4.9-ra467167cdd4-el6-x86_64-bundle.tar

step 2 :解压执行文件

tar -xvf Percona-XtraBackup-2.4.9-ra467167cdd4-el6-x86_64-bundle.tar

step 3:安装

yum install percona-xtrabackup-24-2.4.9-1.el6.x86_64.rpm –y

step 4 安装验证

查看 可执行文件

which xtrabackup

版本检查

xtrabackup --version

 五. 全量备份与全量恢复 

Step 1 环境介绍及准备 

 测试环境:在一台服务器上开启2个实例,一个是3306,另一个是3307.测试的基本步骤是将3306实例上的数据库备份、然后还原到3306实例中。

用来测试的数据库为Test_Xtrabackup

step 2 全量备份

innobackupex  --port 3306  --socket /tmp/mysql_3306.sock --user=root --password=输入自己的密码 /data/backup/full

因为测试方便使用了 root 账号,进而不可以输入对应 --host参数。

因为测试服务器上,安装了多实例,所以输入了 --socket 参数。

生成的全量备份文件会存储在一个以备份时间命名的子目录下。

 step 3 Prepare 阶段

在创建备份后,备份数据其实处于不可用状态。因为在redo log中可能存在未提交的事务和已经提交的事务,需要通过准备阶段来使备份数据达到一致的状态。通过此阶段,备份数据就可以用来恢复了。

在准备阶段,需要指定选项 --apply-log 和备份文档路径。

innobackupex --apply-log /data/backup/full/2019-04-19_10-56-22

step 4 还原环境的准备

我们打算将数据库还原到同一台服务器上的3307 端口的mysql实例中。

先登入看下。

测试需要 关闭3307 实例

将实例对应的数据文档清空(/data/mysql3307/data)

step 5 恢复阶段

在Prepare阶段过后,如果需要用备份数据来恢复数据库,则只需要指定--copy-back 和备份数据所在目录即可。

innobackupex --datadir=/data/mysql3307/data  --copy-back  /data/backup/full/2019-04-19_10-56-22

innobackupex 将所有的数据相关文件复制到服务器中的datadir目录,该目录有my.cnf文件中的datadir选项指定。因为是多实例,在此直接指定。

step 6 修改文件的所有权

复制完成后,文件属性不会改变。在大多数的情况下,在启动MySQL数据库之前,需要修改文件的所有权。

chown -R mysql:mysql /data/mysql3307/data

step 7 开启恢复后的实例,数据验证

以上截图说明 3306 上的数据已恢复至3307 实例上。

 六. 增量备份与恢复的测试

1. 备份阶段

环境准备,在完整备份前,创建了一个 表 T_full_table;并向此表插入了一笔数据,如下图所示:

 增量备份基于全量备份,所以需要创建一个全量备份。全量备份的命令如下:

innobackupex  --port 3306  --socket /tmp/mysql_3306.sock --user=root --password=你的密码 /data/backup/full

全量备份后生成的(时间子)目录为 2019-04-19_21-28-32,这个目录会在第一次增量备份是用到,incremental-basedir 选项参数需要细化到这一层,否则报错。意思是找不到xtrabackup_checkpoints文件。

 全量备份后,向表中insert  2 笔 数据,如下:

第一个增量备份

innobackupex  --port 3306  --socket /tmp/mysql_3306.sock --user=root --password=你的密码  --incremental --incremental-basedir=/data/backup/full/2019-04-19_21-28-32 /data/backup/incremental

 增量备份产生的目录文件为  /data/backup/incremental/2019-04-19_21-30-11

第一次增量备份后,继续insert 2笔数据

第二个增量备份(我们是否可以在增量备份的基础上再做增量备份呢?答案是肯定的,只要把--incremental-basedir设置为上一次增量备份的目录即可。即:增量备份可以基于以前的增量备份完成数据备份。)

innobackupex  --port 3306  --socket /tmp/mysql_3306.sock --user=root --password=你的密码  --incremental --incremental-basedir=/data/backup/incremental/2019-04-19_21-30-11 /data/backup/incremental

第二次增量备份产生的目录文件为 /data/backup/incremental/2019-04-19_21-35-33

再次插入2笔数据,用来验证第三次增量备份

第三个增量备份

 innobackupex  --port 3306  --socket /tmp/mysql_3306.sock --user=root --password=你的密码  --incremental --incremental-basedir=/data/backup/incremental/2019-04-19_21-35-33 /data/backup/incremental

第三次增量备份产生的目录文件为 /data/backup/incremental/2019-04-19_21-38-16

再次插入2笔验证数据,用来验证第四次增量备份。

生成第四个增量备份

innobackupex  --port 3306  --socket /tmp/mysql_3306.sock --user=root --password=你的密码  --incremental --incremental-basedir=/data/backup/incremental/2019-04-19_21-38-16 /data/backup/incremental

第四次增量备份产生的目录文件为 /data/backup/incremental/2019-04-19_21-40-02

最后一次插入2笔验证数据

我们希望还原后的数据库是 刚刚作为第三次完整增量备份的数据库状态。

(此时T_full_table 表的数据,应该为 1(全量),2,3 (一增),21,22 (二赠),31,32 (三赠))

 2. 增量恢复的Prepare阶段

 1) 在全量备份上执行

innobackupex --apply-log --redo-only /data/backup/full/2019-04-19_21-28-32

 2 在第一个增量备份上执行 (增量备份产生的子目录是 2019-04-19_21-30-11,前面完整备份的目录是 /data/backup/full/2019-04-19_21-28-32)

innobackupex --apply-log --redo-only --incremental-dir=/data/backup/incremental/2019-04-19_21-30-11 /data/backup/full/2019-04-19_21-28-32

 3) 在第二个增量备份上执行 (增量备份产生的子目录是 2019-04-19_21-35-33,前面完整备份的目录是 /data/backup/full/2019-04-19_21-28-32)

innobackupex --apply-log --redo-only --incremental-dir=/data/backup/incremental/2019-04-19_21-35-33 /data/backup/full/2019-04-19_21-28-32

4) 在第二个增量备份上执行 (增量备份产生的子目录是 2019-04-19_21-38-16 ,前面完整备份的目录是 /data/backup/full/2019-04-19_21-28-32)

innobackupex --apply-log --incremental-dir=/data/backup/incremental/2019-04-19_21-38-16 /data/backup/full/2019-04-19_21-28-32

3. 增量备份的恢复阶段

通过prepare阶段,base 目录包含了所有数据。在恢复时,只需要通过以下操作即可。

innobackupex --datadir=/data/mysql3307/data  --copy-back  /data/backup/full/2019-04-19_21-28-32

在数据库实例启动前,需要修改文件的所有权,执行指令如下

chown -R mysql:mysql /data/mysql3307/data

4. 开启实例,进行验证

 恢复后的数据,正是我们想要的数据,测试到达了预期效果。

 

注:

现在xtrabackup版本升级到了2.4,相比之前的2.1有了比较大的变化:innobackupex 功能全部集成到 xtrabackup 里面,只有一个 binary,另外为了使用上的兼容考虑,innobackupex作为 xtrabackup 的一个软链,即xtrabackup现在支持非Innodb表备份,并且Innobackupex在下一版本中移除,建议通过xtrabackup替换innobackupex。

 

-----本文部分内容参考梳理于网络知识,仅为笔记,在此原创作者感谢!

]]>
Linux 权限位详解 http://doc.okbase.net/doclist/archive/264656.html doclist 2019/4/20 2:09:35



1. Linux 权限位

对于权限,有点绕,因为文件的权限和目录的权限是有一些区别的。


在Linux中,有5种权限,分别是,r、w、x、s、t。

  • 可读权限:r
  • 可写权限:w
  • 可执行权限:x
  • Setuid:s(Set User ID)
  • Setgid:s(Set Group ID)
  • 粘滞位:t


下面依次讲解权限位:



1.1 可读权限


对于文件,可读权限:

  • 用字符表示:r
  • 用八进制表示:4

  • 可以对读取文件里的内容


对于目录,可读权限:

  • 用字符表示:r
  • 用八进制表示:4
  • 可以列出目录下的内容






1.2 可写权限


对于文件,可写权限:

  • 用字符表示:w
  • 用八进制表示:2

  • 可以对文件进行更改


对于目录,可写权限:

  • 用字符表示:w
  • 用八进制表示:2
  • 可以在目录下创建文件或目录






1.3 可执行权限


对于文件,可写权限:

  • 用字符表示:x
  • 用八进制表示:1

  • 可以执行该文件(脚本或命令)


对于目录,可写权限:

  • 用字符表示:x
  • 用八进制表示:1
  • 可以cd进入该目录






1.4 Setuid

这是一个特殊的权限位,


对于文件,Setuid:

  • 用字符表示:s
  • 用八进制表示:4000

Setuid最常用的是配合执行权限x使用,例如,系统中内置命令passwd,它默认是带有s权限位,passwd命令的主要功能是修改用户的密码,而修改密码的流程是:

  1. 将加密后的哈希值写入到/etc/passwd文件对应的用户条目中。
  2. 使用pwconv工具转换到/etc/shadow文件中。
  3. 而普通用户是没有权限修改/etc/passwd/etc/shadow文件

在普通用户尝试执行passwd,该passwd的所有者是root并且设置了Suid,因此passwd以root身份执行。


当你查看进程时,你会发现,进程不是普通用户,而是passwd工具的所有者(root)






1.5 Setgid

这是一个特殊的权限位,


对于目录,Setgid:

  • 用字符表示:s
  • 用八进制表示:2000

当一个目录拥有sgid权限时,其他用户在该目录下创建文件或目录后,它会继承目录的id,即创建的文件或目录的属组为父目录的属组。

[root@self data]# mkdir project
[root@self data]# chmod 2777 project/
[root@self data]# ls -lh
total 0
drwxrwsrwx 2 root root 6 Apr 20 23:42 project
[root@self data]# su bob
[bob@self data]$ mkdir project/test_for_bob
[bob@self data]$ ls -lh project/
total 0
drwxrwsr-x 2 bob root 6 Apr 20 23:42 test_for_bob






1.6 粘滞位

这是一个特殊的权限位,


对于目录粘滞位:

  • 用字符表示:t
  • 用八进制表示:1000

/tmp目录就是使用了粘滞位t,其作用是,在该目录下创建文件或目录后,仅允许其作者(所有者)进行删除操作。其他用户无法删除。






2. Linux 权限表






3. ls -l 输出详解

例如:lrwxrwxrwx. 1 root root 7 Oct 3 02:33 bin -> usr/bin


  • 第一个字符的含义:

    • -:常规文件
    • b:块特殊文件
    • c:字符特殊文件
    • C:高性能(”连续数据“)文件
    • d:目录
    • D:门(Solaris 2.5及以上版本)
    • l:符号链接
    • M:离线(”前已“)文件(Cray DMF)
    • n:网络专用文件(HP-UX)
    • p:FIFO(命名管道)
    • P:断开(Solaros 10及以上)
    • s:套接字
    • ?:其他文件
  • 第二个字符的含义:

    • r:属主的读权限
  • 第三个字符的含义:

    • w:属主的写权限
  • 第四个字符的含义:

    • x:属主的执行权限

    • S:设置了SUID,没有执行权限
    • s:设置了SUID,具有执行权限
  • 第五个字符的含义:

    • r:属组的读权限
  • 第六个字符的含义:

    • w:属主的写权限
  • 第七个字符的含义:

    • x:属组执行权限

    • S:设置了SGID,没有执行权限
    • s:设置了SGID,具有执行权限
  • 第八个字符的含义:

    • r:其他人的读权限
  • 第九个字符的含义:

    • w:其他人的写权限
  • 第十个字符的含义:

    • x:其他人的执行权限
    • T:设置了粘滞位,没有执行权限
    • t:设置了粘滞位,具有执行权限
  • 第十一个字符的含义:

    • .:没有任何其他替代访问方法的SELinux安全上下文(没有设置ACL)
    • +:具有任何其他组合访问方法的SELinux安全上下文(设置了ACL)
  • 第十二个字符的含义:该文件的硬链接数量
  • 第十三个字符的含义:该文件的属主
  • 第十四个字符的含义:该文件的属组
  • 第十五个字符的含义:该文件的大小
  • 第十六到第十八个字符的含义:最后一次修改的时间
  • 第十九个字符的含义:文件或目录的名称
  • 第二十个字符的含义:链接符号
  • 第二十一个字符的含义:链接文件的源文件






4. umask 掩码


umask是一个内置命令。其作用是指定创建的文件或目录的默认权限。

使用方法:umask [-S|-p] [mode]

  • -S:打印出字符权限位
  • -p:打印八进制数权限位(默认)


使用不加任何参数的umask会打印出八进制的权限:默认0022

  • 第一个数字表示:特殊权限位的八进制数
  • 第二个数字表示:属主的八进制数的反掩码
  • 第三个数字表示:属组的八进制数的反掩码
  • 第四个数字表示:其他人的八进制数的反掩码


例如:手动更改,可使用八进制,也可以使用字符表示

[root@localhost data]# umask 
0022
[root@localhost data]# mkdir test_dir
[root@localhost data]# touch test_txt
[root@localhost data]# ls -lh
total 0
drwxr-xr-x 2 root root 6 Apr 21 01:34 test_dir
-rw-r--r-- 1 root root 0 Apr 21 01:34 test_txt
[root@localhost data]# rm -rf *
[root@localhost data]# umask 0777
[root@localhost data]# mkdir test_dir
[root@localhost data]# touch test_txt
[root@localhost data]# ls -lh
total 0
d--------- 2 root root 6 Apr 21 01:37 test_dir
---------- 1 root root 0 Apr 21 01:37 test_txt


注意:尽管你的umask设置为0000,后续创建文件的权限依然是666。出于安全着想,执行权限必须手动添加。所以你会看到,目录权限为777,而文件权限为666



]]>
自动化测试 | UI Automator 进阶指南 http://doc.okbase.net/doclist/archive/264655.html doclist 2019/4/20 0:50:59

UI Automator 相关介绍:

  • 跨应用的用户界面自动化测试
  • 包含在 AndroidX Test(https://developer.android.com/training/testing) 中
  • 支持的 Android 系统:>= Android 4.3 (API level 18)
  • 基于 instrumentation,依赖于 AndroidJUnitRunner 测试运行器

设置 UI Automator(Set up UI Automator)

在编写测试代码前,先确保以下两个配置:
1、测试代码存放位置
2、项目依赖(https://developer.android.com/training/testing/set-up-project)

(1) 添加 Gradle 依赖(Add Gradle dependencies)

  • app 目录下的 build.gradle 添加:
allprojects {
    repositories {
        jcenter()
        google()
    }
}
  • dependencies 添加需要的 AndroidX Test Package, 比如:
dependencies {
    ...
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}
  • 如果测试代码需要基于 junit 的类,比如 Assert 和 TestSuiteLoader,在 android 区块中添加(只需要添加需要用到的 library:https://developer.android.com/training/testing/set-up-project#junit-based-libs):
android {
    ...

    // Gradle automatically adds 'android.test.runner' as a dependency.
    useLibrary 'android.test.runner'

    useLibrary 'android.test.base'
    useLibrary 'android.test.mock'
}

(2) 添加 manifest 声明(Add manifest declarations)
此步骤可选,具体请看 https://developer.android.com/training/testing/set-up-project#add-manifest-declarations

当前面的配置完成后,进行其他配置:
app下的build.gralde:

dependencies {
    ...
    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

当所有配置都完成后,进行被测应用的 UI 组件分析,确保能被识别以及接入控制。

检查设备上的用户界面(Inspect the UI on a device)

uiautomatorviewer:

(1) 启动手机上的被测应用

(2) 手机连接电脑

(3) 打开 Terminal, 进入目录 <android-sdk>/tools/

(4) 运行:uiautomatorviewer

查看应用的用户界面属性:

(1) 点击左上角 "Device Screenshot" 按钮

(2) 左边是 UI 组件,右下半部分是属性,右上半部分是布局层级

(3) 可选功能:点击右上角 "Toggle NAF Nodes" 按钮(黄色三角形,内有感叹号),查看无法被识别/访问的UI组件。---这个功能我都没搞懂怎么用,点击后貌似没效果

确保 activity 可访问(Ensure your activity is accessible)

Android 原生元素具有更好的访问性,利于测试代码的编写,无需额外的支持
如果是自定义 UI 元素,需要(1)创建一个继承自 ExploreByTouchHelper 的实体类(2)通过调用 setAccessibilityDelegate() 将新创建的类的实例和特定的自定义 UI 元素相关联
给自定义视图元素添加无障碍功能的其他参考资料:https://developer.android.com/guide/topics/ui/accessibility/custom-views.html
学习资料 for 提高 Android 的无障碍性/可访问性:https://developer.android.com/guide/topics/ui/accessibility/apps.html

创建一个 UI Automator 测试类(Create a UI Automator test class)

UI Automator 测试类的写法和 JUnit 4 测试类的写法是一样的。
JUnit 4 测试类的学习资料:https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests.html#build

在测试类开头添加注解:@RunWith(AndroidJUnit4.class)
同时,明确 AndroidX Test 中的 AndroidJUnitRunner 类为默认的测试运行器。这个步骤的详细描述:https://developer.android.com/training/testing/ui-testing/uiautomator-testing.html#run

在 UI Automator 测试类中执行以下编程模型:

  1. 获取一个 UiDevice 对象去接入测试设备,调用 getInstance() 方法,传入 Instrumentation 对象作为参数。
  2. 通过 UiObject 对象调用 findObject() 方法接入显示在设备上的 UI 组件(例如,当前手机屏幕显示的用户界面)。
  3. 通过调用 UiObject 方法在 UI 组件上模拟一个交互的动作。例如,调用 performMultiPointerGesture() 方法模拟多指触控,调用 setText() 方法编辑文本框。当测试包含多个 UI 组件或者更加复杂的操作序列时,在第二步和第三步中可重复调用各种 API.
  4. 当执行完这些用户交互的动作后,检查返回的结果是否符合预期。
    这些步骤在以下章节会讲的更加详细。

访问用户界面组件 (Access UI components)

UiDevice: 接入和控制设备状态的首要方法,可执行设备级别的行为,例如改变屏幕旋转方向、按下硬件按钮、以及点击 home 和 menu 键。

从设备的主屏幕开始测试是一个好的实践。在主屏幕(或者其他你在设备上选定的开始位置),可以调用 UI Automator API 提供的方法和指定的 UI 元素进行交互。

以下代码片段展示了如何获取一个 UiDevice 的实例以及模拟按下 home 键的操作:

import org.junit.Before;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
...
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {

    private static final String BASIC_SAMPLE_PACKAGE
            = "com.example.android.testing.uiautomator.BasicSample";
    private static final int LAUNCH_TIMEOUT = 5000;
    private static final String STRING_TO_BE_TYPED = "UiAutomator";
    private UiDevice device;

    @Before
    public void startMainActivityFromHomeScreen() {
        // Initialize UiDevice instance
        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

        // Start from the home screen
        device.pressHome();

        // Wait for launcher
        final String launcherPackage = device.getLauncherPackageName();
        assertThat(launcherPackage, notNullValue());
        device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
                LAUNCH_TIMEOUT);

        // Launch the app
        Context context = ApplicationProvider.getApplicationContext();
        final Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
        // Clear out any previous instances
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);

        // Wait for the app to appear
        device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
                LAUNCH_TIMEOUT);
    }
}

示例代码中的声明:@SdkSuppress(minSdkVersion = 18), 帮助确定测试只运行在 Android4.3(API level 18)或更高级别的设备上。(UI Automator 框架要求的)

  • findObject()
  • UiObject
UiObject cancelButton = device.findObject(new UiSelector()
        .text("Cancel")
        .className("android.widget.Button"));
UiObject okButton = device.findObject(new UiSelector()
        .text("OK")
        .className("android.widget.Button"));

// Simulate a user-click on the OK button, if found.
if(okButton.exists() && okButton.isEnabled()) {
    okButton.click();
}

指定一个选择器(Specify a selector)

UiSelector 类:在当前显示的用户界面中查询一个特定的元素。

  • childSelector()
  • UiAutomatorObjectNotFoundException
UiObject appItem = device.findObject(new UiSelector()
        .className("android.widget.ListView")
        .instance(0)
        .childSelector(new UiSelector()
        .text("Apps")));

tips:

  • 如果在一个页面上找到一个以上的相同元素,自动返回第一个匹配的元素作为目标 UiObject.
  • 可以通过整合多个属性来缩小搜索范围。
  • 如果没有找到目标元素,抛出 UiAutomatorObjectNotFoundException 异常。
  • 可以使用 childSelector() 方法缩小多个 UiSelector 实例范围。
  • 如果有 Resource ID, 用这个代替 text 和 content-descripter.
  • text 元素比较脆弱,有多种原因可能导致测试失败。(比如:多语言)
    在选择区域中去明确一个对象状态是非常有用的。比如:选择一个已选中的列表以进行取消选中状态,调用 checked() 方法,将参数设为 "true".

执行动作(Perform actions)

当获取 UiObject 对象后,可以调用 UiObject 类中的方法在其上执行相应操作:

  • click(): 点击
  • dragTo(): 拖动
  • setText(): 设置文本
  • clearTextField(): 清空文本
  • swipeUp(): 向上滑动
  • swipeDown(): 向下滑动
  • swipeLeft(): 向左滑动
  • swipeRight(): 向右滑动

通过 getContext() 方法获取到 Context 后,可以进行发送 Intent 或者启动 Activity 的操作。

public void setUp() {
    ...

    // Launch a simple calculator app
    Context context = getInstrumentation().getContext();
    Intent intent = context.getPackageManager()
            .getLaunchIntentForPackage(CALC_PACKAGE);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

    // Clear out any previous instances
    context.startActivity(intent);
    device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}

在集合上执行动作(Perform actions on collections)

  • UiCollection 类:在一个 item 的集合上模拟用户操作(例如,歌曲列表或者邮件列表)。
    如何创建一个 UiCollection 对象:明确一个搜索UI容器或者其他子 UI 元素集合的 UiSelector. 例如,包含子 UI 元素的 layout 视图。
UiCollection videos = new UiCollection(new UiSelector()
        .className("android.widget.FrameLayout"));

// Retrieve the number of videos in this collection:
int count = videos.getChildCount(new UiSelector()
        .className("android.widget.LinearLayout"));

// Find a specific video and simulate a user-click on it
UiObject video = videos.getChildByText(new UiSelector()
        .className("android.widget.LinearLayout"), "Cute Baby Laughing");
video.click();

// Simulate selecting a checkbox that is associated with the video
UiObject checkBox = video.getChild(new UiSelector()
        .className("android.widget.Checkbox"));
if(!checkBox.isSelected()) checkbox.click();

在可滚动视图中执行动作(Perform actions on scrollable views)

  • UiScrollable 类:在手机屏幕上模拟垂直或者水平的滚动操作。这个类适用于当需要找到屏幕外的 UI 元素时,可以通过滚动操作将这个 UI 元素带到屏幕内。
UiScrollable settingsItem = new UiScrollable(new UiSelector()
        .className("android.widget.ListView"));
UiObject about = settingsItem.getChildByText(new UiSelector()
        .className("android.widget.LinearLayout"), "About tablet");
about.click();

验证结果(Verify results)

InstrumentationTestCase 继承自 TestCase,可以使用标准的 JUnit Assert 方法进行结果验证。
以下代码片段展示了如何验证计算器加法:

private static final String CALC_PACKAGE = "com.myexample.calc";
public void testTwoPlusThreeEqualsFive() {
    // Enter an equation: 2 + 3 = ?
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("two")).click();
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("plus")).click();
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("three")).click();
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("equals")).click();
    // Verify the result = 5
    UiObject result = device.findObject(By.res(CALC_PACKAGE, "result"));
    assertEquals("5", result.getText());
}

在设备或虚拟机上运行 UI Automator 测试用例(Run UI Automator tests on a device or emulator)

可以通过 Android Studio 或者命令行运行 UI Automator tests. 确保项目的默认 instrumentation runner 是 AndroidJUnitRunner.

参考资料(Additional resources)

Samples:
https://github.com/googlesamples/android-testing/tree/master/ui/uiautomator/BasicSample 基础的UI Automator 示例代码
Codelabs:
https://codelabs.developers.google.com/codelabs/android-testing/index.html


欢迎关注微信公众号"测试开发Stack"

]]>
Java基础之流程控制 http://doc.okbase.net/doclist/archive/264654.html doclist 2019/4/20 0:50:53

一、顺序结构

       顺序结构的程序语句只能被执行一次。如果您想要同样的操作执行多次,,就需要使用循环结构。

  if-else-if 语句

语法:

  if(条件){

         当条件为true时,执行大括号内的代码

  }else if(条件){}

代码实例:

public static void main(String[] args){
       int a=2;
       if(a>1){
             System.out.println("该数字大于1");
       }else if(a<1){
                   System.out.println("该数字小于1");
             }
        System.out.println("该数字为1");
}

 switch语句

     switch 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。

     语法:

     switch( 变量 ){

     case 值1:

            break;

     case 值2:

            break;

      default:

       }

      当程序执行到break关键字时,跳出当前的switch语句;

代码实例:

 public static void main(String[] args){
            int b=2;
            switch(b){
                case 4:System.out.println("该值是4");   //情况一
                    break;
                case 2:System.out.println("该值是2");   //情况二
                    break;
                default:
            }

        }

       注意事项:

  • switch 语句中的变量类型可以是: byte、short、int 或者 char。从 Java SE 7 开始,switch 支持字符串类型了,同时 case 标签必须为字符串常量或字面量。
  • switch 语句可以拥有多个 case 语句。每个 case 后面跟一个要比较的值和冒号。
  • case 语句中的值的数据类型必须与变量的数据类型相同,而且只能是常量或者字面常量。
  • 当变量的值与 case 语句的值相等时,那么 case 语句之后的语句开始执行,直到 break 语句出现才会跳出 switch 语句。
  • 当遇到 break 语句时,switch 语句终止。程序跳转到 switch 语句后面的语句执行。case 语句不必须要包含 break 语句。如果没有 break 语句出现,程序会继续执行下一条 case 语句,直到出现 break 语句。
  • switch 语句可以包含一个 default 分支,该分支必须是 switch 语句的最后一个分支。default 在没有 case 语句的值和变量值相等的时候执行。default 分支不需要 break 语句。

二、循环结构

  while循环语句

  语法:

  while(条件){

         当条件为true时,执行循环体内的代码;

  }

  备注:满足循环的三个条件:初始化变量、关系运算、迭代

 

public static void main(String[] args){
           while(true){   //只要条件为true程序就一直会执行下去
               System.out.println("我爱你");
           }
       }

 do-while循环语句

  语法:

  do{

  }while(条件);

  备注:与while循环的区别是,当条件为false时,也会被执行一次。

public static void main(String[] args){
           do {
               System.out.println("我爱你");
           } while(false);
       }
  for循环语句

  语法:

  for(int i = 0 ; i < 10 ; i++){

  }

       嵌套for循环

打印直角三角形

for(int i = 1 ; i < 10 ; i++){
            for(int j = 1 ; j <= i ; j++){
                System.out.print("* ");
            }
            System.out.println();

输出其结果

* 
* * 
* * * 
* * * * 
* * * * * 
* * * * * * 
* * * * * * * 
* * * * * * * * 
* * * * * * * * * 

打印乘法口诀

for(int i = 1 ; i < 10 ; i++){
            for(int j = 1 ; j < 10-i ; j++){
                System.out.print("\t");
            }
            for(int j = 1 ; j <=i ; j++){
                System.out.print(j+"×"+i+"="+(i*j)+"\t");
            }
            System.out.println();
        }

输出结果

                                                                               1×1=1    
                                                                      1×2=2    2×2=4    
                                                             1×3=3    2×3=6    3×3=9    
                                                   1×4=4    2×4=8    3×4=12    4×4=16    
                                        1×5=5    2×5=10    3×5=15    4×5=20    5×5=25    
                              1×6=6    2×6=12    3×6=18    4×6=24    5×6=30    6×6=36    
                    1×7=7    2×7=14    3×7=21    4×7=28    5×7=35    6×7=42    7×7=49    
         1×8=8     2×8=16    3×8=24    4×8=32    5×8=40    6×8=48    7×8=56    8×8=64    
1×9=9    2×9=18    3×9=27    4×9=36    5×9=45    6×9=54    7×9=63    8×9=72    9×9=81   
  增强 for 循环

       Java5 引入了一种主要用于数组的增强型 for 循环。

       Java 增强 for 循环语法格式如下:

       语法:

  for(声明语句 : 表达式) { 
//代码句子
}
代码实例
public class Test {
   public static void main(String args[]){
      int [] numbers = {10, 20, 30, 40, 50};
 
      for(int x : numbers ){
         System.out.print( x );
         System.out.print(",");
      }
      System.out.print("\n");
      String [] names ={"James", "Larry", "Tom", "Lacy"};
      for( String name : names ) {
         System.out.print( name );
         System.out.print(",");
      }
   }
}

运行结果

10,20,30,40,50,
James,Larry,Tom,Lacy,

三、循环控制

  break语句

  break 可以用于所有的循环语句或者 switch 语句中,用来跳出整个语句块。

  break 跳出该关键字所在的循环,并且继续执行该循环下面的语句。

代码实例

public class Test {
   public static void main(String args[]) {
      int [] numbers = {10, 20, 30, 40, 50};
 
      for(int x : numbers ) {
         // x 等于 30 时跳出循环
         if( x == 30 ) {
            break;
         }
         System.out.print( x );
         System.out.print("\n");
      }
   }
}

运行结果

10
20

 

  continue语句

  continue 适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。

  在 for 循环中,continue 语句使程序立即跳转到更新语句。

  在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。

运行实例

public class Test {
   public static void main(String args[]) {
      int [] numbers = {10, 20, 30, 40, 50};
 
      for(int x : numbers ) {
         if( x == 30 ) {
        continue;
         }
         System.out.print( x );
         System.out.print("\n");
      }
   }
}

运行结果

10
20
40
50

 



 

 

]]>
python网络-计算机网络基础(23) http://doc.okbase.net/doclist/archive/264653.html doclist 2019/4/20 0:31:09

一、网络简介

网络是由节点和连线构成,表示诸多对象及其相互联系。

一个人玩:

 

两个人玩:

 

多个人玩:

说明

  • 网络就是一种辅助双方或者多方能够连接在一起的工具
  • 如果没有网络可想单机的世界是多么的孤单

使用网络的目的

  • 就是为了联通多方然后进行通信用的,即把数据从一方传递给另外一方
  • 前面的学习编写的程序都是单机的,即不能和其他电脑上的程序进行通信
  • 为了让在不同的电脑上运行的软件,之间能够互相传递数据,就需要借助网络的功能
  • 所谓的网络编程就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信

 

二、tcp/ip简介

1. 什么是协议

有的说英语,有的说中文,有的说德语,说同一种语言的人可以交流,不同的语言之间就不行了

为了解决不同种族人之间的语言沟通障碍,现规定国际通用语言是英语,这就是一个规定,这就是协议

2. 计算机网络沟通用什么

现在的生活中,不同的计算机只需要能够联网(有线无线都可以)那么就可以相互进行传递数据,那么不同种类之间的计算机到底是怎么进行数据传递的呢?就像说不同语言的人沟通一样,只要有一种大家都认可都遵守的协议即可,那么这个计算机都遵守的网络通信协议叫做TCP/IP协议

3. TCP/IP协议(族)

早期的计算机网络,都是由各厂商自己规定一套协议,IBM、Apple和Microsoft都有各自的网络协议,互不兼容

为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议簇(Internet Protocol Suite)就是通用协议标准。

因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议

常用的网络协议如下图所示:

说明:

  • 网际层也称为:网络层
  • 网络接口层也称为:链路层

 

三、端口简介

那么TCP/IP协议中的端口指的是什么呢?端口就好一个房子的门,是出入这间房子的必经之路。

如果一个进程需要收发网络数据,那么就需要有这样的端口

在linux系统中,端口可以有65536(2的16次方)个之多!

既然有这么多,操作系统为了统一管理,所以进行了编号,这就是端口号

2. 端口号

端口是通过端口号来标记的,端口号只有整数,范围是从0到65535

3. 端口是怎样分配的

  • 端口号不是随意使用的,而是按照一定的规定进行分配。
  • 端口的分类标准有好几种,我们这里不做详细讲解,只介绍一下知名端口和动态端口

4.知名端口(Well Known Ports)

知名端口是众所周知的端口号,范围从0到1023

  • 80端口分配给HTTP服务
  • 21端口分配给FTP服务

可以理解为,一些常用的功能使用的号码好比:电话号码110、10086、10010一样

一般情况下,如果一个程序需要使用知名端口的需要有root权限

5.动态端口(Dynamic Ports)

  • 动态端口的范围是从1024到65535
  • 之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。
  • 动态分配是指当一个系统进程或应用程序进程需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。
  • 当这个进程关闭时,同时也就释放了所占用的端口号。
  • 用“netstat -an”查看端口状态

6、端口总结

端口有什么用呢 ? 我们知道,一台拥有IP地址的主机可以提供许多服务,比如HTTP(万维网服务)、FTP(文件传输)、SMTP(电子邮件)等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。 需要注意的是,端口并不是一一对应的。比如你的电脑作为客户机访问一台WWW服务器时,WWW服务器使用“80”端口与你的电脑通信,但你的电脑则可能使用“3457”这样的端口。

 

四、IP地址简介

IP地址就像是我们的家庭住址一样,如果你要写信给一个人,你就要知道他(她)的地址,这样邮递员才能把信送到。计算机发送信息就好比是邮递员,它必须知道唯一的“家庭地址”才能不至于把信送错人家。只不过我们的地址使用文字来表示的,计算机的地址用二进制数字表示。

IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)。

ip地址的分类

A类IP地址

  • 一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”,
  • 地址范围1.0.0.1-126.255.255.254
  • 二进制表示为:00000001 00000000 00000000 00000001 - 01111110 11111111 11111111 11111110
  • 可用的A类网络有126个,每个网络能容纳1677214个主机

B类IP地址

  • 一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,
  • 地址范围128.1.0.1-191.255.255.254
  • 二进制表示为:10000000 00000001 00000000 00000001 - 10111111 11111111 11111111 11111110
  • 可用的B类网络有16384个,每个网络能容纳65534主机

C类IP地址

  • 一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”
  • 范围192.0.1.1-223.255.255.254
  • 二进制表示为: 11000000 00000000 00000001 00000001 - 11011111 11111111 11111110 11111110
  • C类网络可达2097152个,每个网络能容纳254个主机

D类地址用于多点广播

  • D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址。
  • 它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中
  • 多点广播地址用来一次寻址一组计算机
  • 地址范围224.0.0.1-239.255.255.254

E类IP地址

  • 以“1111”开始,为将来使用保留
  • E类地址保留,仅作实验和开发用

私有ip

  • 在这么多网络IP中,国际规定有一部分IP地址是用于我们的局域网使用,也就
  • 是属于私网IP,不在公网中使用的,它们的范围是:
  • 10.0.0.0~10.255.255.255
  • 172.16.0.0~172.31.255.255
  • 192.168.0.0~192.168.255.255
  • IP地址127.0.0.1~127.255.255.255用于回路测试,
  • 如:127.0.0.1可以代表本机IP地址,用http://127.0.0.1就可以测试本机中配置的Web服务器。

 

五、子网掩码简介

要想理解什么是子网掩码,就不能不了解IP地址的构成。互联网是由许多小型网络构成的,每个网络上都有许多主机,这样便构成了一个有层次的结构。IP地址在设计时就考虑到地址分配的层次特点,将每个IP地址都分割成网络号和主机号两部分,以便于IP地址的寻址操作。

IP地址的网络号和主机号各是多少位呢?

如果不指定,就不知道哪些位是网络号、哪些是主机号,这就需要通过子网掩码来实现。

子网掩码不能单独存在,它必须结合IP地址一起使用。

子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分子网掩码的设定必须遵循一定的规则。

与IP地址相同,子网掩码的长度也是32位,

  • 左边是网络位,用二进制数字“1”表示;
  • 右边是主机位,用二进制数字“0”表示。

假设IP地址为“192.168.1.1”子网掩码为“255.255.255.0”,子网掩码装换二进制位:11111111 11111111 11111111 00000000

  • 其中,“1”有24个,代表与此相对应的IP地址左边24位是网络号;
  • “0”有8个,代表与此相对应的IP地址右边8位是主机号。
  • 这样,子网掩码就确定了一个IP地址的32位二进制数字中哪些是网络号、哪些是主机号。
  • 这对于采用TCP/IP协议的网络来说非常重要,只有通过子网掩码,才能表明一台主机所在的子网与其他子网的关系,使网络正常工作。

最常用的子网掩码是“255.255.255.0”的网络:

  • 最后面一个数字可以在0~255范围内任意变化,因此可以提供256个IP地址。
  • 但是实际可用的IP地址数量是256-2,即254个,因为主机号不能全是“0”或全是“1”。
  • 主机号全为0,表示网络号
  • 主机号全为1,表示网络广播

 

六、socket简介

1.本地的进程间通信(IPC)有很多种方式,例如

  • 队列
  • 同步(互斥锁、条件变量等)

以上通信方式都是在一台机器上不同进程之间的通信方式,那么问题来了,网络中进程之间如何通信?

2. 网络中进程之间如何通信

  • 首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!
  • 在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。
  • 其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。
  • 这样利用ip地址,协议,端口就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互

3. 什么是socket

  • socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:
  • 它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
  • 例如我们每天浏览网页、QQ 聊天、收发 email 等等

4. 创建socket

在 Python 中 使用socket 模块的函数 socket 就可以完成:

socket.socket(AddressFamily, Type)

说明:

函数 socket.socket 创建一个 socket,返回该 socket 的描述符,该函数带有两个参数:

  • Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
  • Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)

创建一个tcp socket(tcp套接字)

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print 'Socket Created'

创建一个udp socket(udp套接字)

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

print 'Socket Created'
]]>
python+scrapy 爬取西刺代理ip(一)(ubuntu环境下) -赖大大 -博客园 http://doc.okbase.net/doclist/archive/264652.html doclist 2019/4/20 0:09:51

第一步:环境搭建

1.python2 或 python3

2.用pip安装下载scrapy框架

具体就自行百度了,主要内容不是在这。

第二步:创建scrapy(简单介绍)

1.Creating a project(创建项目)

scrapy startproject 项目名称

2.Defining our item(定义我们的项目)

3.writing a spider(写spider)

scrapy genspider (spider的名称)(爬取的网页)

4.writing & Configure an item Pipeline(编写和配置项目管道)

5.Execute crawl(执行爬虫)

scrapy crawl (spider的名称)

第三步:具体实现

1.创建项目

进入scrapy项目的工作区间(xici项目名)

scrapy startproject xici

此时项目结构是这样

 

2.创建spider爬虫

进入项目创建(注意后面的不是具体地址,是域名)

 scrapy genspider xicidaili 'xicidaili.com'

此时,你会发现在spiders文件夹下多了个 xicidaili.py 文件(这就是爬虫文件)

 

3.编写items.py

 根据我们的需求编写

代理ip最主要是ip、端口和类型(http或https)

items.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class XiciItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    ip = scrapy.Field()
    port = scrapy.Field()
    types = scrapy.Field()

4.编写xicidaili.py

具体爬虫操作就在这个文件里实现

xicidaili.py

# -*- coding: utf-8 -*-
import scrapy
from xici.items import XiciItem

class XicidailiSpider(scrapy.Spider):
    name = 'xicidaili'
    allowed_domains = ['xicidaili.com']
    #把http://xicidaili.com/地址改为我们需要爬去的具体地址
    start_urls = ['https://www.xicidaili.com/nn/']

    def parse(self, response):
        #我们发现ip都是在table标签里,那我们就用xpath选择table元素
        lis = response.xpath('//table[@id="ip_list"]')
        #抓取table下的tr,一个tr就一个ip,端口,类型
        trs = lis[0].xpath('tr')

        items = []
        #除掉第一行
        for ip in trs[1:]:
            item = XiciItem()
            #抓取每一行具体的内容
            # //*[@id="ip_list"]/tbody/tr[2]/td[2]
            item["ip"] = ip.xpath('td[2]/text()')[0].extract()

            # //*[@id="ip_list"]/tbody/tr[2]/td[3]
            item["port"] = ip.xpath('td[3]/text()')[0].extract()

            # //*[@id="ip_list"]/tbody/tr[2]/td[6]
            item["types"] = ip.xpath('td[6]/text()').extract()

            #加入到数组
            items.append(item)
        #返回给items.py
        return items

5.编写pipelines.py

pipelines文件是对数据进行持久化存储的 

MySQLdb不支持python3,所以我要用pymysql代替

没有pymysql需要用pip下载,数据库表也要自己建

pipelines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import pymysql

class XiciPipeline(object):
    def __init__(self):
        self.client = pymysql.connect(
            host='127.0.0.1',
            port=3306,
            user='root',  # 使用自己的用户名
            passwd='root',  # 使用自己的密码
            db='test',  # 数据库名
            charset='utf8'
        )
        # 拿到游标
        self.cur = self.client.cursor()

    def process_item(self, item, spider):
        #mysql防止sql注入
        sql = ("insert into proxyip(ip,port,types) values (%s,%s,%s)")
        lis = (item['ip'], item['port'], item['types'])
        try:

            self.cur.execute(sql, lis)
            # 向数据库提交
            self.client.commit()
        except Exception as e:
            print("Insert error:", e)
        #关闭
        self.cur.close()
        return item

6.编写middlewares.py

middlewares.py中设置用户代理中间件

对爬虫进行伪装,伪装成人工操作,否则网站会监测出你是爬虫,并拦截你

 在middlewares.py文件中加入以下代码:

from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
import random

#设置代理ip
class MyProxyMiddleware(object):
'''
设置Proxy
'''

def __init__(self, ip):
self.ip = ip

@classmethod
def from_crawler(cls, crawler):
return cls(ip=crawler.settings.get('PROXIES'))

def process_request(self, request, spider):
ip = random.choice(self.ip)
request.meta['proxy'] = ip

class MyUserAgentMiddleware(UserAgentMiddleware):
'''
设置User-Agent
'''

def __init__(self, user_agent):
self.user_agent = user_agent

@classmethod
def from_crawler(cls, crawler):

return cls(
user_agent=crawler.settings.get('USER_AGENTS_LIST')
)

def process_request(self, request, spider):
agent = random.choice(self.user_agent)
request.headers['User-Agent'] = agent

7.编写settings.py

我们刚才编写的middlewares.py和pipelines.py都要在settings.py文件里面进行配置,设置优先级等等。

(一定要记得,否则你写的数据持久化存储和爬虫伪装是没有生效的,记得把ROBOTSTXT_OBEY改为False)

修改settings.py文件代码如下:

BOT_NAME = 'xici'

SPIDER_MODULES = ['xici.spiders']
NEWSPIDER_MODULE = 'xici.spiders'

#对middlewares编写的进行配置
DOWNLOADER_MIDDLEWARES = {
    'xici.middlewares.MyProxyMiddleware': 543,
    'scrapy.downloadermiddleware.useragent.UserAgentMiddleware': None,
    'xici.middlewares.MyUserAgentMiddleware': 400,
}

#配置pipelines.py
ITEM_PIPELINES = {
    'xici.pipelines.XiciPipeline': 300
}

#代理的ip
#你看到此篇文章的时候,ip已经没用了,你可以在西刺网站上拿一个试
PROXIES = ['http://192.168.42.249:808']

#user_agent
USER_AGENTS_LIST = [
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
    "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
    "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
    "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10"
]

# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'xici (+http://www.yourdomain.com)'

#要设置ROBOTSTXT_OBEY改为False
#如果为True,则遵守robots协议(爬虫协议)

ROBOTSTXT_OBEY = False

8.执行爬虫

进入项目里面,列出爬虫

scrapy list

scrapy crawl xicidaili

 

成功爬取

9.问题注意 

1.在执行的时候,可能会卡住,原因是上面用的代理ip已经过期,需要另外找,

或者你可以把ip代理部分去掉,上面示例不用代理ip也能爬取,

找ip代理也是因为你频繁的访问,它是禁止了你的ip,没有也可以不用! 

 2.网站内的html标签有所改变,我上面xpath已经没办法找到我们想要的内容了,需要修改以下xpath

以上是我学习scrapy的分享,有什么不对,望大家指点

]]>
Java基础篇——线程、并发编程知识点全面介绍(面试、学习的必备索引文档) http://doc.okbase.net/doclist/archive/264651.html doclist 2019/4/20 0:09:44

原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10739579.html,希望大家多多支持!!!

一、线程基础

1、线程与进程

  • 线程是指进程中的一个执行流程,一个进程中可以运行多个线程。
  • 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间),比如一个qq.exe就是一个进程。

2、线程的特点

  • 线程共享分配给该进程的所有资源
  • 线程之间实际上轮换执行(也就是线程切换)
  • 一个程序至少有一个进程,一个进程至少有一个线程
  • 线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
  • 线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:
    • 一个指向当前被执行指令的指令指针
    • 一个栈
    • 一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值
    • 一个私有的数据区

3、线程的作用

  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率(并发执行)

4、线程的创建

  • 继承Thread类
  • 实现Runnable接口
  • 通过ThreadPool获取

5、线程的状态(生命周期)

  • 创建:当用new操作符创建一个线程时。此时程序还没有开始运行线程中的代码
  • 就绪:当start()方法返回后,线程就处于就绪状态
  • 运行:当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法
  • 阻塞:所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态
  • 死亡:1、run方法正常退出而自然死亡;2、一个未捕获的异常终止了run方法而使线程猝死

    线程生命周期.jpg

6、线程的优先级、线程让步yield、线程合并join、线程睡眠sleep

  • 优先级:线程总是存在优先级,优先级范围在1~10之间(数值越大优先级越高),线程默认优先级是5,优先级高的理论上先执行,但实际不一定。
  • 线程让步:yield方法调用后 ,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态(使用yield()的目的是让相同优先级的线程之间能适当的轮转执行)。
  • 线程合并:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
  • 线程睡眠:sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。

7、线程的分类(以下两者的唯一区别之处就在虚拟机的离开)

  • 守护线程: thread.setDaemon(true),必须在thread.start()之前设置,GC线程就是一个守护线程。
  • 普通线程

8、正确结束线程(给出一些方案)

  • 使用Thread.stop()方法,但是该方法已经被废弃了,使用它是极端不安全的,会造成数据不一致的问题。
  • 使用interrupt()方法停止一个线程,直接调用该方法不会终止一个正在运行的线程,需要加入一个判断语句才可以完成线程的停止。
  • 使用共享变量的方式,在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。

二、线程同步

1、线程同步的意义

  • 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏

2、锁的原理

  • Java中每个对象都有一个内置锁,内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁。
  • 当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
  • 当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
  • 一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
  • 释放锁是指持锁线程退出了synchronized同步方法或代码块。

3、锁和同步的理解

  • 只能同步方法,而不能同步变量和类。
  • 每个对象只有一个锁,当提到同步时,应该清楚在什么上同步,也就是说,在哪个对象上同步。
  • 不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
  • 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法
  • 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
  • 线程睡眠时,它所持的任何锁都不会释放。
  • 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
  • 同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
  • 在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。

4、对象锁和类锁的区别

  • 对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。
  • 类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。

5、线程的死锁及规避

  • 死锁是线程间相互等待锁锁造成的,一旦程序发生死锁,程序将死掉。
  • 如果我们能够避免在对象的同步方法中调用其它对象的同步方法,那么就可以避免死锁产生的可能性。

6、volatile关键字(推荐大家一片文章)

  • 正确使用 volatile变量
  • 与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。
  • 如果严格遵循 volatile 的使用条件 —— 即变量真正独立于其他变量和自己以前的值 —— 在某些情况下可以使用 volatile 代替 synchronized 来简化代码。

三、线程的交互

1、线程交互的基础知识

  • void notify()——唤醒在此对象监视器上等待的单个线程。
  • void notifyAll()——唤醒在此对象监视器上等待的所有线程。
  • void wait()——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
  • void wait(longtimeout)——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。
  • void wait(longtimeout, int nanos)——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

2、注意点

  • 必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
  • 当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。如果线程仍然在完成同步代码,则线程在移出之前不会放弃锁。因此,调用notify()并不意味着这时该锁变得可用。

四、线程池

1、好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

2、线程池的创建使用

    public class ThreadPoolExecutor extends AbstractExecutorService {
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    }
  • 参数介绍:
    • corePoolSize:核心池的大小
    • maximumPoolSize:线程池最大线程数
    • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
    • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
      • TimeUnit.DAYS; //天
      • TimeUnit.HOURS; //小时
      • TimeUnit.MINUTES; //分钟
      • TimeUnit.SECONDS; //秒
      • TimeUnit.MILLISECONDS; //毫秒
      • TimeUnit.MICROSECONDS; //微妙
      • TimeUnit.NANOSECONDS; //纳秒
    • workQueue:一个阻塞队列,用来存储等待执行的任务
    • threadFactory:线程工厂,主要用来创建线程
    • handler:表示当拒绝处理任务时的策略,有以下四种取值:
      • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
      • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
      • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
      • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

3、java默认实现的4中线程池(不建议使用)

  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。
  • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

五、并发编程相关内容

1、synchronized 的局限性 与 Lock 的优点

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

2、Lock 和 ReadWriteLock

  • Lock接口,ReentrantLock(可重入锁)是唯一的实现类。

    public interface Lock {
        void lock();
        //lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
        //也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
        void lockInterruptibly() throws InterruptedException;
        boolean tryLock();
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        void unlock();
        Condition newCondition();
    }
  • ReadWriteLock接口,ReentrantReadWriteLock实现了ReadWriteLock接口。

    public interface ReadWriteLock {
        Lock readLock();
        Lock writeLock();
    }   
    • 读读共享
    • 写写互斥
    • 读写互斥
    • 写读互斥

3、信号量(Semaphore)

  • 介绍:Java的信号量实际上是一个功能完毕的计数器,对控制一定资源的消费与回收有着很重要的意义,信号量常常用于多线程的代码中,并能监控有多少数目的线程等待获取资源,并且通过信号量可以得知可用资源的数目等等。
  • 特点:
    • 以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
    • 单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
    • 信号量解决了锁一次只能让一个线程访问资源的问题,信号量可以指定多个线程,同时访问一个资源。
  • 分为公平模式和非公平模式(默认非公平)

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

    区别在于:公平模式会考虑是否已经有线程在等待,如果有则直接返回-1表示获取失败;而非公平模式不会关心有没有线程在等待,会去快速竞争资源的使用权。
    说到竞争就得提到AbstractQueuedSynchronizer同步框架,一个仅仅需要简单继承就可以实现复杂线程的同步方案,建议大家去研究一下。

4、闭锁(CountDownLatch)

  • 介绍:闭锁是一种同步工具,可以延迟线程的进度直到终止状态。可以把它理解为一扇门,当闭锁到达结束状态之前,这扇门一直是关闭的,没有任何线程可以通过。当闭锁到达结束状态时,这扇门会打开并允许所有线程通过,并且闭锁打开后不可再改变状态。
    闭锁可以确保某些任务直到其他任务完成后才继续往下执行。

  • 使用介绍
    • 构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。
    • 与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
    • 其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。

4、栅栏(CyclicBarrier)

  • 介绍:栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。

  • CyclicBarrier和CountDownLatch的区别:
    • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
    • CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
    • CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。

5、原子量 (Atomic)

  • 介绍:Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的Atomic是指不能分割成若干部分的意思。如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的。通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成)

  • 特性:在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。

  • 注意:原子量虽然可以保证单个变量在某一个操作过程的安全,但无法保证你整个代码块,或者整个程序的安全性。因此,通常还应该使用锁等同步机制来控制整个程序的安全性。

6、Condition

  • 介绍:在Java程序中,任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object类上),主要包括wait()、wait(long)、notify()、notifyAll()方法,这些方法与synchronized关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有区别的。
  • Condition与Object中的wati,notify,notifyAll区别:
    • Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。
      不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。
    • Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。如果采用Object类中的wait(),notify(),notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程",而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。

7、并发编程概览

concurrent.png

六、总结

  • 到此线程的基本内容介绍就差不多了,这篇文章偏理论一些,每个知识点的介绍并不全面。
  • 大家可以以此篇文章为索引,来展开对并发编程的深入学习,细细咀嚼每个知识点,相信你会有巨大的收获!!!

个人博客地址:

cnblogs:https://www.cnblogs.com/baixianlong
csdn:https://blog.csdn.net/tiantuo6513
segmentfault:https://segmentfault.com/u/baixianlong
github:https://github.com/xianlongbai

]]>
深度学习之卷积神经网络(CNN)详解与代码实现(二) http://doc.okbase.net/doclist/archive/264650.html doclist 2019/4/19 23:28:51

                                

                            用Tensorflow实现卷积神经网络(CNN)

            本文系作者原创,转载请注明出处:https://www.cnblogs.com/further-further-further/p/10737065.html 

目录

1.踩过的坑(tensorflow)

2.tensorboard

3.代码实现(python3.6)

4.运行结果以及分析

 

1.踩过的坑(tensorflow)

上一章CNN中各个算法都是纯手工实现的,可能存在一些难以发现的问题,这也是准确率不高的一个原因,这章主要利用tensorflow框架来实现卷积神经网络,数据源还是cifar(具体下载见上一章)

在利用tensorflow框架实现CNN时,需要注意以下几点:

1.输入数据定义时,x只是起到占位符的作用(看不到真实值,只是为了能够运行代码,获取相应的tensor节点,这一点跟我们之前代码流程完全相反, 真正数据流的执行在session会话里) 

x:输入数据,y_: 标签数据,keep_prob: 概率因子,防止过拟合。

定义,且是全局变量。

x = tf.placeholder(tf.float32, [None, 3072], name='x') 
y_ = tf.placeholder(tf.float32, [None, 10], name='y_')
keep_prob = tf.placeholder(tf.float32)

后面在session里必须要初始化

sess.run(tf.global_variables_initializer())

在session run时必须要传得到该tensor节点含有参数值(x, y_, keep_prob)

 

train_accuracy = accuracy.eval(feed_dict={
                    x: batch[0], y_: batch[1], keep_prob: 1.0})

 

2.原始数据集标签要向量化;

例如cifar有10个类别,如果类别标签是 6 对应向量[0,0,0,0,0,1,0,0,0,0]

3.知道每一步操作的数据大小的变化,不然,报错的时候很难定位(个人认为这也是tensorflow的弊端,无法实时追踪定位);

  注意padding = 'SAME'和'VALID'的区别

  padding = 'SAME' => Height_后 = Height_前/Strides 跟padding无关  向上取整

  padding = 'VALID'=>  Height_后 = (Height_前 - Filter + 1)/Strides  向上取整

4.打印tensorboard流程图,可以直观看到每步操作数据大小的变化;

2. tensorboard

tensorboard就是一个数据结构流程图的可视化工具,通过tensorboard流程图,可以直观看到神经网络的每一步操作以及数据流的变化。

操作步骤:

1. 在session会话里加入如下代码,打印结果会在当前代码文件相同路径的tensorboard文件下,默认是

tf.summary.FileWriter("tensorboard/", sess.graph)

2. 在运行里输入cmd,然后输入(前提是安装好了tensorboard => pip install  tensorboard)

tensorboard --logdir=D:\Project\python\myProject\CNN\tensorflow\captchaIdentify\tensorboard --host=127.0.0.1

'D:\Project\python\myProject\CNN\tensorflow\captchaIdentify\tensorboard' 是我生成的tensorboard文件的绝对路径,你替换成你自己的就可以了。

正确运行后会显示 ‘Tensorboard at http://127.0.0.1:6006’,说明tensorboard服务已经起来了,在浏览器页面输入

http://127.0.0.1:6006即可显示流程图。

3.代码实现(python3.6)

代码逻辑实现相对比较简单,在一些重要逻辑实现上,我已做了注释,如果大家有什么疑义,可以留言给我,我们一起交流。

因为原始图片数据集太大,不好上传,大家可以直接在http://www.cs.toronto.edu/~kriz/cifar.html下载CIFAR-10 python version,

有163M,放在代码文件同路径下即可。

cifar放置路径

 

 

start.py

 

  1 # coding=utf-8
  2 # Disable linter warnings to maintain consistency with tutorial.
  3 # pylint: disable=invalid-name
  4 # pylint: disable=g-bad-import-order
  5 from __future__ import absolute_import
  6 from __future__ import division
  7 from __future__ import print_function
  8 import argparse
  9 import sys
 10 import tempfile
 11 #from tensorflow.examples.tutorials.mnist import input_data
 12 import tensorflow as tf
 13 '''
 14  卷积神经网络实现10类(airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck) 
 15  60000张图片的识别
 16  5000次,准确率有 58%;
 17  20000次,准确率有 68.89%;
 18  相比mnist数字图片识别准确度低,原因有:
 19  mnist训练图片是灰度图片,纹理简单,数字的可变性小,而cifar是彩色图片,纹理复杂,动物可变性大;
 20 '''
 21 try:
 22     from . import datesets
 23 except Exception:
 24     import datesets
 25 
 26 FLAGS = None
 27 
 28 def deepnn(x):
 29     with tf.name_scope('reshape'):
 30         x_image = tf.reshape(x, [-1, 32, 32, 3])
 31     ## 第一层卷积操作 ##
 32     with tf.name_scope('conv1'):
 33         W_conv1 = weight_variable([5, 5, 3, 32])
 34         b_conv1 = bias_variable([32])
 35         h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
 36 
 37     with tf.name_scope('pool1'):
 38         h_pool1 = max_pool_2x2(h_conv1)
 39 
 40     # Second convolutional layer -- maps 32 feature maps to 64.
 41     ## 第二层卷积操作 ##
 42     with tf.name_scope('conv2'):
 43         W_conv2 = weight_variable([5, 5, 32, 64])
 44         b_conv2 = bias_variable([64])
 45         h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
 46 
 47     with tf.name_scope('pool2'):
 48         h_pool2 = max_pool_2x2(h_conv2)
 49 
 50     ## 第三层全连接操作 ##
 51     with tf.name_scope('fc1'):
 52         W_fc1 = weight_variable([8 * 8 * 64, 1024])
 53         b_fc1 = bias_variable([1024])
 54         h_pool2_flat = tf.reshape(h_pool2, [-1, 8 * 8 * 64])
 55         h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
 56 
 57     with tf.name_scope('dropout'):
 58         keep_prob = tf.placeholder(tf.float32)
 59         h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
 60 
 61     ## 第四层输出操作 ##
 62     with tf.name_scope('fc2'):
 63         W_fc2 = weight_variable([1024, 10])
 64         b_fc2 = bias_variable([10])
 65         y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
 66     return y_conv, keep_prob
 67 
 68 def conv2d(x, W):
 69     return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
 70 
 71 def max_pool_2x2(x):
 72     return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
 73                           strides=[1, 2, 2, 1], padding='SAME')
 74 
 75 def weight_variable(shape):
 76     initial = tf.truncated_normal(shape, stddev=0.1)
 77     return tf.Variable(initial)
 78 
 79 def bias_variable(shape):
 80     initial = tf.constant(0.1, shape=shape)
 81     return tf.Variable(initial)
 82 
 83 def main(_):
 84     # Import data
 85     mnist = datesets.read_data_sets(train_dir = '.\\cifar-10-batches-py\\', one_hot=True)
 86 
 87     # Create the model
 88     # 声明一个占位符,None表示输入图片的数量不定,28*28图片分辨率
 89     x = tf.placeholder(tf.float32, [None, 3072], name='x')
 90 
 91     # 类别是0-9总共10个类别,对应输出分类结果
 92     y_ = tf.placeholder(tf.float32, [None, 10], name='y_')
 93     y_conv, keep_prob = deepnn(x)
 94     # 通过softmax-loss求交叉熵
 95     with tf.name_scope('loss'):
 96         cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv)
 97     # 求均值
 98     cross_entropy = tf.reduce_mean(cross_entropy)
 99     # 计算梯度,更新参数值
100     with tf.name_scope('adam_optimizer'):
101         train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
102 
103     with tf.name_scope('accuracy'):
104         correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
105         correct_prediction = tf.cast(correct_prediction, tf.float32)
106     accuracy = tf.reduce_mean(correct_prediction)
107 
108    # graph_location = tempfile.mkdtemp()
109    # print('Saving graph to: %s' % graph_location)
110    # train_writer.add_graph(tf.get_default_graph())
111 
112     with tf.Session() as sess:
113         # 打印流程图
114         writer = tf.summary.FileWriter("tensorboard/", sess.graph)
115         sess.run(tf.global_variables_initializer())
116         for i in range(20000):
117             batch = mnist.train.next_batch(50)
118             if i % 1000 == 0:
119                 train_accuracy = accuracy.eval(feed_dict={
120                     x: batch[0], y_: batch[1], keep_prob: 1.0})
121                 print('step %d, training accuracy %g' % (i, train_accuracy))
122             train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
123 
124         print('test accuracy %g' % accuracy.eval(feed_dict={
125             x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
126 
127 if __name__ == '__main__':
128     parser = argparse.ArgumentParser()
129     parser.add_argument('--data_dir', type=str,
130                         default='/tmp/tensorflow/mnist/input_data',
131                         help='Directory for storing input data')
132     FLAGS, unparsed = parser.parse_known_args()
133     tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
View Code

datasets.py

  1 import numpy
  2 from tensorflow.python.framework import dtypes
  3 from tensorflow.python.framework import random_seed
  4 from six.moves import xrange
  5 from tensorflow.contrib.learn.python.learn.datasets import base
  6 import pickle
  7 import os
  8 
  9 class DataSet(object):
 10     """Container class for a dataset (deprecated).
 11 
 12     THIS CLASS IS DEPRECATED. See
 13     [contrib/learn/README.md](https://www.tensorflow.org/code/tensorflow/contrib/learn/README.md)
 14     for general migration instructions.
 15     """
 16     def __init__(self,
 17                  images,
 18                  labels,
 19                  fake_data=False,
 20                  one_hot=False,
 21                  dtype=dtypes.float32,
 22                  reshape=True,
 23                  seed=None):
 24         """Construct a DataSet.
 25         one_hot arg is used only if fake_data is true.  `dtype` can be either
 26         `uint8` to leave the input as `[0, 255]`, or `float32` to rescale into
 27         `[0, 1]`.  Seed arg provides for convenient deterministic testing.
 28         """
 29         seed1, seed2 = random_seed.get_seed(seed)
 30         # If op level seed is not set, use whatever graph level seed is returned
 31         numpy.random.seed(seed1 if seed is None else seed2)
 32         dtype = dtypes.as_dtype(dtype).base_dtype
 33         if dtype not in (dtypes.uint8, dtypes.float32):
 34             raise TypeError(
 35                 'Invalid image dtype %r, expected uint8 or float32' % dtype)
 36         if fake_data:
 37             self._num_examples = 10000
 38             self.one_hot = one_hot
 39         else:
 40             assert images.shape[0] == labels.shape[0], (
 41                 'images.shape: %s labels.shape: %s' % (images.shape, labels.shape))
 42             self._num_examples = images.shape[0]
 43 
 44             # Convert shape from [num examples, rows, columns, depth]
 45             # to [num examples, rows*columns] (assuming depth == 1)
 46             if reshape:
 47                 assert images.shape[3] == 3
 48                 images = images.reshape(images.shape[0],
 49                                         images.shape[1] * images.shape[2] * images.shape[3])
 50             if dtype == dtypes.float32:
 51                 # Convert from [0, 255] -> [0.0, 1.0].
 52                 images = images.astype(numpy.float32)
 53                 images = numpy.multiply(images, 1.0 / 255.0)
 54         self._images = images
 55         self._labels = labels
 56         self._epochs_completed = 0
 57         self._index_in_epoch = 0
 58 
 59     @property
 60     def images(self):
 61         return self._images
 62 
 63     @property
 64     def labels(self):
 65         return self._labels
 66 
 67     @property
 68     def num_examples(self):
 69         return self._num_examples
 70 
 71     @property
 72     def epochs_completed(self):
 73         return self._epochs_completed
 74 
 75     def next_batch(self, batch_size, fake_data=False, shuffle=True):
 76         """Return the next `batch_size` examples from this data set."""
 77         if fake_data:
 78             fake_image = [1] * 784
 79             if self.one_hot:
 80                 fake_label = [1] + [0] * 9
 81             else:
 82                 fake_label = 0
 83             return [fake_image for _ in xrange(batch_size)], [
 84                 fake_label for _ in xrange(batch_size)
 85             ]
 86         start = self._index_in_epoch
 87         # Shuffle for the first epoch
 88         if self._epochs_completed == 0 and start == 0 and shuffle:
 89             perm0 = numpy.arange(self._num_examples)
 90             numpy.random.shuffle(perm0)
 91             self._images = self.images[perm0]
 92             self._labels = self.labels[perm0]
 93         # Go to the next epoch
 94         if start + batch_size > self._num_examples:
 95             # Finished epoch
 96             self._epochs_completed += 1
 97             # Get the rest examples in this epoch
 98             rest_num_examples = self._num_examples - start
 99             images_rest_part = self._images[start:self._num_examples]
100             labels_rest_part = self._labels[start:self._num_examples]
101             # Shuffle the data
102             if shuffle:
103                 perm = numpy.arange(self._num_examples)
104                 numpy.random.shuffle(perm)
105                 self._images = self.images[perm]
106                 self._labels = self.labels[perm]
107             # Start next epoch
108             start = 0
109             self._index_in_epoch = batch_size - rest_num_examples
110             end = self._index_in_epoch
111             images_new_part = self._images[start:end]
112             labels_new_part = self._labels[start:end]
113             return numpy.concatenate(
114                 (images_rest_part, images_new_part), axis=0), numpy.concatenate(
115                 (labels_rest_part, labels_new_part), axis=0)
116         else:
117             self._index_in_epoch += batch_size
118             end = self._index_in_epoch
119             return self._images[start:end], self._labels[start:end]
120 
121 def read_data_sets(train_dir,
122                    one_hot=False,
123                    dtype=dtypes.float32,
124                    reshape=True,
125                    validation_size=5000,
126                    seed=None):
127 
128 
129 
130 
131     train_images,train_labels,test_images,test_labels = load_CIFAR10(train_dir)
132     if not 0 <= validation_size <= len(train_images):
133         raise ValueError('Validation size should be between 0 and {}. Received: {}.'
134                          .format(len(train_images), validation_size))
135 
136     validation_images = train_images[:validation_size]
137     validation_labels = train_labels[:validation_size]
138     validation_labels = dense_to_one_hot(validation_labels, 10)
139     train_images = train_images[validation_size:]
140     train_labels = train_labels[validation_size:]
141     train_labels = dense_to_one_hot(train_labels, 10)
142 
143     test_labels = dense_to_one_hot(test_labels, 10)
144 
145     options = dict(dtype=dtype, reshape=reshape, seed=seed)
146     train = DataSet(train_images, train_labels, **options)
147     validation = DataSet(validation_images, validation_labels, **options)
148     test = DataSet(test_images, test_labels, **options)
149 
150     return base.Datasets(train=train, validation=validation, test=test)
151 
152 
153 def load_CIFAR_batch(filename):
154     """ load single batch of cifar """
155     with open(filename, 'rb') as f:
156         datadict = pickle.load(f, encoding='bytes')
157         X = datadict[b'data']
158         Y = datadict[b'labels']
159         X = X.reshape(10000, 3, 32, 32).transpose(0,2,3,1).astype("float")
160         Y = numpy.array(Y)
161         return X, Y
162 
163 def load_CIFAR10(ROOT):
164     """ load all of cifar """
165     xs = []
166     ys = []
167     for b in range(1,6):
168         f = os.path.join(ROOT, 'data_batch_%d' % (b, ))
169         X, Y = load_CIFAR_batch(f)
170         xs.append(X)
171         ys.append(Y)
172     Xtr = numpy.concatenate(xs)
173     Ytr = numpy.concatenate(ys)
174     del X, Y
175     Xte, Yte = load_CIFAR_batch(os.path.join(ROOT, 'test_batch'))
176     return Xtr, Ytr, Xte, Yte
177 
178 def dense_to_one_hot(labels_dense, num_classes):
179     """Convert class labels from scalars to one-hot vectors."""
180     num_labels = labels_dense.shape[0]
181     index_offset = numpy.arange(num_labels) * num_classes
182     labels_one_hot = numpy.zeros((num_labels, num_classes))
183     labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1
184     return labels_one_hot
View Code

 

4.运行结果以及分析

这里选取55000张图片作为训练样本,测试样本选取5000张。

tensorboard可视流程图

 

运行5000次,准确率:58%

 

运行20000次,准确率:68.89%

运行40000次,准确率:100%

不要让懒惰占据你的大脑,不要让妥协拖垮了你的人生。青春就是一张票,能不能赶上时代的快车,你的步伐就掌握在你的脚下。

]]>
LearnOpenGL学习笔记(六)——纹理单元 http://doc.okbase.net/doclist/archive/264649.html doclist 2019/4/19 23:28:44
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}

      在刚才的程序中,关于片段着色器的中我们声明了一个采样器(Sampler),一般来讲我们需要用glUniform1i()函数进行将纹理对象(数据),从CPU中传入显存中的着色器这样一个过程。但是现实是我们没有这么做,我们只是在主函数里绑定了目标,就自动传入到片段着色器里面了。这就是我们忽视的一个概念——纹理单元。

    一个纹理的位置值通常称为一个纹理单元(Texture Unit)。之所以我们没有去用glUniform1i()函数,是因为一个纹理的默认纹理单元是0,它是默认的激活纹理单元。

如果我们只传入一个纹理对象,那么倒是不用担心纹理单元的问题,反正自动传入,你绑定就好了。但是当有多个纹理对象要传入的时候,我们必须指定纹理对象,然后再主函数用glUniform1i()函数一个一个对接到着色器内部完毕,否则一切就乱套了。

      纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。

使用glUniform1i设置采样器:

 

       使用glUniform1i()函数作为着色器内部和程序沟通的桥梁需要知道两件事情,一个是在着色器内部接受信息的对象为位置
(layout)。一个是外界的数据对象。严格来讲传入数据本身也不是这个函数做的,这个函数是告诉着色器那个纹理对象对应哪个采样
器对象。至于传入这个是没有函数对应的,就是激活纹理对象,绑定纹理对象,激活下一个纹理对象,绑定下一个对象。就可以告诉计算
机全部了。
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元 
glBindTexture(GL_TEXTURE_2D, texture);

渲染之前设置:

ourShader.use(); // 别忘记在激活着色器前先设置uniform!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置
ourShader.setInt("texture2", 1); // 或者使用着色器类设置

while(...) 
{
    [...]
}

渲染过程中:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);

glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以我们在前面的例子
里当我们使用glBindTexture的时候,无需激活任何纹理单元。

  

 

]]>
Web API 处理机制剖析 --- 拨开迷雾看本质 http://doc.okbase.net/doclist/archive/264648.html doclist 2019/4/19 23:28:37

 前言 最近开发了几个项目,用到了web api,也通过项目加深了对web api的理解。本文试图从内部原理讲解web api的本质。透过重重迷雾,看清本质,就能更好的把握和利用好web api。

1 Web API 的本质

 1.1 交互说明 

Web API 是基于http传输协议的函数调用。http是应用最广泛的传输协议,web服务端就实现了http服务器。由于web的流行,也带动了web服务器的完善和优化。web服务器的功能也不仅限于传输html文本,任何数据都可以通过文本传输(其实,图片,视频也可以通过变通的方式实现传输)。函数的调用就是发送数据和接收数据的过程;既然http也能发送和接收数据,当然也可以通过http实现函数调用,这就是web api。

 

http是传输协议,并不解释传输的内容。http协议同时也定义了一些标准的术语,方便客户端和服务端交互。一个典型的http发送和响应数据如下:

---->request

Request URL: https://www.baidu.com/
Request Method: GET
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Cookie: BAIDUID=43E2CBB543131B011BC1861E7CDD83DB:FG=1
Host: www.baidu.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36

body data

<----reponse

Status Code: 200 OK
Remote Address: 111.13.100.91:443
Referrer Policy: no-referrer-when-downgrade
Bdpagetype: 1
Bdqid: 0xecff8dba0009a0bc
Cache-Control: private
Connection: Keep-Alive
Content-Type: text/html
Cxy_all: baidu+5bd9cbe0adf9080de3f7682cf8f45af1
Date: Fri, 19 Apr 2019 13:44:36 GMT
Expires: Fri, 19 Apr 2019 13:44:05 GMT
Server: BWS/1.1
Set-Cookie: BIDUPSID=43E2CBB543131B011BC1861E7CDD83DB; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1555681476; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com


body data

http分为包头和包体,包头包含一些关键字段,如Accept,Cookie,这些是http的保留字。你也可以通过包头传输数据,只要不使用保留字就行。如:

myguid: 6F9619FF-8B86-D011-B42D-00C04FC964FF

通常包头传输一组函数公用的数据,比如有10个函数,这些函数都用到guid,可以把guid放入包头。

1.2 包体数据说明

http请求(request)数据:

a)对于浏览器的表单,包体数据格式如下:

key1=value1&key2=value2

就是key,value对,通过&号分割。key,value也可以是汉字,会做转义处理。这些细节不需要我们处理。只需要知道key、value可以是任何字符就行。

b)json和其他数据格式

对于web api,包体数据可以是json格式。当然不限于json,可以是任意形式的数据,只要双方对处理协议达成一致就行。

{
    "sites": [
    { "name":"菜鸟教程" , "url":"www.runoob.com" }, 
    { "name":"google" , "url":"www.google.com" }, 
    { "name":"微博" , "url":"www.weibo.com" }
    ]
}

传输复杂的数据建议使用json,json函数可以把复杂的类变量序列化;对方反序列化,就可以得到一个类变量。这样,远程函数调用与本地调用区别就很小;仅仅多一层json序列化。

2 asp.net 对web API的实现

常言:巧妇难为无米之炊。对于web api,http承载的数据就是米,asp.net框架就是巧妇。web api的本质就是数据的发送和接收,asp.net 能做的的就是方便你的接收和返回。asp.net这里是好心帮程序员减轻负担,但这种好心也有副作用:很难探究web api的本质,遇到问题难以从本质上去分析解决。

2.1 数据如何变成函数参数

我们一般不处理http原始数据,这些数据一般会映射为函数参数。这种映射关系多种多样,见下图:

 

asp.net做这种映射处理就是为了减轻开发人员的负担,但是开发人员必须了解这些映射关系。不同框架处理这种映射逻辑也是不一样的,其实,开发人员可以定制自己的映射逻辑;但是好像没必要这样做,直接处理http数据,就是从asp.net request变量抽取http数据再处理,也增加不了多少工作量;这样反而使开发人员对底层数据了解更加透彻。

 2.2 asp.net 客户端调用举例

使用类WebClient做为示例:

    //客户端调用
    void clientPost()
    {
        using (var client = new System.Net.WebClient())
        {
            var postParam = new NameValueCollection();
            //传递参数
            postParam.Add("code", "123");
            postParam.Add("name", "tom);
           //http body部分就变为 code=123&name=tom

            var responsebytes = client.UploadValues(uri, postParam);

            string strResult = System.Text.Encoding.UTF8.GetString(responsebytes);
        }
    }

    //服务端:
    string ServerDeal(string code, string name)
    {
        //http body数据部分映射为参数:code为123,name为tom
        return "ok";
    }
NameValueCollection类将数据组成key、value对,并处理字符转义。通过上面的代码可以看出,web api调用其实很简单,框架帮我们做了数据映射,字符转义等常规操作,让我从繁杂的琐事中脱身,专注处理业务逻辑。
后记对事物的了解要抓住本质,只要了解了本质,就一通百通。web api原理并不繁杂,但是由于上层的封装,使我们很难了解到底层处理原理,导致很难抓住事物的本质。本文通过自身的领悟,试图剖析其本质,希望对读者有所裨益!
]]>