一、select多路复用
1 句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间) 2 3 参数: 可接受四个参数(前三个必须) 4 返回值:三个列表 5 6 select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。 7 1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中 8 2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中 9 3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中 10 4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化 11 当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
1 import select 2 import socket 3 4 sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 6 sk1.bind(('127.0.0.1', 8001),) 7 sk1.listen(5) 8 9 sk2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 10 sk2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 11 sk2.bind(('127.0.0.1', 8002),) 12 sk2.listen(5) 13 14 inputs = [sk1, sk2] 15 w_inputs = [] 16 while True: 17 # IO多路复用,同时监听多个SOCKET对象. 18 r, w, e = select.select(inputs, w_inputs, inputs, 0.05) # 19 20 for obj in r: 21 print("obj::::", obj) 22 print("sk1::::", sk1) 23 if obj in [sk1, sk2]: 24 # 新连接 25 print("新连接....", obj) 26 conn, addr = obj.accept() 27 inputs.append(conn) 28 else: 29 # 有连接用户发送消息来了... 30 print("有用户发数据了", obj) 31 try: 32 data = obj.recv(1024) 33 except Exception as e: 34 data = "" 35 if data: 36 w_inputs.append(obj) 37 # obj.sendall(data) 38 else: 39 obj.close() 40 inputs.remove(obj) 41 w_inputs.remove(obj) 42 43 for obj in w_inputs: 44 obj.sendall(b'ok') 45 w_inputs.remove(obj)
1 import socket 2 3 client = socket.socket() 4 5 client.connect(('127.0.0.1', 8001)) 6 7 while True: 8 v = input(">>>>") 9 if v == 'exit': 10 break 11 client.sendall(bytes(v, encoding='utf8')) 12 ret = client.recv(1024) 13 print("server respone:", ret) 14 15 client.close()
二、socketserver模块
SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。
即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。
1.ThreadingTCPServer
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。
使用ThreadingTCPServer:
- 创建一个继承自 SocketServer.BaseRequestHandler 的类
- 类中必须定义一个名称为 handle 的方法
- 启动ThreadingTCPServer
1 import SocketServer 2 3 class MyServer(SocketServer.BaseRequestHandler): 4 5 def handle(self): 6 # print self.request,self.client_address,self.server 7 conn = self.request 8 conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.') 9 Flag = True 10 while Flag: 11 data = conn.recv(1024) 12 if data == 'exit': 13 Flag = False 14 elif data == '0': 15 conn.sendall('通过可能会被录音.balabala一大推') 16 else: 17 conn.sendall('请重新输入.') 18 19 20 if __name__ == '__main__': 21 server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer) 22 server.serve_forever()
1 import socket 2 3 4 ip_port = ('127.0.0.1',8009) 5 sk = socket.socket() 6 sk.connect(ip_port) 7 sk.settimeout(5) 8 9 while True: 10 data = sk.recv(1024) 11 print 'receive:',data 12 inp = raw_input('please input:') 13 sk.sendall(inp) 14 if inp == 'exit': 15 break 16 17 sk.close()
2.源码剖析
内部调用流程为:
- 启动服务端程序
- 执行 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方法)
1 import socket 2 import threading 3 import select 4 5 6 def process(request, client_address): 7 print request,client_address 8 conn = request 9 conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.') 10 flag = True 11 while flag: 12 data = conn.recv(1024) 13 if data == 'exit': 14 flag = False 15 elif data == '0': 16 conn.sendall('通过可能会被录音.balabala一大推') 17 else: 18 conn.sendall('请重新输入.') 19 20 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 21 sk.bind(('127.0.0.1',8002)) 22 sk.listen(5) 23 24 while True: 25 r, w, e = select.select([sk,],[],[],1) 26 print 'looping' 27 if sk in r: 28 print 'get request' 29 request, client_address = sk.accept() 30 t = threading.Thread(target=process, args=(request, client_address)) 31 t.daemon = False 32 t.start() 33 34 sk.close()
如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 Threading 两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。