日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

用python實(shí)現(xiàn)自己的http服務(wù)器——多進(jìn)程、多線程、協(xié)程、單進(jìn)程非堵塞版

系統(tǒng) 1723 0

了解http協(xié)議

http請(qǐng)求頭

          
            GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
          
        

最主要的頭兩行分析如下:

  • GET表示一個(gè)讀取請(qǐng)求,將從服務(wù)器獲得網(wǎng)頁(yè)數(shù)據(jù),/表示URL的路徑,URL總是以/開(kāi)頭,/就表示首頁(yè),最后的HTTP/1.1指示采用的HTTP協(xié)議版本是1.1。
  • 目前HTTP協(xié)議的版本就是1.1,但是大部分服務(wù)器也支持1.0版本,主要區(qū)別在于1.1版本允許多個(gè)HTTP請(qǐng)求復(fù)用一個(gè)TCP連接,以加快傳輸速度。
  • Host: www.baidu.com表示請(qǐng)求的域名是www.baidu.com。如果一臺(tái)服務(wù)器有多個(gè)網(wǎng)站,服務(wù)器就需要通過(guò)Host來(lái)區(qū)分瀏覽器請(qǐng)求的是哪個(gè)網(wǎng)站。

http響應(yīng)頭

          
            HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0x8ef7ae5901149cf7
Cache-Control: private
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Wed, 28 Aug 2019 01:59:49 GMT
Expires: Wed, 28 Aug 2019 01:59:48 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=249; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=1426_21111_20697_29522_29518_29099_29568_29220_26350; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
          
        

說(shuō)明:

  • 200表示一個(gè)成功的響應(yīng),后面的OK是說(shuō)明。
  • Content-Type指示響應(yīng)的內(nèi)容,這里是text/html表示HTML網(wǎng)頁(yè)。
  • 請(qǐng)求頭和響應(yīng)頭通過(guò)\r\n來(lái)?yè)Q行。
  • 響應(yīng)頭和body響應(yīng)體中也通過(guò)\r\n來(lái)分隔。

簡(jiǎn)單的http服務(wù)器

有多簡(jiǎn)單呢?運(yùn)行程序后打開(kāi)瀏覽器,只能顯示hello world。

          
            import socket


def service_client(new_socket):
    # 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
    # GET / HTTP/1.1
    request = new_socket.recv(1024)
    print(request)
    # 返回http響應(yīng)
    resposne = "HTTP/1.1 200 OK\r\n"
    resposne += "\r\n"
    resposne += "
            

hello world

" new_socket.send(resposne.encode("utf-8")) # 關(guān)閉套接字 new_socket.close() def main(): # 創(chuàng)建套接字 http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 防止端口被占用無(wú)法啟動(dòng)程序 http_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 綁定端口 http_server.bind(("", 80)) # 變?yōu)楸O(jiān)聽(tīng)套接字 http_server.listen(128) while True: # 等在新客戶端連接 client, info = http_server.accept() # 為這個(gè)客戶端服務(wù) service_client(client) if __name__ == "__main__": main()

單進(jìn)程http服務(wù)器

它上之前有一個(gè)升級(jí)就是可以返回靜態(tài)的html頁(yè)面。

          
            import socket
import re


def service_client(new_socket):
    # 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
    # GET / HTTP/1.1
    request = new_socket.recv(1024).decode("utf-8")
    # print(request)
    request_lines = request.splitlines()
    req = re.match(r"[^/]+(/\S*)", request_lines[0])
    file_name: str = ""
    if req:
        file_name = req.group(1)
        if file_name == "/":
            file_name = "/index.html"
        print(file_name)
    # print(request_lines)
    # 返回http響應(yīng)

    try:
        # 打開(kāi)要請(qǐng)求的html文件,并返回給客戶端。網(wǎng)頁(yè)在當(dāng)前路徑的html文件夾里面。
        with open("./html" + file_name, "r", encoding="utf-8") as f:
            resposne = "HTTP/1.1 200 OK\r\n"
            resposne += "\r\n"
            # resposne += "
            

hello world

" resposne += f.read() except Exception as e: resposne = "HTTP/1.1 400 NOT FOUND\r\n" resposne += "\r\n" resposne += "--file not found--" new_socket.send(resposne.encode("utf-8")) # 關(guān)閉套接字 new_socket.close() def main(): # 創(chuàng)建套接字 http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 防止端口被占用無(wú)法啟動(dòng)程序 http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 綁定端口 http_server.bind(("", 80)) # 變?yōu)楸O(jiān)聽(tīng)套接字 http_server.listen(128) while True: # 等在新客戶端連接 client, info = http_server.accept() # 為這個(gè)客戶端服務(wù) service_client(client) if __name__ == "__main__": main()

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

