python網(wǎng)絡(luò)編程基礎(chǔ)(連載)10 靜態(tài)web實(shí)現(xiàn)

gitbook鏈接:用python帶你進(jìn)入AI中的深度學(xué)習(xí)技術(shù)領(lǐng)域https://www.gitbook.com/book/scrappyzhang/python_to_deeplearn/details

github鏈接:https://github.com/ScrappyZhang/python_web_Crawler_DA_ML_DL

9 基于socket的靜態(tài)web服務(wù)器

前面8章我們學(xué)習(xí)了網(wǎng)絡(luò)編程最基礎(chǔ)的概念和在python中的使用方法碌燕。為了更充分的體現(xiàn)這些基礎(chǔ)概念的重要性罚舱,本節(jié)綜合所有基礎(chǔ)知識(shí)帶大家一同使用學(xué)習(xí)到的知識(shí)來(lái)實(shí)踐編寫一個(gè)靜態(tài)web服務(wù)器——python web server,簡(jiǎn)稱PWB监氢。

9.1 顯示固定頁(yè)面的web服務(wù)器

這一節(jié)為了更好的理解靜態(tài)web服務(wù)器的搭建過(guò)程,我們先來(lái)搭建一個(gè)只能顯示固定頁(yè)面內(nèi)容的web服務(wù)器衅码。如何實(shí)現(xiàn)呢拯刁?通過(guò)面向?qū)ο蟮乃季S,我們可以先創(chuàng)建一個(gè)web服務(wù)器類HTTPServer肆良,然后實(shí)例化調(diào)用運(yùn)行即可筛璧。一般情況下web服務(wù)器都是一直在運(yùn)行并處理客戶端請(qǐng)求逸绎,因此我們的這個(gè)web服務(wù)器類除了初始化__init__方法外惹恃,還需要一個(gè)永久運(yùn)行監(jiān)聽(tīng)客戶端(瀏覽器)連接的方法serve_forever和一個(gè)連接成功后向客戶端(瀏覽器)發(fā)送響應(yīng)數(shù)據(jù)的方法handlerequest。代碼框架如下:

import socket

SERVER_ADDR = (HOST, PORT) = '', 8888  # 服務(wù)器地址
VERSION = 1.0  # web服務(wù)器版本號(hào)


class HTTPServer():
    def __init__(self, server_address):
        """初始化服務(wù)器TCP套接字"""
        pass

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        pass

    def handlerequest(self, client_socket):
        """客戶端請(qǐng)求處理棺牧,發(fā)送響應(yīng)數(shù)據(jù)"""
        pass


def run():
    """運(yùn)行服務(wù)器"""
    pwb = HTTPServer(SERVER_ADDR)
    print('web server:PWB %s on port %d...\n' % (VERSION, PORT))
    pwb.serve_forever()


if __name__ == '__main__':
    run()

運(yùn)行一下巫糙,可以看到終端輸出一下信息提示web服務(wù)器在8888端口運(yùn)行,由于它并沒(méi)有任何功能颊乘,所以僅僅是提示信息参淹。

web server:PWB 1.0 on port 8888...

接下來(lái)我們需要做的就是將代碼框架中的空缺部分一一填滿。

根據(jù)TCP服務(wù)器的搭建過(guò)程乏悄,我們知道浙值,HTTPServer類的初始化方法中應(yīng)當(dāng)實(shí)現(xiàn)這些功能:TCP服務(wù)套接字的創(chuàng)建、地址綁定并啟動(dòng)監(jiān)聽(tīng)檩小。

    def __init__(self, server_address):
        """初始化服務(wù)器TCP套接字"""
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.tcp_socket.bind(server_address)
        self.tcp_socket.listen(128)

