主頁 > 後端開發 > 14_Web服務器-并發服務器

14_Web服務器-并發服務器

2020-09-11 09:01:19 後端開發

1.服務器概述

    1.硬體服務器(IBM,HP): 主機 集群

    2.軟體服務器(HTTPserver Django flask): 網路服務器,在后端提供網路功能邏輯處理資料處理的程式或者架構等

    3.服務器架構
        c/s(客戶端client服務器server): b/s架構充分發揮了PC機的性能
        b/s(瀏覽器browser服務器server): 隸屬于c/s架構,b/s架構統一了應用的介面

    4.服務器追求: 處理速度快,資料更安全,并發量大
        硬體: 更高配置,更多主機,集成,分布
        軟體: 程式占有更少的資源,更流暢的運行,處理更多的并發

2.服務器模型

    1.基本的服務器模型
        1.回圈服務器模型
            模式: 單執行緒程式,回圈接收連接或者請求然后處理,處理后繼續回圈
            缺點: 不能同時處理多個客戶端的并行,不允許某個客戶端長期占有服務器
            優點: 結構比較簡單,適用于UDP程式,要求處理請求可以很快完成
        2.IO多路復用服務器模型
            模式: 通過同時監控多個IO來達到IO并發的目的
            缺點: 也是單執行緒,不能長期阻塞,不適用處理大量CPU占有高的程式
            優點: 開銷小,比較適合IO密集型的服務端程式
        3.并發服務器模型
            模式:
                每有一個客戶端鏈接請求就創建一個新的行程或執行緒處理客戶端的請求
                而主行程或主執行緒可以繼續接收其它客戶端的連接
            缺點: 資源消耗比較大,客戶端需要長期占有服務器的情況
            優點: 多任務,高并發

    2.并發服務器-多行程(fork, multiprocessing)
        1.創建套接字,系結,監聽
        2.接收客戶端請求
        3.創建子行程處理客戶端請求,父行程繼續準備接受新的客戶端連接
        4.客戶端退出,銷毀相應的子行程

    3.并發服務器-多執行緒(threading)
        1.相比多行程服務器的優缺點
            缺點: 需要用到同步互斥,可能受到GIL的影響(網路IO執行緒并發可以)
            優點: 資源消耗比較少
        2.使用模塊: threading socket
        3.步驟
            1.創建套接字,系結,監聽
            2.接收客戶端連接請求,創建新的執行緒
            3.主執行緒繼續接收下一個客戶端連接請求,分支執行緒處理客戶端事件
            4.處理事件結束退出執行緒關閉套接字

    4.并發服務器-多協程(gevent + monkey)

    5.使用集成模塊完成網路并發-socketserver模塊
        實作行程tcp并發的類
            'ForkingMixIn','TCPServer', 'StreamRequestHandler'
        實作行程udp并發的類
            'ForkingMixIn', 'UDPServer', 'DatagramRequestHandler',
        實作執行緒tcp并發的類
            'ThreadingMixIn', 'TCPServer', 'StreamRequestHandler'
        實作執行緒udp并發的類
            'ThreadingMixIn', 'UDPServer','DatagramRequestHandler',
        其他類
            'ForkingTCPServer' = 'ForkingMixIn' + 'TCPServer'
            'ForkingUDPServer' = 'ForkingMixIn' + 'UDPServer'
            'ThreadingTCPServer' = 'ThreadingMixIn' + 'TCPServer'
            'ThreadingUDPServer' = 'ThreadingMixIn' + 'UDPServer'

3.HTTP超文本傳輸協議概述

    1.在Web應用中,服務器把網頁傳給瀏覽器,實際上就是把網頁的HTML代碼發送給瀏覽器讓瀏覽器顯示出

    2.瀏覽器和服務器之間的傳輸協議是HTTP,HTTP是在網路上傳輸HTML的協議,用于瀏覽器和服務器的通信

    3.HTML是一種用來定義網頁的文本,會HTML,就可以撰寫網頁

    4.用途:
        1.網站中網頁的傳輸和資料傳輸
        2.基于HTTP協議的編程傳輸資料

    5.特點:
        1.應用層協議,傳輸層使用TCP連接
        2.簡單靈活介面使用方便
        3.幾乎支持所有的資料型別
        4.是無狀態的
        5.長連接(HTTP 1.1)

4.Web網站概述

1.瀏覽器訪問網站程序

        1.域名決議

        2.向服務器發送三次握手

        3.客戶端(瀏覽器)發起HTTP請求

        4.傳輸層使用TCP協議建立連接,層層打包將請求內容發送給服務器

        5.web服務器解包后決議HTTP請求,交給后端應用程式處理

        6.后端應用得到結果,通過web服務器回發給前端

        7.發送tcp四次回送

        8.瀏覽器訪問網站流程圖: https://www.processon.com/view/link/5efcac3507912929cb6b33df

2.請求(request)的結構格式

        1.請求行(確定具體的請求型別):
            GET(請求方法) /index.thml(請求資源) HTTP/1.1(協議版本)
            # 請求方法
            GET: 獲取網路資源
            POST: 提交一定的附加資料,得到回傳結果
            HEAD: 獲取回應的頭資訊
            PUT: 更新服務器資源
            DELETE: 洗掉服務器資源
            TRACE: 用于測驗
            CONNECT: 保留方法
            OPTIONS: 請求獲取服務器性能和資訊

        3.請求頭(對請求內容的資訊描述)
            # 選項: 值
            Accept: text/html,application/xhtml+xml  # 瀏覽器可以接收的文本格式
            Accept-Encoding: gzip, deflate  # 瀏覽器可以接收的壓縮格式
            Accept-Language: zh-CN,zh;q=0.9  # 瀏覽器可以接收的語言
            Connection: keep-alive  # 長連接
            Cookie: BAIDUID=8A4DA4339C1B8A74DD251F7D9F834C76:FG=1
            Host: news.baidu.com  # 請求的服務器地址
            Referer: https://www.baidu.com/  # 防盜鏈,防止惡意請求
            Upgrade-Insecure-Requests: 1
            User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64)  # 發送請求的瀏覽器的版本

        3.空行

        4.請求體(具體請求引數):
            get請求: get引數 &a=1&b=2
            post請求: post提交的內容

