摘要:函數(shù)實(shí)現(xiàn)基于上述原理,首先,需要創(chuàng)建一個與協(xié)議關(guān)聯(lián)的套接字,并設(shè)置超時以控制用于接收數(shù)據(jù)包的時間套接字。創(chuàng)建套接字后,需要實(shí)現(xiàn)一個函數(shù)來構(gòu)建,打包并將數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī)。套接字將一直等待,直到收到數(shù)據(jù)包或達(dá)到超時限制。
最后一次更新于 2019/07/10
ICMP Ping 目的此任務(wù)是重新創(chuàng)建第3講(延遲,丟失和吞吐量)中討論的ping客戶端。
Ping 是一個用于在計算機(jī)網(wǎng)絡(luò)中測量延遲和丟失的工具。
在實(shí)際應(yīng)用中,我們可以通過 ping 命令分析判斷網(wǎng)絡(luò)失敗的原因。當(dāng)然,這類信息也可用于幫助我們選擇性能更佳的IP地址作為代理服務(wù)器。
Ping 通常使用 Internet 控制消息協(xié)議 (ICMP) 報文來測量網(wǎng)絡(luò)中的延遲和丟失:本機(jī)在 ICMP 包中發(fā)送回響請求(ICMP類型代碼為8)給另一個主機(jī)。然后,主機(jī)解包數(shù)據(jù)包并提取ICMP類型代碼并匹配請求和回復(fù)之間的ID。如果遠(yuǎn)程主機(jī)的響應(yīng)報文ICMP類型代碼為0,然后我們可以計算發(fā)送請求和接收回復(fù)之間經(jīng)過的時間,進(jìn)而精確的計算兩臺主機(jī)之間網(wǎng)絡(luò)的延遲。
注意: IP數(shù)據(jù)報和ICMP錯誤代碼的結(jié)構(gòu)(ICMP類型代碼為3)如下所示。因特網(wǎng)校驗(yàn)和也是數(shù)據(jù)包的重要部分,但它不是本函數(shù)實(shí)現(xiàn)的核心。
基于上述原理,首先,需要創(chuàng)建一個與協(xié)議ICMP關(guān)聯(lián)的套接字,并設(shè)置超時以控制用于接收數(shù)據(jù)包的時間套接字。
# 運(yùn)行特權(quán)TCP套接字,1是與協(xié)議ICMP關(guān)聯(lián)的套接字模塊常量。 icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, 1) icmp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeout)
創(chuàng)建套接字后,需要實(shí)現(xiàn)一個函數(shù)來構(gòu)建,打包并將ICMP數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī)。
如圖所示,如果創(chuàng)建一個32字節(jié)大小的數(shù)據(jù)包,那么只有四個字節(jié)長度來存儲有效負(fù)載數(shù)據(jù)。
因此,以浮點(diǎn)格式(4個字節(jié))存儲當(dāng)前時間幀是比較好的解決辦法。
但是,由于精度損失,不能使用此數(shù)據(jù)來計算總網(wǎng)絡(luò)延遲。
"!" 但是,由于精度損失,我永遠(yuǎn)不會使用此數(shù)據(jù)來計算總網(wǎng)絡(luò)延遲。
構(gòu)建和打包ICMP數(shù)據(jù)包源代碼:
def receive_one_ping(icmp_socket, port_id, timeout, send_time): while True: # 1. 等待套接字并得到回復(fù)。 wait_for_data = select.select([icmp_socket], [], [], timeout) # 2. 一旦接受,記錄當(dāng)前時間。 data_received = time.time() rec_packet, addr = icmp_socket.recvfrom(1024) ip_header = rec_packet[8: 12] icmp_header = rec_packet[20: 28] payload_size = struct.calcsize("!f") # 3. 解壓包首部行查找有用的信息。 type, code, checksum, id, sequence = struct.unpack("!bbHHh", icmp_header) # 4. 檢查收發(fā)之間的 ID 是否匹配。 if type == 0 and id == port_id: # type should be 0 ttl = struct.unpack("!b", ip_header[0:1])[0] delay_time = data_received - send_time # 5. 返回比特大小,延遲率和存活時間。 return payload_size * 8, delay_time, ttl elif type == 3 and code == 0: return 0 # 網(wǎng)絡(luò)無法到達(dá)的錯誤。 elif type == 3 and code == 1: return 1 # 主機(jī)無法到達(dá)的錯誤。
當(dāng)從同一主機(jī)獲得所有ping測試結(jié)果時,需要另一個函數(shù)來顯示所有測量的最小時間,平均時間和最大延遲。
def ping_statistics(list): max_delay = list[0] mini_delay = list[0] sum = 0 for item in list: if item >= max_delay: max_delay = item elif item <= mini_delay: mini_delay = item sum += item avg_delay = int(sum / (len(list))) return mini_delay, max_delay, avg_delay
最后一件事是處理異常。需要處理不同的ICMP錯誤代碼和返回值的超時。代碼如下所示:
def ping(host, count_num="4", time_out="1"): # 1. 查找主機(jī)名,將其解析為IP地址。 ip_addr = socket.gethostbyname(host) successful_list = list() lost = 0 error = 0 count = int(count_num) timeout = int(time_out) timedout_mark = False for i in range(count): # i 是序列的值 # 打印報文首部行 ...... try: # 2. 調(diào)用 doOnePing 函數(shù)。 ping_delay = do_one_ping(ip_addr, timeout, i) # 3. 打印出返回的延遲信息。 if ping_delay == 0 or ping_delay == 1: # 獲取本機(jī)的 IP 地址。 ip_addr = socket.gethostbyname(socket.gethostname()) print("Reply from {ipAdrr}: ".format(ipAdrr = ip_addr), end = "") result = "Destination host unreachable." if ping_delay == 0 else "Destination net unreachable." print(result) error += 1 else: bytes, delay_time, ttl = ping_delay[0], int(ping_delay[1] * 1000), ping_delay[2] print("Reply from {ipAdrr}: ".format(ipAdrr = ip_addr), end = "") # 如果可以成功接收數(shù)據(jù)包, # 在list里追加延遲時間。 successful_list.append(delay_time) # 如果延遲時間小于 1 ms,則記為0。 ...... except TimeoutError: # 超時類型 lost += 1 print("Request timed out.") # 如果它不總是超時的情況, # 我們需要計算最大延遲時間。 if timedout_mark is False: timedout_mark = True time.sleep(1) # 每秒。 # 4. 繼續(xù)執(zhí)行直到結(jié)束。 ......輸出結(jié)果
C:UsersasusDesktoplab_solutionICMP Ping>ICMPPing.py>ping www.baidu.com Pinging www.baidu.com [111.13.100.92] with 32 of data: Reply from 111.13.100.92: bytes = 32 time = 28ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 35ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 33ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 31ms TTL = 51. Ping statistics for 111.13.100.92: Packet: Sent = 4, Received = 4, lost = 0 (0% loss). Approximate round trip times in milli - seconds: Minimum = 28ms, Maximum = 35ms, Average = 31ms. C:UsersasusDesktoplab_solutionICMP Ping>ICMPPing.py>ping google.com Pinging google.com [172.217.161.174] with 32 of data: Request timed out. Request timed out. Request timed out. Request timed out. Ping statistics for 172.217.161.174: Packet: Sent = 4, Received = 0, lost = 4 (100% loss). C:UsersasusDesktoplab_solutionICMP Ping>ICMPPing.py>ping www.baidu.com -n 6 Pinging www.baidu.com [111.13.100.92] with 32 of data: Reply from 111.13.100.92: bytes = 32 time = 29ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 46ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 33ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 44ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 36ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 35ms TTL = 51. Ping statistics for 111.13.100.92: Packet: Sent = 6, Received = 6, lost = 0 (0% loss). Approximate round trip times in milli - seconds: Minimum = 29ms, Maximum = 46ms, Average = 37ms. C:UsersasusDesktoplab_solutionICMP Ping>ICMPPing.py>ping www.baidu.com -n 6 -w 2 Pinging www.baidu.com [111.13.100.92] with 32 of data: Reply from 111.13.100.92: bytes = 32 time = 25ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 35ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 20ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 55ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 34ms TTL = 51. Reply from 111.13.100.92: bytes = 32 time = 37ms TTL = 51. Ping statistics for 111.13.100.92: Packet: Sent = 6, Received = 6, lost = 0 (0% loss). Approximate round trip times in milli - seconds: Minimum = 20ms, Maximum = 55ms, Average = 34ms.路由追蹤 目的
此任務(wù)是重新創(chuàng)建第3講(延遲,丟失和吞吐量)中的路由追蹤工具。這用于測量主機(jī)和到達(dá)目的地的路徑上的每一跳之間的延遲。在實(shí)際應(yīng)用中,路由追蹤可以找到源主機(jī)和目標(biāo)主機(jī)之間的路由器以及到達(dá)每個路由器所需的時間。
原理如上圖所示,源主機(jī)使用ICMP echo請求報文,但有一個重要的修改:
存活時間(TTL)的值初始為1。這可以確保我們從第一跳獲得響應(yīng)。 一旦報文到達(dá)路由器,TTL計數(shù)器就會遞減。
當(dāng)TTL達(dá)到0時,報文將返回到源主機(jī),ICMP 類型為11(已超出TTL且IP數(shù)據(jù)報尚未到達(dá)目標(biāo)并被丟棄)。
每次增加TTL都會重復(fù)此過程,直到我們收到回復(fù)。如果echo回復(fù)ICMP類型為0,則表示IP數(shù)據(jù)報已到達(dá)目的地。
然后我們就可以停止運(yùn)行路由追蹤的腳本了。在此過程中,可能會發(fā)生異常并且我們需要處理錯誤代碼與 ICMP Ping 相同。
基于上述原理,首先,除了創(chuàng)建一個與協(xié)議ICMP關(guān)聯(lián)的套接字并設(shè)置超時來控制用于接收數(shù)據(jù)包的套接字外,還需要通過
socket.setsockopt(level, optname, value) 函數(shù)設(shè)置套接字的TTL。
client_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, 1) # ICMP client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO , time_out) client_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, struct.pack("I", ttl))
創(chuàng)建套接字后,需要實(shí)現(xiàn)一個函數(shù)來構(gòu)建,打包并將ICMP數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī)。這部分代碼和在 ICMP Ping 中的構(gòu)建和打包ICMP數(shù)據(jù)包源代碼一致。
下一步是等待并收到回復(fù)。套接字將一直等待,直到收到數(shù)據(jù)包或達(dá)到超時限制。 通過 ICMP 回響報文發(fā)現(xiàn)并報告無法訪問目標(biāo)主機(jī)和無法訪問目標(biāo)網(wǎng)路。這部分和接受數(shù)據(jù)包源碼相似但路由追蹤需要額外記錄每次訪問到路由器的IP地址。代碼如下所示:
def receive_one_trace(icmp_socket, send_time, timeout): try: # 1. 等待套接字并得到回復(fù)。 ... Similar to Receive Packet Source ... # 2. 一旦接受,記錄當(dāng)前時間。 rec_packet, retr_addr = icmp_socket.recvfrom(1024) # 3. 解壓包首部行查找有用的信息。 ... Similar to Receive Packet Source ... # 4. 通過代號類型檢查數(shù)據(jù)包是否丟失。 ... Similar to Receive Packet Source ... except TimeoutError : print_str = "* " # 超時。 finally: ..... # 打印延遲時間。 # 返回當(dāng)前路由器的IP地址。 # 如果超時的話,直接返回字符串。 try: retre_ip = retr_addr[0] except IndexError: retre_ip = "Request timeout" finally: return retre_ip
最后一件事是為每個路由器實(shí)現(xiàn)重復(fù)測量,解析在對各自主機(jī)名的響應(yīng)中找到的IP地址并處理異常。代碼如下所示:
def trace_route(host, timeout=2): # 可配置超時,使用可選參數(shù)設(shè)置。 # 1. 查找主機(jī)名,將其解析為IP地址。 ip_addr = socket.gethostbyname(host) ttl = 1 print("Over a maximum of {max_hop} hops: ".format(max_hop = MAX_HOP)) print("Tracing route to " + host + " [{hostIP}]:".format(hostIP = ip_addr)) for i in range(MAX_HOP): sys.stdout.write("{0: >3}".format(str(ttl) + " ")) cur_addr = do_three_trace(ip_addr, ttl, i, timeout) try: sys.stdout.write("{0:<}".format(" " + socket.gethostbyaddr(cur_addr)[0] + " [" + cur_addr + "]" + " ")) except (socket.herror, socket.gaierror): sys.stdout.write("{0:<}".format(" " + cur_addr + " ")) if cur_addr == ip_addr : break ttl += 1 sys.stdout.write(" Trace complete. ")輸出結(jié)果
Over a maximum of 30 hops: Tracing route to www.baidu.com [111.13.100.91]: 1 16 ms 15 ms 15 ms 10.129.0.1 ...... # All successful 5 * * * Request timeout 6 * * * Request timeout ...... # All successful 9 * * * Request timeout ...... # All successful 13 * * * Request timeout 14 22 ms 22 ms 21 ms 111.13.100.91 Trace complete. C:UsersasusDesktoplab_solutionTraceroute>Traceroute.py>tracert 10.129.21.147 Over a maximum of 30 hops: Tracing route to 10.129.21.147 [10.129.21.147]: 1 Host unreachable * Host unreachable DESKTOP-6VPPJQ8 [10.129.34.15] 2 * Host unreachable * DESKTOP-6VPPJQ8 [10.129.34.15] ...... All situations are the same 29 Host unreachable * Host unreachable DESKTOP-6VPPJQ8 [10.129.34.15] 30 * Host unreachable * DESKTOP-6VPPJQ8 [10.129.34.15] Trace complete.Web服務(wù)器 目的
此任務(wù)是構(gòu)建一個簡單的HTTP Web服務(wù)器。根據(jù)第4講(Web 和 HTTP)中學(xué)習(xí)到的知識,Web 服務(wù)器是Internet的基礎(chǔ)部分,它們提供我們熟悉的網(wǎng)頁和內(nèi)容。
網(wǎng)頁由對象組成,這些對象可以是HTML文件,JPEG圖像,Java小程序等。HTTP流量通常綁定到端口80,端口8080是常用的替代方案。因此,虛擬主機(jī)(機(jī)器)使用本地端口號80和8080。
HTTP/1.1 含有許多類型的 HTTP 請求,在本任務(wù)中,只考慮 HTTP GET 請求。
如下圖所示,一個簡單的 web 服務(wù)器從前端接受 HTTP 請求報文。收到此請求后,簡單 Web 服務(wù)器將從郵件中提取所請求對象的路徑,然后嘗試從硬盤中檢索請求的對象。如果它成功找到硬盤中的對象,它會將對象發(fā)送回具有相應(yīng)首部行的客戶端(其中包含Status-Code 200)。否則,它將使用HTTP響應(yīng)報文(將包含404 Not Found"狀態(tài)將"Not Found"網(wǎng)頁發(fā)送到客戶端。
line)". HTTP 請求和響應(yīng)報文的格式已在下圖5.(a)和5.(b)顯示。
基于上述原理,首先,創(chuàng)建一個支持 IPv4 的套接字并將其綁定在高于1024的端口上。Web服務(wù)器應(yīng)該同時監(jiān)聽5個請求,并具有處理多個并發(fā)連接的能力。
Web 服務(wù)器運(yùn)行源碼:
def start_server(server_port , server_address): # 將 web 服務(wù)器綁定到可配置端口,定義為可選參數(shù)。 # 1. 常見一個服務(wù)器套接字。 server_socket = socket(AF_INET, SOCK_STREAM) #IPv4 # 2. 將服務(wù)器套接字綁定到服務(wù)器地址和服務(wù)器端口。 server_socket.bind(("", server_port)) # 3. 持續(xù)監(jiān)聽與服務(wù)器套接字的連接。 server_socket.listen(5) while True: # 4. 當(dāng)接受連接時,調(diào)用 handleRequest 函數(shù),傳遞新的連接套接字。 connection_socket , (client_ip, client_port) = server_socket.accept() # 創(chuàng)建一個多線程服務(wù)器實(shí)現(xiàn),能夠處理多個并發(fā)連接。 start_new_thread(handle_request , (connection_socket , client_ip, client_port)) # 5. 關(guān)閉服務(wù)器端的套接字。 server_socket.close() # 不然服務(wù)器會一直監(jiān)聽,不會主動關(guān)閉。 # 從這里開始運(yùn)行。 server_port = int(sys.argv[1]) server_address = gethostbyname(gethostname()) start_server(server_port)
創(chuàng)建套接字后,web 服務(wù)器需要處理HTTP GET請求。在處理之前,創(chuàng)建了一個StrProcess類來重寫字符串模塊中的split方法。用空格分割一個字符串,只處理請求行。因此,當(dāng)它找到字符"r"時,它將停止處理并返回結(jié)果。
StrProcess 類源碼:
class StrProcess(str): def __init__(self, str): """用字符型變量 str 初始該屬性""" self.str = str def split_str(self): """實(shí)現(xiàn)分割操作""" spilt_list = [] start = 0 for i in range(len(self.str)): if self.str[i] == " ": spilt_list.append(self.str[start:i]) start = i + 1 if self.str[i] == " ": break return spilt_list[1]
最后一件事是處理 HTTP GET 請求和異常。由于非持久性HTTP,不需要在true循環(huán)時寫入以接收HTTP請求報文。如果套接字收到空報文,則應(yīng)該關(guān)閉它。否則,web 服務(wù)器需要檢查對象是否存在于緩存中。如果對象存在,則使用"HTTP/1.1 200 OK rnrn"將對象發(fā)送到客戶端,否則發(fā)生"FileNotFoundError"異常,然后對于"未找到"的HTML文件使用"HTTP/1.1 404 Not Foundrnrn"發(fā)送到客戶端。 發(fā)送HTTP響應(yīng)報文后,HTTP服務(wù)器將關(guān)閉TCP連接。代碼如下所示:
def handle_request(tcp_socket, client_ip, client_port): print("Client ({ip}: {port}) is coming...".format(ip = client_ip, port = client_port)) try: # 1. 在連接套接字上從客戶端接收請求報文。 msg = tcp_socket.recv(1024).decode() if not msg: print("Error! server receive empty HTTP request.") tcp_socket.close() # 從strProc類創(chuàng)建新對象(handlestr)。 handle_str = StrProcess(msg) # 2. 從報文中提取所請求對象的路徑(HTTP 首部行的第二部分)。 file_name = handle_str.split_str()[1:] # 3. 從磁盤中查找相應(yīng)的文件。 # 檢查請求的對象是否存在。 f = open(file_name) f.close() # 如果對象存在,準(zhǔn)備發(fā)送 "HTTP/1.1 200 OK " 到套接字。 status = "200 OK" except FileNotFoundError: # 否則,準(zhǔn)備發(fā)送 "HTTP/1.1 404 Not Found " 到套接字。 status = "404 Not Found" file_name = "NotFound.html" re_header = "HTTP/1.1 " + status + " " # 最后一個"" "" 意味著頭報文的結(jié)束。 # 4. 發(fā)送正確的HTTP響應(yīng)。 tcp_socket.send(re_header.encode()) # 5. 存儲在臨時緩沖區(qū)中 with open(file_name, "rb") as f: file_content = f.readlines() # 6. 將文件的內(nèi)容發(fā)送到套接字。 for strline in file_content: tcp_socket.send(strline) # 7. 關(guān)閉連接的套接字。 print("Bye to Client ({ip}: {port})".format(ip = client_ip, port = client_port)) tcp_socket.close()
編寫了一個多帶帶的HTTP客戶端來查詢 web 服務(wù)器。此客戶端可以發(fā)送 HTTP GET 請求并在控制臺上接收HTTP響應(yīng)報文。該程序的優(yōu)點(diǎn)是它只需輸入對象的名稱或選擇保留或離開即可查詢對象。
HTTP 客戶端源碼:
from socket import * import sys # 1. 設(shè)置 web 服務(wù)器的地址。 host_port = int(sys.argv[1]) host_address = gethostbyname(gethostname()) # 2. 創(chuàng)建客戶端套接字以啟動與 web 服務(wù)器的 TCP 連接。 tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect((host_address , host_port)) # 3. 輸入要查詢 web服務(wù)器的文件客戶端。 print("Hello, which document do you want to query?") while True: obj = input("I want to query: ") # 4. 發(fā)送 HTTP 請求報文。 message = "GET /" + obj + " HTTP/1.1 " "Host: " + host_address + ":" + str(host_port) + " " "Connection: close " "Upgrade-Insecure-Requests: 1 " "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 " ...... # 首部行。 "Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 " tcp_client.send(message.encode()) while True: # 5. 接收HTTP響應(yīng)報文并將其打印到控制臺。 data = tcp_client.recv(1024) if not data: break print("Web server responded to your request:") print(data.decode()) tcp_client.close() # 關(guān)閉當(dāng)前的連接。 # 6. 詢問客戶是否要繼續(xù)。 ans = input(" Do you want to cut this connection(y/n) :") if ans == "y" or ans == "Y": break elif ans == "n" or ans == "N": # 重新嘗試。 print("Anything else I can help you?") tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect((host_address , host_port)) else: print("Command Error, quit.") break輸出結(jié)果 WebServer.py
you can test the web server by accessing: http://10.129.34.15:8899/hello.html Wait for TCP clients... Client (10.129.34.15: 6123) is coming... Bye to Client (10.129.34.15: 6123) Client (10.129.34.15: 6135) is coming... Bye to Client (10.129.34.15: 6135) C:UserasusDesktoplab_solutionWeb Server>python Client.py 8899 Hello, which document do you want to query? I want to query: hello.htmlClient.py
Web server responded to your request: HTTP/1.1 200 OK Web server responded to your request:Web代理服務(wù)器 目的Hello World HTML Hello World
Web server responded to your request: Do you want to cut this connection(y/n) :n Anything else I can help you? I want to query: index.html Web server responded to your request: HTTP/1.1 404 Not Found Do you want to cut this connection(y/n) :y Process finished with exit code 0
此任務(wù)是構(gòu)建一個簡單的 web 代理服務(wù)器。根據(jù)第4講(Web 和 HTTP)中所學(xué)到的知識, web 代理服務(wù)器充當(dāng)客戶端和服務(wù)器,這意味著它具有 web 服務(wù)器和客戶端的所有功能。它和 web 服務(wù)器之間最顯著的區(qū)別是發(fā)送的請求報文
和響應(yīng)報文都要通過 web 服務(wù)器傳遞。web 代理服務(wù)器在任何地方(大學(xué),公司和住宅ISP)使用,以減少客戶請求和流量的響應(yīng)時間。
web 代理服務(wù)器的原理基于 web 服務(wù)器。web 代理服務(wù)器從客戶端接收 HTTP 請求報文,并從請求行中提取方法類型。
如果請求類型是 GET,從同一行獲取URL并檢查請求的對象是否存在于緩存中。否則,web 代理服務(wù)器會將客戶端的請求轉(zhuǎn)發(fā)給 web 服務(wù)器。然后,web 服務(wù)器將生成響應(yīng)報文并將其傳遞給 web 代理服務(wù)器,web 代理服務(wù)器又將其發(fā)送到客戶端并為將來的請求緩存副本。
如果請求類型是 DELETE,代理服務(wù)器會首先確認(rèn)請求,如果對象存在緩存中,j只是從緩存中刪除它并發(fā)送帶有 Status-Code 200 的 HTTP 響應(yīng)報文。否則,web 代理服務(wù)器發(fā)送帶有 Status-Code 404 的 HTTP 響應(yīng)報文。
如果請求類型是 POST, 處理過程比上述方法類型更容易,web 代理服務(wù)器只是以二進(jìn)制格式將對象寫入磁盤,然后發(fā)送帶有 Status-Code 200 的 HTTP 響應(yīng)報文(輸入在實(shí)體行中上傳)。如果方法是 PUT,則只需要在實(shí)體行中返回 true。
如果請求類型是 HEAD, web 代理服務(wù)器僅返回 HTTP 響應(yīng)報文的報文首部行和狀態(tài)行。
簡化過程如下所示。
基于上述原理,首先,創(chuàng)建一個支持 IPv4 的套接字并將其綁定在高于1024的端口上。web 代理服務(wù)器和 web 服務(wù)器非常類似,唯一區(qū)別是它是單線程的。
我在 StrProcess 類中添加了其他方法使 web 代理服務(wù)器獲取對象或連接到 web 服務(wù)器的效率更高。
StrProcess 類源碼:
class StrProcess(str): def __init__(self, str): """用字符型變量 str 初始該屬性""" self.str = str def split_str(self): """實(shí)現(xiàn)分割操作""" spilt_list = [] start = 0 for i in range(len(self.str)): if self.str[i] == " ": spilt_list.append(self.str[start:i]) start = i + 1 if self.str[i] == " ": break try: return spilt_list[0],spilt_list[1] except IndexError: return None def get_cmd_type(self): """從 HTTP 請求行中提取請求方法""" return self.split_str()[0] def get_body(self): """從 HTTP 請求報文實(shí)體行中提取數(shù)據(jù)""" body_start = self.str.find(" ") + 4 return self.str[body_start:] def get_referer(self): """從 HTTP 請求行中提取引用""" ref_pos = self.str.find("Referer: ") + 9 ref_stop = self.str.find(" ", ref_pos+1) get_ref = self.str[ref_pos:ref_stop] get_ref_start = get_ref.find("9/") + 2 get_ref_path = self.str[ref_pos+get_ref_start:ref_stop] return get_ref_path def get_path(self): """從 HTTP 請求請求行中提取URL""" original_path = self.split_str()[1] for i in range(len(original_path)): if original_path[i] == "/": original_path = original_path[i+1:] return original_path def change_name(self): """將所有特殊符號轉(zhuǎn)換為 "-"。""" original_name = self.get_path() for i in range(len(original_name)): if original_name[i] == "/" or original_name[i] == "?" or original_name[i] == "=" or original_name[i] == "&" or original_name[i] == "%": original_name = original_name[:i] + "-" + original_name[i+1:] return original_name def get_hostname(self): """從URL中提取主機(jī)名""" whole_URL = self.get_path() for i in range(len(whole_URL)): if whole_URL[i] == "/": host_name = whole_URL[:i] return host_name return whole_URL
創(chuàng)建套接字后,web 代理服務(wù)器需要處理不同的 HTTP 請求類型和異常。在這部分中,文件處理應(yīng)該以二進(jìn)制格式使用,我們必須考慮對象類型(可以是.jpg,.svg,.ico等)。
處理 HTTP 請求源碼:
def start_listen(tcp_socket, client_ip, client_port): # 1. 在連接套接字上從客戶端接收請求報文。 message = tcp_socket.recv(1024).decode() # 從strProc類創(chuàng)建新對象(handlestr)。 handle_str = StrProcess(message) print("client is coming: {addr}:{port}".format(addr = client_ip, port = client_port)) file_error = False global host try: command = handle_str.get_cmd_type() # 2. 從報文中提取所請求對象的路徑(HTTP 首部行的第二部分)。 filename = handle_str.change_name() # 3. 找到特定的方法類型并處理請求。 if command == "DELETE" : # 刪除緩存中存在的對象 os.remove("./Cache/" + filename) tcp_socket.send(b"HTTP/1.1 200 OK ") print("File is removed.") elif command == "GET" or command == "HEAD": print("Client want to {c} the {o}.".format(c=command, o=filename)) # 檢查請求的對象是否存在。 f = open("./Cache/" + filename, "rb") file_content = f.readlines() f.close() if command == "GET": print("File in cache!") # 從磁盤中查找相應(yīng)的文件(如果存在)。 for i in range(0, len(file_content)): tcp_socket.send(file_content[i]) # 發(fā)送 HTTP 響應(yīng)報文。 else: # "HEAD" 方法。 list_to_str = "" for i in range(0, len(file_content)): list_to_str += file_content[i].decode() HTTP_header_end = list_to_str.find(" ") # 僅發(fā)送 HTTP 響應(yīng)報文首部行。 tcp_socket.send(list_to_str[:HTTP_header_end+4].encode()) elif command == "PUT" or command == "POST": # 只實(shí)現(xiàn)上傳文件。 f = open("./Cache/" + filename, "ab") f.write(b"HTTP/1.1 200 OK " + handle_str.get_body().encode()) f.close() print("Update successfully!") tcp_socket.send(b"HTTP/1.1 200 OK ") body_re = b"true" if command == "PUT" else handle_str.get_body().encode() tcp_socket.send(body_re) else: tcp_socket.send(b"HTTP/1.1 400 Bad Request ") # 4. 如果緩存中不存在該文件,則處理異常。 except (IOError, FileNotFoundError): if command == "GET": # 在代理服務(wù)器上創(chuàng)建套接字。 c = socket(AF_INET, SOCK_STREAM) hostname = handle_str.get_hostname() file = handle_str.split_str()[1] print("The file isn"t in the cache!") try: # 連接到端口80的套接字。 c.connect((hostname, 80)) host = hostname # 記錄真實(shí)的主機(jī)名。 request = "GET " + "http:/" + file + " HTTP/1.1 " except: try: # 需要使用全局主機(jī)或引用主機(jī)名。 new_host = handle_str.get_referer() if host == "" else host c.connect((new_host, 80)) request = "GET " + "http://" + new_host + file + " HTTP/1.1 " except: tcp_socket.send(b"HTTP/1.1 404 Not Found ") with open("./Cache/NotFound.html", "rb") as f: file_content = f.readlines() for strline in file_content: tcp_socket.send(strline) file_error = True if file_error is False: c.sendall(request.encode()) # 將響應(yīng)讀入緩沖區(qū)。 print("The proxy server has found the host.") # 在緩存中為請求的文件創(chuàng)建一個新文件。 # 此外,將緩沖區(qū)中的響應(yīng)發(fā)送到客戶端套接字和緩存中的相應(yīng)文件。 writeFile = open("./Cache/" + filename, "wb") print("The proxy server is receiving data...") # 接受 HTTP 響應(yīng)報文直到所有報文都被接收。 while True: data = c.recv(4096) if not data: break sys.stdout.write(">") # 將文件的內(nèi)容發(fā)送到套接字。 tcp_socket.sendall(data) writeFile.write(data) writeFile.close() sys.stdout.write("100% ") c.close() elif command == "DELETE": tcp_socket.send(b"HTTP/1.1 204 Not Content ") except (ConnectionResetError, TypeError): print("Bye to client: {addr}:{port}".format(addr = client_ip, port = client_port)) # 關(guān)閉客戶端的套接字。 print("tcp socket closed ") tcp_socket.close()輸出結(jié)果 瀏覽器測試 WebProxy.py
C:UsersasusDesktoplab_solutionWeb Proxy>python WebProxy.py 8899 Wait for TCP clients... wait for request: client is coming: 127.0.0.1:4596 Client want to GET the s-wd-facebook-rsv_bp-0-ch-tn-baidu-bar-rsv_spt-3-ie-utf-8-rsv_enter-1-oq-face-f-3-inputT-3356. The file is not in the cache! The proxy server has found the host. The proxy server is receiving data... >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>100% tcp socket closed源碼
如果我的文章可以幫到您,勞煩您點(diǎn)進(jìn)源碼點(diǎn)個 ★ Star 哦!
https://github.com/Hephaest/C...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/45121.html
摘要:年月宣布支持時間延長到年。更詳細(xì)的發(fā)布列表參閱官網(wǎng)的版本號分為三段,形如。其中表示大版本號,一般當(dāng)整體重寫,或出現(xiàn)不向后兼容的改變時,增加表示功能更新,出現(xiàn)新功能時增加表示小的改動如修復(fù)了某個,只要有修改就增加。年公司正式發(fā)布。 < 返回索引頁 Python語言簡介 Python介紹及發(fā)展 介紹 Python 官方網(wǎng)站:https://www.python.org/, 大家可以到此處下...
摘要:學(xué)習(xí)筆記七數(shù)學(xué)形態(tài)學(xué)關(guān)注的是圖像中的形狀,它提供了一些方法用于檢測形狀和改變形狀。學(xué)習(xí)筆記十一尺度不變特征變換,簡稱是圖像局部特征提取的現(xiàn)代方法基于區(qū)域圖像塊的分析。本文的目的是簡明扼要地說明的編碼機(jī)制,并給出一些建議。 showImg(https://segmentfault.com/img/bVRJbz?w=900&h=385); 前言 開始之前,我們先來看這樣一個提問: pyth...
摘要:作者宋天龍來源科技大本營導(dǎo)語一切都始于年的那個圣誕節(jié),的誕生并不算恰逢其時,它崛起充滿了機(jī)遇巧合,也有其必然性。年的圣誕節(jié),開始編寫語言的編譯器。年發(fā)布的標(biāo)志著的框架基本確定。年月發(fā)布了系列的最后一個版本,主版本號為。 showImg(https://segmentfault.com/img/remote/1460000019862276); 作者 | 宋天龍來源 | AI科技大本營 ...
摘要:蠎周刊年度最贊親俺們又來回顧又一個偉大的年份兒包去年最受歡迎的文章和項(xiàng)目如果你錯過了幾期就這一期不會丟失最好的嗯哼還為你和你的準(zhǔn)備了一批紀(jì)念裇從這兒獲取任何時候如果想分享好物給大家在這兒提交喜歡我們收集的任何意見建議通過來吧原文 Title: 蠎周刊 2015 年度最贊Date: 2016-01-09 Tags: Weekly,Pycoder,Zh Slug: issue-198-to...
閱讀 3608·2020-12-03 17:42
閱讀 2779·2019-08-30 15:54
閱讀 2233·2019-08-30 15:44
閱讀 579·2019-08-30 14:08
閱讀 980·2019-08-30 14:00
閱讀 1116·2019-08-30 13:46
閱讀 2796·2019-08-29 18:33
閱讀 2939·2019-08-29 14:11