Python3 从零单排28_线程队列&进程池&线程池

  1.线程队列

  线程队列有三种:先进先出,后进先出,按优先级进出,具体如下:

 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())
View Code

 

  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)
      回调函数

 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!")
View Code

    2.2同步回调  开启的多线程变成了串行,拿到第一个线程的执行结果才继续往下继续运行

 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)
View Code

    2.3异步回调

 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)
View Code

    2.4map用法

 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
View Code

 

  3.queue实现的线程池

  套接字服务端代码:

 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()
View Code

  上面的配套客户端代码:

 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()
View Code

 

  4.基于线程的FTP服务器

    1.在之前开发的FTP基础上,开发支持多并发的功能
    2.不能使用SocketServer模块,必须自己实现多线程
    3.必须用到队列Queue模块,实现线程池
    4.允许配置最大并发数,比如允许只有10个并发用户

    实现功能如下:
      用户加密认证
      允许同时多用户登录(用到并发编程的知识,选做)
      每个用户有自己的家目录,且只能访问自己的家目录
      对用户进行磁盘配额,每个用户的可用空间不同(选做)
      允许用户在ftp server上随意切换目录
      允许用户查看当前目录下的文件
      允许上传和下载文件,并保证文件的一致性
      文件传输过程中显示进度条

    服务端代码:

  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()
View Code

    客户端代码:

  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)
View Code

 

posted @ 2018-12-25 17:22  毛斯钢  阅读(577)  评论(0编辑  收藏  举报