3.回應(response)的結構格式

        1.回應行(反饋回應的情況): HTTP/1.1(協議版本) 200(回應碼) OK(資訊)
            # 回應碼
            1xx: 提示資訊,表示請求已經接收,正在處理
            2xx: 請求回應成功
            3xx: 重定向,完成任務需要其它操作
            4xx: 客戶端錯誤
            5xx: 服務端錯誤

            # HTTP回應狀態碼劃分
            100-199: 表示成功接收請求,要求客戶端繼續提交下一次請求才能完成整個處理程序
            200-299: 表示成功接收請求并已完成整個處理程序,常用200
            300-399: 未完成請求,客戶需進一步細化請求,常用302,307,304
            400-499: 客戶端的請求有錯誤,常用404
            500-599: 服務器端出現錯誤,常用500

            # 回應碼示例
            200 成功
            401 沒有訪問權限
            404 資源不存在
            500 服務器發生未知錯誤
            503 服務器暫時無法執行

        2.回應頭(對回應的具體描述)
            # 選項: 值
            Accept: text/html,application/xhtml+xml,application/xml
            Accept-Encoding: gzip, deflate
            Accept-Language: zh-CN,zh;q=0.9
            Connection: keep-alive
            Cookie: BAIDUID=8A4DA4339C1B8A74DD251F7D9F834C76:FG=1
            Host: news.baidu.com
            Referer: https://www.baidu.com/
            Upgrade-Insecure-Requests: 1
            User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64)

        3.空行

        4.回應體(具體回傳給客戶的內容): 檔案,圖片等

5.訪問百度請求資訊和應答資訊

    1.描述資訊

General  # 簡單的描述
Request URL: https://www.baidu.com/  # 請求網址
Request Method: GET  # 請求方法
Status Code: 200 OK  # 狀態代碼 有資料 成功
Remote Address: 14.215.177.39:443  # 遠程地址:服務器地址
Referrer Policy: no-referrer-when-downgrade  # 表示從https協議降為http協議時不發送referrer給跳轉網站的服務器

    2.應答資訊

Response Headers    view source  # 回應頭  源始頭
Bdpagetype: 1  # 百度服務器定義的特定值
Bdqid: 0xedfaa2040000c90c  # 百度服務器定義的特定值
Cache-Control: private  # 快取控制:私有
Connection: Keep-Alive  # 連接:tpc socket的可靠連接
Content-Encoding: gzip  # 服務器通過這個頭告訴瀏覽器資料的壓縮格式
Content-Type: text/html  # 服務器通過這個頭告訴瀏覽器回送資料的型別
Cxy_all: baidu+d83b58345e15ab078f816c529e1cef79
Date: Sun, 14 Apr 2019 02:47:43 GMT  # 時間
Expires: Sun, 14 Apr 2019 02:47:31 GMT  # 到期時間
Server: BWS/1.1  # 服務器通過這個頭告訴瀏覽器服務器的型號
Set-Cookie: delPer=0; path=/; domain=.baidu.com
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=0; path=/
Set-Cookie: H_PS_PSSID=1427_28827_21091_28768_28722_28558_28832_28585_28604_28606; path=/; domain=.baidu.com
"""Set-Cookie
    服務器與客戶端驗證身份的資訊
    其中path=/; domain=.baidu.com為域名判斷條件,如果滿足該條件,才可以向服務器發送cookie
"""
Strict-Transport-Security: max-age=172800  # 通知瀏覽器,這個網站禁止使用HTTP方式加載,瀏覽器應該自動把所有嘗試使用HTTP的請求自動替換為HTTPS請求
Transfer-Encoding: chunked  # Transfer-Encoding,是一個 HTTP 頭部欄位(回應頭域),字面意思是傳輸編碼,最新的 HTTP規范里,只定義了一種編碼傳輸:分塊編碼(chunked),資料分解成一系列資料塊,并以一個或多個塊發送,這樣服務器可以發送資料而不需要預先知道發送內容的總大小
Vary: Accept-Encoding  # 告訴代理服務器快取兩種版本的資源:壓縮和非壓縮
X-Ua-Compatible: IE=Edge,chrome=1  # 這是一個檔案兼容模式的定義,主要用于加強代碼對IE的兼容性,強制IE使用當前本地最新版標準模式渲染或者用chrome內核渲染


Response Headers    view parsed  # 回應頭  已決議源始頭
HTTP/1.1 200 OK  # 使用HTTP協議進行傳輸的中協議版本號為1.1請求狀態碼為200 OK
Bdpagetype: 1  # 百度服務器定義的特定值
Bdqid: 0xfa9b2b6a00029507  # 百度服務器定義的特定值
Cache-Control: private  # 對資料進行快取
Connection: keep-alive  # # 連接:tpc socket的可靠連接
Content-Encoding: gzip  # 傳輸資源的檔案壓縮格式
Content-Type: text/html;charset=utf-8  # 傳輸資源的檔案型別和編碼型別
Date: Thu, 04 Jun 2020 08:56:33 GMT  # 資源的發送時間
Expires: Thu, 04 Jun 2020 08:56:29 GMT  # 資源過期時間
Server: BWS/1.1  # 服務器的系統版本,其中BWS為百度自有服務器(Baidu Web Server)
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=31728_1460_31326_21078_31069_31762_31605_31322_30824; path=/; domain=.baidu.com
"""Set-Cookie
    服務器與客戶端驗證身份的資訊
    其中path=/; domain=.baidu.com為域名判斷條件,如果滿足該條件,才可以向服務器發送cookie
"""
Strict-Transport-Security: max-age=172800
"""Strict-Transport-Security: max-age=172800
    使用https安全傳輸,防止在瀏覽器默認http請求和服務器https資源之間進行轉換的時候,遭受中間人攻擊
    瀏覽器收到該條回應欄位,會在max-age秒之內自動將http轉換成https的安全訪問形式,過期后會重復之前的步驟
"""
Traceid: 1591260993276042138618058074865138832647  # 標記了瀏覽器發起的某個請求,這個id可在服務端從接收請求到 回應請求中流轉,甚至接力傳遞給下游應用中流轉,用于唯一標記和定外這次請求,一般用于定位請求日志
X-Ua-Compatible: IE=Edge,chrome=1  # # 這是一個檔案兼容模式的定義,主要用于加強代碼對IE的兼容性,強制IE使用當前本地最新版標準模式渲染或者用chrome內核渲染
Transfer-Encoding: chunked   # Transfer-Encoding,是一個 HTTP 頭部欄位(回應頭域),字面意思是傳輸編碼,最新的 HTTP規范里,只定義了一種編碼傳輸:分塊編碼(chunked),資料分解成一系列資料塊,并以一個或多個塊發送,這樣服務器可以發送資料而不需要預先知道發送內容的總大小

    3.請求資訊

