黄色网页视频 I 影音先锋日日狠狠久久 I 秋霞午夜毛片 I 秋霞一二三区 I 国产成人片无码视频 I 国产 精品 自在自线 I av免费观看网站 I 日本精品久久久久中文字幕5 I 91看视频 I 看全色黄大色黄女片18 I 精品不卡一区 I 亚洲最新精品 I 欧美 激情 在线 I 人妻少妇精品久久 I 国产99视频精品免费专区 I 欧美影院 I 欧美精品在欧美一区二区少妇 I av大片网站 I 国产精品黄色片 I 888久久 I 狠狠干最新 I 看看黄色一级片 I 黄色精品久久 I 三级av在线 I 69色综合 I 国产日韩欧美91 I 亚洲精品偷拍 I 激情小说亚洲图片 I 久久国产视频精品 I 国产综合精品一区二区三区 I 色婷婷国产 I 最新成人av在线 I 国产私拍精品 I 日韩成人影音 I 日日夜夜天天综合

Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)

系統(tǒng) 2363 0

最后一次更新于 2019/07/10

ICMP Ping

目的

此任務(wù)是重新創(chuàng)建第3講(延遲,丟失和吞吐量)中討論的ping客戶端。
Ping 是一個(gè)用于在計(jì)算機(jī)網(wǎng)絡(luò)中測(cè)量延遲和丟失的工具。
在實(shí)際應(yīng)用中,我們可以通過(guò) ping 命令分析判斷網(wǎng)絡(luò)失敗的原因。當(dāng)然,這類信息也可用于幫助我們選擇性能更佳的IP地址作為代理服務(wù)器。

原理

Ping 通常使用 Internet 控制消息協(xié)議 ( ICMP ) 報(bào)文來(lái)測(cè)量網(wǎng)絡(luò)中的延遲和丟失:本機(jī)在 ICMP 包中發(fā)送回響請(qǐng)求(ICMP類型代碼為8)給另一個(gè)主機(jī)。然后,主機(jī)解包數(shù)據(jù)包并提取ICMP類型代碼并匹配請(qǐng)求和回復(fù)之間的ID。如果遠(yuǎn)程主機(jī)的響應(yīng)報(bào)文ICMP類型代碼為0,然后我們可以計(jì)算發(fā)送請(qǐng)求和接收回復(fù)之間經(jīng)過(guò)的時(shí)間,進(jìn)而精確的計(jì)算兩臺(tái)主機(jī)之間網(wǎng)絡(luò)的延遲。

注意 : IP數(shù)據(jù)報(bào)和ICMP錯(cuò)誤代碼的結(jié)構(gòu)(ICMP類型代碼為3)如下所示。因特網(wǎng)校驗(yàn)和也是數(shù)據(jù)包的重要部分,但它不是本函數(shù)實(shí)現(xiàn)的核心。
Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)_第1張圖片
Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)_第2張圖片

函數(shù)實(shí)現(xiàn)

基于上述原理,首先,需要?jiǎng)?chuàng)建一個(gè)與協(xié)議ICMP關(guān)聯(lián)的套接字,并設(shè)置超時(shí)以控制用于接收數(shù)據(jù)包的時(shí)間套接字。

          
            # 運(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)一個(gè)函數(shù)來(lái)構(gòu)建,打包并將ICMP數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī)。
如圖所示,如果創(chuàng)建一個(gè)32字節(jié)大小的數(shù)據(jù)包,那么只有四個(gè)字節(jié)長(zhǎng)度來(lái)存儲(chǔ)有效負(fù)載數(shù)據(jù)。
因此,以浮點(diǎn)格式(4個(gè)字節(jié))存儲(chǔ)當(dāng)前時(shí)間幀是比較好的解決辦法。
但是,由于精度損失,不能使用此數(shù)據(jù)來(lái)計(jì)算總網(wǎng)絡(luò)延遲。
"!" 但是,由于精度損失,我永遠(yuǎn)不會(huì)使用此數(shù)據(jù)來(lái)計(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)前時(shí)間。
        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. 返回比特大小,延遲率和存活時(shí)間。
            return payload_size * 8, delay_time, ttl
        elif type == 3 and code == 0:
            return 0  # 網(wǎng)絡(luò)無(wú)法到達(dá)的錯(cuò)誤。
        elif type == 3 and code == 1:
            return 1  # 主機(jī)無(wú)法到達(dá)的錯(cuò)誤。
          
        

