python并发与网络编程
Linux 操作系统及其组成
1.操作系统的作用
操作系统 : 与硬件交互
应用程序 --> 请求调用操作系统功能 --> 硬件调用(cpu,内存)
操作系统(OS)是管理计算机硬件与软件资源的计算机程序,同时也是计算机系统的内核与基石。操作系统需
要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系
统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
2.Linux 操作系统组成
一个典型的 Linux 操作系统组成为:Linux 内核,文件系统,命令行 shell,图形界面和桌面环境,并包各种工
具和应用软件。
1:Linux 内核: Linux 操作系统的核心代码
2:文件系统:通常指称管理磁盘数据的系统,可将数据以目录或文件的型式存储。每个文件系统都有自己的特
殊格式与功能
3:shell 命令: 接收用户命令,然后调用相应的应用程序,并根据用户输入的指令来反馈给用户指定的信息。
shell 命令:文件操作命令
linux 下的目录结构
Linux常用命令
1、pwd --print wrok directory 查看当前所在文件夹
2、cd 目录名 --change directory 切换文件夹
3、ls,ls -l,ls -a --list 查看当前文件夹下的内容
4、mkdir 目录名 --make directory 创建目录
5、touch 目录名 --touch 如果文件不存在,新建文件
6、rm 文件名 --remove 删除指定的文件名
7、rm -r liu --remove 删除文件夹liu
8、clear --clear 清屏
8、tar --
9、cp --copy 复制文件或者目录
10、mv -- 移动文件
11、cat -- 查看文件内容
IO
- 定义
在内存中存在数据交换的操作认为是IO操作,比如和终端交互 ,和磁盘交互,和网络交互等
- 程序分类
- IO密集型程序:在程序执行中有大量IO操作,而cpu运算较少。消耗cpu较少,耗时长。
- 计算密集型程序:程序运行中计算较多,IO操作相对较少。cpu消耗多,执行速度快,几乎没有阻塞。
文件
文件是保存在持久化存储设备(硬盘、U盘、光盘…)上的一段数据。从格式编码角度分为文本文件(打开后会自动解码为字符)、二进制文件(视频、音频等)。在Python里把文件视作一种类型的对象,类似之前学习过的其它类型。
字节串(bytes)
在python3中引入了字节串的概念,与str不同,字节串以字节序列值表达数据,更方便用来处理二进程数据。因此在python3中字节串是常见的二进制数据展现方式。
- 普通的ascii编码字符串可以在前面加b转换为字节串,例如:b’hello’
- 字符串转换为字节串方法 :str.encode()
- 字节串转换为字符串方法 : bytes.decode()
文件读写
对文件实现读写的基本操作步骤为:打开文件,读写文件,关闭文件
#所有文件都可以用二进制方式打开(b)
#但是二进制格式文件则不能用文本方式打开(后续读写出错)
f = open('a.py','r+') # 要求文件存在
f = open('a.py','w') # 文件不存在创建存在清空
f = open('a.py','a') # 文件不存在创建,存在追加
f = open('a.py','rb') # 加 b后续的读写都以字节串操作
f.close() #关闭文件对象
- 打开文件
file_object = open(file_name, access_mode='r', buffering=-1)
功能:打开一个文件,返回一个文件对象。
参数:file_name 文件名;
access_mode 打开文件的方式,如果不写默认为‘r’
文件模式 操作
r 以读方式打开 文件必须存在
w 以写方式打开
文件不存在则创建,存在清空原有内容
a 以追加模式打开
r+ 以读写模式打开 文件必须存在
w+ 以读写模式打开文件
不存在则创建,存在清空原有内容
a+ 以读写模式打开 追加模式
rb 以二进制读模式打开 同r
wb 以二进制写模式打开 同w
ab 以二进制追加模式打开 同a
rb+ 以二进制读写模式打开 同r+
wb+ 以二进制读写模式打开 同w+
ab+ 以二进制读写模式打开 同a+
buffering 1表示有行缓冲,默认则表示使用系统默认提供的缓冲机制。
返回值:成功返回文件操作对象。
- 读取文件
read([size]) 注意:返回结果是字符串 str
功能: 来直接读取文件中字符。
参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size最多读取给定数目个字符(字节)。
返回值: 返回读取到的内容
- 注意:文件过大时候不建议直接读取到文件结尾,读到文件结尾会返回空字符串。
f = open('dict.txt','r') data = f.read()# 读取文件到最后遇到空字符 #data = f.read(50)#读取文件中的前50个字符 print(data) f.close()
readline([size]) 注意:返回结果是字符串:str
功能: 用来读取文件中一行
参数: 如果没有给定size参数(默认值为-1)或者size值为负,表示读取一行,给定size表示最多读取制定的字符(字节)。
返回值: 返回读取到的内容f = open('dict.txt','r') data = f.readline(500)#最多读取500个字符(包含空字符) #data = f.readline()#读取一行 print(data) f.close()
readlines([sizeint]) 注意:返回结果是列表:list
功能: 读取文件中的每一行作为列表中的一项
参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size表示读取到size字符所在行为止。
返回值: 返回读取到的内容列表
文件对象本身也是一个可迭代对象,在for循环中可以迭代文件的每一行。
f = open('dict.txt','r') data = f.readlines(200) # 读取前200个字节所在的所有行的内容形成列表 # data = f.readlines() #读到最后 print(data) f.close()
for line in f:
print(line) #注意:返回结果是字符串:str
f = open('Install.txt','r')
data = f.read()
print(data)
# 循环读取文件内容
while True:
# 如果读到文件结尾 read()会读到空字符串
data = f.read(1024)
# 读到结尾跳出循环
if not data:
break
print(data)
# 读取文件一行内容
data = f.readline(5)
print(data)
# 读取内容形成列表
data = f.readlines(20) # 读取前20个字节所在的所有行
print(data)
f.close()
- 写入文件
write(string)
功能: 把文本数据或二进制数据块的字符串写入到文件中去
参数:要写入的内容
- 如果需要换行要自己在写入内容中添加\n
writelines(str_list)
功能:接受一个字符串列表作为参数,将它们写入文件。
参数: 要写入的内容列表f = open('a.py','w') # 写操作 f.write("hello \n") f.write("你好 \n") # 将列表中每一项分别写入文件内 l = ['hello world\n','hello kitty\n'] f.writelines(l) f.close() #a.py文件结果如下: hello 你好 hello world hello kitty
# 生成文件对象 f with open('a.py') as f: data = f.read() print(data) # with 语句块结束 f 自动销毁
- 关闭文件
打开一个文件后我们就可以通过文件对象对文件进行操作了,当操作结束后使用close()关闭这个对象可以防止一些误操作,也可以节省资源。
file_object.close()
- with操作
python中的with语句使用于对资源进行访问的场合,保证不管处理过程中是否发生错误或者异常都会执行规定的“清理”操作,释放被访问的资源,比如有文件读写后自动关闭、线程中锁的自动获取和释放等。
with语句的语法格式如下:
with context_expression [as target(s)]:
with-body
通过with方法可以不用close(),因为with生成的对象在语句块结束后会自动处理,所以也就不需要close了,但是这个文件对象只能在with语句块内使用。
with open('file','r+') as f:
f.read()
注意
- 加b的打开方式读写要求必须都是字节串
- 无论什么文件都可以使用二进制方式打开,但是二进制文件使用文本方式打开读写会出错
其他操作
刷新缓冲区
缓冲:系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,再由缓冲区送到磁盘中去。从磁盘中读数据,则一次从磁盘文件将一批数据读入到内存缓冲区中,然后再从缓冲区将数据送到程序的数据区。
缓冲区功能:减少和磁盘的交互次数,提高了读写效率。
刷新缓冲区条件:
- 缓冲区被写满
- 程序执行结束或者文件对象被关闭
- 行缓冲遇到换行
- 程序中调用flush()函数
f = open('a.py','w')
while True:
data = input(">>")
if not data:
break
f.write(data + '\n')
f.flush() # 刷新缓冲区
f.close()
flush()
该函数调用后会进行一次磁盘交互,将缓冲区中的内容写入到磁盘。
文件偏移量
'''
编写一个程序,向一个文件写入如下内容
1. 2019-1-1 18:18:18
2. 2019-1-1 18:18:20
每隔2秒写入一次,每条占一行。
ctrl-c/红点 结束程序,下次启动后序号要跟之前的连续
需要可以在编辑器中实时看到文件写入情况
'''
# import time
# while True:
# f = open('a.py','w')
# f.write('2019-1-1 18:18:18\n')
# f.write('2019-1-1 18:18:20\n')
# print('---------------')
# time.sleep(2)
import time
f = open('a.py','ab+')#因为要实时看到,每次追加,所以用a+,w只会每次修改,不能叠加.
#文件偏移量定位到开头
f.seek(0)
n = 0
#获取多少行
for line in f:
n += 1
print (n)
# while True:
# n += 1
# time.sleep(2)
# s = '%d. %s\n'%(n,time.ctime())
# f.write(s.encode())
# f.flush() #随时查看
- 定义
打开一个文件进行操作时系统会自动生成一个记录,记录中描述了我们对文件的一系列操作。其中包括每次操作到的文件位置。文件的读写操作都是从这个位置开始进行的。
- 基本操作
tell()
功能:获取文件偏移量大小
seek(offset[,whence])
功能:移动文件偏移量位置
参数:offset 代表相对于某个位置移动的字节数。负数表示向前移动,正数表示向后移动。
whence是基准位置的默认值为 0,代表从文件开头算起,1代表从当前位置算起,2 代表从文件末尾算起。
- 必须以二进制方式打开文件时基准位置才能是1或者2
文件描述符
- 定义
系统中每一个IO操作都会分配一个整数作为编号,该整数即这个IO操作的文件描述符。
- 获取文件描述符
fileno()
通过IO对象获取对应的文件描述符
文件管理函数
- 获取文件大小
os.path.getsize(file)
- 查看文件列表
os.listdir(dir)
- 查看文件是否存在
os.path.exists(file)
- 判断文件类型
os.path.isfile(file)
- 删除文件
os.remove(file)
网络编程基础
计算机网络功能主要包括实现资源共享,实现数据信息的快速传递。
OSI七层模型
制定组织: ISO(国际标准化组织)
作用:使网络通信工作流程标准化
应用层 : 提供用户服务,具体功能有应用程序实现
表示层 : 数据的压缩优化加密
会话层 : 建立用户级的连接,选择适当的传输服务
传输层 : 提供传输服务
网络层 : 路由选择,网络互联
链路层 : 进行数据交换,控制具体数据的发送
物理层 : 提供数据传输的硬件保证,网卡接口,传输介质
优点
- 建立了统一的工作流程
- 分部清晰,各司其职,每个步骤分工明确
- 降低了各个模块之间的耦合度,便于开发
四层模型(TCP/IP模型)
背景 : 实际工作中工程师无法完全按照七层模型要求操作,逐渐演化为更符合实际情况的四层
数据传输过程
- 发送端由应用程序发送消息,逐层添加首部信息,最终在物理层发送消息包。
- 发送的消息经过多个节点(交换机,路由器)传输,最终到达目标主机。
- 目标主机由物理层逐层解析首部消息包,最终到应用程序呈现消息。
网络协议
在网络数据传输中,都遵循的规定,包括建立什么样的数据结构,什么样的特殊标志等。
网络基础概念
- IP地址
功能:确定一台主机的网络路由位置
查看本机网络地址命令: ifconfig(linux命令),ipconfig(wind命令)
结构
IPv4 点分十进制表示 172.40.91.185 每部分取值范围0–255(现在大部分电脑都是IPv4)
IPv6 128位 扩大了地址范围
- 域名
定义: 给网络服务器地址起的名字
作用: 方便记忆,表达一定的含义
ping [ip] : 测试和某个主机是否联通
- 端口号(port)
作用:端口是网络地址的一部分,用于区分主机上不同的网络应用程序。
特点:一个系统中的应用监听端口不能重复
取值范围: 1 – 65535
1–1023 系统应用或者大众程序监听端口
1024–65535 自用端口
传输层服务
面向连接的传输服务(基于TCP协议的数据传输)
-
传输特征 : 提供了可靠的数据传输,可靠性指数据传输过程中无丢失,无失序,无差错,无重复。
-
实现手段 : 在通信前需要建立数据连接,通信结束要正常断开连接。
三次握手(建立连接)
客户端向服务器发送消息报文请求连接
服务器收到请求后,回复报文确定可以连接
客户端收到回复,发送最终报文连接建立
四次挥手(断开连接)
主动方发送报文请求断开连接
被动方收到请求后,立即回复,表示准备断开
被动方准备就绪,再次发送报文表示可以断开
主动方收到确定,发送最终报文完成断开
- 适用情况 : 对数据传输准确性有明确要求,传数文件较大,需要确保可靠性的情况。比如:网页获取,文件下载,邮件收发。
面向无连接的传输服务(基于UDP协议的数据传输)
-
传输特点 : 不保证传输的可靠性,传输过程没有连接和断开,数据收发自由随意。
-
适用情况 : 网络较差,对传输可靠性要求不高。比如:网络视频,群聊,广播
socket套接字编程
套接字介绍
-
套接字 : 实现网络编程进行数据传输的一种技术手段
-
Python实现套接字编程:import socket
-
套接字分类
流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字)
数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现udp网络传输方案。(无连接–udp协议–不可靠–数据报套接字)
tcp套接字编程
服务端流程
'''
tcp_server tcp套接字服务端功能流程
注意:注意流程及函数的使用
'''
import socket
from time import sleep
# 创建tcp套接字
sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址
sockfd.bind(('0.0.0.0', 8888))
# 设置监听
sockfd.listen(5)
# 阻塞等待客户端连接
while True:
print('Waiting for connect.....')
try:
connfd, addr = sockfd.accept() # 等待处理客户端连接要求
print('Connect from', addr) # 打印连接的客户端
except KeyboardInterrupt:
print('服务端退出')
break
except Exception as e:
print(e)
continue
# 收发消息
while True:
data = connfd.recv(1024) # 接收客户端消息
# 连接的客户端退出,recv会立即返回空字符串
if not data: # 连接的客户端退出recv会立即返回空字符串
break
print(data.decode())
sleep(0.1)
n = connfd.send(b'thanks#') # 'thanks#'共7个字节
print('Send %d bytes' % n) # 所有 %d = 7
connfd.close()
# 关闭套接字
sockfd.close()
- 创建套接字
sockfd=socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)
功能:创建套接字
参数: socket_family 网络地址类型 AF_INET表示ipv4
socket_type 套接字类型 SOCK_STREAM(流式) SOCK_DGRAM(数据报)
proto 通常为0 选择子协议
返回值: 套接字对象
- 绑定地址
本地地址 : ‘localhost’ , ‘127.0.0.1’
网络地址 : ‘172.40.91.185’
自动获取地址: ‘0.0.0.0’
sockfd.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port) ('0.0.0.0',8888)
- 设置监听
sockfd.listen(n)
功能 : 将套接字设置为监听套接字,确定监听队列大小
参数 : 监听队列大小
- 等待处理客户端连接请求
connfd,addr = sockfd.accept()
功能: 阻塞等待处理客户端请求
返回值: connfd 客户端连接套接字
addr 连接的客户端地址
- 消息收发
data = connfd.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容
n = connfd.send(data)
功能 : 发送消息
参数 :要发送的内容 bytes格式
返回值: 发送的字节数
- 关闭套接字
sockfd.close()
功能:关闭套接字
客户端流程
'''
tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
data = input('Msg:')
# data为空退出循环
if not data:
break
sockfd.send(data.encode())#发送字节串
data = sockfd.recv(1024)
print('Server:',data.decode())
#关闭套接字
sockfd.close()
#结果:客户端输入:Msg:11111111
显示:Server:thanks#
输入空时结束客户端输入
上述程序的服务端:Waiting for connect.....
Connect from ('***.***.*.***', 58779)
11111111
Send 7 bytes
- 创建套接字
注意:只有相同类型的套接字才能进行通信
- 请求连接
sockfd.connect(server_addr)
功能:连接服务器
参数:元组 服务器地址
- 收发消息
注意: 防止两端都阻塞,recv send要配合
- 关闭套接字
tcp 套接字数据传输特点
- tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。
- tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError
- 一个监听套接字可以同时连接多个客户端,也能够重复被连接
网络收发缓冲区
- 网络缓冲区有效的协调了消息的收发速度
- send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞。
tcp粘包
原因:tcp以字节流方式传输,没有消息边界。多次发送的消息被一次接收,此时就会形成粘包。
影响:如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响。
处理方法
- 人为的添加消息边界
- 控制发送速度
UDP套接字编程
服务端流程
'''
udp_server.py 服务端
'''
from socket import *
#创建udp套接字
sockfd = socket(AF_INET,SOCK_DGRAM)
#绑定地址
server_addr = (('0.0.0.0',8888))
sockfd.bind(server_addr)
#循环收发
while True:
data,addr = sockfd.recvfrom(5)
print('收到的消息:',data.decode())
sockfd.sendto(b'Thanks',addr)
#关闭套接字
sockfd.close()
- 创建数据报套接字
sockfd = socket(AF_INET,SOCK_DGRAM)
- 绑定地址
sockfd.bind(addr)
- 消息收发
data,addr = sockfd.recvfrom(buffersize)
功能: 接收UDP消息
参数: 每次最多接收多少字节
返回值: data 接收到的内容
addr 消息发送方地址
n = sockfd.sendto(data,addr)
功能: 发送UDP消息
参数: data 发送的内容 bytes格式
addr 目标地址
返回值:发送的字节数
- 关闭套接字
sockfd.close()
客户端流程
'''
udp_client.py udp客户端
'''
from socket import *
#服务器地址
ADDR = ('***.***.*.***',8888)
sockfd = socket(AF_INET,SOCK_DGRAM)
while True:
data = input('Msg:')
if not data:
break
sockfd.sendto(data.encode(),ADDR)
msg,addr = sockfd.recvfrom(1024)
print('From server:',msg.decode())
sockfd.close()
#结果:客户端输入:Msg:11111111
From server:thanks#
输入空时结束客户端输入
上述程序的服务端:收到的消息: 11111111
- 创建套接字
- 收发消息
- 关闭套接字
总结 :tcp套接字和udp套接字编程区别
- 流式套接字是以字节流方式传输数据,数据报套接字以数据报形式传输
- tcp套接字会有粘包,udp套接字有消息边界不会粘包
- tcp套接字保证消息的完整性,udp套接字则不能
- tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
- tcp套接字使用send,recv收发消息,udp套接字使用sendto,recvfrom
#查单词服务端:
from socket import *
f = open('dict.txt')
def find_word(word):
f.seek(0) # 每次查找前先将位置定位到开头
for line in f:# 每次获取一行
w = line.split(' ')[0]
# 如果遍历到的单词已经大于目标,就结束查找
if w > word:
break
elif w == word:
return line
return "没有找到单词"
sockfd = socket(AF_INET,SOCK_DGRAM)
sockfd.bind(('0.0.0.0',8888))
while True:# 循环接受单词返回单词解释
word,addr = sockfd.recvfrom(1024)
# 调用查单词
result = find_word(word.decode())
sockfd.sendto(result.encode(),addr)
f.close()
sockfd.close()
'''
使用udp完成从客户端输入单词,得到单词解释
客户端可以循环输入单词
'''
#查找单词客户端
from socket import *
ADDR = ('***.***.*.***',8888)
sockfd = socket(AF_INET,SOCK_DGRAM)
dict = open('dict.txt','r')
while True:
word = input('word:')
if not word:
break
sockfd.sendto(word.encode(),ADDR)
word,addr = sockfd.recvfrom(1024)
print(word.decode())
sockfd.close()
socket套接字属性
【1】 sockfd.type 套接字类型
【2】 sockfd.family 套接字地址类型
【3】 sockfd.getsockname() 获取套接字绑定地址
【4】 sockfd.fileno() 获取套接字的文件描述符
【5】 sockfd.getpeername() 获取连接套接字客户端地址
【6】 sockfd.setsockopt(level,option,value)
功能:设置套接字选项
参数: level 选项类别 SOL_SOCKET
option 具体选项内容
value 选项值
struct模块进行数据打包
'''
使用udp在客户端输入学生信息,在服务端将学生信息写入文件
要求每个学生的信息各占一行
'''
#学生信息服务端:
from socket import *
import struct
#定义数据传输格式
st = struct.Struct('i20sif')
#创建套接字接受内容
s = socket(AF_INET,SOCK_DGRAM)
s.bind(('0.0.0.0',8888))
#打开文件
f = open('student.txt','a')
#循环接收单词
while True:
data,addr = s.recvfrom(1024)
#将信息转换为元组数据(1,b'lili',13,85)
data =st.unpack(data)
info = '%d %-10s %d %.2f\n'%data #-为左对齐,10为宽度
f.write(info)
f.flush()
f.close()
s.close()
#学生信息客户端:
from socket import *
import struct
#定义数据传输格式
st = struct.Struct('i20sif')
#创建套接字接受内容
s = socket(AF_INET,SOCK_DGRAM)
#服务端地址
ADDR = ('***.***.*.***',8888)
while True:
print('------------------')
id = int(input('ID:'))
name = input('Name:').encode()
age = int(input('Age:'))
score = float(input('Score:'))
#数据打包
data = st.pack(id,name,age,score)
s.sendto(data,ADDR)
sockfd.close()
- 原理: 将一组简单数据进行打包,转换为bytes格式发送。或者将一组bytes格式数据,进行解析。
- 接口使用
Struct(fmt)
功能: 生成结构化对象
参数:fmt 定制的数据结构
st.pack(v1,v2,v3....)
功能: 将一组数据按照指定格式打包转换为bytes
参数:要打包的数据
返回值: bytes字节串
st.unpack(bytes_data)
功能: 将bytes字节串按照指定的格式解析
参数: 要解析的字节串
返回值: 解析后的内容
struct.pack(fmt,v1,v2,v3...)
struct.unpack(fmt,bytes_data)
说明: 可以使用struct模块直接调用pack unpack。此时这两函数第一个参数传入fmt。其他用法功能相同
HTTP传输
HTTP协议 (超文本传输协议)
-
用途 : 网页获取,数据的传输
-
特点
- 应用层协议,传输层使用tcp传输
- 简单,灵活,很多语言都有HTTP专门接口
- 无状态,协议不记录传输内容
- http1.1 支持持久连接,丰富了请求类型
- 网页请求过程
1.客户端(浏览器)通过tcp传输,发送http请求给服务端
2.服务端接收到http请求后进行解析
3.服务端处理请求内容,组织响应内容
4.服务端将响应内容以http响应格式发送给浏览器
5.浏览器接收到响应内容,解析展示
HTTP请求(request)
'''
http请求过程演示
'''
from socket import *
s = socket()
s.bind(('0.0.0.0',8888))
s.listen(3)
c,addr = s.accept()
print('Connect from',addr)
#获取请求
data = c.recv(4096)
c.send(b'hello world')
print(data)
c.close()
s.close()
'''
httpserver v1.0
基本要求: 1.获取来自浏览器的请求
2.判断请求内容是否为/
3.如果是,则将 index.html 发送给浏览器,如果不是,则告知浏览器sorry
4.注意组织http响应格式,判断 200 or 404
'''
from socket import *
# 处理客户端请求
def request(connfd):
# 直接获取HTTP请求
data = connfd.recv(4096)
# 防止浏览器断开
if not data:
return
# 提取请求内容
request_line = data.decode().split('\n')[0]
info = request_line.split(' ')[1]
# 判断请求内容是否为 /
if info == '/':
with open('index.html') as f:
response="HTTP/1.1 200 OK\r\n"
response+="Content-Type:text/html\r\n"
response+="\r\n"
response+=f.read()
else:
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += "<h1>Sorry....</h1>"
# 发送给浏览器
connfd.send(response.encode())
# 搭建tcp服务
sockfd = socket()
sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sockfd.bind(('0.0.0.0',8888))
sockfd.listen(5)
while True:
connfd,addr = sockfd.accept()
print("Connect from",addr)
request(connfd) # 具体处理客户端请求
- 请求行 : 具体的请求类别和请求内容
GET / HTTP/1.1
请求类别 请求内容 协议版本
请求类别:每个请求类别表示要做不同的事情
GET : 获取网络资源
POST :提交一定的信息,得到反馈
HEAD : 只获取网络资源的响应头
PUT : 更新服务器资源
DELETE : 删除服务器资源
CONNECT
TRACE : 测试
OPTIONS : 获取服务器性能信息
- 请求头:对请求的进一步解释和描述
Accept-Encoding: gzip
- 空行
- 请求体: 请求参数或者提交内容
http响应(response)
- 响应格式:响应行,响应头,空行,响应体
- 响应行 : 反馈基本的响应情况
HTTP/1.1 200 OK
版本信息 响应码 附加信息
响应码 :
1xx 提示信息,表示请求被接收
2xx 响应成功
3xx 响应需要进一步操作,重定向
4xx 客户端错误
5xx 服务器错误
- 响应头:对响应内容的描述
Content-Type: text/html
- 响应体:响应的主体内容信息
多任务编程
-
意义: 充分利用计算机CPU的多核资源,同时处理多个应用程序任务,以此提高程序的运行效率。
-
实现方案 :多进程 , 多线程
进程(process)
进程理论基础
- 定义 : 程序在计算机中的一次运行。
- 程序是一个可执行的文件,是静态的占有磁盘。
- 进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
-
系统中如何产生一个进程
【1】 用户空间通过调用程序接口或者命令发起请求
【2】 操作系统接收用户请求,开始创建进程
【3】 操作系统调配计算机资源,确定进程状态等
【4】 操作系统将创建的进程提供给用户使用
-
进程基本概念
-
cpu时间片:如果一个进程占有cpu内核则称这个进程在cpu时间片上。
-
PCB(进程控制块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程。
-
进程ID(PID): 系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复。
Linux查看进程ID : ps -aux
-
父子进程 : 系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0个或多个子进程。父子进程关系便于进程管理。
查看进程树: pstree
- 进程状态
- 三态
就绪态 : 进程具备执行条件,等待分配cpu资源
运行态 : 进程占有cpu时间片正在运行
等待态 : 进程暂时停止运行,让出cpu
- 五态 (在三态基础上增加新建和终止)
新建 : 创建一个进程,获取资源的过程
终止 : 进程结束,释放资源的过程
- 状态查看命令 : ps -aux --> STAT列
S 等待态 R 执行态 Z 僵尸
`+` 前台进程 l 有多线程的
- 进程的运行特征
【1】 多进程可以更充分使用计算机多核资源
【2】 进程之间的运行互不影响,各自独立
【3】 每个进程拥有独立的空间,各自使用自己空间资源
基于fork的多进程编程
fork使用
'''
fork 进程创建演示
'''
import os
from time import sleep
# 创建子进程 #父子进程互不影响,独立进行,几乎同时运行
pid = os.fork() # 生成父子进程,父进程的pid为子进程pid是正整数,子进程pid返回为0.
if pid < 0:
print('create process failed')
elif pid == 0:
# 只有子进程执行
sleep(3)
print('the new process')
else:
# 只有父进程执行
sleep(4)
print('the old process')
print('process test over')
'''
fork进程演示细节
'''
import os
import time
print('----------------')
a=1
def fun():
print('fun-------')
pid = os.fork()
if pid <0:
print('create process failde')
elif pid == 0:
print('child process')
print('a =',a ) # 从父进程空间拷贝了变量
fun()
a = 1000 # 只是修改了自己空间的a
else:
time.sleep(2)
print('parent process')
print('a',a)
#结果:
----------------
child process
a = 1
fun-------
parent process
a 1
pid = os.fork()
功能: 创建新的进程
返回值:整数,如果创建进程失败返回一个负数,如果成功则在原有进程中返回新进程的PID,在新进程中返回0
注意
- 子进程会复制父进程全部内存空间,从fork下一句开始执行。
- 父子进程各自独立运行,运行顺序不一定。
- 利用父子进程fork返回值的区别,配合if结构让父子进程执行不同的内容几乎是固定搭配。
- 父子进程有各自特有特征比如PID PCB 命令集等。
- 父进程fork之前开辟的空间子进程同样拥有,父子进程对各自空间的操作不会相互影响。
进程相关函数
"""
获取进程PID号
"""
import os
from time import sleep
pid = os.fork()
if pid < 0:
print("Error")
elif pid == 0:
sleep(1)
print("Child PID:",os.getpid()) # 自己pid
print("Get parent PID:",os.getppid()) # 父pid
else:
print("Parent PID:", os.getpid()) # 自己pid
print("Get child PID:",pid)
#结果 :
parent PID: 22430
child PID: 22431
get child PID 22431
get parent PID: 22430
'''
fork进程演示细节,孤儿进程及进程退出演示
'''
import os,sys
pid = os.fork()
#父子进程退出对对方互不影响,对方依然运行
if pid <0:
print('error')
elif pid == 0:
#os._exit(0) #子进程退出
print('child process:')
else:
sys.exit('退出父进程')
print('parent process:')
#结果 :
退出父进程
child process:
如果 sys.exit 注释后则
为 :parent process:
os.getpid()
功能: 获取一个进程的PID值
返回值: 返回当前进程的PID
os.getppid()
功能: 获取父进程的PID号
返回值: 返回父进程PID
os._exit(status)
功能: 结束一个进程
参数:进程的终止状态
sys.exit([status])
功能:退出进程
参数:整数 表示退出状态
字符串 表示退出时打印内容
孤儿和僵尸
- 孤儿进程 : 父进程先于子进程退出,此时子进程成为孤儿进程。
特点: 孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理。
- 僵尸进程 : 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程。
特点: 僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸进程会浪费系统的内存资源。
- 如何避免僵尸进程产生
- 使用wait函数处理子进程退出
'''
模拟僵尸进程产生
'''
import os,sys
pid = os.fork()
if pid <0:
print('error')
elif pid == 0:
print('child PID:', os.getpid()) #子pid
sys.exit(5) #子进程结束但不影响父进程的执行
else:
'''
os.wait()处理僵尸
'''
pid,status = os.wait()
print('pid:',pid)
print('status',os.WEXITSTATUS(status))
while True: #父进程不退出
pass
#结果:
child PID: 22701
pid: 22701
status 5
```
pid,status = os.wait()
功能:在父进程中阻塞等待处理子进程退出
返回值: pid 退出的子进程的PID
status 子进程退出状态
```
-
创建二级子进程处理僵尸
‘’’
僵尸进程处理
‘’’
import os
import time
def fun1():
for i in range(3):
time.sleep(2)
print('写代码')
def fun2():
for j in range(2):
time.sleep(4)
print('测代码')
pid = os.fork()
if pid <0:
print('error')
elif pid == 0:
p = os.fork() #创建二级子进程
if p == 0:
fun1()
else:
os._exit(0)
else:
os.wait() #等待回收一级子进程
fun2()
#结果:写代码 写代码 测代码 写代码 测代码
```
【1】 父进程创建子进程,等待回收子进程
【2】 子进程创建二级子进程然后退出
【3】 二级子进程称为孤儿,和原来父进程一同执行事件
-
通过信号处理子进程退出
原理: 子进程退出时会发送信号给父进程,如果父进程忽略子进程信号,则系统就会自动处理子进程退出。
方法: 使用signal模块在父进程创建子进程前写如下语句 :
import signal signal.signal(signal.SIGCHLD,signal.SIG_IGN)
特点 : 非阻塞,不会影响父进程运行。可以处理所有子进程退出
群聊聊天室
''' 功能 : 类似qq群功能 【1】 有人进入聊天室需要输入姓名,姓名不能重复 【2】 有人进入聊天室时,其他人会收到通知:xxx 进入了聊天室 【3】 一个人发消息,其他人会收到:xxx : xxxxxxxxxxx 【4】 有人退出聊天室,则其他人也会收到通知:xxx退出了聊天室 【5】 扩展功能:服务器可以向所有用户发送公告:管理员消息: xxxxxxxxx 1. 需求分析 : 达到什么样的目的,形成什么样的运行流程 * 启动软件--> 输入姓名-->进入-->聊天(收发随意)-> 退出 | | ---不允许进入 2. 技术点的确定 * 消息发送流程: client-->server->client * 网络选择 : udp 进行数据传输 * 存储内容: name address {name:address,} [(name,address),] * 消息收发互不影响 使用多进程,让消息发送接收各占一个进程 3. 结构和注意事项 * 采用什么结构封装 : 函数 * 编写一个功能,测试一个功能 * 注释 4. 功能模块分析 * 网络搭建 客户端 : udp客户端 服务端 : udp服务端 * 进入聊天室 客户端 : * 输入姓名 * 向服务器发送请求 * 接收结果 * 允许则进入聊天,不允许则重写输入姓名 服务器 : * 接受请求,区分请求类型 * 判断用户是否存在 * 如果允许进入,将其加入存储结构 * 通知其他用户,告知本人可以登录 * 如果不允许则结束,告知用户不可以进入 * 聊天 客户端 : * 创建新的进程 * 一个进程循环的发送消息 * 一个进程循环的接收消息 服务端 : * 接收请求,判断请求类型 * 将消息转发给其他用户 xxx : xxxxxxxxx * 退出聊天室 客户端 : * 输入quit 或者 ctrl-c退出 * 发送请求给服务端 * 结束进程 服务端 : * 接收请求,判断请求类型 * 将退出消息发送给其他人 * 将用户从字典删除 * 管理员消息 5. 协议设定 请求格式: 登录 : L name 聊天 : C name text 退出 : Q name 响应格式: 登录 : 成功(OK) 失败(失败原因) 退出 : 给客户端发送 EXIT 让客户端接收进程退出 ''' #利用UDP创建服务端 from socket import * import os,sys #服务端地址 ADDR = ('0.0.0.0',8888) #存储用户的结构{name:address} user = {} #处理登入 def do_login(s,name,addr): if name in user or '管理员' in name: s.sendto('用户存在'.encode(),addr) return #加入用户 msg = '欢迎 %s 进入聊天室'%name for i in user: s.sendto(msg.encode(),user[i]) user[name] = addr s.sendto(b'OK',addr) #聊天 def do_chat(s,name,text): msg = '\n %s:%s'%(name,text) for i in user: #刨除本人 if i!= name: s.sendto(msg.encode(),user[i]) #退出 def do_quit(s,name): msg = '\n %s 退出聊天室'%name for i in user: if i != name: #其他人 s.sendto(msg.encode(),user[i]) else: s.sendto(b'EXIT',user[i]) del user[name] #删除该用户 #接收请求,分发给不同方法处理 def do_request(s): while True: #循环接收来自客户端请求 data,addr = s.recvfrom(1024) tmp = data.decode().split(' ',2)#表示只切割前连个部分 #根据不同请求类型分发函数处理 #L 进入 C聊天 Q退出 if tmp[0] == 'L': do_login(s,tmp[1],addr) elif tmp[0] == 'C': do_chat(s,tmp[1],tmp[2]) elif tmp[0] == 'Q': if tmp[1] in user: do_quit(s, tmp[1]) #搭建网络 def main(): #udp客户端 s = socket(AF_INET,SOCK_DGRAM) s.bind(ADDR) #开辟新进程处理管理员消息 pid = os.fork() if pid == 0: #子进程处理管理员消息 while True: msg = input('管理员消息:') msg = 'C 管理员 ' +msg s.sendto(msg.encode(),ADDR) else: do_request(s)#处理客户端请求 main()
#利用UDP创建客户端 from socket import * import os,sys #服务端地址 ADDR = ('***.***.*.***',8888) #进入聊天室 def login(s): while True: try: name = input('请输入名称:') if not name: continue except KeyboardInterrupt: sys.exit('谢谢使用') msg = 'L '+name s.sendto(msg.encode(),ADDR) #接收反馈结果 data,addr = s.recvfrom(128) if data.decode() == 'OK': print('你已进入聊天室') return name else: print(data.decode()) #子进程函数 def send_msg(s,name):#循环发消息 while True: try: text = input('头像:') except KeyboardInterrupt: text = 'quit' if text.strip() == 'quit': msg = 'Q' + name s.sendto(msg.encode(),ADDR) sys.exit('退出聊天室') msg = 'C %s %s'%(name,text) s.sendto(msg.encode(),ADDR) def recv_msg(s):#循环接收消息 while True: data,addr = s.recvfrom(2048) #接收进程退出 if data.decode() == 'EXIT': sys.exit() print(data.decode()+'\n头像:',end='') #客户端启动函数 def main(): s = socket(AF_INET,SOCK_DGRAM) name = login(s) #请求进入聊天室 #创建新的进程 pid = os.fork() if pid < 0: print('error') return elif pid == 0: send_msg(s,name) #子进程接收消息 else: recv_msg(s) #父进程接收消息 main()
multiprocessing 模块创建进程
进程创建方法
'''
创建进程演示:1.编写进程函数
2.生成进程对象
3.启动进程
4.回收进程
'''
import multiprocessing as mp
import time
a = 1
#进程执行函数
def fun():
print('开始一个进程')
time.sleep(2)
global a
# a =1000
print('a = ',a)
a =100
print('子进程结束')
#创建进程对象
p = mp.Process(target=fun)
p.start() #开始进程
#父进程执行事件
time.sleep(3)
print('父进程执行了')
p.join()#回收进程()内为超时时间
print('a:',a)
print('----------------')
#结果 :
开始一个进程
a = 1
子进程结束
父进程执行了
a: 1
----------------
#因为只有子进程调用了函数 , 函数从上到下执行 , 所以 a=1,如果 a 放在 print 上则第一 a=1000, 另一个依然为 a=1
'''
创建多个子进程
'''
import multiprocessing as mp
from time import sleep
import os
def th1():
sleep(2)
print('吃饭')
print(os.getppid(),'--',os.getpid())
def th2():
sleep(3)
print('睡觉')
print(os.getppid(),'--',os.getpid())
def th3():
sleep(4)
print('打豆豆')
print(os.getppid(),'--',os.getpid())
things = [th1,th2,th3]
jobs = []
for th in things:
p = mp.Process(target = th)
jobs.append(p) #对进程对象进行存储
p.start()
#一起回收
for i in jobs:
i.join()
'''
给进程函数传参
'''
from multiprocessing import Process
from time import sleep
#含有参数的进程函数
def worker(second,name):
for i in range(3):
sleep(second)
print('I am %s'%name)
print('I am working---')
#通过args 给函数位置传参
p = Process(target=worker,args=(2,'xiaoming'))
# p = Process(target=worker,args=(2,),kwargs={'name':'xiaoxiao'})
p.start()
p.join()
-
流程特点
【1】 将需要子进程执行的事件封装为函数
【2】 通过模块的Process类创建进程对象,关联函数
【3】 可以通过进程对象设置进程信息及属性
【4】 通过进程对象调用start启动进程
【5】 通过进程对象调用join回收进程 -
基本接口使用
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数
args 元组,用于给target函数位置传参
kwargs 字典,给target函数键值传参
p.start()
功能 : 启动进程
注意:启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建
p.join([timeout])
功能:阻塞等待回收进程
参数:超时时间
'''
timeit.py文件:实质为一个装饰器
求10000以内所有的质数之和,请分别使用单进程,4进程,10进程完成,
然后记录每次完成的时间,求和用sum()函数
'''
import time
def timeit(func):#函数装饰器
def wrapper(*args,**kwargs):
start_time = time.time()
res = func(*args,**kwargs)
end_time = time.time()
print('%s函数运行时间:%.3f'%(func.__name__,end_time -start_time))
return res
return wrapper
from timeit import timeit
from multiprocessing import Process
#判断是否为质数
def isPrime(i):
if i <= 1:
return False
for j in range(2,i):
if i % j ==0:
return False
return True
#单进程完成任务
@timeit
def no_multi_process():
prime = []
for i in range(1,100001):
if isPrime(i):
prime.append(i)
sum(prime)
#自定义进程类
class Prime(Process):
def __init__(self, prime, begin, end):
self.prime = prime
self.begin = begin # 取值的开始
self.end = end # 取值的末尾
super().__init__()
def run(self):
for i in range(self.begin, self.end):
if isPrime(i):
self.prime.append(i)
sum(self.prime)
@timeit
def use_4_process():
prime = []
jobs = []
for i in range(1, 100001, 25000):
p = Prime(prime, i, i + 25000)
jobs.append(p)
p.start()
[i.join() for i in jobs]
@timeit
def use_10_process():
prime = []
jobs = []
for i in range(1, 100001, 10000):
p = Prime(prime, i, i + 10000)
jobs.append(p)
p.start()
[i.join() for i in jobs]
if __name__ == '__main__':
no_multi_process()
use_4_process()
use_10_process()
#结果:no_multi_process函数运行时间:45.852
use_4_process函数运行时间:27.557
use_10_process函数运行时间:22.066
注意
- 使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响。
- 子进程只运行target绑定的函数部分,其余内容均是父进程执行内容。
- multiprocessing中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成。
- multiprocessing创建的子进程中无法使用标准输入
- 进程对象属性
"""
进程对象属性
"""
from multiprocessing import Process
import time
def tm():
for i in range(3):
print(time.ctime())
time.sleep(2)
p = Process(target = tm,name = 'Tarena')
# 父进程退出,其所有子进程也退出
p.daemon = True
p.start() # 进程真正产生
print("Name:",p.name) # 进程名
print("PID:",p.pid) # pid号
print("is alive:",p.is_alive()) # 是否在生命周期
#结果:Name: Tarena
PID: 17460
is alive: True
p.name 进程名称
p.pid 对应子进程的PID号
p.is_alive() 查看子进程是否在生命周期
p.daemon 设置父子进程的退出关系
- 如果设置为True则子进程会随父进程的退出而结束
- 要求必须在start()前设置
- 如果daemon设置成True 通常就不会使用 join()
自定义进程类
'''
自定义进程类:三进程
'''
from multiprocessing import Process
#自定义进程类
class MyProcess(Process):
def __init__(self,val):
self.val =val
super().__init__()
def fun1(self):
print('第一步')
def fun2(self):
print('第二步')
def fun3(self):
print('第三步')
#作为流程启动函数
def run(self):
for i in range(self.val):
self.fun1()
self.fun2()
self.fun3()
if __name__ == '__main__':
p = MyProcess(2)
p.start()
p.join()
#结果:
第一步
第二步
第三步
第一步
第二步
第三步
-
创建步骤
【1】 继承Process类
【2】 重写__init__
方法添加自己的属性,使用super()加载父类属性
【3】 重写run()方法 -
使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
【3】 调用join回收线程
进程池实现
'''
进程池的使用案例
'''
from multiprocessing import Pool
from time import sleep,ctime
#进程池事件
def worker(msg):
sleep(2)
print(ctime(),'--',msg) #当前时间年月日,时分秒.
#创建进程池,注父进程结束,子进程自动销毁
pool = Pool()#括号内默认为计算机几核处理系统,就几项一起做
#向进程池队列中添加事件
for i in range(7):
msg = 'liu %d'%i
pool.apply_async(func=worker,args=(msg,))
#关闭进程池
pool.close()
#回收进程池中的进程
pool.join()
# 结果 :
Mon Aug 5 19:37:47 2019 -- liu 2
Mon Aug 5 19:37:47 2019 -- liu 0
Mon Aug 5 19:37:47 2019 -- liu 1
Mon Aug 5 19:37:47 2019 -- liu 3
Mon Aug 5 19:37:49 2019 -- liu 5
Mon Aug 5 19:37:49 2019 -- liu 4
Mon Aug 5 19:37:49 2019 -- liu 6
-
必要性
【1】 进程的创建和销毁过程消耗的资源较多
【2】 当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大
【3】 进程池技术很好的解决了以上问题。 -
原理
创建一定数量的进程来处理事件,事件处理完进 程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。
- 进程池实现
【1】 创建进程池对象,放入适当的进程
from multiprocessing import Pool
Pool(processes)
功能: 创建进程池对象
参数: 指定进程数量,默认根据系统自动判定
【2】 将事件加入进程池队列执行
pool.apply_async(func,args,kwds)
功能: 使用进程池执行 func事件
参数: func 事件函数
args 元组 给func按位置传参
kwds 字典 给func按照键值传参
返回值: 返回函数事件对象
【3】 关闭进程池
pool.close()
功能: 关闭进程池
【4】 回收进程池中进程
pool.join()
功能: 回收进程池中进程
进程间通信(IPC)
-
必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
-
常用进程间通信方法
管道 消息队列 共享内存 信号 信号量 套接字
管道通信(Pipe)
注意 :1.multiprocessing 中提供的通信只用于亲缘关系进程间的通信
- 管道在父进程中创建 , 子进程从父进程中获取管道对象
'''
pipe.py管道操作
注意:1.multiprocessing中提供的通信只用于亲缘关系进程间的通信
2.管道在父进程中创建,子进程从父进程中获取管道对象
'''
from multiprocessing import Process,Pipe
#创建管道对象 若()内参数为False,则表示:fd1只能recv,fd2只能send.
fd1,fd2 = Pipe()#括号内默认为True
#APP1可以使用app2提供的信息登入
def app1():
print('启动app1,请登入')
print('请求app2授权')
#写管道
fd1.send('app1可以用你的账户登入吗?')
data = fd1.recv()
if data:
print('登入成功:',data)
def app2():
request = fd2.recv() #阻止等待读取管道
print(request)
fd2.send(('liu','123'))#发送python数据类型
p1 = Process(target=app1)
p2 = Process(target=app2)
p1.start()
p2.start()
p1.join()
p2.join()
#结果
启动 app1, 请登入
请求 app2 授权
app1 可以用你的账户登入吗 ?
登入成功 : ('liu', '123')
- 通信原理
在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信
- 实现方法
from multiprocessing import Pipe
fd1,fd2 = Pipe(duplex = True)
功能: 创建管道
参数:默认表示双向管道
如果为False 表示单向管道
返回值:表示管道两端的读写对象
如果是双向管道均可读写
如果是单向管道fd1只读 fd2只写
fd.recv()
功能 : 从管道获取内容
返回值:获取到的数据
fd.send(data)
功能: 向管道写入内容
参数: 要写入的数据
消息队列
'''
消息队列演示
注意:消息存入与去除关系为先入先出
'''
from multiprocessing import Queue,Process
from time import sleep
from random import randint
#创建队列
q = Queue(5) #最大存储5个消息
def request():
for i in range(6):
x = randint(1,100)
y = randint(1,100)
q.put((x,y))#写消息队列
print('--------')
def handle():
while not q.empty():
data = q.get() #读消息队列
print('x+y=',data[0] +data[1])
sleep(1)
p1 = Process(target=request)
p2 = Process(target=handle)
p1.start()
p2.start()
p1.join()
p2.join()
#结果 :
--------
--------
--------
--------
--------
x+y= 152
--------
x+y= 153
x+y= 110
x+y= 120
x+y= 106
x+y= 183
1.通信原理
在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
- 实现方法
from multiprocessing import Queue
q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象
q.put(data,[block,timeout])
功能:向队列存入消息
参数:data 要存入的内容
block 设置是否阻塞 False为非阻塞
timeout 超时检测
q.get([block,timeout])
功能:从队列取出消息
参数:block 设置是否阻塞 False为非阻塞
timeout 超时检测
返回值: 返回获取到的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 获取队列中消息个数
q.close() 关闭队列
共享内存
'''
value.py开辟共享内存空间
注意:共享内存中只能有一个值
'''
from multiprocessing import Process,Value
import time
import random
#创建共享内存
money = Value('i',5000)
def man():
for i in range(30):
time.sleep(0.01)
#修改共享内存
money.value += random.randint(1,1000)
def girl():
for i in range(30):
time.sleep(30)
time.sleep(0.005)
money.value -= random.randint(100,800)
p1 = Process(target=man)
p2 = Process(target=girl)
p1.start()
p2.start()
p1.join()
p2.join()
print('一个月余额:',money.value)
#结果:一个月余额: 5000
"""
array.py共享内存中存放列表,字节串
"""
from multiprocessing import Process,Array
# 创建共享内存,初始数据 [1,2,3,4]
# shm = Array('i',[1,2,3,4])
# shm = Array('i',4) # 开辟4个整形的列表空间
shm = Array('c',b'hello')
def fun():
# 共享内存对象可以迭代
for i in shm:
print(i)
shm[0] = b'H' # 修改共享内存
p = Process(target=fun)
p.start()
p.join()
for i in shm:
print(i)
print(shm.value) # 整体打印字节串
#结果:b'h'
b'e'
b'l'
b'l'
b'o'
b'hello'
-
通信原理:在内中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前内容。
-
实现方法
from multiprocessing import Value,Array
obj = Value(ctype,data)
功能 : 开辟共享内存
参数 : ctype 表示共享内存空间类型 'i' 'f' 'c'
data 共享内存空间初始数据
返回值:共享内存对象
obj.value 对该属性的修改查看即对共享内存读写
obj = Array(ctype,data)
功能: 开辟共享内存空间
参数: ctype 表示共享内存数据类型
data 整数则表示开辟空间的大小,其他数据类型表示开辟空间存放的初始化数据
返回值:共享内存对象
Array共享内存读写: 通过遍历obj可以得到每个值,直接可以通过索引序号修改任意值。
* 可以使用obj.value直接打印共享内存中的字节串
信号量(信号灯集)
信号量可以当做是一种资源,执行任务需要消耗信号量资源,这样可以控制进程执行行为
'''
sem.py信号量演示
注意: 信号量可以当做是一种资源,执行任务需要消耗信号量资源,这样可以控制进程执行行为
'''
from multiprocessing import Process,Semaphore
from time import sleep
import os
#创建信号量灯集
sem = Semaphore(3)
#任务函数(系统中最多能够同时运行三个该任务)
def handle():
sem.acquire()# 消耗一个信号量
print('%s执行任务'%os.getpid())
sleep(2)
print('%s拯救了宇宙'%os.getpid())
sem.release()#增加一个信号量
jobs = []
for i in range(3):
p = Process(target=handle)
jobs.append(p)
p.start()
for i in jobs:
i.join()
#结果 :
26874 执行任务
26875 执行任务
26876 执行任务
26874 拯救了宇宙
26875 拯救了宇宙
26876 拯救了宇宙
- 通信原理
给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。
- 实现方法
from multiprocessing import Semaphore
sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象
sem.acquire() 将信号量减1 当信号量为0时阻塞
sem.release() 将信号量加1
sem.get_value() 获取信号量数量
"""
用两个子进程分别拷贝图片的上半部分和下半部分
"""
from multiprocessing import Process
import os
filename = "./timg.jpg"
size = os.path.getsize(filename)
# 所有进程使用的是同一个IO,相互有影响
# fr = open(filename,'rb')
# print(fr.fileno())
# 复制上半部分
def top():
fr = open(filename,'rb')
print(fr.fileno())
fw = open('top.jpg','wb')
n = size//2
fw.write(fr.read(n))
fr.close()
fw.close()
# 下半部分
def bot():
fr = open(filename, 'rb')
print(fr.fileno())
fw = open('bot.jpg', 'wb')
fr.seek(size//2,0)
fw.write(fr.read())
fr.close()
fw.close()
p1 = Process(target=top)
p2 = Process(target=bot)
p2.start()
p1.start()
p1.join()
p2.join()
线程编程(Thread)
线程基本概念
-
什么是线程
【1】 线程被称为轻量级的进程
【2】 线程也可以使用计算机多核资源,是多任务编程方式
【3】 线程是系统分配内核的最小单元
【4】 线程可以理解为进程的分支任务 -
线程特征
【1】 一个进程中可以包含多个线程
【2】 线程也是一个运行行为,消耗计算机资源
【3】 一个进程中的所有线程共享这个进程的资源
【4】 多个线程之间的运行互不影响各自运行
【5】 线程的创建和销毁消耗资源远小于进程
【6】 各个线程也有自己的ID等特征
threading模块创建线程
'''
thread1.py 线程基础使用
步骤: 1.创建线程对象
2.启动线程
3.回收线程
'''
from threading import Thread
from time import sleep
import os
a = 1
# 线程函数
def music():
global a
print("a = ",a)
a = 10000
for i in range(3):
sleep(2)
print(os.getpid(),"播放: 葫芦娃")
# 线程对象
t = Thread(target = music)
t.start() # 启动线程
for i in range(3):
sleep(1)
print(os.getpid(),"播放: 黄河大合唱")
t.join() # 回收线程
print("-----------")
print("a:",a)
#结果:
a = 1
9860 播放: 黄河大合唱
98609860 播放: 黄河大合唱
播放: 葫芦娃
9860 播放: 黄河大合唱
9860 播放: 葫芦娃
9860 播放: 葫芦娃
-----------
a: 10000
"""
线程基础演示
"""
from threading import Thread
from time import sleep
# 含有参数的线程函数
def fun(sec,name):
print("线程函数传参")
sleep(sec)
print("%s执行完毕"%name)
# 创建多个线程
jobs=[]
for i in range(3):
t = Thread(target=fun,args=(2,),kwargs={'name':'T%d'%i})
#target绑定fun函数,2秒传递给sec,键name对应的值传递给形参name
jobs.append(t) # 存线程对象
t.start()
for i in jobs:
i.join()
#结果:线程函数传参
线程函数传参
线程函数传参
T2执行完毕T0执行完毕T1执行完毕
【1】 创建线程对象
from threading import Thread
t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
【2】 启动线程
t.start()
【3】 回收线程
t.join([timeout])
线程对象属性
'''
thread_attr.py 线程属性案例
'''
from threading import Thread
from time import sleep
def fun():
sleep(3)
print('线程属性示例')
t = Thread(target=fun)
t.setDaemon(True)#主线程退出,分支线程也退出
t.start()
t.setName('liu')#设置修改线程名称
print('Name:',t.getName())
#获取线程名称
print('Daemon:',t.isDaemon())
#t.join()
#结果 :Name: liu
Daemon: True
#若注释 t.setDaemon(True) 且打开 t.join()
#结果:Name: liu
Daemon: False
线程属性示例
t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称
t.is_alive() 查看线程是否在生命周期
t.daemon 设置主线程和分支线程的退出关系
t.setDaemon() 设置daemon属性值
t.isDaemon() 查看daemon属性值daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用。
自定义线程类
'''
自定义线程类
'''
from threading import Thread
from time import sleep,ctime
class MyThread(Thread):
def __init__(self,target,args=(),kwargs={}):#关键字传参
self.target = target
self.args = args
self.kwargs = kwargs
super().__init__()
#添加其他方法run
def run(self):
self.target(*self.args,**self.kwargs)
def player(sec,song):
for i in range(3):
print('Playing %s:%s'%(song,ctime()))
sleep(sec)
t = MyThread(target=player,args=(3,),kwargs={'song':'凉凉'})
t.start()
t.join()
#结果 :
Playing 凉凉 :Tue Aug < 19:29:1< 2019
Playing 凉凉 :Tue Aug < 19:29:19 2019
Playing 凉凉 :Tue Aug < 19:29:22 2019
-
创建步骤
【1】 继承Thread类
【2】 重写__init__
方法添加自己的属性,使用super()加载父类属性
【3】 重写run()方法 -
使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
【3】 调用join回收线程
同步互斥
线程间通信方法
- 通信方法
线程间使用全局变量进行通信
- 共享资源争夺
-
共享资源:多个进程或者线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。
-
影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要同步互斥机制协调操作顺序。
- 同步互斥机制
同步 : 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作。
互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。
线程同步互斥方法
线程Event
'''
event互斥方案
'''
from threading import Event,Thread
s = None # 全局变量用于通信
e = Event() # 事件对象
def yangzilong():
print('小明前来拜山')
global s
s = '小明小强小红'
e.set() #修改完s
t = Thread(target = yangzilong)
t.start()
print('口令对是自己人')
e.wait()#阻塞等待
if s == '小明小强小红':
print('三个和尚')
print('确认过,你是自己人')
else:
print('打死他')
t.join()
#结果:小明前来拜山
口令对是自己人
三个和尚
确认过,你是自己人
from threading import Event
e = Event() 创建线程event对象
e.wait([timeout]) 阻塞等待e被set
e.set() 设置e,使wait结束阻塞
e.clear() 使e回到未被设置状态
e.is_set() 查看当前e是否被设置
线程锁 Lock
'''
线程锁,Lock
'''
from threading import Thread,Lock
a = b = 0
lock = Lock()
def value():
while True:
lock.acquire()
if a != b:
print('a=%d,b=%d'%(a,b))
lock.release()#解锁操作
t = Thread(target=value)
t.start()
while True:
with lock: #with上锁
a +=1
b +=1 #语句块结束解锁
t.join()
from threading import Lock
lock = Lock() 创建锁对象
lock.acquire() 上锁 如果lock已经上锁再调用会阻塞
lock.release() 解锁
with lock: 上锁
...
...
with代码块结束自动解锁
死锁及其处理
- 定义
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
- 死锁产生条件
from time import sleep
from threading import Thread,Lock
# 交易类
class Account:
def __init__(self,_id,balance,lock):
self.id = _id # 谁
self.balance = balance # 有多少钱
self.lock = lock # 锁
# 取钱
def withdraw(self,amount):
self.balance -= amount # 取多少
# 存钱
def deposit(self,amount):
self.balance += amount # 存多少
# 查看余额
def get_balance(self):
return self.balance
# 创建两个账户
Tom = Account('Tom',5000,Lock())
Alex = Account('Alex',8000,Lock())
# 转账行为
def transfer(from_,to,amount):
# 从 from_ --> to 转amount
if from_.lock.acquire(): # 锁住自己账户
from_.withdraw(amount) # 自己账户扣除
sleep(0.5)
if to.lock.acquire(): # 对方账户上锁
to.deposit(amount) # 对方账户增加
to.lock.release() # 对方解锁
from_.lock.release() # 自己解锁
print("%s给%s转了%d"%(from_.id,to.id,amount))
t1 = Thread(target=transfer,args=(Tom,Alex,2000))
t2 = Thread(target=transfer,args=(Alex,Tom,3500))
t1.start()
t2.start()
t1.join()
t2.join()
print(Tom.get_balance())
print(Alex.get_balance())
死锁发生的必要条件
- 互斥条件:指线程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放,通常CPU内存资源是可以被系统强行调配剥夺的。
- 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,即进程集合{T0,T1,T2,···,Tn}中的T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。
死锁的产生原因
简单来说造成死锁的原因可以概括成三句话:
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已拥有的资源
- 都不放弃自己拥有的资源
- 如何避免死锁
死锁是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生。通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率。
'''
使用多线程下载一个文件.
'''
from threading import Thread,Lock
import os #导入文件模块
lock =Lock()
urls = ['/home/tarena/',
'/home/tarena/音乐/',
'/home/tarena/文档/',
'/home/tarena/liu/',
'/home/tarena/模板/'
]
#先判断列表中哪个元素有想要的文件
filename = input('要下载的文件:')
list01 = []
for i in urls:
if os.path.exists(i+filename):#判断文件是否存在
list01.append(i+filename)
#没有文件
if not list01:
print('没有资源')
os._exit(0) #所有进程退出
#计算文件大小
file_size = os.path.getsize(list01[0])
#将文件分块
block_size = file_size // len(list01)
#打开一个本地文件
fd = open(filename,'wb')
#下载一块内容
def download(path,num):
f = open(path,'rb')
seek_num = block_size * (num-1)
f.seek(seek_num)
data = f.read(block_size)
with lock:
fd.seek(seek_num)
fd.write(data)
# fd.seek(seek_num)
# #下载过程
# size = block_size
# while True:
# if size <= 1024:
# data = f.read(size)
# fd.write(data)
# break
# else:
# data = f.read(1024)
# fd.write(data)
# size -= 1024
num = 1
jobs = []
for path in list01:
t = Thread(target=download,args=(path,num))
jobs.append(t)
t.start()
num += 1 #下载第几块
for i in jobs:
i.join()
python线程GIL
Python 线程 : 只适合高延迟 , 多阻塞的 IO 行为 。
- python线程的GIL问题 (全局解释器锁)
什么是GIL :由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。
导致后果: 因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞高延迟IO时可以提升程序效率,其他情况并不能对效率有所提升。
GIL问题建议
- 尽量使用进程完成无阻塞的并发行为
- 不使用c作为解释器 (Java C#)
- 结论 : 在无阻塞状态下,多线程程序和单线程程序执行效率几乎差不多,甚至还不如单线程效率。但是多进程运行相同内容却可以有明显的效率提升。
进程线程的区别联系
区别联系
- 两者都是多任务编程方式,都能使用计算机多核资源
- 进程的创建删除消耗的计算机资源比线程多
- 进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
- 一个进程可以有多个分支线程,两者有包含关系
- 多个线程共享进程资源,在共享资源操作时往往需要同步互斥处理
- 进程线程在系统中都有自己的特有属性标志,如ID,代码段,命令集等。
使用场景
-
任务场景:如果是相对独立的任务模块,可能使用多进程,如果是多个分支共同形成一个整体任务可能用多线程
-
项目结构:多种编程语言实现不同任务模块,可能是多进程,或者前后端分离应该各自为一个进程。
-
难易程度:通信难度,数据处理的复杂度来判断用进程间通信还是同步互斥方法。
要求
- 对进程线程怎么理解/说说进程线程的差异
- 进程间通信知道哪些,有什么特点
- 什么是同步互斥,你什么情况下使用,怎么用
- 给一个情形,说说用进程还是线程,为什么
- 问一些概念,僵尸进程的处理,GIL问题,进程状态
并发网络通信模型
常见网络模型
- 循环服务器模型 :循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个。
优点:实现简单,占用资源少
缺点:无法同时处理多个客户端请求
适用情况:处理的任务可以很快完成,客户端无需长期占用服务端程序。udp比tcp更适合循环。
- 多进程/线程网络并发模型:每当一个客户端连接服务器,就创建一个新的进程/线程为该客户端服务,客户端退出时再销毁该进程/线程。
优点:能同时满足多个客户端长期占有服务端需求,可以处理各种请求。
缺点: 资源消耗较大
适用情况:客户端同时连接量较少,需要处理行为较复杂情况。
-
IO并发模型:利用IO多路复用,异步IO等技术,同时处理多个客户端IO请求。
优点 : 资源消耗少,能同时高效处理多个IO行为
缺点 : 只能处理并发产生的IO事件,无法处理cpu计算适用情况:HTTP请求,网络传输等都是IO行为。
基于fork的多进程网络并发模型
'''
fork_sercer.py 基于fork的多进程网络并发模型
1. 创建监听套接字
2. 等待接收客户端请求
3. 客户端连接创建新的进程处理客户端请求
4. 原进程继续等待其他客户端连接
5. 如果客户端退出,则销毁对应的进程
tcp_server tcp套接字服务端功能流程
'''
from socket import *
import os
import signal #处理僵尸进程
ADDR = ('0.0.0.0',8888)
#客户端处理函数,循环收发消息
def handle(c):
while True:
data = c.recv(1024).decode()
if not data:
break
print(data)
c.send(b'ok')
#创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
#处理僵尸进程
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
print('listen the part 8888---')
while True:
#循环等待客户端连接
try:
c,addr = s.accept()
print('Connect from',addr)
except KeyboardInterrupt:
os._exit(0)
except Exception as e:
print(e)
continue
#创建新的进程
pid = os.fork()
if pid == 0:
#子进程要处理具体的客户端请求
s.close()
handle(c)
os._exit(0)
else:
c.close()
'''
tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
data = input('Msg:')
# data为空退出循环
if not data:
break
sockfd.send(data.encode())#发送字节串
data = sockfd.recv(1024)
print('Server:',data.decode())
#关闭套接字
sockfd.close()
实现步骤
- 创建监听套接字
- 等待接收客户端请求
- 客户端连接创建新的进程处理客户端请求
- 原进程继续等待其他客户端连接
- 如果客户端退出,则销毁对应的进程
基于threading的多线程网络并发
'''
thread_server.py基于threading的多线程网络并发
1. 创建监听套接字
2. 循环接收客户端连接请求
3. 当有新的客户端连接创建线程处理客户端请求
4. 主线程继续等待其他客户端连接
5. 当客户端退出,则对应分支线程退出
'''
from socket import *
from threading import Thread
import os
ADDR = ('0.0.0.0',8888)
#客户端处理函数,循环收发消息
def handle(c):
while True:
data = c.recv(1024).decode()
if not data:
break
print(data)
c.send(b'ok')
#创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
print('listen the part 8888---')
while True:
#循环等待客户端连接
try:
c,addr = s.accept()
print('Connect from',addr)
except KeyboardInterrupt:
os._exit(0)
except Exception as e:
print(e)
continue
#创建新的线程处理请求
client = Thread(target=handle,args=(c,))
client.setDaemon(True)
client.start()
'''
tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
data = input('Msg:')
# data为空退出循环
if not data:
break
sockfd.send(data.encode())#发送字节串
data = sockfd.recv(1024)
print('Server:',data.decode())
#关闭套接字
sockfd.close()
#结果:本客户端:Msg:1222
Server: ok
Msg:999
Server: ok
Msg:666
Server: ok
Msg:输入空格结束
#对应上述服务端:listen the part 8888---
Connect from ('***.***.*.***', 57436)
1222
999
666
实现步骤
- 创建监听套接字
- 循环接收客户端连接请求
- 当有新的客户端连接创建线程处理客户端请求
- 主线程继续等待其他客户端连接
- 当客户端退出,则对应分支线程退出
ftp 文件服务器
'''
ftp 文件服务器
功能
【1】 分为服务端和客户端,要求可以有多个客户端同时操作。
【2】 客户端可以查看服务器文件库中有什么文件。
【3】 客户端可以从文件库中下载文件到本地。
【4】 客户端可以上传一个本地文件到文件库。
【5】 使用print在客户端打印命令输入提示,引导操作
技术点确认:
1.并发:多线程并发
2.数据传输:tcp传输,文件传输都是tcp传输
结构设计:
将基本功能封装为类
功能模块:
1.搭建网络通信
2.查看文件列表
3.下载文件
4.上传文件
5.客户端退出
协议:(协议类型 参数)
查看列表 L
退出 Q
下载 get filename
上传 put filename
'''
from socket import *
from threading import Thread
import os,sys
import time
# 全局变量
ADDR = ('0.0.0.0',8888)
FTP = "/home/tarena/FTP/" # 文件库路径
# 功能类 (线程类)
# 查文档, 下载,上传
class FTPServer(Thread):
def __init__(self,connfd):
super().__init__()
self.connfd = connfd
# 处理文件列表
def do_list(self):
# 获取文件列表
files = os.listdir(FTP)
if not files:
self.connfd.send("文件库为空".encode())
return
else:
self.connfd.send(b'OK')
time.sleep(0.1)
# 拼接文件
filelist = ''
for file in files:
filelist += file + '\n'
self.connfd.send(filelist.encode())
def do_get(self,filename):
try:
f = open(FTP+filename,'rb')
except Exception:
# 文件不存在
self.connfd.send('文件不存在'.encode())
return
else:
self.connfd.send(b'OK')
time.sleep(0.1)
# 发送文件
while True:
data = f.read(1024)
if not data:
time.sleep(0.1)
self.connfd.send(b'##')
break
self.connfd.send(data)
def do_put(self,filename):
if os.path.exists(FTP+filename):
self.connfd.send("文件已存在".encode())
return
else:
self.connfd.send(b'OK')
# 接收文件
f = open(FTP + filename,'wb')
while True:
data = self.connfd.recv(1024)
if data == b'##':
break
f.write(data)
f.close()
# 循环接受来自客户端的请求
def run(self):
while True:
request=self.connfd.recv(1024).decode()
if not request or request == 'Q':
return # 线程退出
elif request == 'L':
self.do_list()
elif request[0] == 'G':
filename = request.split(' ')[-1]
self.do_get(filename)
elif request[0] == 'P':
filename = request.split(' ')[-1]
self.do_put(filename)
# 启动函数
def main():
# 创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(ADDR)
s.listen(5)
print("Listen the port 8080....")
while True:
# 循环等待客户端连接
try:
c, addr = s.accept()
print("Connect from", addr)
except KeyboardInterrupt:
os._exit(0)
except Exception as e:
print(e)
continue
# 创建新的线程处理请求
client = FTPServer(c)
client.setDaemon(True)
client.start()
main()
#利用TCP创建客户端
import time
from socket import *
import sys
# 服务器地址
ADDR = ('***.***.*.***',8888)
# 文件处理类
class FTPClient:
# 所有函数都使用sockfd,所以把它变为属性变量
def __init__(self,sockfd):
self.sockfd = sockfd
def do_list(self):
self.sockfd.send(b'L') # 发送请求
# 等待回复 (服务端能否满足请求)
data = self.sockfd.recv(128).decode()
if data == 'OK':
# 一次性接收所有文件
data = self.sockfd.recv(4096)
print(data.decode())
else:
print(data)
def do_quit(self):
self.sockfd.send(b'Q') # 退出请求
self.sockfd.close()
sys.exit("谢谢使用")
def do_get(self,filename):
# 发送请求
self.sockfd.send(('G '+filename).encode())
# 等待回复
data = self.sockfd.recv(128).decode()
if data == 'OK':
f = open(filename,'wb')
# 循环接收内容,写入文件
while True:
data = self.sockfd.recv(1024)
if data == b'##': # 发送完成
break
f.write(data)
f.close()
else:
print(data)
def do_put(self,filename):
try:
f = open(filename,'rb')
except Exception as e:
print("该文件不存在")
return
# 发送请求
filename = filename.split('/')[-1]
self.sockfd.send(('P '+filename).encode())
# 等待反馈
data = self.sockfd.recv(128).decode()
if data == 'OK':
while True:
data = f.read(1024)
if not data:
time.sleep(0.1)
self.sockfd.send(b'##')
break
self.sockfd.send(data)
f.close()
else:
print(data)
# 启动函数
def main():
sockfd = socket()
try:
sockfd.connect(ADDR)
except Exception as e:
print(e)
return
ftp = FTPClient(sockfd) # 实例化对象,用于调用功能
# 循环发送请求给服务器
while True:
print("""\n
=========Command============
**** list ****
**** get file ****
**** put file ****
**** quit ****
============================
""")
cmd = input("输入命令:")
if cmd.strip() == 'list':
ftp.do_list()
elif cmd.strip() == 'quit':
ftp.do_quit()
elif cmd[:3] == 'get':
filename = cmd.split(' ')[-1]
ftp.do_get(filename)
elif cmd[:3] == 'put':
filename = cmd.split(' ')[-1]
ftp.do_put(filename)
else:
print("请输入正确命令")
main()
#结果:此客户端:
=========Command============
**** list ****
**** get file ****
**** put file ****
**** quit ****
============================
输入命令:11
请输入正确命令
=========Command============
**** list ****
**** get file ****
**** put file ****
**** quit ****
============================
输入命令:get
文件不存在
=========Command============
**** list ****
**** get file ****
**** put file ****
**** quit ****
============================
输入命令:put
该文件不存在
=========Command============
**** list ****
**** get file ****
**** put file ****
**** quit ****
============================
输入命令:quit
谢谢使用
- 功能
【1】 分为服务端和客户端,要求可以有多个客户端同时操作。
【2】 客户端可以查看服务器文件库中有什么文件。
【3】 客户端可以从文件库中下载文件到本地。
【4】 客户端可以上传一个本地文件到文件库。
【5】 使用print在客户端打印命令输入提示,引导操作
IO并发
IO 分类
IO分类:阻塞IO ,非阻塞IO,IO多路复用,异步IO等
阻塞IO :默认形态 , 效率低 , 顺序执行
1.定义:在执行IO操作时如果执行条件不满足则阻塞。阻塞IO是IO的默认形态。
2.效率:阻塞IO是效率很低的一种IO。但是由于逻辑简单所以是默认IO行为。
3.阻塞情况:
-
因为某种执行条件没有满足造成的函数阻塞
e.g. accept input recv -
处理IO的时间较长产生的阻塞状态
e.g. 网络传输,大文件读写
#### 非阻塞IO
'''
block_io.py
socked 非阻塞io示例:
'''
from socket import *
from time import *
f = open('log.txt','a+')
#tcp服务端
s = socket()
s.bind(('0.0.0.0',8888))
s.listen(3)
#非阻塞io设置
# s.setblocking(False)
#超时时间
s.settimeout(2)
while True:
print('waiting from connect--')
try:
connfd,addr = s.accept()
except (BlockingIOError,timeout) as e:
sleep(2)
f.write('%s:%s\n'%(ctime(),e))
f.flush()
else:
print('connect from',addr)
data = connfd.recv(1024).decode()
print(data)
'''
tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
data = input('Msg:')
# data为空退出循环
if not data:
break
sockfd.send(data.encode())#发送字节串
data = sockfd.recv(1024)
print('Server:',data.decode())
#关闭套接字
sockfd.close()
- 定义 :通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。
- 设置套接字为非阻塞IO
sockfd.setblocking(bool)
功能:设置套接字为非阻塞IO
参数:默认为True,表示套接字IO阻塞;设置为False则套接字IO变为非阻塞
-
超时检测 :设置一个最长阻塞时间,超过该时间后则不再阻塞等待。
sockfd.settimeout(sec)
功能:设置套接字的超时时间
参数:设置的时间
IO多路复用:
-
同时监控多个 IO 事件。
-
有 IO 可以处理则告诉应用。
-
在应用中处理 IO。
-
定义
同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。
- 具体方案
select方法 : windows linux unix
poll方法: linux unix
epoll方法: linux
select 方法
'''
select tcp 服务端:
思路分析:
【1】 将关注的IO放入对应的监控类别列表
【2】 当IO装备就绪时,通知select函数返回
【3】 遍历返回值列表,处理就绪的IO
'''
from socket import *
from select import select
#创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
#设置关注的IO列表
rlist = [s] #s用于等待处理连接
wlist = []
xlist = []
#循环IO监控
while True:
# print(rlist)
rs,ws,xs = select(rlist,wlist,xlist)
# print('-----',rs,ws)
#遍历返回值列表,判断哪个IO就绪
for r in rs:
if r is s:
c,addr = r.accept()
print('connect from',addr)
rlist.append(c)#增加新的关注的IO
else:#表明有客户端发送消息
data = r.recv(1024).decode()
if not data:#客户端退出则取消对其关注
rlist.remove(r)
r.close()
continue
print(data)
# r.send(b'OK')
wlist.append(r)
for w in ws:
w.send(b'OK')
wlist.remove(w)
for x in xs:
pass
'''
tcp_client.py tcp客户端流程
'''
from socket import *
#创建tcp套接字
sockfd = socket() #默认参数转为tcp套接字
#连接服务端程序
server_addr = ('***.***.*.***',8888)
sockfd.connect(server_addr)
#发送接收端消息
while True:
data = input('Msg:')
# data为空退出循环
if not data:
break
sockfd.send(data.encode())#发送字节串
data = sockfd.recv(1024)
print('Server:',data.decode())
#关闭套接字
sockfd.close()
#结果:本客户端:Msg:11
Server: OK
Msg:22
Server: OK
Msg:
#服务端:connect from ('***.***.*.***', 60369)
11
22
rs, ws, xs=select(rlist, wlist, xlist[, timeout])
功能: 监控IO事件,阻塞等待IO发生
参数:rlist 列表 存放关注的等待发生的IO事件
wlist 列表 存放关注的要主动处理的IO事件
xlist 列表 存放关注的出现异常要处理的IO
timeout 超时时间
返回值: rs 列表 rlist中准备就绪的IO
ws 列表 wlist中准备就绪的IO
xs 列表 xlist中准备就绪的IO
select 实现tcp服务
【1】 将关注的IO放入对应的监控类别列表
【2】通过select函数进行监控
【3】遍历select返回值列表,确定就绪IO事件
【4】处理发生的IO事件
注意
wlist中如果存在IO事件,则select立即返回给ws
处理IO过程中不要出现死循环占有服务端的情况
IO多路复用消耗资源较少,效率较高
###@@扩展: 位运算
定义 : 将整数转换为二进制,按二进制位进行运算
运算符号:
& 按位与 | 按位或 ^ 按位异或 << 左移 >> 右移
e.g. 14 --> 01110
19 --> 10011
14 & 19 = 00010 = 2 一0则0
14 | 19 = 11111 = 31 一1则1
14 ^ 19 = 11101 = 29 相同为0不同为1
14 << 2 = 111000 = 56 向左移动低位补0
14 >> 2 = 11 = 3 向右移动去掉低位
poll方法
'''
poll_server 完成TCP套接服务
【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
'''
from socket import *
from select import *
#创建监听套接字作为关注的IO
s =socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(3)
#创建poll对象
p = poll()
#建立查找字典,通过IO的fileno查找io对象
#始终与register的IO保持一致.
fdmap = {s.fileno():s}
#关注s
p.register(s,POLLIN | POLLERR)
#循环监控IO发生
while True:
events = p.poll()#阻塞等待IO发生
#遍历查看哪个IO准备就绪了.
for fd,event in events:
print(events)
if fd == s.fileno():
c,addr = fdmap[fd].accept()
print('connect from',addr)
#关注客户端连接套接字
p.register(c,POLLIN | POLLHUP)
fdmap[c.fileno()] = c #维护字典
elif event & POLLIN:
data = fdmap[fd].recv(1024).decode()
if not data:
p.unregister(fd)
fdmap[fd].close()
del fdmap[fd]
continue
print(data)
fdmap[fd].send(b'OK')
p = select.poll()
功能 : 创建poll对象
返回值: poll对象
p.register(fd,event)
功能: 注册关注的IO事件
参数:fd 要关注的IO
event 要关注的IO事件类型
常用类型:POLLIN 读IO事件(rlist)
POLLOUT 写IO事件 (wlist)
POLLERR 异常IO (xlist)
POLLHUP 断开连接
e.g. p.register(sockfd,POLLIN|POLLERR)
p.unregister(fd)
功能:取消对IO的关注
参数:IO对象或者IO对象的fileno
events = p.poll()
功能: 阻塞等待监控的IO事件发生
返回值: 返回发生的IO
events格式 [(fileno,event),()....]
每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项为该IO就绪的事件类型
poll_server 步骤
【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
epoll方法
-
使用方法 : 基本与poll相同
- 生成对象改为 epoll()
- 将所有事件类型改为EPOLL类型
-
epoll特点
- epoll 效率比select poll要高
- epoll 监控IO数量比select要多
- epoll 的触发方式比poll要多 (EPOLLET边缘触发)
协程技术
基础概念
-
定义:纤程,微线程。是允许在不同入口点不同位置暂停或开始的计算机程序,简单来说,协程就是可以暂停执行的函数。
-
协程原理 : 记录一个函数的上下文,协程调度切换时会将记录的上下文保存,在切换回来时进行调取,恢复原有的执行内容,以便从上一次执行位置继续执行。
-
协程优缺点
优点
- 协程完成多任务占用计算资源很少
- 由于协程的多任务切换在应用层完成,因此切换开销少
- 协程为单线程程序,无需进行共享资源同步互斥处理
缺点
协程的本质是一个单线程,无法利用计算机多核资源
####扩展延伸@标准库协程的实现
python3.5以后,使用标准库asyncio和async/await 语法来编写并发代码。asyncio库通过对异步IO行为的支持完成python的协程。虽然官方说asyncio是未来的开发方向,但是由于其生态不够丰富,大量的客户端不支持awaitable需要自己去封装,所以在使用上存在缺陷。更多时候只能使用已有的异步库(asyncio等),功能有限
第三方协程模
- greenlet模块
'''
协程行为示例
'''
from greenlet import *
def fun1():
print('执行fun1')
gr2.switch()
print('结束fun1')
gr2.switch()
def fun2():
print('执行fun2')
gr1.switch()
print('结束fun2')
#将函数变成协程
gr1 = greenlet(fun1)
gr2 = greenlet(fun2)
gr1.switch()#选择要执行的协程函数
#结果:执行fun1
执行fun2
结束fun1
结束fun2
'''
gevent生成协程示例
'''
import gevent
from gevent import monkey
monkey.patch_time()#修改对time模块中阻塞的解释器行为
from time import sleep
#协程函数
def fun1(a,b):
print('running fun1---',a,b)
# gevent.sleep(3)
sleep(3)
print('fun1 again--')
def fun2():
print('running fun2---')
# gevent.sleep(2)
sleep(2)
print('fun2 again--')
#生成协程对象
f = gevent.spawn(fun1,1,2)
g = gevent.spawn(fun2)
gevent.joinall([f,g])
#结果:running fun1--- 1 2
running fun2---
fun2 again--
fun1 again--
-
安装 : sudo pip3 install greenlet
-
函数
greenlet.greenlet(func)
功能:创建协程对象
参数:协程函数
g.switch()
功能:选择要执行的协程函数
- gevent模块
"""
gevent生成协程演示
"""
import gevent
from gevent import monkey
monkey.patch_time() # 修改对time模块中阻塞的解释行为
from time import sleep
# 协程函数
def foo(a,b):
print("Running foo ...",a,b)
# gevent.sleep(3)
sleep(3)
print("Foo again..")
def bar():
print("Running bar ...")
# gevent.sleep(2)
sleep(2)
print("Bar again..")
# 生成协程对象
f = gevent.spawn(foo,1,2)
g = gevent.spawn(bar)
gevent.joinall([f,g]) #阻塞等待f,g代表的协程执行完毕
#结果:Running foo ... 1 2
Running bar ...
Bar again..
Foo again..
"""
gevent server 基于协成的tcp并发
思路 : 1. 每个客户函数端设置为协成
2. 将socket模块下的阻塞变为可以触发协程跳转
"""
import gevent
from gevent import monkey
monkey.patch_all() # 执行脚本,修改socket
from socket import *
def handle(c):
while True:
data = c.recv(1024).decode()
if not data:
break
print(data)
c.send(b'OK')
c.close()
# 创建tcp套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
# 循环接收来自客户端连接
while True:
c,addr = s.accept()
print("Connect from",addr)
# handle(c) # 处理具体客户端请求
gevent.spawn(handle,c) # 协程方案
"""
tcp_client.py tcp客户端流程
"""
from socket import *
# 创建tcp套接字
sockfd = socket() # 默认参数-->tcp套接字
# 连接服务端程序
server_addr = ('127.0.0.1',8888)
sockfd.connect(server_addr)
# 发送接收消息
while True:
data = input("Msg:")
# data为空退出循环
if not data:
break
sockfd.send(data.encode()) # 发送字节串
data = sockfd.recv(1024)
print("Server:",data.decode())
# 关闭套接字
sockfd.close()
#结果:本客户端:Msg:11
Server: OK
Msg:22
Server: OK
Msg:
#上面服务端:Connect from ('127.0.0.1', 62174)
11
22
-
安装:sudo pip3 install gevent
-
函数
gevent.spawn(func,argv)
功能: 生成协程对象
参数:func 协程函数
argv 给协程函数传参(不定参)
返回值: 协程对象
gevent.joinall(list,[timeout])
功能: 阻塞等待协程执行完毕
参数:list 协程对象列表
timeout 超时时间
gevent.sleep(sec)
功能: gevent睡眠阻塞
参数:睡眠时间
* gevent协程只有在遇到gevent指定的阻塞行为时才会自动在协程之间进行跳转
如gevent.joinall(),gevent.sleep()带来的阻塞
- monkey脚本
作用:在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程,因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。
转换方法:gevent 提供了一个脚本程序monkey,可以修改底层解释IO阻塞的行为,将很多普通阻塞转换为gevent阻塞。
使用方法
【1】 导入monkey
from gevent import monkey
【2】 运行相应的脚本,例如转换socket中所有阻塞
monkey.patch_socket()
【3】 如果将所有可转换的IO阻塞全部转换则运行all
monkey.patch_all()
【4】 注意:脚本运行函数需要在对应模块导入前执行
HTTPServer v2.0
'''
HTTPServer v2.0
基本要求: 1.获取来自浏览器的请求
2.判断请求内容是否为/
3.如果是,则将 index.html 发送给浏览器
如果不是,则告知浏览器sorry
4.注意组织http响应格式,判断 200 or 404
5.根据请求组织数据内容
6.将数据内容形成http响应格式返回给浏览器
升级点 :
【1】采用IO并发,可以满足多个客户端同时发起请求情况
【2】做基本的请求解析,根据具体请求返回具体内容,
同时满足客户端简单的非网页请求情况
【3】通过类接口形式进行功能封装
技术分析: select IO 多路复用 TCP传输
封装设计: 类封装
在用户的使用角度进行流程设计 1.你的类要实现什么功能
2.你希望用户如何使用你的类
基本原则: 1.当实现的功能比较复杂,需分步实现
将实例装换为对象,用对象调用指定方法,按步骤实现
2.实现的类需要用户自己去决定很多内容细节,你去实现一个
父类,再让用户去继承你的功能,在子类中自我完善.
3.当实现的类功能并不复杂是一个简单的服务功能,你尽量替用户
完善功能,不能替用户决定的量通过参数的传入即可.
HTTPServer v2.0
env: python3.6
IO多路复用:http浏览
'''
from socket import *
from select import select
#具体功能服务
class HTTPServer:
def __init__(self,host='0.0.0.0',post=80,dir=None):
self.host = host
self.post = post
self.dir = dir
self.address = (host,post)
#select监控列表
self.rlist = []
self.wlist = []
self.xlist = []
#直接创建TCP套接字
self.create_socket()
self.bind()
#创建套接字
def create_socket(self):
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#绑定客户端
def bind(self):
self.sockfd.bind(self.address)
#启动入口
def serve_forever(self):
self.sockfd.listen(3)
print('listen the port %d'%self.post)
#搭建IO多路复用监控各种IO请求
self.rlist.append(self.sockfd)
while True:
rs,ws,xs = select(self.rlist,self.wlist,self.xlist)
for r in rs:
#有浏览器连接过来
if r is self.sockfd:
c,addr = r.accept()
self.rlist.append(c)
else:#处理客户端请求
self.handle(r)
#处理客户端请求
def handle(self,connfd):
#接收HTTP请求
request = connfd.recv(1024)
print(request)
#客户端断开
if not request:
self.rlist.remove(connfd)
connfd.close()
return
#提取请求内容(将字节串按行切割)
request_line = request.splitlines()[0]
info = request_line.decode().split(' ')[1]
print('请求内容:',info)
#根据请求内容进行数据处理
#分两类:1.请求网页,2.其他
if info == '/' or info[-5:] == '.html':
self.get_html(connfd,info)
else:
self.get_data(connfd,info)
#处理网页
def get_html(self,connfd,info):
if info == '/':#请求主页
filename = self.dir + '/index.html'
else:
filename = self.dir + info
try:
fd = open(filename)
except Exception:#网页不存在
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += "<h1>Sorry-----</h1>"
else:
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += fd.read()
finally:#将内容发送给浏览器
connfd.send(response.encode())
#处理其他
def get_data(self,connfd,info):
f = open(self.dir+'/timg.jpg','rb')
data = f.read()
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:image/jpeg\r\n"
response += "\r\n"
response = response.encode() +data
connfd.send(response)
#用户如何用HTTPServer
if __name__ == '__main__':
'''
通过HTTPServer快速启动服务,用于展示自己的网页
'''
#需要用户自己决定的内容
HOST = '0.0.0.0'
POST = 8888
DIR = './static' #网页存储位置
httpd = HTTPServer()
httpd.serve_forever()#服务启动入口
1. 主要功能 :
【1】 接收客户端(浏览器)请求
【2】 解析客户端发送的请求
【3】 根据请求组织数据内容
【4】 将数据内容形成http响应格式返回给浏览器
2. 升级点 :
【1】 采用IO并发,可以满足多个客户端同时发起请求情况
【2】 做基本的请求解析,根据具体请求返回具体内容,同时满足客户端简单的非网页请求情况
【3】 通过类接口形式进行功能封装
4】 注意:脚本运行函数需要在对应模块导入前执行
HTTPServer v2.0
'''
HTTPServer v2.0
基本要求: 1.获取来自浏览器的请求
2.判断请求内容是否为/
3.如果是,则将 index.html 发送给浏览器
如果不是,则告知浏览器sorry
4.注意组织http响应格式,判断 200 or 404
5.根据请求组织数据内容
6.将数据内容形成http响应格式返回给浏览器
升级点 :
【1】采用IO并发,可以满足多个客户端同时发起请求情况
【2】做基本的请求解析,根据具体请求返回具体内容,
同时满足客户端简单的非网页请求情况
【3】通过类接口形式进行功能封装
技术分析: select IO 多路复用 TCP传输
封装设计: 类封装
在用户的使用角度进行流程设计 1.你的类要实现什么功能
2.你希望用户如何使用你的类
基本原则: 1.当实现的功能比较复杂,需分步实现
将实例装换为对象,用对象调用指定方法,按步骤实现
2.实现的类需要用户自己去决定很多内容细节,你去实现一个
父类,再让用户去继承你的功能,在子类中自我完善.
3.当实现的类功能并不复杂是一个简单的服务功能,你尽量替用户
完善功能,不能替用户决定的量通过参数的传入即可.
HTTPServer v2.0
env: python3.6
IO多路复用:http浏览
'''
from socket import *
from select import select
#具体功能服务
class HTTPServer:
def __init__(self,host='0.0.0.0',post=80,dir=None):
self.host = host
self.post = post
self.dir = dir
self.address = (host,post)
#select监控列表
self.rlist = []
self.wlist = []
self.xlist = []
#直接创建TCP套接字
self.create_socket()
self.bind()
#创建套接字
def create_socket(self):
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#绑定客户端
def bind(self):
self.sockfd.bind(self.address)
#启动入口
def serve_forever(self):
self.sockfd.listen(3)
print('listen the port %d'%self.post)
#搭建IO多路复用监控各种IO请求
self.rlist.append(self.sockfd)
while True:
rs,ws,xs = select(self.rlist,self.wlist,self.xlist)
for r in rs:
#有浏览器连接过来
if r is self.sockfd:
c,addr = r.accept()
self.rlist.append(c)
else:#处理客户端请求
self.handle(r)
#处理客户端请求
def handle(self,connfd):
#接收HTTP请求
request = connfd.recv(1024)
print(request)
#客户端断开
if not request:
self.rlist.remove(connfd)
connfd.close()
return
#提取请求内容(将字节串按行切割)
request_line = request.splitlines()[0]
info = request_line.decode().split(' ')[1]
print('请求内容:',info)
#根据请求内容进行数据处理
#分两类:1.请求网页,2.其他
if info == '/' or info[-5:] == '.html':
self.get_html(connfd,info)
else:
self.get_data(connfd,info)
#处理网页
def get_html(self,connfd,info):
if info == '/':#请求主页
filename = self.dir + '/index.html'
else:
filename = self.dir + info
try:
fd = open(filename)
except Exception:#网页不存在
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += "<h1>Sorry-----</h1>"
else:
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += fd.read()
finally:#将内容发送给浏览器
connfd.send(response.encode())
#处理其他
def get_data(self,connfd,info):
f = open(self.dir+'/timg.jpg','rb')
data = f.read()
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:image/jpeg\r\n"
response += "\r\n"
response = response.encode() +data
connfd.send(response)
#用户如何用HTTPServer
if __name__ == '__main__':
'''
通过HTTPServer快速启动服务,用于展示自己的网页
'''
#需要用户自己决定的内容
HOST = '0.0.0.0'
POST = 8888
DIR = './static' #网页存储位置
httpd = HTTPServer()
httpd.serve_forever()#服务启动入口
1. 主要功能 :
【1】 接收客户端(浏览器)请求
【2】 解析客户端发送的请求
【3】 根据请求组织数据内容
【4】 将数据内容形成http响应格式返回给浏览器
2. 升级点 :
【1】 采用IO并发,可以满足多个客户端同时发起请求情况
【2】 做基本的请求解析,根据具体请求返回具体内容,同时满足客户端简单的非网页请求情况
【3】 通过类接口形式进行功能封装