日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python 全栈开发,Day36(作业讲解(大文件下载以及进度条展示),socket的更多方法介绍,验证客户端链接的合法性hmac,socketserver)...

發布時間:2025/3/15 python 11 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python 全栈开发,Day36(作业讲解(大文件下载以及进度条展示),socket的更多方法介绍,验证客户端链接的合法性hmac,socketserver)... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?先來回顧一下昨天的內容

黏包現象
粘包現象的成因 :
  tcp協議的特點 面向流的 為了保證可靠傳輸 所以有很多優化的機制
  無邊界 所有在連接建立的基礎上傳遞的數據之間沒有界限
  收發消息很有可能不完全相等
  緩存機制,導致沒發過去的消息會在發送端緩存
    沒接收完的消息會在接收端緩存
解決:
  給應用層定制協議
解決方案一:只發送一條信息
  先發送一個定長表示待發送數據長度的bytes 先接收一個固定長度
  再發送要發送的數據 再按照長度接收數據

解決方案二 :發送的多條信息
  先發送一個定長表示待發送字典長度的bytes 先接收一個固定長度
  再發送要發送字典(字典中存儲的是文件信息) 再按照長度接收字典
  再發送文件 再根據字典中的信息接收相應的內容

?

那么這2種方案,如何選擇呢?
如果發送數據之前,發送的定制協議超過1個變量時,就應該使用字典
否則,就需要發送多次,比如:文件名的長度,文件名,文件大小的長度,文件的內容...

發送的次數越多,在網絡上,就比較浪費時間。
如果將上面的5個變量,放到一個字典里面,只需要發送一次,就可以了。

如果只有1個變量,就使用第一種方案

?

一、作業講解(大文件下載以及進度條展示)? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

大文件傳輸:

server.py

import os import json import socket import struct filepath = r'E:\BaiduYunDownload\[電影天堂www.dy2018.com]移動迷宮3:死亡解藥BD國英雙語中英雙字.mp4'sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen()conn,addr = sk.accept() filename = os.path.basename(filepath) filesize = os.path.getsize(filepath) dic = {'filename':filename,'filesize':filesize} str_dic = json.dumps(dic).encode('utf-8') len_dic = len(str_dic) length = struct.pack('i',len_dic) conn.send(length) # dic的長度 conn.send(str_dic) # dic with open(filepath,'rb') as f: # 文件while filesize:content = f.read(4096)conn.send(content)filesize -= len(content)'''這里不能減等4096,因為文件,最后可能只有3字節。要根據讀取的長度len(content),來計算才是合理的。''' conn.close() sk.close()

client.py

import json import struct import socketsk = socket.socket() sk.connect(('127.0.0.1',9000))dic_len = sk.recv(4) dic_len = struct.unpack('i',dic_len)[0] dic = sk.recv(dic_len) str_dic = dic.decode('utf-8') dic = json.loads(str_dic) with open(dic['filename'],'wb') as f: # 使用wb更嚴謹一些,雖然可以使用abwhile dic['filesize']:content = sk.recv(4096) #這邊的4096,可以不用和server對應,改成1024,也可以dic['filesize'] -= len(content)f.write(content) sk.close()

先執行server.py,再執行client.py

等待30多秒,當前目錄就會出現一個視頻文件,打開確認,是否可以播放。

?

客戶體驗太差了,用戶不知道啥時候能接收完,程序到底有沒有卡住?下載花了多長時間?都不知道

下面來一個進階版的,增加進度條和下載時間

主要是修改client.py,代碼如下:

import json import struct import socket import sys import timedef processBar(num, total): # 進度條rate = num / totalrate_num = int(rate * 100)if rate_num == 100:r = '\r%s>%d%%\n' % ('=' * rate_num, rate_num,)else:r = '\r%s>%d%%' % ('=' * rate_num, rate_num,)sys.stdout.write(r)sys.stdout.flushstart_time = time.time() # 開始時間sk = socket.socket() sk.connect(('127.0.0.1',9000))dic_len = sk.recv(4) dic_len = struct.unpack('i',dic_len)[0] dic = sk.recv(dic_len) str_dic = dic.decode('utf-8') dic = json.loads(str_dic) with open(dic['filename'],'wb') as f: # 使用wb更嚴謹一些,雖然可以使用abcontent_size = 0while True:content = sk.recv(4096)
f.write(content) # 寫入文件content_size += len(content) # 接收大小processBar(content_size,dic['filesize']) # 執行進度條函數if content_size == dic['filesize']:break # 當接收的總大小等于文件大小時,終止循環sk.close() # 關閉連接end_time = time.time() # 結束時間 print('本次下載花費了{}秒'.format(end_time - start_time))

執行效果如下:

上面效果展示了100個等號,太長了,那么要縮減到1/3呢?

修改進度條函數

