最后一次更新于
2019/07/10
ICMP Ping
目的
此任務是重新創建第3講(延遲,丟失和吞吐量)中討論的ping客戶端。
Ping 是一個用于在計算機網絡中測量延遲和丟失的工具。
在實際應用中,我們可以通過
ping
命令分析判斷網絡失敗的原因。當然,這類信息也可用于幫助我們選擇性能更佳的IP地址作為代理服務器。
原理
Ping 通常使用 Internet 控制消息協議 ( ICMP ) 報文來測量網絡中的延遲和丟失:本機在 ICMP 包中發送回響請求(ICMP類型代碼為8)給另一個主機。然后,主機解包數據包并提取ICMP類型代碼并匹配請求和回復之間的ID。如果遠程主機的響應報文ICMP類型代碼為0,然后我們可以計算發送請求和接收回復之間經過的時間,進而精確的計算兩臺主機之間網絡的延遲。
注意
: IP數據報和ICMP錯誤代碼的結構(ICMP類型代碼為3)如下所示。因特網校驗和也是數據包的重要部分,但它不是本函數實現的核心。
函數實現
基于上述原理,首先,需要創建一個與協議ICMP關聯的套接字,并設置超時以控制用于接收數據包的時間套接字。
# 運行特權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個字節)存儲當前時間幀是比較好的解決辦法。
但是,由于精度損失,不能使用此數據來計算總網絡延遲。
"!" 但是,由于精度損失,我永遠不會使用此數據來計算總網絡延遲。
構建和打包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.
Web服務器
目的
此任務是構建一個簡單的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
輸出結果
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:\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
Client.py
Web server responded to your request:
HTTP/1.1 200 OK
Web server responded to your request:
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
Web代理服務器
目的
此任務是構建一個簡單的 web 代理服務器。根據第4講(Web 和 HTTP)中所學到的知識, web 代理服務器充當客戶端和服務器,這意味著它具有 web 服務器和客戶端的所有功能。它和 web 服務器之間最顯著的區別是發送的請求報文
和響應報文都要通過 web 服務器傳遞。web 代理服務器在任何地方(大學,公司和住宅ISP)使用,以減少客戶請求和流量的響應時間。
原理
web 代理服務器的原理基于 web 服務器。web 代理服務器從客戶端接收 HTTP 請求報文,并從請求行中提取方法類型。
- 如果請求類型是 GET ,從同一行獲取URL并檢查請求的對象是否存在于緩存中。否則,web 代理服務器會將客戶端的請求轉發給 web 服務器。然后,web 服務器將生成響應報文并將其傳遞給 web 代理服務器,web 代理服務器又將其發送到客戶端并為將來的請求緩存副本。
- 如果請求類型是 DELETE ,代理服務器會首先確認請求,如果對象存在緩存中,j只是從緩存中刪除它并發送帶有 Status-Code 200 的 HTTP 響應報文。否則,web 代理服務器發送帶有 Status-Code 404 的 HTTP 響應報文。
- 如果請求類型是 POST , 處理過程比上述方法類型更容易,web 代理服務器只是以二進制格式將對象寫入磁盤,然后發送帶有 Status-Code 200 的 HTTP 響應報文(輸入在實體行中上傳)。如果方法是 PUT,則只需要在實體行中返回 true。
- 如果請求類型是 HEAD , 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()
輸出結果
瀏覽器測試
WebProxy.py
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...
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
