HTTP協議?
HTTP是一個應用層協議,由請求和響應構成,是一個標准的客戶端服務器模型。HTTP是一個無狀態的協議。
通常承載於TCP協議之上,有時也承載於TLS或SSL協議層之上,這個時候,就成了我們常說的HTTPS
默認HTTP的端口號為80,HTTPS的端口號為443。
what? 無狀態什么鬼?
HTTP無狀態協議是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果后續處理需要前面的信息,
則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快
由於web等客戶端與服務器交互的應用程序出現后HTTP的無狀態嚴重阻礙了這些應用的實現效率 說以就產生了cookie和Session
cookie:
當用戶使用瀏覽器訪問一個支持Cookie的網站的時候,用戶會提供包括用戶名在內的個人信息並且提交至服務器;
接着,服務器在向客戶端回傳相應的超文本的同時也會發回這些個人信息,當然這些信息並不是存放在HTTP響應體(Response Body)中的,
而是存放於HTTP響應頭(Response Header);當客戶端瀏覽器接收到來自服務器的響應之后,
瀏覽器會將這些信息存放在一個統一的位置,對於Windows操作系統而言,
我們可以從: [系統盤]:\Documents and Settings\[用戶名]\Cookies目錄中找到存儲的Cookie;自此,客戶端再向服務器發送請求的時候,
都會把相應的Cookie再次發回至服務器。而這次,Cookie信息則存放在HTTP請求頭(Request Header)了。
Session :
所謂session就是指客戶端與服務端之間的一種交互過程的狀態信息(數據) 這個狀態的定界以及生命期是應用本身的事情
當一個用戶向服務器發送第一個請求時,服務器為其建立一個session 並且會給這個session創建一個標識號
這個用戶隨后的請求都應該包括這個標識好服務器會對這個標識判斷請求屬於哪一個session
這種機制不使用IP作為標識,因為很多機器是代理服務器上網,無法區分主機 可以用cookie和URL重寫來實現session標識號(sessionID)
URL只是一個統稱 實際上是URI包含URL和URN由於URN用的非常少 幾乎說有的URI都是URL所以人們更喜歡叫URL
os.listdir(path)
獲取文件
列表
os.path.isfile() :
判斷一個 文件是否為
普通文件
os.path.isdir() :
判斷一個文件是否為
目錄
TFTP 文件服務器
項目功能 :
* 客戶端有簡單的頁面命令提示
* 功能包含:
1. 查看服務器文件庫中的文件列表(普通文件)
2. 可以下載其中的某個文件到本地
3. 可以上傳客戶端文件到服務器文件庫
* 服務器需求 :
1. 允許多個客戶端同時操作
2.每個客戶端可能回連續發送命令
技術分析:
1. tcp套接字更適合文件傳輸
2. 並發方案 ---》 fork 多進程並發
3. 對文件的讀寫操作
4. 獲取文件列表 ----》 os.listdir()
粘包的處理
整體結構設計
1. 服務器功能封裝在類中(上傳,下載,查看列表)
2. 創建套接字,流程函數調用 main()
3. 客戶端負責發起請求,接受回復,展示
服務端負責接受請求,邏輯處理
編程實現
1. 搭建整體結構,創建網絡連接
2. 創建多進程和類的結構
3. 每個功能模塊的實現
服務器端:
from socket import * import os import signal import sys import time # 文件庫 FILE_PATH = "/home/tarena/" # 實現功能模塊 class TftpServer(object): def __init__(self,connfd): self.connfd = connfd # 查詢 def do_list(self): # 獲取列表 file_list = os.listdir(FILE_PATH) if not file_list: self.connfd.send("文件庫為空".encode()) # 服務器目錄無文件 return else: # 有文件 self.connfd.send(b'OK') time.sleep(0.1) files = "" for file in file_list: # 發送所有普通文件的文件名並且不是隱藏文件 if os.path.isfile(FILE_PATH+file) and file[0] != '.': # 文件名間隔符 用於客戶端解析 files = files + file + '#' # 一次全部發送 簡單粗暴 self.connfd.send(files.encode()) # 下載 def do_get(self,filename): # 判斷文件是否純在 try: fd = open(FILE_PATH + filename,'rb') except: self.connfd.send("文件不存在".encode()) return self.connfd.send(b'OK') time.sleep(0.1) # 發送文件 try: while True: data = fd.read(1024) if not data: break self.connfd.send(data) except Exception as e: print(e) time.sleep(0.1) self.connfd.send(b'##') # 表示文件發送完成 print("文件發送完畢") # 上傳 def do_put(self,filename): # 限制文件命重復導致覆蓋源文件 try: fd = open(FILE_PATH+filename,'xb') except: self.connfd.send("無法上傳".encode()) return except FileExistsError: self.connfd.send("文件已存在".encode()) return self.connfd.send(b'OK') # 上傳文件 while True: data = self.connfd.recv(1024) if data == b'##': break fd.write(data) fd.close() print("文件上傳完畢") # 流程控制,創建套接字,創建並發,方法調用 def main(): HOST = '0.0.0.0' PORT = 8888 ADDR = (HOST,PORT) # 創建套接字 sockfd = socket() sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sockfd.bind(ADDR) sockfd.listen(5) # 忽略子進程退出 signal.signal(signal.SIGCHLD, signal.SIG_IGN) # 循環等待客戶端鏈接 while True: try: connfd, addr = sockfd.accept() except KeyboardInterrupt: sockfd.close() sys.exit("服務器退出") except Exception as e: print(e) continue print("客戶端登錄:",addr) # 創建父子進程 pid = os.fork() # 進入子進程 if pid == 0: # 關閉子進程內無用套接字 sockfd.close() tftp = TftpServer(connfd) # __init__傳參 while True: data = connfd.recv(1024).decode() # 斷開連接 if (not data) or data[0] == 'Q': print("客戶端退出") sys.exit(0) elif data[0] == "L": # 申請查詢 tftp.do_list() elif data[0] == 'G': # 解析文件名 filename = data.split(' ')[-1] # 申請下載 tftp.do_get(filename) elif data[0] == 'P': filename = data.split(' ')[-1] # 申請上傳 tftp.do_put(filename) else: print("客戶端發送錯誤指令") else: # 關閉父進程內無用套接字 connfd.close() # 父進程只用來做客戶端鏈接 continue if __name__ == "__main__": main()
客戶端:
from socket import * import sys import time # 實現各種功能請求 class TftpClient(object): def __init__(self,sockfd): self.sockfd = sockfd def do_list(self): self.sockfd.send(b'L') # 發送請求類型 # 接收服務器回應 data = self.sockfd.recv(1024).decode() if data == "OK": data = self.sockfd.recv(4096).decode() files = data.split('#') for file in files: print(file) print("文件展示完畢") else: # 請求失敗原因 print(data) # 下載指定文件 def do_get(self,filename): self.sockfd.send(('G ' + filename).encode()) data = self.sockfd.recv(1024).decode() # 請求成功 if data == 'OK': fd = open(filename,'wb') while True: data = self.sockfd.recv(1024) # 結束符 if data == b'##': break fd.write(data) fd.close() print("%s 下載完成\n"%filename) else: # 請求失敗原因 print(data) def do_put(self,filename): # 判斷本地是否有要上傳的文件 try: fd = open(filename,'rb') except: print("上傳文件不存在") return self.sockfd.send(("P "+filename).encode()) data = self.sockfd.recv(1024).decode() # 請求成功 if data == 'OK': while True: data = fd.read(1024) if not data: break self.sockfd.send(data) fd.close() # 發送結束符並防止粘包 time.sleep(0.1) self.sockfd.send(b'##') print("%s 上傳完畢"%filename) else: # 請求失敗原因 print(data) # 創建套接字並建立連接 def main(): # 終端輸入地址 if len(sys.argv) < 3: print("argv is error") return HOST = sys.argv[1] PORT = int(sys.argv[2]) ADDR = (HOST,PORT) sockfd = socket() sockfd.connect(ADDR) # 創建對象 tftp = TftpClient(sockfd) while True: print("") print("==========命令選項===========") print("********** list *********") print("********** get file ******") print("********** put file ******") print("********** quit *********") print("=============================") cmd = input("輸入命令>>") # 去除空格判斷命令 if cmd.strip() == "list": # 查詢 tftp.do_list() # 獲取文件上傳或下載命令 elif cmd[:3] == "get": # 拆分命令獲取文件名 filename = cmd.split(' ')[-1] # 下載 tftp.do_get(filename) elif cmd[:3] == "put": filename = cmd.split(' ')[-1] # 上傳 tftp.do_put(filename) # 退出 elif cmd.strip() == "quit": sockfd.send(b'Q') sockfd.close() sys.exit("歡迎使用") else: print("請輸入正確命令!") if __name__ == "__main__": main() 多線程並發 threading模塊完成多線程並發 對比多進程並發 優勢 : 資源消耗少 缺點 : 需要注意對共享資源的操作 實現步驟: 1. 創建套接字,綁定,監聽 2. 接收客戶端連接請求,創建新的線程 3. 主線程繼續等待其他客戶端連接,分支線程執行客戶端具體請求 4. 處理完客戶端請求后分支線程自然退出,關閉客戶端套接字 示例: from socket import * import os,sys from threading import * HOST = "0.0.0.0" PORT = 8888 ADDR = (HOST,PORT) #客戶端處理函數 def handler(connfd): print("Connect from",connfd.getpeername()) while True: data = connfd.recv(1024).decode() if not data: break print(data) connfd.send(b'Receive your msg') connfd.close() def main(ADDR): s = socket() s.bind(ADDR) s.listen(5) while True: try: connfd,addr = s.accept() # 處理 Ctrl + C except KeyboardInterrupt: s.close() sys.exit("服務器退出") # 其他異常 except Exception as e: print(e) continue # 創建子線程用於處理客戶端請求 t = Thread(target=handler,args= (connfd,)) t.setDaemon(True) t.start() if __name__ == __main__: main()
socket並發集成模塊
python2 SocketServer
python3
socketserver
功能 :
通過模塊提供的接口組合可以完成多進程/多線程 tcp/udp的 並發程序
StreamRequestHandler 處理tcp請求
DatagramRequestHandler 處理udp請求
ForkingMixIn 創建多進程
ThreadingMixIn 創建多線程
TCPServer 創建tcp server
UDPServer 創建udp server
ForkingTCPServer 等於 ForkingMixIn + TCPServer
ForkingUDPServer 等於 ForkingMixIn + UDPServer
ThreadingTCPServer 等於 ThreadingMixIn + TCPServer
ThreadingUDPServer 等於 ThreadingMixIn + UDPServer
示例:
#多進程 tcp server from socketserver import * #創建server類 # class Server(ForkingMixIn,TCPServer): # class Server(ForkingTCPServer): # pass #多線程tcp並發 class Server(ThreadingTCPServer): pass #具體的請求處理類 class Handler(StreamRequestHandler): def handle(self): # self.request ==> accept返回的套接字 print("Connect from",self.request.getpeername()) while True: data = self.request.recv(1024).decode() if not data: break print(data) self.request.send(b'Receive') if __name__ == __main__: #創建server對象 server = Server(("0.0.0.0",8888),Handler) #啟動服務器 server.serve_forever()
基於多線程並發的HTTPServer
1. 接收瀏覽器http請求
2. 對請求進行一定的解析
3. 根據解析結果返回對應內容
4. 如果沒有請求內容則返回404
5. 組織Response格式進行回發
升級:
* 使用多線程並發
* 增加了具體的請求解析和404情況
* 使用類進行代碼封裝
* 增加一定的數據獲取功能
技術點 : threading並發
tcp socket 傳輸
HTTP請求和響應格式
相比上次升級了一點點
from socket import * from threading import Thread import time # 存放靜態頁面的目錄 STATIC_DIR = "./static" ADDR = ('0.0.0.0', 8000) # HTTPServer類,封裝具體功能 class HTTPServer(object): def __init__(self, address): # 創建套接字 self.sockfd = socket() # 設置端口重用 self.sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.sockfd.bind(address) self.sockfd.listen(5) # 為對象增加屬性變量 self.name = "HTTPServer" self.port = address[1] self.address = address # 啟動服務器 def serve_forever(self): print("Listen the port %d"%self.port) while True: # 循環接收客戶端請求並創建新的套接字 connfd, addr = self.sockfd.accept() # 創建線程並運行處理具體請求 clientThread = Thread(target = self.handleRequest,args = (connfd,)) # 主線程結束時結束線程 clientThread.setDaemon(True) clientThread.start() def handleRequest(self, connfd): # 接收客戶端請求 request = connfd.recv(4096) # 按行切割 字符串 requestHeadlers = request.splitlines() # 獲取請求行 print(connfd.getpeername(), ":" , requestHeadlers[0]) # 獲取請求內容並解析 getRequest = str(requestHeadlers[0]).split(' ')[1] # 並判斷請求類型 if getRequest == '/' or getRequest[-5:] == '.html': # 請求行為網頁請求 data = self.get_html(getRequest) else: # 請求指定數據內容 data = self.get_data(getRequest) # 響應請求並返還內容 connfd.send(data.encode()) connfd.close() # 用於處理網頁請求 def get_html(self,page): # 判斷是否為主頁請求 if page == "/": filename = STATIC_DIR + "/index.html" else: filename = STATIC_DIR + page try: f = open(filename) except Exception: # 沒有找到頁面 responseHeadlers = "HTTP/1.1 404 Not Found\r\n" responseHeadlers += "Content-Type: text/html\r\n" responseHeadlers += '\r\n' responseBody = "<h1>Sorry,not found the page</h1>" else: responseHeadlers = "HTTP/1.1 200 OK\r\n" responseHeadlers += "Content-Type: text/html\r\n" responseHeadlers += '\r\n' for i in f: responseBody += i # 頁面存不存在否響應 finally: return responseHeadlers + responseBody # 用於處理數據內容請求 def get_data(self,data): responseHeadlers = "HTTP/1.1 200 OK\r\n" responseHeadlers += "\r\n" if data == "/time": responseBody = time.ctime() elif data == "/ParisGabriel": responseBody = "Welcome to ParisGabriel" else: responseBody = "The data not found" return responseHeadlers + responseBody if __name__ == "__main__": # 生成服務器對象 httpd = HTTPServer(ADDR) # 啟動服務器 httpd.serve_forever()