目录
- 多线程并发
- 集成模块的使用
- HTTPServer
- 协程基础
- gevent
1. 多线程并发
threading的多线程并发
对比多进程并发 :
1. 线程消耗资源较少
2. 线程应更注意共享资源的操作
3. 在python中应注意GIL问题,网络延迟较高,线程并发也是一种可行方法
实现步骤:
1. 创建套接字,绑定监听
2. 接受客户端请求,创建新的线程
3. 主线程继续接收,其他客户端连接
4. 分支线程启动对应的函数处理客户端请求
5. 当客户端断开,分支线程结束
traceback模块
traceback.print_exc()
功能 : 更详细的打印异常信息
和 try ... except 一起使用
eg:
import traceback
a = 10
try:
b = a / 0
except Exception as e:
print("Error: ",e)
print("Program Over")
多线程客户端
#thread_server.py
from socket import *
import os,sys
from threading import *
def handler(connfd):
print("Connect from :",connfd.getpeername())
while True:
data = connfd.recv(1024)
if not data :
break
print(data.decode())
connfd.send("Receive Request".encode())
connfd.close()
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST,PORT)
print("Lisen Port = ",PORT)
#创建套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
#等待客户端请求
while True:
try:
connfd,addr = s.accept()
except KeyboardInterrupt:
s.close()
sys.exit("server quit")
except Exception as e:
print("Error: ",e)
continue
t = Thread(target = handler,args=(connfd,))
t.setDaemon(True)
t.start()
2. 集成模块的使用
集成模块的使用 :
python2 SocketServer
python3 socketserver
功能 :通过模块的不同类的组合完成多进程/多线程 的 tcp/udp 的并发
>>>
import socketserver
dir(socketserver)
>>>
StreamRequestHandler 处理tcp套接字请求
DatagramRequestHandler 处理udp套接字请求
TCPServer 创建tcp server
UDPServer 创建udp server
ForkingMixIn 创建多进程
ForkingTCPServer --> ForkingMinIn + TCPServer
ForkingUDPServer --> ForkingMinIn + UDPServer
ThreadingMixIn 创建多线程
ThreadingTCPServer --> ThreadingMinIn + TCPServer
ThreadingUDPServer --> ThreadingMinIn + UDPServer
TCP多进程并发
#socketserver.py
#TCP多进程并发
from socketserver import *
#class Server(ForkingMixIn,TCPServer):
#class Server(ForkingTCPServer):
class Server(ThreadingMixIn,TCPServer):
pass
class Handler(StreamRequestHandler):
def handler(self):
#self.request <==> accept()返回的套接字
print("Connect from:",self.request.getpeername())
while True:
data = self.request.recv(1024)
if not data:
break
print(data.decode())
self.request.send(b"Receive")
if __name__ == "__main__":
server_addr = ("0.0.0.0",8888)
print("listen to the port 8888...")
#创建服务器对象
server = Server(server_addr,Handler)
#启动服务器
server.serve_forever()
UDP多线程并发
pass
class Handler(DatagramRequestHandler):
def handler(self):
while True:
data,addr = self.rfile.readline()
print(self.client_address)
if not data:
break
print(data.decode())
self.wfile.write(b"Receive")
if __name__ == "__main__":
server_addr = ("0.0.0.0",8888)
print("listen to the port 8888...")
#创建服务器对象
server = Server(server_addr,Handler)
#启动服务器
server.serve_forever()
3. HTTPServer
HTTPServer 2.0
1.接收客户端请求
2.解析客户端请求
3.组织数据,形成 HTTP response
4.将数据发送给客户端
升级 :
1.采用多线程并发接收多个客户端请求
2.基本的请求解析,根据请求返回相应的内容
3.除了可以请求静态网页,也可以请求简单的数据
4.将所有功能封装在一个类中
技术点 :
1. socket TCP 套接字
2.http协议的请求响应格式
3.线程并发的创建方法
4.类的基本使用
HTTP Server 2.0
#coding = utf-8
'''
HTTP Server Ver.2.0
update log
1.多线程并发
2.可以请求简单数据
3.能进行简单请求解析
4.结构使用类进行封装
'''
from socket import *
from threading import Thread
import sys
#httpserver类 封装具体的服务器功能
class HTTPServer(object):
def __init__(self,server_addr,static_dir):
self.server_addr = server_addr
self.static_dir = static_dir
self.ip = server_addr[0]
self.port = server_addr[1]
#创建套接字
self.create_socket()
def create_socket(self):
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
self.sockfd.bind(self.server_addr)
#设置监听等待客户端连接
def server_forever(self):
self.sockfd.listen(5)
print("Listen the port",self.port)
while True:
try:
connfd,addr = self.sockfd.accept()
except KeyboardInterrupt:
self.sockfd.close()
sys.exit("Server Quit")
except Exception as e:
print("Error: ",e)
continue
#创建新的线程处理请求
clientThread = Thread(target = self.handleRequest,args=(connfd,))
clientThread.setDaemon(True)
clientThread.start()
#具体的客户端请求函数
def handleRequest(self,connfd):
#接受客户端请求
request = connfd.recv(4096)
#解析请求内容
requestHeaders = request.splitlines()
print(connfd.getpeername(),":",requestHeaders[0])
#获取具体的请求内容
getRequset = str(requestHeaders[0]).split(" ")[1]
if getRequset == "/" or getRequset[-5:] == ".html":
self.get_html(connfd,getRequset)
else:
self.get_data(connfd,getRequset)
connfd.close()
def get_html(self,connfd,getRequset):
if getRequset == "/":
filename = self.static_dir+"/index.html"
else:
filename = self.static_dir+getRequset
try:
f = open(filename)
except Exception:
#没有找到网页,返回404
responseHeaders = "HTTP/1.1 404 NOT FOUND\r\n"
responseHeaders += "\r\n"
responseBody = "Sorry,not found the page"
else:
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += "\r\n"
responseBody = f.read()
finally:
response = responseHeaders + responseBody
connfd.send(response.encode())
def get_data(self,connfd,getRequset):
urls = ['/time','/edu','/python']
if getRequset in urls:
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += "\r\n"
if getRequset == '/time':
import time
responseBody = time.ctime()
elif getRequset == '/edu':
responseBody = "Let's study"
elif getRequset == '/python':
responseBody = "Life is short,I use Python"
else:
responseHeaders = "HTTP/1.1 404 NOT FOUNF\r\n"
responseHeaders += "\r\n"
responseBody = "Sorry,not found the data"
response = responseHeaders + responseBody
connfd.send(response.encode())
if __name__ == "__main__":
#服务器IP
server_addr = ("0.0.0.0",8000)
#我的静态页面存储目录
static_dir = "./static"
#生成对象
httpd = HTTPServer(server_addr,static_dir)
#启动服务器
httpd.server_forever()
4. 协程基础
协程 (高并发技术):
定义:协程的本质是一个单线程程序,所以协程不能使用计算机多核资源
又称纤程,微线程
作用:能够高效的完成并发任务,占用较少的资源
因此协程的并发量较高
原理:通过记录应用层的上下文栈区,实现在运行中进行上下文跳转
达到可以选择性地运行想要运行的部分
以此提高程序的运行效率
优点 : 消耗资源少 / 无需切换开销
无须同步互斥 / IO并发性好
缺点 : 无法利用计算机多核
yield ---> 协程实现的基本关键字
第三方库 :greenlet / gevent
greenlet:
greenlet.greenlet() 生成协程对象
gr.swith() 选择要执行的协程事件
from greenlet import greenlet
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
5. gevent
1. 将协程事件封装为函数
2. 生成协程对象
gevent.spawn(func,argv)
功能 : 生成协程对象
参数 : func 协程函数 / argv 给协程函数传参
返回值 : 协程对象
3.回收协程
gevent.joinall()
功能: 回收协程
参数: 列表:将要回收的协程对象放入列表
gevent.sleep(n)
功能:设置协程阻塞,让协程跳转
参数: n 阻塞时间
from gevent import monkey
monkey.patch_all()
功能 : 修改套接字的IO阻塞行为
*必须在socket导入之前使用
#gevent0.py
import gevent
from time import sleep
def foo(a,b):
print("a = %d, b = %d"%(a,b))
gevent.sleep(2)
print("Running foo again")
def bar():
print("Running int bar")
gevent.sleep(3)
print("Running bar again")
#生成协程
f = gevent.spawn(foo,1,2)
g = gevent.spawn(bar)
#sleep(3)
print("======")
gevent.joinall([f,g]) #特定阻塞
print("++++++")
======
a = 1, b = 2
Running int bar
Running foo again
Running bar again
++++++
实现套接字的协程并发
import gevent
from gevent import monkey
monkey.patch_all()
from socket import *
from time import ctime
def server(port):
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(("0.0.0.0",port))
s.listen(3)
while True:
c,addr = s.accept()
print("connect from",addr)
#循环服务器
#handler(c)
#协程服务器
gevent.spawn(handler,c)
def handler(c):
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(ctime().encode())
c.close()
if __name__ == "__main__":
server(8888)