python以單線程非阻塞模式實現HTTP服務器,瀏覽器通過HTTP與之通信5

        多進程一般來說要比單進程效率高,因爲多進程可以解決了單進程recv()阻塞等待的問題。而實際上單進程也可以有非阻塞模式,實現多進程的功能,並且效率更高。所謂單線程非阻塞模式:

          1.首先開啓socket非阻塞模式

          2.然後將socket接受的新客戶端請求放入到一個列表中,不用等待其接受數據完成

          3.遍歷列表,實現數據接收,如果已經接收或則客戶端斷開連接,則將該新客戶端請求移除列表。

1.python實現單進程非堵塞服務器模型

import socket
import time

tcp_server_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_tcp.bind(("", 7899))
tcp_server_tcp.listen(128)
tcp_server_tcp.setblocking(False)  # 設置套接字爲非堵塞的方式

client_socket_list = list()

while True:

    time.sleep(0.5)

    try:
        new_socket, new_addr = tcp_server_tcp.accept()
    except Exception as ret:
        print("---沒有新的客戶端到來---")
    else:
        print("---只要沒有產生異常,那麼也就意味着 來了一個新的客戶端----")
        new_socket.setblocking(False)  # 設置套接字爲非堵塞的方式
        client_socket_list.append(new_socket)

    for client_socket in client_socket_list:
        try:
            recv_data = client_socket.recv(1024)
        except Exception as ret:
            print(ret)
            print("----這個客戶端沒有發送過來數據----")
        else:
            print("-----沒有異常-----")
            print(recv_data)
            if recv_data:
                # 對方發送過來數據
                print("----客戶端發送過來了數據-----")
            else:
                # 對方調用close 導致了 recv返回
                client_socket.close()
                client_socket_list.remove(client_socket)
                print("---客戶端已經關閉----")

如下,通過網絡調試助手,實現多客戶端與之同時通信,服務器以單線程非阻塞模式運行。1.先啓動一個網絡調試助手1,與服務器進行連接,然後不發送數據。2.再啓動一個網絡調試助手2,建立連接,這個時候發送數據。結果顯示服務器端非阻塞模式,與調試助手2進行通信,而不是等到調試助手1發送數據過來。兩者可以併發運行。

2.python實現單進程非堵塞服務器並且與瀏覽器進行通信

   以上面單線程非阻塞模型爲基礎,基於之前案例實現一個單線程非阻塞的HTTP服務器,實現瀏覽器可以基於http協議進行發送請求和解析。瀏覽器展示返回的一個標準的HTML網頁,此外實現服務器解析客戶端多次請求並且返回請求結果。即:客戶端根據HTML裏面的各種鏈接,再發送HTTP請求給服務器,拿到相應的圖片、視頻、Flash、JavaScript腳本、CSS等各種資源,最終顯示出一個完整的頁面。

import time
import socket
import re


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

    def __init__(self, port, documents_root):

        # 1. 創建套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2. 綁定本地信息
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind(("", port))
        # 3. 變爲監聽套接字
        self.server_socket.listen(128)

        self.server_socket.setblocking(False)
        self.client_socket_list = list()

        self.documents_root = documents_root

    def run_forever(self):
        """運行服務器"""

        # 等待對方鏈接
        while True:

            # time.sleep(0.5)  # for test

            try:
                new_socket, new_addr = self.server_socket.accept()
            except Exception as ret:
                print("-----1----", ret)  # for test
            else:
                new_socket.setblocking(False)   
                self.client_socket_list.append(new_socket)

            for client_socket in self.client_socket_list:
                try:
                    request = client_socket.recv(1024).decode('utf-8')
                except Exception as ret:
                    print("------2----", ret)  # for test
                else:
                    if request:
                        self.deal_with_request(request, client_socket) #將接受到的請求組發給deal_with_request函數去完成解析和響應
                    else:
                        client_socket.close()
                        self.client_socket_list.remove(client_socket)  

            print(self.client_socket_list)


    def deal_with_request(self, request, client_socket):
        """爲這個瀏覽器服務器"""
        if not request:
            return

        request_lines = request.splitlines()
        for i, line in enumerate(request_lines):
            print(i, line)

        # 提取請求的文件(index.html)
        # GET /a/b/c/d/e/index.html HTTP/1.1
        ret = re.match(r"([^/]*)([^ ]+)", request_lines[0])
        if ret:
            print("正則提取數據:", ret.group(1))
            print("正則提取數據:", ret.group(2))
            file_name = ret.group(2)
            if file_name == "/":
                file_name = "/index.html"


        # 讀取文件數據
        try:
            f = open(self.documents_root+file_name, "rb")
        except:
            response_body = "file not found, 請輸入正確的url"
            response_header = "HTTP/1.1 404 not found\r\n"
            response_header += "Content-Type: text/html; charset=utf-8\r\n"
            response_header += "Content-Length: %d\r\n" % (len(response_body))
            response_header += "\r\n"

            # 將header返回給瀏覽器
            client_socket.send(response_header.encode('utf-8'))

            # 將body返回給瀏覽器
            client_socket.send(response_body.encode("utf-8"))
        else:
            content = f.read()
            f.close()

            response_body = content
            response_header = "HTTP/1.1 200 OK\r\n"
            response_header += "Content-Length: %d\r\n" % (len(response_body))
            response_header += "\r\n"

            # 將header返回給瀏覽器
            client_socket.send( response_header.encode('utf-8') + response_body)


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


def main():
    port = 7777
    http_server = WSGIServer(port, DOCUMENTS_ROOT)
    http_server.run_forever()


if __name__ == "__main__":
    main()

使用瀏覽器連接該服務器地址,可以實現正常網頁的訪問和響應。

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

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