Request Headers view source  # 請求頭  源始頭
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
"""Accept
    客戶端希望接受的檔案型別,后面的q=0.9(0<q<1)代表權重系數,越大表示越期待
"""
Accept-Encoding: gzip, deflate, br  # 設定接受的編碼格式
Accept-Language: zh-CN,zh;q=0.9  # 瀏覽器支持的語言為簡體中文和中文,優先支持簡體中文
Cache-Control: max-age=0  # 快取控制
Connection: keep-alive
"""Connection: keep-alive
    該欄位可以承載三種不同型別的標簽:
    HTTP首部欄位名,列出了只與此連接有關的首部任意標簽值,用于描述此連接的非標準選項值(close)表示的是TCP連接是否關閉,HTTP資料傳輸采用的TCP三次握手協議
    如果值為close,則該次傳輸資料結束關閉TCP連接,下次傳輸資料,要重新進行三次握手
    如果值為默認的keep-alive,則保持資料通道,下次資料傳輸可以直接傳遞,省去三次握手的程序
    一般我們只用值close來控制TCP連接狀態
"""
    Cookie: BIDUPSID=D97A5AC4617B3E6A2486D73D9F89F5AF; PSTM=1590403180; BAIDUID=D97A5AC4617B3E6AA36FE3B792FD9646:FG=1; BD_UPN=12314353; cflag=13%3A3; BD_HOME=1; H_PS_PSSID=31728_1460_31326_21078_31069_31762_31605_31322_30824; delPer=0; BD_CK_SAM=1; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; yjs_js_security_passport=64f53ded3515c88a93fa8fb6ab0e4dafa11418ec_1591253192_js; PSINO=5; COOKIE_SESSION=1708_0_9_9_24_19_0_3_9_7_4_6_1708_0_6_0_1591254838_0_1591254844%7C9%230_0_1590910191%7C1
    Host: www.baidu.com  # # 瀏覽器發給服務器,宣告瀏覽器訪問哪臺主機
    Upgrade-Insecure-Requests: 1
"""Upgrade-Insecure-Requests: 1
    該欄位用于http和https格式的過渡,一般如果請求http時,服務器資源為https,或是相反,瀏覽器會提示或報錯
    而Google瀏覽器使用該欄位向服務器表明,瀏覽器本身可以將http轉換成https,服務器可以隨意發,瀏覽器會自動將http://該成https://
"""
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
"""User-Agent
    用戶Agent代理,即用戶發出HTTP請求的客戶端程式(例:瀏覽器Mozilla/5.0版本,Chrome/73.0.3683.86版本,spider爬蟲,Web robot等)
"""


Request Headers view parsed  # 請求頭  已決議源始頭
GET / HTTP/1.1  # 請求方式 請求目錄 協議版本
Host: www.baidu.com  # 瀏覽器發給服務器,宣告瀏覽器訪問哪臺主機
Connection: keep-alive  # 瀏覽器發給服務器,宣告請求完后是斷開鏈接還是維持鏈接
Cache-Control: max-age=0  # 快取控制
Upgrade-Insecure-Requests: 1  # 瀏覽器發給服務器,宣告自己支持這種操作,也就是我能讀懂你服務器發過來的上面這條資訊,并且在以后發請求的時候不用http而用https
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36  # 瀏覽器發給服務器,宣告支持的瀏覽器及版本
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3  # 瀏覽器發給服務器,宣告瀏覽器所支持的資料型別
Accept-Encoding: gzip, deflate, br  # 瀏覽器發給服務器,宣告瀏覽器支持的編碼型別
Accept-Language: zh-CN,zh;q=0.9  # 瀏覽器發給服務器,宣告瀏覽器支持的語言
Cookie: BAIDUID=BF236BEC6E4EF4DCF1AD838C2D217739:FG=1; PSTM=1555138860; BIDUPSID=88B1B87B29B6B36C8E48F551D3574BFE; BD_UPN=12314353; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=1427_28827_21091_28768_28722_28558_28832_28585_28604_28606; BDUSS=HU1UVhKRnNDMEhiYjlOVjNPQUhYfkxlZ2xTejV-c0Y0c2RzUVFSQUxtTVBOZHBjSUFBQUFBJCQAAAAAAAAAAAEAAABZiE1gQW5nZWxzxbe6wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-oslwPqLJcW; delPer=0; BD_CK_SAM=1; PSINO=6; H_PS_645EC=c374KRUZaisqZePbVCOq52p9mRyTUyW7oNYJp%2Bbf2HRkfC7pCZ%2F5tPvURnM; ZD_ENTRY=baidu; BD_HOME=1

6.Web靜態服務器-單任務

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import socket
import re


def handle_request(client_socket):
    """為一個客戶端進行服務"""
    # 1.接收瀏覽器發送過來的HTTP請求
    recv_data = https://www.cnblogs.com/tangxuecheng/p/client_socket.recv(1024).decode('utf-8', errors="ignore")
    request_header_lines = recv_data.splitlines()
    for line in request_header_lines:
        print(line)

    # GET /index.html HTTP/1.1
    http_request_line = request_header_lines[0]
    get_file_name = re.match("[^/]+(/[^ ]*)", http_request_line).group(1)
    print("file name is ===>%s" % get_file_name)  # for test

    # 如果沒有指定訪問哪個頁面,則默認訪問index.html
    # GET / HTTP/1.1
    if get_file_name == "/":
        get_file_name = DOCUMENTS_ROOT + "/index.html"
    else:
        get_file_name = DOCUMENTS_ROOT + get_file_name
    print("file name is ===2>%s" % get_file_name)  # for test

    # 2.回傳HTTP格式的資料給瀏覽器
    try:
        f = open(get_file_name, "rb")
    except IOError:
        # 404表示沒有這個頁面
        response_headers = "HTTP/1.1 404 not found\r\n"
        response_headers += "\r\n"
        response_body = "====sorry, file not found====".encode("utf-8")
    else:
        # 2.1 準備發送給瀏覽器的資料-->header
        response_headers = "HTTP/1.1 200 OK\r\n"
        # 2.2 用一個空的行與body進行隔開
        response_headers += "\r\n"
        # 2.3 準備發送給瀏覽器的資料-->body
        response_body = f.read()
        f.close()
    finally:
        # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
        # 先發送response的頭資訊
        client_socket.send(response_headers.encode("utf-8"))
        # 再發送body
        client_socket.send(response_body)
        # 關閉套接字
        client_socket.close()


def main():
    """作為程式的主控制入口"""
    # 1.創建資料流套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.設定當服務器先close 即服務器端4次揮手之后資源能夠立即釋放,保證下次運行程式時可以立即系結7890埠
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 3.系結服務器地址
    tcp_server_socket.bind(("", 7890))
    # 4.套接字由主動改為監聽,并設定佇列長度
    tcp_server_socket.listen(128)
    while True:
        # 5.等待客戶端連接
        client_socket, client_addr = tcp_server_socket.accept()
        # 6.為客戶端服務
        handle_request(client_socket)

    # 7.關閉監聽套接字
    tcp_server_socket.close()


# 配置服務器靜態資源路徑
DOCUMENTS_ROOT = "./html"

