前情回顾
线程
线程间的通信 ---》 全局变量
线程间同步互斥 ---》 Event Lock
Python线程的GIL ---》 Python中线程效率的低下
进程和线程 区别和联系, 使用情况
服务器:循环服务器
同一时刻中能有一个客户端占有服务器
并发服务器
IO并发
io多路复用 : 不希望IO长时间阻塞
多进程多线程并发
多进程 : 资源的消耗较多
多线程
多进程并发编程
fork tcp : 当有一个客户端连接则创建一个新的进程
为其服务,父进程继续等待接收新的客户
端连接请求
文件服务器
cookie
文件处理函数
os.listdir(PATH) : 获取一个目录内文件列表
os.path.isfile() :判断一个文件的类型是否为普通文件
os.path.isdir() :判断一个文件的类型是否为目录
threading 的多线程并发
对比多进程并发:
优点 : 资源消耗少
缺点 : 需要注意共享该资源的争夺
Python多线程收到GIL的影响
实现步骤:
- 创建套接字,绑定,监听
- 接收客户端连接请求,创建新的线程
- 主线程继续等待其他客户端连接,分支线程执行客户端请求
- 处理完客户端请求后,分支线程退出,关闭客户端套接字
#01_thread_server.py
from socket import *
from threading import Thread
import sys
HOST = ‘0.0.0.0’
PORT = 8888
ADDR = (HOST,PORT)
def client_handle©:
print(“Connect from”,c.getpeername())
while True:
data = c.recv(1024).decode()
if not data:
break
print(data)
c.send(b"Receive your message")
c.close()
#创建tcp套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen()
while True:
try:
c,addr = s.accept()
except KeyboardInterrupt:
s.close()
sys.exit(“服务器退出”)
except Exception as e:
print(e)
continue
#创建线程,绑定函数执行具体客户端请求
t = Thread(target = client_handle,args = (c,))
t.setDaemon(True)
t.start()
socket服务器集成模块
python2 SocketServer
python3 socketserver
功能 : 通过模块的接口完成基于多进程/多线程的
tcp/udp 的socket并发程序
模块类
DatagramRequestHandler 处理udp请求
StreamRequestHandler 处理tcp请求
UDPServer 提供udp服务端类
TCPServer 提供tcp服务端类
ForkingMixIn 提供进程创建
ForkingTCPServer ==》ForkingMixIn + TCPServer
ForkingUDPServer ==》ForkingMixIn + UDPServer
ThreadingMixIn 提供线程创建
ThreadingTCPServer ==》ThreadingMixIn + TCPServer
ThreadingUDPServer ==》ThreadingMixIn + TCPServer
02_sock_server.py
from socketserver import *
创建多进程 tcp 并发
创建进程tcp服务器类
class Server(ForkingTCPServer):
class Server(ForkingMixIn,TCPServer):
# pass
#多线程tcp并发
class Server(ThreadingTCPServer):
pass
#具体请求处理类
class Handler(StreamRequestHandler):
def handle(self):
#self.request 相当于accept创建的套接字
print(“Connect from”,self.request.getpeername())
while True:
data = self.request.recv(1024).decode()
if not data:
break
print(data)
self.request.send(b"Receive your message")
#生产服务器对象,传入addr和具体处理类
server = Server((“0.0.0.0”,8888),Handler)
#启动服务器
server.serve_forever()
03_sock_server1.py
from socketserver import *
#多进程 udp并发
class Server(ForkingMixIn,UDPServer):
pass
class Handler(DatagramRequestHandler):
def handle(self):
while True:
#接收消息
data = self.rfile.readline().decode()
if not data:
break
print(data)
#发送消息
self.wfile.write
(b"Receive your message")
server = Server((‘127.0.0.1’,8888),Handler)
server.serve_forever()
基于多线程并发的 HTTPServer
HTTPServer的作用
1.就收浏览器发送的http请求
2.对http请求进行解析
3.组织响应内容进行回发
升级
- 使用多线程的并发可以同时处理多个客户端请求
- 增加简单的应用程序,使浏览器不仅可以访问静态网页
也可以访问后台程序 - 使用类对服务器功能模块进行封装
技术点 : threading 并发
tcp socket 套接字
http协议响应内容进行字符串拼接
#04_HttpServer.py
‘’’
http server 第二版
‘’’
from socket import *
from threading import Thread
import time
ADDR = (‘0.0.0.0’,8000)
#存放静态页面的目录
STATIC_DIR = “./static”
#httpserver类 封装服务器功能
class HTTPServer(object):
def init(self,addr):
#套接字创建
self.sockfd = socket()
self.sockfd.setsockopt
(SOL_SOCKET,SO_REUSEADDR,1)
self.sockfd.bind(addr)
self.sockfd.listen(5)
#为对象添加一些属性
self.name = “HttpServer”
self.port = 8000
self.address = addr
# 监听客户端的链接请求,创建新的线程处理
def serve_forever(self):
print("Listen to port 8000.....")
while True:
connfd,addr = self.sockfd.accept()
#创建新的线程处理具体请求
clientThread = \
Thread(target = self.handleRequest,\
args = (connfd,))
clientThread.setDaemon(True)
clientThread.start()
def handleRequest(self,connfd):
#接收客户端请求
request = connfd.recv(4096)
requestHeadlers = request.splitlines()
#请求行
print(connfd.getpeername(),":",\
requestHeadlers[0])
#获取具体请求
getRequest = \
str(requestHeadlers[0]).split(' ')[1]
#访问静态网页
if getRequest[-3:] != ".py":
if getRequest == '/':
getFilename = STATIC_DIR + "/index.html"
else:
getFilename = STATIC_DIR + getRequest
try:
f = open(getFilename)
except Exception:
#没有找到页面
responseHeaders = "HTTP/1.1 404 not found\r\n"
responseHeaders += "\r\n"
responseBody = "===Sorry,the page not found==="
else:
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += "\r\n"
responseBody = f.read()
finally:
response = responseHeaders + responseBody
connfd.send(response.encode())
#访问后台程序
else:
#通过函数得到响应体
responseBody = self.application()
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += "\r\n"
response = responseHeaders + responseBody
connfd.send(response.encode())
connfd.close()
#获取去你要使用的外部函数,变为属性
def setApp(self,application):
self.application = application
#后台程序
def app():
return “\n=假设这是一个很复杂的程序,你得到了一个很牛逼的内容=\n%s”%time.ctime()
if name == “main”:
httpd = HTTPServer(ADDR)
httpd.setApp(app)
#启动服务器
httpd.serve_forever()
协程
定义: 纤程 微线程 ,协程本质只有一个线程在运行
功能原理 : 通过应用层程序,记录上下文栈区,实现在程序执行过程中
的跳跃,选择可以不阻塞的部分执行,从而提高IO的执行效率
优点 : 资源消耗很少
无需多线程那样来回切换的开销
无需进行同步互斥操作
IO并发性好
缺点 : 无法利用计算机的多核资源
程序不能够一个客户端单独长时间占有服务端
yield–》 协程的基本实现关键字
sudo pip3 install greenlet
sudo pip3 install gevent
greenlet
greenlet.greenlet()
gr.switch()
05_greenlet1.py
def test1():
print(12)
gr2.switch()
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch()
print(78)
#协程对象
gr1 = greenlet(test1)
gr2 = greenlet(test2)
#调用协程函数
gr1.switch()
12
56
34
78
gevent
- 将协程事件封装为函数
gevent.spawn(func,argv)
功能 : 将事件变为协程事件并启动
参数 : func 传入一个函数变为协程
argv 给func函数传参
返回值 : 协程对象
gevent.joinall()
功能:回收协程
gevent.sleep(n)
功能 ; 模拟IO阻塞的情况
from gevent import monkey
monkey.patch_all()
功能 : 在导入socket模块之前使用,修改socket的IO设置行为
06_gevent_test.py
import gevent
def foo():
print(“Running in foo”)
gevent.sleep(2)
print(“switch to foo again”)
def bar():
print(“Running in bar”)
gevent.sleep(3)
print(“switch to bar again”)
#将两个函数设置为协成,此时协成函数运行
f = gevent.spawn(foo)
b = gevent.spawn(bar)
#回收协成
gevent.joinall([f,b])
Running in foo
Running in bar
switch to foo again
switch to bar again
#07_gevent_server.py
import gevent
from gevent import monkey
#需要在socket导入前执行,改变socket的属性
monkey.patch_all()
from socket import *
#套接字创建
def server(port):
s = socket()
s.bind((‘0.0.0.0’,port))
s.listen(5)
while True:
c,addr = s.accept()
print(‘Connect from’,addr)
gevent.spawn(handle,c)
#处理客户端请求
def handle©:
while True:
data = c.recv(1024).decode()
if not data:
break
print(“recv:”,data)
c.send(b"Receive your message")
c.close()
if name == “main”:
server(8888)