网络编程补充
網絡編程補充
- 1.認識
- 2.Socket網絡編程
- 3.TCP通訊
- 4.echo模型
- 5.UDP
- 6.UDP廣播
- 7.HTTP
- 8.HTTP響應
- 9.建立響應目錄
- 10.動態請求處理
- 11.urllib3
- 12.twisted模塊認識
- 13.twisted開發TCP程序
- 14.使用twisted開發UDP程序
- 15.Deferred
1.認識
| 1 | 應用層 | 提供網絡服務操作接口 |
| 2 | 表示層 | 對要傳輸的數據進行處理,例如:數據編碼 |
| 3 | 會話層 | 管理不同的通訊節點之間的連接信息 |
| 4 | 傳輸層 | 建立不同節點之間的網絡連接,為數據追加段信息 |
| 5 | 網絡層 | 將網絡地址映射為mac地址實現數據包轉發,為數據追加包信息 |
| 6 | 數據鏈路層 | 將要發送的數據包轉換為數據幀,是其在不可靠的物理鏈路上進行可靠的數據傳輸,為數據追加幀信息 |
| 7 | 物理層 | 利用物理設備實現數據的傳輸(二進制數據傳輸) |
2.Socket網絡編程
Socket(套接字),是一種對TCP/UDP 網絡協議進行的一種包裝(或者稱為協議的一種抽象應用),本身的特點提供了不同進程之間的數據通訊操作
- TCP(傳輸控制協議)
- 采用有狀態的通訊機制進行傳輸,在通訊時會通過三次握手機制保證與一個指定節點的數據傳輸的可靠性,在通訊完畢會通過四次揮手的機制關閉連接,由于每次數據的通訊前都需要消耗大量的時間進行連接控制,所以執行性能低,且資源占用較大
- UDP(數據報協議 /用戶數據報協議)
- 采用無狀態的通訊機制進行傳輸。沒有了TCP中復雜的握手與揮手處理機制,這樣就節約了大量的系統資源,同時數據傳輸性能較高,但由于不保存單個節點的連接狀態,所以發送的數據不一定可以被全部接受。
- UDP不需要鏈接就可以直接發送數據,并且多個接受端都可以同時接受同樣的信息,所以UDP適合于廣播操作
不論是TCP還是UDP協議,都是對傳輸層操作的保證,數據按照OSI 七層模型來說 一定要通過網絡層進行路由的配置,同時利用數據鏈路層添加數據幀,最終利用物理層發出,但是由于Socket機制的存在,所以開發者只需要編寫處理的核心代碼,而具體的傳輸,協議操作就完全被包裝了
3.TCP通訊
TCP是面向連接的網絡傳輸協議,在進行TCP通訊的過程中其安全性以及穩定性都是最高的,雖然性能會差些,但是對于當前網絡環境來講主要還是使用TCP協議的居多
python中使用socket.socket類即可實現TCP程序開發:
| 1 | socket() | 構造 | 獲取socket類對象 |
| 2 | bind(hostname,port) | 方法 | 在指定主機的端口綁定監聽 |
| 3 | listen() | 方法 | 在綁定的端口上開啟監聽 |
| 4 | accept() | 方法 | 等待客戶端連接,連接后返回客戶端地址 |
| 5 | send(data) | 方法 | 發送數據 |
| 6 | recv(buffer) | 方法 | 接收數據 |
| 7 | close() | 方法 | 關閉套接字連接 |
| 8 | connect(hostname,port) | 方法 | 設置連接的主機名稱與端口號 |
整個Socket網絡編程之中基本的核心流程就是服務端開啟監聽端口,等待客戶端連接,而客戶端想要訪問服務器就必須進行服務器的地址連接,而后進行響應的數據的請求或響應內容的接收
#-------------這是服務端-------- import socket #服務端的地址端口 SERVER_HOST='localhost' SERVER_PORT=8080 def main():#socket網絡服務每一次處理完成之后一定要使用close()關閉,所以使用with結構定義with socket.socket() as server_socket:#創建服務端Socketserver_socket.bind((SERVER_HOST,SERVER_PORT))#綁定服務端主機與端口server_socket.listen()#開啟監聽print('[服務端]服務端啟動完成,在%s端口上監聽等待客戶端連接。。。'%SERVER_PORT)new_socket,iport=server_socket.accept()#等待客戶端連接,處于阻塞狀態new_socket.send('你好,這里是服務端!'.encode())print(iport,'連接成功! 響應:',new_socket.recv(128).decode()) if __name__ == '__main__':main() #------------這是客戶端---------- import socket SERVER_HOST='127.0.0.1'#要連接的服務端的主機名稱或ip地址 SERVER_PORT=8080 def main():with socket.socket() as client_socket:#建立客戶端socketclient_socket.connect((SERVER_HOST,SERVER_PORT))#連接服務器print('服務端響應數據:%s'%client_socket.recv(128).decode())#接收數據長度為128字client_socket.send('你好,這里是客戶端!'.encode())#發送消息 if __name__ == '__main__':main()4.echo模型
echo程序模型來源于echo命令,在操作系統內部提供一個echo命令進行內容的回顯
echo指令 輸入什么–返回什么
將echo的概念擴大到網絡環境中,就可以理解為客戶端輸入一組數據發送到服務端,那么服務端接受之后對該數據進行響應,這種模型就是網絡編程echo模型
在整個網絡編程中,由于所有網絡程序一定要有一個綁定的端口號存在,所以一個端口只允許綁定一個服務,如果出現端口被占用的情況,那么程序將無法正常啟動
對于當前服務端程序如果想要測試,簡單可以直接通過telnet命令來完成,每當用戶輸入一個內容之后就會立即將此內容發送到服務端,但window命令行采用的GBK編碼,會造成亂碼,但是可以測試服務端是正確的
#-------------這是服務端-------- import socket #服務端的地址端口 SERVER_HOST='localhost' SERVER_PORT=8080 def main():#socket網絡服務每一次處理完成之后一定要使用close()關閉,所以使用with結構定義with socket.socket() as server_socket:#創建服務端Socketserver_socket.bind((SERVER_HOST,SERVER_PORT))#綁定服務端主機與端口server_socket.listen()#開啟監聽print('[服務端]服務端啟動完成,在%s端口上監聽等待客戶端連接。。。'%SERVER_PORT)cli_socket,iport=server_socket.accept()#等待客戶端連接,處于阻塞狀態with cli_socket:#進行客戶端的處理while True:#不斷進行信息的接收與響應data = cli_socket.recv(128).decode() # 接收客戶端傳過來的數據if data.upper()=='BYEBYE':#客戶端輸入此指令cli_socket.send('exit'.encode())break#結束循環else:#進行正常的響應cli_socket.send(('echo %s'%data).encode())#向客戶端進行去請求響應 if __name__ == '__main__':main() #------------這是客戶端---------- import socket SERVER_HOST='127.0.0.1'#要連接的服務端的主機名稱或ip地址 SERVER_PORT=8080 def main():with socket.socket() as client_socket:#建立客戶端socketclient_socket.connect((SERVER_HOST,SERVER_PORT))#連接服務器while True:#客戶端要不斷與服務端交互input_data=input('請輸入要發送的數據(輸入byebye結束):')client_socket.send(input_data.encode())#數據發送echo_data=client_socket.recv(100).decode()if echo_data.upper()=='EXIT':#結束break#斷開連接else:print(echo_data)#輸出服務端響應內容 if __name__ == '__main__':main()當服務器端引入并發編程的概念之后,那么就可以同時進行多個客戶端的請求處理,在開發行業內有一個“高并發”指的就是連接客戶端比較多,所以這個時候如何處理好服務端處理性能就成為項目設計的關鍵
#-------------這是服務端-------- import multiprocessing import socket #服務端的地址端口 SERVER_HOST='localhost' SERVER_PORT=8080 def echo_handle(cli_socket,iport):#進程處理函數print('[服務端],在%s端口上監聽客戶端。。。' % iport[1])with cli_socket: # 進行客戶端的處理while True: # 不斷進行信息的接收與響應data = cli_socket.recv(128).decode() # 接收客戶端傳過來的數據if data.upper() == 'BYEBYE': # 客戶端輸入此指令cli_socket.send('exit'.encode())break # 結束循環else: # 進行正常的響應cli_socket.send(('echo %s' % data).encode()) # 向客戶端進行去請求響應 def main():#socket網絡服務每一次處理完成之后一定要使用close()關閉,所以使用with結構定義with socket.socket() as server_socket:#創建服務端Socketserver_socket.bind((SERVER_HOST,SERVER_PORT))#綁定服務端主機與端口server_socket.listen()#開啟監聽while True:#不斷接受請求print('[服務端]服務端啟動完成,在%s端口上監聽等待客戶端連接。。。' % SERVER_PORT)cli_socket, iport = server_socket.accept() # 等待客戶端連接,處于阻塞狀態process=multiprocessing.Process(target=echo_handle,args=(cli_socket,iport),name='客戶端進程-%s'%iport[1])#定義進程process.start()#啟動進程 if __name__ == '__main__':main()5.UDP
UDP也是網絡傳輸層上的一種協議,但與TCP相比,UDP本身采用的是不安全的連接,所以來講每一次通過UDP發送對1數據不一定可以接收到,但是由于其性能比較好,所以未來會有廣闊的發展前景
在Python中對于TCP/UDP本身的實現結構差別不大,都是通過socket.socket類完成的,只需要設置一些參數即可將其設為UDP(數據報協議)
UDP與TCP服務端最大的區別是不再需要過多的考慮到數據穩定性的連接問題了,所以也不再設置有具體的監聽操作,在每次接收到請求之后只需要獲取客戶端的原始地址,直接根據原路返回即可
#-------------這是服務端-------- import socket #服務端的地址端口 SERVER_HOST='localhost' SERVER_PORT=8080 def main():#socket網絡服務每一次處理完成之后一定要使用close()關閉,所以使用with結構定義#socket.AF_INET ip4網絡協議進行服務端創建#socket.SOCK_DGRAM創建一個數據報協議的服務端(UDP)with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as server_socket:#創建服務端Socketserver_socket.bind((SERVER_HOST,SERVER_PORT))#綁定服務端主機與端口print('[服務端]服務端啟動完成,在%s端口上監聽等待客戶端連接。。。'%SERVER_PORT)while True:#不斷進行接收data,iport=server_socket.recvfrom(30)#接收客戶端發送的數據print(iport, '連接成功! 響應:')echo_data=('echo %s'%data.decode()).encode()#響應數據 從哪來會那去server_socket.sendto(echo_data,iport)#將內容響應到發送端上if __name__ == '__main__':main() #------------這是客戶端---------- import socket SERVER_HOST='127.0.0.1'#要連接的服務端的主機名稱或ip地址 SERVER_PORT=8080 def main():with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as client_socket:#建立客戶端socketwhile True:#客戶端要不斷與服務端交互input_data=input('請輸入要發送的數據(輸入byebye結束):')client_socket.sendto(input_data.encode(),(SERVER_HOST,SERVER_PORT))#數據發送if input_data:#如果有數據echo_data=client_socket.recv(100).decode()#響應數據print('服務端響應數據: %s'%echo_data)#輸出內容else:#沒有數據 (直接回車表示程序結束)break #退出交互if __name__ == '__main__':main()6.UDP廣播
使用UDP除了可以建立快速的網絡通訊之外,實際還有一個主要的功能就是實現數據廣播的操作,它可以實現一個局域網內的所有主機信息的廣播處理,要實現UDP廣播操作,則一定要在程序之中使用如下的方法進行定義:
setsockopt(self,level:int,optname:int,value:Union[int,bytes]) #level:設置選項所在的協議層編號,有如下四個可用的配置項 #socket.SOL_SOCKET:基本套接字接口 #socket.IPPROTO_IP:IP4套接字接口 #socket.IPPROTO_IPV6:IPv6套接字接口 #socket.IPPROTO_TCP:TCP套接字接口 #optname:設置選項名稱,例如,如果要進行廣播則可以使用 socket.BROADCAST #value:設置選項的具體內容如果要進行廣播肯定要有廣播的接收端,而接收端不一定可以接收到廣播,但只要打開接收端就可以接收到廣播
#------------這是廣播接收端---------- import socket BROADCAST_CLIENT_ADDR=('0.0.0.0',21567)#客戶端的綁定地址 當前主機 SERVER_HOST='127.0.0.1'#要連接的服務端的主機名稱或ip地址 SERVER_PORT=8080 def main():with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as client_socket:#建立客戶端socketclient_socket.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)#設置廣播模式client_socket.bind(BROADCAST_CLIENT_ADDR)#綁定廣播客戶端地址while True:#不斷進行接收message,iport=client_socket.recvfrom(100)#接收廣播信息print('接收的消息內容為%s,消息來源%s,消息端口%s'%(message.decode(),iport[0],iport[1]))if __name__ == '__main__':main()當客戶端執行后就持續等待服務端消息的發送,就跟所有手機一樣,如果手機沒有待機的狀態輪詢服務器,那么就不可能接聽電話或者短息。
#-------------這是廣播發送端-------- import socket BROADCAST_SERVER_ADDR=('<broadcast>',21567)#設置廣播地址 def main():#socket網絡服務每一次處理完成之后一定要使用close()關閉,所以使用with結構定義#socket.AF_INET ip4網絡協議進行服務端創建#socket.SOCK_DGRAM創建一個數據報協議的服務端(UDP)with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as server_socket:#創建服務端Socketserver_socket.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)#設置廣播模式server_socket.sendto('這是廣播發送端'.encode(),BROADCAST_SERVER_ADDR) if __name__ == '__main__':main()在進行廣播處理的時候只需要設置一個《 broadcast》地址就可以實現廣播的處理,而對于接收端而言則不能保證信息可以正常接收
7.HTTP
在標準的網絡通信之中使用的是socket編程,而socket編程是對TCP/UDP協議進行抽象實現,在整個實現之中,可以清楚的發現,幾乎不需要過多的考慮TCP/UDP實現細節,而后完全基于socket就可以非常簡單的實現了
但是socket編程本身會存在一個問題,就是必須提供兩個程序端:客戶端/服務端,服務端是整個網絡編程的核心所在,但是如果每一次服務端的升級都需要進行客戶端的強制更新,那么這種做法就會顯得非常麻煩了,所以在傳統網絡編程的基礎上就形成了HTTP協議(是針對TCP協議的一種更高級的包裝,TCP協議本身存有性能問題,所以HTTP實現也可能產生更大的性能問題,所以未來可能在UDP協議基礎上實現HTTP協議)。
HTTP協議是一種應用在www萬維網上實現數據傳輸的一種數據交互協議。客戶端基于瀏覽器向服務器端發送HTTP服務請求,服務端會根據用戶的請求進行數據文件的加載,并將要回應的數據信息以HTML文件格式進行傳輸,當瀏覽器接收到此數據信息時就可以直接進行代碼的解析并將數據信息顯示給用戶瀏覽
在整個的HTTP開發流程之中,最為重要的設計就放在HTML代碼的編寫上,對于WEB服務器開發者而言更為重要是清楚HTTP服務器的開發
雖然HTTP是基于TCP協議基礎之上開發的新協議,但其本質并沒有脫離傳統的TCP協議(可靠連接,數據交互),隨后在TCP協議的基礎上擴充了HTTP自己的內容,就成為了新的協議,而這些內容實際上都是隨著每一次請求和響應的頭部信息來進行發送的
在整個HTTP請求和響應的處理過程中,核心問題就在于:請求和響應的頭部信息有哪些,響應狀態碼(HTTP服務請求之后的狀態碼是確定響應能否正確執行的關鍵部分)
在HTTP協議之中,為了便于用戶的請求,所以設計有多種請求模式(比較常見的就是get/post),對于一些流行的Restful設計的結構,有可能會進行這些不同模式的請求區分,隨著HTTP版本的不斷提升,請求的模式也在不斷的增加
| 1 | GET | 請求指定的頁面信息,并返回實體主體 |
| 2 | HEAD | 類似于get請求,只不過返回的響應中沒有具體的內容,用于獲取請求頭部數據 |
| 3 | POST | 向指定的資源提交數據進行處理請求(例如提交表單或上傳文件) |
| 4 | PUT | 從客戶端向服務器傳輸數據取代替指定文檔的內容 |
| 5 | DELETE | 請求服務器刪除指定的頁面 |
| 6 | CONNECT | HTTP/1.1協議中預留給能夠將連接改為管道方式的代理服務器 |
| 7 | OPTIONS | 允許客戶端查看服務器的性能 |
| 8 | TRACE | 回顯服務器接收到的請求,主要用于測試或診斷 |
在每一次客戶端發送HTTP請求的時候除了真實的內容之外,還會包含有許多的頭部信息
| 1 | Accept | 設置客戶端顯示類型 | Accept: text/html,application |
| 2 | Accept-Encoding | 設置瀏覽器可以支持的壓縮編碼類型 | Accept-Encoding: gzip, deflate, br |
| 3 | Accept-Language | 瀏覽器可接受的語言 | Accept-Language: zh-CN,zh;q=0.9 |
| 4 | Cookie | 將客戶端保存的數據發送到服務器 | Cookie:name=lsf |
| 5 | Content-Length | 請求內容的長度 | Content-Length:348 |
| 6 | Content-Type | 請求與實體對應的MIME信息 | |
| 7 | HOST | 請求主機 | HOST:www.baidu.com |
| 8 | Referer | 訪問來路 | Referer:https://www.baidu.com.html |
服務器能否正常運行,還有一個關鍵性的問題,就是服務器端對于請求的響應編碼回應
| 1** | 信息,服務器接收到請求,需要請求者繼續執行操作 |
| 2** | 成功,操作被成功接收并處理 |
| 3** | 重定向,需要進一步的操作以完成請求 |
| 4** | 客戶端錯誤,請求包含語法錯誤或無法完成請求 |
| 5** | 服務器錯誤,服務器在處理請求的過程中發送了錯誤 |
在每一次HTTP服務器響應的時候實際也存在各種頭信息,這些頭信息實際上就是告訴瀏覽器該如何解釋代碼
| 1 | Content-Encoding | 返回壓縮編碼類型 |
| 2 | Content-Language | 響應內容支持的語言 |
| 3 | Content-Length | 響應內容的長度 |
| 4 | Content-Type | 響應數據的MIME類型 |
| 5 | Last-Modified | 請求資源的最后修改時間 |
| 6 | Location | 重定向路徑 |
| 7 | refersh | 資源定時刷新配置 |
| 8 | Server | web服務器軟件名稱 |
| 9 | Set-Cookie | 設置Http C ookie |
8.HTTP響應
在HTTP編程之中核心的本質依舊是進行請求和響應,只不過這個響應處理數據之外還需包含頭信息,這些內容一定要被瀏覽器進行解析,瀏覽器在進行請求的時候需要依據服務器的主機名稱和訪問端口進行請求的發送,
所有的HTTP服務器一定要通過瀏覽器進行訪問,服務器綁定在本機的80端口上,那么就可直接進行本地服務訪問,瀏覽器輸入:http://localhost
9.建立響應目錄
如果html代碼以字符串的形式出現在整個Python程序里面,那么這樣的HTML代碼是很難被前端進行維護的,前端需要的是一個可以進行響應的處理目錄,相當于建立一個專屬的html響應代碼目錄,而后所有要響應的內容都要保存在此目錄之中
#---------------http基礎服務端--------- import socket #http是基于TCP協議,所以一定使用socket import re import os #進行文件路徑的定義 #os.getcwd() 當前文件的根目錄 os.sep \ HTML_ROOT_DIR=os.getcwd()+os.sep#響應目錄 import multiprocessing #考慮到性能問題,為每一次請求開啟一個新的進程 class HttpServer:'''服務器的程序類'''def __init__(self,port):#服務器要有一個監聽的端口self.server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#創建socket實例#考慮到不同系統的問題,80端口是一個必爭端口,該端口屬于系統的核心端口,所以將核心任務與核心端口綁定self.server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)self.server_socket.bind(('0.0.0.0',port))#綁定核心端口self.server_socket.listen()#啟動監聽def start(self):'''服務器開始提供服務'''while True:#持續提供服務cli_socket,iport=self.server_socket.accept()#接收客戶端請求print('新的客戶端連接,客戶端ip:%s,客戶端端口:%s'%(iport[0],iport[1]))#輸出客戶端信息#將客戶端都設置為一個獨立的進程存在 都分別進行請求的回應handle_cli_process=multiprocessing.Process(target=self.handle_response,args=(cli_socket,))handle_cli_process.start()#進程啟動def handle_response(self,cli_socket):'''對每一個指定的客戶端進行響應'''request_headers=cli_socket.recv(1024)#用戶通過瀏覽器發送的請求本身就攜帶頭信息#使用正則提取請求頭信息file_name=re.match(r'\w+ +(/[^ ]*)',request_headers.decode().split('\r\n')[0]).group(1)# file_name=request_headers.decode().split(' ',2)[1]if file_name=='/':file_name= 'wenjian/index.html' #為根目錄if file_name.endswith('.wenjian') or file_name.endswith('.htm'):cli_socket.send(self.get_html_data(file_name).encode())#服務端響應else:#二進制圖表內容cli_socket.send(self.get_binary_data(file_name))#響應二進制數據cli_socket.close()#HTTP不保留用戶狀態,所以每次處理后都斷開連接,否則會造成性能開支,且這些開支是無意義的def read_file(self,file_name):#讀文件數據file_path=os.path.normpath(HTML_ROOT_DIR+file_name)#文件的完整路徑file=open(file_path,'rb')#采用二進制流的形式讀取file_data=file.read()#讀取文件內容file.close()return file_data #返回讀取的數據def get_binary_data(self,file_name):#二進制文件的讀取response_body=self.read_file(file_name)return response_bodydef get_html_data(self,file_name):#讀取指定文件response_start_line = 'HTTP/2 200 OK\r\n'# 響應頭response_headers = 'Server PWS/2.0\r\n' # 可以添加更多的響應頭信息response_body =self.read_file(file_name).decode()#設置響應內容response = response_start_line + response_headers + '\r\n' + response_body # 最終的響應內容return response def main():http_server=HttpServer(80)#80為服務器的默認端口,可以不用輸入,直接輸入域名即可http_server.start()#開啟服務 if __name__ == '__main__':main()10.動態請求處理
對于web開發來說,分為兩個處理階段:靜態web處理,動態web處理,在之前設置的響應目錄實際上就是屬于靜態web處理,而動態web是可以進行動態的判斷來決定最終返回的數據內容。
#---------------http基礎服務端--------- import socket #http是基于TCP協議,所以一定使用socket import re import os #進行文件路徑的定義 import sys #模塊加載定位 sys.path.append('page')#追加模塊加載路徑 #os.getcwd() 當前文件的根目錄 os.sep \ # HTML_ROOT_DIR=os.getcwd()+os.sep#響應目錄 # HTML_ROOT_DIR=r'F:\pythonstudy\python-Study\靜態Web服務器搭建\wenjian\static'#響應目錄 HTML_ROOT_DIR=r'F:\pythonstudy\python-Study\靜態Web服務器搭建\wenjian'#響應目錄 import multiprocessing #考慮到性能問題,為每一次請求開啟一個新的進程 class HttpServer:'''服務器的程序類'''def __init__(self,port):#服務器要有一個監聽的端口self.server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#創建socket實例#考慮到不同系統的問題,80端口是一個必爭端口,該端口屬于系統的核心端口,所以將核心任務與核心端口綁定self.server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)self.server_socket.bind(('0.0.0.0',port))#綁定核心端口self.server_socket.listen()#啟動監聽def start(self):'''服務器開始提供服務'''while True:#持續提供服務cli_socket,iport=self.server_socket.accept()#接收客戶端請求print('新的客戶端連接,客戶端ip:%s,客戶端端口:%s'%(iport[0],iport[1]))#輸出客戶端信息#將客戶端都設置為一個獨立的進程存在 都分別進行請求的回應handle_cli_process=multiprocessing.Process(target=self.handle_response,args=(cli_socket,))handle_cli_process.start()#進程啟動def handle_response(self,cli_socket):'''對每一個指定的客戶端進行響應'''request_headers=cli_socket.recv(1024)#用戶通過瀏覽器發送的請求本身就攜帶頭信息print(request_headers.decode())#使用正則提取請求頭信息# file_name=re.match(r'\w+ +(/[^ ]*)',request_headers.decode().split('\r\n')[0]).group(1)file_name=request_headers.decode().split(' ',2)[1]if file_name.startswith('/page'):#要訪問的是一個動態頁面request_name=file_name[file_name.index('/',1)+1:]#訪問路徑print('訪問路徑:"',request_name)param_value=""#請求參數if request_name.__contains__('?'):#參數路徑分隔符request_param=request_name[request_name.index('?')+1:]param_value=request_param.split('=')[1]#獲取參數名稱request_name=request_name[0:request_name.index('?')]#獲取模塊名稱model_name=request_name.split('/')[0]#模塊名稱method_name=request_name.split('/')[1]#函數名稱model=__import__(model_name)#加載模塊method=getattr(model,method_name)response_body=method(param_value)response_start_line = 'HTTP/2 200 OK\r\n'response_headers = 'Server PWS/2.0\r\n' # 可以添加更多的響應頭信息response = response_start_line + response_headers + '\r\n' + response_body # 最終的響應內容cli_socket.send(response.encode())cli_socket.close()#HTTP不保留用戶狀態,所以每次處理后都斷開連接,否則會造成性能開支,且這些開支是無意義的 def main():http_server=HttpServer(80)#80為服務器的默認端口,可以不用輸入,直接輸入域名即可http_server.start()#開啟服務 if __name__ == '__main__':main() #page包中echo.py文件 def service(param):#響應的處理函數if param:#如果此時的param有數據return '<h1>'+param+'</h1>'else:return '<h1>NO found</h1>' #訪問路徑:http://localhost/page/echo/service?param=11.urllib3
urllib是Python中提供的一個url請求訪問的模塊,利用該模塊可以實現瀏覽器的模擬訪問,而urllib3是此模塊的升級版,主要是為Python3服務的,兩者功能類似,只不過有一些細微的差別
#--------------urllib3-------------- import urllib3 url='http://www.baidu.com'#頁面的訪問路徑 def main():request_headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4676.0 Safari/537.36'}http=urllib3.PoolManager(num_pools=5,headers=request_headers)#獲取urllib3進程管理對象response=http.urlopen('GET',url)#發送get請求print(response.headers)#響應頭信息print(response.data.decode())#響應文件pass if __name__ == '__main__':main()12.twisted模塊認識
java中的IO:
- 傳統BIO模型–同步阻塞IO
- 偽異步IO模型–以BIO為基礎,通過線程方式維護所有IO線程,實現相對高效的線程開銷及管理
- NIO模型–一種同步非阻塞IO
twisted類似NIO,是Python之中提供的專門實現異步處理的IO概念,它的主要功能是提升服務端數據的處理能力
難道使用多進程,多線程等并發技術不能良好的解決性能問題嗎?
要想理解twisted設計思想,那么首先就必須清楚傳統服務器端程序開發中存在的問題?
為了讓服務端的程序更加高效的客戶端請求處理,所以引入并發編程,將每一個客戶端單獨啟動一個進程或線程,這樣就可以實現服務器的并發響應
對于此時的開發架構已經充分的發揮出了電腦硬件性能的作用,使用硬件提供的核心支持,進行并發編程實現,但需要清楚的是早期的電腦硬件是沒有這樣所謂的多核CPU概念的,早期的設計里面使用的單核CPU,需要非常細致的解決不同進程以及線程彼此間所謂的等待與喚醒機制(死鎖問題),雖然單進程性能不高,但是卻可以有效的解決所謂的不同進程或線程之間可能產生的死鎖問題。
如果不使用并發編程的形式,那么就不會有并發編程之中的問題(資源切換,系統調度,同步與等待所帶來的性能損耗)
所有的傳統服務端,如果采用的是阻塞的模式,那么就會持續發生等待的操作問題,而這種等待的問題是嚴重的損耗服務端性能的,即便服務端硬件在強大,損耗也挺嚴重。
阻塞IO本質:使用水壺燒開水,在旁邊盯著看,怕水開后,水壺燒壞
非阻塞IO本質:不盯著水壺,對水壺進行定期的不斷輪詢,沒開就繼續干其他事,開了就結束燒水
twisted是一個事件驅動的網絡引擎,最大特點是提供有一個事件循環處理,當外部事件發生時使用回調機制來觸發相應的處理操作,多個任務在一個線程中執行,,這種方式可以使程序盡可能的減少對于其他線程的依賴,也使得程序開發人員不在關注線程安全問題
twsited中所有處理事件(注冊,注銷,運行,回調處理等)全部交由reactor進行統一管理,在整個程序運行過程中,reactor循環會以單線程的模式持續運行,當需要執行回調處理時reactor會暫停循環,當回調操作執行完畢后將繼續采用循環的形式進行其他任務處理,由于這種操作是從平臺行為中抽象出來的,這樣就使得網絡協議棧的任何位置很容易的進行事件響應
13.twisted開發TCP程序
使用twisted最大的特點是進行服務端程序的開發,這樣的開發會為服務端的資源利用帶來極大的便利
使用twisted實現echo程序
如果要實現echo服務端程序的開發,那么讓服務端的處理類繼承一個twisted.internet.protocol.Protocol 父類,隨后就根據自己的需要來選擇要復寫的方法
處理流程:定義事件的處理回調操作程序–工廠中注冊–Reactor依據工廠來獲得相應的事件回調處理操作類
#---------------twisted客戶端---------- import twisted import twisted.internet.protocol import twisted.internet.reactor SERVER_HOST='localhost'#服務主機 SERVER_PORT=8080#連接端口 class Client(twisted.internet.protocol.Protocol):#定義用戶端處理類def connectionMade(self):print('服務器連接成功,可以進行數據交互,若要結束,則直接回車,,')self.send()#建立連接后就進行數據的發送def dataReceived(self,data):#接收服務端的數據print('服務端 接收到數據:%s'%data.decode())#輸出接收到的數據self.send()#繼續發送def send(self):#數據發送 自定義的方法input_data=input('請輸入要發送的數據:')if input_data:#如果有數據self.transport.write(input_data.encode())else:#沒有輸入內容,表示操作的結束self.transport.loseConnection()#關閉連接 class DefaultClientFactory(twisted.internet.protocol.ClientFactory):#客戶端工廠protocol=Client#定義回調clientConnectionLost=clientConnectionFailed=lambda self,connector,reason:twisted.internet.reactor.stop()#停止循環 def main():twisted.internet.reactor.connectTCP(SERVER_HOST,SERVER_PORT,DefaultClientFactory())#連接主機服務twisted.internet.reactor.run()#程序運行 if __name__ == '__main__':main()通過程序執行結果可以發現,此處避免了非常繁瑣的并發控制的操作,沒有了多進程或多線程的操作控制部分,整個執行流程都是基于單線程的運行模式完成(Python中的多線程存在GIL全局鎖問題,這就解決了此類問題)
14.使用twisted開發UDP程序
TCP是面向連接的可靠的網絡服務,所以不管使用的是socket還是twisted都需要進行連接的操作控制,這樣一定會造成不必要的性能開支,所以twisted內部也支持有UDP程序開發,因為UDP不需要保證可靠連接,所以只需要定義好用戶的處理回調操作即可。
#----------UDP twisted服務端------- import twisted import twisted.internet.protocol import twisted.internet.reactor SERVER_PORT=8080 class EchoServer(twisted.internet.protocol.DatagramProtocol):#數據報協議def datagramReceived(self,datagram,addr):#接收數據處理print('服務端 接收到消息,消息來源IP:%s,來源端口:%s'% addr)print('服務端 接收到數據消息:%s'%datagram.decode())echo_data='echo %s'%datagram.decode()#設置回應信息self.transport.write(echo_data.encode(),addr)#將信息返回給指定客戶端 def main():twisted.internet.reactor.listenUDP(SERVER_PORT,EchoServer())#服務監聽print('服務器啟動完成,等待客戶端連接。。。')twisted.internet.reactor.run()#事件循環 if __name__ == '__main__':main()使用UDP進行處理的時候不在需要使用那些連接的控制,同時也不在需要通過工廠才可以與Reactor進行銜接,從結構上更加的簡單了
#------------UDP twisted客戶端操作--------- import twisted import twisted.internet.reactor import twisted.internet.protocol SERVER_HOST='127.0.0.1' SERVER_PORT=8080 CLIENT_PORT=0#客戶端地址 class EchoClient(twisted.internet.protocol.DatagramProtocol):#UDP客戶端1def startProtocol(self):#連接的回調self.transport.connect(SERVER_HOST,SERVER_PORT)#連接print('服務器連接成功,可以進行數據交互,如果要結束會話,直接回車')self.send()#消息發送def datagramReceived(self,datagram,addr):#接收數據處理print(datagram.decode())self.send()#下一次數據發送def send(self):#數據發送 自定義方法input_data=input('請輸入要發送的信息:')if input_data:self.transport.write(input_data.encode())else:twisted.internet.reactor.stop()#停止輪詢 def main():twisted.internet.reactor.listenUDP(CLIENT_PORT,EchoClient())#服務監聽twisted.internet.reactor.run()#開啟事件循環 if __name__ == '__main__':main()所有網絡程序進行開發的過程中實際上只有一個核心的目的:提升服務端的資源的可用性(發揮出最大的性能),減少操作的延遲,但是,UDP當今的應用都是在即時消息通訊操作上,而對于TCP的開發操作依然是主流
15.Deferred
在網絡開發之中,對于服務端性能提升可以使用twisted直接完成,但是在一些客戶端與網絡服務器端交互的過程之中,有可能下載需要下載一些比較龐大的文件內容(圖片,視頻等等),按照傳統的客戶端的開發模型來講,此時就需要持續進行下載,而對于當前的客戶端也將進入到一個阻塞的開發狀態,那么在這樣的情況下為了解決客戶端的阻塞問題,就提供了Deferred的概念
在twisted設計之中最大的特點就是持續的強調采用非阻塞的形式來完成,同時盡可能的減少并發操作,通過事件輪詢的方式來提升程序的可用資源,基于事件輪詢的機制設計出一套Deferred的模型
在整個程序執行完畢后,就可以直接利用所設置的calback()操作進行操作完成后的調用,基于這樣的操作模型就減少了并發編程的使用,但使用twisted程序都是在解決網絡通訊的性能問題,那么最佳的做法肯定就是Defered應用在網絡的開發環境中。
通過Deffer模型實現TCP的echo客戶端
這種交互的模型主要是Deferred優化了客戶端之中的處理結構,在實際開發中,一個服務端有可能繼續調用其他的服務器端,而這個服務器還有可能同時要處理用戶的請求
- 無智亦無得,以無所得故
總結
- 上一篇: 第一个Google Glass项目
- 下一篇: 他的体育课在下午五点结束使用计算机,外研