Tedu Python 教学部 |
---|
Author:吕泽 |
什么是网络 : 计算机网络功能主要包括实现资源共享,实现数据信息的快速传递。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GTl1WonJ-1609849651547)(/home/tarena/month02/lz/Network/img/1.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3xw0JBMU-1609849651550)(/home/tarena/month02/lz/Network/img/2.png)]
面临问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5nc0gzyS-1609849651551)(/home/tarena/month02/lz/Network/img/4.jpg)]
OSI 7层模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aEtuUIen-1609849651553)(/home/tarena/month02/lz/Network/img/5.jpg)]
好处
建立了统一的通信标准
降低开发难度,每层功能明确,各司其职
七层模型实际规定了每一层的任务,该完成什么事情
TCP/IP模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e9ZA3EZa-1609849651555)(/home/tarena/month02/lz/Network/img/6.jpg)]
网络协议
什么是网络协议:在网络数据传输中,都遵循的执行规则。
网络协议实际上规定了每一层在完成自己的任务时应该遵循什么规范。
需要应用工程师做的工作 : 编写应用工功能,明确对方地址,选择传输服务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VMllolYJ-1609849651557)(/home/tarena/month02/lz/Network/img/7.jpg)]
IP地址
IP地址 : 即在网络中标识一台计算机的地址编号。
IP地址分类
IPv4 特点
IPv6 特点(了解)
IP地址相关命令
ifconfig : 查看Linux系统下计算机的IP地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lSWjhi3k-1609849651558)(/home/tarena/month02/lz/Network/img/7.png)]
ping [ip]:查看计算机的连通性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O91u4v5V-1609849651560)(/home/tarena/month02/lz/Network/img/8.png)]
公网IP和内网IP
端口号
端口:网络地址的一部分,在一台计算机上,每个网络程序对应一个端口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PyXBPEuF-1609849651560)(/home/tarena/month02/lz/Network/img/3.png)]
端口号特点
服务端(Server):服务端是为客户端服务的,服务的内容诸如向客户端提供资源,保存客户端数据,处理客户端请求等。
客户端(Client) :也称为用户端,是指与服务端相对应,为客户提供一定应用功能的程序,我们平时使用的手机或者电脑上的程序基本都是客户端程序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CiAGDrv7-1609849651561)(/home/tarena/month02/lz/Network/img/10.jpg)]
套接字(Socket) : 实现网络编程进行数据传输的一种技术手段,网络上各种各样的网络服务大多都是基于 Socket 来完成通信的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDnvWnzT-1609849651564)(/home/tarena/month02/lz/Network/img/9.jpg)]
Python套接字编程模块:import socket
sockfd=socket.socket(socket_family,socket_type,proto=0)
功能:创建套接字
参数:socket_family 网络地址类型 AF_INET表示ipv4
socket_type 套接字类型 SOCK_DGRAM 表示udp套接字 (也叫数据报套接字)
proto 通常为0 选择子协议
返回值: 套接字对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8eEAp50A-1609849651564)(/home/tarena/month02/lz/Network/img/address.png)]
sockfd.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port) ('0.0.0.0',8888)
data,addr = sockfd.recvfrom(buffersize)
功能: 接收UDP消息
参数: 每次最多接收多少字节
返回值: data 接收到的内容
addr 消息发送方地址
n = sockfd.sendto(data,addr)
功能: 发送UDP消息
参数: data 发送的内容 bytes格式
addr 目标地址
返回值:发送的字节数
sockfd.close()
功能:关闭套接字
服务端客户端流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-idwTB9Mv-1609849651565)(/home/tarena/month02/lz/Network/img/11.png)]
"""
套接字使用基础示例
"""
import socket
# 创建一个UDP套接字
udp_socket = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM)
"""
1. 127.0.0.1 或者 localhost 那么 另外一端只能在
同一个计算机上通过127.0.0.1 访问之
2. 绑定自己的网络IP地址,那么另外一端可以在任何位置
通过 该主机IP地址访问之
3. 绑定自己的网络0.0.0.0地址,那么另外一端可以在
同一计算机上使用127.0.0.1访问之 或者 在任何位置
通过IP地址访问之
"""
udp_socket.bind(("172.40.91.108", 8800))
"""
练习: 基于刚才程序的基础上完成
客户端可以循环发送内容,服务端接收
当客户端直接回车什么都不输入时客户端结束
"""
"""
udp 服务端基础功能示例
重点代码 !!!
"""
from socket import *
# 创建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 绑定地址
udp_socket.bind(("0.0.0.0", 8888))
# 接收消息
while True:
data, addr = udp_socket.recvfrom(5)
print(addr, "收到:", data.decode()) # data字节串
# 发送消息 发送字节串
n = udp_socket.sendto(b"Thanks", addr)
print("发送了%d bytes" % n)
# 关闭套接字
udp_socket.close()
"""
udp 客户端流程示例
重点代码 !!!
"""
from socket import *
# 服务端地址
ADDR = ("172.40.91.108", 8888)
# 创建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 发送内容
while True:
msg = input(">>")
# msg为空 则退出循环
if not msg:
break
udp_socket.sendto(msg.encode(), ADDR)
# 接收反馈
data, addr = udp_socket.recvfrom(1024)
print("From server:", data.decode())
udp_socket.close()
"""
练习: 编写一个服务端和一个客户端
客户端循环输入单词,发送给服务端,从服务端获取
单词解释,打印出来。
* 使用 dict --> words 表完成
* 数据库和服务端一定是在一起的
"""
from socket import *
import pymysql
# 连接数据库的字典
DATABASE = {
"host": "localhost",
"port": 3306,
"user": "root",
"password": "123456",
"database": "dict",
"charset": "utf8"
}
# 服务器地址
ADDR = ("0.0.0.0", 8888)
# 数据处理
class Database:
def __init__(self):
self.db = pymysql.connect(**DATABASE)
self.cur = self.db.cursor()
def close(self):
self.cur.close()
self.db.close()
# 查找单词
def query_word(self, word):
sql = "select mean from words where word=%s;"
self.cur.execute(sql, [word])
mean = self.cur.fetchone() # 得到解释 ()
# 考虑是否查询到
if mean:
return mean[0]
else:
return "Not Found"
def main():
udp_socket = socket(AF_INET, SOCK_DGRAM)
udp_socket.bind(ADDR)
db = Database() # 实例化对象,处理数据库方法
# 接收单词
while True:
word, addr = udp_socket.recvfrom(50)
# 查找单词解释
mean = db.query_word(word.decode())
# 发送单词解释
udp_socket.sendto(mean.encode(), addr)
# 关闭套接字
udp_socket.close()
if __name__ == '__main__':
main()
"""
客户端
"""
from socket import *
# 服务端地址
ADDR = ("172.40.91.108", 8888)
# 创建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 发送内容
while True:
word = input("Word:")
# msg为空 则退出循环
if not word:
break
udp_socket.sendto(word.encode(), ADDR)
# 接收服务器发来的解释
data, addr = udp_socket.recvfrom(1024)
print("%s : %s" % (word, data.decode()))
udp_socket.close()
面向连接的传输服务
三次握手(建立连接)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rs7nzOIa-1609849651566)(/home/tarena/month02/lz/Network/img/1_scws.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OMrRiYvz-1609849651567)(/home/tarena/month02/lz/Network/img/1_schs.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNQ65ONh-1609849651569)(/home/tarena/month02/lz/Network/img/1_TCP_Server.png)]
sockfd=socket.socket(socket_family,socket_type,proto=0)
功能:创建套接字
参数:socket_family 网络地址类型 AF_INET表示ipv4
socket_type 套接字类型 SOCK_STREAM 表示tcp套接字 (也叫流式套接字)
proto 通常为0 选择子协议
返回值: 套接字对象
sockfd.listen(n)
功能 : 将套接字设置为监听套接字,确定监听队列大小
参数 : 监听队列大小
n:最多只能为1024
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8S9EOMv2-1609849651570)(/home/tarena/month02/lz/Network/img/11.jpg)]
connfd,addr = sockfd.accept()
功能: 阻塞等待处理客户端请求
返回值: connfd 客户端连接套接字
addr 连接的客户端地址
data = connfd.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容
n = connfd.send(data)
功能 : 发送消息
参数 :要发送的内容 bytes格式
返回值: 发送的字节数
"""
tcp 服务端基础示例
"""
from socket import *
# 创建tcp套接字 (不写参数默认也是tcp)
tcp_socket = socket(AF_INET,SOCK_STREAM)
# 绑定改地址
tcp_socket.bind(("0.0.0.0",8888))
# 设置监听
tcp_socket.listen(5)
# 等待客户端连接
print("等待连接......")
connfd,addr = tcp_socket.accept()
print("连接了:",addr)
# 收发消息
data = connfd.recv(1024)
print("接收到:",data.decode())
connfd.send(b"Thanks")
# 关闭套接字
connfd.close() # 断开连接
tcp_socket.close()
"""
tcp 客户端 基础示例
"""
from socket import *
# 服务器地址
server_addr = ("127.0.0.1",8888)
# 创建tcp套接字
tcp_socket = socket()
# 连接服务器
tcp_socket.connect(server_addr)
# 收发消息
msg = input(">>")
tcp_socket.send(msg.encode())
data = tcp_socket.recv(1024)
print("从服务器收到:",data.decode())
# 关闭套接字
tcp_socket.close()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EWunuKJt-1609849651571)(/home/tarena/month02/lz/Network/img/1_TCP_Client.png)]
sockfd.connect(server_addr)
功能:连接服务器
参数:元组 服务器地址
注意: 防止两端都阻塞,recv send要配合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HA0Rlr1r-1609849651573)(/home/tarena/month02/lz/Network/img/12.png)]
训练1: 将代码修改为客户端可以循环发送消息,接收thanks
输入 ## 客户端服务端均退出
训练2: 客户端退出后,服务端不退出,继续可以连接
处理下一个客户端。
"""
tcp 服务端循环实例1
重点代码 !!!
"""
from socket import *
from time import sleep
# 创建tcp套接字 (不写参数默认也是tcp)
tcp_socket = socket(AF_INET,SOCK_STREAM)
# 绑定改地址
tcp_socket.bind(("0.0.0.0",8888))
# 设置监听
tcp_socket.listen(5)
# 循环等待客户端连接
while True:
print("等待连接......")
connfd,addr = tcp_socket.accept()
print("连接了:",addr)
# 循环与某一个客户端收发消息
while True:
data = connfd.recv(5)
# 客户端断开连接,此时recv返回空字节串
if not data:
break
# 收到了## 表示客户端已经退出
# if data == b"##":
# break
print("接收到:",data.decode())
connfd.send(b"Thanks#")
# sleep(0.1)
connfd.close() # 断开连接
# 关闭套接字
tcp_socket.close()
"""
tcp 客户端 循环实例
重点代码 !!!
"""
from socket import *
# 服务器地址
server_addr = ("127.0.0.1",8888)
# 创建tcp套接字
tcp_socket = socket()
# 连接服务器
tcp_socket.connect(server_addr)
# 收发消息
while True:
msg = input(">>")
# 直接回车msg为空
if not msg:
break
tcp_socket.send(msg.encode())
# 发送##告知服务端自己退出
# if msg == "##":
# break
data = tcp_socket.recv(1024)
print("从服务器收到:",data.decode())
# 关闭套接字
tcp_socket.close()
"""
tcp 服务端 循环模型2
重点代码 !!!
"""
from socket import *
# 创建tcp套接字
tcp_socket = socket()
# 绑定改地址
tcp_socket.bind(("0.0.0.0",8888))
# 设置监听
tcp_socket.listen(5)
# 循环等待客户端连接
while True:
print("等待连接......")
connfd,addr = tcp_socket.accept()
print("连接了:",addr)
# 收发消息 : 每次连接只能收发一次消息
data = connfd.recv(1024)
print("收到:",data.decode())
connfd.send(b"Thanks")
connfd.close()
tcp_socket.close()
"""
tcp 循环模型 2
重点代码 !!!
"""
from socket import *
# 服务器地址
server_addr = ("127.0.0.1",8888)
# 所有连接操作 和数据操作 全在循环中
while True:
msg = input(">>")
if not msg:
break
tcp_socket = socket()
tcp_socket.connect(server_addr)
tcp_socket.send(msg.encode())
data = tcp_socket.recv(1024)
print("从服务器收到:",data.decode())
tcp_socket.close()
"""
练习: 使用tcp完成,将一个图片从客户端上传的服务端
注意,图片有可能比较大,不允许一次性 read()读取
在服务端以当前日期为名字存储
2020-10-16.jpg
思路 : 客户段读取文件内容发送
服务端接收内容,写入文件
补充要求 : 当文件上传完成后,服务端通知一下客户端
“上传成功” 客户端接收到这个消息后 打印出来
"""
from socket import *
from time import localtime
# 创建tcp套接字服务端
tcp_socket = socket()
tcp_socket.bind(("0.0.0.0",8888))
tcp_socket.listen(5)
# 循环接收客户端连接
while True:
connfd,addr = tcp_socket.accept()
# 打开一个以当前日期命名的文件
filename = "%d-%d-%d.jpg"%localtime()[:3]
file = open(filename,'wb')
# 接收某一个客户端上传的图片
while True:
# 边收边写入
data = connfd.recv(1024)
if data == b'##':
break
file.write(data)
file.close()
# 发送通知
connfd.send("上传完成".encode())
connfd.close()
# 关闭套接字
tcp_socket.close()
"""
客户端
"""
from socket import *
# 服务器地址
server_addr = ("127.0.0.1", 8888)
# 创建tcp套接字
tcp_socket = socket()
tcp_socket.connect(server_addr)
file = open("timg.jfif", 'rb') # 二进制打开
# 边读取边发送
while True:
data = file.read(1024)
if not data:
break
tcp_socket.send(data)
file.close()
tcp_socket.send(b"##")
# 接收通知
msg = tcp_socket.recv(1024)
print(msg.decode())
# 关闭套接字
tcp_socket.close()
"""
练习2: 编写一个程序,启动服务端后 作为对话机器人
小美。 客户端可以输入问题给小美,小美做出回答
如果没有找到答案则回复 “人家还小不知道”
要求,可以多个客户端一起启动
"""
from socket import *
import re
CHAT_FILE = "./chat.txt"
chat = [] # [(key,answer)]
# 提取文件中的内容,放在列表中
def answer():
# 打开对话文件
file = open(CHAT_FILE)
for line in file:
# 匹配出关键词和答案
tup = re.findall(r"(\w+)\s+(.*)", line)
chat.extend(tup) # 列表的合并
file.close()
# 找答案
def find(q):
for key, value in chat:
# 如果关键词在问题中
if key in q:
return value # 返回准备的答案
return "人家还小不知道啦!"
def main():
answer() # 生成列表
# 创建监听套接字
tcp_socket = socket()
tcp_socket.bind(("0.0.0.0", 8888))
tcp_socket.listen(5)
# 循环等待客户端连接
while True:
print("等待问题......")
connfd, addr = tcp_socket.accept()
# 接收问题
data = connfd.recv(1024)
# 找答案
value = find(data.decode())
connfd.send(value.encode())
connfd.close()
tcp_socket.close()
if __name__ == '__main__':
main()
"""
客户端
"""
from socket import *
# 服务器地址
server_addr = ("127.0.0.1", 8888)
# 所有连接操作 和数据操作 全在循环中
while True:
msg = input("我:")
if not msg:
break
tcp_socket = socket()
tcp_socket.connect(server_addr)
tcp_socket.send(msg.encode())
data = tcp_socket.recv(1024)
print("小美:", data.decode())
tcp_socket.close()
tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。
tcp连接中如果一端已经不存在,仍然试图通过send向其发送数据则会产生BrokenPipeError
一个服务端可以同时连接多个客户端,也能够重复被连接
tcp粘包问题
产生原因
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7NoWXMI-1609849651574)(/home/tarena/month02/lz/Network/img/13.jpg)]
带来的影响
处理方法
传输特征
套接字编程区别
使用场景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-txGd6gkg-1609849651575)(/home/tarena/month02/lz/Network/img/14.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9OLHEZFZ-1609849651577)(/home/tarena/month02/lz/Network/img/1_tcpsjb.png)]
源端口和目的端口 各占2个字节,分别写入源端口和目的端口。
序号 占4字节。TCP是面向字节流的。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。例如,一报文段的序号是301,而接待的数据共有100字节。这就表明本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。
确认号 占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(序号501~700),这表明B正确收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701。
确认ACK(ACKnowledgment) 仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1。
同步SYN(SYNchronization) 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。
终止FIN(FINis,意思是“完”“终”) 用来释放一个连接。当FIN=1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。
多任务
即操作系统中可以同时运行多个任务。比如我们可以同时挂着qq,听音乐,同时上网浏览网页。这是我们看得到的任务,在系统中还有很多系统任务在执行,现在的操作系统基本都是多任务操作系统,具备运行多任务的能力。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IBbj8IHd-1609849651577)(/home/tarena/month02/lz/Network/img/13.png)]
计算机原理
CPU:计算机硬件的核心部件,用于对任务进行执行运算。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BCu3WtYH-1609849651578)(/home/tarena/month02/lz/Network/img/14.jpg)]
操作系统调用CPU执行任务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5ZE0ZHl-1609849651579)(/home/tarena/month02/lz/Network/img/15.png)]
cpu轮训机制 : cpu都在多个任务之间快速的切换执行,切换速度在微秒级别,其实cpu同时只执行一个任务,但是因为切换太快了,从应用层看好像所有任务同时在执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ctP8ssz-1609849651582)(/home/tarena/month02/lz/Network/img/16.gif)]
多核CPU:现在的计算机一般都是多核CPU,比如四核,八核,我们可以理解为由多个单核CPU的集合。这时候在执行任务时就有了选择,可以将多个任务分配给某一个cpu核心,也可以将多个任务分配给多个cpu核心,操作系统会自动根据任务的复杂程度选择最优的分配方案。
什么是多任务编程
多任务编程即一个程序中编写多个任务,在程序运行时让这多个任务一起运行,而不是一个一个的顺次执行。
比如微信视频聊天,这时候在微信运行过程中既用到了视频任务也用到了音频任务,甚至同时还能发消息。这就是典型的多任务。而实际的开发过程中这样的情况比比皆是。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cn6WZOgf-1609849651583)(/home/tarena/month02/lz/Network/img/12.jpg)]
多任务意义
提高了任务之间的配合,可以根据运行情况进行任务创建。
比如: 你也不知道用户在微信使用中是否会进行视频聊天,总不能提前启动起来吧,这是需要根据用户的行为启动新任务。
充分利用计算机资源,提高了任务的执行效率。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S9iE17MR-1609849651584)(/home/tarena/month02/lz/Network/img/17.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cb7EDWLQ-1609849651585)(/home/tarena/month02/lz/Network/img/18.jpg)]
定义: 程序在计算机中的一次执行过程。
程序是一个可执行的文件,是静态的占有磁盘。
进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9957Xoof-1609849651586)(/home/tarena/month02/lz/Network/img/19.jpg)]
进程状态
三态
就绪态 : 进程具备执行条件,等待系统调度分配cpu资源
运行态 : 进程占有cpu正在运行
等待态 : 进程阻塞等待,此时会让出cpu
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axyl3Jko-1609849651588)(/home/tarena/month02/lz/Network/img/4_3.png)]
五态 (在三态基础上增加新建和终止)
新建 : 创建一个进程,获取资源的过程
终止 : 进程结束,释放资源的过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oBfyrMhm-1609849651589)(/home/tarena/month02/lz/Network/img/4_5.png)]
进程命令
查看进程信息
ps -aux
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdPmkhVU-1609849651590)(/home/tarena/month02/lz/Network/img/20.png)]
进程树形结构
pstree
使用模块 : multiprocessing
创建流程
【1】 将需要新进程执行的事件封装为函数
【2】 通过模块的Process类创建进程对象,关联函数
【3】 可以通过进程对象设置进程信息及属性
【4】 通过进程对象调用start启动进程
【5】 通过进程对象调用join回收进程资源
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数
args 元组,用于给target函数位置传参
kwargs 字典,给target函数键值传参
p.start()
功能 : 启动进程
注意 : 启动进程此时target绑定函数开始执行,该函数作为新进程执行内容,此时进程真正被创建
p.join([timeout])
功能:阻塞等待回收进程
参数:超时时间
"""
Process进程基础创建示例:
将需要新进程执行的事件封装为函数
通过模块的Process类创建进程对象,关联函数
通过进程对象调用start启动进程
通过进程对象调用join回收进程资源
"""
import multiprocessing as mp
from time import sleep
a = 1
# 进程的执行函数
def fun():
print("开始执行一个进程内容")
global a
print("a = ", a)
a = 10000
sleep(3)
print("一个任务假装执行了3秒结束")
# 创建进程对象 绑定函数
p = mp.Process(target=fun)
# 启动进程 这时进程产生,进程执行fun函数
p.start()
print("我也要干点事情")
sleep(2)
print("我这件事做了2秒")
# 阻塞等待回收进程 将创建的进程资源释放
p.join()
print("a:", a) # 1
"""
含有参数的进程函数示例
"""
from multiprocessing import Process
from time import sleep
# 含有参数的进程函数
def worker(sec, name):
for i in range(3):
sleep(sec)
print("I'm %s" % name)
print("I'm working")
# 位置传参 args = (,)一定为元组
# p = Process(target=worker,args=(2,"Levi"))
# 关键字传参 kwargs={}
p = Process(target=worker,
args=(2,),
kwargs={
"name": "Tom"})
p.start()
p.join(3) # 最多等待3秒
print("==================")
"""
同时创建多个子进程
"""
from multiprocessing import Process
from time import sleep
import os, sys
def th1():
sleep(3)
print("吃饭")
print(os.getppid(), "---", os.getpid())
def th2():
sleep(2)
print("睡觉")
print(os.getppid(), "---", os.getpid())
def th3():
sys.exit("打豆豆进程退出")
sleep(4)
print("打豆豆")
print(os.getppid(), "---", os.getpid())
things = [th1, th2, th3]
jobs = [] # 存储进程对象
# 循环创建进程
for th in things:
p = Process(target=th)
jobs.append(p) # 存储进程对象
p.start()
for i in jobs:
i.join()
"""
练习1 : 大文件拆分
将一个文件拆分成2个部分,按照字节数平分
要求使用两个子进程完成这件事,要求上下两个部分
的拆分工作同时进程
思路: 一个进程拷贝上半部分 函数
一个进程拷贝下半部分 函数
两个子进程同时执行
os.path.getsize() 获取文件大小
文件操作位置: seek(1000,0)
"""
from multiprocessing import Process
import os
# 获取文件大小
size = os.path.getsize("zly.jpg")
# 如果父进程中打开文件,各子进程直接使用fr
# 那么文件偏移量会相互影响,所以这里应该在各自子
# 进程中打开
# fr = open("zly.jpg", 'rb')
# 上半部分
def top():
fr = open("zly.jpg", 'rb')
fw = open("top.jpg", 'wb')
n = size // 2 # 从头开始复制n个字节
while n >= 1024:
fw.write(fr.read(1024))
n -= 1024
else:
fw.write(fr.read(n))
fr.close()
fw.close()
# 下半部分
def bot():
fr = open("zly.jpg", 'rb')
fw = open("bot.jpg", 'wb')
fr.seek(size // 2, 0) # 偏移量放在中间
while True:
# 从一半开始复制
data = fr.read(1024)
if not data:
break
fw.write(data)
fr.close()
fw.close()
jobs = [] # 存储进程对象
# 循环创建进程
for th in [bot, top]:
p = Process(target=th)
jobs.append(p) # 存储进程对象
p.start()
[i.join() for i in jobs]
进程执行现象理解 (难点)
进程对象属性
"""
进程属性信息 解释
"""
from multiprocessing import Process
import time
def fun():
for i in range(3):
print(time.ctime())
time.sleep(2)
# 创建进程对象
p = Process(target=fun, name="Aid")
# 该子进程会随父进程而退出 start前设置
p.daemon = True
p.start() # 进程有了
print("Name:", p.name) # 进程名
print("PID:", p.pid) # PID
print("is alive:", p.is_alive())
os.getpid()
功能: 获取一个进程的PID值
返回值: 返回当前进程的PID
os.getppid()
功能: 获取父进程的PID号
返回值: 返回父进程PID
sys.exit(info)
功能:退出进程
参数:字符串 表示退出时打印内容
孤儿和僵尸
孤儿进程 : 父进程先于子进程退出,此时子进程成为孤儿进程。
僵尸进程 : 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会成为僵尸进程。
特点: 僵尸进程虽然结束,但是会存留部分进程信息资源在内存中,大量的僵尸进程会浪费系统的内存资源。
如何避免僵尸进程产生
使用join()回收
在父进程中使用signal方法处理
from signal import *
signal(SIGCHLD,SIG_IGN)
"""
僵尸进程的处理
"""
from multiprocessing import Process
from time import sleep
from signal import *
def worker():
for i in range(3):
sleep(2)
print("I'm Levi")
print("I'm working")
# 忽略子进程退出
signal(SIGCHLD, SIG_IGN)
p = Process(target=worker)
p.start()
print(p.pid)
# p.join() # 处理僵尸
while True:
pass
进程的基本创建方法将子进程执行的内容封装为函数。如果我们更热衷于面向对象的编程思想,也可以使用类来封装进程内容。
创建步骤
【1】 继承Process类
【2】 重写__init__
方法添加自己的属性,使用super()加载父类属性
【3】 重写run()方法
使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
【3】 调用join回收进程
"""
自定义进程类
"""
from multiprocessing import Process
class MyProcess(Process):
def __init__(self,value):
self.value = value
# 调用父类init保留父类属性
super().__init__()
def fun1(self):
print("假设这个事情很复杂")
def fun2(self):
print("特别复杂 too",self.value)
# 进程做的事情
def run(self):
self.fun1()
self.fun2()
p = MyProcess(3)
p.start() # 运行run'作为一个进程
p.join()
# 猜想源码怎么写的
# class Process:
# def __init__(self,target=None):
# self.target = target
#
# def run(self):
# self.target()
#
# def start(self):
# self.run()
"""
作业: 1. 进程函数使用熟练,自定义进程类
求100000以内质数之和,写成一个函数
写一个装饰器求一个这个函数运行时间
将100000分成4等份 分别使用4个进程求
每一份的质数之和,四个进程同时执行
记录时间
将100000分成10等份 分别使用10个进程求
每一份的质数之和,10个进程同时执行
记录时间
"""
import time
from multiprocessing import Process
# 装饰器
def timeis(f):
def wrapper(*args, **kwargs):
start_time = time.time()
res = f(*args, **kwargs)
end_time = time.time()
print("函数执行时间:", end_time - start_time)
return res
return wrapper
# 判断一个数n是否为质数
def isprime(n):
if n <= 1:
return False
for i in range(2, n // 2 + 1):
if n % i == 0:
return False
return True
class Prime(Process):
def __init__(self, begin, end):
self.begin = begin
self.end = end
super().__init__()
def run(self):
prime = []
for i in range(self.begin, self.end):
if isprime(i):
prime.append(i) # 是质数加到列表中
print(sum(prime))
@timeis
def multi_process(n):
jobs = []
step = 100000 // n
for i in range(1, 100001, step):
p = Prime(i, i + step) # 数值区间
jobs.append(p)
p.start()
[i.join() for i in jobs]
# @timeis
# def prime_sum():
# prime = []
# for i in range(1,100001):
# if isprime(i):
# prime.append(i) # 是质数加到列表中
# print(sum(prime))
multi_process(10)
# 函数执行时间: 6.944215297698975
# multi_process(10)
# 函数执行时间: 7.557642936706543
# multi_process(4)
# 函数执行时间: 14.412267923355103
# prime_sum()
必要性
【1】 进程的创建和销毁过程消耗的资源较多
【2】 当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大
【3】 进程池技术很好的解决了以上问题。
原理
创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。
进程池实现
from multiprocessing import Pool
Pool(processes)
功能: 创建进程池对象
参数: 指定进程数量,默认根据系统自动判定
pool.apply_async(func,args,kwds)
功能: 使用进程池执行 func事件
参数: func 事件函数
args 元组 给func按位置传参
kwds 字典 给func按照键值传参
pool.close()
功能: 关闭进程池
pool.join()
功能: 回收进程池中进程
"""
进程池使用示例
* 父进程退出则进程池会自动销毁
"""
from multiprocessing import Pool
from time import sleep, ctime
import random
# 进程池事件函数
def worker(msg, sec):
print(ctime(), "---", msg)
sleep(sec)
# 创建进程池
pool = Pool(4)
# 向进程池队列添加事件
for i in range(10):
msg = "Tedu-%d" % i
pool.apply_async(func=worker,
args=(msg, random.randint(1, 4)))
# 关闭进程池 不能添加新的事件
pool.close()
# 阻塞回收进程池
pool.join()
"""
练习1 : 使用进程池完成
拷贝一个指定的目录 (文件夹中全是普通文件没有子文件夹)
思路 : 1. 什么事情作为进程池事件 (拷贝文件)
2. 拷贝文件函数 找共性封装 特性传参
os.listdir()
os.mkdir("xxx")
"""
from multiprocessing import Pool, Queue
import os
q = Queue() # 创建一个消息队列
# 拷贝每一个文件 --》 进程池要做的事情
def copy(filename, old_folder, new_folder):
fr = open(old_folder + "/" + filename, 'rb')
fw = open(new_folder + "/" + filename, 'wb')
while True:
data = fr.read(1024)
if not data:
break
n = fw.write(data) # 获取已经拷贝的大小
q.put(n) # 将字节数放到消息队列
fr.close()
fw.close()
# 获取文件夹总大小
def get_size(dir):
total_size = 0
for file in os.listdir(dir):
total_size += os.path.getsize(dir + '/' + file)
return total_size # 返回文件夹大小
# 创建进程池 参数为要拷贝的目录
def main(old_folder):
# 创建新文件夹
new_folder = old_folder + "-备份"
os.mkdir(new_folder)
total_size = get_size(old_folder)
pool = Pool(4)
for file in os.listdir(old_folder):
pool.apply_async(func=copy,
args=(file, old_folder, new_folder))
pool.close()
copy_size = 0 # 已经拷贝的大小
while copy_size < total_size:
copy_size += q.get()
print("已拷贝:%.2f%%" % (copy_size / total_size * 100))
pool.join()
if __name__ == '__main__':
main("/home/tarena/FTP")
必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
常用进程间通信方法:消息队列,套接字等。
消息队列使用
通信原理: 在内存中开辟空间,建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
实现方法
from multiprocessing import Queue
q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象
q.put(data,[block,timeout])
功能:向队列存入消息
参数:data 要存入的内容
block 设置是否阻塞 False为非阻塞
timeout 超时检测
block和timeout一般情况下不会写
q.get([block,timeout])
功能:从队列取出消息
参数:block 设置是否阻塞 False为非阻塞
timeout 超时检测
返回值: 返回获取到的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 获取队列中消息个数
q.close() 关闭队列
"""
消息队列进程间通信演示
"""
from multiprocessing import Process,Queue
# 创建消息队列
q = Queue()
def request():
name = "Levi"
passwd = "123456"
# 通过消息队列传送给另外一个进程
q.put(name)
q.put(passwd)
def handle():
# 从消息队列获取内容
name = q.get()
passwd = q.get()
print("用户:",name)
print("密码:",passwd)
p1 = Process(target=request)
p2 = Process(target=handle)
p1.start()
p2.start()
p1.join()
p2.join()
**群聊聊天室 **
功能 : 类似qq群功能
【1】 有人进入聊天室需要输入姓名,姓名不能重复
【2】 有人进入聊天室时,其他人会收到通知:xxx 进入了聊天室
【3】 一个人发消息,其他人会收到:xxx : xxxxxxxxxxx
【4】 有人退出聊天室,则其他人也会收到通知:xxx退出了聊天室
【5】 扩展功能:服务器可以向所有用户发送公告:管理员消息: xxxxxxxxx
群聊聊天室
需求分析
* 有人进入聊天室需要输入姓名,姓名不能重复
* 有人进入聊天室时,其他人会收到通知:xxx 进入了聊天室
* 一个人发消息,其他人会收到:xxx : xxxxxxxxxxx
* 有人退出聊天室,则其他人也会收到通知:xxx退出了聊天室
技术分析 c/s
存储人员信息: 服务端
存什么 : 名字 地址
怎么存 : {name:address}
[(name,address),...]
class Person:
def __init__(self,name,address):
self.name = name
self.address = address
消息的网络传递 : udp
消息发送:转发的方法 客户端-》服务端-》客户端
收发消息: 多进程,一个负责发送,一个负责接收
功能模块划分 封装方法 : 函数封装
框架模型
进入聊天室
聊天
退出聊天室
网络协议设置
请求类型 数据参量
进入 LOGIN name
聊天 CHAT name 说话的内容
退出 EXIT
功能模块逻辑具体分析
框架模型
服务端 : 1. 创建udp网络服务端
2. 循环接收各种客户端请求
3. 根据请求做出调用
客户端 : 1. 创建udp网络
进入聊天室
客户端 : 1. 输入姓名
2. 发送给服务端
3. 接收服务端反馈
4. Y 进入聊天 N 回到第一步
服务端 : 1. 接收请求
2. 判断是否有这个姓名
3. 根据判断发送结果
Y -》存储用户 告知其他人
N -》over
聊天
客户端 :1. 创建子进程
2. 父进程循环发送消息
子进程循环接收消息
服务端 : 1. 接收请求
2. 将消息转发给其他人
退出聊天室
客户端 1. 输入exit 表示退出
2. 发送请求
服务端 1. 接收请求
2. 告知其他人
3. 删除该用户
优化完善
"""
Author: Levi
Email: [email protected]
Time: 2020-10-20
env: python3.6 pycharm
socket and Process exercise
"""
from socket import *
from multiprocessing import Process
# 服务器地址
HOST = "0.0.0.0"
PORT = 8000
ADDR = (HOST, PORT)
# 存储用户信息 {name:address}
user = {
}
# 处理进入聊天室
def login(sock, name, addr):
if name in user or "管理" in name:
# 反馈结果
sock.sendto(b"FAIL", addr)
else:
sock.sendto(b"OK", addr)
# 循环通知其他人
msg = "欢迎 %s 进入聊天室" % name
for i in user:
sock.sendto(msg.encode(), user[i])
user[name] = addr # 增加该用户
# print(user)
# 处理聊天
def chat(sock, name, content):
msg = "%s : %s" % (name, content)
for i in user:
# 出去本人
if i != name:
sock.sendto(msg.encode(), user[i])
# 处理退出
def exit(sock, name):
del user[name] # 删除用户
msg = "%s 退出了聊天室" % name
for i in user:
sock.sendto(msg.encode(), user[i])
# 处理客户端请求
def request(sock):
# 循环接收各种客户端请求 (总分模式)
while True:
# 接收所有客户端所有请求
data, addr = sock.recvfrom(1024)
# 对数据结构进行简单解析
tmp = data.decode().split(' ', 2)
if tmp[0] == "LOGIN":
# tmp --> [LOGIN,name]
login(sock, tmp[1], addr)
elif tmp[0] == "CHAT":
# tmp --> [CHAT,name,content]
chat(sock, tmp[1], tmp[2])
elif tmp[0] == "EXIT":
# tmp--> [EXIT,name]
exit(sock, tmp[1])
# 程序启动函数
def main():
# UDP套接字
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(ADDR)
# 创建子进程
p = Process(target=request, args=(sock,))
p.daemon = True
p.start()
# 发送管理员信息
while True:
content = input("管理员消息:")
if content == 'exit':
break
msg = "CHAT 管理员消息 " + content
sock.sendto(msg.encode(), ADDR)
if __name__ == '__main__':
main()
"""
chat room 客户端代码
"""
from socket import *
from multiprocessing import Process
import sys
# 服务器地址
ADDR = ('119.3.124.77', 8000)
# 处理登录
def login(sock):
while True:
# 进入聊天室
name = input("Name:")
# 发送姓名
msg = "LOGIN " + name
sock.sendto(msg.encode(), ADDR)
# 接收结果
result, addr = sock.recvfrom(128)
if result.decode() == 'OK':
print("进入聊天室")
return name
else:
print("该用户已存在")
# 接收消息
def recv_msg(sock):
while True:
data, addr = sock.recvfrom(1024 * 10)
msg = "\n%s\n发言:" % data.decode()
print(msg, end="") # 不换行
# 发送消息
def send_msg(sock, name):
while True:
try:
content = input("发言:")
except KeyboardInterrupt:
content = "exit"
# 输入exit表示要退出聊天室
if content == "exit":
msg = "EXIT " + name
sock.sendto(msg.encode(), ADDR)
sys.exit("您已退出聊天室")
msg = "CHAT %s %s" % (name, content)
# 给服务端发送聊天请求
sock.sendto(msg.encode(), ADDR)
# 网络连接
def main():
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(("0.0.0.0",12345)) # 地址不变化
name = login(sock) # 进入聊天室
# 创建子进程 用于接收消息
p = Process(target=recv_msg, args=(sock,))
p.daemon = True # 父进程退出子进程也退出
p.start()
send_msg(sock, name) # 父进程发送消息
if __name__ == '__main__':
main()
什么是线程
【1】 线程被称为轻量级的进程,也是多任务编程方式
【2】 也可以利用计算机的多cpu资源
【3】 线程可以理解为进程中再开辟的分支任务
线程特征
【1】 一个进程中可以包含多个线程
【2】 线程也是一个运行行为,消耗计算机资源
【3】 一个进程中的所有线程共享这个进程的资源
【4】 多个线程之间的运行同样互不影响各自运行
【5】 线程的创建和销毁消耗资源远小于进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H7gPpSky-1609849651591)(/home/tarena/month02/lz/Network/img/21.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5IH2yhMv-1609849651592)(/home/tarena/month02/lz/Network/img/22.jpg)]
创建方法
【1】 创建线程对象
from threading import Thread
t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
【2】 启动线程
t.start()
【3】 回收线程
t.join([timeout])
"""
线程基本使用示例
"""
import threading
from time import sleep
import os
a = 1
# 线程的执行函数
def music():
for i in range(3):
sleep(2)
print(os.getpid(), "播放:甜蜜蜜")
global a
print("a = ", a)
a = 10000
# 创建线程对象
t = threading.Thread(target=music)
# 启动线程
t.start()
for i in range(4):
sleep(1)
print(os.getpid(), "播放:葫芦娃")
# 回收线程
t.join()
print("a:", a)
"""
创建线程实例2
"""
from threading import Thread
from time import sleep
# 含有参数的线程函数
def fun(sec, name):
print("含有参数的线程函数")
sleep(sec)
print("%s线程执行完成" % name)
# 创建多个线程
jobs = []
for i in range(5):
t = Thread(target=fun,
args=(2,),
kwargs={
"name": "T%d" % i})
jobs.append(t)
t.start() # 启动线程
# 回收线程
[i.join() for i in jobs]
"""
线程属性
"""
from threading import Thread
from time import sleep
def fun():
sleep(3)
print("线程属性设置")
t = Thread(target=fun)
# 分之线程会随主线程退出
t.setDaemon(True)
t.start()
print(t.is_alive())
t.setName("Tarena") # 线程名称
print(t.getName())
"""
练习:模拟一个售票系统程序
一共500张票 ---》T1---T500
编程10个线程模拟10个售票窗口机器 记为 W1-W10
10个窗口同时售票知道所有票都卖出为止
票按照顺序出售
每个窗口卖出一张后 w2----T346
卖出一张需要0.1s
"""
from threading import Thread
from time import sleep
ticket = ["T%d" % x for x in range(1, 501)]
# 买票函数
def sell(w):
while ticket:
print("%s --- %s" % (w, ticket.pop(0)))
sleep(0.1)
jobs = []
for i in range(1, 11):
t = Thread(target=sell, args=("w%d" % i,))
jobs.append(t)
t.start() # 启动线程
# 回收线程
[i.join() for i in jobs]
创建步骤
【1】 继承Thread类
【2】 重写__init__
方法添加自己的属性,使用super()加载父类属性
【3】 重写run()方法
使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
【3】 调用join回收线程
"""
自定义线程类
"""
from threading import Thread
import time
class MyThread(Thread):
def __init__(self, song):
self.song = song
super().__init__() # 加载父类方法
def run(self):
for i in range(3):
print("playing %s:%s" % (self.song, time.ctime()))
time.sleep(2)
t = MyThread("凉凉")
t.start() # 运行run方法,作为一个线程执行
t.join()
线程通信方法: 线程间使用全局变量进行通信
共享资源争夺
同步互斥机制
同步 : 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NbgAWljn-1609849651594)(/home/tarena/month02/lz/Network/img/7_tb.png)]
互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0g8BtqZ-1609849651595)(/home/tarena/month02/lz/Network/img/7_hc.png)]
from threading import Event
e = Event() 创建线程event对象
e.wait([timeout]) 阻塞等待e被set
e.set() 设置e,使wait结束阻塞
e.clear() 使e回到未被设置状态
e.is_set() 查看当前e是否被设置
"""
event 同步互斥方法
"""
from threading import Thread, Event
e = Event() # event对象
msg = None # 用于线程通信
# 线程函数
def 杨子荣():
print("杨子荣前来拜山头")
global msg
msg = "天王盖地虎"
e.set() # 解除阻塞
t = Thread(target=杨子荣)
t.start()
# 主线程验证口令
print("说对口令就是自己人")
e.wait() # 阻塞等待
if msg == '天王盖地虎':
print("宝塔镇河妖")
print("确认过眼神,你是对的人")
else:
print("打死他...无情啊...")
t.join()
from threading import Lock
lock = Lock() 创建锁对象
lock.acquire() 上锁 如果lock已经上锁再调用会阻塞
lock.release() 解锁
with lock: 上锁
...
...
with代码块结束自动解锁
"""
lock 线程锁
"""
from threading import Thread, Lock
lock = Lock()
a = b = 0 # 共享资源
def value():
while True:
with lock:
if a != b:
print("a = %d,b = %d" % (a, b))
# with语句块后自动解锁
t = Thread(target=value)
t.start()
while True:
lock.acquire() # 上锁
a += 1
b += 1
lock.release() # 解锁
t.join()
"""
练习2: 创建两个线程同时执行
一个线程负责打印 1---52 52个数字
另一个线程打印 A--Z 26个字母
要求打印结果为 12A34B.....5152Z
"""
from threading import Thread, Lock
lock1 = Lock()
lock2 = Lock()
def print_num():
for i in range(1, 53, 2):
lock1.acquire()
print(i)
print(i + 1)
lock2.release()
def print_chr():
for i in range(65, 91):
lock2.acquire()
print(chr(i))
lock1.release()
t1 = Thread(target=print_num)
t2 = Thread(target=print_chr)
lock2.acquire() # 让打印数字先执行
t1.start()
t2.start()
t1.join()
t2.join()
什么是死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMlgWfL6-1609849651597)(/home/tarena/month02/lz/Network/img/ss.jpg)]
死锁产生条件
互斥条件:指线程使用了互斥方法,使用一个资源时其他线程无法使用。
请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,在获取到新的资源前不会释放自己保持的资源。
不剥夺条件:不会受到线程外部的干扰,如系统强制终止线程等。
环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,如 T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。
如何避免死锁
什么是GIL问题 (全局解释器锁)
由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。
导致后果
因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞任务时可以提升程序效率,其他情况并不能对效率有所提升。
GIL问题建议
尽量使用进程完成无阻塞的并发行为
不使用c作为解释器 (Java C#)
Guido的声明:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235
结论
使用场景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AJtN0P7h-1609849651597)(/home/tarena/month02/lz/Network/img/23.jpg)]
什么是网络并发
在实际工作中,一个服务端程序往往要应对多个客户端同时发起访问的情况。如果让服务端程序能够更好的同时满足更多客户端网络请求的情形,这就是并发网络模型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iDk2GGRX-1609849651598)(/home/tarena/month02/lz/Network/img/24.jpg)]
循环网络模型问题
循环网络模型只能循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个。这样的网络模型虽然简单,资源占用不多,但是无法同时处理多个客户端请求就是其最大的弊端,往往只有在一些低频的小请求任务中才会使用。
多任务并发模型具体指多进程多线程网络并发模型,即每当一个客户端连接服务器,就创建一个新的进程/线程为该客户端服务,客户端退出时再销毁该进程/线程,多任务并发模型也是实际工作中最为常用的服务端处理模型。
"""
多进程网络并发模型
重点代码 !!!
创建网络套接字用于接收客户端请求
等待客户端连接
客户端连接,则创建新的进程具体处理客户端请求
主进程继续等待其他客户端连接
如果客户端退出,则销毁对应的进程
"""
from socket import *
from multiprocessing import Process
from signal import *
# 服务器地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
# 实现具体的业务功能,客户端请求都在这里处理
def handle(connfd):
# 与客户端配合测试
while True:
data = connfd.recv(1024)
if not data:
break
print(data.decode())
connfd.send(b"ok")
connfd.close()
# 函数中编写并发服务
def main():
sock = socket() # tcp套接字
sock.bind(ADDR)
sock.listen(5)
print("Listen the port %d" % PORT)
signal(SIGCHLD, SIG_IGN) # 处理僵尸进程
while True:
# 循环接收客户端连接
try:
connfd, addr = sock.accept()
print("Connect from", addr)
except KeyboardInterrupt:
# 退出服务
sock.close()
break
# 为连接的客户端创建新进程
p = Process(target=handle, args=(connfd,))
p.daemon = True # 客户端随服务端退出
p.start()
if __name__ == '__main__':
main()
"""
多线程网络并发模型
重点代码 !!!
"""
from socket import *
from threading import Thread
# 服务器地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
# 实现具体的业务功能,客户端请求都在这里处理
class MyThread(Thread):
def __init__(self, connfd):
self.connfd = connfd
super().__init__()
def run(self):
# 与客户端配合测试
while True:
data = self.connfd.recv(1024)
if not data:
break
print(data.decode())
self.connfd.send(b"ok")
self.connfd.close()
# 函数中编写并发服务
def main():
sock = socket() # tcp套接字
sock.bind(ADDR)
sock.listen(5)
print("Listen the port %d" % PORT)
while True:
# 循环接收客户端连接
try:
connfd, addr = sock.accept()
print("Connect from", addr)
except KeyboardInterrupt:
# 退出服务
sock.close()
break
# 使用自定义线程类为连接的客户端创建新线程
t = MyThread(connfd)
t.setDaemon(True) # 客户端随服务端退出
t.start()
if __name__ == '__main__':
main()
ftp 文件服务器
【1】 分为服务端和客户端,要求可以有多个客户端同时操作。
【2】 客户端可以查看服务器文件库中有什么文件。
【3】 客户端可以从文件库中下载文件到本地。
【4】 客户端可以上传一个本地文件到文件库。
【5】 使用print在客户端打印命令输入提示,引导操作
"""
ftp 文件服务 客户端
c / s 连接 发请求 获取结果
"""
from socket import *
import sys
from time import sleep
# 服务端地址
ADDR = ("127.0.0.1", 8888)
# 发起请求的所有功能
class FTPClient:
def __init__(self, sock):
self.sock = sock
# 请求文件列表
def do_list(self):
self.sock.send(b"LIST") # 发送请求
result = self.sock.recv(128).decode() # 等回复
# 根据不同结果分情况处理
if result == "OK":
# 接收文件列表
while True:
file = self.sock.recv(1024).decode()
if file == '##':
break
print(file)
else:
print("文件库为空")
# 退出
def do_exit(self):
self.sock.send(b"EXIT")
self.sock.close()
sys.exit("谢谢使用")
# 处理上传
def do_put(self, file):
# 测一下这个文件是否存在
try:
f = open(file, 'rb')
except:
print("文件不存在")
return
# 防止file带有文件路径,提取文件名
filename = file.split("/")[-1]
data = "STOR " + filename
self.sock.send(data.encode()) # 发请求
result = self.sock.recv(128).decode() # 等待回复
if result == 'OK':
# 上传文件 读--》发送
while True:
data = f.read(1024)
if not data:
break
self.sock.send(data)
sleep(0.1)
self.sock.send(b"##")
f.close()
else:
print("该文件已存在")
# 处理下载
def do_get(self, file):
data = "RETR " + file
self.sock.send(data.encode()) # 发请求
result = self.sock.recv(128).decode() # 等待回复
if result == 'OK':
# 接收文件
f = open(file, 'wb')
while True:
data = self.sock.recv(1024)
if data == b"##":
break
f.write(data)
f.close()
else:
print("该文件不存在")
# 启动函数
def main():
sock = socket()
sock.connect(ADDR)
# 实例化对象用于调用方法
ftp = FTPClient(sock)
while True:
print("""
============ 命令选项 =============
list
get file
put file
exit
==================================
""")
cmd = input("请输入命令:")
if cmd == "list":
ftp.do_list()
elif cmd == 'exit':
ftp.do_exit()
elif cmd[:3] == 'put':
file = cmd.split(' ')[-1]
ftp.do_put(file)
elif cmd[:3] == 'get':
file = cmd.split(' ')[-1]
ftp.do_get(file)
else:
print("请输入正确命令")
if __name__ == '__main__':
main()
"""
ftp文件管理服务端
多线程 tcp 并发
"""
from socket import *
from threading import Thread
import os
from time import sleep
# 服务器地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
# 文件库位置
FTP = "/home/tarena/FTP/"
# 实现具体的业务功能,客户端请求都在这里处理
class FTPServer(Thread):
def __init__(self, connfd):
self.connfd = connfd
super().__init__()
# 处理请求文件列表
def do_list(self):
# 判断文件库是否为空
files = os.listdir(FTP)
if not files:
self.connfd.send(b"FAIL") # 失败
else:
self.connfd.send(b"OK")
sleep(0.1)
# 一次发送所有文件名
data = "\n".join(files)
self.connfd.send(data.encode())
sleep(0.1)
self.connfd.send(b"##")
# 处理上传
def do_put(self, filename):
# 判断文件是否已存在
if os.path.exists(FTP + filename):
self.connfd.send(b"FAIL")
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 do_get(self, filename):
try:
f = open(FTP + filename, 'rb')
except:
# 文件不存在
self.connfd.send(b"FAIL")
return
else:
self.connfd.send(b"OK")
sleep(0.1)
# 发送文件
while True:
data = f.read(1024)
if not data:
break
self.connfd.send(data)
sleep(0.1)
self.connfd.send(b"##")
f.close()
# 线程启动方法
def run(self):
while True:
# 接收某一个各类请求
data = self.connfd.recv(1024).decode()
# print(data)
if not data or data == "EXIT":
break
elif data == "LIST":
self.do_list()
elif data[:4] == "STOR":
filename = data.split(' ')[-1]
self.do_put(filename)
elif data[:4] == "RETR":
filename = data.split(' ')[-1]
self.do_get(filename)
self.connfd.close()
# 函数中搭建并发结构
def main():
sock = socket() # tcp套接字
sock.bind(ADDR)
sock.listen(5)
print("Listen the port %d" % PORT)
while True:
# 循环接收客户端连接
try:
connfd, addr = sock.accept()
print("Connect from", addr)
except KeyboardInterrupt:
# 退出服务
sock.close()
break
# 使用自定义线程类为连接的客户端创建新线程
t = FTPServer(connfd)
t.setDaemon(True) # 客户端随服务端退出
t.start()
if __name__ == '__main__':
main()
什么是IO
在程序中存在读写数据操作行为的事件均是IO行为,比如终端输入输出 ,文件读写,数据库修改和网络消息收发等。
程序分类
IO分类:阻塞IO ,非阻塞IO,IO多路复用,异步IO等。
设置套接字为非阻塞IO
sockfd.setblocking(bool)
功能:设置套接字为非阻塞IO
参数:默认为True,表示套接字IO阻塞;设置为False则套接字IO变为非阻塞
"""
非阻塞IO实例
"""
from socket import *
s = socket()
s.bind(("0.0.0.0", 8888))
s.listen(5)
# 设置为非阻塞
s.setblocking(False)
while True:
try:
c, addr = s.accept()
print("Connect from ", addr)
except BlockingIOError as e:
print("干点别的")
else:
data = c.recv(1024)
print(data.decode())
超时检测 :设置一个最长阻塞时间,超过该时间后则不再阻塞等待。
sockfd.settimeout(sec)
功能:设置套接字的超时时间
参数:设置的时间
"""
非阻塞IO实例2
"""
from socket import *
from time import sleep,ctime
log = open("my.log",'a') # 打开一个日志
s = socket()
s.bind(("0.0.0.0",8888))
s.listen(5)
# 设置为非阻塞
# s.setblocking(False)
# 设置超时检测
s.settimeout(3)
while True:
try:
c,addr = s.accept()
print("Connect from ",addr)
except timeout as e:
print("发生超时")
msg = "%s : %s\n" % (ctime(), e)
log.write(msg)
log.flush()
except BlockingIOError as e:
print("非阻塞日志")
sleep(2)
msg = "%s : %s\n"%(ctime(),e)
log.write(msg)
log.flush()
else:
data = c.recv(1024)
print(data.decode())
定义
同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。
具体方案
select 方法
rs, ws, xs=select(rlist, wlist, xlist[, timeout])
功能: 监控IO事件,阻塞等待IO发生
参数:rlist 列表 读IO列表,添加等待发生的或者可读的IO事件
wlist 列表 写IO列表,存放要可以主动处理的或者可写的IO事件
xlist 列表 异常IO列表,存放出现异常要处理的IO事件
timeout 超时时间
返回值: rs 列表 rlist中准备就绪的IO
ws 列表 wlist中准备就绪的IO
xs 列表 xlist中准备就绪的IO
"""
select IO 多路复用方法
"""
from socket import *
from select import select
# 制作一些IO对象
file = open("my.log",'a+')
sock_tcp = socket()
sock_tcp.bind(("0.0.0.0",8888))
sock_tcp.listen(5)
sock_udp = socket(AF_INET,SOCK_DGRAM)
# 监控IO
print("开始监控IO")
rs,ws,xs = select([],[sock_udp,file],[])
print("rlist:",rs)
print("wlist:",ws)
print("xlist:",xs)
"""
基于select的 IO网络并发模型
IO 多路复用与非阻塞搭配
重点代码 !!!
"""
from socket import *
from select import select
# 网络地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST,PORT)
# 创建监听套接字
sockfd = socket()
sockfd.bind(ADDR)
sockfd.listen(5)
# 设置非阻塞
sockfd.setblocking(False)
# 初始只有监听套接字,先关注他
rlist = [sockfd] # 客户端连接
wlist = []
xlist = []
# 循环监控IO对象
while True:
rs,ws,xs = select(rlist,wlist,xlist)
# 处理就绪的IO
for r in rs:
# 有客户端连接
if r is sockfd:
connfd, addr = r.accept()
print("Connect from", addr)
# 将客户端连接套接字也监控起来
connfd.setblocking(False)
rlist.append(connfd)
else:
# 某个客户端发消息 connfd 就绪
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
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 IO 多路复用方法
"""
from socket import *
from select import *
# 制作一些IO对象
file = open("my.log", 'a+')
sock_tcp = socket()
sock_tcp.bind(("0.0.0.0", 8888))
sock_tcp.listen(5)
sock_udp = socket(AF_INET, SOCK_DGRAM)
# 查找字典 需要与register的IO保持一直
map = {
sock_tcp.fileno(): sock_tcp,
sock_udp.fileno(): sock_udp,
file.fileno(): file}
# 准备poll方法
p = poll() # 生成poll对象
p.register(sock_tcp, POLLIN | POLLERR)
p.register(sock_udp, POLLOUT)
p.register(file, POLLOUT)
print("开始监控IO")
events = p.poll()
# events --> [(fileno,event),()]
# 必须获取到IO对象才能调用方法处理IO
print(events)
"""
基于poll的 IO网络并发模型
IO 多路复用与非阻塞搭配
"""
from socket import *
from select import *
# 网络地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
# 创建监听套接字
sockfd = socket()
sockfd.bind(ADDR)
sockfd.listen(5)
# 设置非阻塞
sockfd.setblocking(False)
# 创建poll对象
p = poll()
# 查找字典 fileno--> io object
map = {
sockfd.fileno(): sockfd}
# 初始监听套接字,先关注他
p.register(sockfd, POLLIN)
# 循环监控IO对象
while True:
# events --> [(fileno,event),()]
events = p.poll()
# 遍历events 处理就绪的IO
for fd, event in events:
# 分类讨论
if fd == sockfd.fileno():
connfd, addr = map[fd].accept()
print("Connect from", addr)
# 将客户端连接套接字也监控起来
connfd.setblocking(False)
p.register(connfd, POLLIN | POLLERR)
map[connfd.fileno()] = connfd # 维护字典
elif event == POLLIN:
# 某个客户端发消息 connfd 就绪
data = map[fd].recv(1024).decode()
# 客户端退出处理
if not data:
p.unregister(fd) # 不再关注
map[fd].close()
del map[fd] # 从字典删除
continue
print(data)
# map[fd].send(b"OK")
p.register(fd, POLLOUT)
elif event == POLLOUT:
map[fd].send(b"OK")
p.register(fd, POLLIN)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWD9fnT7-1609849651600)(/home/tarena/month02/lz/day16/select_poll.png)]
epoll方法
使用方法 : 基本与poll相同
生成对象改为 epoll()
将所有事件类型改为EPOLL类型
"""
基于epoll的 IO网络并发模型
IO 多路复用与非阻塞搭配
重点代码 !!!
"""
from socket import *
from select import *
# 网络地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
# 创建监听套接字
sockfd = socket()
sockfd.bind(ADDR)
sockfd.listen(5)
# 设置非阻塞
sockfd.setblocking(False)
# 创建epoll对象
ep = epoll()
# 查找字典 fileno--> io object
map = {
sockfd.fileno(): sockfd}
# 初始监听套接字,先关注他
ep.register(sockfd, EPOLLIN)
# 循环监控IO对象
while True:
# events --> [(fileno,event),()]
events = ep.poll()
print("你有新的IO需要处理哦", events)
# 遍历events 处理就绪的IO
for fd, event in events:
# 分类讨论
if fd == sockfd.fileno():
connfd, addr = map[fd].accept()
print("Connect from", addr)
# 将客户端连接套接字也监控起来
connfd.setblocking(False)
ep.register(connfd, EPOLLIN | EPOLLET) # 设置边缘触发
map[connfd.fileno()] = connfd # 维护字典
elif event == EPOLLIN:
# 某个客户端发消息 connfd 就绪
data = map[fd].recv(1024).decode()
# 客户端退出处理
if not data:
ep.unregister(fd) # 不再关注
map[fd].close()
del map[fd] # 从字典删除
continue
print(data)
# map[fd].send(b"OK")
ep.unregister(fd)
ep.register(fd, EPOLLOUT)
elif event == EPOLLOUT:
map[fd].send(b"OK")
ep.unregister(fd)
ep.register(fd, EPOLLIN)
利用IO多路复用等技术,同时处理多个客户端IO请求。
优点 : 资源消耗少,能同时高效处理多个IO行为
缺点 : 只针对处理并发产生的IO事件
适用情况:HTTP请求,网络传输等都是IO行为,可以通过IO多路复用监控多个客户端的IO请求。
并发服务实现过程
【1】将关注的IO准备好,通常设置为非阻塞状态。
【2】通过IO多路复用方法提交,进行IO监控。
【3】阻塞等待,当监控的IO有发生时,结束阻塞。
【4】遍历返回值列表,确定就绪IO事件。
【5】处理发生的IO事件。
【6】继续循环监控IO发生。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9A6Uiy9J-1609849651601)(/home/tarena/month02/lz/Network/img/2_wzfw.png)]
GET / HTTP/1.1
请求类别 请求内容 协议版本
请求类别:每个请求类别表示要做不同的事情
GET : 获取网络资源
POST :提交一定的信息,得到反馈
HEAD : 只获取网络资源的响应头
PUT : 更新服务器资源
DELETE : 删除服务器资源
Accept-Encoding: gzip
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NisHFrrI-1609849651602)(./img/request.jpg)]
HTTP/1.1 200 OK
版本信息 响应码 附加信息
响应码 :
1xx 提示信息,表示请求被接收
2xx 响应成功
3xx 响应需要进一步操作,重定向
4xx 客户端错误
5xx 服务器错误
Content-Type: text/html
Content-Length:109\r\n
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wnyPaZjr-1609849651603)(/home/tarena/month02/lz/Network/img/response.png)]
"""
http 请求响应示例
"""
from socket import *
s = socket()
s.bind(("0.0.0.0",8888))
s.listen(5)
c,addr = s.accept() # 浏览器连接
print("Connect from",addr)
# 接收浏览器发送的HTTP请求
data = c.recv(1024 * 10)
print(data.decode())
# 发送http响应给浏览器
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += "Sorry...."
c.send(response.encode())
c.close()
s.close()
"""
练习: 编写一个程序完成,如果浏览器访问
127.0.0.1:8888/python的时候可以访问到
Python.html网页,否则则访问不到任何内容,得到404
响应
提示 : 提取请求内容 --》 分情况讨论
读取网页内容 作为响应体发送
"""
from socket import *
s = socket()
s.bind(("0.0.0.0", 8880))
s.listen(5)
c, addr = s.accept() # 浏览器连接
print("Connect from", addr)
# 接收浏览器发送的HTTP请求
data = c.recv(1024 * 10).decode()
tmp = data.split(" ")
# 判断请求内容
if tmp[1] == "/python":
with open("Python.html") as f:
content = f.read()
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += content
else:
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += "Sorry..."
# 发送响应
c.send(response.encode())
c.close()
s.close()
【1】 接收客户端(浏览器)请求
【2】 解析客户端发送的请求
【3】 根据请求组织数据内容
【4】 将数据内容形成http响应格式返回给浏览器
特点 :
【1】 采用IO并发,可以满足多个客户端同时发起请求情况
【2】 通过类接口形式进行功能封装
【3】 做基本的请求解析,根据具体请求返回具体内容,同时可以满足客户端的网页效果加载
"""
web server 程序
完成一个类,提供给使用者
可以通过这个类快速搭建服务
完成网页展示
"""
from socket import *
from select import select
import re
# 封装所有web后端功能
class WebServer:
def __init__(self, host="0.0.0.0", port=80, html=None):
self.host = host
self.port = port
self.html = html
self.rlist = []
self.wlist = []
self.xlist = []
self.create_socket()
self.bind()
# 创建设置套接字
def create_socket(self):
self.sock = socket()
self.sock.setblocking(False)
# 绑定地址
def bind(self):
self.address = (self.host, self.port)
self.sock.bind(self.address)
# 启动整个服务
def start(self):
self.sock.listen(5)
print("Listen the port %d" % self.port)
# 先监控监听套接字
self.rlist.append(self.sock)
# 循环监控IO对象
while True:
rs, ws, xs = select(self.rlist, self.wlist, self.xlist)
# 处理就绪的IO
for r in rs:
# 有客户端连接
if r is self.sock:
connfd, addr = r.accept()
print("Connect from", addr)
# 将客户端连接套接字也监控起来
connfd.setblocking(False)
self.rlist.append(connfd)
else:
# 处理浏览器端发的请求
try:
self.handle(r)
except:
pass
self.rlist.remove(r)
r.close()
# 处理客户端请求
def handle(self, connfd):
# http请求
request = connfd.recv(1024 * 10).decode()
# 使用正则匹配请求内容
pattern = r"[A-Z]+\s+(?P/\S*)"
result = re.match(pattern, request)
if request:
# 提取请求内容
info = result.group("info")
print("请求内容:", info)
self.send_html(connfd, info)
# 发送响应
def send_html(self, connfd, info):
# 对info分情况
if info == "/":
filename = self.html + "/index.html"
else:
filename = self.html + info
# 打开判断文件是否存在
try:
file = open(filename, "rb")
except:
# 请求的网页不存在
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
with open(self.html + "/404.html") as file:
response += file.read()
response = response.encode()
else:
# 请求的网页存在
data = file.read() # 字节串
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += "Content-Length:%d\r\n" % len(data)
response += "\r\n"
response = response.encode() + data
file.close()
finally:
connfd.send(response)
if __name__ == '__main__':
# 需要用户决定: 地址 网页
httpd = WebServer(host="0.0.0.0",
port=8000,
html="./static")
# 启动服务
httpd.start()
衡量高并发的关键指标
响应时间(Response Time) : 接收请求后处理的时间
吞吐量(Throughput): 响应时间+QPS+同时在线用户数量
每秒查询率QPS(Query Per Second): 每秒接收请求的次数
每秒事务处理量TPS(Transaction Per Second):每秒处理请求的次数(包含接收,处理,响应)
同时在线用户数量:同时连接服务器的用户的数量
多大的并发量算是高并发
没有最高,只要更高
比如在一个小公司可能QPS2000+就不错了,在一个需要频繁访问的门户网站可能要达到QPS5W+
C10K问题
早先服务器都是单纯基于进程/线程模型的,新到来一个TCP连接,就需要分配1个进程(或者线程)。而进程占用操作系统资源多,一台机器无法创建很多进程。如果是C10K就要创建1万个进程,那么单机而言操作系统是无法承受的,这就是著名的C10k问题。创建的进程线程多了,数据拷贝频繁, 进程/线程切换消耗大, 导致操作系统崩溃,这就是C10K问题的本质!
为了解决C10K问题,现在高并发的实现已经是一个更加综合的架构艺术。涉及到进程线程编程,IO处理,数据库处理,缓存,队列,负载均衡等等,这些我们在后面的阶段还会学习。此外还有硬件的设计,服务器集群的部署,服务器负载,网络流量的处理等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KkQ6rz9m-1609849651604)(./img/25.jpg)]
实际工作中,应对更庞大的任务场景,网络并发模型的使用有时也并不单一。比如多进程网络并发中每个进程再开辟线程,或者在每个进程中也可以使用多路复用的IO处理方法。
def __init__(self, host="0.0.0.0", port=80, html=None):
self.host = host
self.port = port
self.html = html
self.rlist = []
self.wlist = []
self.xlist = []
self.create_socket()
self.bind()
# 创建设置套接字
def create_socket(self):
self.sock = socket()
self.sock.setblocking(False)
# 绑定地址
def bind(self):
self.address = (self.host, self.port)
self.sock.bind(self.address)
# 启动整个服务
def start(self):
self.sock.listen(5)
print("Listen the port %d" % self.port)
# 先监控监听套接字
self.rlist.append(self.sock)
# 循环监控IO对象
while True:
rs, ws, xs = select(self.rlist, self.wlist, self.xlist)
# 处理就绪的IO
for r in rs:
# 有客户端连接
if r is self.sock:
connfd, addr = r.accept()
print("Connect from", addr)
# 将客户端连接套接字也监控起来
connfd.setblocking(False)
self.rlist.append(connfd)
else:
# 处理浏览器端发的请求
try:
self.handle(r)
except:
pass
self.rlist.remove(r)
r.close()
# 处理客户端请求
def handle(self, connfd):
# http请求
request = connfd.recv(1024 * 10).decode()
# 使用正则匹配请求内容
pattern = r"[A-Z]+\s+(?P/\S*)"
result = re.match(pattern, request)
if request:
# 提取请求内容
info = result.group("info")
print("请求内容:", info)
self.send_html(connfd, info)
# 发送响应
def send_html(self, connfd, info):
# 对info分情况
if info == "/":
filename = self.html + "/index.html"
else:
filename = self.html + info
# 打开判断文件是否存在
try:
file = open(filename, "rb")
except:
# 请求的网页不存在
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
with open(self.html + "/404.html") as file:
response += file.read()
response = response.encode()
else:
# 请求的网页存在
data = file.read() # 字节串
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += "Content-Length:%d\r\n" % len(data)
response += "\r\n"
response = response.encode() + data
file.close()
finally:
connfd.send(response)
if name == ‘main’:
# 需要用户决定: 地址 网页
httpd = WebServer(host=“0.0.0.0”,
port=8000,
html="./static")
# 启动服务
httpd.start()
## 并发技术探讨(扩展)
### 高并发问题
* 衡量高并发的关键指标
- 响应时间(Response Time) : 接收请求后处理的时间
- 吞吐量(Throughput): 响应时间+QPS+同时在线用户数量
- 每秒查询率QPS(Query Per Second): 每秒接收请求的次数
- 每秒事务处理量TPS(Transaction Per Second):每秒处理请求的次数(包含接收,处理,响应)
- 同时在线用户数量:同时连接服务器的用户的数量
* 多大的并发量算是高并发
* 没有最高,只要更高
比如在一个小公司可能QPS2000+就不错了,在一个需要频繁访问的门户网站可能要达到QPS5W+
* C10K问题
早先服务器都是单纯基于进程/线程模型的,新到来一个TCP连接,就需要分配1个进程(或者线程)。而进程占用操作系统资源多,一台机器无法创建很多进程。如果是C10K就要创建1万个进程,那么单机而言操作系统是无法承受的,这就是著名的C10k问题。创建的进程线程多了,数据拷贝频繁, 进程/线程切换消耗大, 导致操作系统崩溃,这就是C10K问题的本质!
### 更高并发的实现
为了解决C10K问题,现在高并发的实现已经是一个更加综合的架构艺术。涉及到进程线程编程,IO处理,数据库处理,缓存,队列,负载均衡等等,这些我们在后面的阶段还会学习。此外还有硬件的设计,服务器集群的部署,服务器负载,网络流量的处理等。
[外链图片转存中...(img-KkQ6rz9m-1609849651604)]
实际工作中,应对更庞大的任务场景,网络并发模型的使用有时也并不单一。比如多进程网络并发中每个进程再开辟线程,或者在每个进程中也可以使用多路复用的IO处理方法。