本节内容:
1.socketserver模块实现并发
2.ftp上传、下载示例
3.线程
4.打印进度条
一、socketserver模块实现并发
为什么要讲socketserver?
我们之前写的tcp协议的socket是不是一次只能和一个客户端通信,如果用socketserver可以实现和多个客户端通信。
它是在socket的基础上进行了一层封装,也就是说底层还是调用的socket,
在py2.7里面叫做SocketServer也就是大写了两个S,在py3里面就小写了。
后面我们要写的FTP作业,需要用它来实现并发,
也就是同时可以和多个客户端进行通信,多个人可以同时进行上传下载等。
1、socketserver怎么用

1 import socketserver #1、引入模块 2 class MyServer(socketserver.BaseRequestHandler): #2、自己写一个类,类名自己随便定义,然后继承socketserver这个模块里面的BaseRequestHandler这个类 3 4 def handle(self): #3、写一个handle方法,必须叫这个名字 5 #self.request #6、self.request 相当于一个conn 6 7 self.request.recv(1024) #7、收消息 8 msg = '亲,学会了吗' 9 self.request.send(bytes(msg,encoding='utf-8')) #8、发消息 10 11 self.request.close() #9、关闭连接 12 13 # 拿到了我们对每个客户端的管道,那么我们自己在这个方法里面的就写我们接收消息发送消息的逻辑就可以了 14 pass 15 if __name__ == '__mian__': 16 #thread 线程,现在只需要简单理解线程,别着急,后面很快就会讲到啦,看下面的图 17 server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer) # 4、使用socketserver的ThreadingTCPServer这个类,将IP和端口的元祖传进去,还需要将上面咱们自己定义的类传进去,得到一个对象,相当于我们通过它进行了bind、listen 18 server.serve_forever() #5、使用我们上面这个类的对象来执行serve_forever()方法,他的作用就是说,我的服务一直开启着,就像京东一样,不能关闭网站,对吧,并且serve_forever()帮我们进行了accept 19 20 #注意: 21 #有socketserver 那么有socketclient的吗? 22 #当然不会有,我要作为客户去访问京东的时候,京东帮我也客户端了吗,客户端是不是在我们自己的电脑啊,并且socketserver对客户端没有太高的要求,只需要自己写一些socket就行了。
2、ThreadingTCPServer,多线程,简单解释:看图
3、通过上面的代码,我们来分析socket的源码:
(大家还记得面向对象的继承吗,来,实战的时候来啦)

1 在整个socketserver这个模块中,其实就干了两件事情:1、一个是循环建立链接的部分,每个客户链接都可以连接成功 2、一个通讯循环的部分,就是每个客户端链接成功之后,要循环的和客户端进行通信。 2 看代码中的:server=socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer) 3 4 还记得面向对象的继承吗?来,大家自己尝试着看看源码: 5 6 查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer 7 8 实例化得到server,先找ThreadMinxIn中的__init__方法,发现没有init方法,然后找类ThreadingTCPServer的__init__,在TCPServer中找到,在里面创建了socket对象,进而执行server_bind(相当于bind),server_active(点进去看执行了listen) 9 找server下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中 10 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address) 11 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address) 12 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找.... 13 源码分析总结: 14 15 基于tcp的socketserver我们自己定义的类中的 16 17 self.server即套接字对象 18 self.request即一个链接 19 self.client_address即客户端地址 20 基于udp的socketserver我们自己定义的类中的 21 22 self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>) 23 self.client_address即客户端地址
4、一个完整的sockeserver代码示例:

1 import socketserver 2 class Myserver(socketserver.BaseRequestHandler): 3 def handle(self): 4 self.data = self.request.recv(1024).strip() 5 print("{} wrote:".format(self.client_address[0])) 6 print(self.data) 7 self.request.sendall(self.data.upper()) 8 9 if __name__ == "__main__": 10 HOST, PORT = "127.0.0.1", 9999 11 12 # 设置allow_reuse_address允许服务器重用地址 13 socketserver.TCPServer.allow_reuse_address = True 14 # 创建一个server, 将服务地址绑定到127.0.0.1:9999 15 #server = socketserver.TCPServer((HOST, PORT),Myserver) 16 server = socketserver.ThreadingTCPServer((HOST, PORT),Myserver) 17 # 让server永远运行下去,除非强制停止程序 18 server.serve_forever() 19 20 tcp_server.py