if __name__ == "__main__":
    main()

7.Web靜態服務器-多行程(multiprocessing)

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import socket
import multiprocessing
import re


class WSGIServer:
    """定義一個WSGI服務器的類"""
    def __init__(self, server_address):
        # 1.創建資料流套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2.設定當服務器先close 即服務器端4次揮手之后資源能夠立即釋放,保證下次運行程式時可以立即系結7890埠
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3.系結服務器地址
        self.tcp_server_socket.bind(server_address)
        # 4.套接字由主動改為監聽,并設定佇列長度
        self.tcp_server_socket.listen(128)

    def serve_forever(self):
        """回圈運行服務器,等待客戶端鏈接并為客戶端服務"""
        while True:
            # 5.等待客戶端鏈接
            client_socket, client_addr = self.tcp_server_socket.accept()
            # 6.為客戶端服務
            new_process = multiprocessing.Process(target=self.handle_request, args=(client_socket,))
            new_process.start()

            # 因為子行程已經復制了父行程的套接字等資源,所以父行程呼叫close不會將他們對應的這個鏈接關閉的
            client_socket.close()

    def handle_request(self, client_socket):
        """用一個新的行程為客戶端服務"""
        # 1.接收瀏覽器發送過來的HTTP請求
        recv_data = https://www.cnblogs.com/tangxuecheng/p/client_socket.recv(1024).decode('utf-8', errors="ignore")
        request_header_lines = recv_data.splitlines()
        for line in request_header_lines:
            print(line)

        # GET /index.html HTTP/1.1
        http_request_line = request_header_lines[0]
        get_file_name = re.match("[^/]+(/[^ ]*)", http_request_line).group(1)
        print("file name is ===>%s" % get_file_name)  # for test

        # 如果沒有指定訪問哪個頁面,則默認訪問index.html
        # GET / HTTP/1.1
        if get_file_name == "/":
            get_file_name = DOCUMENTS_ROOT + "/index.html"
        else:
            get_file_name = DOCUMENTS_ROOT + get_file_name
        print("file name is ===2>%s" % get_file_name)  # for test

        # 2.回傳HTTP格式的資料給瀏覽器
        try:
            f = open(get_file_name, "rb")
        except IOError:
            # 404表示沒有這個頁面
            response_headers = "HTTP/1.1 404 not found\r\n"
            response_headers += "\r\n"
            response_body = "====sorry, file not found====".encode("utf-8")
        else:
            # 2.1 準備發送給瀏覽器的資料-->header
            response_headers = "HTTP/1.1 200 OK\r\n"
            # 2.2 用一個空的行與body進行隔開
            response_headers += "\r\n"
            # 2.3 準備發送給瀏覽器的資料-->body
            response_body = f.read()
            f.close()
        finally:
            # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
            # 先發送response的頭資訊
            client_socket.send(response_headers.encode("utf-8"))
            # 再發送body
            client_socket.send(response_body)
            # 關閉套接字
            client_socket.close()


# 設定服務器的地址
SERVER_ADDR = ("", 7890)
# 設定服務器靜態資源的路徑
DOCUMENTS_ROOT = "./html"


def main():
    http_server = WSGIServer(SERVER_ADDR)
    print("web Server: Serving HTTP on port %d ...\n" % SERVER_ADDR[1])
    http_server.serve_forever()


if __name__ == "__main__":
    main()

8.Web靜態服務器-多行程(os.fork)

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import socket
import os
import signal
import re


class WSGIServer:
    """定義一個WSGI服務器的類"""

    def __init__(self, server_address):
        # 1.創建資料流套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2.設定當服務器先close 即服務器端4次揮手之后資源能夠立即釋放,保證下次運行程式時可以立即系結7890埠
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3.系結服務器地址
        self.tcp_server_socket.bind(server_address)
        # 4.套接字由主動改為監聽,并設定佇列長度
        self.tcp_server_socket.listen(128)

    def serve_forever(self):
        """回圈運行服務器,等待客戶端鏈接并為客戶端服務"""
        print("父行程PID[%s]等待客戶端的鏈接" % os.getpid())
        while True:
            try:
                # 5.等待客戶端鏈接
                client_socket, client_addr = self.tcp_server_socket.accept()
            except KeyboardInterrupt:  # 用戶中斷執行(通常是輸入^C)
                raise
            except Exception:
                continue
            # 6.處理子行程,避免成為僵尸行程-->SIGCHLD: 子行程改變狀態時,父行程會收到這個信號;SIG_IGN: 忽略這個信號
            signal.signal(signal.SIGCHLD, signal.SIG_IGN)
            # 7.父行程為客戶端創建新的行程
            pid = os.fork()
            if pid < 0:
                print("創建子行程失敗...")
                # 關閉客戶端套接字
                client_socket.close()
            elif pid == 0:
                # 子行程中不需要使用監聽套接字,因此關閉子行程中的監聽套接字
                self.tcp_server_socket.close()
                # 8.為客戶端服務
                self.handle_request(client_socket)
            else:
                # 父行程中不需要使用客戶端套接字,因此關不父行程中的客戶端套接字
                client_socket.close()
                continue

    def handle_request(self, client_socket):
        """用一個新的行程為客戶端服務"""
        # 1.接收瀏覽器發送過來的HTTP請求
        recv_data = https://www.cnblogs.com/tangxuecheng/p/client_socket.recv(1024).decode('utf-8', errors="ignore")
        request_header_lines = recv_data.splitlines()
        for line in request_header_lines:
            print(line)

        # GET /index.html HTTP/1.1
        http_request_line = request_header_lines[0]
        get_file_name = re.match("[^/]+(/[^ ]*)", http_request_line).group(1)
        print("file name is ===>%s" % get_file_name)  # for test

        # 如果沒有指定訪問哪個頁面,則默認訪問index.html
        # GET / HTTP/1.1
        if get_file_name == "/":
            get_file_name = DOCUMENTS_ROOT + "/index.html"
        else:
            get_file_name = DOCUMENTS_ROOT + get_file_name
        print("file name is ===2>%s" % get_file_name)  # for test

        # 2.回傳HTTP格式的資料給瀏覽器
        try:
            f = open(get_file_name, "rb")
        except IOError:
            # 404表示沒有這個頁面
            response_headers = "HTTP/1.1 404 not found\r\n"
            response_headers += "\r\n"
            response_body = "====sorry, file not found====".encode("utf-8")
        else:
            # 2.1 準備發送給瀏覽器的資料-->header
            response_headers = "HTTP/1.1 200 OK\r\n"
            # 2.2 用一個空的行與body進行隔開
            response_headers += "\r\n"
            # 2.3 準備發送給瀏覽器的資料-->body
            response_body = f.read()
            f.close()
        finally:
            # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
            # 先發送response的頭資訊
            client_socket.send(response_headers.encode("utf-8"))
            # 再發送body
            client_socket.send(response_body)
            # 關閉套接字
            client_socket.close()


