python實現多進程與多線程HTTP服務器,瀏覽器通過HTTP與之通信3

        因爲網絡間通信是基於TCP協議傳輸數據的,而服務器與瀏覽器之間通信是基於HTTP協議的,那麼下面基於python實現一個多進程或多線程tcp服務器,瀏覽器可以基於http協議進行發送請求和解析。瀏覽器展示返回的一個標準的HTML網頁,此外實現服務器解析客戶端多次請求並且返回請求結果。即:客戶端根據HTML裏面的各種鏈接,再發送HTTP請求給服務器,拿到相應的圖片、視頻、Flash、JavaScript腳本、CSS等各種資源,最終顯示出一個完整的頁面。

1.代碼實現多進程HTTP服務器與測試

      如下所謂多進程HTTP服務器和之前的單進程區別就是,這裏使用一個主進程接受accept()請求,但是使用多進程分別去解析,響應瀏覽器的請求。舉個簡單例子就是:之前是一個人接受請求,並且處理請求;現在是一個人負責接受請求,然後分發給不同人的人去處理請求。這樣整體來說響應和處理的速度要快很多,這既是多進程多線程在這裏的體現。因爲之前如果使用單進程單線程一旦recv()函數堵塞時,這個通信都會堵塞,現在使用多線程與多進程則解決了這個堵塞問題。

import socket
import re
import multiprocessing

def service_client(new_socket):
    """爲這個客戶端返回數據"""

    # 1. 接收瀏覽器發送過來的請求 ,即http請求
    # GET / HTTP/1.1
    request = new_socket.recv(1024).decode("utf-8")
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">" * 20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])  #獲取請求文件名
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"

    # 2. 返回http格式的數據,給瀏覽器

    try:
        f = open("./html" + file_name, "rb")
    except: #對於沒有找到請求文件路徑的返回結果
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()
        # 2.1 準備發送給瀏覽器的數據---header
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"
        # 2.2 準備發送給瀏覽器的數據---boy
        # response += "hahahhah"

        # 將response header發送給瀏覽器
        new_socket.send(response.encode("utf-8"))
        # 將response body發送給瀏覽器
        new_socket.send(html_content)

    # 關閉套接
    new_socket.close()

def main():
    """用來完成整體的控制"""
    # 1. 創建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 綁定
    tcp_server_socket.bind(("", 7890))

    # 3. 變爲監聽套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待新客戶端的鏈接
        new_socket, client_addr = tcp_server_socket.accept()

        # 5. 爲這個客戶端服務,這裏啓動多進程去處理服務器接收的請求
        p = multiprocessing.Process(target=service_client, args=(new_socket,))
        p.start()  #同樣,子進程都是用start()方法啓動

        new_socket.close()  #注意使用多進程以後這裏多了一個close().之前單進程沒有的。主進程和子進程都要關閉。

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


if __name__ == "__main__":
    main()

'''瀏覽器請求後服務器打印顯示的請求記錄
D:\software\python3\python.exe D:/pythoyworkspace/file_demo/Class_Demo/pachong/bbb.py

>>>>>>>>>>>>>>>>>>>>
['GET / HTTP/1.1', 'Host: 192.168.1.1:7890', 'Connection: keep-alive', 'Upgrade-Insecure-Requests: 1', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Encoding: gzip, deflate', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', '']

>>>>>>>>>>>>>>>>>>>>
['GET /classic.css HTTP/1.1', 'Host: 192.168.1.1:7890', 'Connection: keep-alive', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', 'Accept: text/css,*/*;q=0.1', 'Referer: http://192.168.1.1:7890/', 'Accept-Encoding: gzip, deflate', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', '']

>>>>>>>>>>>>>>>>>>>>
['GET /images/qt-logo.png HTTP/1.1', 'Host: 192.168.1.1:7890', 'Connection: keep-alive', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', 'Accept: image/webp,image/apng,image/*,*/*;q=0.8', 'Referer: http://192.168.1.1:7890/', 'Accept-Encoding: gzip, deflate', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', '']

>>>>>>>>>>>>>>>>>>>>
['GET /images/mainwindow-vertical-tabs.png HTTP/1.1', 'Host: 192.168.1.1:7890', 'Connection: keep-alive', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', 'Accept: image/webp,image/apng,image/*,*/*;q=0.8', 'Referer: http://192.168.1.1:7890/qt4-3-intro.html', 'Accept-Encoding: gzip, deflate', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', '']

>>>>>>>>>>>>>>>>>>>>
['GET /images/mainwindow-custom-dock.png HTTP/1.1', 'Host: 192.168.1.1:7890', 'Connection: keep-alive', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', 'Accept: image/webp,image/apng,image/*,*/*;q=0.8', 'Referer: http://192.168.1.1:7890/qt4-3-intro.html', 'Accept-Encoding: gzip, deflate', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', '']

...........
'''

使用瀏覽器去訪問服務器網址,瀏覽器成功解析,並且網頁中的子鏈接也可以打卡,成功處理,完成響應。

