正如前面的socket模块部分看到的一样,写一个简单套接字服务器不是很难,如果想实现超出继承的应用,最好寻求一些帮助,socketserver模块是标准库中很多服务器框架的基础,这些服务器架构包括BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer、DocXMLRPCServer,所有的这些服务器框架都为基础服务器增加了特定功能;
socketserver内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求
ThreadingTCPServer(多线程,真并发)
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。
使用ThreadingTCPServer:
- 创建一个继承自 SocketServer.BaseRequestHandler 的类
- 类中必须定义一个名称为 handle 的方法
- 启动ThreadingTCPServer
用socketserver对ssh程序做修改,实现多用户同时操作互不影响
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #-Author-Lian 4 5 #scoketserver 6 7 import socketserver,os 8 9 class Myserver(socketserver.BaseRequestHandler): 10 def handle(self): 11 while True: 12 conn = self.request 13 # conn,add = server.accept() 14 while True: 15 print("开始收") 16 client_data = conn.recv(1024) 17 client_data = client_data.decode("utf-8") 18 if client_data == "exit": #收到exit 退出 19 break 20 send_data = os.popen(client_data).read() #执行命令结果,要发送的数据 21 send_data = send_data.encode("utf-8") #转换为bytes类型 22 23 length = str(len(send_data)) #统计发送数据的长度 24 conn.sendall(length.encode("utf-8")) #长度以bytes类型发送过去 25 26 return_value = conn.recv(1024) 27 return_value = return_value.decode("utf-8") 28 29 if return_value == "start": 30 if not send_data: # 如果执行结果为空,表示命令不存在 31 conn.sendall((client_data +"命令不存在").encode("utf-8")) 32 else: 33 conn.sendall(send_data) 34 conn.close() 35 36 if __name__ == '__main__': 37 server = socketserver.ThreadingTCPServer(("127.0.0.1",8888),Myserver) 38 server.serve_forever() 39 40 ssh 服务端多用户同时连接
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #-Author-Lian 4 5 #ssh client 6 7 import socket 8 9 ip_port = ("127.0.0.1",8888) 10 client = socket.socket() 11 client.connect(ip_port) 12 13 while True: 14 cmd = input("->>").strip() 15 if not cmd: #空字符 重新输入 16 continue 17 client.sendall(cmd.encode("utf-8")) #要执行的命令发送过去 18 if cmd == "exit": #如果为exit 退出连接 19 break 20 21 length = client.recv(1024) #数据长度 22 length = length.decode("utf-8") 23 length = int(length) #长度转换为int 24 25 client.sendall("start".encode("utf-8")) #发送字节start 26 27 sum_data = "" #初始汇总的数据 28 while length >= 0: #循环收数据 29 server_data = client.recv(1024) 30 length -=1024 31 sum_data +=server_data.decode("utf-8") 32 print(sum_data) #打印最终的执行数据 33 34 client.close() 35 36 ssh 客户端多用户同时连接
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方法)
对源码进行精简做一个程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import socket import threading import select def process(request, client_address): 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 : r, w, e = select.select([sk,],[],[], 1 ) print 'looping' if sk in r: print 'get request' request, client_address = sk.accept() t = threading.Thread(target = process, args = (request, client_address)) t.daemon = False t.start() sk.close() |
如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 Threading 两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)