# 設定服務器的地址
SERVER_ADDR = ("", 7890)
# 設定服務器靜態資源的路徑
DOCUMENTS_ROOT = "./html"


def main():
    http_server = WSGIServer(SERVER_ADDR)
    print("web Server: Serving HTTP on port %d ...\n" % SERVER_ADDR[1])
    http_server.serve_forever()


if __name__ == "__main__":
    main()

9.Web靜態服務器-多行程(集成模塊 socketserver)

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
from socketserver import ForkingMixIn
from socketserver import TCPServer
from socketserver import StreamRequestHandler
import re


# class WSGIServer(ForkingTCPServer):  # 寫法一 'ForkingTCPServer' = 'ForkingMixIn' + 'TCPServer'
class WSGIServer(ForkingMixIn, TCPServer):  # 寫法二
    """定義一個創建服務器類繼承自模塊中的創建服務器類"""
    pass


class HandleRequest(StreamRequestHandler):
    """定義一個處理請求類繼承模塊的處理請求類"""

    def handle(self):  # 固定的入口方法
        # 等待客戶端連接 self.request等同于accept()創建一個新的客戶端連接
        client_socket = self.request
        client_addr = self.request.getpeername()  # for test
        print("客戶端 %s 連接上服務器" % str(client_addr))  # for test

        while True:
            # 1.接收瀏覽器發送過來的HTTP請求
            recv_data = https://www.cnblogs.com/tangxuecheng/p/client_socket.recv(1024).decode('utf-8', errors="ignore")
            request_header_lines = recv_data.splitlines()
            for line in request_header_lines:
                print(line)

            # GET /index.html HTTP/1.1
            http_request_line = request_header_lines[0]
            get_file_name = re.match("[^/]+(/[^ ]*)", http_request_line).group(1)
            print("file name is ===>%s" % get_file_name)  # for test

            # 如果沒有指定訪問哪個頁面,則默認訪問index.html
            # GET / HTTP/1.1
            if get_file_name == "/":
                get_file_name = DOCUMENTS_ROOT + "/index.html"
            else:
                get_file_name = DOCUMENTS_ROOT + get_file_name
            print("file name is ===2>%s" % get_file_name)  # for test

            # 2.回傳HTTP格式的資料給瀏覽器
            try:
                f = open(get_file_name, "rb")
            except IOError:
                # 404表示沒有這個頁面
                response_body = "====sorry, file not found====".encode("utf-8")
                response_headers = "HTTP/1.1 404 not found\r\n"
                response_headers += "Content-Type: text/html; charset=utf-8\r\n"
                response_headers += "Content-Length: %d\r\n" % (len(response_body))
                response_headers += "\r\n"
            else:
                # 2.1 準備發送給瀏覽器的資料-->body
                response_body = f.read()
                f.close()
                # 2.2 準備發送給瀏覽器的資料-->header
                response_headers = "HTTP/1.1 200 OK\r\n"
                # 2.3 發送body資料長度,讓服務器根據長度確認body資料已完成接收,實作長連接
                response_headers += "Content-Length: %d\r\n" % (len(response_body))
                # 2.4 用一個空的行與body進行隔開
                response_headers += "\r\n"
            finally:
                # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
                # 先發送response的頭資訊
                client_socket.send(response_headers.encode("utf-8"))
                # 再發送body
                client_socket.send(response_body)
                # 長連接下不需要關閉套接字,而是在header頭中宣告要發送的body長度
                # client_socket.close()


# 設定服務器的地址
SERVER_ADDR = ("", 7890)
# 設定服務器靜態資源的路徑
DOCUMENTS_ROOT = "./html"


def main():
    http_server = WSGIServer(SERVER_ADDR, HandleRequest)
    print("web Server: Serving HTTP on port %d ...\n" % SERVER_ADDR[1])
    http_server.serve_forever()


if __name__ == "__main__":
    main()

10.Web靜態服務器-多執行緒(threading)

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import socket
import threading
import re


class WSGIServer:
    """定義一個WSGI服務器的類"""
    def __init__(self, server_address):
        # 1.創建資料流套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2.設定當服務器先close 即服務器端4次揮手之后資源能夠立即釋放,保證下次運行程式時可以立即系結7890埠
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3.系結服務器地址
        self.tcp_server_socket.bind(server_address)
        # 4.套接字由主動改為監聽,并設定佇列長度
        self.tcp_server_socket.listen(128)

    def serve_forever(self):
        """回圈運行服務器,等待客戶端鏈接并為客戶端服務"""
        while True:
            # 5.等待客戶端鏈接
            client_socket, client_addr = self.tcp_server_socket.accept()
            # 6.為客戶端服務
            new_thread = threading.Thread(target=self.handle_request, args=(client_socket,))
            new_thread.start()

            # 因為執行緒是共享同一個套接字,所以主執行緒不能關閉,否則子執行緒就不能再使用這個套接字
            # client_socket.close()

    def handle_request(self, client_socket):
        """用一個新的行程為客戶端服務"""
        # 1.接收瀏覽器發送過來的HTTP請求
        recv_data = https://www.cnblogs.com/tangxuecheng/p/client_socket.recv(1024).decode('utf-8', errors="ignore")
        request_header_lines = recv_data.splitlines()
        for line in request_header_lines:
            print(line)

        # GET /index.html HTTP/1.1
        http_request_line = request_header_lines[0]
        get_file_name = re.match("[^/]+(/[^ ]*)", http_request_line).group(1)
        print("file name is ===>%s" % get_file_name)

        # 如果沒有指定訪問哪個頁面,則默認訪問index.html
        # GET / HTTP/1.1
        if get_file_name == "/":
            get_file_name = DOCUMENTS_ROOT + "/index.html"
        else:
            get_file_name = DOCUMENTS_ROOT + get_file_name
        print("file name is ===2>%s" % get_file_name)

        # 2.回傳HTTP格式的資料給瀏覽器
        try:
            f = open(get_file_name, "rb")
        except IOError:
            # 404表示沒有這個頁面
            response_headers = "HTTP/1.1 404 not found\r\n"
            response_headers += "\r\n"
            response_body = "====sorry, file not found====".encode("utf-8")
        else:
            # 2.1 準備發送給瀏覽器的資料-->header
            response_headers = "HTTP/1.1 200 OK\r\n"
            # 2.2 用一個空的行與body進行隔開
            response_headers += "\r\n"
            # 2.3 準備發送給瀏覽器的資料-->body
            response_body = f.read()
            f.close()
        finally:
            # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
            # 先發送response的頭資訊
            client_socket.send(response_headers.encode("utf-8"))
            # 再發送body
            client_socket.send(response_body)
            # 關閉套接字
            client_socket.close()


