02: SocketServer服务
网络编程其他篇
目录:
1.1 SocketServer四种基本流及 异步处理理论部分 返回顶部
1、SocketServer作用
1. socket无法支持多并发,SocketServer 可以实现多并发
2. SocketServer使编写一个Socket服务器通信变得更加简单
3. SocketServer其实就是对socket的再封装
2、SocketServer提供了4个基本的服务类
1. TCPServer针对TCP套接字流
2. UDPServer针对UDP数据报套接字
3. UnixStreamServer和UnixDatagramServer针对UNIX域套接字,不常用(Unix本机间通信)
4. 它们的继承关系如下:
3、异步处理
1. 上面四个服务类都是同步处理请求的,一个请求没处理完不能处理下一个请求
2. 要想支持异步模型,可以利用多继承让server类继承ForkingMixIn 或 ThreadingMixIn mix-in classes。
3. ForkingMixIn利用多进程(分叉)实现异步。
4. ThreadingMixIn利用多线程实现异步。
4.、请求处理类
1. 要实现一项服务,还必须派生一个handler class请求处理类,并重写父类的handle()方法
2. handle方法就是用来专门是处理请求的,该模块是通过服务类和请求处理类组合来处理请求的。
3. SocketServer模块提供的请求处理类有BaseRequestHandler,以及它的派生类StreamRequestHandler和DatagramRequestHandler
4. 从名字看出可以一个处理流式套接字,一个处理数据报套接字。
5、实现异步,支持多连接
1. 前面介绍服务类时提到过,四个基本的服务类默认是同步模型的。
2. 要想支持异步可以利用多继承从ForkingMixIn 或ThreadingMixInmix-in classes和一个基本的服务类继承来定义一个支持异步的服务类
3. 比如:class Server(ThreadingMixIn, TCPServer): pass
4. ForkingMixIn 要考虑进程间的通信。ThreadingMixIn要考虑线程访问同一变量时的同步和互斥
1.2 创建socketserver实现: 多客户端并发 返回顶部
1、创建一个socketserver 至少分以下几步
1. 创建一个request handler class(请求处理类),继承自BaseRequestHandler class并重写它的handle()方法,该方法将处理客户端的请求。
2. 实例化一个server class对象,并将服务的地址和之前创建的request handler class传递给它。
server class对象有这些:TCPServer;UDPServer;UnixStreamServer;UnixDatagramServer
3. 调用server class对象的handle_request() 或 serve_forever()方法来开始处理请求
4. handle_request和server_forever区别是server_forever只是反复handle_request而已
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socketserver class MyTcpServer(socketserver.BaseRequestHandler): def handle(self): while True: self.request.recv(1024) self.request.send('I am server'.encode('utf-8')) server = socketserver.ThreadingTCPServer(('127.0.0.1',8888),MyTcpServer) server.serve_forever()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socketserver #每个客户端过来都会实例化一个MyTCPHandler实例,并调用handle方法来处理与客户端的通信 class MyTCPHandler(socketserver.BaseRequestHandler): #1 第一步:创建一个request handler class(请求处理类),继承自BaseRequestHandler class def handle(self): #2 第二步:并重写它的handle()方法,该方法将处理客户端的请求 while True: try: self.data = self.request.recv(1024).strip() #conn.recv(1024) = self.request.recv() print("{} wrote:".format(self.client_address[0])) print(self.data) self.request.send(self.data.upper()) #conn.send()=self.request.sendall() except ConnectionResetError as e: print("error",e) break if __name__ == "__main__": HOST, PORT = "localhost", 9999 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) #socketserver.TCPServer(单) server.serve_forever() #4 第四步:调用server class对象的serve_forever()方法来开始处理请求 #3 第三步:实例化一个server class对象,并将服务的地址和之前创建的request handler class传递给它 #注: 实例化一个server class对象工作原理 #a. (HOST, PORT):将监听地址当做一个参数传递给socketserver.TCPServer() #b. 每连接一个客户端就用MyTCPHandler实例化一个对象,将实例对象传送给socketserver.TCPServer #c. 然后服务器端使用MyTCPHandler类中的handle方法与客户端通信
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket client = socket.socket() #声明socket类型,同时生成socket连接对象 client.connect(('localhost',9999)) #指定要连接的服务器地址和端口号 while True: msg = input(">>:").strip() #输入要发送给服务器的消息 if len(msg) == 0:continue #如果客户端输入空格,让客户端继续输入 client.send(msg.encode("utf-8")) #将输入的消息发送到服务器端 data = client.recv(1024) #接收服务器端发送过来的数据,每次1024字节 print('client_recv:',data) #将从服务器端接收的数据在客户端打印出来 client.close()
1.3 SocketServer实现多并发FTP 部分功能 返回顶部
1、使用SocketServer完成FTP的多用户上传这个步骤
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socketserver,os,json #服务器端代码 class MyTCPHandler(socketserver.BaseRequestHandler): def put(self,*args): '''接收客户端文件''' cmd_dic = args[0] filename = cmd_dic["filename"] filesize = cmd_dic["size"] if os.path.isfile(filename): f = open(filename + '.new','wb') else: f = open(filename,'wb') print("file not exist",filename) self.request.send(b"200 ok") receive_size = 0 while receive_size < filesize: data = self.request.recv(1024) f.write(data) receive_size += len(data) else: print("file [%s] hsa uploaded ..."%filename) def handle(self): while True: try: self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) cmd_dic = json.loads(self.data.decode()) action = cmd_dic["action"] if hasattr(self,action): func = getattr(self,action) func(cmd_dic) except ConnectionResetError as e: print("error",e) break if __name__ == "__main__": HOST, PORT = "0.0.0.0", 9999 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) server.serve_forever()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket,os,json class FtpClient(object): def __init__(self): self.client = socket.socket() def help(self): msg = '''ls; pwd; cd .. ;get filename ; put filename''' print(msg) def connect(self,ip,port): self.client.connect((ip,port)) def interactive(self): #self.authenticate() while True: cmd = input(">>:").strip() if len(cmd) == 0:continue cmd_str = cmd.split()[0] if hasattr(self,'cmd_%s'%cmd_str): func = getattr(self,'cmd_%s'%cmd_str) func(cmd) else: self.help() def cmd_put(self,*args): cmd_split = args[0].split() if len(cmd_split) > 1: filename = cmd_split[1] if os.path.isfile(filename): filesize = os.stat(filename).st_size msg_dic = { "action": "put", "filename":filename, 'size':filesize, 'overridden':True } self.client.send( json.dumps(msg_dic).encode('utf-8') ) server_response = self.client.recv(1024) #防止连包, 等服务器确认 f = open(filename,'rb') for line in f: self.client.send(line) else: print("file upload success.....") f.close() else: print(filename,"is not exist") def cmd_get(self): pass ftp = FtpClient() ftp.connect("1.1.1.3",9999) ftp.interactive()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1、将server端代码复制到Linux下,路径:/bbb/server.py 2. 将client端代码直接放到PyCharm中运行 3. 在PyCharm中执行put tt.py将本目录下的tt.py文件上传到linux的/bbb文件夹中 >>:put tt.py file upload success..... 4. 执行命令完成后就可以看到有这个文件:/bbb/tt.py
2、文件传输时显示进度条
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import sys,time def view_bar(num, total): rate = num / total rate_num = int(rate * 100) r = '\r[%s%s]%d%%' % ("="*num, " "*(100-num), rate_num, ) sys.stdout.write(r) sys.stdout.flush() for i in range(0, 101): time.sleep(0.1) view_bar(i, 100) # 1)这里使用的是sys.stdout.write(r),输出结果是进度条增加,但是不会换行 # 2)2.x中的print无法指定end符号为其他值,默认会输出一个"\n",也就是用一次必定换到下一行 # 3)到了3.x中print成为了一个真正意义上的函数,后来就可以任意指定end符号的值,你可以输出一次后末尾添加上任意你想要的值,而不是强制换行。 # 4)stdout没有end这个符号这一说,输出不会换行,因此如果你想同一样输出多次,在需要输出的字符串对象里面加上"\r",就可以回到行首了。
作者:学无止境
出处:https://www.cnblogs.com/xiaonq
生活不只是眼前的苟且,还有诗和远方。