當(dāng)從同一主機(jī)獲得所有ping測(cè)試結(jié)果時(shí),需要另一個(gè)函數(shù)來(lái)顯示所有測(cè)量的最小時(shí)間,平均時(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錯(cuò)誤代碼和返回值的超時(shí)。代碼如下所示:

          
            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 是序列的值
        # 打印報(bào)文首部行
        ......
        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里追加延遲時(shí)間。
                successful_list.append(delay_time)
                # 如果延遲時(shí)間小于 1 ms,則記為0。
                ......
        except TimeoutError:  # 超時(shí)類型
            lost += 1
            print("Request timed out.")
            # 如果它不總是超時(shí)的情況,
            # 我們需要計(jì)算最大延遲時(shí)間。
            if timedout_mark is False:
                timedout_mark = True
        time.sleep(1)  # 每秒。
    #  4. 繼續(xù)執(zhí)行直到結(jié)束。
    ......
          
        

輸出結(jié)果

          
            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.
          
        

路由追蹤

目的

此任務(wù)是重新創(chuàng)建第3講(延遲,丟失和吞吐量)中的路由追蹤工具。這用于測(cè)量主機(jī)和到達(dá)目的地的路徑上的每一跳之間的延遲。在實(shí)際應(yīng)用中,路由追蹤可以找到源主機(jī)和目標(biāo)主機(jī)之間的路由器以及到達(dá)每個(gè)路由器所需的時(shí)間。

原理

Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)_第3張圖片

如上圖所示,源主機(jī)使用ICMP echo請(qǐng)求報(bào)文,但有一個(gè)重要的修改:
存活時(shí)間 ( TTL )的值初始為1。這可以確保我們從第一跳獲得響應(yīng)。 一旦報(bào)文到達(dá)路由器,TTL計(jì)數(shù)器就會(huì)遞減。
當(dāng)TTL達(dá)到0時(shí),報(bào)文將返回到源主機(jī),ICMP 類型為11(已超出TTL且IP數(shù)據(jù)報(bào)尚未到達(dá)目標(biāo)并被丟棄)。
每次增加TTL都會(huì)重復(fù)此過(guò)程,直到我們收到回復(fù)。如果echo回復(fù)ICMP類型為0,則表示IP數(shù)據(jù)報(bào)已到達(dá)目的地。
然后我們就可以停止運(yùn)行路由追蹤的腳本了。在此過(guò)程中,可能會(huì)發(fā)生異常并且我們需要處理錯(cuò)誤代碼與 ICMP Ping 相同。

函數(shù)實(shí)現(xiàn)

基于上述原理,首先,除了創(chuàng)建一個(gè)與協(xié)議ICMP關(guān)聯(lián)的套接字并設(shè)置超時(shí)來(lái)控制用于接收數(shù)據(jù)包的套接字外,還需要通過(guò)
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)一個(gè)函數(shù)來(lái)構(gòu)建,打包并將ICMP數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī)。這部分代碼和在 ICMP Ping 中的 構(gòu)建和打包ICMP數(shù)據(jù)包源代碼 一致。

下一步是等待并收到回復(fù)。套接字將一直等待,直到收到數(shù)據(jù)包或達(dá)到超時(shí)限制。 通過(guò) ICMP 回響報(bào)文發(fā)現(xiàn)并報(bào)告 無(wú)法訪問(wèn)目標(biāo)主機(jī) 無(wú)法訪問(wèn)目標(biāo)網(wǎng)路 。這部分和 接受數(shù)據(jù)包源碼 相似但路由追蹤需要額外記錄每次訪問(wèn)到路由器的IP地址。代碼如下所示:

          
            def receive_one_trace(icmp_socket, send_time, timeout):
    try:
        # 1. 等待套接字并得到回復(fù)。
        ... Similar to Receive Packet Source ...
        # 2. 一旦接受,記錄當(dāng)前時(shí)間。
        rec_packet, retr_addr = icmp_socket.recvfrom(1024)
        # 3. 解壓包首部行查找有用的信息。
        ... Similar to Receive Packet Source ...
        # 4. 通過(guò)代號(hào)類型檢查數(shù)據(jù)包是否丟失。
        ... Similar to Receive Packet Source ...
    except TimeoutError :
        print_str = "*   "  # 超時(shí)。
    finally:
        ..... # 打印延遲時(shí)間。
    # 返回當(dāng)前路由器的IP地址。
    # 如果超時(shí)的話,直接返回字符串。
    try:
        retre_ip = retr_addr[0]
    except IndexError:
        retre_ip = "Request timeout"
    finally:
        return retre_ip
          
        

