用C语言创建多个用户,实现支持多用户在线的FTP程序(C/S)
1 importjson2 importos3 importshelve4 importstruct5 importsubprocess6
7 from conf importsettings8 from lib importcommon9
10
11 classHandlerRequest:12 """處理用戶請求."""
13 max_packet_size = 8192
14 encoding = 'utf-8'
15
16 struct_fmt = 'i'
17 fixed_packet_size = 4
18
19 logger =common.load_my_logging_cfg()20
21 def __init__(self, request, address):22 self.request =request23 self.address =address24
25 self.residual_space_size =None26
27 self.breakpoint_resume =None28
29 self.username =None30 self.user_obj =None31 self.user_current_dir =None32
33 defclient_close(self):34 """關(guān)閉客戶端連接."""
35 self.request.close()36
37 defhandle_request(self):38 """處理客戶端請求."""
39 count =040 while count < 3: #連接循環(huán)
41 try:42 ifself.auth():43 #收消息
44 user_dic =self.receive_header()45 action_type = user_dic.get('action_type')46 ifaction_type:47 if hasattr(self, '_%s' %action_type):48 func = getattr(self, '_%s' %action_type)49 func(user_dic)50 else:51 self.send_header(status_code=201)52 #發(fā)消息
53 else:54 self.send_header(status_code=200)55 else:56 count += 1
57 self.send_header(status_code=199)58 exceptConnectionResetError:59 break
60 #關(guān)閉客戶端連接
61 self.logger.info('----連接斷開---- ip:%s port:%s' %self.address)62 self.client_close()63
64 defunfinished_file_check(self):65 self.logger.info('#執(zhí)行unfinished_file_check命令# ip:%s port:%s' %self.address)66
67 if notlist(self.breakpoint_resume.keys()):68 self.send_header(status_code=851)69 return
70
71 #self.breakpoint_resume[file_path] =
72 #{'file_size': _file_size, 'unfinished_file_path': unfinished_file_path, 'file_name': _file_name}
73 msg_list =[]74
75 for index, abs_path in enumerate(self.breakpoint_resume.keys(), 1):76
77 user_path = '/'.join(abs_path.split(self.username)[-1].split(os.sep))78 print('abs_path:', user_path)79 file_name = self.breakpoint_resume[abs_path]['file_name']80 src_file_size = self.breakpoint_resume[abs_path]['file_size']81 unfinished_file_size = os.path.getsize(self.breakpoint_resume[abs_path]['unfinished_file_path'])82 percent = unfinished_file_size / src_file_size * 100
83
84 msg = """
85 數(shù)量: %s 文件路徑: %s 文件名: %s86 文件原大小: %s字節(jié) 未完成的文件大小: %s字節(jié) 上傳的百分比: %.2f%%87 """ %(index, user_path, file_name, src_file_size, unfinished_file_size, percent)88
89 msg_list.append(msg)90 #msg_dic['/03_函數(shù)調(diào)用的三種形式.mp4'] = 5772100
91 #msg_dic[user_path] = unfinished_file_size
92 #self.send_header(status_code=850, msg_list=msg_list, msg_dic=msg_dic)
93 self.send_header(status_code=850, msg_list=msg_list)94
95 defauth(self):96 """用戶登陸認(rèn)證."""
97 ifself.user_current_dir:98 returnTrue99
100 #涉及到交叉導(dǎo)入
101 from core importmain102 #收消息
103 auth_dic =self.receive_header()104
105 user_name = auth_dic.get('username')106 user_password = auth_dic.get('password')107 md5_password = common.md5('password', password=user_password)108
109 #print(user_name, user_password, md5_password)
110
111 accounts =main.FTPServer.load_accounts()112 if user_name inaccounts.sections():113 if md5_password == accounts[user_name]['password']:114 self.send_header(status_code=100)115
116 self.username =user_name117 self.user_obj =accounts[user_name]118 self.user_obj['home'] =os.path.join(settings.USER_HOME_DIR, user_name)119 self.user_current_dir = self.user_obj['home']120
121 #print('self.user_obj:', self.user_obj)
122 #print("self.user_obj['home']:", self.user_obj['home'])
123
124 self.residual_space_size =common.conversion_quota(125 self.user_obj['quota']) - common.get_size(self.user_obj['home'])126
127 breakpoint_resume_dir_path = os.path.join(self.user_obj['home'], '.upload')128 if notos.path.isdir(breakpoint_resume_dir_path):129 os.mkdir(breakpoint_resume_dir_path)130 self.breakpoint_resume = shelve.open(os.path.join(breakpoint_resume_dir_path, '.upload.shv'))131 self.unfinished_file_check()132
133 self.logger.info('#認(rèn)證成功# ip:%s port:%s' %self.address)134 returnTrue135 self.logger.info('#認(rèn)證失敗# ip:%s port:%s' %self.address)136 returnFalse137
138 def_ls(self, cmd_dic):139 """
140 運(yùn)行dir命令將結(jié)果發(fā)送到客戶端.141 :param cmd_dic: {'path': [], 'action_type': 'ls'}142 或 {'path': ['目錄1', '目錄2', '目錄xxx'], 'action_type': 'ls'}143 或 {'path': ['?'], 'action_type': 'ls'}144 :return: None145 """
146 #print('_ls:', cmd_dic)
147 self.logger.info('#執(zhí)行l(wèi)s命令# ip:%s port:%s' %self.address)148
149 #核驗(yàn)路徑
150 dir_path =self.verify_path(cmd_dic)151 if notdir_path:152 dir_path =self.user_current_dir153
154 if cmd_dic.get('path') == ['?']: #為用戶提供ls /?命令
155 dir_path = '/?'
156
157 sub_obj =subprocess.Popen(158 'dir %s' %dir_path,159 shell=True,160 stderr=subprocess.PIPE,161 stdout=subprocess.PIPE162 )163 stderr_bytes, stdout_bytes =sub_obj.stderr.read(), sub_obj.stdout.read()164 cmd_size = len(stderr_bytes) +len(stdout_bytes)165
166 #發(fā)報頭
167 self.send_header(status_code=301, cmd_size=cmd_size)168 #發(fā)消息
169 self.request.sendall(stderr_bytes)170 self.request.sendall(stdout_bytes)171
172 def_cd(self, cmd_dic):173 """
174 根據(jù)用戶的目標(biāo)目錄, 改變用戶的當(dāng)前目錄的值.175 :param cmd_dic: {'action_type': 'cd', 'path': ['..']}176 或 {'action_type': 'cd', 'path': ['目錄1', '目錄2', '目錄xxx'], }177 :return: None178 Z:\pycharm\開發(fā)FTP程序之路\第2次FTP_第四模塊作業(yè)\FUCK_FTP\server\home\egon\目錄1179 """
180 #print('_cd:', cmd_dic)
181 self.logger.info('#執(zhí)行cd命令# ip:%s port:%s' %self.address)182
183 #核驗(yàn)路徑
184 dir_path =self.verify_path(cmd_dic)185 ifdir_path:186 if os.path.isdir(dir_path): #判斷用戶切換的路徑是否存在
187 self.user_current_dir =dir_path188 if dir_path == self.user_obj['home']:189 current_dir = '~'
190 else:191 join_dir = ''.join(dir_path.split('%s' % self.username)[1:])192 current_dir = '/'.join(join_dir.split('\'))193 self.send_header(status_code=400, current_dir=current_dir)194 else:195 self.send_header(status_code=499)196 else:197 self.send_header(status_code=498)198
199 def_mkdir(self, cmd_dic):200 """
201 更具用戶的目標(biāo)目錄, 且目錄不存在, 創(chuàng)建目錄標(biāo)目錄, 生成多層遞歸目錄.202 :param cmd_dic: {'action_type': 'mkdir', 'path': ['目錄1']}203 或 {'action_type': 'mkdir', 'path': ['目錄2', '目錄3', '目錄xxx']}204 :return: None205 """
206 #print('_mkdir:', cmd_dic)
207 self.logger.info('#執(zhí)行mkdir命令# ip:%s port:%s' %self.address)208
209 dir_path =self.verify_path(cmd_dic)210 ifdir_path:211 if not os.path.isdir(dir_path): #判斷用戶要創(chuàng)建的目錄時否存在
212 os.makedirs(dir_path)213 self.send_header(status_code=500)214 else:215 self.send_header(status_code=599)216 else:217 self.send_header(status_code=598)218
219 def_rmdir(self, cmd_dic):220 """
221 更具用戶的目標(biāo)目錄, 刪除不為空的目錄.222 :param cmd_dic: {'path': ['目錄1', '目錄xxx', '空目錄'], 'action_type': 'rmdir'}223 :return: None224 """
225 #print('_rmdir:', cmd_dic)
226 self.logger.info('#執(zhí)行rmdir命令# ip:%s port:%s' %self.address)227
228 dir_path =self.verify_path(cmd_dic)229 ifdir_path:230 ifos.path.isdir(dir_path):231 ifos.listdir(dir_path):232 self.send_header(status_code=699)233 else:234 os.rmdir(dir_path)235 self.send_header(status_code=600)236 else:237 self.send_header(status_code=698)238 else:239 self.send_header(status_code=697)240
241 def_remove(self, cmd_dic):242 """
243 更具用戶的目標(biāo)文件, 刪除該文件244 :param cmd_dic: {'path': ['目錄1', '目錄xxx', '文件'], 'action_type': 'remove'}245 :return:246 """
247 #print('_remove:', cmd_dic)
248 self.logger.info('#執(zhí)行remove命令# ip:%s port:%s' %self.address)249 file_path =self.verify_path(cmd_dic)250
251 iffile_path:252 ifos.path.isfile(file_path):253 #判斷用戶刪除的文件是否是要續(xù)傳的文件, 如果是則先把把續(xù)傳的記錄刪除
254 if file_path inself.breakpoint_resume.keys:255 delself.breakpoint_resume[file_path]256 os.remove(file_path)257 self.send_header(status_code=700)258 else:259 self.send_header(status_code=799)260 else:261 self.send_header(status_code=798)262
263 def_resume_upload(self, cmd_dic):264 """
265 860: '您正在繼續(xù)上傳文件, 在您繼傳之前, 您的目前空間:%s!',266 869: '您選擇文件路徑中沒有要續(xù)傳的文件, 請核對!',267 :param cmd_dic:268 :return:269 """
270 #print('def _resume_upload ===> cmd_args', cmd_dic)
271 self.logger.info('#執(zhí)行resume_upload命令# ip:%s port:%s' %self.address)272 self._upload(cmd_dic, resume_upload=True)273
274 def _upload(self, cmd_dic, resume_upload=False):275 """客戶端276 800: '你可以上傳文件, 在您上傳之前, 您的目前空間:%s!',277 801: '上傳文件成功, 您上傳完后的剩余空間:%s!',278 850: '您的還有為上傳完的文件, 是否繼續(xù)上傳!',279 851: '檢測您不存在未上傳完成的文件!',280 852: '您不能進(jìn)行續(xù)傳, 因?yàn)樵撐募峭暾募?',281 860: '您正在繼續(xù)上傳文件, 在您繼傳之前, 您的目前空間:%s!',282 869: '您選擇文件路徑中沒有要續(xù)傳的文件, 請核對!',283 894: '您不需要再對本路徑下上傳文件, 該文件在您的當(dāng)前路徑下已經(jīng)存在!',284 895: '上傳文件失敗, md5效驗(yàn)不一致, 部分文件內(nèi)容在網(wǎng)絡(luò)中丟失, 請重新上傳!',285 896: '上傳文件失敗, 您的空間不足, 您的上傳虛假文件大小, 您的剩余空間:%s!',286 897: '上傳文件失敗, 您的空間不足, 您的剩余空間:%s!',287 898: '上傳文件失敗, 上傳命令不規(guī)范!',288 899: '上傳文件必須要有文件的md5值以及文件名!',289 """
290 #print('_upload:', cmd_dic)
291 if notresume_upload:292 self.logger.info('#執(zhí)行upload命令# ip:%s port:%s' %self.address)293
294 #效驗(yàn): 897, 898, 899
295 _path, _file_md5, _file_name, _file_size = cmd_dic.get('path'), cmd_dic.get('file_md5'), cmd_dic.get(296 'file_name'), cmd_dic.get('file_size')297 file_path = self.verify_upload_action(cmd_dic, _path=_path, _file_md5=_file_md5, _file_name=_file_name,298
299 _file_size=_file_size)300
301 if resume_upload: #斷點(diǎn)續(xù)傳時執(zhí)行
302 if not file_path or file_path not inself.breakpoint_resume.keys():303 #869: '您選擇文件路徑中沒有要續(xù)傳的文件, 請核對!',
304 self.send_header(status_code=869)305 return
306
307 #找到之前未穿完的文件名
308 unfinished_file_path = self.breakpoint_resume[file_path]['unfinished_file_path']309 already_upload_size =os.path.getsize(unfinished_file_path)310
311 #效驗(yàn)成功通知續(xù)傳信號
312 #860: '您正在繼續(xù)上傳文件, 在您繼傳之前, 您的目前空間:%s!',
313 self.send_header(status_code=860, residual_space_size=self.residual_space_size,314 already_upload_size=already_upload_size)315
316 total_size = _file_size -already_upload_size317 mode = 'a'
318 else: #正常上傳執(zhí)行
319 if notfile_path:320 return
321
322 #判斷用戶上傳的文件是否重復(fù)
323 ifos.path.isfile(file_path):324 #894: '您不需要再對本路徑下上傳文件, 該文件在您的當(dāng)前路徑下已經(jīng)存在!',
325 self.send_header(status_code=894)326 return
327 else:328 unfinished_file_path = '%s.%s' % (file_path, 'upload')329
330 #效驗(yàn)成功通知上傳信號: 800
331 #800: '你可以上傳文件, 在您上傳之前, 您的目前空間:%s!',
332 self.send_header(status_code=800, residual_space_size=self.residual_space_size)333
334 total_size =_file_size335 mode = 'w'
336
337 #記錄斷點(diǎn)的功能: 在服務(wù)端用戶的路徑, 記錄文件大小, 加上后綴的路徑, 文件名
338 #或再次為未傳完的文件記錄斷點(diǎn)
339 self.breakpoint_resume[file_path] = {'file_size': _file_size, 'unfinished_file_path': unfinished_file_path,340 'file_name': _file_name}341
342 #開始接收文件
343 receive_size =0344 with open(unfinished_file_path, '%sb' %mode) as f:345 while receive_size <346 data_bytes="self.request.recv(self.max_packet_size)347" receive_size f.write>
350 os.rename(unfinished_file_path, file_path)351 #刪除記錄斷點(diǎn)的功能
352 delself.breakpoint_resume[file_path]353
354 #801, 895, 896
355 #效驗(yàn)用戶端發(fā)送的md5于本次上傳完畢的md5值
356 upload_file_md5 = common.md5(encryption_type='file', path=file_path)357 if upload_file_md5 !=_file_md5:358 #print('def _upload ===> upload_file_md5:%s, _file_md5:%s' % (upload_file_md5, _file_md5))
359 #895: '上傳文件失敗, md5效驗(yàn)不一致, 部分文件內(nèi)容在網(wǎng)絡(luò)中丟失, 請重新上傳!',
360 self.send_header(status_code=895)361 os.remove(file_path)362 return
363
364 #安全性問題: 再次判斷用戶是否以假的文件大小來跳出服務(wù)端限制的配額
365 if receive_size >self.residual_space_size:366 #896: '上傳文件失敗, 您的空間不足, 您的上傳虛假文件大小, 您的剩余空間:%s!',
367 self.send_header(status_code=896, residual_space_size=self.residual_space_size)368 os.remove(file_path)369 return
370 else:371 self.residual_space_size = self.residual_space_size -receive_size372 #print('def _upload ===> receive_size:', receive_size)
373 #print('def _upload ===> os.path.getsize(file_path)', os.path.getsize('%s' % file_path))
374 #801: '上傳文件成功, 您上傳完后的剩余空間:%s!',
375 self.send_header(status_code=801, residual_space_size=self.residual_space_size)376
377 def_resume_download(self, cmd_dic):378 self._download(cmd_dic, resume_download=True)379
380 def _download(self, cmd_dic, resume_download=False):381 self.logger.info('#執(zhí)行download命令# ip:%s port:%s' %self.address)382
383 file_path =self.verify_path(cmd_dic)384 if notfile_path:385 #999: '下載文件失敗, 您要下載的文件路徑不規(guī)范!',
386 self.send_header(status_code=999)387 return
388
389 if notos.path.isfile(file_path):390 #998: '下載文件失敗, 您要下載的文件路徑不存在!',
391 self.send_header(status_code=998)392 return
393
394 #通知可以開始下載
395 #900: '準(zhǔn)備開始下載文件!'.
396 file_name = file_path.split(os.sep)[-1]397 file_size =os.path.getsize(file_path)398 file_md5 = common.md5('file', file_path)399 unfinished_file_size = cmd_dic.get('unfinished_file_size')400 ifresume_download:401 #950: '準(zhǔn)備開始續(xù)傳文件!',
402 self.send_header(status_code=950, file_name=file_name, file_size=file_size, file_md5=file_md5)403 else:404 #900: '準(zhǔn)備開始下載文件!'.
405 self.send_header(status_code=900, file_name=file_name, file_size=file_size, file_md5=file_md5)406
407 #打開文件發(fā)送給客戶端
408 with open(file_path, 'rb') as f:409 ifresume_download:410 f.seek(unfinished_file_size)411 for line inf:412 self.request.sendall(line)413
414 def verify_upload_action(self, cmd_dic, *, _path, _file_name, _file_md5, _file_size):415 """
416 核驗(yàn)上傳功能.417 897: '上傳文件失敗, 您的空間不足, 您的剩余空間:%s!',418 898: '上傳文件失敗, 上傳命令不規(guī)范!',419 899: '上傳文件必須要有文件的md5值以及文件名!',420 """
421 #_path=['03_函數(shù)調(diào)用的三種形式.mp4']
422 if _path isNone:423 if _file_name and _file_md5 and_file_size:424 if _file_size >self.residual_space_size:425 #print('def _upload ===> self.residual_space_size:', self.residual_space_size)
426
427 #897: '上傳文件失敗, 您的空間不足, 您的剩余空間:%s!',
428 self.send_header(status_code=897, residual_space_size=self.residual_space_size)429 returnFalse430 else:431 #Z:pycharm開發(fā)FTP程序之路第2次FTP_第四模塊作業(yè)FUCK_FTPserverhomeegon 3_函數(shù)調(diào)用的三種形式.mp4
432 file_path =os.path.join(self.user_current_dir, _file_name)433 else:434 #899: '上傳文件必須要有文件的md5值以及文件名!',
435 self.send_header(status_code=899)436 returnFalse437 else:438 path =self.verify_path(cmd_dic)439
440 if notpath:441 #898: '上傳文件失敗, 上傳命令不規(guī)范!',
442 self.send_header(status_code=898)443 returnFalse444 else:445 #Z:pycharm開發(fā)FTP程序之路第2次FTP_第四模塊作業(yè)FUCK_FTPserverhomeegon 3_函數(shù)調(diào)用的三種形式.mp4
446 file_path =os.path.join(path, _file_name)447 returnfile_path448
449 defverify_path(self, cmd_dic):450 """
451 核驗(yàn)客戶端傳過來的路徑.452 :param cmd_dic: {'action_type': 'ls', 'path': []}453 或 {'action_type': 'ls', 'path': ['目錄1', '目錄xxx']}454 或 {action_type': 'cd', 'path': ['目錄2', '目錄xxx']}455 :return: None456 Z:\pycharm\開發(fā)FTP程序之路\第2次FTP_第四模塊作業(yè)\FUCK_FTP\server\home\egon\目錄1457 Z:\pycharm\開發(fā)FTP程序之路\第2次FTP_第四模塊作業(yè)\FUCK_FTP\server\home\egon\目錄1458 """
459 #print(cmd_dic)
460 path = cmd_dic.get('path')461 ifpath:462 ifisinstance(path, list):463 for element inpath:464 if notisinstance(element, str):465 path =None466 returnpath467 abspath = os.path.normpath(os.path.join(self.user_current_dir, *path))468 #print('def verify_path() ===> abspath:', abspath)
469 if abspath.startswith(self.user_obj['home']):470 path =abspath471 else:472 path = None #用戶目錄超出限制
473 else:474 path = None #不是列表類型例: '字符串'
475 else:476 path = None #[]
477 #print('def verify_path() ====> path', path)
478 returnpath479
480 defreceive_header(self):481 """
482 接收客戶端數(shù)據(jù).483 :return: {'action_type': 'cd', 'path': ['目錄1', '目錄xxx']}484 """
485 header_bytes =self.request.recv(self.fixed_packet_size)486 request_dic_json_length =struct.unpack(self.struct_fmt, header_bytes)[0]487 #print('request_dic_json_length:', request_dic_json_length)
488 #接收報頭
489 request_dic_json =self.request.recv(request_dic_json_length).decode(self.encoding)490 request_dic =json.loads(request_dic_json)491
492 #print('request_dic:', request_dic)
493
494 if notrequest_dic:495 return{}496 #print("def receive_header():", request_dic)
497 returnrequest_dic498
499 def send_header(self, *, status_code, **kwargs):500 """
501 發(fā)送數(shù)據(jù)給客戶端.502 :param status_code: 400503 :param kwargs: {'current_dir': '/home/egon/目錄1/目錄xxx'}504 :return: None505 """
506 #print(status_code)
507 #print(kwargs)
508 from core importmain509
510 response_dic =kwargs511 response_dic['status_code'] =status_code512 response_dic['status_msg'] =main.FTPServer.STATUS_CODE[status_code]513 response_dic.update(kwargs)514
515 response_dic_json_bytes =json.dumps(response_dic).encode(self.encoding)516 response_dic_json_bytes_length =len(response_dic_json_bytes)517 header_bytes =struct.pack(self.struct_fmt, response_dic_json_bytes_length)518
519 #print('header_bytes:', header_bytes)
520
521 #發(fā)送報頭
522 self.request.sendall(header_bytes)523 #發(fā)送json后bytes后的字典response_dic
524 self.request.sendall(response_dic_json_bytes)
346>總結(jié)
以上是生活随笔為你收集整理的用C语言创建多个用户,实现支持多用户在线的FTP程序(C/S)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信用卡逾期停卡还款后能用吗 能不能用银行
- 下一篇: 信用卡逾期天数怎么算的 以月为单位记录逾