python网络编程之并发服务器丶HTTP协议
该文档仅用作学习笔记,转载请表明出处
单进程服务器
- 完成一个简单的TCP服务器
案例v11:
'''
完成一个简单的TCP服务器
单进程服务器
'''
from socket import *
serSocket = socket(AF_INET,SOCK_STREAM)
#重复使用绑定的信息
serSocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
localAddr = ('',7788)
serSocket.bind(localAddr)
serSocket.listen(5)
while True:
print('--------主进程:等待客户端到来--------')
newSocket,destAddr = serSocket.accept()
print('--------主进程:负责数据的处理--------')
try:
while True:
recvData = newSocket.recv(1024)
if len(recvData) > 0 :
print('recv[{}]:{}'.format(str(destAddr),recvData))
else:
print('[{}]客户端已经关闭'.format(str(destAddr)))
break
finally:
newSocket.close()
serSocket.close()
这种服务器:
- 同一时刻只能为一个客户进行服务,布恩那个同时为多个客户服务。
- 类似于找一个明星签字一样,客户需要耐心等待才可以获取到服务当服务器为一个客户端服务时,而另外的客户端发起了connect,只要服务器listen的队列有空闲的位置,就会为这个新客户端进行连接,并且客户端可以发送数据,但当服务器为这个新客户端服务时,可能一次性把所有数据接收完毕。
- 当recv接收数据时,返回值为空,即没有返回数据,那么意味着客户端已经调用了close关闭了;因此服务器通过判断recv接收数据是否为空来判断客户端是否已经下线。
多进程服务器
'''
多进程服务器
'''
from socket import *
from multiprocessing import *
from time import sleep
#处理客户端的请求并为其服务
def dealWithClient(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData) > 0:
print('recv[{}]:{}'.format(str(destAddr), recvData))
else:
print('[{}]客户端已经关闭'.format(str(destAddr)))
break
newSocket.close()
#主函数
def main():
# 创建服务器套接字 serSocket
serSocket = socket(AF_INET, SOCK_STREAM)
# 重复使用绑定的信息
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)
try:
while True:
print('--------主进程:等待客户端到来--------')
newSocket, destAddr = serSocket.accept()
print('--------进程x:负责数据的处理--------')
client = Process(target=dealWithClient,args=(newSocket,destAddr))
client.start()
newSocket.close()
finally:
serSocket.close()
if __name__ == '__main__':
main()
多线程服务器
'''
多线程服务器
'''
from socket import *
from threading import Thread
from time import sleep
#处理客户端的请求并为其服务
def dealWithClient(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData) > 0:
print('recv[{}]:{}'.format(str(destAddr), recvData))
else:
print('[{}]客户端已经关闭'.format(str(destAddr)))
break
newSocket.close()
def main():
serSocket = socket(AF_INET, SOCK_STREAM)
# 重复使用绑定的信息
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)
try:
while True:
print('--------主进程:等待客户端到来--------')
newSocket, destAddr = serSocket.accept()
print('--------线程x:负责数据的处理--------')
client = Thread(target=dealWithClient,args=(newSocket,destAddr))
client.start()
finally:
serSocket.close()
if __name__ == '__main__':
main()
单进程-非堵塞服务器
- 多进程多线程的原因:如果没有多线程多进程,那么任务是单任务的,即在为一个顾客服务的时候,不能同时为其他顾客服务。
'''
创建一个单进程非堵塞的服务器
'''
from socket import *
serSocket = socket(AF_INET,SOCK_STREAM)
localAddr = ('',7790)
serSocket.bind(localAddr)
#让socket变成非堵塞
serSocket.setblocking(False)
#设置成被动套接字
serSocket.listen(5)
#用来保存所有已经链接的客户端信息
clientAddrList = []
while True:
#等待一个新的客户端到来(即完成3次握手的客户端)
try:
clientSocket, clientAddr = serSocket.accept()
except:
pass
else:
print("一个新的客户端到来:{}".format(str(clientAddr)))
clientSocket.setblocking(False)
clientAddrList.append(( clientSocket, clientAddr))
#处理数据
for clientSocket,clientAddr in clientAddrList:
try:
recvData = clientSocket.recv(1024)
except:
pass
else:
#数据处理 如果接受到的数据的长度大于0则输出,如果小于0,断开连接
if len(recvData) > 0:
print("[{}]:{}".format(str(clientAddr),recvData))
else:
clientSocket.close()
clientAddrList.remove((clientSocket,clientAddr))
print("[{}]已经下线".format(str(clientAddr)))
select版 - TCP单进程服务器
- select原理
- selet可以完成对一些套接字的检测。把套接字列表中可以接受信息的套接字返回。
- selet的效率高于单进程-非堵塞服务器。
- selct回显服务器
'''
select回显服务器
'''
import socket
import select
import sys
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('',7789))
server.listen(5)
inputs = [server,]
running = True
while True:
#调用select函数,阻塞等待
# readable可以接受的套接字列表
# writeable可以发送的套接字列表
# readable可以接受的套接字列表
readable,writeable,exceptinal = select.select(inputs,[],[]) #3个参数都是列表
#第一个是检测列表中的套接字可不可以收数据
#第二个是检测列表中的套接字可不可以发数据
#第三个是检测列表中的套接字是否产生一些异常
#数据抵达 循环
for sock in readable:
#监听到有新的连接
if sock == server:
conn,addr = server.accept()
#select监听的socket
inputs.append(conn)
#监听到键盘有输入
#elif sock == sys.stdin:
# cmd = sys.stdin.readline()
# running = False
# break
#有数据到达
else:
#读取客服端连接发送的数据
data =sock.recv(1024)
if data:
sock.send(data)
else:
# 移除select监听的socket
inputs.remove(sock)
sock.close()
if not running:
break
server.close()
- select目前可以在几乎所有的平台。
- 缺点在于:
- 单个进程能够监视的文件描述符的数量存在最大限制。
- 对socket进行扫描的时是依次扫描的,即采用轮询的方式,效率比较低。
- 套接字多事,select()都要通过遍历FD_SETSIZE 这个Socket来完成调度,不管哪个Socket是活跃的,都要遍历一遍。会浪费很多cpu时间。
epoll版 - TCP单进程服务器
- select:最多1024,轮询方式
- poll:解决了套接字有上限的问题,轮询方式
- epoll:没有1024的限制,事件通知机制(非轮询)
'''
epoll服务器
'''
import socket
import select
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(('',7788))
s.listen(5)
epoll = select.epoll
epoll.register(s.fileno(),select.EPOLLIN|select.EPOLLET)
connecyions = {}
addresses = {}
while True:
epoll_list = epoll.poll()
for fd,exents in epoll_list:
if fd == s.fileno():
conn,addr = s.accept()
print("有新的客服端来到{}".format(str(addr)))
connecyions[comm.fileno()] = coon
addresses[comm.fileno()] = addr
epoll.register(conn.fileno(),select.EPOLLIN|select.EPOLLET)
elif events == select.EPOLLIN:
recvData = commections[fd].recv(1024)
if recvData:
print("recv:{}".format(recvData))
else:
epoll.unregister(fd)
connecyions[fd].close()
print("[{}]客户端断开连接".format(addresses[fd]))
协程
- 协程,又称微线程,纤程。
- 协程是一个执行单元,它自带cpu上下文,这样只要在合适的时机,我们可以把一个协程切换到另一个协程,只要这个过程中保存或恢复CPU上下文那么程序还是可以运行的。
- 进程 -----》线程------》协程
- 计算密集型:需要占用大量的cpu资源 ----》使用多进程
- IO密集型:需要网络功能,大量时间都在等待网络数据的到来 ----》使用多线程或者协程