http服務(wù)器是完成了,但是如果同時(shí)有好多人訪問(wèn)的話它反應(yīng)就會(huì)非常慢,所有又在之前的基礎(chǔ)上做了升級(jí),增加服務(wù)器的并發(fā)能力。

          
            import socket
import re
from multiprocessing import Process

def service_client(new_socket):
    # 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
    # GET / HTTP/1.1
    request = new_socket.recv(1024).decode("utf-8")
    # print(request)
    request_lines = request.splitlines()
    req = re.match(r"[^/]+(/\S*)", request_lines[0])
    file_name: str = ""
    if req:
        file_name = req.group(1)
        if file_name == "/":
            file_name = "/index.html"
        print(file_name)
    # print(request_lines)
    # 返回http響應(yīng)

    try:
        with open("./html" + file_name, "r", encoding="utf-8") as f:
            resposne = "HTTP/1.1 200 OK\r\n"
            resposne += "\r\n"
            # resposne += "
            

hello world

" resposne += f.read() except Exception as e: resposne = "HTTP/1.1 400 NOT FOUND\r\n" resposne += "\r\n" resposne += "--file not found--" new_socket.send(resposne.encode("utf-8")) # new_socket.send(body) # 關(guān)閉套接字 new_socket.close() def main(): # 創(chuàng)建套接字 http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 防止端口被占用無(wú)法啟動(dòng)程序 http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 綁定端口 http_server.bind(("", 80)) # 變?yōu)楸O(jiān)聽(tīng)套接字 http_server.listen(128) while True: # 等在新客戶端連接 client, info = http_server.accept() # 開(kāi)啟一個(gè)子進(jìn)程為這個(gè)客戶端服務(wù) p = Process(target=service_client,args=(client,)) p.start() # 子進(jìn)程會(huì)復(fù)制主進(jìn)程發(fā)的資源,故把主進(jìn)程的socket關(guān)閉。 client.close() if __name__ == "__main__": main()

多線程服務(wù)器

我們知道進(jìn)程耗費(fèi)資源是非常大的,所以這次使用了耗費(fèi)資源小的線程來(lái)實(shí)現(xiàn)多任務(wù)。

          
            import socket
import re
from threading import Thread


def service_client(new_socket):
    # 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
    # GET / HTTP/1.1
    request = new_socket.recv(1024).decode("utf-8")
    # print(request)
    request_lines = request.splitlines()
    req = re.match(r"[^/]+(/\S*)", request_lines[0])
    file_name: str = ""
    if req:
        file_name = req.group(1)
        if file_name == "/":
            file_name = "/index.html"
        print(file_name)
    # print(request_lines)
    # 返回http響應(yīng)

    try:
        with open("./html" + file_name, "r", encoding="utf-8") as f:
            resposne = "HTTP/1.1 200 OK\r\n"
            resposne += "\r\n"
            # resposne += "
            

hello world

" resposne += f.read() except Exception as e: resposne = "HTTP/1.1 400 NOT FOUND\r\n" resposne += "\r\n" resposne += "--file not found--" new_socket.send(resposne.encode("utf-8")) # new_socket.send(body) # 關(guān)閉套接字 new_socket.close() def main(): # 創(chuàng)建套接字 http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 防止端口被占用無(wú)法啟動(dòng)程序 http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 綁定端口 http_server.bind(("", 80)) # 變?yōu)楸O(jiān)聽(tīng)套接字 http_server.listen(128) while True: # 等在新客戶端連接 client, info = http_server.accept() # 開(kāi)啟一個(gè)子線程為這個(gè)客戶端服務(wù) p = Thread(target=service_client, args=(client,)) p.start() if __name__ == "__main__": main()

gevent協(xié)程版的服務(wù)器

協(xié)程在一個(gè)線程中執(zhí)行,減少了線程之間的切換,多線程的升級(jí)版,擁有更好的處理能力。

          
            import socket
import re
import gevent
from gevent import monkey

monkey.patch_all()