最后一件事是為每個(gè)路由器實(shí)現(xiàn)重復(fù)測(cè)量,解析在對(duì)各自主機(jī)名的響應(yīng)中找到的IP地址并處理異常。代碼如下所示:

          
            def trace_route(host, timeout=2):
    # 可配置超時(shí),使用可選參數(shù)設(shè)置。
    # 1. 查找主機(jī)名,將其解析為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")
          
        

輸出結(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:\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服務(wù)器

目的

此任務(wù)是構(gòu)建一個(gè)簡(jiǎn)單的HTTP Web服務(wù)器。根據(jù)第4講(Web 和 HTTP)中學(xué)習(xí)到的知識(shí),Web 服務(wù)器是Internet的基礎(chǔ)部分,它們提供我們熟悉的網(wǎng)頁(yè)和內(nèi)容。
網(wǎng)頁(yè)由對(duì)象組成,這些對(duì)象可以是HTML文件,JPEG圖像,Java小程序等。HTTP流量通常綁定到端口80,端口8080是常用的替代方案。因此,虛擬主機(jī)(機(jī)器)使用本地端口號(hào)80和8080。

原理

HTTP/1.1 含有許多類型的 HTTP 請(qǐng)求,在本任務(wù)中,只考慮 HTTP GET 請(qǐng)求。
如下圖所示,一個(gè)簡(jiǎn)單的 web 服務(wù)器從前端接受 HTTP 請(qǐng)求報(bào)文。收到此請(qǐng)求后,簡(jiǎn)單 Web 服務(wù)器將從郵件中提取所請(qǐng)求對(duì)象的路徑,然后嘗試從硬盤中檢索請(qǐng)求的對(duì)象。如果它成功找到硬盤中的對(duì)象,它會(huì)將對(duì)象發(fā)送回具有相應(yīng)首部行的客戶端(其中包含Status-Code 200)。否則,它將使用HTTP響應(yīng)報(bào)文(將包含404 Not Found"狀態(tài)將"Not Found"網(wǎng)頁(yè)發(fā)送到客戶端。
line)". HTTP 請(qǐng)求和響應(yīng)報(bào)文的格式已在下圖5.(a)和5.(b)顯示。
Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)_第4張圖片
Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)_第5張圖片

函數(shù)實(shí)現(xiàn)

基于上述原理,首先,創(chuàng)建一個(gè)支持 IPv4 的套接字并將其綁定在高于1024的端口上。Web服務(wù)器應(yīng)該同時(shí)監(jiān)聽(tīng)5個(gè)請(qǐng)求,并具有處理多個(gè)并發(fā)連接的能力。

Web 服務(wù)器運(yùn)行源碼:

          
            def start_server(server_port , server_address):
    # 將 web 服務(wù)器綁定到可配置端口,定義為可選參數(shù)。
    # 1. 常見(jiàn)一個(gè)服務(wù)器套接字。
    server_socket = socket(AF_INET, SOCK_STREAM) #IPv4
    # 2. 將服務(wù)器套接字綁定到服務(wù)器地址和服務(wù)器端口。
    server_socket.bind(("", server_port))
    # 3. 持續(xù)監(jiān)聽(tīng)與服務(wù)器套接字的連接。
    server_socket.listen(5)
    while True:
        # 4. 當(dāng)接受連接時(shí),調(diào)用 handleRequest 函數(shù),傳遞新的連接套接字。
        connection_socket , (client_ip, client_port) = server_socket.accept()
        # 創(chuàng)建一個(gè)多線程服務(wù)器實(shí)現(xiàn),能夠處理多個(gè)并發(fā)連接。
        start_new_thread(handle_request , (connection_socket , client_ip, client_port))
     # 5. 關(guān)閉服務(wù)器端的套接字。
     server_socket.close() # 不然服務(wù)器會(huì)一直監(jiān)聽(tīng),不會(huì)主動(dòng)關(guān)閉。

 # 從這里開(kāi)始運(yùn)行。
 server_port = int(sys.argv[1])
 server_address = gethostbyname(gethostname())
 start_server(server_port)
          
        

創(chuàng)建套接字后,web 服務(wù)器需要處理HTTP GET請(qǐng)求。在處理之前,創(chuàng)建了一個(gè)StrProcess類來(lái)重寫字符串模塊中的split方法。用空格分割一個(gè)字符串,只處理請(qǐng)求行。因此,當(dāng)它找到字符"r"時(shí),它將停止處理并返回結(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] == "\r":
                break
        return spilt_list[1]
          
        