def processBar(num, total): # 進度條rate = num / totalrate_num = int(rate * 100)if rate_num == 100:r = '\r%s>%d%%\n' % ('=' * int(rate_num / 3), rate_num,) # 控制等號輸出數量,除以3,表示顯示1/3else:r = '\r%s>%d%%' % ('=' * int(rate_num / 3), rate_num,)sys.stdout.write(r)sys.stdout.flush

再次執行:

?再來一個高級版,顯示綠色的飛機

代碼如下:

def processBar(num, total): # 進度條rate = num / totalrate_num = int(rate * 100)pretty = '?'if rate_num == 100:r = '\r\033[32m{}\033[0m{}%\n'.format(pretty * int(rate_num / 5), rate_num,)else:r = '\r\033[32m{}\033[0m{}%'.format(pretty * int(rate_num / 5), rate_num,)sys.stdout.write(r)sys.stdout.flush

效果如下:

再來一個每秒換色

導入一個隨機換色類

import randomclass Prompt(object): # 提示信息顯示colour_dic = {'red': 31,'green': 32,'yellow': 33,'blue': 34,'purple_red': 35,'bluish_blue': 36,'white': 37,}def __init__(self):pass@staticmethoddef display(msg, colour='white'):choice = Prompt.colour_dic.get(colour)# print(choice)if choice:info = "\033[1;{};1m{}\033[1;0m".format(choice, msg)return infoelse:return Falsedef random_color(msg): # 隨機換色colour_list = []for i in Prompt.colour_dic:colour_list.append(i)length = len(colour_list) - 1 # 最大索引值index = random.randint(0, length) # 隨機數ret = Prompt.display(msg, colour_list[index]) # 隨機顏色return ret

修改client.py

from Prompt import Promptdef processBar(num, total): # 進度條rate = num / totalrate_num = int(rate * 100)pretty = Prompt.random_color('?') # 隨機換色if rate_num == 100:r = '\r{}{}%\n'.format(pretty * int(rate_num / 5), rate_num,)else:r = '\r{}{}%'.format(pretty * int(rate_num / 5), rate_num,)sys.stdout.write(r)sys.stdout.flush

執行效果如下:

?

?增加MD5校驗? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

server.py

import os import json import socket import struct import hashlibsk = socket.socket() sk.bind(('127.0.0.1', 9000)) sk.listen()conn, addr = sk.accept() filename = '[電影天堂www.dy2018.com]移動迷宮3:死亡解藥BD國英雙語中英雙字.mp4' # 文件名 absolute_path = os.path.join('E:\BaiduYunDownload',filename) # 文件絕對路徑 buffer_size = 1024*1024 # 緩沖大小,這里表示1MBmd5obj = hashlib.md5() with open(absolute_path, 'rb') as f:while True:content = f.read(buffer_size) # 每次讀取指定字節if content:md5obj.update(content)else:break # 當內容為空時,終止循環md5 = md5obj.hexdigest() print(md5) # 打印md5值dic = {'filename':filename,'filename_md5':str(md5),'buffer_size':buffer_size,'filesize':os.path.getsize(absolute_path)} str_dic = json.dumps(dic).encode('utf-8') len_dic = len(str_dic) length = struct.pack('i', len_dic) conn.send(length) # dic的長度 conn.send(str_dic) # dic with open(absolute_path, 'rb') as f: # 文件while dic['filesize']:content = f.read(dic['buffer_size'])conn.send(content)dic['filesize'] -= len(content)'''這里不能減等4096,因為文件,最后可能只有3字節。要根據讀取的長度len(content),來計算才是合理的。''' conn.close()

client.py

import json import struct import socket import sys import time import hashlib import os from Prompt import Promptdef processBar(num, total): # 進度條rate = num / totalrate_num = int(rate * 100)pretty = Prompt.random_color('?')if rate_num == 100:r = '\r{}{}%\n'.format(pretty * int(rate_num / 5), rate_num,)else:r = '\r{}{}%'.format(pretty * int(rate_num / 5), rate_num,)sys.stdout.write(r)sys.stdout.flushstart_time = time.time() # 開始時間sk = socket.socket() sk.connect(('127.0.0.1',9000))dic_len = sk.recv(4) dic_len = struct.unpack('i',dic_len)[0] dic = sk.recv(dic_len) str_dic = dic.decode('utf-8') dic = json.loads(str_dic)md5 = hashlib.md5() with open(dic['filename'],'wb') as f: # 使用wb更嚴謹一些,雖然可以使用abcontent_size = 0while True:content = sk.recv(dic['buffer_size']) # 接收指定大小f.write(content) # 寫入文件content_size += len(content) # 接收大小md5.update(content) # 摘要processBar(content_size,dic['filesize']) # 執行進度條函數if content_size == dic['filesize']:break # 當接收的總大小等于文件大小時,終止循環md5 = md5.hexdigest()print(md5) # 打印md5值if dic['filename_md5'] == str(md5):print(Prompt.display('md5校驗正確--下載成功','green'))else:print(Prompt.display('文件驗證失敗', 'red'))os.remove(dic['filename']) # 刪除文件sk.close() # 關閉連接end_time = time.time() # 結束時間 print('本次下載花費了{}秒'.format(end_time - start_time))