永久運(yùn)行監(jiān)聽(tīng)接收連接的方法serve_forevet主要功能是實(shí)現(xiàn)連接上客戶端开呐,并調(diào)用客戶端請(qǐng)求處理方法self.handlerequest。

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            client_socket, client_address = self.tcp_socket.accept()
            print(client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
            self.handlerequest(client_socket)

客戶端請(qǐng)求處理方法handlerequest則需要獲取客戶端(瀏覽器)發(fā)送來(lái)的請(qǐng)求數(shù)據(jù),并經(jīng)過(guò)解析后向?yàn)g覽器發(fā)送一組包括響應(yīng)頭和響應(yīng)體的數(shù)據(jù)筐付。這里由于我們是返回一個(gè)固定的內(nèi)容hello PWB卵惦,因此我們只要接收到了瀏覽器的請(qǐng)求,便可以發(fā)送固定的響應(yīng)成功頭和固定的響應(yīng)體hello PWB給瀏覽器瓦戚。

    def handlerequest(self, client_socket):
        """客戶端請(qǐng)求處理沮尿,發(fā)送響應(yīng)數(shù)據(jù)"""
        # 收取瀏覽器請(qǐng)求信息,并在服務(wù)器端打印顯示
        request_data = client_socket.recv(2048).decode('utf-8')
        request_header_lines = request_data.splitlines()
        for line in request_header_lines:
            print(line)

        # 響應(yīng)信息:本例顯示固定內(nèi)容:響應(yīng)成功 + hello PWB
        resp_headers = "HTTP/1.1 200 OK\r\n"  # 200代表響應(yīng)成功并找到資源
        resp_headers += "Server: PWB" + str(VERSION) + '\r\n'  # 告訴瀏覽器服務(wù)器
        resp_headers += '\r\n'  # 空行隔開(kāi)body
        resp_body = 'hello PWB\r\n'  # 固定的顯示內(nèi)容
        resp_data = resp_headers + resp_body

        # 發(fā)送相應(yīng)數(shù)據(jù)至瀏覽器
        client_socket.send(resp_data.encode('utf-8'))
        client_socket.close()  # HTTP短連接较解,請(qǐng)求完即關(guān)閉TCP連接

這樣子畜疾,我們的代碼框架就根據(jù)需求功能填充完畢了。

完整代碼如下:

'''net09_web_PWB1.py'''
import socket

SERVER_ADDR = (HOST, PORT) = '', 8888  # 服務(wù)器地址
VERSION = 1.0  # web服務(wù)器版本號(hào)


class HTTPServer():
    def __init__(self, server_address):
        """初始化服務(wù)器TCP套接字"""
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.tcp_socket.bind(server_address)
        self.tcp_socket.listen(128)

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            client_socket, client_address = self.tcp_socket.accept()
            print(client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
            self.handlerequest(client_socket)

    def handlerequest(self, client_socket):
        """客戶端請(qǐng)求處理印衔,發(fā)送響應(yīng)數(shù)據(jù)"""
        # 收取瀏覽器請(qǐng)求信息庸疾,并在服務(wù)器端打印顯示
        request_data = client_socket.recv(2048).decode('utf-8')
        request_header_lines = request_data.splitlines()
        for line in request_header_lines:
            print(line)

        # 響應(yīng)信息:本例顯示固定內(nèi)容:響應(yīng)成功 + hello PWB
        resp_headers = "HTTP/1.1 200 OK\r\n"  # 200代表響應(yīng)成功并找到資源
        resp_headers += "Server: PWB" + str(VERSION) + '\r\n'  # 告訴瀏覽器服務(wù)器
        resp_headers += '\r\n'  # 空行隔開(kāi)body
        resp_body = 'hello PWB'  # 固定的顯示內(nèi)容
        resp_data = resp_headers + resp_body

        # 發(fā)送相應(yīng)數(shù)據(jù)至瀏覽器
        client_socket.send(resp_data.encode('utf-8'))
        client_socket.close()  # HTTP短連接,請(qǐng)求完即關(guān)閉TCP連接


def run():
    """運(yùn)行服務(wù)器"""
    pwb = HTTPServer(SERVER_ADDR)
    print('web server:PWB %s on port %d...\n' % (VERSION, PORT))
    pwb.serve_forever()


if __name__ == '__main__':
    run()

運(yùn)行服務(wù)器程序当编,并在瀏覽器中輸入localhost:8888來(lái)訪問(wèn)web服務(wù)器届慈,我們可以看到顯示的內(nèi)容為hello PWB。打開(kāi)檢查忿偷,我們可以對(duì)比查看瀏覽器中顯示的各項(xiàng)請(qǐng)求響應(yīng)信息和我們web服務(wù)器打印顯示的請(qǐng)求信息金顿,完全一致。

由于我們的請(qǐng)求處理中無(wú)論接收到什么信息鲤桥,都是向?yàn)g覽器發(fā)送hello PWB揍拆,因此當(dāng)我們輸入localhost:8888/11時(shí)也是顯示hello PWB。這就是固定內(nèi)容顯示的web服務(wù)器茶凳。

