网络编程之FTP云盘程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
#ftp_server.py
import socket,json,hashlib,struct,os,socketserver
#md5加密类
class getMd5(object):
    # 普通加密用于账户密码加密方法
    @classmethod
    def makeMd5(cls,msg):
        md5=hashlib.md5(b'salt!@#$')
        md5.update(msg.encode('utf-8'))
        return md5.hexdigest()
 
    # 图片加密用于数据校验方法
    @classmethod
    def noEncodeMd5(cls,msg):
        md5 = hashlib.md5(b'salt!@#$')
        md5.update(msg)
        return md5.hexdigest()
 
 
#数据校验类
class DataVer(object):
    # 接收数据校验方法,guanDao信息管道,RecvData接收的数据
    @classmethod
    def RecvDataVer(cls,guanDao,recvData):
        print('开始MD5校验...')
        md5_data_len = struct.unpack('i', guanDao.recv(4))[0# 解包
        md5_data = guanDao.recv(md5_data_len).decode('gbk'# 解码得到客户端加密数据字符串
        if md5_data == getMd5.noEncodeMd5(recvData):  # 对比加密字符串
            guanDao.send('ok'.encode('gbk'))
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')
 
    # (断点续传)发送数据相同性校验方法
    @classmethod
    def ddxcRecvDataVer(cls, guanDao, file_path):
        client_md5DataInfoDicJsonStr_len = struct.unpack('i', guanDao.recv(4))[0]# 解包
        client_md5DataInfoDic_JsonStr = guanDao.recv(client_md5DataInfoDicJsonStr_len).decode('gbk')# 解码得到客户端加密数据字符串
        client_md5DataInfo_Dic = json.loads(client_md5DataInfoDic_JsonStr)  # 得到客户端文件信息的字典
        loaded_len=client_md5DataInfo_Dic['client_Data_len']#服务端要读取的长度
        with open(file_path,'rb')as f:
            server_data=f.read(loaded_len)#读服务端数据
        if getMd5.noEncodeMd5(server_data)==client_md5DataInfo_Dic['client_md5Data']:#对比服务端数据加密数据与客户端加密数据对比
            guanDao.send(struct.pack('i',1))
            print('数据校验成功,可以进行断点续传')
            return loaded_len
        else:
            guanDao.send(struct.pack('i',0))
            print('数据校验失败,不可以进行断点续传')
            return 0
 
    # 发送数据校验方法,guanDao信息管道,sendData发送的数据
    @classmethod
    def SendDataVer(cls,guanDao,sendData):
        print('开始MD5数据校验...')
        MD5_data = getMd5.noEncodeMd5(sendData)  # 加密数据字符串
        guanDao.send(struct.pack('i', len(MD5_data)))  # 打包
        guanDao.send(MD5_data.encode('gbk'))  # 编码发送加密数据字符串
        if guanDao.recv(1024).decode('gbk') == 'ok'# 对比返回数据
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')
 
    # (断点续传)接收数据相同性校验方法
    @classmethod
    def ddxcSendDataVer(cls, guanDao, sendData,data_fromServer_len):#sendData 数据 data_fromServer_len数据长度
        MD5_data = getMd5.noEncodeMd5(sendData)  # 加密数据字符串
        #发送数据相关信息字典
        sendData_info_dic={
            'MD5_data':MD5_data,
            'data_fromServer_len':data_fromServer_len
        }
        sendData_infoDic_jsonstr=json.dumps(sendData_info_dic)#字典变成json字符串
        guanDao.send(struct.pack('i', len(sendData_infoDic_jsonstr)))  # 打包发送json字符串长度
        guanDao.send(sendData_infoDic_jsonstr.encode('gbk'))  # 编码发送json字符串长度
 
 
#程序流程类
class ProgramProcess(object):
    # 主页面方法
    @classmethod
    def MainPage(cls,conn):
        gongNeng_lst = ['login', 'zhuce'# 功能列表
        while 1:
            print('接受执行哪个方法的指令。。。')
            fangshi=conn.recv(1024).decode('gbk')
            # print('字符串指令:',fangshi)
            conn.send('ok'.encode('gbk'))
            # print('发送了确认接收的指令ok')
            if fangshi.upper()=='Q':
                print('程序结束')
                break
            func=getattr(cls,gongNeng_lst[int(fangshi)-1])#反射
            func(conn)#运行对应方法
 
    # 注册方法
    @classmethod
    def zhuce(cls,conn):
        while 1:
            print('等待接受数据。。。')
            from_client_name = conn.recv(1024).decode('utf-8')
            print('接收到的数据为:',from_client_name)
            if from_client_name.upper()=='Q':
                print('返回主界面')
                break
            with open('user_info', 'a+', encoding='utf-8')as f:
                f.seek(0)#把光标移到开头
                for line in f:
                    if from_client_name == line.strip().split('|')[0]:
                        print('账号已存在,不允许注册')
                        conn.send(json.dumps(0).encode('utf-8'))
                        break
                else:
                    print('允许注册')
                    conn.send(json.dumps(1).encode('utf-8'))
                    from_client_info=conn.recv(1024).decode('utf-8')
                    name=from_client_info.split('|')[0]#获取到名字
                    neiCun=input('请给新用户分配内存空间M(空为默认值50M):')
                    if neiCun=='':
                        neiCun='50'
                    f.write(from_client_info+'|'+neiCun+'\n')#把账号|密码写入文件
                    file_path=r'.\server_file' + '//' + name#生成账户路径
                    os.makedirs(file_path)#新建账户文件夹
                    print('注册成功!!!')
                    conn.send(json.dumps(1).encode('utf-8'))
 
    # 登录方法
    @classmethod
    def login(cls,conn):
        flag = 0
        while not flag:
            print('等待接受数据。。。')
            from_client_usrInfo = conn.recv(1024).decode('gbk')#获取账号
            if from_client_usrInfo.upper()=='Q':
                print('返回主界面')
                break
            with open('user_info', 'r', encoding='utf-8')as f:
                for line in f:
                    if from_client_usrInfo in line.strip():#数据比对成功
                        print('数据比对成功')
                        conn.send(json.dumps(1).encode('utf-8'))
                        if conn.recv(1024).decode('gbk')=='ok':#收到为‘ok’
                            conn.send(line.strip().encode('gbk'))#把文件name|psw|neiCun数据发送过去
                            name,psw,neiCun=line.strip().split('|')#把此行数据分割放入对象
                            obj = Account(name, psw, neiCun)#实例化对象
                            obj.sendPage(conn)#进入第二界面
                            flag = 1
                        break
                else:
                    print('数据比对失败')
                    conn.send(json.dumps(0).encode('utf-8'))
 
 
#账户及操作相关类
class Account(object):
    # 初始化方法
    def __init__(self,name,psw,neiCun):
        self.name=name
        self.psw=psw
        self.neiCun=neiCun
        self.path=r'.\server_file'+'//'+name
 
    # 功能主页面方法
    def sendPage(self,conn):
        gongNeng_lst = ['update', 'download','newdir','deldir' # 功能列表
        while 1:
            print('接受执行哪个方法的指令。。。')
            num_str=conn.recv(1024).decode('gbk')
            # print('字符串指令:',fangshi)
            conn.send('ok'.encode('gbk'))
            # print('发送了确认接收的指令ok')
            if num_str.upper()=='Q':
                print('返回主界面')
                break
            if num_str=='5':#当字符串数字为5时
                print('选择的功能:查看文件夹')
                Account.show(conn,self.path)#运行遍历文件夹方法
            else:
                func=getattr(self,gongNeng_lst[int(num_str)-1])#反射
                func(conn)#运行对应方法
 
    # 上传文件方法
    def update(self,conn):
        print('选择的功能:上传文件')
        while 1:
            updata_path=self.path+'//'#默认上传文件到自己的文件夹
            client_fileinfo_len=struct.unpack('i',conn.recv(4))[0]#解包4字节json字典字符串长度
            if client_fileinfo_len:#发过来的数据不是0
                # print('file_info字典json字符串的长度:',client_fileinfo_len)
                client_fileinfo_dic=json.loads(conn.recv(client_fileinfo_len).decode('gbk'))#接收file_info字典
                if client_fileinfo_dic['updataPath'] == '':#判断字典里的上传保存路径是否为空
                    updata_path=self.path#拼接上传文件的路径上层文件夹的路径
                    updata_path_new=self.path+'//'+client_fileinfo_dic['filename']#此路径是上传数据写文件的路径
                else:
                    updata_path=os.path.join(updata_path,client_fileinfo_dic['updataPath'])#拼接上传文件的路径上层文件夹的路径
                    updata_path_new=updata_path+'//'+client_fileinfo_dic['filename']#此路径是上传数据写文件的路径
                if os.path.exists(updata_path):#判断上传文件保存路径是否存在
                    conn.send(struct.pack('i',1))#发送打包数据1
                    fromclient_wenjian_length = client_fileinfo_dic['filesize'# 上传文件的大小
                    print('上传文件的字节长度:', fromclient_wenjian_length)
                    usedNeiCun = Account.CalculateFileSize(self.path)  # 计算已使用内存
                    usedNeiCun = usedNeiCun / (1024 * 1024# 转换成M
                    SurplusNeiCun = float(self.neiCun) - float(usedNeiCun)  # 剩余内存
                    print('剩余内存:', SurplusNeiCun)
                    fromclient_wenjian_len = fromclient_wenjian_length / (1024 * 1024# 上传文件大小M
                    if SurplusNeiCun > fromclient_wenjian_len:  # 内存可接受这么大的文件
                        conn.send(struct.pack('i', 1))  # 内存可接受这么大数据向服务端发送打包1
                        if os.path.exists(updata_path_new):#判断文件路径updata_path_new是否存在相同的文件
                            conn.send(struct.pack('i',1)) #此路径文件已存在发送打包1数据
                            print('上传的文件已存在,请进行MD5数据校验看数据是否完整')
                            self.duanDianXuChuan(conn,updata_path_new,fromclient_wenjian_length)#进入断点续传方法,把管道,与路径传过去
                        else:#不存在此文件路径时
                            conn.send(struct.pack('i',0)) #此路径文件不存在发送打包0数据
                            recv_data_len = 0  # 接收数据的长度
                            recv_data = b''
                            with open(updata_path_new, 'wb')as f:  # 打开服务端接收文件的
                                while recv_data_len < fromclient_wenjian_length:  # 当接收数据长度小于总数据长度时进入循环
                                    data = conn.recv(1024)
                                    f.write(data)
                                    recv_data_len += len(data)
                                    recv_data += data
                                    per_cent = recv_data_len * 100 // fromclient_wenjian_length
                                    # 通过\r来实现同一行打印,每次打印都回到行首打印
                                    print('\r' + '%d%%  %s' % (per_cent, '>' * (per_cent // 5)), end='')
                                else:
                                    conn.send(struct.pack('i', 1))  # 向客户端发送接收完毕的数据
                                    print('\n' + '上传接收完毕,接收字节长度为:', recv_data_len)
                                    DataVer.RecvDataVer(conn, recv_data)  # 调用接收数据校验方法
                                    usedNeiCun = Account.CalculateFileSize(self.path)  # 计算已使用内存
                                    usedNeiCun = usedNeiCun / (1024 * 1024# 转换成M
                                    conn.send(struct.pack('f', usedNeiCun))  # 返回已使用内存
                    else:
                        conn.send(struct.pack('i', 0))  # 内存不足发送0
                        conn.send(struct.pack('f', SurplusNeiCun))
                        print('内存不足')
                else:
                    conn.send(struct.pack('i',0))
                    print('上传文件保存路径不存在')
            else:
                print('退出上传功能')
                break
 
    # 下载文件方法
    def download(self,conn):
        print('选择的功能:下载文件')
        while 1:
            file_path_new=conn.recv(1024).decode('gbk')#接受要下载的文件名称
            if file_path_new.upper()=='Q':
                print('退出下载功能')
                break
            file_path=self.path+'//'+file_path_new#默认服务器给客户端发送的文件路径
            if os.path.exists(file_path):#判断文件路径是否存在
                conn.send(struct.pack('i',1))#路径存在发送四字节1
                file_info={
                    'filename':os.path.basename(file_path_new),
                    'filepath':file_path,
                    'filesize':os.path.getsize(file_path)
                }#文件信息组成的字典
                print('文件字节长度:', file_info['filesize'])
                conn.send(struct.pack('i',len(json.dumps(file_info))))#发送file_info字典序列化成字符串的长度
                conn.send(json.dumps(file_info).encode('gbk'))#发送file_info字典序列化后的编码
                if struct.unpack('i',conn.recv(4))[0]:#判断客户端是否存在要下载的文件
                    print('客户端已存在此文件,下面进行数据校验')
                    self.downLoadDuanDianXuChuan(conn,file_path)#调用断点续传方法
                else:
                    send_data_len = 0#发送数据长度
                    send_data=b''#发送的数据
                    with open(file_path, 'rb')as f:
                        while send_data_len<file_info['filesize']:
                            data=f.read(1024)
                            conn.send(data)
                            send_data_len+=len(data)
                            send_data+=data
                            per_cent = send_data_len*100 // file_info['filesize']
                            # 通过\r来实现同一行打印,每次打印都回到行首打印
                            print('\r' + '%d%%  %s' % (per_cent, '>' * (per_cent // 5)), end='')
                        else:
                            if conn.recv(1024).decode('utf-8')=='ok':
                                print('\n'+'下载数据发送完毕,发送字节长度为:',send_data_len)
                                DataVer.SendDataVer(conn,send_data)#调用发送数据校验方法
            else:
                conn.send(struct.pack('i',0))#文件路径不存在发送4字节数值0
                print('文件路径不存在')
 
    # 新建文件夹方法
    def newdir(self,conn):
        print('选择的功能:新建文件夹')
        while 1:
            path=conn.recv(1024).decode('gbk')
            if path.upper()=='Q':
                print('退出新建文件功能')
                break
            new_path=self.path+'//'+path#拼接新建文件夹的路径
            new_name = os.path.basename(path)  # 获取新建文件夹的名字
            new_path_dir=new_path.replace('//'+new_name,'')#获取上层文件夹路径
            if os.path.exists(new_path_dir):#判断上层文件件路径是否存在
                conn.send(struct.pack('i',1))
                os.makedirs(new_path)#新建文件夹
            else:
                conn.send(struct.pack('i',0))
                print('路径不存在')
 
    # 删除文件夹方法
    def deldir(self,conn):
        print('选择的功能:删除文件夹')
        while 1:
            path = conn.recv(1024).decode('gbk')
            if path.upper()=='Q':
                print('退出删除文件功能')
                break
            new_path = self.path + '//' + path  # 拼接删除文件夹的路径
            new_name = os.path.basename(path)  # 获取删除文件夹的名字
            new_path_dir = new_path.replace('//' + new_name, '')  # 获取上层文件夹路径
            if os.path.exists(new_path_dir):  # 判断上层文件件路径是否存在
                conn.send(struct.pack('i', 1))
                os.removedirs(new_path)  # 删除文件夹
            else:
                conn.send(struct.pack('i', 0))
                print('路径不存在')
 
    #上传的断点续传的方法
    def duanDianXuChuan(self,conn,updata_path_new,fromclient_wenjian_length):
        #updata_path_new 文件上传到服务端的保存路径
        #fromclient_wenjian_length 上传文件的总长度
        with open(updata_path_new,'rb')as f:#打开服务端文件
            data_from_server=f.read()#服务端数据
            data_fromServer_len=len(data_from_server)#服务端数据的长度
        DataVer.ddxcSendDataVer(conn,data_from_server,data_fromServer_len)#调用数据校验方法
        if struct.unpack('i', conn.recv(4))[0]:
            print('可以进行断点续传')
            with open(updata_path_new,'ab+')as f:
                while data_fromServer_len<fromclient_wenjian_length:
                    data=conn.recv(1024)
                    f.write(data)#继续上次数据上传
                    data_fromServer_len+=len(data)
                    percent=data_fromServer_len*100//fromclient_wenjian_length
                    print('\r %d%% %s '%(percent,'>'*(percent//5)),end='')
                else:
                    print('\n断点续传完成')
                    f.seek(0)
                    datas=f.read()
                    from_clientdata_md5str=conn.recv(len(getMd5.noEncodeMd5(datas))).decode('gbk')
                    if from_clientdata_md5str==getMd5.noEncodeMd5(datas):
                        conn.send(struct.pack('i',1))
                        print('md5数据校验成功!!!')
                    else:
                        conn.send(struct.pack('i',0))
                        print('MD5数据校验失败!!!')
        else:
            print('数据不一致不能进行断点续传')
 
    #下载的断点续传方法
    def downLoadDuanDianXuChuan(self,conn,file_path):
        loaded_len=DataVer.ddxcRecvDataVer(conn,file_path)#调用数据校验方法
        if loaded_len:#进入断点续传的条件
            server_data_len=os.path.getsize(file_path)#服务端数据字节长度
            with open(file_path,'rb')as f:
                f.seek(loaded_len)#光标挪到读到的位置
                while loaded_len < server_data_len:
                    data=f.read(1024)
                    conn.send(data)#发送断点数据
                    loaded_len+=len(data)
                    percent=(loaded_len*100)//server_data_len
                    print('\r %d%% %s'%(percent,'>'*(percent//5)),end='')
                else:
                    print('\n完成了断点续传')
                    f.seek(0)
                    datas=f.read()
                    conn.send(getMd5.noEncodeMd5(datas).encode('gbk'))
                    if struct.unpack('i',conn.recv(4))[0]:
                        print('MD5数据校验成功!!')
                    else:
                        print('MD5数据校验失败!!')
 
    # 查看文件夹方法
    @staticmethod
    def show(conn,file_path):
        nameList = os.listdir(file_path)  # 获取账号根目录下的name列表
        conn.send(json.dumps(nameList).encode('gbk'))  # json转字符串编码发送
        while 1:
            name=conn.recv(1024).decode('gbk')#接收客户端发来的name
            if name.upper()=='Q':
                print('返回上层文件夹')
                break
            new_path=file_path+'//'+name#拼接新路径
            if os.path.isdir(new_path):#判断路径是否为文件夹
                conn.send(struct.pack('i',1))#是文件夹发送打包的1
                Account.show(conn,new_path)#进入递归
            else:
                conn.send(struct.pack('i',0))#如果不是文件夹发送给客户端打包0
 
    # 计算文件夹大小的方法
    @staticmethod
    def CalculateFileSize(path):
        usedNeiCun=0
        list_name=os.listdir(path)#获取路径下的文件和文件夹名字的列表
        for name in list_name:
            path_abs=os.path.join(path,name)#拼接路径
            if os.path.isfile(path_abs):#如果是文件
                usedNeiCun+=os.path.getsize(path_abs)#计算文件大小
            elif os.path.isdir(path_abs):#如果是文件夹
                size=Account.CalculateFileSize(path_abs)#接受内层计算的值
                usedNeiCun+=size
        return usedNeiCun #返回已经使用的内存大小
 
 
#多线程通信类
class myServer(socketserver.BaseRequestHandler):
    #获取信息管道方法
    def handle(self):
        print('连接成功!!!')
        conn=self.request
        ProgramProcess.MainPage(conn)
        print('断开连接,等待下次连接>>>>>>>>>>>>>')
        conn.close()
 
#程序入口
if __name__ == '__main__':
    socketserver.TCPServer.allow_reuse_address = True#允许端口重用
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), myServer)
    server.serve_forever()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
#ftp_client.py
import socket,json,hashlib,struct,os
#md5加密类
class getMd5(object):
    # 普通加密用于账户密码加密方法
    @classmethod
    def makeMd5(cls,msg):
        md5=hashlib.md5(b'salt!@#$')
        md5.update(msg.encode('utf-8'))
        return md5.hexdigest()
 
    # 图片加密用于数据校验方法
    @classmethod
    def noEncodeMd5(cls,msg):
        md5 = hashlib.md5(b'salt!@#$')
        md5.update(msg)
        return md5.hexdigest()
 
 
#数据校验类
class DataVer(object):
    # 接收数据校验方法,guanDao信息管道,RecvData接收的数据
    @classmethod
    def RecvDataVer(cls,guanDao,recvData):
        print('开始MD5校验...')
        md5_data_len = struct.unpack('i', guanDao.recv(4))[0# 解包
        md5_data = guanDao.recv(md5_data_len).decode('gbk'# 解码得到客户端加密数据字符串
        if md5_data == getMd5.noEncodeMd5(recvData):  # 对比加密字符串
            guanDao.send('ok'.encode('gbk'))
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')
 
    # (断点续传)发送数据相同性校验方法
    @classmethod
    def ddxcRecvDataVer(cls, guanDao, file_path):#_file_path 本地上传文件路径
        sendData_jsonstr_len = struct.unpack('i', guanDao.recv(4))[0# 解包
        sendData_infoDic_jsonstr = guanDao.recv(sendData_jsonstr_len).decode('gbk'# 解码得到客户端json字符串
        sendData_info_dic=json.loads(sendData_infoDic_jsonstr)#发送数据相关信息字典
        read_data_len=sendData_info_dic['data_fromServer_len']#读文件的长度
        with open(file_path,'rb')as f:
            client_read_data=f.read(read_data_len)
            #read_data_len读文件的长度
            #client_read_data 读取的数据
        if getMd5.noEncodeMd5(client_read_data)==sendData_info_dic['MD5_data']:#数据相同可以断点续传
            guanDao.send(struct.pack('i',1))#发送打包数据1
            print('数据相同可以断点续传')
            return read_data_len
        else:
            guanDao.send(struct.pack('i', 0))  # 发送打包数据0
            print('同一路径下数据不同,无法进行断点续传')
            return 0
 
    # 发送数据校验方法,guanDao信息管道,sendData发送的数据
    @classmethod
    def SendDataVer(cls, guanDao, sendData):
        print('开始MD5数据校验...')
        MD5_data = getMd5.noEncodeMd5(sendData)  # 加密数据字符串
        guanDao.send(struct.pack('i', len(MD5_data)))  # 打包
        guanDao.send(MD5_data.encode('gbk'))  # 编码发送加密数据字符串
        if guanDao.recv(1024).decode('gbk') == 'ok'# 对比返回数据
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')
 
    # (断点续传)接收数据相同性校验方法
    @classmethod
    def ddxcSendDataVer(cls, guanDao,abs_download_path):
        with open(abs_download_path,'rb')as f:
            client_Data=f.read()#读出服务端数据
        client_md5Data=getMd5.noEncodeMd5(client_Data)#md5加密得到加密数据字符串
        client_md5DataInfo_Dic={
            'client_md5Data':client_md5Data,
            'client_Data_len':len(client_Data)
        }
        client_md5DataInfoDic_JsonStr=json.dumps(client_md5DataInfo_Dic)#json字符串
        guanDao.send(struct.pack('i', len(client_md5DataInfoDic_JsonStr)))  # 打包
        guanDao.send(client_md5DataInfoDic_JsonStr.encode('gbk'))  # 编码发送加密数据字符串
        if guanDao.recv(4)[0]:
            print('数据校验成功,可以进行断点续传')
            return len(client_Data)#已经下载的数据长度
        else:
            print('数据校验失败,不可以进行断点续传')
            return 0
 
#程序流程类
class ProgramProcess(object):
    # 程序入口的方法
    @classmethod
    def MainPage(cls):
        client = sendMSG.buid_connct()  # 调用生成client对象的方法
        lst = ['login', 'zhuce']
        while 1:
            print('''
    欢迎进入FTP文件系统
****************************
1.登录
2.注册
****************************
                ''')
            num = input('请输入要执行的序号(按Q退出):')
            if num=='1'or num=='2'or num.upper()=='Q':#判断输入合法
                client.send(num.encode('gbk'))
                if num.upper() == 'Q':
                    print('程序退出>>>>>>>')
                    break
                if client.recv(1024).decode('gbk') == 'ok':
                    func = getattr(cls, lst[int(num) - 1])
                    func(client)
            else:
                print('输入有误')
        client.close()
 
    # 注册方法
    @classmethod
    def zhuce(cls, client):
        while 1:
            name = input('请输入注册的账号(按q退出):')
            client.send(name.encode('utf-8'))
            if name.upper() == 'Q':
                break
            if json.loads(client.recv(1024).decode('utf-8')):
                psw = getMd5.makeMd5(input('请输入密码:'))
                print('等待服务端为您分配磁盘内存空间...')
                if sendMSG.senD(client, name, psw):
                    print('注册成功!!!')
            else:
                print('账号已存在')
 
    # 登陆方法
    @classmethod
    def login(cls, client):
        while 1:
            name = input('请输入账号(Q退出):')
            if name.upper() == 'Q':
                client.send(name.encode('gbk'))
                break
            psw = getMd5.makeMd5(input('请输入密码:'))
            if sendMSG.senD(client, name, psw):
                client.send('ok'.encode('gbk'))
                print('登陆成功!!!!')
                account_info=client.recv(1024).decode('gbk')#接受name|psw|neiCun数据
                name,psw,neiCun=account_info.split('|')#切割解构
                obj=Acount(name,psw,neiCun)#实例化对象
                obj.sendPage(client)#调用实例方法
                break
            else:
                print('账号或密码错误')
 
 
#账户相关类
class Acount(object):
    # 初始化方法
    def __init__(self,name,psw,neiCun):
        self.name=name
        self.psw=psw
        self.neiCun=neiCun
 
    # 功能界面方法
    def sendPage(self,client):
        while 1:
            print('''
      FTP系统主界面
****************************
1.上传文件
2.下载文件
3.新建文件夹
4.删除文件夹
5.查看文件夹
****************************
            ''')
            run_lst=['update','download','newdir','deldir',]
            chioce_num=input('请输入要执行的数字(按Q退出):')
            if chioce_num=='1'or chioce_num=='2'or chioce_num=='3'or chioce_num=='4'or chioce_num=='5'or chioce_num.upper()=='Q':#判断输入合法性
                client.send(chioce_num.encode('gbk'))#发送方法编号\
                if client.recv(1024).decode('gbk')=='ok':#接收到服务端返回的🆗数据
                    if chioce_num.upper() == 'Q':
                        print('返回上一层')
                        break
                    if chioce_num=='5':#如果输入的是'5'
                        print('选择的功能:查看文件夹')
                        Acount.show(client)#运行show静态方法
                    else:
                        func=getattr(self,run_lst[int(chioce_num)-1])#反射
                        func(client)#运行对应方法
                else:
                    print('没有收到服务端返回数据')
            else:
                print('输入有误')
 
    # 上传文件方法
    def update(self,client):
        print('选择的功能:上传文件')
        while 1:
            client_file_path=input('请输入上传文件的本地路径(Q退出):')
            if client_file_path.upper()=='Q':
                client.send(struct.pack('i',0))#发送打包数据0
                print('退出文件上传功能')
                break
            if os.path.exists(client_file_path):#本地上传路径存在
                flag=0
                while not flag:
                    updata_path=input('请输入保存到服务端的路径(层级用//隔开,为空是默认路径):')
                    file_info={
                        'filename':os.path.basename(client_file_path),#文件信息字典
                        'filepath':client_file_path,
                        'filesize':os.path.getsize(client_file_path),
                        'updataPath':updata_path
                    }#file_info字典
                    print('上传文件的字节长度:',file_info['filesize'])
                    file_info_jsonstr=json.dumps(file_info)#字典变成json字符串
                    fileInfo_jsonstrlen_stru=struct.pack('i',len(file_info_jsonstr))#打包字典json字符串长度
                    client.send(fileInfo_jsonstrlen_stru)#发送字典长度包
                    client.send(file_info_jsonstr.encode('gbk'))  # 发送字典file_info
                    if struct.unpack('i', client.recv(4))[0]:  # 上传文件保存到服务端的路径存在
                        flag = 1
                        if struct.unpack('i',client.recv(4))[0]:#如果内存空间可以放下此文件大小
                            if struct.unpack('i',client.recv(4))[0]:#如果上传文件的路径下有与上传文件同名的文件
                                print('服务端路径下已有与上传文件同名文件,进行MD5比对中...')
                                self.duanDianXuChuan(client,client_file_path)#进入断点续传方法
                            else:#如果上传文件的路径下没有与上传文件同名的文件
                                send_data_len=0#发送的数据长度
                                send_data=b''
                                with open(file_info['filepath'],'rb')as f:
                                    while send_data_len<file_info['filesize']:#当接收的数据长度小于总数据长度时
                                        data=f.read(1024)
                                        client.send(data)
                                        send_data_len+=len(data)
                                        send_data+=data
                                        per_cent = send_data_len*100//file_info['filesize']
                                        # 通过\r来实现同一行打印,每次打印都回到行首打印
                                        print('\r' + '%d%%  %s' % (per_cent,'>'*(per_cent//5)),end='')
                                    else:
                                        if struct.unpack('i',client.recv(4))[0]:
                                            print('\n'+'上传完毕,上传数据字节长度为',file_info['filesize'])
                                            DataVer.SendDataVer(client,send_data)#调用发送数据校验方法
                                            usdNeiCun=struct.unpack('f',client.recv(4))[0]
                                            print('总内存: %s M'%self.neiCun)
                                            print('已使用内存为: %d  M'% usdNeiCun)
                        else:
                            SurplusNeiCun=struct.unpack('f',client.recv(4))[0]#剩余空间
                            file_size=file_info['filesize']/(1024*1024)#上传文件大小M
                            print('内存剩余空间:%sM '%SurplusNeiCun)
                            print('文件大小: %sM'%file_size)
                            print('内存空间不足!!!!!')
                    else:
                        print('文件保存到服务端的路径不存在!!!')
            else:
                print('本地上传文件路径不存在!!!!')
 
    # 下载文件方法
    def download(self,client):
        print('选择的功能:下载文件')
        while 1:
            file_path=input('请输入要下载文件的路径(层级用//隔开,Q退出):').replace(' ','')
            client.send(file_path.encode('gbk'))#发送要下载的文件名
            if file_path.upper()=='Q':
                print('退出下载功能')
                break
            if struct.unpack('i',client.recv(4))[0]:#如果文件名存在
                download_path=r'.\client_file'+'//' # 默认下载路径
                print('等待接收服务端发送的文件...')
                server_fileinfojsonstr_len = struct.unpack('i',client.recv(4))[0]#拆包4字节file_info字典的json字符串长度
                # print('file_info字典的json字符串长度:', server_fileinfojsonstr_len)
                server_fileinfo_jsonstr=client.recv(server_fileinfojsonstr_len).decode('gbk')#接收file_info字典的json字符串
                file_info_dic=json.loads(server_fileinfo_jsonstr)#json反序列化变成file_info字典
                fromclient_wenjian_length=file_info_dic['filesize']#获取文件长度
                print('下载文件的字节长度:',fromclient_wenjian_length)
                abs_download_path=os.path.join(download_path,file_info_dic['filename'])#下载的全路径
                if os.path.exists(abs_download_path):#下载到服务端的路径存在
                    client.send(struct.pack('i',1))#服务端存在要下载的文件
                    print('客户端已存在此文件,下面进行数据校验')
                    self.downLoadDuanDianXuChuan(client,abs_download_path,fromclient_wenjian_length)#调用断点续传方法
                else:
                    client.send(struct.pack('i',0))#服务端不存在要下载的文件
                    recv_data_len=0#接收的文件字节长
                    recv_data=b''
                    with open(abs_download_path, 'wb')as f:
                        while recv_data_len < fromclient_wenjian_length:#当接受的字节长度总和小于数据总长度时进入循环
                            data = client.recv(1024)
                            f.write(data)#写入文件
                            recv_data_len += len(data)
                            recv_data+=data
                            # 进度条百分比前的数字X(X%)
                            per_cent = recv_data_len*100 // fromclient_wenjian_length
                            # 通过\r来实现同一行打印,每次打印都回到行首打印
                            print('\r' + '%d%%  %s' % (per_cent, '>' * (per_cent // 5)), end='')
                        else:
                            client.send('ok'.encode('utf-8'))#发送接收完毕信号
                            print('\n'+'下载完毕,接收数据字节长度为:',recv_data_len)
                            DataVer.RecvDataVer(client,recv_data)#调用接收数据校验方法
            else:
                print('客户端不存在此文件')
 
    # 遍历文件夹方法
    @staticmethod
    def show(client):
        print('本层文件夹目录为:')
        nameList = json.loads(client.recv(1024).decode('gbk'))
        while 1:
            for name in nameList:
                print(name)
            new_name=input('请选择要继续查看的文件夹名(Q退出):')
            if new_name.upper() =='Q':
                client.send(new_name.encode('gbk'))  # 把q发送给服务端
                print('返回上层目录')
                break
            if new_name in nameList:#输入的名字在列表中
                client.send(new_name.encode('gbk'))#发送new_name
                if struct.unpack('i',client.recv(4))[0]:#判断是不是文件夹
                    Acount.show(client)#进入递归
                else:
                    print('此名字不是文件夹')
            else:
                print('本层目录不存在此文件夹名')
 
    # 新建文件夹方法
    def newdir(self,client):
        print('选择的功能:新建文件夹')
        while 1:
            path=input('请输入新建夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):')
            client.send(path.encode('gbk'))
            if path.upper()=='Q':
                print('退出新建文件功能')
                break
            if struct.unpack('i',client.recv(4))[0]:
                print('新建文件夹成功!!')
            else:
                print('文件路径错误')
 
    # 删除文件夹方法
    def deldir(self,client):
        print('选择的功能:删除文件夹')
        while 1:
            path=input('请输入删除夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):')
            client.send(path.encode('gbk'))
            if path.upper()=='Q':
                print('退出删除文件功能')
                break
            if struct.unpack('i',client.recv(4))[0]:
                print('删除文件夹成功!!!')
            else:
                print('文件路径错误')
 
    #上传断点续传方法
    def duanDianXuChuan(self,cilent,client_file_path):
        readed_data_len=DataVer.ddxcRecvDataVer(cilent,client_file_path)#文件从此位置开始读
        if readed_data_len:#是否进行断点续传校验方法
            print('服务端已经存在数据大小:',readed_data_len)
            data_len=os.path.getsize(client_file_path)#文件总长度
            with open(client_file_path,'rb')as f:
                f.seek(readed_data_len)#把光标放到断点位置
                while readed_data_len <data_len:#已读长度小于总长度
                    data=f.read(1024)
                    readed_data_len+=len(data)
                    cilent.send(data)#发送断点数据
                    percent = readed_data_len * 100 // data_len
                    print('\r %d%% %s ' % (percent, '>' * (percent // 5)), end='')
                else:
                    print('\n 断点续传完成')
                    f.seek(0)
                    datas=f.read()
                    cilent.send(getMd5.noEncodeMd5(datas).encode('gbk'))
                    if struct.unpack('i',cilent.recv(4))[0]:
                        print('md5数据校验成功!!')
                    else:
                        print('MD5数据校验失败')
 
    #下载断点续传方法
    def downLoadDuanDianXuChuan(self,client,abs_download_path,fromclient_wenjian_length):
        loaded_len=DataVer.ddxcSendDataVer(client,abs_download_path)#调用数据校验方法
        if loaded_len:#进入断点续传的条件
            with open(abs_download_path,'ab+')as f:
                while loaded_len < fromclient_wenjian_length:
                    data=client.recv(1024)
                    f.write(data)
                    loaded_len+=len(data)
                    percent=(loaded_len*100)//fromclient_wenjian_length
                    print('\r %d%%  %s'%(percent,'>'*(percent//5)),end='')
                else:
                    print('\n断点续传完成')
                    f.seek(0)
                    datas=f.read()
                    fromserver_md5_str=client.recv(len(getMd5.noEncodeMd5(datas))).decode('gbk')
                    if fromserver_md5_str==getMd5.noEncodeMd5(datas):
                        client.send(struct.pack('i',1))
                        print('md5数据校验成功!!')
                    else:
                        client.send(struct.pack('i',0))
                        print('md5数据校验失败!!')
 
#发送信息相关类
class sendMSG(object):
    # 建立连接方法,返回client对象
    @classmethod
    def buid_connct(cls):
        client = socket.socket()
        client.connect(('127.0.0.1', 8001))
        return client
 
    # 发送数据user_info方法,返回账号密码是否正确(1/0)
    @classmethod
    def senD(cls,client,name,psw):
        user_info = name + '|' + psw
        # print('数据传输格式:',user_info)
        client.send(user_info.encode('utf-8'))
        from_server_res=json.loads(client.recv(1024).decode('utf-8'))
        return from_server_res
 
 
#程序入口
if __name__=='__main__':
     ProgramProcess.MainPage()

  

posted @   冒蓝火的加特林哒哒哒  阅读(306)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示