1.线程队列
线程队列有三种:先进先出,后进先出,按优先级进出,具体如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import queue 2 3 # 先进先出 4 q = queue.Queue(3) 5 6 q.put(1) 7 q.put(2) 8 q.put(3) 9 # q.put(4) # 再放阻塞,等待队列消费 10 # q.put(4,block = False) # 不阻塞,强制放数据,如果满的情况下直接报错 等价与 q.put_nowait(4) 11 # q.put(4,block = True) # 阻塞,等待放数据,如果满的情况下阻塞,默认是True 12 # q.put(4, block=True, timeout=3) # 阻塞等待3秒,3秒还在阻塞,强制放数据,满的情况下报错 13 print(q.full()) 14 print(q.empty()) 15 16 print(q.get()) 17 print(q.get()) 18 print(q.get()) 19 # print(q.get()) # 再拿阻塞,等待队列新增数据 block timeout同put 20 print(q.full()) 21 print(q.empty()) 22 23 24 # 后进先出 同堆栈原理 25 q = queue.LifoQueue(3) 26 27 q.put(1) 28 q.put(2) 29 q.put(3) 30 # q.put(4) # 再放阻塞,等待队列消费 31 # q.put(4,block = False) # 不阻塞,强制放数据,如果满的情况下直接报错 等价与 q.put_nowait(4) 32 # q.put(4,block = True) # 阻塞,等待放数据,如果满的情况下阻塞,默认是True 33 # q.put(4, block=True, timeout=3) # 阻塞等待3秒,3秒还在阻塞,强制放数据,满的情况下报错 34 print(q.full()) 35 print(q.empty()) 36 37 print(q.get()) 38 print(q.get()) 39 print(q.get()) 40 # print(q.get()) # 再拿阻塞,等待队列新增数据 block timeout同put 41 print(q.full()) 42 print(q.empty()) 43 44 # 优先级进出 优先级越小的先出 45 q = queue.PriorityQueue(3) 46 47 q.put([50, 1]) 48 q.put([20, 2]) 49 q.put([30, 3]) 50 # q.put([50, 4]) # 再放阻塞,等待队列消费 51 print(q.full()) 52 print(q.empty()) 53 54 print(q.get()) 55 print(q.get()) 56 print(q.get()) 57 # print(q.get()) # 再拿阻塞,等待队列新增数据 block timeout同put 58 print(q.full()) 59 print(q.empty())
2.进程池&线程池
在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信。
然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多,这会对服务端主机带来巨大的压力,甚至于不堪重负而瘫痪。
于是我们必须对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行,这就是进程池或线程池的用途。
例如进程池,就是用来存放进程的池子,本质还是基于多进程,只不过是对开启进程的数目加上了限制。
2.1基本用法:
1、submit(fn, *args, **kwargs)
异步提交任务
2、map(func, *iterables, timeout=None, chunksize=1)
取代for循环submit的操作
3、shutdown(wait=True)
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前
4、result(timeout=None)
取得结果
5、add_done_callback(fn)
回调函数
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor 2 from threading import current_thread 3 import time, random, os 4 5 6 def sayhi(name): 7 print("%s say hi... pid:%s; current_thread:%s" % (name, os.getpid(), current_thread().getName())) 8 time.sleep(random.randint(1, 3)) 9 print("%s say bye... pid:%s; current_thread:%s" % (name, os.getpid(), current_thread().getName())) 10 11 12 if __name__ == "__main__": 13 # pool = ProcessPoolExecutor(3) # 实例化进程池,指定最大进程数为3 14 pool = ThreadPoolExecutor(3) # 实例化线程池,指定最大线程数为3 15 for i in range(10): 16 pool.submit(sayhi, "xg%s" % i,) 17 # 关闭pool的submit功能,不可以再丢进程或线程进线程池。 18 pool.shutdown(wait=True) # 此刻统计当前pool里的所有进程或线程数,每运行完一个-1,直到等于0时,往下运行代码。等同于进程线程的join 19 print("all over!")
2.2同步回调 开启的多线程变成了串行,拿到第一个线程的执行结果才继续往下继续运行
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 # 钓鱼大赛,参赛者钓鱼,然后称重。 2 from concurrent.futures import ThreadPoolExecutor 3 import time, random, os 4 5 6 def fishing(name): 7 print("%s is fishing..." % name) 8 time.sleep(random.randint(2, 5)) 9 fish = random.randint(5, 15) * "m" 10 res = {"name": name, "fish": fish} 11 return res 12 13 14 def weigh(res): 15 name = res["name"] 16 size = len(res["fish"]) 17 print("%s 钓到的鱼大小为 %s kg" % (name, size)) 18 19 20 if __name__ == "__main__": 21 pool = ThreadPoolExecutor(3) 22 res1 = pool.submit(fishing, "xt").result() # 同步拿结果,拿到结果才继续往下走 23 weigh(res1) 24 res2 = pool.submit(fishing, "dj").result() 25 weigh(res2) 26 res3 = pool.submit(fishing, "hh").result() 27 weigh(res3)
2.3异步回调
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 from concurrent.futures import ThreadPoolExecutor 2 import time, random, os 3 4 5 def fishing(name): 6 print("%s is fishing..." % name) 7 time.sleep(random.randint(2, 5)) 8 fish = random.randint(5, 15) * "m" 9 res = {"name": name, "fish": fish} 10 return res 11 12 13 def weigh(pool_obj): 14 res = pool_obj.result() # 拿到线程对象的运行结果,因为是线程运行完才会调用weigh,所以马上能拿到结果 15 name = res["name"] 16 size = len(res["fish"]) 17 print("%s 钓到的鱼大小为 %s kg" % (name, size)) 18 19 20 if __name__ == "__main__": 21 pool = ThreadPoolExecutor(3) 22 pool.submit(fishing, "xt").add_done_callback(weigh) # 当线程执行完后,将线程对象当参数传给weigh 23 pool.submit(fishing, "dj").add_done_callback(weigh) 24 pool.submit(fishing, "hh").add_done_callback(weigh)
2.4map用法
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor 2 3 import os,time,random 4 def task(n): 5 print('%s is runing' %os.getpid()) 6 time.sleep(random.randint(1,3)) 7 return n**2 8 9 if __name__ == '__main__': 10 11 executor=ThreadPoolExecutor(max_workers=3) 12 13 # for i in range(11): 14 # future=executor.submit(task,i) 15 16 executor.map(task,range(1,12)) #map取代了for+submit
3.queue实现的线程池
套接字服务端代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import socket, queue 2 from threading import Thread, currentThread 3 4 5 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 6 server.bind(("127.0.0.1", 8089)) 7 server.listen(1000) 8 # pool = ThreadPoolExecutor(2) 9 q = queue.Queue(1000) 10 11 12 def rec_data(): 13 while True: 14 conn = q.get() 15 while True: 16 try: 17 res = conn.recv(1024) 18 if not res:break 19 res = res.upper() 20 conn.send(res) 21 print("server cunrrent thread: %s" % currentThread().getName()) 22 except Exception as e: 23 print(e) 24 conn.close() 25 q.task_done() 26 break 27 28 29 def start(): 30 print("starting...") 31 for i in range(2): 32 t = Thread(target=rec_data) 33 t.daemon = True 34 t.start() 35 while True: 36 conn, addr = server.accept() 37 q.put(conn) 38 # pool.submit(rec_data, conn, addr) 39 40 41 if __name__ == "__main__": 42 start()
上面的配套客户端代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import socket 2 from threading import Thread, currentThread 3 4 5 def send_msg(): 6 client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 7 client.connect(("127.0.0.1", 8089)) 8 count = 1 9 while True: 10 msg = input(">>>>:") 11 # msg = "%s say hello %s " % (currentThread().getName(), count) 12 print("%s trying to send datas..." % currentThread().getName()) 13 client.send(msg.encode()) 14 print("%s trying to resv datas..." % currentThread().getName()) 15 res = client.recv(1024) 16 count += 1 17 print(res) 18 19 20 if __name__ == "__main__": 21 # for i in range(100): 22 # t = Thread(target=send_msg) 23 # t.start() 24 send_msg()
4.基于线程的FTP服务器
1.在之前开发的FTP基础上,开发支持多并发的功能
2.不能使用SocketServer模块,必须自己实现多线程
3.必须用到队列Queue模块,实现线程池
4.允许配置最大并发数,比如允许只有10个并发用户
实现功能如下:
用户加密认证
允许同时多用户登录(用到并发编程的知识,选做)
每个用户有自己的家目录,且只能访问自己的家目录
对用户进行磁盘配额,每个用户的可用空间不同(选做)
允许用户在ftp server上随意切换目录
允许用户查看当前目录下的文件
允许上传和下载文件,并保证文件的一致性
文件传输过程中显示进度条
服务端代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 # -*- coding: utf-8 -*- 2 # @Time : 2018/12/14 9:11 3 # @Author : Xiao 4 5 6 import socket 7 import os 8 import sys 9 import struct 10 import json 11 import hashlib 12 import configparser 13 import subprocess 14 import queue 15 from threading import Thread 16 from conf.settings import * 17 18 19 class Myserver(object): 20 server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 21 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 22 server_socket.bind((SERVER_IP, SERVER_PORT)) 23 server_socket.listen(MAX_CONNECT) 24 q = queue.Queue(MAX_QUEUE) # 初始化队列大小,最对只能有多少个等待链接 25 26 def __init__(self, conn): 27 """实例化时自动启动文件服务""" 28 self.conn = conn # 接收到的客户连接信息 29 self.client_addr = None # 接收到的客户连接地址信息 30 self.header_len_bytes = None # 发送数据的报文头信息 31 self.users = self.get_users() # 拿到用户信息,登录的时候用来判断 32 self.online = 0 # 用户的登录状态,每次接收到客户端的命令都要判断用户是否已经登录,未登录需要先登录 33 self.home = None # 用户的家目录,用户登录的时候会更新家目录 34 self.cur = None # 用户的当前目录,用户登录的时候当前目录就是家目录,随着用户cd命令执行更新 35 self.quota = 0 # 用户的家目录的大小限制,用户上传时会先判断目录大小是否够接收文件 36 print("starting....") 37 38 @staticmethod 39 def get_md5(var): 40 """"加密,盐值为123456""" 41 salt = "123456" 42 new_var = salt + var 43 m = hashlib.md5() 44 m.update(new_var.encode()) 45 return m.hexdigest() 46 47 @staticmethod 48 def get_users(): 49 '''拿到用户基础信息''' 50 """"初始化用户信息""" 51 users = configparser.ConfigParser() 52 users.read(DATA_PATH) 53 return users 54 55 @property 56 def get_code(self): 57 """拿到客户端上传文件的操作结果""" 58 code_len_bytes = self.conn.recv(4) 59 code_len = struct.unpack("i", code_len_bytes)[0] # struct.unpack解压数据,得到数据头信息长度 60 code_str = self.conn.recv(code_len).decode("utf-8") # 根据上面的长度接收数据头信息 61 code_dic = json.loads(code_str, encoding="utf-8") 62 code = code_dic["code"] 63 msg = code_dic["msg"] 64 return code, msg 65 66 @property 67 def get_msg(self): 68 """拿到客户端发来的请求""" 69 header_len = struct.unpack("i", self.header_len_bytes)[0] # struct.unpack解压数据,得到数据头信息长度 70 header_str = self.conn.recv(header_len).decode("utf-8") # 根据上面的长度接收数据头信息 71 header = json.loads(header_str, encoding="utf-8") 72 msg_size = header["msg_size"] # 根据数据头信息得到本次要接收的数据大小 73 msg = self.conn.recv(msg_size).decode("utf-8") 74 return msg 75 76 def send_code(self,code_dic): 77 """发送本次请求的结果状态,客户端根据这些状态做下一步操作""" 78 res_code_bytes = bytes(json.dumps(code_dic), encoding='utf-8') 79 res_code_len_bytes = struct.pack("i", len(res_code_bytes)) 80 self.conn.send(res_code_len_bytes) 81 self.conn.send(res_code_bytes) 82 83 def _login(self, user_info): 84 """登陆逻辑,登录成功后初始化用户的家目录、当前目录、登录状态等信息""" 85 info_lis = user_info.split(",") 86 if len(info_lis) == 2: 87 user, pwd = info_lis 88 pwd = self.get_md5(pwd) 89 if user in self.users and pwd == self.users[user].get("password"): 90 code_dic = {"code": "0", "msg": "login success!"} 91 self.send_code(code_dic) 92 self.home = os.path.join(HOME_PATH, self.users[user].get("home_dir")) 93 self.cur = self.home 94 self.quota = float(self.users[user].get("quota"))*(1024**3) 95 self.online = 1 96 return True 97 code_dic = {"code": "1", "msg": "error username or password!"} 98 self.send_code(code_dic) 99 100 def _sz(self, file_name): 101 """发送文件给客户端 102 1.首先拿到客户端的确认信息,是否上传 103 2.确定后,判断文件是否存在,并告诉客户端接下来是传文件还是通知文件不存在 104 3.文件存在则开始发送文件 105 """ 106 res_code, res_msg = self.get_code 107 if res_code == "0": 108 file_path = os.path.join(self.cur, file_name) 109 if os.path.exists(file_path) and os.path.isfile(file_path): 110 code_dic = {"code": "0", "msg": "start to download %s " % file_name} 111 self.send_code(code_dic) 112 file_size = os.path.getsize(file_path) 113 header = {"file_size": file_size, "file_name": file_name, "md5": "123456"} 114 header_bytes = bytes(json.dumps(header), encoding='utf-8') 115 header_len_bytes = struct.pack("i", len(header_bytes)) 116 self.conn.send(header_len_bytes) 117 self.conn.send(header_bytes) 118 with open(file_path, "rb") as f: 119 for line in f: 120 self.conn.send(line) 121 else: 122 code_dic = {"code": "1", "msg": "%s is a directory or file doesn't exist!" % file_name} 123 self.send_code(code_dic) 124 125 def _rz(self, file_name): 126 """保存来自文件 127 1.首先确认客户端是否要传文件 128 2.确定后,判断文件是否存在,并告诉客户端接下来是接收文件还是通知文件已存在 129 3.用户当前目录文件不存在则开始接收文件 130 """ 131 res_code, res_msg = self.get_code 132 if res_code == "0": 133 file_name = os.path.basename(file_name) 134 file_abspath = os.path.join(self.cur, file_name) 135 if not os.path.exists(file_abspath): 136 res_code = {"code": "0", "msg": "start to upload file %s..." % file_name} 137 self.send_code(res_code) 138 header_len_bytes = self.conn.recv(4) # 接收4个字节的数据头信息 139 header_len = struct.unpack("i", header_len_bytes)[0] # struct.unpack解压数据,得到数据头信息长度 140 header_str = self.conn.recv(header_len).decode("utf-8") # 根据上面的长度接收数据头信息 141 header = json.loads(header_str, encoding="utf-8") 142 file_size = header["file_size"] # 根据数据头信息得到本次要接收的数据大小 143 empty_size =float(self.quota) - os.path.getsize(self.home) 144 if empty_size < file_size: 145 res_code = {"code": "1", "msg": "only %s space left,no space to accept file %s" % (empty_size,file_name)} 146 self.send_code(res_code) 147 else: 148 res_code = {"code": "0", "msg": "uploading file %s..." % file_name} 149 self.send_code(res_code) 150 recv_size = 0 151 with open(file_abspath, "wb") as f: 152 while recv_size < file_size: # 当接收到的数据小于本次数据长度时就一直接收 153 line = self.conn.recv(1024) 154 f.write(line) # 将每次接收到的数据拼接 155 recv_size += len(line) # 实时记录当前接收到的数据长度 156 else: 157 res_code = {"code": "1", "msg": "%s is already exists..." % file_name} 158 self.send_code(res_code) 159 160 def _ls(self, dirname): 161 """ 162 1.接收客户端需要查看的是哪个目录 163 2.判断目录是否存在,存在继续往下走,不存在则直接告诉客户端失败,目录不存在 164 3.执行命令,如果服务器端是linux系统,则用ls,如果是windows则用dir 165 4.如果命令执行结果为空,返回客户端当前目录下没有文件 166 5.如果不为空,则开始发送目录下的文件夹或文件信息 167 """ 168 new_dirname = os.path.join(self.cur, dirname) if dirname != "." else self.cur 169 print(new_dirname) 170 cmd = "dir" if sys.platform.lower().startswith("win") else "ls" 171 if os.path.exists(new_dirname) and not os.path.isfile(new_dirname): 172 res = subprocess.Popen("%s %s" % (cmd, new_dirname), shell=True, stdout=subprocess.PIPE) 173 out = res.stdout.read() 174 if out: 175 print(out.decode("GBK")) 176 res_code = {"code": "0", "msg": "%s dir has follow files or dirs..." % dirname} 177 self.send_code(res_code) 178 header = {"file_size": len(out)} 179 header_bytes = bytes(json.dumps(header), encoding='utf-8') 180 header_len_bytes = struct.pack("i", len(header_bytes)) 181 self.conn.send(header_len_bytes) 182 self.conn.send(header_bytes) 183 self.conn.send(out) 184 else: 185 res_code = {"code": "3", "msg": "%s current dir is empty..." % dirname} 186 self.send_code(res_code) 187 else: 188 res_code = {"code": "1", "msg": "%s no such directory" % dirname} 189 self.send_code(res_code) 190 191 def _cd(self, dirname): 192 """ 193 1.接收到客户端的需要进入的目录信息,跟当前目录进行拼接 194 2.如果目录存在,则修改当前目录的变量值,如果不存在则告诉客户端,目录不存在 195 """ 196 new_dirname = os.path.join(self.cur, dirname) 197 if os.path.exists(new_dirname) and not os.path.isfile(new_dirname): 198 res_code = {"code": "0", "msg": "切换成功,当前目录为 %s " % dirname} 199 self.send_code(res_code) 200 self.cur = new_dirname 201 else: 202 res_code = {"code": "1", "msg": "切换失败, %s 目录不存在" % dirname} 203 self.send_code(res_code) 204 205 def comunication(self): 206 """ 207 通信主程序,每个实例都是通过这个方法和客户端通信的。 208 1.先判断用户的是否登陆,如果没有登陆而且请求不是login,则返回客户端让其登陆,如果已登陆则往下走 209 2.判断用户请求的方法是否正确,不正确则返回客户端,请求方法有误,如果方法存在则往下走 210 3.调用具体的方法 211 """ 212 while True: 213 try: 214 self.header_len_bytes = self.conn.recv(4) # 接收4个字节的数据头信息 215 if not self.header_len_bytes: 216 break 217 msg = self.get_msg 218 print(msg) 219 method, args = msg.split(" ", 1) 220 if not self.online and method != "login": 221 res_code = {"code": "2", "msg": "please login first!"} 222 self.send_code(res_code) 223 elif hasattr(self, "_%s" % method.lower()) and args: 224 res_code = {"code": "0", "msg": "wait moment,it's working now"} 225 self.send_code(res_code) 226 func = getattr(self, "_%s" % method.lower()) 227 func(args) 228 else: 229 res_code = {"code": "1", "msg": "error request %s !" % msg} 230 self.send_code(res_code) 231 except Exception as e: 232 print(e) 233 self.q.task_done() 234 self.conn.close() 235 break 236 237 @classmethod 238 def start(cls): 239 """ 240 循环拿队列q里的链接,拿到一个实例化一个,然后启动comunication方法,开始和客户端交互 241 """ 242 while True: 243 conn = cls.q.get() 244 client = Myserver(conn) 245 client.comunication() 246 247 @classmethod 248 def create_thread(cls): 249 ''' 250 开启多线程,线程数为settings设置的最大并发数 251 ''' 252 for i in range(MAX_RUN): 253 t = Thread(target=cls.start) 254 t.daemon = True 255 t.start() 256 257 @classmethod 258 def run(self): 259 """ 260 启动,循环等链接,每来一个链接就赛到队列里 261 """ 262 self.create_thread() 263 while True: 264 print("waiting for connection...") 265 conn, client_addr = self.server_socket.accept() 266 self.q.put(conn) 267 268 269 if __name__ == "__main__": 270 Myserver.run()
客户端代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 # -*- coding: utf-8 -*- 2 # @Time : 2018/12/14 9:11 3 # @Author : Xiao 4 5 6 import socket 7 import struct 8 import json 9 import os 10 from settings import * 11 12 13 class Myclient(object): 14 def __init__(self): 15 self.server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 16 self.server_socket.connect((SERVER_IP, SERVER_PORT)) # 主动初始化TCP服务器连接 17 18 def send_msg(self, msg_bytes): 19 """发送本次请求的指令,服务端根据指令返回数据""" 20 header = {"msg_size": len(msg_bytes)} 21 header_bytes = json.dumps(header).encode("utf-8") 22 header_size = struct.pack("i", len(header_bytes)) 23 self.server_socket.send(header_size) 24 self.server_socket.send(header_bytes) 25 self.server_socket.send(msg_bytes) 26 27 def send_code(self, code_dic): 28 """发送上传文件的结果状态,服务端根据这些状态做下一步操作""" 29 res_code_bytes = bytes(json.dumps(code_dic), encoding='utf-8') 30 res_code_len_bytes = struct.pack("i", len(res_code_bytes)) 31 self.server_socket.send(res_code_len_bytes) 32 self.server_socket.send(res_code_bytes) 33 34 @property 35 def get_code(self): 36 """拿到服务端关于本次指令的操作结果""" 37 code_len_bytes = self.server_socket.recv(4) 38 code_len = struct.unpack("i", code_len_bytes)[0] # struct.unpack解压数据,得到数据头信息长度 39 code_str = self.server_socket.recv(code_len).decode("utf-8") # 根据上面的长度接收数据头信息 40 code_dic = json.loads(code_str, encoding="utf-8") 41 code = code_dic["code"] 42 msg = code_dic["msg"] 43 return code, msg 44 45 def login(self): 46 """登陆函数""" 47 count = 0 48 while count < 3: 49 user = input("your name:").strip() 50 password = input("your password:").strip() 51 if user and password: 52 msg = "login %s,%s" % (user, password) 53 msg_bytes = msg.encode("utf-8") 54 self.send_msg(msg_bytes) 55 res_code, res_msg = self.get_code 56 print(res_msg) 57 if res_code == "0": 58 login_code, login_msg = self.get_code 59 print(login_msg) 60 if login_code == "0": 61 Myclient.online = 1 62 break 63 count += 1 64 else: 65 print("账号或密码不能为空!") 66 else: 67 exit("too many login!") 68 69 def _sz(self, file_name): 70 """下载文件 71 1.首先判断客户端本地是否存在文件,存在则告诉服务端不用传了,不存在则往下走 72 2.接收服务器端返回的文件操作结果,是否可以下载,不可以现在则打印服务端的msg,可以下载则开始接收文件 73 """ 74 file_abspath = os.path.join(DOWNLOAD_PATH, file_name) 75 if not os.path.exists(file_abspath): 76 data_code = {"code": "0", "msg": file_name} 77 self.send_code(data_code) 78 res_code, res_msg = self.get_code 79 print(res_msg) 80 if res_code == "0": 81 header_len_bytes = self.server_socket.recv(4) # 接收4个字节的数据头信息 82 header_len = struct.unpack("i", header_len_bytes)[0] # struct.unpack解压数据,得到数据头信息长度 83 header_str = self.server_socket.recv(header_len).decode("utf-8") # 根据上面的长度接收数据头信息 84 header = json.loads(header_str, encoding="utf-8") 85 file_size = header["file_size"] # 根据数据头信息得到本次要接收的数据大小 86 recv_size = 0 87 tmp_size = 0 88 per_size = file_size/50 89 num = 1 90 with open(file_abspath, "wb") as f: 91 while recv_size < file_size: # 当接收到的数据小于本次数据长度时就一直接收 92 if tmp_size > per_size: 93 rate = str((recv_size / file_size)*100)[:5] + "%" 94 print("%s %s" % ("#"*num, rate)) 95 tmp_size = 0 96 num += 1 97 line = self.server_socket.recv(1024) 98 f.write(line) # 将每次接收到的数据拼接 99 recv_size += len(line) # 实时记录当前接收到的数据长度 100 tmp_size += len(line) 101 print("%s %s" % ("#" * (num+1), "100.00%")) 102 print("download file %s success!" % file_name) 103 else: 104 res_code = {"code": "1", "msg": file_name} 105 self.send_code(res_code) 106 print("%s is already exists" % file_name) 107 108 def _rz(self, file_path): 109 """上传文件 110 1.首先判断客户端本地是否存在文件,不存在则告诉服务端不传了,存在则往下走 111 2.接收服务器端返回的文件操作结果,是否可以上传,不可以现在则打印服务端的msg,可以上传则开始发送文件 112 """ 113 if os.path.exists(file_path) and os.path.isfile(file_path): 114 file_name = os.path.basename(file_path) 115 res_code = {"code": "0", "msg": file_name} 116 self.send_code(res_code) 117 res_code, res_msg = self.get_code 118 print(res_msg) 119 if res_code == "0": 120 file_size = os.path.getsize(file_path) 121 header = {"file_size": file_size, "file_name": file_name, "md5": "123456"} 122 header_bytes = bytes(json.dumps(header), encoding='utf-8') 123 header_len_bytes = struct.pack("i", len(header_bytes)) 124 self.server_socket.send(header_len_bytes) 125 self.server_socket.send(header_bytes) 126 res_code, res_msg = self.get_code 127 print(res_msg) 128 upload_size = 0 129 tmp_size = 0 130 per_size = file_size / 50 131 num = 1 132 if res_code == "0": 133 with open(file_path, "rb") as f: 134 for line in f: 135 if tmp_size > per_size: 136 rate = str((upload_size / file_size) * 100)[:5] + "%" 137 print("%s %s" % ("#" * num, rate)) 138 tmp_size = 0 139 num += 1 140 self.server_socket.send(line) 141 upload_size += len(line) 142 tmp_size += len(line) 143 print("%s %s" % ("#" * (num + 1), "100.00%")) 144 print("upload file %s success!" % file_name) 145 else: 146 res_code = {"code": "1", "msg": "no such file: %s " % file_path} 147 self.send_code(res_code) 148 print("上传文件不存在!") 149 150 def _ls(self, dirname): 151 """查看当前文件夹下的文件或目录!""" 152 res_code, res_msg = self.get_code 153 print(res_msg) 154 if res_code == "0": 155 header_len_bytes = self.server_socket.recv(4) 156 header_len = struct.unpack("i", header_len_bytes)[0] 157 header_str = self.server_socket.recv(header_len).decode("utf-8") 158 header = json.loads(header_str, encoding="utf-8") 159 file_size = header["file_size"] 160 recv_size = 0 161 res = b'' 162 while recv_size < file_size: 163 res += self.server_socket.recv(1024) 164 recv_size = len(res) 165 res = res.decode("GBK") 166 print(res) 167 168 def _cd(self, dirname): 169 """切换目录""" 170 res_code, res_msg = self.get_code 171 print(res_msg) 172 173 def run(self): 174 """反复向服务器发送请求,当请求的返回操作码为0成功时,调用相应属性,失败时不做任何处理直接循环""" 175 self.login() 176 while True: 177 msg = input(">>>>:").strip() 178 if msg.lower() == "ls": 179 msg = msg + " ." 180 msg_lis = msg.split(" ", 1) 181 if len(msg_lis) == 2 and hasattr(self, "_%s" % msg_lis[0].lower()): 182 method = msg_lis[0].lower() 183 args = msg_lis[1] 184 msg_bytes = msg.encode("utf-8") 185 self.send_msg(msg_bytes) 186 res_code, res_msg = self.get_code 187 print(res_msg) 188 if res_code == "2": 189 self.login() 190 if res_code == "0": 191 func = getattr(self, "_%s" % method.lower()) 192 func(args) 193 else: 194 print("输入格式不正确,请重新输入!") 195 196 def __del__(self): 197 '''析构函数,当程序结束时自动关闭socket''' 198 self.server_socket.close() 199 200 201 if __name__ == "__main__": 202 try: 203 client = Myclient() 204 client.run() 205 except Exception as e: 206 print(e)