websocket1.png

細(xì)心的讀者可能會(huì)發(fā)現(xiàn)嫂拴,瀏覽器還請(qǐng)求了一個(gè)名叫favicon.ico的文件。這個(gè)文件一般為網(wǎng)站標(biāo)識(shí)圖片贮喧,即網(wǎng)頁(yè)標(biāo)簽頁(yè)頂部顯示的圖片筒狠,如下圖所示。

websocket2.png

9.2 顯示指定需要內(nèi)容的web服務(wù)器

一個(gè)WEB服務(wù)器不可能只顯示一個(gè)頁(yè)面箱沦,肯定有特定需求的特定頁(yè)面顯示辩恼,怎么實(shí)現(xiàn)呢?谓形。讀者在上一節(jié)很清楚的看到GET 后的信息類似我們常見(jiàn)的資源文件夾那樣的路徑一樣灶伊。是的,這就是靜態(tài)資源的路徑寒跳。我們可以通過(guò)判斷這里來(lái)識(shí)別瀏覽器請(qǐng)求的是哪個(gè)靜態(tài)資源聘萨。那我們就需要在HTTPServer類中的handlerequest方法修改代碼來(lái)解析瀏覽器請(qǐng)求頭并返回不同的響應(yīng)體

注:在講解演示之前童太,我們?cè)诖a同文件夾目錄下創(chuàng)建了一個(gè)static文件夾米辐,里面有baidu.html碾牌、oschina.html和gitee.html三個(gè)html文件。它們分別是百度儡循、開(kāi)源中國(guó)和碼云三個(gè)網(wǎng)站的首頁(yè)源碼舶吗。讀者可以通過(guò)我們之前的并發(fā)下載網(wǎng)頁(yè)的源碼經(jīng)過(guò)適當(dāng)調(diào)整來(lái)下載生成這三個(gè)html文件;也可以直接在瀏覽器中分別打開(kāi)相應(yīng)的主頁(yè)择膝,將網(wǎng)頁(yè)源碼拷貝在本地建立三個(gè)html文件誓琼。本節(jié)將講解根據(jù)瀏覽器輸入U(xiǎn)RL的不同來(lái)請(qǐng)求獲取這三個(gè)html,就像訪問(wèn)真正的三個(gè)網(wǎng)站一樣肴捉。

首先我們需要解析請(qǐng)求信息腹侣。請(qǐng)求信息的第一行g(shù)et內(nèi)容為請(qǐng)求文件的路徑,通過(guò)正則表達(dá)式便可以將其抓取出來(lái)齿穗。代碼如下:

        pattern = r'[^/]+(/[^ ]*)'
        request_html_name = re.match(pattern, request_header_lines[0]).group(1)

一般web服務(wù)器都會(huì)在靜態(tài)資源URL的基礎(chǔ)之上來(lái)添加一些路徑來(lái)實(shí)現(xiàn)避免被用戶訪問(wèn)到不該訪問(wèn)的內(nèi)容傲隶。后面我們會(huì)看到本例中URL為localhost:8888/baidu.html,實(shí)際上調(diào)用的是./static/baidu.html文件窃页。假如我們作為web服務(wù)器放一個(gè)非常機(jī)密的文件password.txt跺株,這種常見(jiàn)的文件名稱和類型,很容易會(huì)被黑客等直接通過(guò)localhost:8888/password.txt獲取到并造成數(shù)據(jù)泄露脖卖。所以一般web服務(wù)器內(nèi)部都會(huì)對(duì)靜態(tài)資源路徑有一套編碼解碼機(jī)制來(lái)加密乒省。這里我們假設(shè)我們的默認(rèn)頁(yè)面就是baidu.html。即localhost:8888畦木、localhost:8888/袖扛、localhost:8888/baidu.html顯示的內(nèi)容一致。

        if request_html_name == '/':
            request_html_name = STATIC_PATH + 'baidu.html'
        else:
            request_html_name = STATIC_PATH + request_html_name