# 設定服務器的地址
SERVER_ADDR = ("", 7890)
# 設定服務器靜態資源的路徑
DOCUMENTS_ROOT = "./html"


def main():
    http_server = WSGIServer(SERVER_ADDR)
    print("web Server: Serving HTTP on port %d ...\n" % SERVER_ADDR[1])
    http_server.serve_forever()


if __name__ == "__main__":
    main()

11.Web靜態服務器-多協程(gevent + monkey)

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import socket
import re
import gevent
from gevent import monkey

monkey.patch_all()


class WSGIServer:
    """定義一個WSGI服務器的類"""
    def __init__(self, server_address):
        # 1.創建資料流套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2.設定當服務器先close 即服務器端4次揮手之后資源能夠立即釋放,保證下次運行程式時可以立即系結7890埠
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3.系結服務器地址
        self.tcp_server_socket.bind(server_address)
        # 4.套接字由主動改為監聽,并設定佇列長度
        self.tcp_server_socket.listen(128)

    def serve_forever(self):
        """回圈運行服務器,等待客戶端鏈接并為客戶端服務"""
        while True:
            # 5.等待客戶端鏈接
            client_socket, client_addr = self.tcp_server_socket.accept()
            # 6.為客戶端服務
            gevent.spawn(self.handle_request, client_socket)

    def handle_request(self, client_socket):
        """用協程為客戶端服務"""
        # 1.接收瀏覽器發送過來的HTTP請求
        recv_data = https://www.cnblogs.com/tangxuecheng/p/client_socket.recv(1024).decode('utf-8', errors="ignore")
        request_header_lines = recv_data.splitlines()
        for line in request_header_lines:
            print(line)

        # GET /index.html HTTP/1.1
        http_request_line = request_header_lines[0]
        get_file_name = re.match("[^/]+(/[^ ]*)", http_request_line).group(1)
        print("file name is ===>%s" % get_file_name)

        # 如果沒有指定訪問哪個頁面,則默認訪問index.html
        # GET / HTTP/1.1
        if get_file_name == "/":
            get_file_name = DOCUMENTS_ROOT + "/index.html"
        else:
            get_file_name = DOCUMENTS_ROOT + get_file_name
        print("file name is ===2>%s" % get_file_name)

        # 2.回傳HTTP格式的資料給瀏覽器
        try:
            f = open(get_file_name, "rb")
        except IOError:
            # 404表示沒有這個頁面
            response_headers = "HTTP/1.1 404 not found\r\n"
            response_headers += "\r\n"
            response_body = "====sorry, file not found====".encode("utf-8")
        else:
            # 2.1 準備發送給瀏覽器的資料-->header
            response_headers = "HTTP/1.1 200 OK\r\n"
            # 2.2 用一個空的行與body進行隔開
            response_headers += "\r\n"
            # 2.3 準備發送給瀏覽器的資料-->body
            response_body = f.read()
            f.close()
        finally:
            # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
            # 先發送response的頭資訊
            client_socket.send(response_headers.encode("utf-8"))
            # 再發送body
            client_socket.send(response_body)
            # 關閉套接字
            client_socket.close()


# 設定服務器的地址
SERVER_ADDR = ("", 7890)
# 設定服務器靜態資源的路徑
DOCUMENTS_ROOT = "./html"


def main():
    http_server = WSGIServer(SERVER_ADDR)
    print("web Server: Serving HTTP on port %d ...\n" % SERVER_ADDR[1])
    http_server.serve_forever()


if __name__ == "__main__":
    main()

12.Web靜態服務器-非阻塞IO(基于非阻塞狀態的單行程單執行緒長連接)

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import sys
import time
import socket
import re


class WSGIServer:
    """定義一個WSGI服務器的類"""
    def __init__(self, server_addr):
        # 1.創建資料流套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2.設定當服務器先close 即服務器端4次揮手之后資源能夠立即釋放,保證下次運行程式時可以立即系結7890埠
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3.系結服務器地址
        self.tcp_server_socket.bind(server_addr)
        # 4.套接字由主動改為監聽,并設定佇列長度
        self.tcp_server_socket.listen(128)
        # 5.設定為非堵塞后,如果accept時,恰巧沒有客戶端connect,那么accept會產生一個例外,所以需要try來進行處理
        self.tcp_server_socket.setblocking(False)  # 將監聽套接字設定為非堵塞
        self.client_socket_list = list()  # 創建客戶端client_socket輪詢串列

    def serve_forever(self):
        """回圈運行服務器,等待客戶端鏈接并為客戶端服務"""
        while True:
            time.sleep(1)  # for test
            try:
                # 6.等待客戶端鏈接
                client_socket, client_addr = self.tcp_server_socket.accept()
            except Exception as ret:
                print("--->1<---沒有客戶端到來: ", ret)  # for test
            else:
                # 7.當有客戶端連接時,將客戶端的套接字加入到輪詢串列
                client_socket.setblocking(False)  # 將客戶端套接字設定為非堵塞
                self.client_socket_list.append(client_socket)

            # 8.遍歷串列看是否有收到客戶端套接字發送的訊息,如果收到訊息則為客戶端服務
            for client_socket in self.client_socket_list:
                try:
                    # 1.接收瀏覽器發送過來的HTTP請求
                    request = client_socket.recv(1024).decode("utf-8", errors="ignore")
                except Exception as ret:
                    print("--->2<---這個客戶端沒有發送過來請求: ", ret)  # for test
                else:
                    if request:
                        # 2.1 為這個客戶端服務
                        self.handle_request(request, client_socket)
                    else:
                        # 2.2 客戶端呼叫了close導致recv回傳,服務端將關閉客戶端套接字并從輪詢串列中洗掉
                        client_socket.close()
                        self.client_socket_list.remove(client_socket)

            print(self.client_socket_list)

    def handle_request(self, request, client_socket):
        """為客戶端服務"""
        # 1.提取客戶端發送過來的資料
        request_header_lines = request.splitlines()
        for i, line in enumerate(request_header_lines):
            print(i, line)

        # GET /index.html HTTP/1.1
        http_request_line = request_header_lines[0]
        ret = re.match(r"([^/]*)([^ ]+)", http_request_line)
        if ret:
            print("正則提取資料:", ret.group(1))
            print("正則提取資料:", ret.group(2))
            get_file_name = ret.group(2)
            # 如果沒有指定訪問哪個頁面,則默認訪問index.html
            # GET / HTTP/1.1
            if get_file_name == "/":
                get_file_name = DOCUMENTS_ROOT + "/index.html"
            else:
                get_file_name = DOCUMENTS_ROOT + get_file_name
            print("要訪問的完整路徑是: %s" % get_file_name)

        # 2.回傳HTTP格式的資料給瀏覽器
        try:
            f = open(get_file_name, "rb")
        except IOError:
            # 404表示沒有這個頁面
            response_body = "====sorry, file not found====".encode("utf-8")
            response_headers = "HTTP/1.1 404 not found\r\n"
            response_headers += "Content-Type: text/html; charset=utf-8\r\n"
            response_headers += "Content-Length: %d\r\n" % (len(response_body))
            response_headers += "\r\n"
        else:
            # 2.1 準備發送給瀏覽器的資料-->body
            response_body = f.read()
            f.close()
            # 2.2 準備發送給瀏覽器的資料-->header
            response_headers = "HTTP/1.1 200 OK\r\n"
            # 2.3 發送body資料長度,讓服務器根據長度確認body資料已完成接收,實作長連接
            response_headers += "Content-Length: %d\r\n" % (len(response_body))
            # 2.4 用一個空的行與body進行隔開
            response_headers += "\r\n"
        finally:
            # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
            # 先發送response的頭資訊
            client_socket.send(response_headers.encode("utf-8"))
            # 再發送body
            client_socket.send(response_body)
            # 長連接下不需要關閉套接字,而是在header頭中宣告要發送的body長度
            # client_socket.close()