1 import socket 2 3 HOST, PORT = "127.0.0.1", 9999 4 data = "hello" 5 6 # 创建一个socket链接,SOCK_STREAM代表使用TCP协议 7 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: 8 sock.connect((HOST, PORT)) # 链接到客户端 9 sock.sendall(bytes(data + " ", "utf-8")) # 向服务端发送数据 10 received = str(sock.recv(1024), "utf-8")# 从服务端接收数据 11 12 print("Sent: {}".format(data)) 13 print("Received: {}".format(received)) 14 15 tcp_client.py
二、ftp上传、下载示例

1 import socket 2 import time 3 import json 4 server = socket.socket() 5 6 server.bind(('127.0.0.1',8001)) 7 server.listen() 8 9 conn,addr = server.accept() 10 11 def upload(conn,client_file_info): 12 # 13 upload_file_path = r'D:jj' + '\' + client_file_info['file_name'] 14 recv_all_datalen = 0 15 with open(upload_file_path,'wb') as f: 16 while recv_all_datalen < client_file_info['file_size']: 17 every_recv_data = conn.recv(1024) 18 every_recv_datalen = len(every_recv_data) 19 recv_all_datalen += every_recv_datalen 20 f.write(every_recv_data) 21 22 print('客户端上传文件') 23 24 25 client_userinfo = conn.recv(1024).decode('utf-8') 26 #定义一个需要返回给服务端的功能列表 27 server_function_list = {'1':'上传','2':'下载'} 28 #将功能字典转换为json字符串,然后进行编码,转成bytes类型 29 json_func_list = json.dumps(server_function_list,ensure_ascii=False) 30 31 32 #服务端登录认证 33 with open('userinformation','r',encoding='utf-8') as f: 34 for i in f: 35 if i.strip() == client_userinfo: 36 #登录成功以后,将服务端能够提供的功能,发送给客户端 37 conn.send(json_func_list.encode('utf-8')) 38 #接收客户端的文件描述信息,其中有一项是功能序号 39 client_fileinfo_json = conn.recv(1024).decode('utf-8') 40 client_file_info = json.loads(client_fileinfo_json) 41 42 print(client_file_info) 43 if client_file_info['func_choice'] == '1': 44 upload(conn,client_file_info) 45 46 break 47 else: 48 #登录错误的信息码 49 conn.send(b'201')

1 import socket 2 import json 3 import os 4 import struct 5 client = socket.socket() 6 7 client.connect(('127.0.0.1',8001)) 8 file_size = os.path.getsize(r'D:python_workspaceday029xxx.mp4') 9 #定义了一个文件描述信息,以字典的形式 10 file_info_dict = { 11 'file_path': r'D:python_workspaceday029xxx.mp4', 12 'file_name': 'xxx.mp4', 13 'file_size':file_size 14 } 15 16 def login(): 17 18 print('欢迎来到登录页面') 19 username = input('请输入用户名>') 20 password = input('请输入密码>') 21 #将用户名和密码拼接成一个字符串 22 user_info = username + '|' + password 23 client.send(user_info.encode('utf-8')) 24 25 #如果服务端的状态码为200,登录成功,如果不是,登录失败,登录成功,这个信息就是服务端的功能信息 26 from_server_status = client.recv(1024).decode('utf-8') 27 if from_server_status == '201': 28 print('登录失败') 29 else: 30 all_file_size = 0 31 #接收的是一个json格式的字符串 32 server_func_list = json.loads(from_server_status) 33 #打印了功能信息 34 print(server_func_list) #{'1': '上传', '2': '下载'} 35 client_func_choice = input('请输入要选择的功能序号>>>') 36 #选择1,说明要上传 37 if client_func_choice == '1': 38 # 上传或者下载的功能序号添加到了文件描述信息的字典中 39 file_info_dict['func_choice'] = client_func_choice 40 file_dict_json = json.dumps(file_info_dict,ensure_ascii=False) 41 #发送文件描述信息 42 client.send(file_dict_json.encode('utf-8')) 43 # 44 with open(file_info_dict['file_path'],'rb') as f: 45 while all_file_size < file_info_dict['file_size']: 46 every_read_data = f.read(1024) 47 client.send(every_read_data) 48 every_read_datalen = len(every_read_data) 49 50 all_file_size += every_read_datalen 51 52 53 def register(): 54 print('注册') 55 56 57 print('-----请登录------') 58 print('1:登录 2:注册') 59 60 #选择登录 61 function_choice = input('请输入你要选择的功能序号>>>>') 62 #通过选择的序号,找到对应的函数执行,一个字典搞定 63 function_dict = {'1':login,'2':register} 64 65 function_dict[function_choice]()
三、打印进度条
import time
for i in range(10):
str_p = i * '*'
print('
'+ str_p,end='') # 每次重写,覆盖上一次的内容,相当于文件操作的w模式
time.sleep(0.5)