最後一次更新於 2019/07/10
html
此任務是從新建立第3講(延遲,丟失和吞吐量)中討論的ping客戶端。
Ping 是一個用於在計算機網絡中測量延遲和丟失的工具。
在實際應用中,咱們能夠經過 ping
命令分析判斷網絡失敗的緣由。固然,這類信息也可用於幫助咱們選擇性能更佳的IP地址做爲代理服務器。前端
Ping 一般使用 Internet 控制消息協議 (ICMP) 報文來測量網絡中的延遲和丟失:本機在 ICMP 包中發送迴響請求(ICMP類型代碼爲8)給另外一個主機。而後,主機解包數據包並提取ICMP類型代碼並匹配請求和回覆之間的ID。若是遠程主機的響應報文ICMP類型代碼爲0,而後咱們能夠計算髮送請求和接收回復之間通過的時間,進而精確的計算兩臺主機之間網絡的延遲。python
注意: IP數據報和ICMP錯誤代碼的結構(ICMP類型代碼爲3)以下所示。因特網校驗和也是數據包的重要部分,但它不是本函數實現的核心。git
基於上述原理,首先,須要建立一個與協議ICMP關聯的套接字,並設置超時以控制用於接收數據包的時間套接字。github
# 運行特權TCP套接字,1是與協議ICMP關聯的套接字模塊常量。 icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, 1) icmp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeout)
建立套接字後,須要實現一個函數來構建,打包並將ICMP數據包發送到目標主機。
如圖所示,若是建立一個32字節大小的數據包,那麼只有四個字節長度來存儲有效負載數據。
所以,以浮點格式(4個字節)存儲當前時間幀是比較好的解決辦法。
可是,因爲精度損失,不能使用此數據來計算總網絡延遲。
"!" 可是,因爲精度損失,我永遠不會使用此數據來計算總網絡延遲。web
構建和打包ICMP數據包源代碼:小程序
def receive_one_ping(icmp_socket, port_id, timeout, send_time): while True: # 1. 等待套接字並獲得回覆。 wait_for_data = select.select([icmp_socket], [], [], timeout) # 2. 一旦接受,記錄當前時間。 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. 檢查收發之間的 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 # 網絡沒法到達的錯誤。 elif type == 3 and code == 1: return 1 # 主機沒法到達的錯誤。
當從同一主機得到全部ping測試結果時,須要另外一個函數來顯示全部測量的最小時間,平均時間和最大延遲。瀏覽器
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. 查找主機名,將其解析爲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. 調用 doOnePing 函數。 ping_delay = do_one_ping(ip_addr, timeout, i) # 3. 打印出返回的延遲信息。 if ping_delay == 0 or ping_delay == 1: # 獲取本機的 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 = "") # 若是能夠成功接收數據包, # 在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. 繼續執行直到結束。 ......
C:\Users\asus\Desktop\lab_solution\ICMP 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:\Users\asus\Desktop\lab_solution\ICMP 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:\Users\asus\Desktop\lab_solution\ICMP 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:\Users\asus\Desktop\lab_solution\ICMP 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.
此任務是從新建立第3講(延遲,丟失和吞吐量)中的路由追蹤工具。這用於測量主機和到達目的地的路徑上的每一跳之間的延遲。在實際應用中,路由追蹤能夠找到源主機和目標主機之間的路由器以及到達每一個路由器所需的時間。服務器
如上圖所示,源主機使用ICMP echo請求報文,但有一個重要的修改:
存活時間(TTL)的值初始爲1。這能夠確保咱們從第一跳得到響應。 一旦報文到達路由器,TTL計數器就會遞減。
當TTL達到0時,報文將返回到源主機,ICMP 類型爲11(已超出TTL且IP數據報還沒有到達目標並被丟棄)。
每次增長TTL都會重複此過程,直到咱們收到回覆。若是echo回覆ICMP類型爲0,則表示IP數據報已到達目的地。
而後咱們就能夠中止運行路由追蹤的腳本了。在此過程當中,可能會發生異常而且咱們須要處理錯誤代碼與 ICMP Ping 相同。
基於上述原理,首先,除了建立一個與協議ICMP關聯的套接字並設置超時來控制用於接收數據包的套接字外,還須要經過socket.setsockopt(level, optname, value)
函數設置套接字的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))
建立套接字後,須要實現一個函數來構建,打包並將ICMP數據包發送到目標主機。這部分代碼和在 ICMP Ping 中的構建和打包ICMP數據包源代碼一致。
下一步是等待並收到回覆。套接字將一直等待,直到收到數據包或達到超時限制。 經過 ICMP 迴響報文發現並報告沒法訪問目標主機和沒法訪問目標網路。這部分和接受數據包源碼類似但路由追蹤須要額外記錄每次訪問到路由器的IP地址。代碼以下所示:
def receive_one_trace(icmp_socket, send_time, timeout): try: # 1. 等待套接字並獲得回覆。 ... Similar to Receive Packet Source ... # 2. 一旦接受,記錄當前時間。 rec_packet, retr_addr = icmp_socket.recvfrom(1024) # 3. 解壓包首部行查找有用的信息。 ... Similar to Receive Packet Source ... # 4. 經過代號類型檢查數據包是否丟失。 ... Similar to Receive Packet Source ... except TimeoutError : print_str = "* " # 超時。 finally: ..... # 打印延遲時間。 # 返回當前路由器的IP地址。 # 若是超時的話,直接返回字符串。 try: retre_ip = retr_addr[0] except IndexError: retre_ip = "Request timeout" finally: return retre_ip
最後一件事是爲每一個路由器實現重複測量,解析在對各自主機名的響應中找到的IP地址並處理異常。代碼以下所示:
def trace_route(host, timeout=2): # 可配置超時,使用可選參數設置。 # 1. 查找主機名,將其解析爲IP地址。 ip_addr = socket.gethostbyname(host) ttl = 1 print("Over a maximum of {max_hop} hops:\n".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) + "\t")) cur_addr = do_three_trace(ip_addr, ttl, i, timeout) try: sys.stdout.write("{0:<}".format(" " + socket.gethostbyaddr(cur_addr)[0] + " [" + cur_addr + "]" + "\n")) except (socket.herror, socket.gaierror): sys.stdout.write("{0:<}".format(" " + cur_addr + "\n")) if cur_addr == ip_addr : break ttl += 1 sys.stdout.write("\nTrace complete.\n\n")
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:\Users\asus\Desktop\lab_solution\Traceroute>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.
此任務是構建一個簡單的HTTP Web服務器。根據第4講(Web 和 HTTP)中學習到的知識,Web 服務器是Internet的基礎部分,它們提供咱們熟悉的網頁和內容。
網頁由對象組成,這些對象能夠是HTML文件,JPEG圖像,Java小程序等。HTTP流量一般綁定到端口80,端口8080是經常使用的替代方案。所以,虛擬主機(機器)使用本地端口號80和8080。
HTTP/1.1 含有許多類型的 HTTP 請求,在本任務中,只考慮 HTTP GET 請求。
以下圖所示,一個簡單的 web 服務器從前端接受 HTTP 請求報文。收到此請求後,簡單 Web 服務器將從郵件中提取所請求對象的路徑,而後嘗試從硬盤中檢索請求的對象。若是它成功找到硬盤中的對象,它會將對象發送回具備相應首部行的客戶端(其中包含Status-Code 200)。不然,它將使用HTTP響應報文(將包含404 Not Found"狀態將"Not Found"網頁發送到客戶端。
line)". HTTP 請求和響應報文的格式已在下圖5.(a)和5.(b)顯示。
基於上述原理,首先,建立一個支持 IPv4 的套接字並將其綁定在高於1024的端口上。Web服務器應該同時監聽5個請求,並具備處理多個併發鏈接的能力。
Web 服務器運行源碼:
def start_server(server_port , server_address): # 將 web 服務器綁定到可配置端口,定義爲可選參數。 # 1. 常見一個服務器套接字。 server_socket = socket(AF_INET, SOCK_STREAM) #IPv4 # 2. 將服務器套接字綁定到服務器地址和服務器端口。 server_socket.bind(("", server_port)) # 3. 持續監聽與服務器套接字的鏈接。 server_socket.listen(5) while True: # 4. 當接受鏈接時,調用 handleRequest 函數,傳遞新的鏈接套接字。 connection_socket , (client_ip, client_port) = server_socket.accept() # 建立一個多線程服務器實現,可以處理多個併發鏈接。 start_new_thread(handle_request , (connection_socket , client_ip, client_port)) # 5. 關閉服務器端的套接字。 server_socket.close() # 否則服務器會一直監聽,不會主動關閉。 # 從這裏開始運行。 server_port = int(sys.argv[1]) server_address = gethostbyname(gethostname()) start_server(server_port)
建立套接字後,web 服務器須要處理HTTP GET請求。在處理以前,建立了一個StrProcess類來重寫字符串模塊中的split方法。用空格分割一個字符串,只處理請求行。所以,當它找到字符"r"時,它將中止處理並返回結果。
StrProcess 類源碼:
class StrProcess(str): def __init__(self, str): """用字符型變量 str 初始該屬性""" self.str = str def split_str(self): """實現分割操做""" 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] == "\r": break return spilt_list[1]
最後一件事是處理 HTTP GET 請求和異常。因爲非持久性HTTP,不須要在true循環時寫入以接收HTTP請求報文。若是套接字收到空報文,則應該關閉它。不然,web 服務器須要檢查對象是否存在於緩存中。若是對象存在,則使用"HTTP/1.1 200 OK rnrn"將對象發送到客戶端,不然發生"FileNotFoundError"異常,而後對於"未找到"的HTML文件使用"HTTP/1.1 404 Not Foundrnrn"發送到客戶端。 發送HTTP響應報文後,HTTP服務器將關閉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類建立新對象(handlestr)。 handle_str = StrProcess(msg) # 2. 從報文中提取所請求對象的路徑(HTTP 首部行的第二部分)。 file_name = handle_str.split_str()[1:] # 3. 從磁盤中查找相應的文件。 # 檢查請求的對象是否存在。 f = open(file_name) f.close() # 若是對象存在,準備發送 "HTTP/1.1 200 OK\r\n\r\n" 到套接字。 status = "200 OK" except FileNotFoundError: # 不然,準備發送 "HTTP/1.1 404 Not Found\r\n\r\n" 到套接字。 status = "404 Not Found" file_name = "NotFound.html" re_header = "HTTP/1.1 " + status + "\r\n\r\n" # 最後一個''\r\n'' 意味着頭報文的結束。 # 4. 發送正確的HTTP響應。 tcp_socket.send(re_header.encode()) # 5. 存儲在臨時緩衝區中 with open(file_name, 'rb') as f: file_content = f.readlines() # 6. 將文件的內容發送到套接字。 for strline in file_content: tcp_socket.send(strline) # 7. 關閉鏈接的套接字。 print("Bye to Client ({ip}: {port})".format(ip = client_ip, port = client_port)) tcp_socket.close()
編寫了一個單獨的HTTP客戶端來查詢 web 服務器。此客戶端能夠發送 HTTP GET 請求並在控制檯上接收HTTP響應報文。該程序的優勢是它只需輸入對象的名稱或選擇保留或離開便可查詢對象。
HTTP 客戶端源碼:
from socket import * import sys # 1. 設置 web 服務器的地址。 host_port = int(sys.argv[1]) host_address = gethostbyname(gethostname()) # 2. 建立客戶端套接字以啓動與 web 服務器的 TCP 鏈接。 tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect((host_address , host_port)) # 3. 輸入要查詢 web服務器的文件客戶端。 print("Hello, which document do you want to query?") while True: obj = input("I want to query: ") # 4. 發送 HTTP 請求報文。 message = "GET /" + obj + " HTTP/1.1\r\n" \ "Host: " + host_address + ":" + str(host_port) + "\r\n" \ "Connection: close\r\n" \ "Upgrade-Insecure-Requests: 1\r\n" \ "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\r\n" \ ...... # 首部行。 "Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7\r\n\r\n" tcp_client.send(message.encode()) while True: # 5. 接收HTTP響應報文並將其打印到控制檯。 data = tcp_client.recv(1024) if not data: break print("Web server responded to your request:") print(data.decode()) tcp_client.close() # 關閉當前的鏈接。 # 6. 詢問客戶是否要繼續。 ans = input('\nDo 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
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:\User\asus\Desktop\lab_solution\Web Server>python Client.py 8899 Hello, which document do you want to query? I want to query: hello.html
Web server responded to your request: HTTP/1.1 200 OK Web server responded to your request: <!DOCTYPE html> <html> <head> <title>Hello World HTML</title> </head> <body> <h1>Hello World</h1> </body> Web server responded to your request: </html> 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
此任務是構建一個簡單的 web 代理服務器。根據第4講(Web 和 HTTP)中所學到的知識, web 代理服務器充當客戶端和服務器,這意味着它具備 web 服務器和客戶端的全部功能。它和 web 服務器之間最顯着的區別是發送的請求報文
和響應報文都要經過 web 服務器傳遞。web 代理服務器在任何地方(大學,公司和住宅ISP)使用,以減小客戶請求和流量的響應時間。
web 代理服務器的原理基於 web 服務器。web 代理服務器從客戶端接收 HTTP 請求報文,並從請求行中提取方法類型。
簡化過程以下所示。
基於上述原理,首先,建立一個支持 IPv4 的套接字並將其綁定在高於1024的端口上。web 代理服務器和 web 服務器很是相似,惟一區別是它是單線程的。
我在 StrProcess 類中添加了其餘方法使 web 代理服務器獲取對象或鏈接到 web 服務器的效率更高。
StrProcess 類源碼:
class StrProcess(str): def __init__(self, str): """用字符型變量 str 初始該屬性""" self.str = str def split_str(self): """實現分割操做""" 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] == "\r": 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 請求報文實體行中提取數據""" body_start = self.str.find('\r\n\r\n') + 4 return self.str[body_start:] def get_referer(self): """從 HTTP 請求行中提取引用""" ref_pos = self.str.find('Referer: ') + 9 ref_stop = self.str.find('\r\n', 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): """將全部特殊符號轉換爲 "-"。""" 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中提取主機名""" 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
建立套接字後,web 代理服務器須要處理不一樣的 HTTP 請求類型和異常。在這部分中,文件處理應該以二進制格式使用,咱們必須考慮對象類型(能夠是.jpg,.svg,.ico等)。
處理 HTTP 請求源碼:
def start_listen(tcp_socket, client_ip, client_port): # 1. 在鏈接套接字上從客戶端接收請求報文。 message = tcp_socket.recv(1024).decode() # 從strProc類建立新對象(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\r\n\r\n") 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!") # 從磁盤中查找相應的文件(若是存在)。 for i in range(0, len(file_content)): tcp_socket.send(file_content[i]) # 發送 HTTP 響應報文。 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("\r\n\r\n") # 僅發送 HTTP 響應報文首部行。 tcp_socket.send(list_to_str[:HTTP_header_end+4].encode()) elif command == "PUT" or command == "POST": # 只實現上傳文件。 f = open("./Cache/" + filename, "ab") f.write(b"HTTP/1.1 200 OK\r\n\r\n" + handle_str.get_body().encode()) f.close() print("Update successfully!") tcp_socket.send(b"HTTP/1.1 200 OK\r\n\r\n") 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\r\n\r\n") # 4. 若是緩存中不存在該文件,則處理異常。 except (IOError, FileNotFoundError): if command == "GET": # 在代理服務器上建立套接字。 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 # 記錄真實的主機名。 request = "GET " + "http:/" + file + " HTTP/1.1\r\n\r\n" except: try: # 須要使用全局主機或引用主機名。 new_host = handle_str.get_referer() if host == "" else host c.connect((new_host, 80)) request = "GET " + "http://" + new_host + file + " HTTP/1.1\r\n\r\n" except: tcp_socket.send(b"HTTP/1.1 404 Not Found\r\n\r\n") 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()) # 將響應讀入緩衝區。 print("The proxy server has found the host.") # 在緩存中爲請求的文件建立一個新文件。 # 此外,將緩衝區中的響應發送到客戶端套接字和緩存中的相應文件。 writeFile = open("./Cache/" + filename, "wb") print("The proxy server is receiving data...") # 接受 HTTP 響應報文直到全部報文都被接收。 while True: data = c.recv(4096) if not data: break sys.stdout.write(">") # 將文件的內容發送到套接字。 tcp_socket.sendall(data) writeFile.write(data) writeFile.close() sys.stdout.write("100%\n") c.close() elif command == "DELETE": tcp_socket.send(b"HTTP/1.1 204 Not Content\r\n\r\n") except (ConnectionResetError, TypeError): print("Bye to client: {addr}:{port}".format(addr = client_ip, port = client_port)) # 關閉客戶端的套接字。 print("tcp socket closed\n") tcp_socket.close()
C:\Users\asus\Desktop\lab_solution\Web 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
若是個人文章能夠幫到您,勞煩您點進源碼點個 ★ Star 哦!
https://github.com/Hephaest/C...