毛毛Python進階之路2——實現雙端互聯
毛毛Python進階之路2——實現雙端互聯
1、兩個月前我開始自學Python
2、一個月前我在室友的壓迫下,我用Python優秀的第三方庫requests庫完成了相對正規的爬蟲專案。可以做到批量爬去網路上美眉的圖片【手動滑稽!!!】
連結:https://blog.csdn.net/qq_42874244/article/details/82855171 ————》 開啟探索之門
3、終於,在昨天!不對,是今天凌晨兩點(頭髮估計要沒了),我完成我的又一個相對以前更正規的專案——“雲端v2.0”。話說為什麼是2.0.了?因為以前做1.0的時候我的知識量太少了,雖然也完成了專案,可自己也是糊里糊塗的,BUG還多!沒辦法!但現在的2.0就不一樣了!看官接招!
一、需要準備的:
- Python當然必不可少啦!(我是用的pycharm)
- Python優秀的第三方庫socke庫(對!TA就是主角!)
- 其他Python內建模組os、hashlib、json、sys
- 其實本次專案最大的亮點——“面向物件程式設計”(我一直以來都只會面向過程!這次的面向物件可真是把我難到了,那也是幾個晚上沒睡覺才搞懂了那麼一丟丟!)
二、專案需要實現的功能:
- 客戶端可以登入、註冊。
- 服務端可以實現一對多。
- 每個使用者有自己的家目錄。
- 上傳檔案到雲端
- 下載檔案到本地
三、具體的實現:
思路:
1、在開始程式設計前我們考慮到我們的服務端是個服務型別的,所以我們應該從client端開始程式設計然後提示server端應該給我們提供哪些幫助!
2、既然是一個專案就應該“褪去小白最青澀的外套”,不再在一個檔案裡面寫完所有的程式,而是將檔案整理做到“鍋碗瓢盆既是一家,又有分類!”,所以我們應該按需求建立“bin—執行程式”“conf—配置檔案”“core—程式主體”“db—使用者資訊”“log—輸出日誌”,當然server端肯定要一個“home—使用者家目錄”。這樣的haul每個檔案就可以“該幹嘛幹嘛去”了。
3、由於我還不會前端開發,和client的互動只能是命令列的形式,所以我們不應該讓使用者輸入太多,這樣使用者體驗不好,所以我們做出來的功能只需要讓使用者選擇輸入我們需要的簡單符號即可,比如“123456789abc”。
4、整體過程應該是:
四、程式原始碼
client 端程式碼
1、start.py
# 執行程式
import os
import sys
sys.path.append(os.path.dirname(os.getcwd()))
from core import client
if __name__ == '__main__':
client.main()
2、core 裡面的 clien.py
from core.Auth_client import Auth
def main():
print("歡迎來到 “雲端V2.0”!\n請選擇:")
print("=" * 50)
start_1 = [("登入","login"),("註冊","register"),("退出","quit")]
for index,item in enumerate(start_1,1):
print(index,item[0])
# 用 enumerate 可以序列化列表,得到選項輸出
# 現在處理相關功能 由於功能較多 而且以後可能會新增 所以採取函式呼叫的方式呼叫相關函式!
# 先寫 Auth 函式
while True:
auth_obj = None
try:
print("=" * 50)
num = int(input("請輸入你的選項:")) # 直接轉為int型可能會出錯!
print("=" * 50)
func_str = start_1[num - 1][1]
except:
print("輸入錯誤!")
# 利用反射就可以不考慮使用者選擇的功能了 直接反射到相應的功能
if hasattr(Auth, func_str):
auth_obj = Auth()
func = getattr(auth_obj, func_str)
ret,username = func() # 在這裡得到一個使用者名稱字,在下面功能執行時需要!
# 在執行 exit() 函式時要考慮 auth.obj 是否還和服務端連線著
elif auth_obj:
auth_obj.socket.sk.close()
exit()
else:
exit()
if ret:
while True:
# 接著執行第二層!功能選擇!
auth_obj = None
print("=" * 50)
print("請選擇功能:")
start_2 = [("進入家目錄","go_home",),("下載檔案","download"),("上傳檔案","upload"),("退出系統","quit")]
for index1,item2 in enumerate(start_2,1):
print(index1,item2[0]) # 用同樣的方法
try:
print("=" * 50)
num = int(input("請輸入你的選項:")) # 直接轉為int型可能會出錯!
print("="*50)
func_str = start_2[num - 1][1]
except:
print("輸入錯誤!")
# 利用反射就可以不考慮使用者選擇的功能了 直接反射到相應的功能
if hasattr(Auth, func_str):
auth_obj = Auth()
func = getattr(auth_obj, func_str)
func(username)
elif auth_obj:
auth_obj.socket.sk.close()
exit()
else:
exit()
3、core裡面的socke_cilent.py
# 這個類是用來和server端來建立連線並收發訊息!
import socket
# server端接受字典時好處理資料,所以採用json包裝傳送的資料!
import json
class MySocketClient:
def __init__(self):
self.sk = socket.socket()
self.sk.connect(("127.0.0.1",8000))
def mysend(self,msg):
send_json = json.dumps(msg)
self.sk.send(send_json.encode("utf-8"))
def myrecv(self):
ret = self.sk.recv(1024)
ret = ret.decode("utf-8")
ret = json.loads(ret)
return ret
def myrecvfile(self,x):
ret = self.sk.recv(x)
return ret
def mysendfile(self,msg):
self.sk.send(msg)
4、core裡面的Auth_client.py
# 目前所需要解決的是 登入 和 註冊
from core.socket_client import MySocketClient
import os
import hashlib
class Auth:
# 建立連線只需要一次就可以 所以採取 “單例模式”
__instance = None
def __new__(cls, *args, **kwargs):
if not cls.__instance:
obj = object.__new__(cls)
obj.socket = MySocketClient()
obj.username = None
cls.__instance = obj
return cls.__instance
def login(self):
print("現在是登入程式!")
username = input("username : ")
password = input("password : ")
# 在某些作業系統裡面如果傳入空到服務端可能會報錯!
if username.strip() and password.strip():
# 考慮到這個檔案是功能合集 需要多次向 server端 多次傳送訊息,而且還要建立連線 所以新建立一個類來處理這方面函式!
self.socket.mysend({"username":username,"password":password,"operation":"login"})
ret = self.socket.myrecv()
if ret == "操作成功!":
return True,username # 在這裡得到一個使用者名稱字,在下面功能執行時需要!
else:
return False,None
def register(self):
print("現在是註冊程式!")
username = input("username : ")
password1 = input("password : ")
password2 = input("password_confirm : ")
# 同樣的 傳入空會出錯 且要保證密碼一樣!
if username.strip() and password1.strip() and password1 == password2:
self.socket.mysend({"username": username,"password":password1,"operation":"register"})
ret = self.socket.myrecv()
if ret == "操作成功!":
return True
else:
return False
def go_home(self,username):# 進入家目錄需要使用者名稱字 通過函式傳過來 一起傳送到server端。
print("進入家目錄成功,以下為您的檔案!")
self.socket.mysend({"username":username,"key":"go_home"})
ret = self.socket.myrecv() # 這裡返回的是一個列表,所以序列化一下
print("="*50)
for i in ret:
print(i)
print("="*50)
return True
def download(self,username):
print("您選擇了下載檔案!")
while True:
content = input("請輸入 “get 檔名”的形式:")
if content.startswith("get"): # 判斷字串裡面是否有 get
self.socket.mysend({"username":username,"key":"download","content":content})
# 1、先接受長度
server_reaponse = self.socket.myrecvfile(1024)
file_size = int(server_reaponse.decode("utf-8"))
print("接收檔案大小為:",file_size)
# 2、接受檔案內容
self.socket.mysendfile("準備好接受".encode("utf-8"))
filename = "new" + content.split(" ")[1]
os.chdir("..")
os.chdir("downfile")
f = open(filename, "wb")
received_size = 0
m = hashlib.md5()
while received_size < file_size:
size = 0 # 準確接收資料大小,解決粘包
if file_size - received_size > 1024: # 多次接收
size = 1024
else: # 最後一次接收完畢
size = file_size - received_size
data = self.socket.myrecvfile(size)
data_len = len(data)
received_size += data_len
print("已接收:",int(received_size/file_size), "%")
m.update(data)
f.write(data)
f.close()
print("實際接收的大小:", received_size) # 解碼
# 3、md5值校驗
md5_sever = self.socket.myrecvfile(1024).decode("utf-8")
md5_client = m.hexdigest()
print("伺服器發來的md5:", md5_sever)
print("接收檔案的md5:", md5_client)
if md5_sever == md5_client:
print("MD5值校驗成功")
else:
print("MD5值校驗失敗")
print("=" * 50)
print("檔案下載成功!")
print("=" * 50)
return True
else:
print("輸入格式錯誤!")
def upload(self,username):
print("您選擇了上傳檔案!")
while True:
print("要求:\n1、請按照格式“Send 檔名”傳送檔案。\n2、請將需要傳送的檔案放在根目錄下的Sendfile目錄下!\n3、承認本程式作者比你帥!")
content = input("請輸入 “Send 檔名”的形式:")
filename = content.split(" ")[1]
if content.startswith("Send"): # 同上 判斷字串裡面是否有按照格式來!
self.socket.mysend({"username": username, "key": "upload", "namefile": filename})
f = self.socket.myrecv()
print(f)
# 1.先發送檔案大小,讓客戶端準備接收
os.chdir("..")
os.chdir("sendfile")
size = os.stat(filename).st_size # 獲取檔案大小
self.socket.mysendfile(str(size).encode("utf-8")) # 傳送資料長度
print("傳送的大小:", size)
# 2.傳送檔案內容
self.socket.myrecvfile(1024) # 接收確認
m = hashlib.md5()
f = open(filename, "rb")
for line in f:
self.socket.mysendfile(line) # 傳送資料
m.update(line)
f.close()
# 3.傳送md5值進行校驗
md5 = m.hexdigest()
self.socket.mysendfile(md5.encode("utf-8")) # 傳送md5值
print("md5:", md5)
print("檔案已傳送至雲端!")
return True
else:
print("輸入格式錯誤!")
以上基本就是client端所有程式碼,具體目錄如下圖所示:
server 端程式碼
1、start.py 執行程式
這裡採用socketserver可以實現一對多!
import os
import sys
sys.path.append(os.path.dirname(os.getcwd()))
from core.server import MyTCPServer
import socketserver
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(("127.0.0.1",8000),MyTCPServer)
server.serve_forever()
2、conf 下的 setting.py
這裡是配置檔案!
import os
addr = ("127.0.0.1",8000)
code = "utf-8"
os.chdir("..")
os.chdir("home")
home_path = os.getcwd()
space = 1024*1024*2
os.chdir("..")
os.chdir("db")
pickle_path = os.getcwd() + "\\user_pickle"
user_info = os.getcwd() + "\\userinfo"
3、core 下的 server.py
import socketserver
import json
import os
import hashlib
from core import views
class MyTCPServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
msg = self.myrecv()
try:
os_str = msg["operation"]
if hasattr(views,os_str):
func = getattr(views,os_str)
ret = func(msg)
if ret:
ret = "操作成功!"
else:
ret = "操作失敗!"
self.mysend(ret)
except:
# 我們不將地址寫死,也就是說server端不管在哪裡我們都可以用這個程式 於是通過 os.chdir() 將程式執行路徑定格在 home 因為我們後面的程式就只是在home裡面執行!
print("選擇程式——", os.getcwd())
os_str = msg["key"]
if hasattr(views,os_str):
func = getattr(views,os_str)
ret = func(msg)
if os_str == "download":#在這裡由於我無法直接在views裡面呼叫server的send 和 recv所以我直接將程式搬到server裡面來了!
content = msg["content"]
filename = content.split(" ")[1]
size = os.stat(filename).st_size
# 1、傳送檔案大小,讓客戶端準備接受!
abc = str(size).encode("utf-8")
self.mysendfile(abc) #傳送資料長度
print("傳送的大小",size)
# 2、傳送檔案內容
self.myrecvfile()
m = hashlib.md5()
for line in ret:
self.mysendfile(line)
m.update(line)
ret.close()
# 3、傳送ms5值進行校驗!
md5 = m.hexdigest()
self.mysendfile(md5.encode("utf-8"))
print("md5 = ",md5)
# self.mysendfile(ret) 無法解決粘包問題
print("使用者下載檔案成功——————》")
os.chdir("..")
elif os_str == "upload":
username = msg["username"]
filename = msg["namefile"]
self.mysend("正在建立檔案…………")
# 1.先接收長度,建議8192
server_response = self.request.recv(1024)
file_size = int(server_response.decode("utf-8"))
print("接收到的大小:", file_size)
# 2.接收檔案內容
self.request.send("準備好接收".encode("utf-8")) # 接收確認
filename = "new" + filename
f = open(filename, "wb"