# 設定服務器靜態資源的路徑
DOCUMENTS_ROOT = "./html"


def main():
    # python3 xxx.py 7890
    if len(sys.argv) == 2:
        port = sys.argv[1]
        if port.isdigit():
            port = int(port)
    else:
        print("運行方式如: python3 xxx.py 7890")
        return
    server_addr = ("", port)
    http_server = WSGIServer(server_addr)
    print("web Server: Serving HTTP on port %d ...\n" % port)
    http_server.serve_forever()


if __name__ == "__main__":
    main()

13.Web靜態服務器-IO多路復用(基于epoll的單行程單執行緒長連接高并發)

# html檔案夾網盤鏈接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓后放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import sys
import socket
import re
import select


class WsgiServer:
    """定義一個WSGI服務器的類"""
    def __init__(self, server_addr):
        # 1.創建資料流套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2.設定當服務器先close 即服務器端4次揮手之后資源能夠立即釋放,保證下次運行程式時可以立即系結7890埠
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3.系結服務器地址
        self.tcp_server_socket.bind(server_addr)
        # 4.套接字由主動改為監聽,并設定佇列長度
        self.tcp_server_socket.listen(128)

        # 5.創建epoll物件
        self.epoll = select.epoll()
        # 6.將tcp服務器的監聽套接對應的fd注冊到epoll中進行監聽,如果fd已經注冊過,則會發生例外
        self.epoll.register(self.tcp_server_socket.fileno(), select.EPOLLIN | select.EPOLLET)
        # 7.創建添加的fd對應的套接字
        self.fd_socket = dict()

    def serve_forever(self):
        """回圈運行服務器,等待客戶端鏈接并為客戶端服務"""
        # 等待新的客戶端連接或者已連接的客戶端發送過來資料
        while True:
            # epoll進行fd掃描的地方,未指定超時時間則為阻塞等待,直到系統檢測到資料到來,通過事件通知方式告訴程式才會解阻塞
            epoll_list = self.epoll.poll()  # epoll_list串列結構: [(fd, event),]
            # fd: 套接字對應的檔案描述符
            # event: 這個檔案描述符到底是什么事件,例如可以呼叫recv接收等

            # 對事件進行判斷
            for fd, event in epoll_list:
                # 如果是服務器的監聽套接字被激活可以收資料,那么意味著可以進行accept,即有新的客戶端連接
                if fd == self.tcp_server_socket.fileno():
                    new_socket, new_addr = self.tcp_server_socket.accept()

                    # 向epoll中注冊連接socket的可讀事件,EPOLLIN(可讀),EPOLLOUT(可寫),EPOLLET(ET模式)
                    # LT模式: 當epoll檢測到描述符事件發生并將此事件通知應用程式
                    # LT模式下應用程式可以不立即處理該事件,下次呼叫epoll時,會再次回應應用程式并通知此事件
                    # ET模式: 當epoll檢測到描述符事件發生并將此事件通知應用程式
                    # LT模式下應用程式必須立即處理該事件,如果不處理,下次呼叫epoll時,不會再次回應應用程式并通知此事件
                    self.epoll.register(new_socket.fileno(), select.EPOLLIN | select.EPOLLET)
                    # 記錄這個資訊
                    self.fd_socket[new_socket.fileno()] = new_socket
                # 如果是客戶端發送資料,客戶端套接字將被激活,服務器可以接收到客服端發送的資料
                elif event == select.EPOLLIN:
                    # # 從激活 fd 上接收
                    request = self.fd_socket[fd].recv(1024).decode("utf-8")
                    if request:
                        self.handle_request(request, self.fd_socket[fd])
                    else:
                        # 在epoll中注銷客戶端的資訊
                        self.epoll.unregister(fd)
                        # 關閉客戶端的檔案句柄
                        self.fd_socket[fd].close()
                        # 在字典中洗掉與已關閉客戶端相關的資訊
                        del self.fd_socket[fd]

    def handle_request(self, request, client_socket):
        """為客戶端服務"""
        # 1.接收瀏覽器發送過來的HTTP請求
        request_header_lines = request.splitlines()
        for i, line in enumerate(request_header_lines):
            print(i, line)

        # GET /index.html HTTP/1.1
        http_request_line = request_header_lines[0]
        ret = re.match(r"([^/]*)([^ ]+)", http_request_line)
        if ret:
            print("正則提取資料:", ret.group(1))
            print("正則提取資料:", ret.group(2))
            get_file_name = ret.group(2)
            # 如果沒有指定訪問哪個頁面,則默認訪問index.html
            # GET / HTTP/1.1
            if get_file_name == "/":
                get_file_name = DOCUMENTS_ROOT + "/index.html"
            else:
                get_file_name = DOCUMENTS_ROOT + get_file_name
            print("要訪問的完整路徑是: %s" % get_file_name)

        # 2.回傳HTTP格式的資料給瀏覽器
        try:
            f = open(get_file_name, "rb")
        except IOError:
            # 404表示沒有這個頁面
            response_body = "====sorry, file not found====".encode("utf-8")
            response_headers = "HTTP/1.1 404 not found\r\n"
            response_headers += "Content-Type: text/html; charset=utf-8\r\n"
            response_headers += "Content-Length: %d\r\n" % (len(response_body))
            response_headers += "\r\n"
        else:
            # 2.1 準備發送給瀏覽器的資料-->body
            response_body = f.read()
            f.close()
            # 2.2 準備發送給瀏覽器的資料-->header
            response_headers = "HTTP/1.1 200 OK\r\n"
            # 2.3 發送body資料長度,讓服務器根據長度確認body資料已完成接收,實作長連接
            response_headers += "Content-Length: %d\r\n" % (len(response_body))
            # 2.4 用一個空的行與body進行隔開
            response_headers += "\r\n"
        finally:
            # 因為頭資訊在組織的時候,是按照字串組織的,不能與以二進制打開檔案讀取的資料合并,因此分開發送
            # 先發送response的頭資訊
            client_socket.send(response_headers.encode("utf-8"))
            # 再發送body
            client_socket.send(response_body)
            # 長連接下不需要關閉套接字,而是在header頭中宣告要發送的body長度
            # client_socket.close()


