做视频有赚钱的网站,软件开发工具的作用,拥有自己的网站 如何做推广,网络网站文章目录 学习目标操作系统不同应用领域的主流操作系统虚拟机 Linux系统的发展史Linux内核版和发行版 Linux系统下的文件和目录结构单用户操作系统vs多用户操作系统Windows和Linux文件系统区别 Linux终端命令格式终端命令格式查阅命令帮助信息 常用命令显示文件和目录切换工作目… 文章目录 学习目标操作系统不同应用领域的主流操作系统虚拟机 Linux系统的发展史Linux内核版和发行版 Linux系统下的文件和目录结构单用户操作系统vs多用户操作系统Windows和Linux文件系统区别 Linux终端命令格式终端命令格式查阅命令帮助信息 常用命令显示文件和目录切换工作目录创建文件和文件夹删除文件和文件夹拷贝、移动文件和文件夹其他日期指令历史指令文件查看数据流重定向、管道建立链接文件搜索归档和压缩文件权限用户管理关机、重启Ubuntu中软件安装与卸载ssh远程登录scp远程拷贝上传/下载 编辑器vi系统性能定时监控系统监控概述psutil python开发环境及网络基础虚拟环境为什么需要虚拟环境虚拟环境搭建 网络通信概述IP地址IP地址查看端口 网络-udp/tcp网络传输方式UDP\TCP)socket简介udp网络程序-发送、接收数据python3编码转换udp端口绑定udp广播udp聊天室TCP简介TCP客户端TCP服务器端案例文件下载器TCP的3次握手TCP的4次挥手浏览器访问服务器的过程——IP地址、域名、DNSHTTP协议HTTP请求协议分析HTTP响应报文协议分析 长连接和短连接案例模拟浏览器实现基于TCP的Web服务器案例 多任务 - 线程多任务的介绍线程使用threading模块创建子线程线程名称、总数量线程参数及顺序守护线程并行和并发自定义线程类多线程-共享全局变量同步和异步互斥锁死锁 案例多任务版udp聊天器TCP服务器端框架 多任务 - 进程进程的基本使用进程名称、pid进程参数、全局变量守护主进程进程、线程对比消息队列 - 基本操作消息队列 - 常见判断Queue实现进程间通信进程池Pool进程池中的Queue案例文件夹拷贝器多进程版 多任务 - 协程可迭代对象及检测方法迭代器及其使用方法自定义迭代对象迭代器案列 - 斐波那契数列生成器生成器案例 - 斐波那契数列生成器 - 使用注意协程 - yield协程 - greenlet协程 - gevent进程、线程、协程对比案例 - 并发下载器 学习目标
能够了解操作系统的历史 能够说出常见的操作系统的及其作用 能够掌握常见的linux文件目录结构 能够使用ls命令查看当前目录下的所有文件 能够使用pwd查看当前操作路径 能够说出绝对路径和相对路径及其作用 能够分别使用touch和mkdir创建文件和文件夹 能够使用cd切换根目录、家目录、当前目录和上层目录 能够使用tab自动补全功能 能够使用clear【CtrlL】清空屏幕操作 能够使用rm删除文件或者文件夹 能够使用mv移动或者重命名文件和文件夹 能够使用日历指令查看近3个月日历并能把时间格式显示为“xxxx年xx月xx日xx时xx分xx秒” 能够使用history显示近20条历史指令 能够使用cat命令、more查看文件以及管道|的使用方法
操作系统
操作系统(Operating System简称OS)是管理和控制计算机硬件与软件资源的计算机程序是直接运行在“裸机”上的最基本的系统软件任何其他软件都必须在操作系统的支持下才能运行。
操作系统作为接口的示意图 没有安装操作系统的计算机通常被称为“裸机” 如果想在裸机上运行子所编写的程序就必须用机器语言书写程序如果计算机上安装了操作系统就可以在操作系统上安装支持的高级语言环境用高级语言开发程序举例说明 未安装操作系统播放音乐步骤如下将歌曲文件从硬盘加载到内存使用声卡对音频数据进行解码将解码后的数据发送给音箱。 安装了操作系统播放音乐步骤如下查找歌曲点击播放 操作系统的作用 对下控制硬件运行对上为应用程序提供支持。
不同应用领域的主流操作系统 桌面操作系统 操作系统说明Windows系列用户群体大macOS适合开发人员Linux应用软件少 服务器操作系统 操作系统说明Linux安全、稳定、免费占有率高Windows Server付费占有率低 嵌入式操作系统 操作系统说明Linux-WinCE- 移动设备操作系统 操作系统说明iOS基于unixAndroid基于Linux虚拟机 虚拟机Virtual Machine指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。 虚拟系统通过生成现有操作系统的全新虚拟镜像具有真实操作系统完全一样的功能进入虚拟系统后所有操作都是在这个全新的独立的虚拟系统里面进行可以独立安装运行软件保存数据拥有自己的独立桌面不会对真正的系统产生任何影响而且能够在现有系统与虚拟镜像之间灵活切换的一类操作系统
Linux系统的发展史 Linux内核版和发行版
Linux内核版本 内核kernel是系统的心脏是运行程序和管理像磁盘和打印机等硬件设备的核心程序它提供了一个在裸设备与应用程序间的抽象层。 Linux内核版本又分为稳定版和开发版两种版本相互关联相互循环。
稳定版具有工业级强度可以广泛地应用和部署。新的稳定版相对于旧的只是修正一些bug或加入一些新的驱动程序。开发版由于要试验各种解决方案所以变化很快。 内核源码网站http://www.kernel.org 所有来自全世界地对Linux源码地修改最终都会汇总到这个网站又Linus领导的开源社区对其进行甄别和修改最终决定是否进入到Linux主线内核源码中。 Linux发行版 Linux发行版也叫GNU/Linux发行版通常包含了包括桌面环境、办公套件、媒体播放器、数据库等应用软件。 常见的发行版本如下Ubuntu, Redhat, Fedora, openSUSE, Linux Mint, Debiian, Manjaro, Mageia, CentOS, Arch
Linux系统下的文件和目录结构
单用户操作系统vs多用户操作系统
单用户操作系统指一台计算机在同一时间只能由一个用户使用一个用户独享系统的全部硬件和软件资源Windows XP之前的版本都是单用户操作系统。 多用户操作系统指一台计算机在同一时间可以由多个用户使用多个用户共享系统的全部硬件和软件资源Unix, Linux的设计初衷就是多用户操作系统。
Windows和Linux文件系统区别 windows下的文件系统 在Windows下打开“计算机”我们看到的是一个个的驱动器盘符 每个驱动器都自己的根目录结构这样形成了多个树并列的情形如下所示 Linux下的文件系统 在Linux下我们是看不到这些驱动器盘符我们看到的是文件夹目录 Ubuntu没有盘符这个概念只有一个根目录/所有文件都在它下面 用户(主/家)目录 位于/home/user,称之为用户工作目录或家目录表示方式/home/user 注意user表示当前登录用户名 Linux主要目录速查表 目录说明/根目录一般根目录下只存放目录在linux下有且只有一个根目录所有的东西都是从这里开始。当在终端里输入/home其实是在告诉电脑先从/根目录开始再进入到home目录进入根目录的路径文件其他位置计算机/bin/usr/bin可执行二进制文件的目录如常用的命令ls、tar、mv、cat等/etc系统配置文件存放的目录不建议在此且录下存放可执行文件重要的配置文件有/etc/inittab; /etc/fstab; /etc/init.d; /etc/X11; /etc/sysconfig; /etc/xinetd.d/home系统默认的用户家目录新增用户账号时用户的家目录都存放在此目录下~ 表示当前用户的家目录~ edu 表示用户edu的家目录/root系统管理员root的家目录/boot放置linux系统启动时用到的一些文件如linux的内核文件/boot/vmlinuz系统引导管理器/boot/grub/dev存放linux系统下的设备文件访问该目录下某个文件相当于访问某个设备常用的是挂载光驱mount /dev/cdrom/mnt/lib/usr/lib/usr/local/lib系统使用的函数库的目录程序在执行过程中需要调用一些额外的参数时需要函数库的协助/lostfound系统异常产生错误时会将一些遗失的片段放置于此目录下/mnt:/media光盘默认挂载点通常光盘挂载于mnt/cdrom下也不一定可以选择任意位置进行挂载/opt给主机额外安装软件所摆放的目录/proc此目录的数据都在内存中如系统核心外部设备网络状态由于数据都存放于内存中所以不占用磁盘空间比较重要的文件有/proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/*等/sbin/usr/sbin/usr/local/sbin放置系统管理员使用的可执行命令如fdisk、shutdown、mount等。与/bin不同的是这几个目录是给系统管理员root使用的命令一般用户只能“查看”而不能设置和使用/tmp一般用户或正在执行的程序临时存放文件的目录任何人都可以访问重要数据不可放置在此目录下/srv服务启动之后需要访问的数据目录。如www服务需要访问的网页数据存放在/srv/www内/usr应用程序存放目录/usr/bin存放应用程序/usr/share存放共享数据/usr/lib存放不能直接运行的确是许多程序运行所必需的一些函数库文件/usr/local存放软件升级包/usr/share/doc系统说明文件存放目录/usr/share/man程序说明文件存放目录/var放置系统执行过程中经常变化的文件/var/log随时更改的日志文件/var/spool/mail邮件存放的目录/var/run程序或服务启动后其PID存放在该目录下
Linux终端命令格式
终端Terminal通常是一个软件控制台在终端中输入指令可以控制电脑的执行内容。 作用大大提升操作系统的操控效率。 打开方式 1. 桌面右键打开终端 2. 快捷键【CtrlAltT】
终端命令格式
command [-options] [parameter] 说明 command 命令名相应功能的英文单词或单词的缩写 [-options]可用来对命令进行控制也可以省略 [parameter]传给命令的参数可以是0个1个或多个
查阅命令帮助信息
command --help 说明显示command命令的帮助信息man command 说明man是manual的缩写是Linux提供的一个手册包含了绝大部分的命令、函数的详细使用说明。使用man时的操作键 操作键功能Enter键一次滚动手册页的一行空格键显示手册页的下一屏f前滚一屏b回滚一屏q退出/word搜索word字符串
常用命令
显示文件和目录 显示当前路径pwd 以树状图列出目录的内容tree [dirName] 可指定目录不指定显示当前目录下的所有内容。 查看文件信息ls lslist缩写功能为列出目录的内容。Linux文件或者目录名称最长可以有265个字符“.” 表当前目录“…”代表上一级目录以“.”开头的文件未隐藏文件。 ls常用参数 选项含义-a显示指定目录下所有子目录与文件包括隐藏文件。-l以列表方式显示文件的详细信息-h配合-l以人性化的方式显示文件大小无此选项文件大小单位为字节
切换工作目录
cd后面可以跟绝对路径也可以跟相对路径cd后如果省略目录则默认切换到当前用户的主家目录。 注Linux所有的目录和文件名大小写敏感。
命令含义cd切换到当前用户的主目录/home/用户目录用户登陆时默认的目录就是用户的主目录。cd ~切换到当前用户的主目录/home/用户目录cd .切换到当前目录cd …(2个点)切换到上级目录cd -可进入上次所在的目录注意不是上级目录
创建文件和文件夹 创建目录mkdir 通过mkdir可以创建一个新的目录。 注意新建目录的名称不能与当前目录中已有的目录或文件同名并且目录创建者必须对当前目录具有写权限。 选项含义-p递归创建目录 创建文件touch 用户可以通过touch来创建一个空文件如下 touch hello.txt # 创建一个hello.txt文件
touch a.txt b.txt c.txt # 创建3个文件分别为a.txt, b.txt, c.txt注意Linux系统中没有严格的后缀格式所以创建文件时可以命名为任意的文件名。 打开文件gedit 通过gedit可以打开文件并进行编辑如下 gedit hello.txt # 打开并编辑单个hello.txt文件
gedit a.txt b.txt c.txt # 同时打开a.txt, b.txt, c.txt多个文件并编辑注意打开文件后终端进入等待状态。
删除文件和文件夹 删除文件rm 可通过rm刚除文件或目录。使用rm命令要小心因为文件删除后不能恢复。为了防止文件误删可以在rm后使用-i参数以逐个确认要删除的文件。 常用参数及含义如下表所示 选项含义-i以交互模式删除提示确认删除与否-f强制删除忽略不存在的文件无需提示-r递归地删除目录下的内容删除文件夹目录时必须加此参数
拷贝、移动文件和文件夹 拷贝 cp 源路径 目标路径 cp命令的功能是将给出的文件或目录复制到另一个文件或目录中相当于DOS下的copy命令。 常用选项说明 选项含义-a该选项通常在复制目录时使用它保留链接、文件属性并递归地复制目录简单而言保持文件原有属性。-f已经存在的目标文件而不提示-i交互式复制在覆盖目标文件之前将给出提示要求用户确认-r若给出的源文件是目录文件则cp将递归复制该目录下的所有子目录和文件目标文件必须为一个目录名。拷贝文件夹目录时必须加此参数-v显示拷贝进度 移动、重命名 mv 源路径原文件名 目标路径新文件名 注意移动文件夹不需要加-r选项 常用选项说明 选项含义-f禁止交互式操作如有覆盖不会提示-i确认交互式操作如有覆盖会提示-v显示移动进度 注意 在一个目录中进行移动才能进行重命名
其他 清屏 clear 作用清除终端上的显示快捷键【CtrlL】 自动补全 tab 在敲出文件/目录/命令的前几个字母之后按下tab键 如果输入的没有歧义系统会自动补全 如果还存在其他文件/目录/命令再按下tab键系统会提示可能存在的命令 查看命令位置 which 命令 which命令用于查找并显示给定命令的绝对路径。 小技巧 按 上/下光标键可以在曾经使用过的命令之间来回切换如果想要退出选择并且不想执行当前选中的命令可以按 【CtrlC】示例先cd到家目录然后输入tree显示所有目录文件按下CtrlC可中止【CtrlShIft】 放大终端窗口的字体显示【Ctrl -】 缩小终端窗口的字体显示
日期指令 日历cal cal命令可以用来显示日历。常用选项说明 选项含义-3显示系统前一个月当前月下一个月的月历-j显示在当年中的第几天(一年日期按天算从1月1号算起默认显示当前月在一年中的天数)-y显示当前年份的日历 日期显示 date “%Y年%m月%d日 %H时%M分%S秒” date命令根据给定格式显示日期或设置系统日期时间。常用选项说明 选项含义%Y四位年份%y两位年%m月份(1-12)%d按月计的日期(1-31)%H小时(0-23)%M分钟(0-59)%S秒数(0-59)%F完整日期格式等价于%Y-%m-%d%T完整时间格式等价于%H:%M:%S
历史指令
查看历史指令 history history命令用于显示指定数目的指令命令读取历史命令文件中的目录到历史命令缓冲区和将历史命令缓冲区中的目录写入命令文件(存储在HOME目录中的bash_history文件中)。执行历史命令 !历史命令编号 文件查看 查看或合并文件内容cat 文件名 cat 文件名1 文件名2 cat命令用于查看、连接文件并打印到标准输出设备上。特点一次性查看。 常用选项说明 选项含义-n由1开始对所有输出的行数编号-b-n相似只不过对于空白行不编号-s遇到连续两行以上空白行换为一行显示通过配合重定向可合并多文件内容到一个文件中。 示例 准备文件如下 1.txt
1111111
22222333344444aaaaa
bbbcccc
dddddeeee2.txt
----------------
22222222
2222222222
222222222222分屏查看文件内容 more 有时信息过长无法在一屏上显示时会出现快速滚屏使得用户无法看清文件的内容此时可以使more命令每次只显示一页按下空格键可以显示下一页按下q键退出显示按下键h可以获取帮助。 常用选项说明 选项含义num例如5从第5行开始查看文件内容-p先清屏再显示文件内容-s当两个以上连续空行换成一行的空白行常用选项说明 选项含义Enter向下n行需要定义默认为1行CtrlF向下滚动一屏F(front前进)CtrlB返回上一屏B(back后退)空格键向下滚动一屏q退出more
数据流重定向、管道 数据流及输出重定向 输出重定向会覆盖原来的内容输出重定向则会追加到文件的尾部。 输入流从键盘或者文件中读取内容到内存中 输出流从计算机内存中把数据写入到文件或者显示到显示器上 重定向改变数据流的默认输出方向。 Linux中有三种流标准输出流、标准错误输出流、标准输入流 标准输出流stdout标准输出对应于终端的输出 正常的数据–终端屏幕上例如cal指令输出当前月份日历输出到哪–屏幕。标准错误输出流stderr 标准错误输出对应于终端的输出 错误信息–终端屏幕上例如cal指令输错成cale错误会显示在屏幕上。标准输入流stdin标准输入对应于你在终端的输入 向系统中输入数据默认就是键盘输入的数据。 文件描述符设备文件说明0/dev/stdin标准输入1/dev/stdout标准输入2/dev/stderr标准错误我们可以通过重定向的技术把输出、输入的信息重定向到其它的地方去。比如我们可以把系统中的错误信息输出到一个文件中去。 如cal test.txttest.txt如果不存在则创建存在则覆盖其内容 管道 指令1 | 指令2 注意 指令1必须要有输出。 管道我们可以理解现实生活中的管子管子的一头塞东西进去另一头取出来这里“|“的左右分为两端左端塞东西写右端取东西读。即一个命令的输出作为另外一个命令的输入去使用。 管道命令操作符是“|”它只能处理经由前面一个指令传出的正确输出信息对错误信息信息没有直接处理能力。然后传递给下一个命令作为标准的输入。 如ls -lh | more
建立链接
Linux链接文件类似于Windows下的快捷方式。链接文件分为 软链接 软链接不占用磁盘空间源文件删除则软链接失效。 ln -s 源文件 链接文件 硬链接hard link也称链接 就是文件的一个或多个文件名。 ln 源文件 链接文件 注意
如果软链接文件和源文件不在同一个目录源文件要使用绝对路径不能使用相对路径.如果没有-s选项代表建立一个硬链接文件两个文件占用相同大小的硬盘空间即使删除了源文件链接文件还是存在所以-s选项是更常见的形式。
修改源文件对软硬链接文件的影响 修改源文件对软硬链接均有影响修改软硬链接对源文件均有影响 删除源文件对软硬链接文件的影响 删除软硬链接对源文件均没有影响删除源文件 软链接不可以用 如果文件有的多个硬链接则无影响 软硬链接使用区别 软链接可以跨文件系统硬链接不可以软链接可以对一个不存在的文件名(filename)进行链接当然此时如果你via这个软链接文件linux会自动新建一个文件名为filename的文件硬链接不可以其源文件必须存在 软链接可以对目录进行连接硬链接不可以
文件搜索 文本搜索 grep Linux系统中grep命令是一种强大的文本搜索工具grep允许对文本文件进行模式查找如果找到匹配模式grep打印包含模式的所有行。grep一般格式为grep [-选项] ‘搜索内容串’ 文件名。在在grep命令中输入字符串参数时最好引号或双引号括起来。 常用选项说明 选项含义-v显示不包含匹配文本的所有行相当于求反-n显示匹配行及行号-i忽略大小写grep搜索内容串可以是正则表达式gerp常用正则表达式 参数含义^a行首搜寻以a开头的行grep -n ^a 1.txtke$行尾搜寻以ke结束的行grep -n ‘ke$’ 1.txt[Ss]igna[Ll]匹配[ ]里中一系列字符中的一个搜寻匹配单词signal、signaL、Signal、SignaL的行grep -n [Ss]ignal[Ll] 1.txt.点匹配一个非换行符的字符匹配e和e之间有任意一个字符可以匹配eee, eae, eve但是不匹配ee, eaaegrep -n e.e 1.txt 查找文件 find find命令功能非常强大通常用来在特定的目录下搜索符合条件的文件也可以用来搜索特定用户属主的文件。 常用用法 命令含义find ./ -name test.sh查找当前目录下所有名为test.sh的文件find ./ -name *.sh查找当前目录下所有后缀为.sh的文件find ./ -name [A-Z]*查找当前目录下所有以大写字母开头的文件 * 表示任意字符 ?表示任意一个字符[列举字符] 表示列举出的任意一个字符find /tmp -size 2M查找在/tmp目录下等于2M的文件find /tmp -size 2M查找在/tmp目录下大于2M的文件find /tmp -size -2M查找在/tmp目录下小于2M的文件find ./ -size 4k -size -5M查找当前目录下大于4k小于5M的文件find ./ -perm 777查找当前目录下权限为777的文件或目录
归档和压缩 归档和压缩的概念 归档 归档就是将一些文件放在一起变成一个包便于保存和传输图片和视频数据因为不象文本一样因此多个文件在压缩的时候没有明显效果因此只能做归档进行保存。 压缩 压缩也是一种打包压缩的原理是将文件中相同的信息用一个字符代替致使文件体积变小达到压缩的目的压缩对于文本类或数据类文件有较明显的作用。 归档管理 tar 计算机中的数据经常需要备份tar是Unix/Linux中最常用的备份工具此命令可以把一系列文件归档到一个大文件中也可以把档案文件解开以恢复数据。 tar使用格式 多文件归档tar [参数] 打包文件名 文件1 文件2 目录归档tar [参数] 打包文件名 目录 tar命令很特殊其参数前面可以使用“-”也可以不使用。 常用参数 命令含义-c生成档案文件创建打包文件-v列出归档解档的详细过程显示进度-f指定档案文件名称f后面一定是.tar文件所以必须放 选项最后 \color{red}{选项最后} 选项最后-x解开档案文件注意除了f需要放在参数的最后其它参数的顺序任意。 打包tar -cvf 打包文件名.tar 要打包的文件 当要打包目录下所有文件的时候可以使用*。 解包 tar -xvf 打包文件名.tar 打包 压缩 和 解压 解包 打包 压缩 tar -zcvf 压缩包文件名.tar.gz 待压缩文件或目录 解压 解包 tar -zxvf 压缩包文件名.tar.gz 解压解包到指定目录中 tar -zxvf 压缩包文件名.tar.gz -C 指定目录 文件压缩解压 zip、unzip 通过zip压缩文件的目标文件不需要指定扩展名默认扩展名为zip。压缩目录需要添加选项-r。 压缩文件 zip -r 压缩目录 源目录 、 zip 压缩文件 源文件 解压文件 unzip 压缩文件 解压到当前目录下unzip -d 指定解压目录 压缩文件 几种压缩方式对比 tar.gz的打包和压缩方式相比zip或者bz2产生的压缩包文件更小如下图
文件权限
Linux中的权限系统和上面图中的内容类似Linux中的每个文件、目录都可以分别对拥有者u、同组用户g、其他用户o设置权限。 上图中第一列中从第2个字符开始的9个字符就代表这个文件的权限每三个字母一组每一组都分为 r可读、w可写、x可执行文件文件可以直接运行绿色目录表示这个目录可以打开、-没有权限 修改文件权限 chmod 字母法chmod 用户(u/g/o/a) 权限设置(/-/) 具体权限(r/w/x) 文件名 字母符号含义uuser表示该文件的所有者ggroup表示与该文件的所有者属于同一组者即用户组oother表示其他以外的人aall表示这三者皆是增加权限-撤销权限设定权限rread表示可读取对于一个目录如果没有r权限那么就意味着不能通过Is查看这个目录的内容。权限数字4wwrite表示可写入对于一个目录如果没有w权限那么就意味着不能在目录下创建新的文件权限数字2xexecute表示可执行对于一个目录如果没有x权限那么就意味着不能通过cd进入这个目录权限数字1 数字法chmod 权限数字 文件路径 字母说明r读取权限数字代号为 4 \color{red}{4} 4”w写入权限数字代号为“ 2 \color{red}{2} 2”x执行权限数字代号为“ 1 \color{red}{1} 1”-不具任何权限数字代号为“ 0 \color{red}{0} 0”如执行chmod urwx,grx,or filename就等同于chmod u7,g-5,o4 filename chmod 754 file表示文件所有者读、写、执行权限同组用户读、执行的权限其它用户读权限。 目录权限 注意如果想递归所有目录加上相同权限需要加上参数“-R”·如chmod -R 777 test/递归test目录下所有文件加777权限。 目录的可执行权限一个目录具有可执行权限表示可以切换到该目录。
用户管理 切换账号/用户永久 sudo -s默认切换到root用户需要输入当前用户的密码 su root切换到root用户需要输入root用户密码 su 用户名切换到指定用户。注意从高权限切换到低权限不需要输入密码反之需要。 sudo临时提升权限sudo 命令 sudo命令用来以其他身份来执行命令预设的身份为root。在/etc/sudoers中设置了可执行sudo指 令的用户。若其未经授权的用户企图使用sudo则会发出警告的邮件给管理员。用户使用sudo时必须先输入密码之后有5分钟的有效期限超过期限则必须重新输入密码。 设置用户密码 passwd 用户名修改指定用户的密码 passwd 修改当前登录的用户密码 在Unix/Linux中超级用户可以使用passwd命令为普通用户设置或修改用户密码。用户也可以直接使用该命令来修改自己的密码而无需在命令后面使用用户名。 注意 普通用户修改密码系统默认对密码长度等信息进行验证如果不合法提示修改失败如修改密码为123提示密码太短修改失败 root超级管理员权限修改密码密码长度可以任意设定不进行验证比如修改密码为123能够修改成功 退出登录账号 exit 如果没有用户在栈中直接退出终端 如果多次切换用户退出到上次登录的用户 查看登录用户 who who命令用于查看当前所有登录系统的用户信息。 常用选项 选项含义-q或–count只显示用户的登录账号和登录用户的数量统计用户数-u或–heading显示列标题显示最后一次操作距现在的时间
关机、重启
关机 shutdown 命令含义shutdown -h now立即关机其中now相当于时间为0的状态shutdown -h 20:25系统在今天的20:25会关机可以取消关机shutdown -h 10系统再过十分钟后自动关机shutdown -c撤销关机 重启reboot 命令含义reboot重新启动操作系统shutdown -r now重新启动操作系统shutdown会给别的用户提示
Ubuntu中软件安装与卸载
在ubuntu当中安装应用程序常见的有三种方法分别是make install, deb包方式 (类似windows.exe), apt-get和安装源码包三种下面针对每一种方法各举例来说明。 make install源代码安装包也称tarball) 优点软件根据实际的机器硬件进行配置和编译性能最好、最稳定。 缺点需要使用源代码编译、安装比较麻烦。 tarball就相当于我们自己做馒头一样自己买面合面自己蒸虽然比较麻烦但是自己做的很香还可以根据自己的口味调整。 一般步骤如下 1./configure ##这个步骤是建立makefile这个文件。 2makeclean ##消除下上次编泽过的目标文件之类的不是必须要有但保险起见还是做一下。 3make ##会依据makefile当中默认工作也就是第一个进行编译行为主要是进行gcc将源码编译成为可执行的目标文件而这个可执行文件放置在目前所在的目录之下。 4make install一般是最后的安装步骤make会依据makefile关于install的选项将上个步骤编译完 成的数据安装到默认的目录中。 dpkg安装deb包 优点安装包直接运行安装相对tarballi简单些。 缺点存在包依赖的问题安装时需要手动下载很多安装包。 包依赖安装A包时报错提示需要B包下载安装B包时报错提示需要C包····一直要把所有用的包都下载安装才可以。deb解决了这个问题。 Ubuntu软件包格式为deb安装方法如下sudo dpkg -i package.deb 这种方式类似于windows的软件安装方式在ubuntul图形界面下可以直接双击安装也比较简单但需要下载.deb格式的软件包。 apt-get方法 使用apt-get install来安装应用程序算是最常见的一种安装方法了。 优点最简单方便的安装方式只要一条指令系统就可以自动下载并安装所有的包。 缺点必须要有软件源连网或者搭建软件源 apt-get就像外卖一样一个电话香喷喷的饼头和菜就送到家了非常方便。 一般格式为sudo apt-get install xxx 这种方式简单相暴、它能够帮我们联网自动下载安装包及其依赖包然后安装。
apt-get方法安装/卸载软件 相关命令
命令说明sudo apt-get update更新源sudo apt-get insatll package安装包sudo apt-get remove package删除包 寻找国内镜像源 所谓的镜像源可以理解为提供下载软件的地方比如Android手机上可以下载软件的91手机助手iOS手机上可以下载软件的AppStore。 清华大学开源软件镜像站https://mirrors.tuna.tsinghua.edu.cn 备份Ubuntu默认的源地址 sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup 配置软件源 编辑源服务器列表文件sudo gedit /etc/apt/sources.list 更新软件源 做完此步骤之后就可以进行apt-get install下载了sudo apt-get update 测试并安装vim编辑器 sudo apt-get install vim
ssh远程登录
ssh介绍 SSH为Secure Shell的缩写由IETF的网络工作小组Network Working Group所制定SSH为建立在应用层和传输层基础上的安全协议。 SSH是目前较可靠专为远程登录会话和其他网络服务提供安全性的协议。常用于远程登录以及用户之间进行资料拷贝。 利用SSH协议可以有效防止远程管理过程中的信息泄露问题。SSH最初是UNIX系统上的一个程序后来又迅速扩展到其他操作平台。SSH在正确使用时可弥补网络中的漏洞。SSH客户端适用于多种平台。几乎所有UNIX平台一包括HP-UX、Linux、AIX、Solaris、Digital UNIX、Irix以及其他平台都可运行SSH。 使用SSH服务需要安装相应的服务器和客户端。客户端和服务器的关系如果A机器想被B机器远程控制那么A机器需要安装SSH服务器B机器需要安装SSH客户端。安装ssh 服务器端安装ssh serversudo apt-get install openssh-server 客户端远程登录ssh 远程ssh服务器用户名远程ssh服务器IP地址 使用ssh访问如访问出现错误。可查看是否有该文件~/.ssh/known_ssh尝试删除该文件解决。使用ssh链接服务器 SSH告知用户这个主机不能识别这时键入yesSSH就会将相关信息写入~/.ssh/know_hosts’中再次访问就不会有这些信息了。然后输入完口令就可以登录到主机了。 Mac OS/Window 10终端SSH远程连接步骤如下 1输入ssh用户名服务器ip地址ssh teacher192.168.31.178 2询问是否要继续连接输入yesAre you sure you want to continue connecting (yes/no) ? yes 3输入teacher用户的密码teacher192.168.31.178s password:如果输入正确会连接成功。 4exit退出ssh关闭远程连接。 Window 7 SSH 工具——putty.exe
scp远程拷贝上传/下载
上传 scp 本地路径 服务器用户服务器ip:服务器路径 下载 scp 服务器用户服务器ip:服务器路径 本地路径 如果操作的是目录使用scp -r 使用该命令的前提条件要求目标主机已经成功安装openssh-server如没有安装使用sudo apt-get install openssh-server来安装。
编辑器vi vi简介 vi是“Visual interface“”的简称它在Linux上的地位就仿佛Edit程序在DOS上一样。它可以执行输出、删除、查找、替换、块操作等众多文本操作而且用户可以根据自己的需要对其进行定制vi不是一个排版程序它不象Word或WPS那样可以对字体、格式、段落等其他属性进行编排它只是一个文本编辑程序。vi没有菜单只有命令且命令繁多。 vi有三种基本工作模式命令模式、文本输入模式、末行模式 命令模式 任何时候不管用户处于何种模式只要按一下ESC键即可使vi进入命令模式我们在shell环境提示符为$)下输入启动vi命令进入编辑器时也是处于该模式下。在该模式下用户可以输入各种合法的vi命令用于管理自己的文档。此时从键盘上输入的任何字符都被当做编辑命令来解释若输入的字符是合法的vi命令则vi在接受用户命令之后完成相应的动作。但需注意的是所输入的命令并不在屏幕上显示出来。若输入的字符不是vi的合法命令vi会响铃报警。 文本输入模式 在命令模式下输入插入命令i、附加命令a、打开命令o、修改命令c、取代命令r或替换命令s都可以进入文本输入模式。在该模式下用户输入的任何字符都被vi当做文件内容保存起来并将其显示在屏幕上。在文本输入过程中若想回到命令模式下按键ESC即可。 末行模式 末行模式也称ex转义模或。在命令模式下用户按“:键即可进入末行模式下此时vi会在显示窗口的最后一行通常也是屏幕的最后一行显示一个“:”作为末行模式的提示符等待用户输入命令。多数文件管理命令都是在此模式下执行的如把编辑缓冲区的内容写到文件中等末行命令执行完后vi自动回到命令模式。例如:sp newfile 则分出一个窗口编辑newfile文件。如果要从命令模式转换到编辑模式可以键入命令a或者i如果需要从文本模式返回则按Esc键即可。在命令模式下输入“即可切换到末行模式然后输入命令。 vim基础操作 vim是从vi发展出来的一个文本编辑器。代码补完、编译及错误跳转等做了一些增强。 步骤 创建/打开文件vi 文件名→a/i/o进入编辑模式→编辑文件→Esc到命令模式→:进入末行模式→wq保存并退出。 进入编辑模式 命令含义i和Ii在光标前插入I在行首插入a和Aa在光标后插入A在行末插入o和Oo在光标所在行下一行插入O在光标所在行上一行插入 进入命令模式 【Esc】从插入模式/末行模式进入命令模式 2.1 移动光标 命令含义h光标向左移动j光标向下移动k光标向上移动l光标向右移动H、M、L光标移动到可见屏幕第一行H中间行M最后一行L^和$^移动到行首$移动到行末G和ggG文档最后一行gg文档第一行Ctrlf、Ctrlb向前翻屏、向后翻屏Ctrld、Ctrlu向前半屏、向后半屏{和}{向上移动一段}向后移动一段w和bw向前移动一个单词b向后移动一个单词2.2 删除命令 命令含义x和Xx删除光标所在字符X删除光标前一个字符包含光标位置字符dd和n dddd删除所在行5 dd刷除指定行数5d0和Dd0刚除光标前本行所有内容D删除光标后本行所有内容包含光标位置字符dw删除光标所在位置的字包含光标所在位置字符2.3 撤销命令 命令含义u一步一步撤销Ctrlr反撤销重做2.4 重复命令 命令含义.重复执行上一次操作的命令2.5 移动命令 命令含义文本行向左移动文本行向右移动2.6 复制粘贴 命令含义yy、n yyy复制当前行5 yy复制5行p在光标所在位置向下新开一行粘贴2.7 查找替换 命令含义命令模式下r和Rr替换当前字符R替换光标后的字符命令模式下/ strn查找下一个N查找前一个末行模式下%s/abc/123/g将文件中所有abc替换为123末行模式下1, 10s/abc/123/g将第1行至第10行之间的abc替换成123 进入末行模式 命令含义:q退出:w保存:q!强制退出不保存!强制的意思:qw!强制退出并且保存
系统性能定时监控
系统监控概述
用Python来编写脚本简化日常的运维工作是Python的一个重要用途。在Linux下有许多系统命令可以让我们时刻监控系统运行的状态如ps, top, free等等。要获取这些系统信息Python可以通过subprocess模块调用并获取结果但这样做显得很麻烦尤其要写很多解析代码。
psutil
在Python中获取系统信息的另一个好办法是使用psutil这个第三方模块。 psutil是python system and process utilities的缩写意思python的系统监控及进程的管理的工具是一个功能很强大的跨平台的系统管理库。可以实现命令行中类似ps、top、lsof、netstat、ifconfig、who、df、kill、free、nice、ionice、iostat、iotop等等命令的功能并且以python内置的数据结 构形式返回官方文档(https://pythonhosted.org/psutil/)目前psutil支持的系统有Linux、Window os X和freeBSD等。 psutil安装 psutil是一个第三方的开源项目因此需要先安装才能够使用pip3 install psutil psutil版本查看 使用python3进入交互模式查看版本psutil.version_info 常见功能 获取CPU信息 psutil.cpu_xxx() 选项含义cpu_count()逻辑CPU核数可以通过设置logicalFalse来返回物理CPU的个数psutil.cpu_count(logicalFalse)cpu_times()system系统时间在内核中执行的进程占有CPU的时间其中有些参数在不同的平台下也会有所不同nice(UNIX)被优先级高的进程所占用的时间iowait(Linux)等待l/O完成的时间irq(Linux,BSD)硬件中断维持所花费的时间softirq(Linux)软件中断维持所花费的时间dpc(windows)花费在dpc((Deferred Procedure Call)Windows操作系统中的一种中断调用机制过程中的时间cpu_percent()获取CPU的使用率cpu_percent方法会返回一个浮点值表示当前CPU的利用率两个参数interval与percpuinterval表示间隔默认为0.0输入命令后直接返回percpu是个bool值等于Ture的时候输出每个CPU的利用率此时返回一个列表。interval0.5设置刷新时间间隔为0.5秒percpuTrue获取每个CPU使用率psutil.cpu_percent(interval0.5, percpuTrue) 获取内存信息xxx_memory() 选项含义virtual_memory()在系统中内存的利用率信息通常有如下:total物理内存的总数available可用内存表示没有进入交换区的内存可以直接分配给进程used已经被使用的内存数free空闲内存指完全没有被使用的内存cache缓存的使用数目buffer缓冲的使用数目swap交换分区使用的数目如果需要获得某个具体的信息可以进行如下操作memory.free、memory.used等swap_memory()用于获取交换分区的信息 磁盘信息disk_xxx() 使用psutil类似于Linux下的fdisk命令我们比较关心的是磁盘的利用率以及I/O信息还有分区信息等。 选项含义disk_partitions()用于获取完整的分区信息逻辑设备名挂载点权限文件系统等等)返回一个元组disk_usage()返回硬盘分区或者目录的使用情况单位字节“/”表示获取根目录系统磁盘使用情况disk_io_counters()获取硬盘的/O个数读写信息返回一个元组perdisk参数当为True时返回每个磁盘的信息此时返回一个字典 网络信息net_xxx() 选项含义net_io_counters()用于获取网络总的l/O信息返回一个元组默认pernicFalse当pernic为True时返回每个网路接口的/O信息此时返回一个字典bytes_sent发出的比特数bytes_recv收到的比特数packets_sent发出的包数量packets_recv接受的包数量errin接收时出现的错误总数errout发送时出现的错误总数dropin发送过来时丢包的数量dropout发出时丢包的数量net_connection()返回一个系统中的套接字的链接信息以一个列表的形式返回 获取开机时间 psutil.boot_time() # 获取开机时间Linux格式返回
datetime.datetime.fromtimestamp(psutil.boot_time()).strftime(%Y-%m-%d %H:%M:%S) # 2023-09-19 10:33:05活动用户 psutil.users() # 获取用户的信息代码示例 # 1 导入psutil模块
import psutil
# 2 获取CPU信息
# 2.1 获取CPU核心数
print(psutil.cpu_count()) # 逻辑CPU核心数 16
print(psutil.cpu_count(logicalFalse)) # 获取物理的核心数 8
# 2.2 CPU使用率
print(psutil.cpu_percent(interval0.5)) # 2.8
print(psutil.cpu_percent(interval0.5, percpuTrue)) # 获取每个核心的使用率 [0.0, 6.2, 3.2, 3.1, 0.0, 0.0, 0.0, 0.0, 0.0, 3.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
# 3 获取内存信息
# 3.1 获取内存的整体信息
print(psutil.virtual_memory()) # svmem(total34132516864, available22007361536, percent35.5, used12125155328, free22007361536)
# 3.2 获取内存的使用率
print(psutil.virtual_memory().percent) # 35.5
# 4 获取硬盘信息
# 4.1 获取磁盘的分区信息
print(psutil.disk_partitions()) # [sdiskpart(deviceC:\\, mountpointC:\\, fstypeNTFS, optsrw,fixed, maxfile255, maxpath260), sdiskpart(deviceD:\\, mountpointD:\\, fstypeNTFS, optsrw,fixed, maxfile255, maxpath260)]
# 4.2 获取指定目录的磁盘信息
print(psutil.disk_usage(/)) # sdiskusage(total383954972672, used28907421696, free355047550976, percent7.5)
# 4.3 获取磁盘的使用率
print(psutil.disk_usage(/).percent) # 7.5
# 5 获取网络信息
# 5.1 获取收到的数据包数量
print(psutil.net_io_counters().bytes_recv) # 304114986
# 5.2 获取发送的数据包数量
print(psutil.net_io_counters().bytes_sent) # 171737823实战 功能描述 能够显示当前服务器CPU、内存、硬盘的使用率网络的收发情况能够显示CPU总核心数、总内存、总硬盘能够将日志信息保存到log.txt文件中能够直接执行脚本查看信息。 实现效果 显示的表格样式的字符串代码 log_str |------------------|-------------|--------------|--------------|----------------------------|\n
log_str | 监控时间 | CPU使用率 | 内存使用率 | 硬盘使用率 | 网络收发量 |\n
log_str | |共x核CPU |总计xG内存|总计xG硬盘| |\n
log_str |------------------|-------------|--------------|--------------|----------------------------|\n
log_str | xx | xx% | x.x% | x.x% | 收x/发x |\n
log_str |------------------|-------------|--------------|--------------|----------------------------|\n
print(log_str)代码实现 # 1 导入模块
import psutil
import datetime
# 2 定义变量保存CPU的使用率
cpu_perc psutil.cpu_percent(interval0.5)
# 3 定义变量保存内存信息
memory_info psutil.virtual_memory()
# 4 定义变量保存硬盘信息
disk_info psutil.disk_usage(/)
# 5 定义变量保存网络信息
net_info psutil.net_io_counters()
# 获取系统当前时间
current_time datetime.datetime.now().strftime(%F %T)
# 6 拼接字符串显示
log_str |--------------------|-----------|----------------|--------------|---------------------------|\n
log_str | 监控时间 | CPU使用率 | 内存使用率 | 硬盘使用率 | 网络收发量 |\n
log_str | | 共%d核CPU|总计%.2fG内存|总计%dG硬盘| |\n % (psutil.cpu_count(logicalFalse), memory_info.total/1024/1024/1024, disk_info.total/1024/1024/1024)
log_str |--------------------|-----------|----------------|--------------|---------------------------|\n
log_str |%s | %s%% | %s%% | %s%% | 收%s/发%s |\n % (current_time, cpu_perc, memory_info.percent, disk_info.percent, net_info.bytes_recv, net_info.bytes_sent)
log_str |--------------------|-----------|----------------|--------------|---------------------------|\n
# print(log_str)
# 7 保存监控信息到日志文件
f open(log.txt, a)
f.write(log_str \n\n)
f.close()代码优化——定时监控 a. 定义linux_monitor()实现监控 b. main()启动定时监控 # 1 导入模块
import psutil
import datetimedef linux_monitor(time):定义函数实现信息的显示和日志的保存# 2 定义变量保存CPU的使用率cpu_perc psutil.cpu_percent(intervaltime)# 3 定义变量保存内存信息memory_info psutil.virtual_memory()# 4 定义变量保存硬盘信息disk_info psutil.disk_usage(/)# 5 定义变量保存网络信息net_info psutil.net_io_counters()# 获取系统当前时间current_time datetime.datetime.now().strftime(%F %T)# 6 拼接字符串显示log_str |--------------------|-----------|----------------|--------------|---------------------------|\nlog_str | 监控时间 | CPU使用率 | 内存使用率 | 硬盘使用率 | 网络收发量 |\nlog_str | | 共%d核CPU|总计%.2fG内存|总计%dG硬盘| |\n % (psutil.cpu_count(logicalFalse), memory_info.total/1024/1024/1024, disk_info.total/1024/1024/1024)log_str |--------------------|-----------|----------------|--------------|---------------------------|\nlog_str |%s | %s%% | %s%% | %s%% | 收%s/发%s |\n % (current_time, cpu_perc, memory_info.percent, disk_info.percent, net_info.bytes_recv, net_info.bytes_sent)log_str |--------------------|-----------|----------------|--------------|---------------------------|\n# print(log_str)# 7 保存监控信息到日志文件f open(log.txt, a)f.write(log_str \n\n)f.close()def main():程序的入口死循环每隔一段时间显示一次while True:linux_monitor(5) # 每隔5s# __name__值
# 1 如果当前py文件被其他文件导入则__name__的值是当前的py文件名
# 2 如果直接运行当前py文件则__name__的值是__main__
if __name__ __main__:main()终端方式运行 a. 文件增加可执行权限chmod ux xxx.py b. 告诉终端代码使用python解释器执行#!/home/demo/.Envs/1-basics-python3/bin/python3 c. 进入虚拟环境运行workon 1-basics-python3 d. 执行py文件./xxx.py 邮件监控 yagmail模块使用 yagmail可以更简单地来实现自动发邮件功能。github项目地址https://github.com/kootenpv/yagmail 安装 pip3 install yagmail 安装成功验证 简单实现
yagmail 发送邮件
1. 导入模块
2. 使用yagmail的类创建对象发件人发件人授权码发件的服务器
3. 使用yagmail对象发送邮件指定接收人邮件主题发送的内容# 1. 导入模块
import yagmail# 2. 使用yagmail的类创建对象发件人发件人授权码发件的服务器
# 2.1 发件人375898283qq.com -- user375898283qq.com
# 2.2 发件人授权码passwordmuwflwxwxdirbjgd 非密码
# 2.3 发件服务器hostsmtp.qq.com
ya_obj yagmail.SMTP(user375898282qq.com, passwordmuwflwxwxdirbjgd, hostsmtp.qq.com)# 3. 使用yagmail对象发送邮件指定收件人邮件主题发送的内容
content 测试一下
ya_obj.send(734830953qq.com, 邮件监控, content)代码实现 # 1 导入模块
import psutil
import datetimeimport yagmail
import ygmaildef linux_monitor(time):定义函数实现信息的显示和日志的保存# 2 定义变量保存CPU的使用率cpu_perc psutil.cpu_percent(intervaltime)# 3 定义变量保存内存信息memory_info psutil.virtual_memory()# 4 定义变量保存硬盘信息disk_info psutil.disk_usage(/)# 5 定义变量保存网络信息net_info psutil.net_io_counters()# 获取系统当前时间current_time datetime.datetime.now().strftime(%F %T)# 6 拼接字符串显示log_str |--------------------|-----------|----------------|--------------|---------------------------|\nlog_str | 监控时间 | CPU使用率 | 内存使用率 | 硬盘使用率 | 网络收发量 |\nlog_str | | 共%d核CPU|总计%.2fG内存|总计%dG硬盘| |\n % (psutil.cpu_count(logicalFalse), memory_info.total / 1024 / 1024 / 1024, disk_info.total / 1024 / 1024 / 1024)log_str |--------------------|-----------|----------------|--------------|---------------------------|\nlog_str |%s | %s%% | %s%% | %s%% | 收%s/发%s |\n % (current_time, cpu_perc, memory_info.percent, disk_info.percent, net_info.bytes_recv, net_info.bytes_sent)log_str |--------------------|-----------|----------------|--------------|---------------------------|\n# print(log_str)# 7 保存监控信息到日志文件f open(log.txt, a)f.write(log_str \n\n)f.close()# 8 发送邮件判断内存超过80%或CPU超过90%才发if memory_info.percent 80 or cpu_perc 90 :ya_obj yagmail.SMTP(user375898282qq.com, passwordmuwflwxwxdirbjac, hostsmtp.qq.com)ya_obj.send(734830953qq.com, [系统监控报告], log_str)def main():程序的入口死循环每隔一段时间显示一次while True:linux_monitor(5) # 每隔5sif __name__ __main__:main()python开发环境及网络基础
虚拟环境
为什么需要虚拟环境 virtualenv用于创建独立的python环境多个python相互独立互不影响它能够 1.在没有权限的情况下安装新套件 2.不同应用可以使用不同的套件版本 3.套件升级不影响其他应用 虚拟环境搭建 安装虚拟环境的命令 sudo pip install virtualenv
sudo pip install virtualenvwrapper安装完虚拟环境后如果提示找不到mkvirtualenv命令须配置环境变量 # 1 创建目录用来存放虚拟环境
mkdir
$HOME/.virtualenvs# 2 打开~/.bashrc文件并添加如下
export WORKON_HOME$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh# 3 运行
source ~/.bashrc创建虚拟环境 提示如果不指定python版本默认安装的是python2的虚拟环境。 创建默认python默认版本的虚拟环境mkvirtualenv 虚拟环境名称 虚拟环境的默认位置用户目录(/home/demo)/.Envs/虚拟环境名 注意 创建虚拟环境需要联网创建成功后会自动工作在这个虚拟环境上工作在虚拟环境上提示符最前面会出现“虚拟环境名称” 创建指定python版本的虚拟环境mkvirtualenv -p python路径 虚拟环境名称 -p指的是path的简写。 Python安装的路径默认安装再/usr/bin/目录下。如果找不到使用命令whereis python3 使用虚拟环境 查看虚拟环境的命令 workon 两次tab键 或者 lsvirtualenv 启动/切换虚拟环境 workon 虚拟环境名称 对比使用虚拟环境前后变化查看虚拟环境中的python位置which python 退出虚拟环境deactivate 删除虚拟环境 rmvirtualenv 虚拟环境名称 注意 如果目前的位置在虚拟环境中需要先退出虚拟环境然后才能执行删除 如先退出deactivate 再删除rmvirtualenv py3_f3sk 可以在任何目录执行删除操作如果不知道名字可以rmvirtualen两次Tab键提示所有的虚拟环境 在虚拟环境中安装工具包 提示工具包安装的位置 python2版本~/.virtualenvs/py_flask/lib/python2.7/site-packages/ python3版本~/.virtualenvs/py3_flask/lib/python3.5/site-packages/ 安装包命令 pip install 包名称版本 注意一定不要使用sudo pip …这里是在虚拟环境中安装python包如果使用了sudo权限python包会被安装在主机非虚拟环境下在虚拟环境中找不到这个包。 查看虚拟环境中安装的包pip freeze
网络通信概述
网络的概念 一些相互连接的、以共享资源为目的的计算机的集合。使用网络能够把多方连接在一起然后可以进行数据传递。 网络编程的概念 指的是让在不同的电脑上的软件能够进行数据传递。 学习网络的目的 能够编写基于网络通信的软件。
IP地址
地址 地址就是用来标记地点的。 IP地址 IP地址是指互联网协议地址(英语Internet Protocol Address又译为网际协议地址)是IP Address的缩写。IP地址是P协议提供的一种统一的地址格式。IP地址被用来给Internet上的电脑一个编号。大家日常见到的情况是每台联网的PC上都需要有IP地址才能正常通信。我们可以把“个人电脑”比作“一台电话”那么“IP地址”就相当于“电话号码”。 IP地址的作用 用来在网络中标记一台电脑是网络设备为网络中的每台计算机分配的一个唯一标识。比如192.168.1.1在本地局域网上是唯一的。 IP地址的组成 ip地址由两部分组成网络号主机号 表示的范围xxx.xxx.xxx.0 ~ xxx.xxx.xxx.255 注意前3段为网络号最后1段0-255为主机号。xxx.xxx.xxx.0为内部ipxxx.xxx.xxx.255为广播地址。 IP地址的分类 生活中我们说的IP地址通常指的是IPv4(IP协议的第4个板本) 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-100111111 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-110111111 11111111 11111110 11111110 C类网络可达2097152个每个网络能容纳254个主机。 C类地址中一般xxx.xxx.xxx.0和xxx.xx.xxx.255两个IP地址有特殊用法! 所以理论上xxx.xxx.xxx.0~xxx.xxx.xxx.255能容纳256台主机但是因为2个IP地址不能使用所以 最多能容纳254台。 主机全零(“0.0.0.0”)地址对应于当前主机。 全“1”的IP地址(“255.255.255.255”)是当前子网的广播地址。 D类IP地址 D类地址用于多点广播。D类IP地址第一个字节以“1110”开始它是一个专门保留的地址。 它并不指向特定的网络目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机。地址范围224.0.0.1-239.255.255.254 E类IP地址 以“1111”开始为将来使用保留。 私有IP 在这么多网络IP中国际规定有一部分IP地址是用于我们的局域网使用也就是属于私网IP不在公网中使用的它们的范国是 10.0.0.0~10.255.255.255内网虚拟机中常出现A类 172.16.0.0~172.31.255.255内网子网B类 192.168.0.0~192.168.255.255内网子网C类
注意 IP地址127.0.0.1~127.255.255.255用于回路测试。 特殊的IP地址127.0.0.1 127.0.0.1可以代表本机IP地址。用http://127.0.0.1就可以测试本机中配置的Web服务器。 特殊的域名localhost localhost是本机域名用来解析到本机127.0.0.1ip地址上。 IPv4和IPv6 IPv4 是互联网网络协议(Internet Protocol IP)的第四版也是第一个被广泛使用构成现今互联网技术的基石的协议。采用“点分十进制”表示(如192.168.1.100)一共有2^32-1个估算约为42.9亿个除去一些特用的IP和一些不能用的IP剩下可用的不到40亿。IPv4发展到现在最大的问题是网络地址严重不足。 IPv6是Internet Protocol Version6的缩写其中Internet Protocol译为“互联网协议”。IPv6是IETF(互联网工程任务组Internet Engineering Task Force)设计的用于替代现行版本IP协议(IPv4)的下一代IP协议。采用“冒分十六进制表示如2031:0000:1F1F:0000:0000:0100:11A0:ADDF而IPv6中IP地址的长度为128即有2^128-1个地址号称能够为“地球上每一粒沙子分配一个IP地址” IPv6支持测试http://test-ipv6.com/ 上海交通大学IPv6站http:/ipv6.sjtu.edu.cn/ IP地址查看 虚拟机网卡设置 NAT网络地址转换模式则虚拟机会使用主机VMnet8这块虚拟网卡与我们的真实机进行通信虚拟机与物理主机公用网络。 Bridged桥接模式虚拟机如同一台真实存在的计算机在内网中获取和真实主机同网段IP地址。优点不需要任何设置虚拟机就可以直接和我们真实主机通信缺点虚拟机需要占用真实机网段的一个IP虚拟机能够获取局域网的IP地址。 Ubuntu虚拟机网络设置如下 查看或配置网卡信息 ifconfig 如果我们只是敲ifconfig它会显示所有网卡的信息 查看Window ip地址【Win键R】打开运行 输入“cmd”打开终端窗口 输入命令ipconfig 测试远程主机联通性能 ping 通常用ping换检测网络是否正常或者某台主机是否可以连接。
端口
端口 端口是英文port的意译可以认为是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口其中虚拟端口指计算机内部或交换机路由器内的端口不可见。例如计算机中的80端口、21端口、23端口等。 端口号 端口是通过端口号来标记的端口号只有整数范围是从0到65535。 端口分配 端口号不是随意使用的而是按照一定的规定进行分配。 端口的分类标准有好几种我们这里不做详细讲解只介绍一下知名端口和动态端口 知名端口 知名端扣是众所周知的端口号范围从0~1023。一般情况下如果一个程序需要使用知名端口的需要有root权限。 常见协议默认端口号协议基本作用FTP21文件上传、下载SSH22安全的远程登录TELNET23远程登录SMTP25邮件传输DNS53域名解析HTTP80超文本传输POP3110邮件接收HTTPS443加密传输的HTTPS 动态端口 动态端口的范围是从1024~65535。 之所以称为动态端口是因为它一般不固定分配某种服务而是动态分配。动态分配是指当一个系统程序或应用程序程序需要网络通信时它向主机申请一个端口主机从可用的端口号中分配一个供它使用。当这个程序关闭时同时也就释放了所占用的端口号。 查看端口netstat netstat命令是一个监控TCP/IP网络的非常有用的工具可以显示网络连接、路由表和网络接口信息可以让用户得知目前都有哪些网络连接正在运作。 -a或–a显示所有连线中的Socket -A网络类型或–网络类型列出该网络类型连线中的相关地址 -c或-continuous持续列出网络状态 -C或–cache显示路由器配置的快取信息 -e或–extend:显示网络其他相关信息 -F或–fib显示FIB -g或–groups显示多重广播功能群组组员名单 -h或–help在线帮助 -i或–interfaces显示网络界面信息表单 -l或–listening显示监控中的服务器的Socket -H或–masquerade显示伪装的网路连线 -n或–numeric直援使用ip地址而不通过域名服务器 -N或–netlink或–symbolic显示网络硬件外国设备的符号连接名称 -o或–timers显示计时器 -p或–programs显示正在使用Socket的程序识别码和程序名称 -r或–route显示Routing Table -s或–statistice显示网络工作信息统计表 -t或–tcp显示TCP传输协议的连线状况 -u或–udp显示UDP传输协议的连线状况 -v或–verbose显示指令执行过程 -V或–version显示版本信息 -w或–raw显示RAW传输协议的连线状况 -x或–unix此参数的效果和指定-A unix参数相同 –ip或–inet此参数的效果和指定”-A inet“参数相同 查看所有端口状态netstat -an 查询所有含有21的端口使用情况 netstat -an | grep 21 查看端口号被哪个程序占用 lsof -i [tcp/udp]:端口号 查看服务器socketnetstat -ntl
网络-udp/tcp
网络传输方式UDP\TCP)
网络通信中根据数据发送方法进行多种分类分类方法主要分为两种面向无连接型、面向有连接型
面向无连接型 不要求建立和断开连接。发送端可于任何时候自由发送数据。反之接收端也永远不知道自己会在何时从那里接收到数据。因此面向无连接的情况下接收端需要时常确认是否收到了数据。 在面向无连接的通信中不需要确认对端是否存在即使接收端不存在或无法接受数据发送端也能将数据发送出去。 UDP协议用户数据报协议 (User Datagram Protocol)不提供复杂的控制机制如果传输过程中出现丢包UDP也不负责重发甚至当出现包到达顺序乱掉时候也没有纠正的功能。由于UDP面向无连接它可以随时发送数据。再加上UDP本身的处理既简单又高效因此常用于以下几个方面 包总量较少的通信(DNS) 视频、音频等多媒体通信即时通信 限定于LAN等特定网络中的应用通信 广播通信广播、多播 选择UDP必须要谨慎在网络质量令人十分不满意的环境下UDP协议数据包丢失会比较严重。但是由于UDP的特性它不属于连接型协议因而具有资源消耗小处理速度快的优点。所以通常音频、视频和普通数据在传送时使用UDP较多因为它们即使偶尔丢失一两个数据包也不会对接收结果产生太大影响。 面向有连接型 面向有连接型中在发送数据之前需要在收发主机之间建立一条连接通信线路。面向连接就好像我们平时打电话输入完对方的电话号码拨出之后只有对方拿起电话确认连接才能进行真正的通话通话结束后将电话机扣上就如同切断电源。因此在面向有连接的方式下必须在通信传输前后专门进行建立和断开连接的处理。 TCP协议传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP提供一种面向连接的通信服务只有在确认通信对端存在时才会收发数据从而可以控制通信流量的浪费。TCP提供了数据传输时的各种控制功能丢包时可以进行重发控制还可以将次序乱掉的分包进行顺序控制。
UDP与TCP两者区别
UDPTCP面向无连接不可靠的数据流传输面向连接可靠的数据流传输尽最大努力交付即不保证可靠交付提供可靠的服务通过TCP连接传送的数据无差错、不丢失、不重复、按序到达面向报文面向字节流支持一对一、一对多、多对一合多对多的交互通信只能是点到点的
socket简介 什么是socket socket简称套接字是支持TCP/IP的网络通信的基本操作单元提供的方法可以实现数据的发送和接收。 它能实现不同主机间的进程间通信我们网络上各种各样的服务大多都是基于Socket来完成通信的例如我们每天浏览网页、QQ聊天、收发email等等。 创建socket socket起源于Unix而Unix/Linux基本哲学之一就是一切皆文件”对于文件用【打开】【读写】【关闭】模式来操作socket就是该模式的一个实现socket即是一种特殊的文件一些socket类就是对其进行的操作(读/写IO、打开、关闭) 在Python中使用socket模块的socket类可以完成 import socket
socket.socket(AddressFamily, Type)说明
类socket.socket创建一个socket该类实例化时需要两个参数返回socket对象
参数一AddressFamily地址簇
socket.AF_INET IPv4(默认)、socket.AF_INET6 IPv6
socket.AF_UNIX只能够用于单一的Unix系统进程间通信
参数二Type类型
socket.SOCK_STREAM流式socketfor TCP默认
socket.SOCK_DGRAM数据报式socketfor UDP
--------
socket.SOCK_RAW原始套接字普通的套接字无法处理ICMP、IGMP等网络报文而SOCK_RAW可以其次SOCK_RAW也可以处理特殊的IPv4报文此外利用原始套接字可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM是一种可靠的UDP形式即保证交付数据报但不保证顺序。SOCK RAM用来提供对原始协议的低级访问在需要执行某些特殊操作时使用如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET可靠的连续数据包服务。 1. 导入模块 socket
2. 创建套接字使用IPv4 UDP/TCP方式
3. 数据的传递
4. 关闭套接字
# 1. 导入模块 socket
import socket# 2. 创建套接字使用用IPv4 UDP方式
# 参数一socket.AF_INET 使用IPv4 socket.AF_INET6 使用IPv6
# 参数二 socket.SOCK_DGRAM 使用UDP的传输方式无连接socket.SOCK_STREAM 使用TCP的传输方式有连接
udp_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 3 数据的传递# 4 关闭套接字
udp_socket.close()udp网络程序-发送、接收数据 1. 导入模块socket
2. 创建socket套接字
3. 发送数据
4. 接收数据二进制
5. 解码数据得到字符串
6. 输出显示接收到的内容
7. 关闭套接字
# 1. 导入模块socket
import socket# 2. 创建套接字UDP)参数说明
1. socket.AF_INET 表示IPv4地址
2. socket.SOCK_DGRAM 表示使用UDP协议传输数据无连接udp_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 3 发送数据 sendto()参数说明
1. 要发送的二进制数据字符串转换为二进制格式字符串.encode()
2. 元组类型(字符串类型的ip地址, 整数类型的端口号)指定把参数1的数据发送给谁udp_socket.sendto(helloworld.encode(), (192.168.150.30, 8080))# 4 接收数据二进制recvfrom()recvfrom(1024)方法的作用
1. 从套接字中接收1024个字节的数据
2. 此方法会造成程序的阻塞等待另外一台计算机发来的数据。如果对方发数据了recvfrom会自动解除阻塞如果对方未发送数据会一直等待。
返回数组(接收到的数据的二进制格式, (对方的ip地址, 端口号))recv_data udp_socket.recvfrom(1024)
# print(recv_data[0])# 5 解码数据得到字符串。二进制转换为字符串二进制.decode(gbk)
recv_text recv_data[0].decode(gbk)# 6 输出显示接收到的内容
print(来自, recv_data[1], 的消息, recv_text)
# 7 关闭套接字
udp_socket.close()python3编码转换 编码和解码 文本总是Unicode由str类型进行表示二进制数据使用bytes进行表示。 网络中数据的传输是以**二进制字节码**的方式来进行的所以我们需要通过对Unicode字符串内容进行编码和解码才能达到数据传输的目的。 在Python中 strbytes: encode编码编码就是将字符串转换成字节码涉及到字符串的内部表示bytesstr: decode解码解码就是将字节码转换为字符串将比特位显示成字符 其中encode()与decode()方法可以接收参数其声明分别为 # 编码
字符串.encode(encodingutf-8, errorsstrict)
# 解码
bytes.decode(encodingutf-8, errorsignore)其中的encoding是指在编码解码过程中使用的编码默认为UTF-8errors是指错误的处理方案有strict和ignore两种模式默认为strict模式。 str 你好
bytes str.encode()
print(bytes) # b\xe4\xbd\xa0\xe5\xa5\xbd
str2 bytes.decode()
print(str2) # 你好udp端口绑定 udp网络程序 - 会变得端口号 重新运行多次脚本然后在“网络词试助手”中看到的现象如下 说明 1每重新运行一次网络程序上图中红圈中的数字不一样的原因在于这个数字标识这个网络程序当重新运行时如果没有确定到底用哪个系统默认会随机分配。 2记住一点这个网络程序在运行的过程中这个就唯一标识这个程序所以如果其他电脑上的网络程序如果想要向此程序发送数据那么就需要向这个数字即端口标识的程序发送即可。 udp绑定信息 一般情况下在一台电脑上运行的网络程序有很多为了不与其他的网络程序占用同一个端口号往往在编程中udp的端口号一般不绑定。但是如果需要做成一个服务器端的程序的话是需要绑定的。想想看这又是为什么呢如果报警电话每天都在变想必世界就会乱了所以一般服务性的程序往往需要一个固定的端口号这就是所谓的端口绑定。 方法socket.bind((ip, port)) 注意将socket对象绑定到一个地址但这个地址必须是没有被占用的否则会连接失败。 实例代码 udp接口绑定发送端 # 1 导入模块
import socket
# 2 创建套接字
udp_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3 绑定端口:socket.bind(address)
# address是一个元组元组第一个元素是字符串类型的IP地址第二个元素是整数端口号
# ip地址可以省略省略后表示自己的ip地址
#udp_socket.bind((192.168.150.25, 8888))
udp_socket.bind((, 8888))
# 4 发送数据
udp_socket.sendto(hello.encode(), (192.168.150.30, 8080))
# 5 关闭套接字
udp_socket.close()udp接口绑定接收端 # 1 导入模块
from socket import *
# 2 创建套接字
udp_socket socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3 绑定端口:socket.bind(address)
# address是一个元组元组第一个元素是字符串类型的IP地址第二个元素是整数端口号
# ip地址尽可能写成“”好处当计算机有多个网卡的时候不同网卡的数据都能被接收
#udp_socket.bind((127.0.0.1, 8888))
udp_socket.bind((, 8888))
# 4 接收对方发送的数据
recv_data, ip_port udp_socket.recvfrom(1024)
# 5 解码数据
recv_text recv_data.decode()
# 6 输出显示
print(接收[%s]的信息%s % (str(ip_port), recv_text))
# 7 关闭套接字
udp_socket.close()udp广播
使用UDP的方式发送广播广播地址(Broadcast Address)是专门用于同时向网络中所有工作站进行发送的一个地址。在使用TCP/IP协议的网络中主机标识段host ID为全1的IP地址为广播地址。 IP地址的网络字段和主机字段全为1就是地址255.255.255.255所以向255.255.255.255发送信息就是发送广播消息。
# 1 导入模块
import socket
# 2 创建套接字
udp_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3 设置广播权限套接字默认不允许发送广播需要开启相关权限否则报错PermissionError
# udp_socket.setsockopt(套接字 属性 属性值): socket.SOL_SOCKET表示当前的套接字socket.SO_BROADCAST表示广播属性
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
# 4 发送数据
udp_socket.sendto(Hello.encode(), (255.255.255.255, 8080))
# 5 关闭套接字
udp_socket.close()udp聊天室
说明 在一个电脑中编写1个程序有3个功能1、获取键盘数据并得其发送给对方2、接收数据并显示3、退出聊天系统 思路分析
功能1、发送信息2、接收信息3、退出系统架构
1、发送信息send_msg()
2、接收信息recv_msg()
3、主入口main()
4、当程序独立运行的时候才启动聊天器实现步骤
1、发送信息 send_msg()
1定义变量接收用户输入的接收方的IP地址
2定义变量接收用户输入的接收方的端口号
3定义变量接收用户输入的发送给接收方的内容
4使用socket的sendto()发送信息2、接收信息recv_msg()
1使用socket的recvfrom()接收数据
2解码数据并输出显示3、 主入口main()
1创建套接字
2绑定端口
3打印菜单循环
4接收用户输入的选项
5判断用户的选择并且调用对应的函数
6关闭套接字代码实现
import socket
# 1、发送信息 send_msg()
def send_msg(udp_socket):发送信息# 1定义变量接收用户输入的接收方的IP地址ipaddr input(请输入接收方的IP地址\n)# 判断是否需要默认if len(ipaddr) 0:ipaddr 192.168.150.93print(当前接收方默认IP设置为[%s] % ipaddr)# 2定义变量接收用户输入的接收方的端口号port input(请输入接收方的端口号\n)if len(port) 0:port 8080print(当前接收方默认端口设置为[%s] % port)# 3定义变量接收用户输入的发送给接收方的内容content input(请输入要发送的内容\n)# 4使用socket的sendto()发送信息udp_socket.sendto(content.encode(), (ipaddr, int(port)))# 2、接收信息recv_msg()
def recv_msg(udp_socket):接收信息# 1使用socket的recvfrom()接收数据recv_data, ip_port udp_socket.recvfrom(1024)# 2解码数据recv_text recv_data.decode()# 3并输出显示print(接收到[%s]的消息%s % (str(ip_port), recv_text))# 3、 主入口main()
def main():程序的主入口# 1创建套接字udp_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 2绑定端口udp_socket.bind((, 8080))# 3打印菜单循环while True:print(***************************)print(****** 1、发送信息 ********)print(****** 2、接收信息 ********)print(****** 3、退出系统 ********)# 4接收用户输入的选项sel_num int(input(请输入选项\n))# 5判断用户的选择并且调用对应的函数if sel_num 1:print(您选择的是发送信息)send_msg(udp_socket)elif sel_num 2:print(您选择的是接收信息)recv_msg(udp_socket)elif sel_num 3:print(系统正在退出中...)print(系统退出完成)breakelse:print(输入有误请重新输入)# 6关闭套接字udp_socket.close()if __name__ __main__:程序独立运行的时候才去启动聊天室main()TCP简介 TCP介绍 TCP协议传输控制协议(英语Transmission Control Protocol缩写为TCP) 是一种面向连接的、可靠的、基于字节流的传输层通信协议由IETF的RFC 793定义。 TCP通信需要经过创建连接、数据传送、终止连接三个步骤。 TCP通信模型中在通信开始之前一定要先建立相关的链接才能发送数据类似于生活中“打电话”。 TCP特点 面向连接 通信双方必须先建立连接才能进行数据的传输双方都必须为该连接分配必要的系统内核资源以管理连接的状态和连接上的传输。 双方间的数据传输都可以通过这一个连接进行。 完成数据交换后双方必须断开此连接以释放系统资源。 这种连接是一对一的因此TCP不适用于广播的应用程序基于广播的应用程序请使用UDP协议。可靠传输 a. TCP采用发送应答机制 TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功。 b. 超时重传 发送端发出一个报文段之后就后动定时器如果在定时时间内没有收到应答就重新发送这个报文段。 TCP为了保证不发生丢包就给每个包一个序号同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK)如果发送端实体在合理的往返时延(RTT)内未收到确认那么对应的数据包就被假设为已丢失将会被进行重传。 c. 错误校验顺序校验、去除重复 TCP用一个校验和函数来检验数据是否有错误在发送和接收时都要计算校验。 d. 流量控制和阻塞管理 流量控制用来避免主机发送得过快而使接收方来不及完全收下。 TCP与UDP的不同点 面向连接(确认有创建三方交握连接已创建才作传输) 有序数据传输 重发丢失的数据包 舍弃重复的数据包 无差错的数据传输 阻塞/流量控制 TCP编程的服务器端一般步骤是
1. 创建一个socket用函数socket()
2. 设置socket属性用函数setsockopt(); 可选
3. 绑定IP地址、端口等信息到socket上用函数bind();
4. 开始监听用listen()
5. 接收客户端上来的连接用函数accept()
6. 收发数据用函数send()和recv()
7. 关闭网络连接
8. 关闭监听TCP编程的客户端一般步骤是
1创建一个socket用函数socket():
2设置socket属性用函数setsockopt(); 可选
3绑定IP地址、端口等信息到socket上用函数bind()可选
4设置要连接的对方的IP地址和端口等属性
5连接服务器用函数connect()
6收发数据用函数send()和recv()
7关闭网络连接与之对应的UDP编程步骤要简单许多分别如下
UDP编程的服务器端一般步骤是
1. 创建一个socket用函数socket()
2. 设置socket属性用函数setsockopt()可选
3. 绑定IP地址、端口等信息到socket上用函数bind()
4. 循环接收数据用recvfrom()
5. 关闭网络连接UDP编程的客户端一般步强是
1创建一个socket用函数socket()
2设置socket属性用函数setsockopt()可选
3绑定IP地址、端口等信息到socket上用函数bind()可选
4设置对方的IP地址和端口等属性
5发送数据用函数sendto()
6关闭网络连接UDP通信模型 udp通信模型中在通信开始之前不需要建立相关的链接只需要发送数据即可类似于生活中的“写信。 TCP通信模型——严格区分客户端、服务端 TCP通信模型中在通信开始之前需要建立相关的链接类似于生活中的“打电话。
TCP客户端
tcp的客户满要比服务器端简单很多tcp和服务器建立连接后直接发送数据。 1. 导入模块socket
2. 创建socket套接字
3. 建立tcp连接和服务端建立连接
4. 开始发送数据到服务端
5. 接受数据
6. 关闭套接字
# 1. 导入模块socket
import socket
# 2. 创建socket套接字
tcp_client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM TCP传输方式SOCK_DGRAM UDP传输方式
# 3. 建立tcp连接 connect((ip, port))
tcp_client_socket.connect((192.168.150.71, 8080))
# 4. 开始发送数据到服务端send(内容.encode())
tcp_client_socket.send(hello.encode())
# 5. 接受数据 recv(bufsize) -- 接收TCP数据数据以二进制形式返回bufsize缓冲区大小指定要接收的最大数据量1024
recv_data tcp_client_socket.recv(1024)
recv_text recv_data.decode(GBK) # 解码
print(收到数据, recv_text) # 输出显示
# 6. 关闭套接字
tcp_client_socket.close()TCP服务器端
TCP服务器和客户端建立连接后接收/发送数据给客户端。 1. 导入模块socket
2. 创建套接字
3. bind绑定ip和port
4. listen开启监听(设置套接字为被动模式
5. accept等待客户端的连接
6. recv/send接收/发送数据
7. 关闭连接
# 1. 导入模块socket
import socket
# 2. 创建套接字
tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3. bind绑定ip和port
tcp_server_socket.bind((, 8080))
# 4. listen开启监听(设置套接字为被动模式
# listen() 作用设置tcp_server_socket套接字为被动监听模式不能在主动发送数据
# 128允许接受的最大的连接数在windows 128有效最大允许128个连接但是在linux此数字无效
tcp_server_socket.listen(128)
while True: #可以接受多个客户端连接注意必须等待第一个客户端断开后第二个客户端才能有机会连接。# 5. accept等待客户端的连接# accept()开始接收客户端连接程序会默认进入阻塞状态等待客户端连接如果由客户端连接后程序自动解除阻塞# accept()返回的数据含有两部分1. 返回一个新的套接字socket对象只是服务当前的客户端2. 客户端的ip地址和端口号 元组new_client_socket, client_ip_port tcp_server_socket.accept()print(新客户端来了%s % str(client_ip_port))# 6. recv/send接收/发送数据while True: # 可以接受客户端发来的多条信息recv_data new_client_socket.recv(1024) # 使用新的套接字接受客户端发送的信息if recv_data: # 如果recv_data非空即为真否则为假recv_text recv_data.decode(GBK)print(接收到[%s]的信息%s % (str(client_ip_port), recv_text))else:print(客户端已经断开连接)break# 7. 关闭连接new_client_socket.close() # 表示不能再和当前的客户端通信了
# tcp_server_socket.close() # 表示程序不再接受新的客户端连接已经连接的可以继续服务案例文件下载器 功能分析 实现代码 TCP客户端
目标
将/home/demo/Document/python_projects/1.txt下载到/home/demo/Desktop/1.txt
客户端实现思路
1. 导入模块
2. 创建套接字
3. 建立和服务器的连接
4. 接受用户输入的文件名
5. 发送文件名到服务器端
6. 创建文件并且准备保存
7. 接受服务器发送的文件数据保存到本地循环
8. 关闭套接字
# 1. 导入模块
import socket
# 2. 创建套接字
tcp_client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3. 建立和服务器的连接
tcp_client_socket.connect((192.168.150.71, 8080))
# 4. 接受用户输入的文件名
file_name input(请输入要下载的文件名\n)
# 5. 发送文件名到服务器端
tcp_client_socket.send(file_name.encode())
# 6. 创建文件并且准备保存
with open(/home/demo/Desktop/file_name, wb) as file:# 7. 接受服务器发送的文件数据保存到本地循环while True:file_data tcp_client_socket.recv(1024)if file_data: # 判读数据是否传送完毕file.write(file_data)else:break
# 8. 关闭套接字
tcp_client_socket.close()TCP服务端
客户端实现思路
1. 导入模块
2. 创建套接字
3. 绑定地址和端口
4. 开始监听设置套接字由主动为被动监听模式
5. 等待客户端连接如果有新客户端连接会创建新的套接字
6. 接受客户端发来的文件名
7. 根据文件名读取文件数据
8. 把读取的文件数据发送给客户端循环
9. 关闭和当前客户端的连接
10. 关闭套接字
# 1. 导入模块
import socket
# 2. 创建套接字
tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3. 绑定地址和端口
tcp_server_socket.bind((, 8080))
# 4. 开始监听设置套接字由主动为被动监听模式
tcp_server_socket.listen(128)
# 5. 等待客户端连接如果有新客户端连接会创建新的套接字
while True: # 存在问题多个客户端下载是同步的必须是一个下载完成后另外一个客户端才能连接下载new_client_socket, ip_port tcp_server_socket.accept()print(欢迎新客户端, ip_port)# 6. 接受客户端发来的文件名recv_data new_client_socket.recv(1024)file_name recv_data.decode()try:# 7. 根据文件名读取文件数据with open(file_name, rb) as file:# 8. 把读取的文件数据发送给客户端循环while True:file_data file.read(1024)if file_data: # 判断是否读到了文件的末尾new_client_socket.sendto(file_data)else:breakexcept Exception as e:print(文件%s下载失败 % file_name)else:print(文件%s下载成功 % file_name)# 9. 关闭和当前客户端的连接new_client_socket.close()
# 10. 关闭套接字
# tcp_server_socket.close()TCP的3次握手
概念 所谓三次握手(Three-Way Handshake)即建立TCP连接就是指建立一个TCP连接时需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中这一过程由客户端执行connect来触发。 整体流程如下 (1) 第一次握手Client将标志位SYN连接请求置为1随机产生一个值seqJ报文序号并将该数据包发送给ServerClient进入SYN_SENT状态等待Server确认。 (2) 第二次握手Server收到数据包后由标志位SYN1知道Client请求建立连接Server将标志位SYN和ACK确认都置为1ackJ1确认号随机产生一个值seqK并将该数据包发送给Client以确认连接请求Server进入SYN_RCVD状态。 (3) 第三次握手Client收到确认后检查ack是否为J1ACK是否为1如果正确则将标志位ACK置为1ackK1并将该数据包发送给ServerServer检查ack是否为K1ACK是否为1如果正确则连接建立成功Client和Server进入ESTABLISHED状态完成三次握手随后Client与Server之间可以开始传输数据了。 常见面试题 问 为什么需要三次握手两次不可以吗或者四次、五次可以吗 答 我们来分析一种特殊情况假设客户端请求建立连接发给服务器SYN包等待服务器确认服务器收到确认后如果是两次握手假设服务器给客户端在第二次握手时发送数据数据从服务器发出服务器认为连接已经建立但在发送数据的过程中数据丢失客户端认为连接没有建立会进行重传。假设每次发送的数据一直在丢失客户端一直SYN服务器就会产生多个无效连接占用资源这个时候服务器可能会挂掉。这个现象就是我们听过的“SYN的洪水攻击”。 总结 第三次握手是为了防止如果客户端迟迟没有收到服务器返回确认报文这时会放弃连接重新启动一条连接请求但问题是服务器不知道客户端没有收到所以他会收到两个连接浪费连接 开销。如果每次都是这样就会浪费多个连接开销。 TCP的4次挥手
TCP的4次挥手主要是说TCP断开连接的时候。 (1) 第一次挥手主机1可以是客户端也可以是服务器端设置Sequence Number和Acknowledgment Number向主机2发送一个FIN关闭连接报文段此时主机1进入FIN_WAIT1状态这表示主机1没有数据要发送给主机2了 (2) 第二次挥手主机2收到了主机1发送的FIN报文段向主机1回一个ACK确认报文段Acknowledgment Number为Sequence Number加1主机1进入FIN_WAIT2状态主机2告诉主机1我也没有数据要发送了可以进行关闭连接了 (3) 第三次挥手主机2向主机1发送FIN报文段请求关闭连接同时主机2进入CLOSE_WAIT状态 (4) 第四次挥手主机1收到主机2发送的FIN报文段向主机2发送ACK报文段然后主机1进入TIME_WAIT状态主机2收到主机1的ACK报文段以后就关闭连接此时主机1等待2MSL后依然没有收到回复则证明Server端已正常关闭那好主机1也可以关闭连接了。
三次握手、四次挥手完整图 问题 为什么TIME_WAIT状态需要经过2MSL最大报文段生存时间才能返回到CLOSE状态 首先MSL即Maximum Segnment Lifetime, 就是最大报文生存时间是任何报文再网络上的存在的最长时间超过这个时间的报文将被丢弃。MSL是任何报文段被丢弃前在网络内的最长时间MSL规定为2分钟实际应用中常用的是30秒、1分钟、2分钟等。 TCP的TIME_WAIT需要等待2MSL当TCP的一端发送主动关闭三次挥手完成后发送第四次挥手的ACK包就进入这个状态等待2MSL时间主要目的是防止最后一个ACK包对方没有收到那么对方在超时后将重发第三次挥手的FIN包主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的接口不能使用要等到2MSL时间结束才可以继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。client发送完最后一个ack之后进入time_wait状态但是他怎么知道server有没有收到这个ack呢莫非server也要等待一段时间如果收到了这个ack就close如果没有收到就再发一个fin给client?这么说server最后也有一个time_wait哦求解答 因为网络原因主动关闭的一方发送的这个ACK包很可能延迟从而触发被动连接一方重传FIN包。极端情况下这一去一回就是两倍的MSL时长。如果主动关闭的一方跳过TIME_WAIT直接进人CLOSED或者在TIME_WAIT停留的时长不足两倍的MSL那么当被动关闭的一方早先发出的延迟包到达后就可能出现类似下面的问题1. 旧的TCP连接已经不存在了系统此时只能返回RST包。2. 新的TCP连接被建立起采了延迟包可能干扰新的连接这就是为什么time_wait需要等待2MSLB时长的原因。为什么连接的时候是三次握手关闭的时候却是四次挥手 因为当Server端收到Client端的SYN连接请求报文后可以直接发送SYNACK报文。其中ACK报文是用来应答的SYN报文是用来同步的。但是关闭连接时当Server端收到FIN报文时很可能并不会立即关闭SOCKET所以只能先回复一个ACK报文告诉Client端“你发的FIN报文我收到了”只有等到我Server端所有的报文都发送完了我才能发送FIN报文因此不能一起发送。故需要四步挥手。 浏览器访问服务器的过程——IP地址、域名、DNS IP地址 IP的全称是Internet Protocol Address互联网协议地址就是网络地址。IP地址与我们的身份证一 样都具有唯一性。 网络不分国界的全球范国内的所有主机都有一个“身份证号”就是IP地址不能相同。 IP地址分为IPv4和IPV6IP地址是由32位二进制构成分成四段每段8位二进制。 在现实中我们用“点分十进制”来表示形如“a.b.c.d”形式表示每一段的取值范围是0-255。举例192.168.2.234 特殊的IP地址127.0.0.1每台电脑都有是电脑内部的IP地址。 127.0.0.1代表自己的内部的IP地址永远都是自己访问自己外网无法访问。 域名 概念 域名简称DN(全称Domain Name)域名可以理解为是一个网址就是一个特殊的名字。 为什么要有域名 互联网上的每台主机都有一个唯一的IP地址但是IP地址不方便记忆因此才有了域名。 域名的构成由字母、数字、中划线-长度不超过255个字符。 例如www.sina.com.cn、www.baidu.com、www.hao123.com其中.com称为J顶级域名。 常见的顶级域名 域名备注.com用于商业机构。它是最常见的顶级域名。任何人都可以注册.COM形式的域名.cn中国专用的顶级域名.gov国内域名政府、企事业单位常见域名.org是为各种组织包括非盈利组织而定的任何人都可以注册以.ORG结尾的域名.net最初是用于网络组织例如因特网服务商和维修商。任何人都可以注册以.NET结尾的域名.com.cn国内常见二级域名 localhost是个特殊域名不是地址它可以被配置为任意的IP地址。 不过通常情况下都指向127.0.0.1(ipv4)和::1永远都只能自己访问自己不能访问其它人的localhost 域名。 DNS服务器 DNS(Domain Name System 域名解析系统)主要用来将域名转成对应的IP地址。 DNS是一台运行在互联网上的服务器。 直白理解DNS服务器就可以看做是一个通讯录(姓名域名电话ip地址) 电脑之间的互访只能识别IP地址的访问不识别域名的访问。 本地DNS: 本地DNS服务器是一个文件hosts。hosts是本地的DNSDNS中就是IP地址和域名的对应关系表。 hosts文件是隐藏文件、系统文件、没有扩展名的文件。 Hosts文件路径 windosC:\Windows\System32\drivers\etc linux: /etc/hosts注意linux下修改hosts后需要重层网络命令为/etc/init.d/networking restart 浏览器请求的基本流程如下 当我们在浏览器中输入网址访问网站后服务器会返回HTML标记给浏览器浏览器负责渲染展现出来。浏览器输入网址本地DNS服务器查询远程DNS服务器建立TCP连接
HTTP协议 简介 超文本传输协议HTTPHyperTextTransferProtocol 是互联网上应用最为广泛的一种网络协议所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。 HTTP是一个客户端和服务器端请求和应答的标准TCP。客户端是终端用户服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具客户端发起一个到服务器上指定端口默认端口为80的HTTP请求。 超文本传输协议是一种应用层协议。 链路层数据链路层/网络接口层包括操作系统中的设备驱动程序、计算机中对应的网络接口卡。网络层处理分组在网络中的活动比如分组的选路。运输层主要为两台主机上的应用提供端到端的通信。应用层负责处理特定的应用程序细节。 请求request-响应response式模式 所以http协议分为两个部分请求协议、响应协议 不管是请求还是响应其实http协议都是由一个一个的简单的协议项组成的形式如下 协议名协议内容值比如Host: www.itcast.cn 注意每一个协议项都单独的占用一行 HTTP协议格式查看 在web应用中服务器把网页传给浏览器实际就是把网页的HTML代码发送给浏览器浏览器解析显示出来。而浏览器和服务器之间的传输应用层协议就是HTTP所以 HTML是一种用来定义网页的文本会HTML就可以编写网页。HTTP是用来在网络上传输HTML文本的协议用于浏览器和服务器的通信。 HTTP协议报文格式查看 Windows/Linux平台按F12调出开发者工具MAC选择视图开发者工具
HTTP请求协议分析
http请求又包含了四个部分 1. 请求行request-line 2. 请求头request-header 3. 空行 4. 请求数据request-content也叫作请求内容或者请求主体 1请求行请求方式 资源路劲 协议及版本\r\n 请求行又可以分成三个部分请求方式 请求路径 协议版本 其中GET就是请求方式/model/list_father.php就是请求路径HTTP/1.1就是协议版本号。 注意 1. 请求行需要单独的占一行用来说明当前请求的最基本的信息 2. 请求路径是不包括域名的 3. HTTP协议以前是1.0版本现在是1.1版本
2请求头协议名协议值\r\n 请求头就是所有当前需要用到的协议项的集合 协议项就是浏览器在请求服务器的时候事先告诉服务器的一些信息或者一些事先的约定 常见的请求头有
请求头说明Host当前url所要请求的服务器的主机名域名Accept-Encoding是浏览器发给服务器声明浏览器支持的压缩编码类型比如gzipAccept-Language可以接收的语言类型cn、en有权重的高低之分Referer表示此次请求来自哪个网址Accept-Charset表示浏览器支持的字符集Cookie如果之前当前请求的服务器在浏览器端设置了数据(cookie)那么当前浏览器再次请求该服务器的时候就会把对应的数据带过去User-Agent用户代理当前发起请求的浏览器的内核信息Accept表示浏览器可以接收的据类型text/html、image/imgContent-Length只有post提交的时候才会有的请求头显示的是当前要提交的数据的长度字节If-Modified-Since(get)表示在客户端向服务器请求某个资源文件时询问此资源文件是否被修改过
3空行 就是用来分离请求头和请求数据意思就是请求头到此结束 4请求数据 只有post方式提交的时候才有请求数据是浏览器要发送给服务器端的内容。
请求报文格式总结
HTTP响应报文协议分析
http响应也分成了四个部分 1、响应行状态行 2、响应头 3、空行 4、响应主体响应数据
1响应行 第一行HTTP/1.1 200 OK叫做响应行共分成3部分协议版本号 状态码 状态描述 注意 状态码和状态描速是一一对应的 状态代码有三位应数字组成第一个数字定义了响应的类别且有五种可能取值 1xx指示信息–表示请求已接收继续数处理 2xx成功–表示请求已被成功接收、理解、接受 3xx重定向–要完成请求必须进行更进一步的操作 4xx客户端错误–请求有语法错误或请求无法实现 5xx服务器端错误–服务器未能实现合法的请求。 常见的状态码
状态码含义200OK请求已成功。302Move temporarilyi请求的资源临时从不同的URL响应请求。由于这样的重定向是在时的客户端应当继续向原有地址发送以后的请求304Not Modified文档的内容自上次访问以来或者根据请求的条件并没有改变400Bad Reques语义有误当前请求无法被服务器理解401Unauthorized当前请求需要用户验证403Forbidden服务器收到请求但是拒绝提供此服务404Not Found请求资源不存在408Request Timeouti请求超时500Internal Server Error服务器发生不可预知的错误503Server Unavailable服务器当前不能处理客户端的请求一段时间后可能恢复正常
2响应头 也是一些协议的集合也是协议名值的形式 常见的有
响应头说明Server服务器主机信息Date响应时间Last-Modified文件最后修改时间Content-Length响应主体的长度字节Content-Type响应内容的数据类型text/html, image/png等Location重定向浏览器遇到这个选项就立马跳转不会解析后面的内容Refresh重定向刷新浏览器遇到这个选项就会准备跳转刷新一般有时间限制时间到了才跳转浏览器会继续向下解析Content-Encoding文件编码格式Cache-Control缓存控制no-cached不要缓存
3空行 用来分割响应头与响应主体也就是响应头到此结束 4响应主体 就是服务器反馈给浏览器的数据 响应报文格式总结
长连接和短连接 在HTTP/1.0中默认使用的是短连接也就是说浏览器和服务器每进行一次HTTP操作就建立一次连接但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源如js文件、图像文件、CSS文件等当浏览器每遇到这样一个Web资源就会建立一个HTTP会话。 但从HTTP/1.1起默认使用长连接用以保持连接特性。使用长连接的HTTP协议会在响应头有加入这行代码Connection: keep-alive 在真正的读写操作之前server与client之间必须建立一个连接 当读写操作完成后双方不再需要这个连接时它们可以释放这个连接 连接的建立通过三次握手释放则需要四次挥手 所以说每个连接的建立都是需要资源消耗和时间消耗的。 TCP短连接 概述一次连接一次传输就关闭。 特点会频繁的建立和断开连接当瞬间访问压力比较大的时候服务器响应过慢。 client向server发起连接请求server接到请求双方建立连接client向server发送消息server回应client一次读写完成此时双方任何一个都可以发起close操作 在步骤5中一般都是client先发起close操作。当然也不排除有特殊的情况。从上面的描述看短连接一般只会在client/server间传递一次读写操作 TCP长连接 概述一次连接多次数据传输通信结束关闭连接。 特点要不连不上一旦连上速度有保证当瞬间访问压力比较大的时候服务器不可用。 client向server发起连接server接到请求双方建立连接client向server发送消息server回应client一次读写完成连接不关闭后续读写操作…长时间操作之后client发起关闭请求 TCP长/短连接的优缺点 1长连接可以省去较多的TCP建立和关闭的作节约时间。但是如果用户量太大容易造成服务器负载过高最终导致服务不可用。 2短连接对于服务器来说实现起来较为简单存在的连接都是有用的连接不需要额外的控制手段。但是如果用户访问量很大往往可能在很短时间内需要创建大量的连接造成服务器响应速度过慢。 总结 1小的WEB网站的http服务一般都用短链接因为长连接对于服务端来说会耗费一定的资源来让套接字保持存活-keep alive 2对于中大型WEB网站一般都采用长连接好处是响应用户请求的时间更短用户体验更好虽然更耗硬件资源一些但这都不是事儿。另外数据库的连接用长连接如果用短连接频繁的通信会造成socket错误。
案例模拟浏览器实现
模拟浏览器请求web服务器的网页过程使用TCP实现HTTP协议请求报文格式和响应报文格式 1、导入模块
2、创建套接字
3、建立连接
4、拼接请求协议
5、发送请求协议
6、接收服务器响应内容
7、保存内容
8、关闭连接
# 1、导入模块
import socket
# 2、创建套接字
tcp_client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3、建立连接
tcp_client_socket.connect((www.icoderi.com, 80))
# 4、拼接请求协议
# 4.1 请求行
request_line GET / HTTP/1.1\r\n
# 4.2 请求头
request_header Host:www.icoder.com\r\n
# 4.3 请求空行
request_blank \r\n
# 整体拼接
request_data request_line request_header request_blank
# 5、发送请求协议
tcp_client_socket.send(request_data.encode()) # 请求报文默认是字符串必须转二进制
# 6、接收服务器响应内容
recv_data tcp_client_socket.recv(4096) # 40964Kb
recv_text recv_data.decode() # 解码
# 7、保存内容响应内容
# 7.1 查询\r\n\r\n响应空行的位置
loc recv_text.find(\r\n\r\n) # find进行查找
# 7.2 截取字符串
html_data recv_text[loc4:] # 字符串切片截取
# 保存内容到文件中
with open(index.html, w) as file:file.write(html_data)
# 8、关闭准接
tcp_client_socket.close()基于TCP的Web服务器案例
案例1——返回固定数据 目标/效果 能够实现简单的Web服务器并返回固定数据给浏览器。
整体功能
Web服务器能够绑定固定端口Web服务器端能够接收浏览器请求Web服务器遵守HTTP协议并返回“HelloWorld”字符串给浏览器当浏览器关闭后Web服务器能够显示断开连接Web服务器短时间内重后不会提示address already in use错误 TCP服务端
1、导入socket模块
2、创建tcp套接字
3、设置地址重用
4、绑定端口
5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受
6、接受客户端连接定义函数request_handler())
7、接收客户端浏览器发送的请求协议
8、判断协议是否为空
9、拼接响应的报文
10、发送响应报文给客户端浏览器
11、关闭此次连接的套接字
import socketdef request_handler(new_client_socket, ip_port):接收信息并且做出响应# 7、接收客户端浏览器发送的请求协议request_data new_client_socket.recv(1024)# 8、判断协议是否为空if not request_data:print(%s客户端已经下线 % str(ip_port))new_client_socket.close()return# 9、拼接响应的报文response_line HTTP/1.1 200 OK\r\n # 响应行response_header Server:Python20WS/2.1\r\n # 响应头response_blank \r\n # 响应空行response_body HelloWorld! # 响应主体response_data response_line response_header response_blank response_body# 10、发送响应报文给客户端浏览器new_client_socket.send(response_data.encode())# 11、关闭当前连接new_client_socket.close()def main():主函数# 1、导入socket模块# 2、创建tcp套接字tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 3、设置地址重用(当前套接字, 地址重用, 设置为True)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)# 4、绑定端口tcp_server_socket.bind((, 8080))# 5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受tcp_server_socket.listen(128)# 6、接受客户端连接定义函数request_handler())while True:new_client_socket, ip_port tcp_server_socket.accept()# 调用功能函数处理请求并且响应request_handler(new_client_socket, ip_port)# 11、关闭此次连接的套接字# tcp_server_socket.close()if __name__ __main__:main()案例2——返回固定页面 目标/效果 能够实现返回一个固定的html页面给浏览器的Web服务器。 TCP服务端
1、导入socket模块
2、创建tcp套接字
3、设置地址重用
4、绑定端口
5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受
6、接受客户端连接定义函数request_handler())
7、接收客户端浏览器发送的请求协议
8、判断协议是否为空
9、拼接响应的报文
10、发送响应报文给客户端浏览器
11、关闭此次连接的套接字
import socketdef request_handler(new_client_socket, ip_port):接收信息并且做出响应# 7、接收客户端浏览器发送的请求协议request_data new_client_socket.recv(1024)# 8、判断协议是否为空if not request_data:print(%s客户端已经下线 % str(ip_port))new_client_socket.close()return# 9、拼接响应的报文response_line HTTP/1.1 200 OK\r\n # 响应行response_header Server:Python20WS/2.1\r\n # 响应头response_blank \r\n # 响应空行# 通过with open读取文件with open(static/index.html, rb) as file:# 把读取的文件内容返回给客户端浏览器response_body file.read()response_data (response_line response_header response_blank).encode() response_body# 10、发送响应报文给客户端浏览器new_client_socket.send(response_data)# 11、关闭当前连接new_client_socket.close()def main():主函数# 1、导入socket模块# 2、创建tcp套接字tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 3、设置地址重用(当前套接字, 地址重用, 设置为True)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)# 4、绑定端口tcp_server_socket.bind((, 8080))# 5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受tcp_server_socket.listen(128)# 6、接受客户端连接定义函数request_handler())while True:new_client_socket, ip_port tcp_server_socket.accept()# 调用功能函数处理请求并且响应request_handler(new_client_socket, ip_port)# 11、关闭此次连接的套接字# tcp_server_socket.close()if __name__ __main__:main()案例3——返回指定页面 目标/效果 能够实现根据浏览器不同请求返回对应网页资源的Web服务器。 TCP服务端
1、导入socket模块
2、创建tcp套接字
3、设置地址重用
4、绑定端口
5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受
6、接受客户端连接定义函数request_handler())
7、接收客户端浏览器发送的请求协议
8、判断协议是否为空
9、拼接响应的报文
10、发送响应报文给客户端浏览器
11、关闭此次连接的套接字
import socketdef request_handler(new_client_socket, ip_port):接收信息并且做出响应# 7、接收客户端浏览器发送的请求协议request_data new_client_socket.recv(1024)# 8、判断协议是否为空if not request_data:print(%s客户端已经下线 % str(ip_port))new_client_socket.close()return案例3返回指定页面# 根据客户端浏览器请求的资源路径返回请求资源# 1把请求协议解码得到请求报文的字符串request_text request_data.decode()# 2得到请求行# 2.1查找第一个\r\n出现的位置loc request_text.find(\r\n)# 2.2截取字符串从开头截取到第一个\r\n出现的位置request_line request_text[:loc]# 3把请求行按照空格拆分得到列表request_line_list request_line.split( )# 4得到请求的资源路径file_path request_line_list[1]print([%s]正在请求%s % (str(ip_port), file_path))# 设置默认首页if file_path /:file_path /index.html# 9、拼接响应的报文response_line HTTP/1.1 200 OK\r\n # 响应行response_header Server:Python20WS/2.1\r\n # 响应头response_blank \r\n # 响应空行# 通过with open读取文件try:with open(static file_path, rb) as file:# 把读取的文件内容返回给客户端浏览器response_body file.read() # 响应主体except Exception as e:# 1) 重新修改响应行为404response_line HTTP/1.1 404 Not Found\r\n# 2响应的内容为错误信息response_body Error! (%s) % str(e)# 3把内容转换为字节码response_body response_body.encode()response_data (response_line response_header response_blank).encode() response_body# 10、发送响应报文给客户端浏览器new_client_socket.send(response_data)# 11、关闭当前连接new_client_socket.close()def main():主函数# 1、导入socket模块# 2、创建tcp套接字tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 3、设置地址重用(当前套接字, 地址重用, 设置为True)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)# 4、绑定端口tcp_server_socket.bind((, 8080))# 5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受tcp_server_socket.listen(128)# 6、接受客户端连接定义函数request_handler())while True:new_client_socket, ip_port tcp_server_socket.accept()# 调用功能函数处理请求并且响应request_handler(new_client_socket, ip_port)# 11、关闭此次连接的套接字# tcp_server_socket.close()if __name__ __main__:main()案例4——使用面向对象思想进行封装 目标/效果 能够使用面向对象思想对Web服务器进行封装。 功能分析
使用面向对象思想进行封装通过对象方法.star()启动web服务器 实现思路 1、创建WebServer类 2、创建WebServer类的构造方法__init__()并在构造方法中对tcp_server_socket创建初始化 3、创建start0方法用来启动Web服务器 4、修改如下代码 把套接字初始化的操作放到__init__()中把接受客户端连接的代码放到start()方法中把request_handler()函数变成对象方法选中缩进在main()函数中创建对象wsWebServer()然后启动ws.start() TCP服务端
1、导入socket模块
2、创建tcp套接字
3、设置地址重用
4、绑定端口
5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受
6、接受客户端连接定义函数request_handler())
7、接收客户端浏览器发送的请求协议
8、判断协议是否为空
9、拼接响应的报文
10、发送响应报文给客户端浏览器
11、关闭此次连接的套接字
import socketclass WebServer(object):# 初始化方法def __init__(self):# 1、导入socket模块# 2、创建tcp套接字tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 3、设置地址重用(当前套接字, 地址重用, 设置为True)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)# 4、绑定端口tcp_server_socket.bind((, 8080))# 5、设置监听最大允许客户端连接数128让套接字由主动变为被动接受tcp_server_socket.listen(128)# 定义实例属性保存套接字对象self.tcp_server_socket tcp_server_socketdef start(self):启动web服务器# 6、接受客户端连接定义函数request_handler())while True:new_client_socket, ip_port self.tcp_server_socket.accept()print(新客户来了, ip_port)# 调用功能函数处理请求并且响应self.request_handler(new_client_socket, ip_port)# 11、关闭此次连接的套接字# tcp_server_socket.close()def request_handler(self, new_client_socket, ip_port):接收信息并且做出响应# 7、接收客户端浏览器发送的请求协议request_data new_client_socket.recv(1024)# 8、判断协议是否为空if not request_data:print(%s客户端已经下线 % str(ip_port))new_client_socket.close()return案例3返回指定页面# 根据客户端浏览器请求的资源路径返回请求资源# 1把请求协议解码得到请求报文的字符串request_text request_data.decode()# 2得到请求行# 2.1查找第一个\r\n出现的位置loc request_text.find(\r\n)# 2.2截取字符串从开头截取到第一个\r\n出现的位置request_line request_text[:loc]# 3把请求行按照空格拆分得到列表request_line_list request_line.split( )# 4得到请求的资源路径file_path request_line_list[1]print([%s]正在请求%s % (str(ip_port), file_path))# 设置默认首页if file_path /:file_path /index.html# 9、拼接响应的报文response_line HTTP/1.1 200 OK\r\n # 响应行response_header Server:Python20WS/2.1\r\n # 响应头response_blank \r\n # 响应空行# 通过with open读取文件try:with open(static file_path, rb) as file:# 把读取的文件内容返回给客户端浏览器response_body file.read() # 响应主体except Exception as e:# 1) 重新修改响应行为404response_line HTTP/1.1 404 Not Found\r\n# 2响应的内容为错误信息response_body Error! (%s) % str(e)# 3把内容转换为字节码response_body response_body.encode()response_data (response_line response_header response_blank).encode() response_body# 10、发送响应报文给客户端浏览器new_client_socket.send(response_data)# 11、关闭当前连接new_client_socket.close()def main():主函数# 创建WebServer类的对象wb WebServer()# 对象.start()启动web服务器wb.start()if __name__ __main__:main()多任务 - 线程
多任务的介绍
概念 多任务简单地说就是操作系统可以同时运行多个任务。现在多核CPU已经非常普及了但是即使过去的单核CPU也可以执行多任务。即同一时间多个任务同时执行。 表现形式 window下打开任务管理器可以很清晰看到多个进程在同时执行任务qq、微信等都是已进程的形式寄存在window下。大多我们在写一些控制台程序真正执行的时候都是以进程调度。
python默认是单任务 使用python代码来模拟“唱歌跳舞”这件事情
import timedef sing():唱歌函数for i in range(3):print(正在唱歌...)time.sleep(0.5)def dance():跳舞函数for i in range(3):print(正在跳舞...)time.sleep(0.5)if __name__ __main__:sing()dance()运行结果如下
正在唱歌...
正在唱歌...
正在唱歌...
正在跳舞...
正在跳舞...
正在跳舞...Process finished with exit code 0线程
概念 线程可简单理解为是程序执行的一条分支也是程序执行流的最小单元。线程是被系统独立调度和分派的基本单位线程自己不拥有系统资源只拥有一点儿在运行中必不可少的资源但它可与同属一个进程的其它线程共享进程所拥有的全部资源。 主线程 当一个程序启动时就有一个进程被操作系统(OS)创建与此同时一个线程也立刻运行该线程通常叫做程序的主线程简而言之程序启动就会创建一个主线程。
主线程的重要性有两方面
1) 是产生其他子线程的线程
2) 通常它必须最后完成执行比如执行各种关闭动作子线程 可以看做是程序执行的一条分支当子线程启动后会和主线程一起同时执行。
使用threading模块创建子线程
python的thread模块是比较底层的模块python的threading模块是对thread做了一些包装的可以更加方便的被使用。 核心方法 导入模块threading threading模块的Thread类创建子线程对象t threading.Thread(target函数名) 启动子线程t.start() 代码实现
from time import sleep, ctime
import threadingdef sing():唱歌函数for i in range(3):print(正在唱歌...%d%i)sleep(0.5)def dance():跳舞函数for i in range(3):print(正在跳舞...%d%i)sleep(0.5)if __name__ __main__:print(---开始---:%s%ctime())t1 threading.Thread(targetsing)t2 threading.Thread(targetdance)t1.start()t2.start()print(主线程!) # 子线程在执行的时候主线程也在执行sleep(3)print(---结束---:%s % ctime()) # 主线程会等待所有子线程结束后才结束运行结果如下
---开始---:Tue Nov 14 10:24:04 2023
正在唱歌...0
正在跳舞...0主线程!正在跳舞...1
正在唱歌...1
正在跳舞...2正在唱歌...2---结束---:Tue Nov 14 10:24:07 2023Process finished with exit code 0说明
可以明显看出使用了多线程并发的操作花费时间要短很多。当调用start()时才会真正的创建线程并且开始执行。每个线程都有一个唯一标示符来区分线程中的主次关系。主线程mainThreadMain函数或者程序主入口都可以称为主线程。子线程Thread-x使用threading.Thread()创建出来的都是子线程。线程数量主线程数子线程数。
线程名称、总数量
获取当前活跃的线程对象列表thread_list threading.enumerate() 获取当前活跃的线程数量len(threading.enumerate()) 获取线程的名称threading.current_thread() 获取当前的线程对象对象中含有名称。
from time import sleep, ctime
import threadingdef sing():唱歌函数for i in range(3):print(正在唱歌..., threading.current_thread()) # 获取当前的线程对象sleep(0.5)def dance():跳舞函数for i in range(3):print(正在跳舞..., threading.current_thread())sleep(0.5)if __name__ __main__:print(---开始---:%s%ctime())t1 threading.Thread(targetsing)t2 threading.Thread(targetdance)t1.start()t2.start()while True:thread_num len(threading.enumerate()) # 获取当前活跃的线程数量print(当前线程的数量, thread_num)# 如果只剩下主线程就停止if thread_num 1:breaksleep(0.5)运行结果如下
---开始---:Tue Nov 14 10:41:43 2023
正在唱歌... Thread(Thread-1, started 22808)
正在跳舞...当前线程的数量 3Thread(Thread-2, started 19404)当前线程的数量 3正在跳舞...Thread(Thread-2, started 19404)
正在唱歌... Thread(Thread-1, started 22808)
当前线程的数量正在跳舞...正在唱歌... 3Thread(Thread-1, started 22808)Thread(Thread-2, started 19404)
当前线程的数量 3
当前线程的数量 1Process finished with exit code 0线程参数及顺序
线程参数有三种方式进行传递
元组传递 threading.Thread(target函数名, args(参数1,参数2,...)) 元组中元素的顺序和函数的参数顺序一致字典传递threading.Thread(target函数名, kwargs{参数名: 参数值, ...})元组、字典混合传递threading.Thread(target函数名, args(参数1, 参数2, ...), kwargs{参数名: 参数值, ...})
from time import sleep
import threadingdef sing(a, b, c):唱歌函数print(参数, a, b, c)for i in range(3):print(正在唱歌...)sleep(0.5)def dance():跳舞函数for i in range(3):print(正在跳舞~~~~~)sleep(0.5)if __name__ __main__:# 1、使用元组传递# t1 threading.Thread(targetsing, args(1, 10, 100))# 2、使用字典传递# t1 threading.Thread(targetsing, kwargs{a: 1, c: 100, b: 10})# 3、混合使用元组和字典t1 threading.Thread(targetsing, args(1,), kwargs{c: 100, b: 10})t2 threading.Thread(targetdance)t1.start()t2.start()线程的执行顺序 线程的调度是由CPU或者说操作系统根据当时的状态自行决定所以多个线程的执行是无序的、随机的。
守护线程
守护线程 如果在程序中将子线程设置为守护线程则该子线程会在主线程结束时自动退出设置方式为thread.setDaemon(True), 要在thread.start()之前设置默认是false的也就是主线程结束时子线程依然在执行。 对于python应用我们都知道main方法是入口它的运行代表着主线程开始工作了我们也知道Python虚拟机里面有垃级回收器的存在使得我们做心让main飞奔然而这背后的故事是垃极回收线程作为守护着主线程的守护线程默默的付出着··· 如下代码主线程已经exit()其实并没有真正结束子线程还在继续执行。
import threading
import timedef work():for i in range(5):print(正在执行..., i)time.sleep(0.5)if __name__ __main__:thread_work threading.Thread(targetwork)thread_work.start()# 睡眠2stime.sleep(2)print(Game Over!!)# 让程序退出主线程主动结束exit()运行结果如下
正在执行... 0
正在执行... 1
正在执行... 2
正在执行... 3
Game Over!!
正在执行... 4添加守护线程的代码如下
import threading
import timedef work():for i in range(5):print(正在执行..., i)time.sleep(0.5)if __name__ __main__:thread_work threading.Thread(targetwork)thread_work.setDaemon(True) # 表示子线程守护了主线程主线程结束后子线程也结束thread_work.start()# 睡眠2stime.sleep(2)print(Game Over!!)# 让程序退出主线程主动结束exit()运行结果如下
正在执行... 0
正在执行... 1
正在执行... 2
正在执行... 3
Game Over!!并行和并发
多任务的原理剖析 其实就是操作系统轮流让各个任务交替执行任务1执行0.01秒切换到任务2任务2执行0.01秒再切换到任务3执行0.01秒…这样反复执行下去表面上看每个任务都是交替执行的但是由于CPU的执行速度实在是太快了我们感觉就像所有任务都在同时执行一样。 并发 任务数量大于CPU的核心数。 指的是任务数多于cpu核数通过操作系统的各种任务调度算法实现用多个任务“一起”执行(实际上总有一些任务不在执行因为切换任务的速度相当快看上去一起执行而已) 真正的并行执行多任务只能在多核CPU上实现但是由于任务数量远远多于CPU的核心数量所以操作系统也会自动把很多任务轮流调度到每个核心上执行。 并行 任务数量小于等于CPU的核心数即任务真的是一起执行的。
自定义线程类
通过使用threading模块能完成多任务的程序开发为了让每个线程的封装性更完美所以使用threading模块时往往会定义一个新的子类class只要
让自定义类继承threading.Thread让自定义类重写run方法通过实例化自定义类对象.start()方法启动自定义线程 1、让自定义类继承thread.Thread类
2、重写父类threading.Threadrun方法
3、通过创建子类对象让子类对象.start()就可以启动子线程# 1 导入模块
import threading
import time# 2 自定义线程类并继承threading.Thread
class MyThread(threading.Thread):def __init__(self, num):# 子类先通过super调用父类的初始化方法子类再初始化super().__init__()self.num num# 3 重写父类的run方法def run(self):for i in range(3):# self.name 从父类继承的一个属性print(正在执行子线程的run方法..., i, self.name)time.sleep(0.5)if __name__ __main__:# 4 创建对象mythread MyThread(10)# 5 线程对象.start() 启动线程子类从父类继承了start()方法mythread.start()print(Done!)运行结果如下
正在执行子线程的run方法... 0 Thread-1
Done!
正在执行子线程的run方法... 1 Thread-1
正在执行子线程的run方法... 2 Thread-1底层原理 Thread类 run方法 start() start()中调用了run方法 多线程-共享全局变量
多个线程方法中可以共用全局变量 看看work1线程对全局变量的修改在work2中能否查看修改后的结果
# 定义全局变量
import threading
import timeg_num 0def work1():# 声明g_num是一个全局变量global g_numfor i in range(3):g_num 1print(work1-----, g_num)def work2():print(work2-----, g_num)if __name__ __main__:# 创建2个子线程t1 threading.Thread(targetwork1)t2 threading.Thread(targetwork2)# 启动线程t1.start()t2.start()while len(threading.enumerate()) ! 1:time.sleep(0.5)# 在t1和t2线程执行完毕后打印g_numprint(main-----, g_num)运行结果如下
work1----- 3
work2----- 3
main----- 3问题 多个线程同时访问同一个资源出现资源竞争的问题 假设两个线程t1和t2都要对全局变量g_num默认是0进行加1运算t1和t2都各对g_num加3次g_num的最终的结果应该为6但是由于是多线程同时操作有可能出现下面情况 1g_num0时t1取得g_num0此时系统把t1调度为sleeping状态把t2转换为running状态t2也获得g_num0 2然后t2对得到的值进行加1并赋给g_num使得g_num1 3然后系统又把t2调度为sleeping”把t1转为running线程t1又把它之前得到的0加1后赋值给g_num 4这样导致虽然t1和t2都对g_num加1但结果仍然是g_num1 解决方法1 优先让某个线程先执行线程对象.join() 缺点把多线程变成了单线程影响整体性能。 看看work1线程对全局变量的修改在work2中能否查看修改后的结果
# 定义全局变量
import threading
import timeg_num 0def work1():# 声明g_num是一个全局变量global g_numfor i in range(1000000):g_num 1print(work1-----, g_num)def work2():# 声明g_num是一个全局变量global g_numfor i in range(1000000):g_num 1print(work2-----, g_num)if __name__ __main__:# 创建2个子线程t1 threading.Thread(targetwork1)t2 threading.Thread(targetwork2)# 启动线程t1.start()# 让t1线程优先执行t1执行完毕后t2才能执行t1.join()t2.start()while len(threading.enumerate()) ! 1:time.sleep(0.5)# 在t1和t2线程执行完毕后打印g_numprint(main-----, g_num)运行结果如下
注释t1.join()代码行的运行结果
work1----- 1000000
work2----- 1429445
main----- 1429445未注释t1.join()代码行的运行结果
work1----- 1000000
work2----- 2000000
main----- 2000000解决方法2 可以通过线程同步来进行解决思路如下 1、系统调用t1然后获取到g_num的值为0此时上一把锁即不允许其他线程操作g_num 2、t1对g_num的值进行1 3、t1解锁此时g_num的值为1其他的线程就可以使用g_num了而且是g_num的值不是0而是1 4、同理其他线程在对g_num进行修改时都要先上锁处理完后再解锁在上锁的整个过程中不允许其他线程访问就保证了数据的正确性。
同步和异步
同步 在多任务中多个任务执行有先后顺序一个执行完毕后另外一个再执行。只有一个主线。如你说完我再说同一时间只能做一件事情 异步 在多任务中多个任务执行没有先后顺序多个任务同时执行。存在多条运行主线。如发微信可以不用等对方回复继续发、点外卖点了外卖后可以继续忙其他的事情而不是坐等外卖啥也不做 线程的锁机制 当线程获取资源后立刻进行锁定资源使用完毕后再解锁有效的保证同一时间只有一个线程在使用资源。
互斥锁
当多个线程几平同时修改某一个共享数据的时候需要进行同步控制。 线程同步能够保证多个线程安全访问竞争资源最简单的同步机制是引入互斥锁。 互斥锁为资源引入一个状态锁定/非锁定 某个线程要更改共享数据时先将其锁定此时资源的状态为“锁定”其他线程不能更改直到该线程释放资源将资源的状态变成“非锁定”其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作从而保证了多线程情况下数据的正确性。 threading模块中定义了Lock类可以方便的处理锁定 创建锁mutex threading.Lock() 锁定metex.acquire() 释放mutex.release() 注意 1如果这个锁之前是没有上锁的那么acquire不会堵塞。 2如果在调用acquire对这个锁上锁之前它已经被其他线程上了锁那么此时acquire会堵塞直到这个锁被解锁为止。 3使用原则尽可能少的锁定竞争资源。 1、创建一把互斥锁
2、在使用资源前要锁定资源
3、使用完资源后要解锁资源
# 定义全局变量
import threading
import timeg_num 0def work1():# 声明g_num是一个全局变量global g_numfor i in range(1000000):# 上锁lock.acquire()g_num 1# 解锁lock.release()print(work1-----, g_num)def work2():# 声明g_num是一个全局变量global g_numfor i in range(1000000):# 上锁lock.acquire()g_num 1# 解锁lock.release()print(work2-----, g_num)if __name__ __main__:# 创建一把互斥锁lock threading.Lock()# 创建2个子线程t1 threading.Thread(targetwork1)t2 threading.Thread(targetwork2)# 启动线程t1.start()t2.start()while len(threading.enumerate()) ! 1:time.sleep(0.5)# 在t1和t2线程执行完毕后打印g_numprint(main-----, g_num)运行结果
work1----- 1882544
work2----- 2000000
main----- 2000000死锁
在线程间共享多个资源的时候如果两个线程分别占有一部分资源并且同时等待对方的资源就会造成死锁。尽管死锁很少发生但一旦发生就会造成应用的停止响应。下面看一个死锁的例子
import threading# 定义函数根据下标获取列表元素值
def get_value(index):data_list [1, 3, 5, 7, 9]# 上锁lock.acquire()# 判断下标位置是否正确if index len(data_list):print(下标越界, index)# 释放锁没有这句会造成死锁线程6-9都在等待线程5解锁# lock.release()returnprint(data_list[index])# 解锁lock.release()# 创建10个线程观察资源的等待状态
if __name__ __main__:# 创建一把锁lock threading.Lock()# 循环创建10个线程for i in range(10):t1 threading.Thread(targetget_value, args(i,))t1.start()运行结果如下
1
3
5
7
9
下标越界 5避免锁使用完毕后要及时释放。
案例
多任务版udp聊天器
原版效果无法实现收发信息同时进行必须先选1发送信息再选2接收信息 优化效果发送消息的同时可以同时接收多条信息 程序分析 说明 1编写一个有2个线程的程序 2线程1用来接收数据然后显示 3线程2用来检测健盘数据然后通过udp发送数据 改进思路 1单独开子线程用于接收消息以达到收发消息可以同时进行 2接收消息要能够连续接收多次而不是一次 3设置子线程守护主线程解决无法正常退出问题 参考代码
import socket
import threadingdef send_msg(udp_socket):发送信息# 1定义变量接收用户输入的接收方的IP地址ipaddr input(请输入接收方的IP地址\n)# 判断是否需要默认if len(ipaddr) 0:ipaddr 192.168.150.93print(当前接收方默认IP设置为[%s] % ipaddr)# 2定义变量接收用户输入的接收方的端口号port input(请输入接收方的端口号\n)if len(port) 0:port 8080print(当前接收方默认端口设置为[%s] % port)# 3定义变量接收用户输入的发送给接收方的内容content input(请输入要发送的内容\n)# 4使用socket的sendto()发送信息udp_socket.sendto(content.encode(), (ipaddr, int(port)))def recv_msg(udp_socket):接收信息while True:# 1使用socket的recvfrom()接收数据recv_data, ip_port udp_socket.recvfrom(1024)# 2解码数据recv_text recv_data.decode()# 3并输出显示print(接收到[%s]的消息%s % (str(ip_port), recv_text))def main():程序的主入口# 1创建套接字udp_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 2绑定端口udp_socket.bind((, 8080))# 创建子线程单独接收用户发送的消息thread_recvmsg threading.Thread(targetrecv_msg, args(udp_socket,))# 设置子线程守护主线程(子线程会在主线程结束时自动退出)thread_recvmsg.setDaemon(True)# 启动子线程thread_recvmsg.start()# 3打印菜单循环while True:print(***************************)print(****** 1、发送信息 ********)print(****** 2、退出系统 ********)# 4接收用户输入的选项sel_num int(input(请输入选项\n))# 5判断用户的选择并且调用对应的函数if sel_num 1:print(您选择的是发送信息)send_msg(udp_socket)elif sel_num 2:print(系统正在退出中...)print(系统退出完成)breakelse:print(输入有误请重新输入)# 6关闭套接字udp_socket.close()if __name__ __main__:程序独立运行的时候才去启动聊天室main()TCP服务器端框架
目标 能够使用多线程实现同时接收多个客户端的多条信息 思想 每来一个新的客户端就创建一个新的线程。 参考代码: 1、导入模块
2、创建套接字
3、设置地址可以重用
4、绑定端口
5、设置监听套接字有主动设置为被动
6、接受容户瑞连接
7、接收容户瑞发送的信息
8、解码数据并且进行输出
9、关闭和当前容户瑞的连接# 1、导入模块
import socket
import threadingdef recv_msg(new_client_socket, ip_port):while True:# 7、接收容户瑞发送的信息recv_data new_client_socket.recv(1024)if recv_data:# 8、解码数据并且进行输出recv_text recv_data.decode(GBK)print(收到来自[%s]的信息%s % (str(ip_port), recv_text))else:break# 9、关闭和当前容户瑞的连接new_client_socket.close()# 2、创建套接字
tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3、设置地址可以重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 4、绑定端口
tcp_server_socket.bind((, 8080))
# 5、设置监听套接字有主动设置为被动
tcp_server_socket.listen()
while True:# 6、接受容户瑞连接new_client_socket, ip_port tcp_server_socket.accept()print(新用户上线, ip_port)# recv_msg(new_client_socket, ip_port)# 创建线程thread_recvmsg threading.Thread(targetrecv_msg, args(new_client_socket, ip_port))# 设置线程守护thread_recvmsg.setDaemon(True)# 启动线程thread_recvmsg.start()
# tcp_server_socket.close()多任务 - 进程
进程 进程(Process)是资源分配的最小单位也是线程的容器。 CPU的时间片轮转在不同的时间段切换执行不同的进程但是切换进程是比较耗时的就引来了轻量级进程也就是所谓的线程一个进程中包括多个线程代码流其实也就是进程中同时跑的多个方法体
程序测如xxx.py这是程序是一个静态的。
进程一个程序运行起来后代码用到的资源称之为进程它是操作系统分配资源的基本单元。进程的状态 工作中任务数往往大于CPU的核数即一定有一些任务正在执行而另外一些任务在等待CPU进行执行因此导致了有了不同的状态。
就绪态运行的条件都已经满足正在等在cpu执行执行态cpu正在执行其功能等待态等待某些条件满足例如一个程序sleep了此时就处于等待态
进程的基本使用
multiprocessing模块就是跨平台版本的多进程模块提供了一个Process类来代表一个进程对象这个对象可以理解为是一个独立的进程可以执行另外的事情。 进程使用步骤 导入模块import multiprocessing 创建子进程对象process_obj multiprocessing.Process(targetfunctionName) 启动子进程process_obj.start() 说明 创建子进程跟创建线程十分类似只需要传入一个执行函数和函数的参数创建一个Procecss实例用start()方法启动Process语法结构如下Process([group [, target [, name [, args [, kwargs]]]]]) target如果传递了函数的引用这个子进程就执行这里函数的代码。 args给target指定的函数传递的参数以元组的方式传递 kwargs给target指定的函数传递命名参数 name给进程设定一个名字可以不设定 group指定进程组大多数情况下用不到Process创建的实例对象的常用方法 start()启动子进程实例创建子进程 is_alive()判断进程子进程是否还在活着 join([timeout]) 是否等待子进程执行结束或等待多少秒 terminate()不管任务是否完成立即终止子进程Process创建的实例对象的常用属性 name当前进程的别名默认为Process-NN为从1开始递增的整数 pid当前进程的pid进程号 1、导入模块
2、通过模块提供的Process类创建进程对象
3、启动进程import time
import multiprocessingdef work():for i in range(3):print(正在运行work...)time.sleep(0.5)if __name__ __main__:# 2、通过模块提供的Process类创建进程对象process_obj multiprocessing.Process(targetwork)# 3、启动进程process_obj.start()print(Done)运行结果如下
Done
正在运行work...
正在运行work...
正在运行work...进程名称、pid
获取进程名称 multiprocessing.current_process() 设置子进程名称 multiprocessing.Process(targetxxx, name进程名称) 获取进程id multiprocessing.current_process().pid、os.getpid() 获取进程父id os.getppid() 杀掉进程 kill -9 进程号 可以强制结束某个进程 1、导入模块
2、通过模块提供的Process类创建进程对象
3、启动进程import time
import multiprocessing
import osdef work():print(子进程名称, multiprocessing.current_process()) # 获取子进程名称print(子进程编号, multiprocessing.current_process().pid) # 获取子进程的编号for i in range(3):# 获取进程的父idprint(正在运行work..., os.getpid(), -父id, os.getppid())time.sleep(0.5)if __name__ __main__:# 获取主进程名称print(主进程名称, multiprocessing.current_process())# 获取主进程的编号print(主进程编号, multiprocessing.current_process().pid)# 2、通过模块提供的Process类创建进程对象# target知道子进程要执行的分支函数# name知道子进程的名称process_obj multiprocessing.Process(targetwork, nameP1)# 3、启动进程process_obj.start()print(Done)运行结果如下
主进程名称 _MainProcess(MainProcess, started)
主进程编号 21548
Done
子进程名称 Process(P1, started)
子进程编号 15408
正在运行work... 15408 -父id 21548
正在运行work... 15408 -父id 21548
正在运行work... 15408 -父id 21548进程参数、全局变量
进程的参数传递 args元组、kwargs字段、args和kwargs混合
import multiprocessing
import timedef work(a, b, c):print(参数, a, b, c)for i in range(3):print(正在运行work...)time.sleep(0.5)if __name__ __main__:# 1、使用args传递元组# process_obj multiprocessing.Process(targetwork, args(1, 10, 100))# 2、使用kwargs传递字典# process_obj multiprocessing.Process(targetwork, kwargs{c:100, a:1, b:10})# 3、混合使用args和kwargsprocess_obj multiprocessing.Process(targetwork, args(1,), kwargs{c:100, b:10})process_obj.start()print(Done)运行结果如下
Done
参数 1 10 100
正在运行work...
正在运行work...
正在运行work...进程间是不能够共享全局变量 底层原理子进程会复制主进程的资源到内部运行。
import multiprocessing
import time# 定义全局变量
g_num 10# work1 对全局变量累加
def work1():global g_numfor i in range(10):g_num 1print(---work1---, g_num)# work2 读取全局变量的值如果能读取到说明全局变量能共享否则不能
def work2():print(---work2---, g_num)if __name__ __main__:work1_process multiprocessing.Process(targetwork1)work2_process multiprocessing.Process(targetwork2)work1_process.start()work2_process.start()time.sleep(3)print(---main---, g_num)运行结果如下
---work1--- 20
---work2--- 10
---main--- 10守护主进程
进程守护 子进程和主进程的一种约定当主进程结束的时候子进程也随之结束process_obj.daemonTrue 结束子进程 终止进程执行并非是守护进程process_obj.terminate()
import multiprocessing
import timedef work():for i in range(5):print(正在运行work...)time.sleep(0.5)if __name__ __main__:process_obj multiprocessing.Process(targetwork)# 设置process_obj 子进程守护主进程process_obj.daemon Trueprocess_obj.start()time.sleep(2)print(Done)exit()运行结果如下
未设置守护进程
正在运行work...
正在运行work...
正在运行work...
正在运行work...
Done
正在运行work...设置守护进程
正在运行work...
正在运行work...
正在运行work...
正在运行work...
Doneimport multiprocessing
import timedef work():for i in range(5):print(正在运行work...)time.sleep(0.5)if __name__ __main__:process_obj multiprocessing.Process(targetwork)process_obj.start()time.sleep(2)print(Done)# 终止子进程的执行process_obj.terminate()exit()运行结果如下
正在运行work...
正在运行work...
正在运行work...
正在运行work...
Done进程、线程对比 功能 进程 能够完成多任务比如在一台电脑上能够同时运行多个QQ 线程 能够完成多任务比如一个QQ中的多个聊天窗口 使用区别 1进程是资源分配的基本单位线程是CPU调度的基本单位 2进程运行需要独立的内存资源线程需要到的是必不可少的一点资源 3进程切换慢线程切换更快 4线程不能独立运行必须运行在进程中进程能提供资源 5CPU密集型进程优先I/O密集型使用线程 6一个程序至少有一个进程一个进程至少有一个线程进程更稳定相较线程更稳定 7可以将进程理解为工厂中的一条流水线而其中的线程就是这个流水线上的工人。 进程与线程的选择取决以下几点 对比维度多进程多线程总结数据共享、同步数据共享复杂需要用IPC数据是分开的同步简单因为共享进程数据数据共享简单但也是因为这个原因导致同步复杂各有优势内存、CPU占用内存多切换复杂CPU利用率低占用内存少切换简单CPU利用率高线程占优创建销毁、切换创建销毁、切换复杂速度慢创建销毁、切换简单速度很快线程占优编程、调试编程、调试简单编程、调试复杂进程占优可靠性进程间不会互相影响一个线程挂掉将导致整个进程挂掉进程占优分布式适应于多核、多机分布式如果一台机器不够扩展到多台机器比较简单适用于多核分布式进程占优
选择原则
需要频繁创建销的优先使用线程(如Web服务器)线程的切换速度快所以在需要大量计算切换频繁时用线程如图像处理、算法处理因为对CPU系统的效率使用上线程更占优所以可能要发展到多机分布的用进程多核分布用线程。需要更稳定安全时适合选择进程需要速度时选择线程更好。都满足需求的情况下用你最熟悉、最拿手的方式。 需要提醒的是虽然我给了这么多的选择原则但实际应用中基本上都是“进程线程”的结合方式千万不要真的陷入一种非此即彼的误区。 在Python的原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解释器锁)因此在解释执行python代码时会产生互斥锁来限制线程对共享资源的访问直到解释器遇到/IO操作或者操作次 数达到一定数目时才会释放GIL造成了即使在多核CPU中多线程也只是做着分时切换而已。 消息队列 - 基本操作
Queue 可以使用multiprocessing模块的Queue实现多进程之间的数据传递Queue本身是一个消息列队程序。 创建队列 multiprocessing.Queue(5) # 队列长度为5 放入值 queue.put(值) # 从队列尾部放入值 取值 queue.get() # 从队列头部取值 queue.put_nowait(): 队列未满同queue.put()但是队列已满会报错不等待。 queue.get_nowait(): 队列未空同queue.get()但是队列已空会报错不等待。 队列是multiprocessing模块提供的一个类
1、创建队列指定长度
2、放值
3、取值import multiprocessing# 1、创建队列指定长度
queue multiprocessing.Queue(5)
# 2、放值
queue.put(1)
queue.put(hello)
queue.put([1, 2, 3])
queue.put((4, 5, 6))
queue.put({a: 10, b: 100})
# queue.put(6) # 长度为5放入第6个数据后队列就进入了阻塞状态默认会等待队列先取出值再放入新的值(程序不会结束也不会报错
# queue.put_nowait(6) # 长度为5放入第6个数据后队列已满不会等待直接报错queue.Full
# 3、取值
for i in range(5):value queue.get()print(value)print(--*20)
# -------队列中已经没有值了-------
# print(queue.get()) # 当队列已经为空时再次get()程序进入阻塞状态等待放入新的值到队列然后再取程序不会结束也不会报错
# print(queue.get_nowait()) # 当队列已经为空时不会等待放入新的值直接报错_queue.Empty说明
初始化Queue()对象时例如qQueue()若括号中没有指定最大可接收的消息数量或数量为负值那么就代表可接受的消息数量没有上限直到内存的尽头Queue.qsize(): 返回当前队列包含的消息数量Queue.empty(): 如果队列为空返回True反之False;Queue.full(): 如果队列满了返回True反之False;Queue.get([block[, timeout]]): 获取队列中的一条消息然后将其从列队中移除block默认值为True;如果block使用默认值且没有设置timeout单位秒消息列队如果为空此时程序将被阻塞停在读取状态直到从消息列队读到消息为止如果设置了timeout则会等待timeout秒若还没读取到任何消息则抛出Queue.Empty异常如果block值为False消息列队如果为空则会立刻抛出Queue.Empty异常Queue.get_nowait():相当Queue.get(False);Queue.put(item,[block[,timeout]]): 将item消息写入队列block默认值为True;如果block使用默认值且没有设置timeout单位秒消息列队如果已经没有空间可写入此时程序将被阻塞停在写入状态直到从消息列队腾出空间为止如果设置了timeout则会等待timeout秒若还没空间则抛出Queue.Full异常如果block值为False消息队列如果没有空间可写入则会立刻抛出Queue.Full异常Queue.put_nowait(item): 相当Queue.put(item, False);消息队列 - 常见判断
判断队列是否已满queue.full() 判断队列是否为空queue.empty() 获取队列中消息的个数queue.qsize()队列每get()一次数量就会-1 1、判断是否已满
2、判断是否为空
3、获取队列中消息的个数import multiprocessing# 创建一个长度为3的消息队列
queue multiprocessing.Queue(3)
# 放值
queue.put(1)
queue.put(2)
queue.put(3)
# 1、判断是否已满True满False未满
isFull queue.full()
print(isFull --, isFull) # isFull -- True# 取值
for i in range(3):queue.get()
# 2、判断是否为空True空False未空
isEmpty queue.empty()
print(isEmpty --, isEmpty) # isEmpty -- True# 3、获取队列中消息的个数
print(队列中消息的个数, queue.qsize()) # 队列中消息的个数 0Queue实现进程间通信
思路 利用队列在两个进程间进行传递进而实现数据共享。join()优先让一个进程先执行完成另外一个进程才能启动。 思路
1、准备2个进程
2、准备1个队列1个进程向队列中写入数据然后把队列传递到另一个进程
3、另外1个进程读取数据
# 1、写入数据到队列的函数
import multiprocessing
import timedef write_queue(queue):for i in range(10):# 判断队列是否已满if queue.full():print(队列已满)break# 向队列中放入值queue.put(i)print(成功写入, i)time.sleep(0.5)# 2、 读取队列数据并显示的函数
def read_queue(queue):while True:# 判断队列是否已经为空if queue.qsize() 0:print(队列已空)break# 从队列中读取数据value queue.get()print(成功读取, value)if __name__ __main__:# 3、创建一个空的队列queue multiprocessing.Queue(5)# 4、创建2个进程分别写、读数据write_process multiprocessing.Process(targetwrite_queue, args(queue,))read_process multiprocessing.Process(targetread_queue, args(queue,))# 启动进程write_process.start()# 优先让写数据的进程执行结束后再启动读取数据的进程write_process.join()read_process.start()运行结果如下
成功写入 0
成功写入 1
成功写入 2
成功写入 3
成功写入 4
队列已满
成功读取 0
成功读取 1
成功读取 2
成功读取 3
成功读取 4
队列已空进程池Pool
进程池概述 进程池是一个进程的容器可以自动帮我们创建指定数量的进程并且管理进程及工作。 当需要创建子进程数量不多时可以直接利用multiprocessing中的Process动态成生多个进程但如果是上百甚至上千个目标手动的去创建进程的工作量巨大此时就可以用到multiprocessing模块提供的Pool方法。 初始化Pool时可以指定一个最大进程数当有新的请求提交到Pool中时如果池还没有满那么就会创建一个新的进程用来执行该请求但如果池中的进程数已经达到指定的最大值那么该请求就会等待直到池中有进程结束才会用之前的进程来执行新的任务。 创建进程池方法 pool multiprocessing.Pool(3) 工作方式
同步方式 进程池中的进程一个执行完毕后另一个才能执行多个进程执行有先后顺序pool.apply(函数名, (参数1, 参数2, ...))异步方式进程池中的进程多个进程同时执行没有先后顺序pool.apply_async(函数名, (参数1, 参数2, ...)) 注意 1进程池要close()表示不再接受新的任务pool.close() 2还要join()表示让主进程等待进程池执行结束后再退出 pool.join() 1、创建一个函数用于模拟文件拷贝
2、创建一个进程池长度为3表示进程池中最多能够创建3个进程
3、先用进程池同步方式拷贝文件
4、再用进程池异步方式拷贝文件
import multiprocessing
import time# 1、创建一个函数用于模拟文件拷贝
def copy_work():print(正在拷贝文件..., multiprocessing.current_process())time.sleep(0.5)if __name__ __main__:# 不使用进程拷贝文件time1 time.time()for i in range(10):copy_work()time2 time.time()print(不使用进程拷贝文件用时, time2-time1) # 5.115655422210693# 2、创建一个进程池长度为3表示进程池中最多能够创建3个进程pool multiprocessing.Pool(3)# # 3、先用进程池同步方式拷贝文件time3 time.time()pool.apply(copy_work)time4 time.time()print(使用进程池同步方式拷贝文件用时, time4 - time3) # 0.5013861656188965# 4、再用进程池异步方式拷贝文件time5 time.time()pool.apply_async(copy_work)time6 time.time()pool.close() # 不再接受新的任务pool.join() # 主进程等待进程池执行结束后再退出print(使用进程池异步方式拷贝文件用时, time6 - time5) # 0.0进程池中的Queue 创建进程池中的队列 queue multiprocessing.Manager().Queue(3) 异步方式注意2点 pool.close() pool.join()
import multiprocessing
import time# 1、写入数据到队列的函数
def write_queue(queue):for i in range(5):# 判断队列是否已满if queue.full():print(队列已满)break# 向队列中放入值queue.put(i)print(成功写入, i)time.sleep(0.5)# 2、 读取队列数据并显示的函数
def read_queue(queue):while True:# 判断队列是否已经为空if queue.qsize() 0:print(队列已空)break# 从队列中读取数据value queue.get()print(成功读取, value)time.sleep(0.5)if __name__ __main__:# 1、创建进程池pool multiprocessing.Pool(2)# 2、创建进程池队列queue multiprocessing.Manager().Queue(3)# 3、使用进程池执行任务# 3.1 同步方式time1 time.time()pool.apply(write_queue, (queue,))pool.apply(read_queue, (queue,))time2 time.time()print(同步用时, time2-time1) # 3.077965021133423# 3.2 异步方式time3 time.time()# apply_sync()返回ApplyResult对象result pool.apply_async(write_queue, (queue,))# ApplyResult对象有一个wait()方法类似join()表示后续进程必须等待当前进程执行完再继续result.wait()pool.apply_async(read_queue, (queue,))time4 time.time()print(异步用时, time4 - time3) # 1.5298125743865967pool.close() # 表示不再接收新的任务pool.join() # 主进程会等待进程池结束后再退出案例文件夹拷贝器多进程版 将D:/test文件夹拷贝到d:/Users/Desktop/test
思路
1、定义变量保存源文件夹、目标文件夹所在的路径
2、在目标路径创建新的文件夹
3、获取源文件夹中的所有文件列表
4、遍历列表得到所有文件名
5、定义函数进行文件拷贝文件拷贝函数
参数源文件夹路径、目标文件夹路径、文件名
1、拼接源文件和目标文件的具体路径
2、打开源文件创建目标文件
3、读取源文件的内容写入到目标文件中import multiprocessing
import osdef copy_work(source_dir, dest_dir, file_name):根据参数拷贝文件print(multiprocessing.current_process())# 1、拼接源文件和目标文件的具体路径source_path source_dir / file_namedest_path dest_dir / file_nameprint(source_path, ---, dest_path)# 2、打开源文件with open(source_path, rb) as source_file:# 2、创建目标文件with open(dest_path, wb) as dest_file:while True:# 3、读取源文件的内容写入到目标文件中file_data source_file.read(1024)# 判断文件是否读取完成if file_data:dest_file.write(file_data)else:breakif __name__ __main__:# 1、定义变量保存源文件夹、目标文件夹所在的路径source_dir D:/testdest_dir d:/Users/Desktop/test# 2、在目标路径创建新的文件夹os.mkdir(路径)在指定位置创建文件夹try:os.mkdir(dest_dir)except Exception as e:print(文件夹已经存在)# 3、获取源文件夹中的所有文件列表os.listdir(路径)file_list os.listdir(source_dir)# 创建进程池pool multiprocessing.Pool(3)# 4、遍历列表得到所有文件名for file_name in file_list:# 5、定义函数进行文件拷贝# copy_work(source_dir, dest_dir, file_name) # 效率低# 使用进程持异步方式拷贝文件pool.apply_async(copy_work, (source_dir, dest_dir, file_name))pool.close() # 表示不再接收新的任务pool.join() # 主进程会等待进程池结束后再退出运行结果
SpawnProcess(SpawnPoolWorker-1, started daemon)
D:/test/1.txt --- d:/Users/Desktop/test/1.txt
SpawnProcess(SpawnPoolWorker-2, started daemon)
D:/test/2.txt --- d:/Users/Desktop/test/2.txt
SpawnProcess(SpawnPoolWorker-1, started daemon)
D:/test/3.txt --- d:/Users/Desktop/test/3.txt
SpawnProcess(SpawnPoolWorker-2, started daemon)
D:/test/4.txt --- d:/Users/Desktop/test/4.txt
SpawnProcess(SpawnPoolWorker-1, started daemon)
D:/test/5.txt --- d:/Users/Desktop/test/5.txt多任务 - 协程
可迭代对象及检测方法 迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问直到所有的元素被访问完结束。迭代器只能往前不会后退。 我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用我们把这样的过程称为遍历也叫选代。
可迭代对象 1、可遍历对象就是可迭代对象。 2、列表、元组、字典、字符串都是可迭代对象。 3、100和自定义myclass对象默认都是不可迭代的。 4、myclass对象所属的类MyClass如果包含了__iter__()方法此时myclass就是一个可迭代对象。 5、可迭代对象的本质对象所属的类中包含了__iter__()方法。 可迭代对象的检测 from collections import Iterable result isinstance(待检测对象Iterable)
from collections import Iterabler isinstance({a:1, b:2}, Iterable)
print(r) # Truer isinstance(10, Iterable)
print(r) # False# 自定义一个类
class MyClass(object):pass
# 创建对象
myclass MyClass()
r isinstance(myclass, Iterable)
print(r) # False# 自定义一个类
class MyClass2(object):# 增加一个__iter__方法该方法就是一个迭代器def __iter__(self):pass
# 创建对象
myclass2 MyClass2()
r isinstance(myclass2, Iterable)
print(r) # True迭代器及其使用方法
迭代器的作用 1、记录当前迭代的位置 2、配合next()获取可迭代对象的下一个元素值 获取迭代器 iter(可迭代对象) 获取可迭代对象的值 next(迭代器) for循环的本质 1通过iter(要遍历的对象)获取要遍历的对象的迭代器 2next(迭代器)获取下一个元素 3帮我们捕获了StopIteration异常 自定义迭代器类 必须满足以下2点 1必须含有__iter__() 2必须含有__next__()
class MyIterator(object):def __iter__(self):pass# 当next(迭代器)的时候会自动调用该方法 def __next__(self):pass# 可迭代对象
data_list [1, 3, 5]
# for data in data_list:
# print(data)# 获取迭代器
data_iterator iter(data_list)# 根据迭代器获取下一个元素
value next(data_iterator)
print(value) # 1
value next(data_iterator)
print(value) # 3
value next(data_iterator)
print(value) # 5
value next(data_iterator)
print(value) # StopIteration自定义迭代对象
目标 能够自定义一个列表 1、MyList类
1初始化方法
2__iter__()方法对外提供迭代器
3addItem()方法用来添加数据2、自定义迭代器类MyListIterator
1初始化方法
2迭代器方法 __iter__()
3获取下一个元素值的方法 __next__()目标
mylist MyList()
for value in mylist:print(value)
# 1、MyList类
class MyList(object):# 1初始化方法def __init__(self):# 定义实例属性保存数据self.items []# 2__iter__()方法对外提供迭代器def __iter__(self):# 创建MyListIterator对象无此代码会报错iter() returned non-iterator of type NoneTypemylistIterator MyListIterator(self.items)# 返回迭代器return mylistIterator# 3addItem()方法用来添加数据def addItem(self, data):# 追加保存数据self.items.append(data)# 2、自定义迭代器类MyListIterator
class MyListIterator(object):# 1初始化方法def __init__(self, items):# 定义实例属性保存MyList类传递过来的Itemsself.items items# 记录迭代器迭代的位置self.current_index 0# 2迭代器方法 __iter__()def __iter__(self):pass# 3获取下一个元素值的方法 __next__()# next(mylistIterator)就会调用__next__()方法def __next__(self):# 判断当前的下标是否越界if self.current_index len(self.items):# 1根据下标获取下标对应的元素值data self.items[self.current_index]# 2下标位置1self.current_index 1# 3返回下标对应的数据return data# 如果越界直接抛出异常else:# raise 用于主动抛出异常StopIteration 停止迭代raise StopIterationif __name__ __main__:# 1、创建自定义列表对象mylist MyList()mylist.addItem(a)mylist.addItem(b)mylist.addItem(c)# 2、遍历for循环本质1iter(mylist)获取mylist对象的迭代器 -- MyList -- __iter__()2next(迭代器) 获取下一个值3捕获异常for value in mylist:print(value, end ) # 运行结果a b c 迭代器案列 - 斐波那契数列
迭代器应用 我们发现迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个己有的数据集合中读取的而是通过程序按照一定的规律计算生成的那么也就意味着可以不用再依赖一个已有的数据集合也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取这样可以节省大量的存储内存空间。 举个例子比如数学中有个著名的斐波拉契数列Fibonacci数列中第一个数为0第二个数为1其后的每一个数都可由前两个数相加得到1 1 2 3 5 8 13 …
现在我们想要通过for…in…循环来遍历迭代斐波那契数列中的前n个数。那么这个斐波那契数列我们就可以用迭代器来实现每次迭代都通过数学计算来生成下一个数。
核心思想
1a保存第一列的值b保存第二列的值
2abbab
3取a的值得到斐波那契数列自定义迭代器
1定义迭代器类
2类中必须有__iter__()方法
3类中必须有__next()方法目标
指定生成5列的斐波那契数列
fib Fibonacci(5)
value next(fib)
print(value)
class Fibonacci(object):def __init__(self, num):# 定义实例属性报错生成的列数self.num num# 定义变量保存斐波那契数列的第一列和第二列self.a 1self.b 1# 记录下标位置的实例属性self.current_index 0def __iter__(self):# 返回自己return selfdef __next__(self):# 判断列数是否超过生成的总列数if self.current_index self.num:# 定义变量保存a的值data self.aself.a, self.b self.b, self.aself.bself.current_index 1return dataelse:raise StopIterationif __name__ __main__:# 创建迭代器对象fib Fibonacci(6)# 迭代器本身又是一个迭代器for value in fib: # 运行结果1 1 2 3 5 8 print(value, end )生成器
生成器 利用迭代器我们可以在每次迭代获取数据通过next()方法时按照特定的规律进行生成。但是我们在实现一个迭代器时关于当前迭代到的状态需要我们自己记录进而才能根据当前状态生成下一个数据。为了达到记录当前态并配合next()函数进行迭代使用我们可以采用更简便的语法即生成器(generator)。生成器是一类特殊的迭代器按照一定的规律生成数列。 创建生成器的方法 方法一列表推导式 方法二函数中使用yield yield的作用 1、充当return作用 2、保存程序的运行状态并且暂停程序执行 3、当next的时候可以继续唤醒程序从yield位置继续向下执行 # 列表推导式
data_list [x*2 for x in range(10)]
print(data_list) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
for value in data_list:print(value, end ) # 0 2 4 6 8 10 12 14 16 18# 生成器的创建方法一列表推导式
data_generator (x*2 for x in range(10))
print(data_generator) # generator object genexpr at 0x010E27B0
# next(生成器)也能够得到下一个值
value next(data_generator)
print(---, value) # --- 0
for value in data_generator:print(value, end ) # 2 4 6 8 10 12 14 16 18# 普通函数
def test():return 10
r test()
print(r , r) # r 10# 生成器的创建方法二函数中使用了yield
def test1():yield 10
# g是一个生成器对象
g test1()
print(g) # generator object test1 at 0x014227F0
value next(g)
print(---, value) # --- 10生成器案例 - 斐波那契数列 思路
1、创建一个生成器目标实现斐波那契数列1定义变量a、b保存第一列和第二列2定义变量保存当前生成的位置3循环生成数据条件当前列数总列数4保存a的值5修改a和b的值abbab6返回a的值 yield2、定义变量保存生成器
next(生成器)得到下一个元素值
# 1、创建一个生成器
def fibonacci(n):# 目标实现斐波那契数列# 1定义变量a、b保存第一列和第二列a 1b 1# 2定义变量保存当前生成的位置current_index 0print(11111111111111)# 3循环生成数据条件当前列数 总列数while current_index n:# 4保存a的值data a# 5修改a和b的值a bb a ba, b b, abcurrent_index 1print(2222222222222)# 6返回a的值# yield作用1.充当return作用2.保存程序的运行状态并且暂停程序执行3.当next的时候可以继续换新程序从yield位置继续向下执行yield dataprint(33333333333333)if __name__ __main__:# 2、定义变量保存生成器fib fibonacci(6)# next(生成器)得到下一个元素值value next(fib)print(第1列, value)value next(fib)print(第2列, value)value next(fib)print(第3列, value)运行结果如下
11111111111111
2222222222222
第1列 1
33333333333333
2222222222222
第2列 1
33333333333333
2222222222222
第3列 2生成器 - 使用注意
return作用可以结束生成器的运行。执行到return以后看生成器会停止迭代抛出停止迭代的异常。send的作用能够启动生成器、并传递参数生成器.send(传递给生成器的值)
def fibonacci(n):a 1b 1current_index 0print(11111111111111)while current_index n:data aa, b b, abcurrent_index 1print(2222222222222)# 通过send传递参数给生成器xx yield dataprint(33333333333333)if xx 1:# 生成器中能使用return让生成器结束return return结束生成器的运行if __name__ __main__:fib fibonacci(6)value next(fib)print(第1列, value)try:value next(fib)print(第2列, value)# fib.send(1) xxxyield data 则xxx1value fib.send(1)print(第3列, value)except Exception as e:print(e)运行结果如下
11111111111111
2222222222222
第1列 1
33333333333333
2222222222222
第2列 1
33333333333333
return结束生成器的运行协程 - yield
协程 在不开辟新的线程的基础上实现多个任务。协程是一个特殊的生成器。 协程又称微线程纤程。英文名Coroutine。从技术的角度来说“协程就是你可以暂停执行的函数”。如果你把它理解成“就像生成器一样”那么你就想对了。 线程和进程的操作是由程序触发系统接口最后的执行者是系统协程的操作则是程序员。 协程存在的意义 对于多线程应用CPU通过切片的方式来切换线程间的执行线程切换时需要耗时(保存状态下次维续)。协程则只使用一个线程单线程在一个线程中规定某个代码块执行顺序。 协程的适用场景 当程序中存在大量不需要CPU的操作时(IO)适用于协程。 通俗的理解在一个线程中的某个函数可以在任何地方保存当前函数的一些临时变量等信息然后切换到另外一个函数中执行注意不是通过调用函数的方式做到的并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。 协程和线程差异 在实现多任务时线程切换从系统层面远不止保存和恢复CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文所以一秒钟切换个上百万次系统都抗的住。 协程的基本实现 1、创建work1的生成器
2、创建work2的生成器
3、获取生成器通过next运行生成器
import time# 1、创建work1的生成器
def work1():while True:print(正在执行work1...)yieldtime.sleep(0.5)# 2、创建work2的生成器
def work2():while True:print(正在执行work2......)yieldtime.sleep(0.5)if __name__ __main__:# 3、获取生成器通过next运行生成器w1 work1()w2 work2()while True:next(w1)next(w2)协程 - greenlet
目标 使用greenlet实现协程 greenlet是一个第三方的模块自行的调度的微线程。 Greenlet是python的一个C扩展来源于Stackless python旨在提供可自行调度的“微线程”即协程。generator实现的协程在yield value时只能得value返回给调用者(caller)。而在greenlet中target.switch(value)可以切换到指定的协程(target)然后yield value。greenlet用switch来表示协程的 切换从一个协程切换到另一个协程需要显式指定。 为了更好使用协程来完成多任务python中的greenlet模块对其封装从而使得切换任务变的更加简单。 使用步骤 1、安装greenlet模块 sudo pip3 install greenlet 2、导入greenlet模块from greenlet import greenlet 3、创建greenlet对象g1 greenlet(函数名) 4、切换任务g1.switch() greenlet 实现协程的步骤
1、导入模块
2、创建任务work1work2
3、创建greenlet对象
4、手动switch任务
import time
from greenlet import greenletdef work1():while True:print(正在执行work1...)time.sleep(0.5)# 切换到第二个任务g2.switch()def work2():while True:print(正在执行work2......)time.sleep(0.5)g1.switch()if __name__ __main__:# 创建greenlet的对象g1 greenlet(work1)g2 greenlet(work2)# 执行work1任务g1.switch()协程 - gevent
gevent也是第三方库自动调度协程自动识别程序中的耗时操作。 greenlet已经实现了协程但是这个需要人工切换是不是觉得太麻烦了不要捉急python还有一个比greenlet更强大的并且能够自动切换任务的第三方库gevent。 其原理是当一个greenlet遇到lO(指的是input output输入输出比如网络、文件操作等)操作时比如访问网络就自动切换到其他的greenlet等到IO操作完成再在适当的时候切换回来继续执行。 由于IO操作非常耗时经常使程序处于等待状态有了gevent为我们自动切换协程就保证总有greenlet在运行而不是等待IO。 使用步骤 1、安装gevent模块 sudo pip3 install gevent 2、导入gevent模块import gevent 3、指派任务g1 gevent.spawn(函数名, 参数1, 参数2, ...) 4、让主线程等待协程执行完毕后再退出g1.join 5、查看当前执行任务的协程名称gevent.getcurrent() Gevent不能识别time.sleep(0.5)耗时操作的问题 方法一替换time.sleep(0.5) gevent.sleep(0.5) 方法二打猴子补丁。
打猴子补丁步骤
1. 导入模块from gevent import monkey
2. 破解所有monkey.patch_all()
猴子补丁的作用
1在运行时替换方法、属性等
2在不修改第三方代码的情况下增加原来不支持的功能
3在运行时为内容中的对象增加patch而不是在磁盘中的源代码中增加gevent好处能够自动识别程序中的耗时操作在耗时的时候自动切换到其他的任务
1、导入模块
2、指派任务import time
import gevent
# 打补丁
# 导入monkey模块
from gevent import monkey
# 2.破解
monkey.patch_all()def work1():# 获取当前协程名称print(work1的协程名称, gevent.getcurrent())while True:print(正在执行work1...)# 默认情况下time.sleep()不能被gevent识别为耗时操作# 方法一 把time.sleep() -- gevent.sleep()# time.sleep(0.5)gevent.sleep(0.5)def work2():# 获取当前协程名称print(work2的协程名称, gevent.getcurrent())while True:print(正在执行work2......, gevent.getcurrent())# 方法二给gevent打补丁在不修改程序源代码的情况下为程序增加新的功能目的让gevent识别time.sleep()time.sleep(0.5)if __name__ __main__:# 指派任务g1 gevent.spawn(work1)g2 gevent.spawn(work2)# 让主线程等待协程执行完毕再退出g1.join()g2.join()运行结果如下
work1的协程名称 Greenlet at 0x3986710: work1
正在执行work1...
work2的协程名称 Greenlet at 0x3a29be0: work2
正在执行work2......
正在执行work1...
正在执行work2......
正在执行work1...
正在执行work2......进程、线程、协程对比
1概念
进程 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间不同进程通过进程间通信来通信。由于进程比较重量占据独立的内存所以上下文进程间的切换开销栈、寄存器、虚拟内存、文件句柄等比较大但相对比较稳定安全。资源分配的基本单位线程 线程是进程的一个实体是CPU调度和分派的基本单位它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源只拥有一点在运行中必不可少的资源如程序计数器一组寄存器和栈但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存上下文切换很快资源开销较少但相比进程不够稳定容易丢失数据。CPU调度的基本单位协程 协程是一种用户态的轻量级线程协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时将寄存器上下文和栈保存到其他地方在切回来的时候恢复先前保存的寄存器上下文和栈直接操作栈则基本没有内核切换的开销可以不加锁的访问全局变量所以上下文的切换非常快。协程单线程执行多任务
2三者之间的关系 切换效率 协程线程进程 高效率方式 进程协程 3应用场景
多进程 密集CPU任务需要充分使用多核CPU资源服务器大量的并行计算的时候用多进程。 缺陷多个进程之间通信成本高切换开销大。多线程 密集I/O任务网络I/O磁盘I/O数据库I/O使用多线程合适。 缺陷同一个时间切片只能运行一个线程不能做到高并行但是可以做到高并发。协程 当程序中存在大量不需要CPU的操作时IO适用于协程。 注意多线程请求返回是无序的那个线程有数据返回就处理那个线程而协程返回的数据是有序的。 缺陷单线程执行处理密集CPU和本地磁盘IO的时候性能较低。处理网络I/O性能还是比较高。
案例 - 并发下载器
目标 能够使用协程实现网络图片下载 核心方法 打开网址并返回对应的内容二进制流response_data urllib.request.urlopen(img_url) 批量把协程添加joingevent.joinall([gevent.spawn(函数1名, 参数1, 参数2, ...), gevent.spawn(函数2名, 参数1, 参数2, ...), ...]) 思路
1、定义要下载的图片路径
2、调用文件下载的函数专门下载文件文件下载函数
1、根据url地址请求网络资源
2、在本地创建文件准备保存
3、读取网络资源数据循环
4、把读取的网络资源写入到本地文件中
5、做异常捕获import urllib.request
import gevent
from gevent import monkey
monkey.patch_all()def download_img(imgUrl, file_name):# 1、根据url地址请求网络资源response_data urllib.request.urlopen(imgUrl)# 2、在本地创建文件准备保存try:with open(file_name, wb) as file:while True:# 3、读取网络资源数据循环file_data response_data.read(1024)# 判断读取的数据不为空if file_data:# 4、把读取的网络资源写入到本地文件中file.write(file_data)else:break# 5、做异常捕获except Exception as e:print(文件%s下载失败 % file_name)else:print(文件%s下载成功 % file_name)def main():# 1、定义要下载的图片路径img_url1 https://img0.baidu.com/it/u2617416616,2174928901fm253fmtautoapp138fJPEG?w342h500img_url2 https://img2.baidu.com/it/u3793863728,1272380745fm253fmtautoapp138fJPEG?w889h500img_url3 https://img2.baidu.com/it/u2535596420,3225767821fm253fmtautoapp138fJPEG?w500h731# 2、调用文件下载的函数专门下载文件# 2.1 普通下载# download_img(img_url1, 1.jpg)# download_img(img_url2, 2.jpg)# download_img(img_url3, 3.jpg)# 2.2 协程下载# 批量把协程给join(): 主线程等待所有协程执行完毕再退出gevent.joinall([gevent.spawn(download_img, img_url1, 1.jpg),gevent.spawn(download_img, img_url2, 2.jpg),gevent.spawn(download_img, img_url3, 3.jpg)])if __name__ __main__:main()