至此十籍,我們就已經(jīng)獲悉了用戶請(qǐng)求的具體文件情況蛆封,我們只需根據(jù)相應(yīng)文件名來(lái)讀取相應(yīng)文件并設(shè)置為響應(yīng)體發(fā)送給瀏覽器即可。

html_file = open(request_html_name, 'rb')
resp_body = html_file.read()  # 顯示內(nèi)容為讀取的文件內(nèi)容
html_file.close()

完整的代碼如下:

'''net09_web_PWB2.py'''
import socket
import re

SERVER_ADDR = (HOST, PORT) = '', 8888  # 服務(wù)器地址
VERSION = 2.0  # web服務(wù)器版本號(hào)
STATIC_PATH = './static/'


class HTTPServer():
    def __init__(self, server_address):
        """初始化服務(wù)器TCP套接字"""
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.tcp_socket.bind(server_address)
        self.tcp_socket.listen(128)

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            client_socket, client_address = self.tcp_socket.accept()
            print(client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
            self.handlerequest(client_socket)

    def handlerequest(self, client_socket):
        """客戶端請(qǐng)求處理勾栗,發(fā)送響應(yīng)數(shù)據(jù)"""
        # 收取瀏覽器請(qǐng)求頭惨篱,并在服務(wù)器端打印顯示
        request_data = client_socket.recv(2048).decode('utf-8')
        request_header_lines = request_data.splitlines()
        # print(request_header_lines[0])  # 第一行為請(qǐng)求頭信息
        # 解析請(qǐng)求頭,獲取具體請(qǐng)求信息
        pattern = r'[^/]+(/[^ ]*)'
        request_html_name = re.match(pattern, request_header_lines[0]).group(1)
        # 根據(jù)解析到的內(nèi)容補(bǔ)全將要讀取文件的路徑
        if request_html_name == '/':
            request_html_name = STATIC_PATH + 'baidu.html'
        else:
            request_html_name = STATIC_PATH + request_html_name

        # 根據(jù)文件情況來(lái)返回相應(yīng)的信息
        try:
            html_file = open(request_html_name, 'rb')
        except FileNotFoundError:
            # 文件不存在械姻,則返回文件不存在妒蛇,并返回狀態(tài)碼404
            resp_headers = 'HTTP/1.1 404 not found\r\n'
            resp_headers += "Server: PWB" + str(VERSION) + '\r\n'
            resp_headers += '\r\n'
            resp_body = '==== 404 file not found===='.encode('utf-8')
        else:
            # 文件存在,則讀取文件內(nèi)容楷拳,并返回狀態(tài)碼200
            resp_headers = "HTTP/1.1 200 OK\r\n"  # 200代表響應(yīng)成功并找到資源
            resp_headers += "Server: PWB" + str(VERSION) + '\r\n'  # 告訴瀏覽器服務(wù)器
            resp_headers += '\r\n'  # 空行隔開(kāi)body
            resp_body = html_file.read()  # 顯示內(nèi)容為讀取的文件內(nèi)容
            html_file.close()
        finally:
            resp_data = resp_headers.encode('utf-8') + resp_body  # 結(jié)合響應(yīng)頭和響應(yīng)體
            # 發(fā)送相應(yīng)數(shù)據(jù)至瀏覽器
            client_socket.send(resp_data)
            client_socket.close()  # HTTP短連接,請(qǐng)求完即關(guān)閉TCP連接


def run():
    """運(yùn)行服務(wù)器"""
    pwb = HTTPServer(SERVER_ADDR)
    print('web server:PWB %s on port %d...\n' % (VERSION, PORT))
    pwb.serve_forever()


if __name__ == '__main__':
    run()

可以看到吏奸,當(dāng)輸入localhost:8888時(shí)欢揖,返回的是我們本地的baidu.html 。同樣可以在瀏覽器里看到我們?cè)O(shè)置的web服務(wù)器版本PWB2.0了奋蔚。