2.python實現多進程服務器,用類封裝程序 

下面基於實際開發,將上面的程序使用類進行封裝,結果如下,實際訪問功能和上面一樣。

#coding=utf-8
import socket
import re
import multiprocessing

class WSGIServer(object):

    def __init__(self, server_address):
        # 創建一個tcp套接字
        self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 允許立即使用上次綁定的port
        self.listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 綁定
        self.listen_socket.bind(server_address)
        # 變爲被動,並制定隊列的長度
        self.listen_socket.listen(128)

    def serve_forever(self):
        "循環運行web服務器,等待客戶端的鏈接併爲客戶端服務"
        while True:
            # 等待新客戶端到來
            client_socket, client_address = self.listen_socket.accept()
            print(client_address)  # for test
            new_process = multiprocessing.Process(target=self.handleRequest, args=(client_socket,))
            new_process.start()

            # 因爲子進程已經複製了父進程的套接字等資源,所以父進程調用close不會將他們對應的這個鏈接關閉的
            client_socket.close()

    def handleRequest(self, client_socket):
        "用一個新的進程,爲一個客戶端進行服務"
        recv_data = client_socket.recv(1024).decode('utf-8')
        print(recv_data)
        requestHeaderLines = recv_data.splitlines()
        for line in requestHeaderLines:
            print(line)

        request_line = requestHeaderLines[0]
        get_file_name = re.match("[^/]+(/[^ ]*)", request_line).group(1)
        print("file name is ===>%s" % get_file_name) # for test

        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

        try:
            f = open(get_file_name, "rb")
        except IOError:
            response_header = "HTTP/1.1 404 not found\r\n"
            response_header += "\r\n"
            response_body = "====sorry ,file not found===="
        else:
            response_header = "HTTP/1.1 200 OK\r\n"
            response_header += "\r\n"
            response_body = f.read()
            f.close()
        finally:
            client_socket.send(response_header.encode('utf-8'))
            client_socket.send(response_body)
            client_socket.close()

# 設定服務器的端口
SERVER_ADDR = (HOST, PORT) = "", 8888
# 設置服務器服務靜態資源時的路徑
DOCUMENTS_ROOT = "./html"


def main():
    httpd = WSGIServer(SERVER_ADDR)
    print("web Server: Serving HTTP on port %d ...\n" % PORT)
    httpd.serve_forever()

if __name__ == "__main__":
    main()

3.python通過多線程的方式實現http服務器

多進程改成多線程代碼上改動的很少,只是將multiprocessing改成threading即可,如下:

​
#coding=utf-8
import socket
import re
import threading

class WSGIServer(object):

    def __init__(self, server_address):
        # 創建一個tcp套接字
        self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 允許立即使用上次綁定的port
        self.listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 綁定
        self.listen_socket.bind(server_address)
        # 變爲被動,並制定隊列的長度
        self.listen_socket.listen(128)

    def serve_forever(self):
        "循環運行web服務器,等待客戶端的鏈接併爲客戶端服務"
        while True:
            # 等待新客戶端到來
            client_socket, client_address = self.listen_socket.accept()
            print(client_address)
            new_process = threading.Thread(target=self.handleRequest, args=(client_socket,))
            new_process.start()

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

    def handleRequest(self, client_socket):
        "用一個新的進程,爲一個客戶端進行服務"
        recv_data = client_socket.recv(1024).decode('utf-8')
        print(recv_data)
        requestHeaderLines = recv_data.splitlines()
        for line in requestHeaderLines:
            print(line)

        request_line = requestHeaderLines[0]
        get_file_name = re.match("[^/]+(/[^ ]*)", request_line).group(1)
        print("file name is ===>%s" % get_file_name) # for test

        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

        try:
            f = open(get_file_name, "rb")
        except IOError:
            response_header = "HTTP/1.1 404 not found\r\n"
            response_header += "\r\n"
            response_body = "====sorry ,file not found===="
        else:
            response_header = "HTTP/1.1 200 OK\r\n"
            response_header += "\r\n"
            response_body = f.read()
            f.close()
        finally:
            client_socket.send(response_header.encode('utf-8'))
            client_socket.send(response_body)
            client_socket.close()


# 設定服務器的端口
SERVER_ADDR = (HOST, PORT) = "", 8888
# 設置服務器服務靜態資源時的路徑
DOCUMENTS_ROOT = "./html"


def main():
    httpd = WSGIServer(SERVER_ADDR)
    print("web Server: Serving HTTP on port %d ...\n" % PORT)
    httpd.serve_forever()

if __name__ == "__main__":
    main()

​

同樣使用瀏覽器可以訪問這個多線程服務器,功能上來說,也是差不多。多線程同時處理請求。

統一聲明:關於原創博客內容,可能會有部分內容參考自互聯網,如有原創鏈接會聲明引用;如找不到原創鏈接,在此聲明如有侵權請聯繫刪除哈。關於轉載博客,如有原創鏈接會聲明;如找不到原創鏈接,在此聲明如有侵權請聯繫刪除哈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章