執行輸出:

?

?

二、socket的更多方法介紹? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

服務端套接字函數 s.bind() 綁定(主機,端口號)到套接字 s.listen() 開始TCP監聽 s.accept() 被動接受TCP客戶的連接,(阻塞式)等待連接的到來客戶端套接字函數 s.connect() 主動初始化TCP服務器連接 s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常公共用途的套接字函數 s.recv() 接收TCP數據 s.send() 發送TCP數據 s.sendall() 發送TCP數據 s.recvfrom() 接收UDP數據 s.sendto() 發送UDP數據 s.getpeername() 連接到當前套接字的遠端的地址 s.getsockname() 當前套接字的地址 s.getsockopt() 返回指定套接字的參數 s.setsockopt() 設置指定套接字的參數 s.close() 關閉套接字面向鎖的套接字方法 s.setblocking() 設置套接字的阻塞與非阻塞模式 s.settimeout() 設置阻塞套接字操作的超時時間 s.gettimeout() 得到阻塞套接字操作的超時時間面向文件的套接字的函數 s.fileno() 套接字的文件描述符 s.makefile() 創建一個與該套接字相關的文件 更多方法 官方文檔對socket模塊下的socket.send()和socket.sendall()解釋如下:socket.send(string[, flags]) Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data.send()的返回值是發送的字節數量,這個數量值可能小于要發送的string的字節數,也就是說可能無法發送string中所有的數據。如果有錯誤則會拋出異常。–socket.sendall(string[, flags]) Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Unlike send(), this method continues to send data from string until either all data has been sent or an error occurs. None is returned on success. On error, an exception is raised, and there is no way to determine how much data, if any, was successfully sent.嘗試發送string的所有數據,成功則返回None,失敗則拋出異常。故,下面兩段代碼是等價的:#sock.sendall('Hello world\n')#buffer = 'Hello world\n' #while buffer: # bytes = sock.send(buffer) # buffer = buffer[bytes:] send和sendall方法

?

三、驗證客戶端鏈接的合法性? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

?使用hashlib.md5 加密? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

為什么要隨機字符串,是為了防止客戶端的數據被竊取

生成隨機的bytes類型數據,它是解不出來的

import os print(os.urandom(32))

執行輸出:

b'PO\xca8\xc8\xf3\xa0\xb5,\xdd\xb8K \xa8D\x9cN"\x82\x03\x86g\x18e\xa7\x97\xa77\xb9\xa5VA'

?

server.py

?

import os import socket import hashlibsecret_key = '老衲洗頭用飄柔' # 加密keysk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() while True:try:conn,addr = sk.accept()random_bytes = os.urandom(32) # 隨即產生32個字節的字符串,返回bytesconn.send(random_bytes) # 發送隨機加密keymd5 = hashlib.md5(secret_key.encode('utf-8')) # 使用secret_key作為加密鹽md5.update(random_bytes) #得到MD5消息摘要ret = md5.hexdigest() #以16進制返回消息摘要,它是一個32位長度的字符串msg = conn.recv(1024).decode('utf-8') # 接收的信息解碼if msg == ret:print('是合法的客戶端') # 如果接收的摘要和本機計算的摘要一致,就說明是合法的else:conn.close() # 關閉連接finally: # 無論如何,都執行下面的代碼sk.close() # 關閉連接break

client.py

import socket import hashlib secret_key = '老衲洗頭用飄柔' # 加密key sk = socket.socket() sk.connect(('127.0.0.1',9000))urandom = sk.recv(32) # 接收32字節,也就是os.urandom的返回值 md5_obj = hashlib.md5(secret_key.encode('utf-8')) # 使用加密鹽加密 md5_obj.update(urandom) sk.send(md5_obj.hexdigest().encode('utf-8')) # 發送md5摘要 print('-----') sk.close() # 關閉連接

先執行server.py,再執行client.py

client輸出:-----

server輸出:是合法的客戶端

?

如果100客戶端,來連接呢?秘鑰都是通用的。
一般情況下,用在哪些場景呢?
比如公司級別,比如1臺機器,向100臺服務器獲取數據

假如黑客滲透到內網,得知到服務器IP地址。就可以做端口掃描,一臺計算機的端口范圍是0~65535

掃描6萬多次,就能知道了。

?使用hmac加密? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

hmac是專門來做客戶端合法性的

import hmac obj = hmac.new(key=b'secret_key',msg=b'100212002155') print(obj.hexdigest())