websocket4.png

當(dāng)輸入localhost:8888/gitee.html時(shí)她混,返回的是我們本地的gitee.html烈钞。

webserver5.png

當(dāng)輸入localhost:8888/22時(shí),返回的是404錯(cuò)誤信息坤按。

webser6.png

9.3 多線程毯欣、多進(jìn)程、協(xié)程實(shí)現(xiàn)web服務(wù)器

和第7章的斗魚(yú)照片爬取類似臭脓,我們只需要修改TCP連接客戶服務(wù)套接字的啟動(dòng)方式就可以實(shí)現(xiàn)9.2節(jié)的內(nèi)容了酗钞,也就是修改HTTPServer類的serve_forver方法。限于篇幅来累,本節(jié)只列舉被修改的部分砚作,完整源代碼請(qǐng)參考net09_web_PWB3.py、net09_web_PWB4.py和net09_web_PWB5.py

9.3.1 多線程web服務(wù)器

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            client_socket, client_address = self.tcp_socket.accept()
            print(client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
            td1 = threading.Thread(target=self.handlerequest, args=(client_socket,))
            td1.start()

9.3.2 多進(jìn)程web服務(wù)器

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            client_socket, client_address = self.tcp_socket.accept()
            print(client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
            processes = multiprocessing.Process(target=self.handlerequest, args=(client_socket,))
            processes.start()
            client_socket.close()

9.3.3 協(xié)程web服務(wù)器

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            client_socket, client_address = self.tcp_socket.accept()
            print(client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
            gevent.spawn(self.handlerequest, client_socket)

9.4 拓展epoll非阻塞IO的web服務(wù)器

在9.3節(jié)嘹锁,我們?cè)趕erve_forever方法中通過(guò)while True來(lái)實(shí)現(xiàn)死循環(huán)運(yùn)行實(shí)現(xiàn)并發(fā)web服務(wù)器葫录,不會(huì)出錯(cuò)是因?yàn)槲覀兊姆?wù)端套接字默認(rèn)是阻塞型的,即無(wú)連接時(shí)领猾,會(huì)停在accept()那里米同。這樣的工作模式在海量連接工作時(shí)效率很低。

要向工作效率高摔竿,就可以I/O多路復(fù)用窍霞。多路IO好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。對(duì)于多路IO模型在很多操作系統(tǒng)上都有一些實(shí)現(xiàn)拯坟,比如多數(shù)BSD平臺(tái)都支持的kqueue()但金,或者Windows支持的IOCP,而Linux 平臺(tái)(內(nèi)核版本2.5+)上就有一個(gè)著名的模型epoll郁季。在epoll模型中 由操作系統(tǒng)負(fù)責(zé)監(jiān)聽(tīng)的所有socket冷溃,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,操作系統(tǒng)就通知用戶進(jìn)程梦裂。這樣比 在用戶層面實(shí)現(xiàn)的 for 似枕、while不斷死循環(huán)去檢測(cè)每個(gè)socket的通信狀態(tài) 的代碼高效太多 ,也稱輪詢年柠。如果用戶層面的輪詢 凿歼, 某短時(shí)間段內(nèi)沒(méi)有用戶訪問(wèn),則CPU執(zhí)行流程屬于無(wú)用功(while死循環(huán)中) 產(chǎn)出效率低 浪費(fèi)資源冗恨。并且執(zhí)行效率和輪詢的socket數(shù)量息息相關(guān)答憔, 一般在1024-2048左右。隨著需要輪詢的數(shù)量增加掀抹,輪詢效率越低虐拓。

I/O 多路復(fù)用的特點(diǎn):

