回顾原生socket模块,一次只能处理一个客户端请求,当这个客户端请求全部完成断开连接了,再去处理另外一个客户端得请求.这样服务端得资源其实还有很多是处于空闲状态的.然后我们又通过IO多路复用的方式解决了因为网络IO繁忙而造成浪费服务端资源得问题,通过select侦听网路IO,并以读写分离得形式
实现了接受处理多个请求.
然而这样并不完善,如果同时来多个请求,还是会挂起堵塞.无法并发得处理客户端得请求.还好python给我们提供了一个并发处理socket得功能,那就是socketserver模块.
socketserver内部使用 IO多路复用 以及
多线程
和多进程
,从而实现并发处理多个客户端请求的socket服务端。即:每个客户端请求连接到服务器时socket服务端都会在服务器是创建一个线程
或者进程
专门负责处理当前客户端的所有请求。
如下图:
第一次连接后,数据通讯就通过线程或进程进行数据交换(红色箭头)
ThreadingTCPServer
ThreadingTCPServer
实现的soket服务器内部会为每个client创建一个线程
,该线程用来和客户端进行交互。
ThreadingTCPServer基础
使用ThreadingTCPServer:
- 创建一个继承自 socketserver.BaseRequestHandler 的类
- 类中必须定义一个名称为 handle 的方法
- 启动ThreadingTCPServer
服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
conn = self.request # 接受连接请求,获取客户端socket对象
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.') # 回一条消息给客户端
while True:
data = str(conn.recv(1024), encoding='utf-8')
# 如果客户端没有输入内容则跳出本次循环什么都不执行
if data == len(data):
break
# 根据用户输入的内容执行相应的操作
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
if __name__ == '__main__':
# 实例话对象,传递一个元祖设置启动的IP/PORT,第二个参数把自己定义的类写上作为SocketServer.ThreadingTCPServer的构造函数
server = socketserver.ThreadingTCPServer(('127.0.0.1',8000), MyServer)
# 对象调用父类的启动方法
server.serve_forever()
客户端
import socket
ip_port = ('127.0.0.1',8000)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
while True:
data = sk.recv(1024)
print('receive: %s',data)
inp = input('please input:')
sk.sendall(bytes(inp), encoding='utf-8')
if inp == 'exit':
break
sk.close()
ThreadingTCPServer源码剖析
ThreadingTCPServer的类图关系如下:
学会看源码非常重要!不能仅仅光会用!大赞~ 知道他的过程和实现~ 怎么学会看源码呢?多看然后画类图,如上图!!!
在理解的时候可以把他们想象为,把所有需要用的方法,都在ThreadingTCPServer中
内部调用流程为:
- 运行服务端程序
- 执行 TCPServer.init方法,创建服务端Socket对象并绑定 IP 和 端口
- 执行 BaseServer.init方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle,赋值给 self.RequestHandlerClass
- 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 …
- 当客户端连接到达服务器执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
执行 ThreadingMixIn.process_request_thread 方法
执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)
源码精简版
import socket
import threading
import select
def process(request, client_address): # 模拟定义的handle()方法,这个方法内的代码是socket server与Client端交互代码
print(request,client_address)
conn = request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
flag = True
while flag:
data = conn.recv(1024)
if data == 'exit':
flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.bind(('127.0.0.1', 8002))
sk.listen(5)
while True: # 监控sk文件句柄,循环接受连接请求
r, w, e = select.select([sk, ], [], [], 1)
print('looping')
if sk in r: # 当sk文件句柄发生变化的时候说明是新的客户端连接过来了
print('get request')
request, client_address = sk.accept()
t = threading.Thread(target=process, args=(request, client_address)) # 创建一个线程,并调用自己定义的process方法执行~然后样客户端与之交互
t.daemon = False
t.start()
sk.close()
如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于
select
和Threading
两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。
ForkingTCPServer
ForkingTCPServer和ThreadingTCPServer的使用和执行流程基本一致,只不过在内部分别为请求者建立 “线程” 和 “进程”。
基本使用
服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
if __name__ == '__main__':
# 这里使用socketserver ForkingTCPServer类创建对象
server = socketserver.ForkingTCPServer(('127.0.0.1',8000),MyServer)
server.serve_forever()
客户端
import socket
ip_port = ('127.0.0.1',8000)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
while True:
data = sk.recv(1024)
print('receive:',data)
inp = input('please input:')
sk.sendall(inp)
if inp == 'exit':
break
sk.close()
以上ForkingTCPServer
只是将 ThreadingTCPServer
实例中的代码:
server = SocketServer.ThreadingTCPServer(('127.0.0.1',8000),MyRequestHandler)
变更为:
server = SocketServer.ForkingTCPServer(('127.0.0.1',8000),MyRequestHandler)
SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select
和 os.fork
两个东西,其实本质上就是在服务器端为每一个客户端创建一个进程
,当前新创建的进程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。
源码剖析参考: ThreadingTCPServer
socketserver 模块练习
开发一个支持多用户在线的FTP程序
要求:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
可参考: https://github.com/LiangXianSen/python_-study/tree/master/day9/FTP