一、Socket进阶
运用socket实现简版ssh,即在客户端输入指令,服务器收到指令后执行并返回结果
1 import socket 2 import os 3 server = socket.socket() 4 server.bind(('localhost',9999)) 5 server.listen() 6 7 while True: 8 conn,addr = server.accept() 9 print('new conn : ',addr) 10 while True: 11 print('waiting to be execute ') 12 data = conn.recv(1024) 13 if not data: 14 print('conncetion is cut') 15 break 16 print('execute instruction!',data) 17 18 cmd_res = os.popen(data.decode()).read()#popen返回的是内存地址,加read可以读出内容 19 20 print('before send') 21 if len(cmd_res) == 0:#指令如果没有返回结果如cd,返回客户端'cmd has no output !' 22 cmd_res = 'cmd has no output !' 23 24 conn.send(cmd_res.encode('utf-8')) 25 print('send done!') 26 server.close()
1 import socket 2 3 client = socket.socket() 4 client.connect(('localhost',9999)) 5 while True: 6 cmd = input('>>>:').strip() 7 if len(cmd)==0:continue 8 client.send(cmd.encode('utf-8')) 9 10 cmd_res_size = client.recv(1024)#接收命令长度 11 print('data lenth: ',cmd_res_size.decode()) 12 13 client.close()
但是这样实现,会发现当指令的执行结果内容太长时,无法一次性接收完,然后执行下一条指令时,又会接收到上次没接收完整的内容。因为客户端限制了每次接收的数据最多1024字节,而且socket每次发送接收数据的量是有限的,所以客户端接收的数据最大不能超过8192字节。那么当传输数据超过了客户端能一次接收的最大限制时,就要让客户端分批接收了,可以在发送数据前告知客户端数据的总大小,然后客户端可以计算出分批接收的次数。
1 import socket 2 import os 3 server = socket.socket() 4 server.bind(('localhost',9999)) 5 server.listen() 6 7 while True: 8 conn,addr = server.accept() 9 print('new conn : ',addr) 10 while True: 11 print('waiting to be execute ') 12 data = conn.recv(1024) 13 if not data: 14 print('conncetion is cut') 15 break 16 print('execute instruction!',data) 17 18 cmd_res = os.popen(data.decode()).read()#popen返回的是内存地址,加read可以读出内容 19 print('before send',len(cmd_res)) 20 print('before send') 21 if len(cmd_res) == 0:#指令如果没有返回结果如cd,返回客户端'cmd has no output !' 22 cmd_res = 'cmd has no output !' 23 24 conn.send(str(len(cmd_res.encode())).encode('utf-8')) 25 conn.send(cmd_res.encode('utf-8')) 26 print('send done!') 27 server.close()
1 import socket 2 client = socket.socket() 3 client.connect(('localhost',9999)) 4 while True: 5 cmd = input('>>>:').strip() 6 if len(cmd)==0:continue 7 client.send(cmd.encode('utf-8')) 8 9 cmd_res_size = client.recv(1024)#接收命令长度 10 print('data lenth: ',cmd_res_size.decode()) 11 12 received_size = 0 13 received_data = b'' 14 while received_size < int(cmd_res_size.decode()): 15 data = client.recv(1024) 16 received_size += len(data) 17 received_data += data 18 else: 19 print('cmd res receive done...',received_size) 20 print(received_data.decode()) 21 print('received done!') 22 client.close()
修改后的程序,运行还是会出现‘粘包’的现象,即服务器端连续调用send方法时,多次发送的数据被拼到一起传送给客户端。因为我们调用send方法时,数据并没有立刻发送到客户端,而是先把数据放到了socket的缓冲池中,等缓冲池满了或者超时了才会发送给客户端。这样就导致了客户端一次接收的数据实际可能包含多次的请求结果。我们没办法直接操作缓冲区,所以要解决这个问题,只能让缓冲区超时。可以在服务器端每次发出数据后,等待客户端返回一个收到数据的确认信息,收到确认信息后再发送下一次的数据。
1 import socket 2 import os 3 server = socket.socket() 4 server.bind(('localhost',9999)) 5 server.listen() 6 7 while True: 8 conn,addr = server.accept() 9 print('new conn : ',addr) 10 while True: 11 print('waiting to be execute ') 12 data = conn.recv(1024) 13 if not data: 14 print('conncetion is cut') 15 break 16 print('execute instruction!',data) 17 18 cmd_res = os.popen(data.decode()).read()#popen返回的是内存地址,加read可以读出内容 19 print('before send',len(cmd_res)) 20 21 if len(cmd_res) == 0:#指令如果没有返回结果如cd,返回客户端'cmd has no output !' 22 cmd_res = 'cmd has no output !' 23 conn.send(str(len(cmd_res.encode())).encode('utf-8'))#发送数据前,先发送数据长度给客户端 24 client_ack = conn.recv(1024)#接收客户端的确认信息 25 print(client_ack.decode()) 26 conn.send(cmd_res.encode('utf-8')) 27 print('send done!') 28 server.close()
1 import socket 2 client = socket.socket() 3 client.connect(('192.168.170.133',9999)) 4 while True: 5 cmd = input('>>>:').strip() 6 if len(cmd)==0:continue 7 client.send(cmd.encode('utf-8')) 8 9 cmd_res_size = client.recv(1024)#接收命令长度 10 print('data lenth: ',cmd_res_size.decode()) 11 client.send(b"ready to recv file") #发送确认消息 12 13 received_size = 0 14 received_data = b'' 15 while received_size < int(cmd_res_size.decode()): 16 data = client.recv(1024) 17 received_size += len(data) 18 received_data += data 19 else: 20 print('cmd res receive done...',received_size) 21 print(received_data.decode()) 22 print('received done!') 23 client.close() 24
这样修改后的程序就不会再出现粘包的问题了。
二、SocketServer
Python提供了两个基本的socket模块。一个是socket,它提供了标准的BSD Socket API;另一个是socketServer,它提供了服务器中心类,可以简化网络服务器的开发。
SocketServer提供了4个基本的服务类:
TCPServer针对TCP套接字流
UDPServer针对UDP数据报套接字
UnixStreamServer和UnixDatagramServer针对UNIX域套接字,不常用。这个四个服务类都是同步处理请求的。一个请求没处理完不能处理下一个请求。要想支持异步模型,可以利用多继承让server类继承ForkingMixIn 或 ThreadingMixIn mix-in classes。
要实现一项服务,还必须派生一个handler class请求处理类,并重写父类的handle()方法。handle方法就是用来专门是处理请求的。该模块是通过服务类和请求处理类组合来处理请求的。
SocketServer模块提供的请求处理类有BaseRequestHandler,以及它的派生类StreamRequestHandler和DatagramRequestHandler。从名字看出可以一个处理流式套接字,一个处理数据报套接字。
总结用SocketServer创建一个服务的步骤:
1.创建一个request handler class(请求处理类),继承自BaseRequestHandler class并重写它的handle()方法,该方法将处理到的请求。
2.实例化一个server class对象,并将服务的地址和之前创建的request handler class传递给它。
3.调用server class对象的handle_request() 或 serve_forever()方法来开始处理请求。
1 import socketserver 2 class MyTCPHandler(socketserver.BaseRequestHandler):#定义请求处理类 3 def handle(self): 4 while True: 5 try: 6 self.data = self.request.recv(1024).strip() 7 print('{} wrote:'.format(self.client_address[0])) 8 print(self.data) 9 self.request.send(self.data.upper()) 10 except ConnectionResetError as e: 11 print('err',e) 12 break 13 14 if __name__ == '__main__': 15 host,port = 'localhost',9999 16 server = socketserver.ThreadingTCPServer((host,port),MyTCPHandler)#实例化服务类 17 server.serve_forever()#开启服务