通過(guò)一種機(jī)制使一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符,而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài)傲武,epoll()函數(shù)就可以返回蓉驹。所以, I/O多路復(fù)用城榛,本質(zhì)上在 任何時(shí)候 監(jiān)視任務(wù)都是單進(jìn)程(單線程)模式 進(jìn)行工作,它之所以能提高效率是因?yàn)閟elect / epoll 把進(jìn)來(lái)的socket放到他們的 '監(jiān)視' 列表里面态兴,當(dāng)任何socket有可讀可寫數(shù)據(jù)立馬處理狠持,那如果select\epoll 手里同時(shí)檢測(cè)著很多socket, 一有動(dòng)靜馬上返回給進(jìn)程處理瞻润,比一個(gè)一個(gè)socket過(guò)來(lái),恢復(fù)阻塞等待前的情況開(kāi)始執(zhí)行處理請(qǐng)求 效率高喘垂。

要I/O多路復(fù)用就需要把服務(wù)套接字設(shè)置為非阻塞,但是會(huì)出錯(cuò)敢订。怎么辦呢王污?

self.tcp_socket.setblocking(False)  # 將套接字設(shè)置為非阻塞模式

epoll是Linux內(nèi)核為處理大批量文件描述符而作了改進(jìn)的poll,是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本楚午,它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率昭齐。另一點(diǎn)原因就是獲取事件的時(shí)候,它無(wú)須遍歷整個(gè)被偵聽(tīng)的描述符集矾柜,只要遍歷那些被內(nèi)核IO事件異步喚醒而加入Ready隊(duì)列的描述符集合就行了阱驾。

python中有個(gè)select模塊可以實(shí)現(xiàn)epoll。一般的使用方式為:

  • 創(chuàng)建epoll對(duì)象
    self.epoll = select.epoll()  # 創(chuàng)建一個(gè)epoll對(duì)象
    
  • 注冊(cè)事件文件描述符怪蔑,監(jiān)聽(tīng)epoll.poll列表并執(zhí)行相應(yīng)操作
self.epoll.register(self.tcp_socket.fileno(), select.EPOLLIN | select.EPOLLET) # 將服務(wù)套接字注冊(cè)
self.epoll.register(new_client_socket.fileno(), select.EPOLLIN | select.EPOLLET)


EPOLLIN (可讀)
EPOLLOUT (可寫)
EPOLLET (ET模式)EPOLLLT(LT模式)
LT模式:當(dāng)epoll檢測(cè)到描述符事件發(fā)生并將此事件通知應(yīng)用程序里覆,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll時(shí)缆瓣,
會(huì)再次響應(yīng)應(yīng)用程序并通知此事件喧枷。
ET模式:當(dāng)epoll檢測(cè)到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件弓坞。
如果不處理隧甚,下次調(diào)用epoll時(shí),不會(huì)再次響應(yīng)應(yīng)用程序并通知此事件



epoll_list = self.epoll.poll()
              for fd, events in epoll_list:
                          do something

**單進(jìn)程單線程采用epoll實(shí)現(xiàn)靜態(tài)web服務(wù)器完整代碼**

"""linux平臺(tái) epoll 實(shí)現(xiàn)web服務(wù)器"""
'''net09_web_PWB6.py'''
import socket
import select
import re

SERVER_ADDR = (HOST, PORT) = '', 8888  # 服務(wù)器地址
VERSION = 6.0  # web服務(wù)器版本號(hào)
STATIC_PATH = './static/'