def service_client(new_socket):
    # 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
    # GET / HTTP/1.1
    request = new_socket.recv(1024).decode("utf-8")
    # print(request)
    request_lines = request.splitlines()
    req = re.match(r"[^/]+(/\S*)", request_lines[0])
    file_name: str = ""
    if req:
        file_name = req.group(1)
        if file_name == "/":
            file_name = "/index.html"
        print(file_name)
    # print(request_lines)
    # 返回http響應(yīng)

    try:
        with open("./html" + file_name, "r", encoding="utf-8") as f:
            resposne = "HTTP/1.1 200 OK\r\n"
            resposne += "\r\n"
            # resposne += "
            

hello world

" resposne += f.read() except Exception as e: resposne = "HTTP/1.1 400 NOT FOUND\r\n" resposne += "\r\n" resposne += "--file not found--" new_socket.send(resposne.encode("utf-8")) # new_socket.send(body) # 關(guān)閉套接字 new_socket.close() def main(): # 創(chuàng)建套接字 http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 防止端口被占用無(wú)法啟動(dòng)程序 http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 綁定端口 http_server.bind(("", 80)) # 變?yōu)楸O(jiān)聽(tīng)套接字 http_server.listen(128) while True: # 等在新客戶端連接 client, info = http_server.accept() # 為這個(gè)客戶端服務(wù) gevent.spawn(service_client, client) if __name__ == "__main__": main()

單進(jìn)程非堵塞版長(zhǎng)連接的服務(wù)器

從這個(gè)例子中來(lái)引入epoll版,它的性能應(yīng)該要比協(xié)程的好,與之前所有服務(wù)器的不同之處就是采用了長(zhǎng)連接,通過(guò)響應(yīng)頭中的Content-Length來(lái)指定響應(yīng)體的長(zhǎng)度,從而讓瀏覽器知道頁(yè)面數(shù)據(jù)傳輸完成以后自動(dòng)在同一個(gè)套接字連接中繼續(xù)發(fā)送其他資源文件的請(qǐng)求,效率較高。之前的都是短連接,只要傳輸完當(dāng)前文件就關(guān)閉這個(gè)套接字。

          
            import socket
import re


def service_client(new_socket: object, request: str):
    # 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
    # GET / HTTP/1.1
    # request = new_socket.recv(1024).decode("utf-8")
    # print(request)
    request_lines = request.splitlines()
    req = re.match(r"[^/]+(/\S*)", request_lines[0])
    file_name: str = ""
    if req:
        file_name = req.group(1)
        if file_name == "/":
            file_name = "/index.html"
        print(file_name)
    # print(request_lines)
    # 返回http響應(yīng)

    try:
        with open("./html" + file_name, "r", encoding="utf-8") as f:
            resposne_body: str = f.read()
            resposne_header: str = "HTTP/1.1 200 OK\r\n"
            resposne_header += "Content-Length:%d\r\n" % len(resposne_body)
            resposne_header += "\r\n"
            # resposne += "
            

hello world

" resposne = resposne_header + resposne_body except Exception as e: resposne = "HTTP/1.1 400 NOT FOUND\r\n" resposne += "\r\n" resposne += "--file not found--" new_socket.send(resposne.encode("utf-8")) # new_socket.send(body) # 關(guān)閉套接字 # new_socket.close() def main(): # 創(chuàng)建套接字 http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 防止端口被占用無(wú)法啟動(dòng)程序 http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 綁定端口 http_server.bind(("", 80)) # 變?yōu)楸O(jiān)聽(tīng)套接字 http_server.listen(128) # 設(shè)置套接字為非堵塞方式 http_server.setblocking(False) socket_list: list = [] while True: try: # 等在新客戶端連接 client, info = http_server.accept() # 為這個(gè)客戶端服務(wù) # gevent.spawn(service_client, client) except Exception as e: # print(e) pass else: client.setblocking(False) socket_list.append(client) for socket_client in socket_list: try: recv_data: str = socket_client.recv(1024).decode("utf-8") except Exception as e: # print(e) pass else: if recv_data: service_client(socket_client, recv_data) else: socket_list.remove(socket_client) socket_client.close() if __name__ == "__main__": main()

eopll版的服務(wù)器

這個(gè)版本是性能最高的服務(wù)器。它的基本原理就是select,poll,epoll這個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。

          
            import socket
import re
import select