最后一件事是處理 HTTP GET 請(qǐng)求和異常。由于非持久性HTTP,不需要在true循環(huán)時(shí)寫入以接收HTTP請(qǐng)求報(bào)文。如果套接字收到空?qǐng)?bào)文,則應(yīng)該關(guān)閉它。否則,web 服務(wù)器需要檢查對(duì)象是否存在于緩存中。如果對(duì)象存在,則使用"HTTP/1.1 200 OK rnrn"將對(duì)象發(fā)送到客戶端,否則發(fā)生"FileNotFoundError"異常,然后對(duì)于"未找到"的HTML文件使用"HTTP/1.1 404 Not Foundrnrn"發(fā)送到客戶端。 發(fā)送HTTP響應(yīng)報(bào)文后,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. 在連接套接字上從客戶端接收請(qǐng)求報(bào)文。
        msg = tcp_socket.recv(1024).decode()
        if not msg:
            print("Error! server receive empty HTTP request.")
            tcp_socket.close()
        # 從strProc類創(chuàng)建新對(duì)象(handlestr)。
        handle_str = StrProcess(msg)
        # 2. 從報(bào)文中提取所請(qǐng)求對(duì)象的路徑(HTTP 首部行的第二部分)。
        file_name = handle_str.split_str()[1:]
        # 3. 從磁盤中查找相應(yīng)的文件。
        # 檢查請(qǐng)求的對(duì)象是否存在。
        f = open(file_name)
        f.close()
        # 如果對(duì)象存在,準(zhǔn)備發(fā)送 "HTTP/1.1 200 OK\r\n\r\n" 到套接字。
        status = "200 OK"
    except FileNotFoundError:
        # 否則,準(zhǔn)備發(fā)送 "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" # 最后一個(gè)''\r\n'' 意味著頭報(bào)文的結(jié)束。
    # 4. 發(fā)送正確的HTTP響應(yīng)。
    tcp_socket.send(re_header.encode())
    # 5. 存儲(chǔ)在臨時(shí)緩沖區(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()
          
        

編寫了一個(gè)單獨(dú)的HTTP客戶端來(lái)查詢 web 服務(wù)器。此客戶端可以發(fā)送 HTTP GET 請(qǐng)求并在控制臺(tái)上接收HTTP響應(yīng)報(bào)文。該程序的優(yōu)點(diǎn)是它只需輸入對(duì)象的名稱或選擇保留或離開(kāi)即可查詢對(duì)象。

HTTP 客戶端源碼:

          
            from socket import *
import sys

# 1. 設(shè)置 web 服務(wù)器的地址。
host_port = int(sys.argv[1])
host_address = gethostbyname(gethostname())
# 2. 創(chuàng)建客戶端套接字以啟動(dò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 請(qǐng)求報(bào)文。
    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響應(yīng)報(bào)文并將其打印到控制臺(tái)。
        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. 詢問(wèn)客戶是否要繼續(xù)。
    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
          
        

輸出結(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:\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代理服務(wù)器

目的

此任務(wù)是構(gòu)建一個(gè)簡(jiǎn)單的 web 代理服務(wù)器。根據(jù)第4講(Web 和 HTTP)中所學(xué)到的知識(shí), web 代理服務(wù)器充當(dāng)客戶端和服務(wù)器,這意味著它具有 web 服務(wù)器和客戶端的所有功能。它和 web 服務(wù)器之間最顯著的區(qū)別是發(fā)送的請(qǐng)求報(bào)文
和響應(yīng)報(bào)文都要通過(guò) web 服務(wù)器傳遞。web 代理服務(wù)器在任何地方(大學(xué),公司和住宅ISP)使用,以減少客戶請(qǐng)求和流量的響應(yīng)時(shí)間。

原理

web 代理服務(wù)器的原理基于 web 服務(wù)器。web 代理服務(wù)器從客戶端接收 HTTP 請(qǐng)求報(bào)文,并從請(qǐng)求行中提取方法類型。

  • 如果請(qǐng)求類型是 GET ,從同一行獲取URL并檢查請(qǐng)求的對(duì)象是否存在于緩存中。否則,web 代理服務(wù)器會(huì)將客戶端的請(qǐng)求轉(zhuǎn)發(fā)給 web 服務(wù)器。然后,web 服務(wù)器將生成響應(yīng)報(bào)文并將其傳遞給 web 代理服務(wù)器,web 代理服務(wù)器又將其發(fā)送到客戶端并為將來(lái)的請(qǐng)求緩存副本。
  • 如果請(qǐng)求類型是 DELETE ,代理服務(wù)器會(huì)首先確認(rèn)請(qǐng)求,如果對(duì)象存在緩存中,j只是從緩存中刪除它并發(fā)送帶有 Status-Code 200 的 HTTP 響應(yīng)報(bào)文。否則,web 代理服務(wù)器發(fā)送帶有 Status-Code 404 的 HTTP 響應(yīng)報(bào)文。
  • 如果請(qǐng)求類型是 POST , 處理過(guò)程比上述方法類型更容易,web 代理服務(wù)器只是以二進(jìn)制格式將對(duì)象寫入磁盤,然后發(fā)送帶有 Status-Code 200 的 HTTP 響應(yīng)報(bào)文(輸入在實(shí)體行中上傳)。如果方法是 PUT,則只需要在實(shí)體行中返回 true。
  • 如果請(qǐng)求類型是 HEAD , web 代理服務(wù)器僅返回 HTTP 響應(yīng)報(bào)文的報(bào)文首部行和狀態(tài)行。

簡(jiǎn)化過(guò)程如下所示。
Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)_第6張圖片

