前情回顾
信号 :在进程间通过信号传递讯息
信号处理函数:
os.kill(pid,sig) 发送信号
signal.alarm(sec) 向自己发送时钟
signal.pause() 阻塞等待
signal.signal(sig,handler) 处理信号
handler(sig,frame)
信号量 semaphore
acquire() 删除信号量
release() 添加信号量
get_value() 获取信号量值
同步互斥机制 : 解决对共享资源的争夺
event 事件
e.wait() e.set() e.clear()
lock 锁
acquire() 上锁
release() 解锁
threading 创建线程
Thread() 线程创建类
t.start()
t.join()
线程间通信
通信方法 : 多个线程共用进程空间,所以进程的全局
量对进程内的线程均可见。因此线程间没
有特有的通信方式往往使用全局变量通信
注意事项 :线程间使用全局变量通信往往需要同步
互斥机制做为通信的安全保证
#01_thread0.py
from threading import Thread
from time import sleep
#全局变量
l = []
def th1():
global l
l.append(100)
def th2():
sleep(1)
print(l)
t1 = Thread(target = th1)
t2 = Thread(target = th2)
t1.start()
t2.start()
sleep(0.5)
l = [10000]
t1.join()
t2.join()
#[10000]
线程的同步互斥
线程event对象
创建对象 :
e = threading.Event()
e.wait([timeout]) 如果e为设置状态则不会阻塞,未 设置则阻塞
e.set() 将e变为设置状态
e.clear() 将e的设置去除
#02_thread_event.py
import threading
from time import sleep
msg = None
#创建事件对象
e = threading.Event()
def bar():
print(“呼叫foo”)
global msg
msg = “天王盖地虎”
def foo():
print(“等待口令”)
sleep(2)
if msg == “天王盖地虎”:
print(“宝塔镇河妖,自己人,哈哈哈”)
else:
print(“口令错误,打死他”)
e.set()
def fun():
print(“呵呵…内奸出现”)
sleep(1)
e.wait()
global msg
msg = “小鸡炖蘑菇”
t1 = threading.Thread(target = bar)
t2 = threading.Thread(target = foo)
t3 = threading.Thread(target = fun)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
呼叫foo
等待口令
呵呵…内奸出现
宝塔镇河妖,自己人,哈哈哈
线程锁
lock = threading.Lock() 创建锁对象
lock.acquire() 上锁
lock.release() 解锁
* 在一个进程中对一个线程锁重复上锁则会阻塞
#03_thread_lock.py
import threading
a = b = 0
lock = threading.Lock()
def value():
while True :
lock.acquire()
if a != b:
print(“a = %d,b = %d”%(a,b))
lock.release()
t = threading.Thread(target = value)
t.start()
while True:
lock.acquire()
a += 1
b += 1
lock.release()
t.join()
Python线程的GIL问题 (全局解释器锁)
python —》 支持多线程 --》同步互斥 --》加锁 —》超级锁,
给解释器加锁
后果 : 一个解释器,同一时刻只能解释一个线程。因此
大大降低了Python线程的执行效率
Python 的GIL问题 解决方法
- 尽量使用进程方式并行
- 不适用c作为解释器 c# java
- Python线程适用于高延迟的IO操作,网络操作。不适合
用cpu密集型程序。线程在遇到IO阻塞时会让出解释器和cpu
效率测试
分别测试 在 IO密集型程序和CPU密集型程序下,多进程,多线程,
单进程执行效率
#test.py
#计算密集
def count(x,y):
c = 0
while c < 6000000:
c += 1
x += 1
y += 1
#IO密集
def write():
f = open(‘test.txt’,‘w’)
for x in range(1000000):
f.write(“比利时加油\n”)
f.close()
def read():
f = open(‘test.txt’)
lines = f.readlines()
f.close()
Line cpu: 8.061699390411377
Line IO: 5.261598348617554
Thread cpu: 8.920610427856445
Thread io: 5.60230565071106
process cpu: 4.07219386100769
Process io: 3.383375406265259
进程和线程的区别和联系
1.两者都是多任务编程的方式,都能够使用计算机多核
2.进程的创建和删除要比线程消耗更多计算机资源
3.进程空间独立,数据安全性好,有专门的进程间通信方法
4.线程使用全局变量通信,更加简单,但是往往要与同步互斥机制公用
5.一个进程可以包含多个线程,线程共享进程的资源空间
6.进线程都有自己特有的资源,如命令,属性 id等
使用场景 :
- 需要创建较多的并发,任务比较简单时,线程比较合适
- 如果程序数据资源比较复杂,特别是共享资源较多时,
需要考虑到线程锁的复杂性 - 如果多个任务无必要的关联性,不易将其强行融入到一个进程中
- Python线程不适合cpu密集型程序
总结 :
- 进程线程的区别和关系
- 进程间以什么方式通信,有什么特点
- 同步互斥的意义,有什么方法实现同步互斥
- 僵尸进程,进程状态,线程GIL的概念
- 给一个情景,问选择进程还是线程,怎么做为什么
服务器模型
硬件服务器 : 主机 集群
厂商 : IBM HP 联想 浪潮
软件服务器 : 编写的服务端程序,依托于硬件服务器
运行,提供给用户一定的软件服务
分类 :webserver ----》 网络后端程序提供网络请 求的后端处理和数据交互
httpserver ----》 处理HTTP请求,回复http响 应
邮箱服务器 -----》 提供邮箱服务
文件服务器 -----》 提供文件的上传下载
功能: 网络连接,逻辑处理,数据的交互,数据的传输
协议的实现
模型结构 : c/s (客户端服务器模型)
b/s (浏览器服务器模型)
服务器目标:处理速度快,数据更安全,并发量大
硬件 : 更高的配置,集成分布的技术,更好的网络速
度,更多的主机,网络安全
软件 : 程序占有更少的计算机资源,更稳定的运行效
果,更流畅的运行速度,采用更合理的技术。
处理更高的并发
服务器模型
循环服务器 :单进程程序,循环的接收客户端的请求,
处理请求,每处理完一个请求后再去接收
处理下一个请求
优点 : 实现简单,占用系统资源少
缺点 : 无法同时连接多个客户端,当一个客户端长
期占有服务器时会形成其他客户端无法访问
的情况
使用情况:任务比较短暂,udp套接字更适合循环
并发服务器 : 同时能够处理多个客户端的任务请求。
并发可分为IO并发或者多进程多线程并发。
IO并发 : IO多路复用 协程
优点:资源消耗少,适用于IO类型服务器
缺点:不能监控CPU密集的情况,单线程,不能
长期阻塞的消息的收发
进程/线程并发:为每个客户端单独提供一个进程线 程,处理客户端请求
优点: 客户端可以长期占用服务器
缺点: 消耗系统资源较多
多进程并发模型
使用fork完成并发
1.创建套接字 绑定 监听
2.等待接收客户端请求 accept
3.创建子进程处理客户端请求,
父进程继续准备接收其他客户端连接
4.客户端退出则子进程结束
#04_fork_server.py
#fork tcp 并发
from socket import *
import os,sys
import signal
#创建服务器地址
HOST = ‘172.60.50.50’
PORT = 8888
ADDR = (HOST,PORT)
#处理客户端请求函数
def client_handle©:
print(“子进程处理客户端:”,c.getpeername())
while True:
data = c.recv(1024).decode()
if not data:
break
print(data)
c.send(b’Receive your message’)
#创建tcp监听套接字
s = socket()
#设置端口重用
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#绑定IP和端口号
s.bind(ADDR)
#设置监听
s.listen(5)
#循环:父进程继续准备接收其他客户端连接
#处理僵尸进程
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
#打印端口监听情况
print(“Listen to 8888…”)
#循环接收客户端连接
while True:
try:
c,addr = s.accept()
#异常:用户中断执行(通常是输入ctrl+C)
except KeyboardInterrupt:
s.close()
sys.exit(“服务器退出”)
#其它异常,打印一下异常信息
except Exception as e:
print(e)
continue
#创建子进程处理客户端请求
#有客户端连接,创建子进程
pid = os.fork()
if pid < 0:
print("创建子进程失败")
elif pid == 0:
s.close() #子进程不需要监听套接字
#print("处理客户端请求") #转换成下列函数处理
#调用函数处理客户端请求
client_handle(c)
#子进程处理完客户端请求一定要退出
c.close()
sys.exit(0)
else:
c.close() #父进程不需要和客户端交互
continue
#tcp_client.py
from socket import *
#创建套接字
sockfd = socket(AF_INET,SOCK_STREAM)
#发起连接 地址:服务端地址
sockfd.connect((‘172.60.50.50’,8888))
while True:
data = input(‘发送>>’)
if not data:
break
#将内容变为bytes格式发送
sockfd.send(data.encode())
data = sockfd.recv(1024).decode()
print(“收到消息:”,data)
sockfd.close()
tftp文件服务器
项目功能 :
- 客户端有简单的页面命令提示
- 功能包含 1. 查看服务端文件库文件列表
2. 下载其中的某个文件到本地
3. 将本地文件上传到服务器文件库中
4. 退出 - 服务端需要:1.处理客户端的各种请求
2.允许多个客户端同时进行操作
技术分析 :tcp套接字
并发 —》 多进程
对文件的读写发送操作
查看文件列表时需要考虑到粘包问题
整体结构 :功能封装在类中(上传,下载,查看列表)
流程控制使用main()
创建套接字,创建连接创建进程,接收请求
作业 : 完成 get 和 put功能
#tftp_server.py
‘’’
tftp 文件服务器程序
‘’’
from socket import *
import os
import signal
import sys
import time
#文件库路径
FILE_PATH = “/home/tarena/”
#实现服务器功能模块
class TftpServer(object):
def init(self,connfd):
self.connfd = connfd
def do_list(self):
#获取列表
file_list = os.listdir(FILE_PATH)
if not file_list:
self.connfd.send(‘文件库为空’.encode())
return
else:
self.connfd.send(b’OK’)
time.sleep(0.1)
files = ""
for file in file_list:
if file[0] != '.' and \
os.path.isfile(FILE_PATH + file):
files = files + file + '#'
self.connfd.send(files.encode())
def do_get(self,filename):
try:
fd = open(FILE_PATH + filename,'rb')
except:
self.connfd.send("文件不存在".encode())
return
self.connfd.send(b"OK")
time.sleep(0.1)
#发送文件
try:
for line in fd:
self.connfd.send(line)
fd.close()
except Exception as e:
print(e)
time.sleep(0.1)
self.connfd.send(b'##')
print("文件发送完毕")
def do_put(self,filename):
try:
fd = open(FILE_PATH + filename,'w')
except:
self.connfd.send("无法完成上传")
self.connfd.send(b'OK')
while True:
data = self.connfd.recv(1024).decode()
if data == "##":
break
fd.write(data)
fd.close()
print("上传完毕")
#流程控制,创建套接字连接,接收请求
def main():
#创建服务器地址
HOST = ‘0.0.0.0’
PORT = 8888
ADDR = (HOST,PORT)
#创建套接字
sockfd = socket()
#设置端口可重用
sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#绑定IP和端口号
sockfd.bind(ADDR)
#设置监听
sockfd.listen(5)
#循环:父进程继续准备接收其他客户端连接
#处理僵尸进程
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
#打印端口监听情况
print("Listen to port 8888....")
#循环接收客户端连接
while True:
try:
connfd,addr = sockfd.accept()
except KeyboardInterrupt:
sockfd.close()
sys.exit("服务器退出")
except Exception as e:
print(e)
continue
#打印客户端地址
print("客户端登录:",addr)
#创建子进程处理客户端请求
#有客户端连接,创建子进程
pid = os.fork()
if pid < 0:
print("创建子进程失败")
continue
elif pid == 0:
sockfd.close()
tftp = TftpServer(connfd)
#接收客户端请求
while True:
data = connfd.recv(1024).decode()
if not data:
continue
elif data[0] == 'L':
tftp.do_list()
#data ==> G filename
elif data[0] == 'G':
filename = data.split(' ')[-1]
tftp.do_get(filename)
elif data[0] == 'P':
filename = data.split(' ')[-1]
tftp.do_put(filename)
elif data[0] == 'Q':
print("客户端退出")
sys.exit(0)
else:
connfd.close()
continue
if name == “main”:
main()
#tftp_client.py
from socket import *
import sys
import time
#实现基本的请求功能
class TftpServer(object):
def init(self,sockfd):
self.sockfd = sockfd
def do_list(self):
self.sockfd.send(b"L") #发送请求类型
#等待接收服务器端确认
data = self.sockfd.recv(1024).decode()
if data == 'OK':
data = self.sockfd.recv(4096).decode()
files = data.split('#')
for file in files:
print(file)
print("%%%%%文件列表展示完毕%%%%%\n")
else:
#失败的原因由服务器发送过来
print(data)
def do_get(self,filename):
self.sockfd.send(('G '+filename).encode())
data = self.sockfd.recv(1024).decode()
if data == 'OK':
fd = open(filename,'w')
while True:
data = self.sockfd.recv(1024).decode()
if data == "##":
break
fd.write(data)
fd.close()
print("%s 下载完成\n"%filename)
else:
print(data)
def do_put(self,filename):
try:
fd = open(filename,'rb')
except:
print("上传的文件不存在")
return
self.sockfd.send(("P " + filename).encode())
data = self.sockfd.recv(1024).decode()
if data == 'OK':
for line in fd:
self.sockfd.send(line)
fd.close()
time.sleep(0.1)
self.sockfd.send(b'##')
print("%s 文件上传完毕"%filename)
else:
print(data)
def do_quit(self):
self.sockfd.send(b'Q')
#套接字连接
def main():
# 测一下列表的长度,看IP和端口是否忘输入
if len(sys.argv) < 3:
print(“argv is error”)
return
#创建服务器地址
HOST = sys.argv[1]
PORT = int(sys.argv[2])
#要访问的服务端地址
ADDR = (HOST,PORT)
#创建套接字
sockfd = socket()
#连接
sockfd.connect(ADDR)
tftp = TftpServer(sockfd) #tftp对象调用请求方法
while True:
print("=======命令选项========")
print("******* list *********")
print("*******get file ******")
print("*******put file ******")
print("******* quit *********")
print("======================")
cmd = input("请输入命令>>")
if cmd.strip() == 'list':
tftp.do_list()
elif cmd[:3] == "get":
filename = cmd.split(' ')[-1]
tftp.do_get(filename)
elif cmd[:3] == "put":
filename = cmd.split(' ')[-1]
tftp.do_put(filename)
elif cmd.strip() == "quit":
tftp.do_quit()
sockfd.close()
sys.exit("欢迎使用")
else:
print("请输入正确的命令!!!")
continue
if name == “main”:
main()