def service_client(new_socket: object, request: str):
    # 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
    # GET / HTTP/1.1
    # request = new_socket.recv(1024).decode("utf-8")
    # print(request)
    request_lines = request.splitlines()
    req = re.match(r"[^/]+(/\S*)", request_lines[0])
    file_name: str = ""
    if req:
        file_name = req.group(1)
        if file_name == "/":
            file_name = "/index.html"
        print(file_name)
    # print(request_lines)
    # 返回http響應(yīng)

    try:
        with open("./html" + file_name, "r", encoding="utf-8") as f:
            resposne_body: str = f.read()
            resposne_header: str = "HTTP/1.1 200 OK\r\n"
            resposne_header += "Content-Length:%d\r\n" % len(resposne_body)
            resposne_header += "\r\n"
            # resposne += "
            

hello world

" resposne = resposne_header + resposne_body except Exception as e: resposne = "HTTP/1.1 400 NOT FOUND\r\n" resposne += "\r\n" resposne += "--file not found--" new_socket.send(resposne.encode("utf-8")) # new_socket.send(body) # 關(guān)閉套接字 # new_socket.close() def main(): # 創(chuàng)建套接字 http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 防止端口被占用無(wú)法啟動(dòng)程序 http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 綁定端口 http_server.bind(("", 80)) # 變?yōu)楸O(jiān)聽(tīng)套接字 http_server.listen(128) # 設(shè)置套接字為非堵塞方式 http_server.setblocking(False) # 創(chuàng)建一個(gè)epoll對(duì)象 epl = select.epoll() # 將監(jiān)聽(tīng)套接字對(duì)應(yīng)的fd(文件描述符)注冊(cè)到epoll中 epl.register(http_server.fileno(), select.EPOLLIN) # 存儲(chǔ)fd文件描述符和套接字的對(duì)應(yīng)關(guān)系 fd_event_dict: dict = {} while True: # 默認(rèn)會(huì)堵塞,知道os檢測(cè)到數(shù)據(jù)到來(lái),通過(guò)事件通知方式告訴這個(gè)程序,此時(shí)才會(huì)解堵塞 fd_event_list: list = epl.poll() # [(套接字對(duì)應(yīng)的文件描述符,這個(gè)文件描述符是什么事件),...] for fd, event in fd_event_list: # 如果是監(jiān)聽(tīng)套接字有數(shù)據(jù)過(guò)來(lái),即等待新的客戶端連接 if fd == http_server.fileno(): client, info = http_server.accept() # 將新的套接字注冊(cè)到epoll中 epl.register(client.fileno(), select.EPOLLIN) # 把文件描述符和套接字的對(duì)應(yīng)關(guān)系存入字典 fd_event_dict[client.fileno()] = client elif event == select.EPOLLIN: # 判斷已連接的套接字是否有數(shù)據(jù)發(fā)過(guò)來(lái) recv_data: str = fd_event_dict[fd].recv(1024).decode("utf-8") if recv_data: service_client(fd_event_dict[fd], recv_data) else: fd_event_dict[fd].close() epl.unregister(fd) del fd_event_dict[fd] if __name__ == '__main__': main()

I/O 多路復(fù)用的特點(diǎn):
通過(guò)一種機(jī)制使一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符,而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài),epoll()函數(shù)就可以返回。 所以, IO多路復(fù)用,本質(zhì)上不會(huì)有并發(fā)的功能,因?yàn)槿魏螘r(shí)候還是只有一個(gè)進(jìn)程或線程進(jìn)行工作,它之所以能提高效率是因?yàn)閟elect\epoll 把進(jìn)來(lái)的socket放到他們的 '監(jiān)視' 列表里面,當(dāng)任何socket有可讀可寫(xiě)數(shù)據(jù)立馬處理,那如果select\epoll 手里同時(shí)檢測(cè)著很多socket, 一有動(dòng)靜馬上返回給進(jìn)程處理,總比一個(gè)一個(gè)socket過(guò)來(lái),阻塞等待,處理高效率。


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

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

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

您的支持是博主寫(xiě)作最大的動(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ì)您有幫助就好】

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

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 宁远县| 宁津县| 荃湾区| 且末县| 阿拉善盟| 连山| 白朗县| 济宁市| 祁东县| 东乌珠穆沁旗| 建平县| 玉门市| 疏勒县| 顺昌县| 四会市| 娄底市| 贡嘎县| 盐山县| 启东市| 庐江县| 阳山县| 娄底市| 双桥区| 渭源县| 嘉峪关市| 关岭| 四子王旗| 阜宁县| 丹江口市| 常德市| 巨野县| 额尔古纳市| 瓮安县| 罗甸县| 会昌县| 阿勒泰市| 会同县| 昔阳县| 崇信县| 新郑市| 托克逊县|