執行輸出:

27111d37764a2fe5bc79d297e7b54c35

客戶端也使用hmac,驗證一下,就可以了。

改造server和client

server.py

import os import socket import hmacsecret_key = '老衲洗頭用飄柔'.encode('utf-8') sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() while True:try:conn,addr = sk.accept()random_bytes = os.urandom(32)conn.send(random_bytes)obj = hmac.new(key=secret_key,msg=random_bytes)ret = obj.hexdigest()msg = conn.recv(1024).decode('utf-8')if msg == ret:print('是合法的客戶端')else:conn.close()finally:sk.close()break

client.py

import socket import hmacsecret_key = '老衲洗頭用飄柔'.encode('utf-8') sk = socket.socket() sk.connect(('127.0.0.1', 9000))urandom = sk.recv(32) hmac_obj = hmac.new(key=secret_key, msg=urandom) sk.send(hmac_obj.hexdigest().encode('utf-8')) print('-----') sk.close()

執行效果,同上

?

三、socketserver? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

?SocketServer內部使用 IO多路復用 以及 “多線程” 和 “多進程” ,從而實現并發處理多個客戶端請求的Socket服務端。即:每個客戶端請求連接到服務器時,Socket服務端都會在服務器是創建一個“線程”或者“進 程” 專門負責處理當前客戶端的所有請求。

它能實現多個客戶端,同時連接,它繼承了socket

ThreadingTCPServer實現的Soket服務器內部會為每個client創建一個 “線程”,該線程用來和客戶端進行交互。

使用ThreadingTCPServer:
創建一個繼承自 SocketServer.BaseRequestHandler 的類,必須繼承
類中必須定義一個名稱為 handle 的方法,必須重寫

解讀socketserver源碼 —— http://www.cnblogs.com/Eva-J/p/5081851.html?

看BaseRequestHandler 的源碼,它的hendle方法,是空的

def handle(self):pass

需要自己去實現

server.py

import socketserver class MyServer(socketserver.BaseRequestHandler):def handle(self):print(self.request)server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer) server.serve_forever()

client.py

import socket sk = socket.socket() sk.connect(('127.0.0.1',9000)) sk.close()

先執行server.py,再執行client.py

server輸出

E:/python_script/day30/socketserver/server.py
<socket.socket fd=348, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 65459)>

?

每連接一個client.就會觸發handle,輸出request
監聽,等待連接,接收,全由hendle完成了。

server.py

import socketserver class MyServer(socketserver.BaseRequestHandler):def handle(self):print(self.request)self.request.send(b'hello') # 跟所有的client打招呼print(self.request.recv(1024)) # 接收客戶端的信息server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer) server.serve_forever()

client.py

import socket sk = socket.socket() sk.connect(('127.0.0.1',9000)) print(sk.recv(1024)) inp = input('>>>').encode('utf-8') sk.send(inp) sk.close()

先執行server.py,再執行client.py

client輸出:

b'hello'
>>>hi

server輸出:

<socket.socket fd=316, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 49176)>
b'hi'

開多個客戶端,也可以執行

server能夠和多個client通信

?

連續發送? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

client連續發送:

import socket sk = socket.socket() sk.connect(('127.0.0.1',9000)) while True:print(sk.recv(1024))#inp = input('>>>').encode('utf-8')sk.send(b'hahaha') sk.close()

server連續接收:

import socketserver class MyServer(socketserver.BaseRequestHandler):def handle(self):while True:print(self.request)self.request.send(b'hello') # 跟所有的client打招呼print(self.request.recv(1024)) # 接收客戶端的信息server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer) server.serve_forever()

執行效果如下:

如果server端口重復,使用以下代碼:

# 設置allow_reuse_address允許服務器重用地址socketserver.TCPServer.allow_reuse_address = True

完整代碼如下:

import socketserver class MyServer(socketserver.BaseRequestHandler):def handle(self):while True:print(self.request) # 這里不能使用input,否則卡住了self.request.send(b'hello') # 跟所有的client打招呼print(self.request.recv(1024)) # 接收客戶端的信息 if __name__ == '__main__':socketserver.TCPServer.allow_reuse_address = Trueserver = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)server.serve_forever()

  

?明日默寫:

import socketserver class MyServer(socketserver.BaseRequestHandler):def handle(self):self.request.send(b'hello')msg = self.request.recv(1024)print(msg)if __name__ == '__main__':socketserver.TCPServer.allow_reuse_address = Trueserver = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)server.serve_forever()

  

轉載于:https://www.cnblogs.com/xiao987334176/p/9008331.html

總結

以上是生活随笔為你收集整理的python 全栈开发,Day36(作业讲解(大文件下载以及进度条展示),socket的更多方法介绍,验证客户端链接的合法性hmac,socketserver)...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。