函數(shù)實(shí)現(xiàn)

基于上述原理,首先,創(chuàng)建一個(gè)支持 IPv4 的套接字并將其綁定在高于1024的端口上。web 代理服務(wù)器和 web 服務(wù)器非常類似,唯一區(qū)別是它是單線程的。

我在 StrProcess 類中添加了其他方法使 web 代理服務(wù)器獲取對(duì)象或連接到 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] == "\r":
                break
        try:
            return spilt_list[0],spilt_list[1]
        except IndexError:
            return None

    def get_cmd_type(self):
        """從 HTTP 請(qǐng)求行中提取請(qǐng)求方法"""
        return self.split_str()[0]

    def get_body(self):
        """從 HTTP 請(qǐng)求報(bào)文實(shí)體行中提取數(shù)據(jù)"""
        body_start = self.str.find('\r\n\r\n') + 4
        return self.str[body_start:]

    def get_referer(self):
        """從 HTTP 請(qǐng)求行中提取引用"""
        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 請(qǐng)求請(qǐng)求行中提取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):
        """將所有特殊符號(hào)轉(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 請(qǐng)求類型和異常。在這部分中,文件處理應(yīng)該以二進(jìn)制格式使用,我們必須考慮對(duì)象類型(可以是.jpg,.svg,.ico等)。

處理 HTTP 請(qǐng)求源碼:

          
            def start_listen(tcp_socket, client_ip, client_port):

    # 1. 在連接套接字上從客戶端接收請(qǐng)求報(bào)文。
    message = tcp_socket.recv(1024).decode()
    # 從strProc類創(chuàng)建新對(duì)象(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. 從報(bào)文中提取所請(qǐng)求對(duì)象的路徑(HTTP 首部行的第二部分)。
        filename = handle_str.change_name()
        # 3. 找到特定的方法類型并處理請(qǐng)求。
        if command == "DELETE" :
            # 刪除緩存中存在的對(duì)象
            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))
            # 檢查請(qǐng)求的對(duì)象是否存在。
            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)報(bào)文。
            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")
                # 僅發(fā)送 HTTP 響應(yīng)報(bào)文首部行。
                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\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":
            # 在代理服務(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\r\n\r\n"
            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\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())
                # 將響應(yīng)讀入緩沖區(qū)。
                print("The proxy server has found the host.")
                # 在緩存中為請(qǐng)求的文件創(chuàng)建一個(gè)新文件。
                # 此外,將緩沖區(qū)中的響應(yīng)發(fā)送到客戶端套接字和緩存中的相應(yīng)文件。
                writeFile = open("./Cache/" + filename, "wb")
                print("The proxy server is receiving data...")
                # 接受 HTTP 響應(yīng)報(bào)文直到所有報(bào)文都被接收。
                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%\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))
    # 關(guān)閉客戶端的套接字。
    print("tcp socket closed\n")
    tcp_socket.close()
          
        

輸出結(jié)果

瀏覽器測(cè)試

Python 實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)應(yīng)用程序開(kāi)發(fā)_第7張圖片

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
          
        

源碼

如果我的文章可以幫到您,勞煩您點(diǎn)進(jìn)源碼點(diǎn)個(gè) ★ Star 哦!
https://github.com/Hephaest/C...


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦?。?!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論