class HTTPServer():
    def __init__(self, server_address):
        """初始化服務(wù)器TCP套接字"""
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.tcp_socket.bind(server_address)
        self.tcp_socket.listen(128)
        self.tcp_socket.setblocking(False)  # 將套接字設(shè)置為非阻塞模式
        self.epoll = select.epoll()  # 創(chuàng)建一個(gè)epoll對(duì)象
        self.epoll.register(self.tcp_socket.fileno(), select.EPOLLIN | select.EPOLLET) # 將服務(wù)套接字注冊(cè)
        self.connections = {}
        self.addresses = {}

    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            epoll_list = self.epoll.poll()
            for fd, events in epoll_list:
                if fd == self.tcp_socket.fileno():
                    new_client_socket, new_client_address = self.tcp_socket.accept()
                    print(new_client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
                    self.connections[new_client_socket.fileno()] = new_client_socket # 存入客戶連接事件文件描述符
                    self.addresses[new_client_socket.fileno()] = new_client_address
                    # 向epoll中則側(cè)新socket的可讀事件
                    self.epoll.register(new_client_socket.fileno(), select.EPOLLIN | select.EPOLLET)
                elif events == select.EPOLLIN:
                    self.handlerequest(fd)

    def handlerequest(self, fd):
        """客戶端請(qǐng)求處理渡冻,發(fā)送響應(yīng)數(shù)據(jù)"""
        # 收取瀏覽器請(qǐng)求信息戚扳,并在服務(wù)器端打印顯示
        request_data = self.connections[fd].recv(2048).decode('utf-8')
        # 數(shù)據(jù)為空代表客戶端關(guān)閉了連接,則在監(jiān)聽(tīng)中除去響應(yīng)的文件描述符
        if not request_data:
            self.epoll.unregister(fd)
            self.connections[fd].close()  # server側(cè)主動(dòng)關(guān)閉連接fd
            print("%s---offline---" % str(self.addresses[fd]))
            del self.connections[fd]
            del self.addresses[fd]
        # 若存在數(shù)據(jù)族吻,則按照正常的事件處理
        else:
            request_header_lines = request_data.splitlines()
            # for line in request_header_lines:
            #     print(line)

            # 解析請(qǐng)求頭帽借,獲取具體請(qǐng)求信息
            pattern = r'[^/]+(/[^ ]*)'
            request_html_name = re.match(pattern, request_header_lines[0]).group(1)
            # 根據(jù)解析到的內(nèi)容補(bǔ)全將要讀取文件的路徑
            if request_html_name == '/':
                request_html_name = STATIC_PATH + 'baidu.html'
            else:
                request_html_name = STATIC_PATH + request_html_name

            # 根據(jù)文件情況來(lái)返回相應(yīng)的信息
            try:
                html_file = open(request_html_name, 'rb')
            except FileNotFoundError:
                # 文件不存在,則返回文件不存在超歌,并返回狀態(tài)碼404
                resp_headers = 'HTTP/1.1 404 not found\r\n'
                resp_headers += "Server: PWB" + str(VERSION) + '\r\n'
                resp_headers += '\r\n'
                resp_body = '==== 404 file not found===='.encode('utf-8')
            else:
                # 文件存在砍艾,則讀取文件內(nèi)容,并返回狀態(tài)碼200
                resp_headers = "HTTP/1.1 200 OK\r\n"  # 200代表響應(yīng)成功并找到資源
                resp_headers += "Server: PWB" + str(VERSION) + '\r\n'  # 告訴瀏覽器服務(wù)器
                resp_headers += '\r\n'  # 空行隔開(kāi)body
                resp_body = html_file.read()  # 顯示內(nèi)容為讀取的文件內(nèi)容
                html_file.close()
            finally:
                resp_data = resp_headers.encode('utf-8') + resp_body  # 結(jié)合響應(yīng)頭和響應(yīng)體
                # 發(fā)送相應(yīng)數(shù)據(jù)至瀏覽器
                self.connections[fd].send(resp_data)
                # HTTP短連接握础,請(qǐng)求完即關(guān)閉TCP連接辐董,并除去相應(yīng)事件的文件描述符
                self.epoll.unregister(fd)
                self.connections[fd].close()  # server側(cè)主動(dòng)關(guān)閉連接fd
                print("%s--web請(qǐng)求響應(yīng)完畢-offline---" % str(self.addresses[fd]))
                del self.connections[fd]
                del self.addresses[fd]


def run():
    """運(yùn)行服務(wù)器"""
    pwb = HTTPServer(SERVER_ADDR)
    print('web server:PWB %s on port %d...\n' % (VERSION, PORT))
    pwb.serve_forever()


if __name__ == '__main__':
    run()

當(dāng)然也可以像9.3節(jié)那樣多線程 方式,一個(gè)連接過(guò)來(lái)開(kāi)一個(gè) 線程處理禀综,這樣消耗的內(nèi)存和線程 切換頁(yè)會(huì)耗掉更多的系統(tǒng)資源简烘。所以, 我們可以結(jié)合I/O多路復(fù)用和多線程 來(lái)提高性能并發(fā),I/O復(fù)用負(fù)責(zé)提高接受socket的通知效率定枷,收到請(qǐng)求后孤澎,交給 多線程 來(lái)處理邏輯。完整源碼見(jiàn)net09_web_PWB7.py欠窒。

'''net09_web_PWB7.py'''
    def serve_forever(self):
        """永久運(yùn)行監(jiān)聽(tīng)接收連接"""
        while True:
            epoll_list = self.epoll.poll()
            for fd, events in epoll_list:
                if fd == self.tcp_socket.fileno():
                    new_client_socket, new_client_address = self.tcp_socket.accept()
                    print(new_client_address, '向服務(wù)器發(fā)起了請(qǐng)求')
                    self.connections[new_client_socket.fileno()] = new_client_socket # 存入客戶連接事件文件描述符
                    self.addresses[new_client_socket.fileno()] = new_client_address
                    # 向epoll中則側(cè)新socket的可讀事件
                    self.epoll.register(new_client_socket.fileno(), select.EPOLLIN | select.EPOLLET)
                elif events == select.EPOLLIN:
                    td = threading.Thread(target=self.handlerequest, args=(fd,))
                    td.start()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末覆旭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子岖妄,更是在濱河造成了極大的恐慌型将,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荐虐,死亡現(xiàn)場(chǎng)離奇詭異七兜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)福扬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門腕铸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人铛碑,你說(shuō)我怎么就攤上這事狠裹。” “怎么了汽烦?”我有些...
    開(kāi)封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵涛菠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我撇吞,道長(zhǎng)俗冻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任梢夯,我火速辦了婚禮言疗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颂砸。我一直安慰自己噪奄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布人乓。 她就那樣靜靜地躺著勤篮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪色罚。 梳的紋絲不亂的頭發(fā)上碰缔,一...
    開(kāi)封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音戳护,去河邊找鬼金抡。 笑死瀑焦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梗肝。 我是一名探鬼主播榛瓮,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼巫击!你這毒婦竟也來(lái)了禀晓?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坝锰,失蹤者是張志新(化名)和其女友劉穎粹懒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體顷级,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凫乖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了愕把。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拣凹。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恨豁,靈堂內(nèi)的尸體忽然破棺而出嚣镜,到底是詐尸還是另有隱情,我是刑警寧澤橘蜜,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布菊匿,位于F島的核電站,受9級(jí)特大地震影響计福,放射性物質(zhì)發(fā)生泄漏跌捆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一象颖、第九天 我趴在偏房一處隱蔽的房頂上張望佩厚。 院中可真熱鬧,春花似錦说订、人聲如沸抄瓦。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钙姊。三九已至,卻和暖如春埂伦,著一層夾襖步出監(jiān)牢的瞬間煞额,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膊毁,地道東北人胀莹。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像媚媒,于是被迫代替她去往敵國(guó)和親嗜逻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涩僻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理缭召,服務(wù)發(fā)現(xiàn),斷路器逆日,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • 上一篇《WEB請(qǐng)求處理一:瀏覽器請(qǐng)求發(fā)起處理》嵌巷,我們講述了瀏覽器端請(qǐng)求發(fā)起過(guò)程,通過(guò)DNS域名解析服務(wù)器IP室抽,并建...
    七寸知架構(gòu)閱讀 80,928評(píng)論 21 356
  • 第一章 Nginx簡(jiǎn)介 Nginx是什么 沒(méi)有聽(tīng)過(guò)Nginx搪哪?那么一定聽(tīng)過(guò)它的“同行”Apache吧!Ngi...
    JokerW閱讀 32,642評(píng)論 24 1,002
  • 1.Object關(guān)于今天經(jīng)歷的事情你記得什么坪圾?完成了什么晓折? 調(diào)查問(wèn)卷統(tǒng)計(jì)完成(早知道是我統(tǒng)計(jì),我就直接換成電子問(wèn)卷...
    拍謝少女閱讀 555評(píng)論 0 50
  • 這把火不知燒了多少遍兽泄,同樣的場(chǎng)所漓概,同樣的方式,面熟的人病梢,一次又一次胃珍,不斷重復(fù),就是不知還要燒多少次…… 新年到蜓陌,紅...
    梨花殘淚閱讀 312評(píng)論 0 1