# 設定服務器靜態資源的路徑
DOCUMENTS_ROOT = "./html"


def main():
    # python3 xxx.py 7890
    if len(sys.argv) == 2:
        port = sys.argv[1]
        if port.isdigit():
            port = int(port)
    else:
        print("運行方式如: python3 xxx.py 7890")
        return
    server_addr = ("", port)
    http_server = WsgiServer(server_addr)
    print("web Server: Serving HTTP on port %d ...\n" % port)
    http_server.serve_forever()


if __name__ == "__main__":
    main()

14.C10K問題(單機1萬網路并發連接和資料處理能力)

    1.C10K問題的本質:
        C10K問題本質上是作業系統的問題,對于Web1.0/2.0時代的作業系統而言, 傳統的同步阻塞I/O模型都是一樣的
        處理的方式都是requests per second并發10K和100的區別關鍵在于CPU
        創建的行程執行緒多了,資料拷貝頻繁(快取I/O,內核將資料拷貝到用戶行程空間,阻塞)
        行程/執行緒背景關系切換消耗大,導致作業系統崩潰,這就是C10K問題的本質

    2.C10K解決方案: 每個行程/執行緒同時處理多個連接(IO多路復用)
        1.盡量避免服務器處理超過1萬個的并發連接
        2.通過改進作業系統內核以及用事件驅動服務器(典型技術實作如:Nginx和Node)代替執行緒服務器(典型代表:Apache)
        3.epoll能達到10k并發,但是epoll有Linux,Unix平臺限制
        3.使用libevent庫開發,libevent庫是對/dev/poll, kqueue, event ports, select, poll和epoll介面的封裝

    3.檔案句柄限制
        1.問題概述:
            在linux下撰寫網路服務器程式的朋友肯定都知道每一個tcp連接都要占一個檔案描述符
            一旦這個檔案描述符使用完了,新的連接到來回傳給我們的錯誤是"Socket/File:Can't open so many files"
            這是因為作業系統對可以打開的最大檔案數的限制
        2.行程限制
            執行 ulimit -n 輸出 1024, 說明對于一個行程而言最多只能打開1024個檔案
            要采用此默認配置最多也就可以并發上千個TCP連接,臨時修改: ulimit -n 1000000
            但是這種臨時修改只對當前登錄用戶目前的使用環境有效,系統重啟或用戶退出后就會失效
            重啟后失效的修改,編輯 /etc/security/limits.conf 檔案修改后內容為
                soft nofile 1000000
                hard nofile 1000000
            永久修改: 編輯/etc/rc.local 在其后添加如下內容
                ulimit -SHn 1000000
        3.全域限制
            執行 cat /proc/sys/fs/file-nr
            輸出 3040    0      194572
            輸出釋義: 已經分配的檔案句柄數, 已經分配但沒有使用的檔案句柄數, 最大檔案句柄數
            但在kernel 2.6版本中第二項的值總為0,這并不是一個錯誤,它實際上是已經分配的檔案描述符無一浪費的都已經被使用了
            我們可以把這個數值改大些,用 root 權限修改 /etc/sysctl.conf 檔案
                fs.file-max = 1000000
                net.ipv4.ip_conntrack_max = 1000000
                net.ipv4.netfilter.ip_conntrack_max = 1000000

    4.埠號范圍限制
        問題誤區:
            作業系統上埠號1024以下是系統保留的,從1024-65535是用戶使用的
            由于每個TCP連接都要占一個埠號,所以我們最多可以有60000多個并發連接
        如何標識一個TCP連接
            系統用一個4四元組來唯一標識一個TCP連接: {local ip, local port,remote ip,remote port}
            《UNIX網路編程: 卷一》第四章中對accept的講解來看看概念性的東西,第二個引數cliaddr代表了客戶端的ip地址和埠號
            而我們作為服務端實際只使用了bind時這一個埠,說明埠號65535并不是并發量的限制
        server最大tcp連接數
            server通常固定在某個本地埠上監聽,等待client的連接請求
            不考慮地址重用(unix的SO_REUSEADDR選項)的情況下,即使server端有多個ip,本地監聽埠也是獨占的
            因此server端tcp連接4元組中只有remote ip(也就是client ip)和remote port(客戶端port)是可變的
            得出最大tcp連接為客戶端ip數×客戶端port數

            對IPV4,不考慮ip地址分類等因素,最大tcp連接數約為2的32次方(ip數) × 2的16次方(port數)
            得出server端單機最大tcp連接數約為2的48次方

    5.檔案句柄限制和埠范圍限制小結
        上述得出的是理論上的單機TCP并發連接數,實際上單機并發連接數要受硬體資源(記憶體),網路資源(帶寬)的限制

    6.C10K問題參考自如下鏈接:
        http://www.52im.net/thread-561-1-1.html
        http://www.52im.net/thread-566-1-1.html

15.C10M問題(單機1000萬網路并發連接和資料處理能力)

    1.C10M問題的本質:
        1.硬體上完全可以處理1000萬個以上的并發連接,如果它們不能,那是因為你選擇了錯誤的軟體,而不是底層硬體的問題
        2.OS的內核不是解決C10M問題的辦法,恰恰相反OS的內核正是導致C10M問題的關鍵所在
        3.不要讓OS內核執行所有繁重的任務:
            將資料包處理,記憶體管理,處理器調度等任務從內核轉移到應用程式高效地完成
            讓諸如Linux這樣的OS只處理控制層,資料層完全交給應用程式來處理

    2.C10M問解決方案:
        1.CPU親和性 & 記憶體局域性
        2.RSS, RPS, RFS, XPS
        3.IRQ 優化
        4.Kernel 優化
        5. CPU 記憶體 網路

    3.C10M問題參考自如下鏈接:
        http://www.52im.net/thread-568-1-1.html
        http://www.52im.net/thread-578-1-1.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/5082.html

標籤:Python

上一篇:Python爬取各大汽車銷量資訊

下一篇:u"Field 'download_file_content' doesn't have a default value"錯誤解決

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more