Python3網絡學習案例三:編寫web server


1. 寫在前面

這里總結的並不夠詳細,有時間了再進行補充。

2. 設計思路

HTTP協議是建立在TCP上的
1. 建立服務器端TCP套接字(綁定ip,port),等待監聽連接:listen
(2. 打開瀏覽器(client)訪問這個(ip,port),服務器端接收連接:accept)
3. 獲取瀏覽器的請求內容:data = recv(1024)
# 由於瀏覽器發送的request是HTTP格式的,需要解碼
4. 將接收的報文節解碼:decode
# 解析解碼后的數據
5. 根據行分切數據
6. 解析首部行(header)為:方法,請求路徑+文件名
7. 根據解析首部行獲取的數據來查找並獲取文件內容
8. 構建響應報文(也要是HTTP報文格式的),包括首部行響應信息(200 OK或是file cannot found)
9. 編碼響應報文:encode
10. 關閉socket連接

3. 兩個版本

3.1 多線程版本

這里采用多線程的方法對每一個請求連接本機的請求建立連接,缺點在於除非關閉服務器程序,否則已建立連接的套接字不會被釋放,耗費資源

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import socket
import threading


def handleRequest(tcpSocket):
    # 1. Receive request message from the client on connection socket
    requestData = tcpSocket.recv(1024)
    # 2. Extract the path of the requested object from the message (second part of the HTTP header)
    requestList = requestData.decode().split("\r\n")
    reqHeaderLine = requestList[0]
    print("request line: " + reqHeaderLine)
    fileName = reqHeaderLine.split(" ")[1].replace("/", "")
    #  3. Read the corresponding file from disk
    try:
        file = open("./" + fileName, 'rb')  # read the corresponding file from disk
        print("fileName: " + fileName)
        # 4. Store in temporary buffer
        content = file.read().decode()  # store in temporary buffer
        file.close()
        resHeader = "HTTP/1.1 200 OK\r\n" + \
                    "Server: 127.0.0.1\r\n" + "\r\n"
        response = (resHeader + content).encode(encoding="UTF-8")  # send the correct HTTP response
    except FileNotFoundError:
        content = "404 NOT FOUND\n"
        resHeader = "HTTP/1.1 404 Not Found\r\n" + \
                    "Server: 127.0.0.1\r\n" + "\r\n"
        response = (resHeader + content).encode(encoding="UTF-8")  # send the correct HTTP response error
        # 5. Send the correct HTTP response error
        tcpSocket.sendall(response)
    # 6. Send the content of the file to the socket
    else:
        tcpSocket.sendall(response)
    # 7. Close the connection socket
    tcpSocket.close()


def startServer(serverAddress, serverPort):
    # 1. Create server socket
    serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 2. Bind the server socket to server address and server port
    serverSocket.bind((serverAddress, serverPort))
    # 3. Continuously listen for connections to server socket
    serverSocket.listen(0)
    # 4. When a connection is accepted, call handleRequest function, passing new connection socket (see
    # https://docs.python.org/3/library/socket.html#socket.socket.accept)
    while True:
        try:
            print("wait for connecting...")
            print("while true")
            tcpSocket, clientAddr = serverSocket.accept()
            print("one connection is established, ", end="")
            print("address is: %s" % str(clientAddr))
            handleThread = threading.Thread(target=handleRequest, args=(tcpSocket,))
            handleThread.start()
        except Exception as err:
            print(err)
            break
    #  5. Close server socket
    serverSocket.close()


if __name__ == '__main__':
    while True:
        try:
            hostPort = int(input("Input the port you want: "))
            startServer("", hostPort)
            break
        except Exception as e:
            print(e)
            continue

 

3.2 多進程版本

改進了多線程版本的“缺點”

import multiprocessing
import socket


def handleReq(clientSocket):
    requestData = clientSocket.recv(1024)
    requestList = requestData.decode().split("\r\n")
    reqHeaderLine = requestList[0]
    print("request line: " + reqHeaderLine)
    fileName = reqHeaderLine.split(" ")[1].replace("/", "")
    try:
        file = open("./" + fileName, 'rb')  # read the corresponding file from disk
        print("fileName: " + fileName)  # 查看文件名
    except FileNotFoundError:
        responseHeader = "HTTP/1.1 404 Not Found\r\n" + \
                         "Server: 127.0.0.1\r\n" + "\r\n"

        responseData = responseHeader + "No such file\nCheck your input\n"

        content = (responseHeader + responseData).encode(encoding="UTF-8")  # send the correct HTTP response error
    else:
        content = file.read()  # store in temporary buffer
        file.close()
    resHeader = "HTTP/1.1 200 OK\r\n"
    fileContent01 = "Server: 127.0.0.1\r\n"
    fileContent02 = content.decode()
    response = resHeader + fileContent01 + "\r\n" + fileContent02  # send the correct HTTP response
    clientSocket.sendall(response.encode(encoding="UTF-8"))


def startServer(serverAddr, serverPort):
    serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serverSocket.bind((serverAddr, serverPort))
    serverSocket.listen(0)
    while True:
        try:
            print("wait for connecting...")
            print("while true")
            clientSocket, clientAddr = serverSocket.accept()
            print("one connection is established, ", end="")
            print("address is: %s" % str(clientAddr))
            handleProcess = multiprocessing.Process(target=handleReq, args=(clientSocket,))
            handleProcess.start()  # handle request
            clientSocket.close()
            print("client close")
        except Exception as err:
            print(err)
            break
    serverSocket.close()  # while出錯了就關掉


if __name__ == '__main__':
    ipAddr = "127.0.0.1"
    port = 8000
    startServer(ipAddr, port)

這個版本與多線程版本的區別:

1. 建立套接字時對套接字進行了相關設置【稍后解釋】

2. 在開啟新進程之后調用“clientSocket.close()”釋放資源

對第一點不同的解釋

下面解釋的來源:https://www.jb51.net/article/50858.htm

python定義了setsockopt()和getsockopt(),一個是設置選項,一個是得到設置。這里主要使用setsockopt(),具體結構如下:

setsockopt(level,optname,value)

level定義了哪個選項將被使用。通常情況下是SOL_SOCKET,意思是正在使用的socket選項。它還可以通過設置一個特殊協議號碼來設置協議選項,然而對於一個給定的操作系統,大多數協議選項都是明確的,所以為了簡便,它們很少用於為移動設備設計的應用程序。

optname參數提供使用的特殊選項。關於可用選項的設置,會因為操作系統的不同而有少許不同。如果level選定了SOL_SOCKET,那么一些常用的選項見下表:

選項

意義

期望值

SO_BINDTODEVICE

可以使socket只在某個特殊的網絡接口(網卡)有效。也許不能是移動便攜設備

一個字符串給出設備的名稱或者一個空字符串返回默認值

SO_BROADCAST

允許廣播地址發送和接收信息包。只對UDP有效。如何發送和接收廣播信息包

布爾型整數

SO_DONTROUTE

禁止通過路由器和網關往外發送信息包。這主要是為了安全而用在以太網上UDP通信的一種方法。不管目的地址使用什么IP地址,都可以防止數據離開本地網絡

布爾型整數

SO_KEEPALIVE

可以使TCP通信的信息包保持連續性。這些信息包可以在沒有信息傳輸的時候,使通信的雙方確定連接是保持的

布爾型整數

SO_OOBINLINE

可以把收到的不正常數據看成是正常的數據,也就是說會通過一個標准的對recv()的調用來接收這些數據

布爾型整數

SO_REUSEADDR

socket關閉后,本地端用於該socket的端口號立刻就可以被重用。通常來說,只有經過系統定義一段時間后,才能被重用。

布爾型整數

本節在學習時,用到了SO_REUSEADDR選項,具體寫法是:

S.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 這里value設置為1,表示將SO_REUSEADDR標記為TRUE,操作系統會在服務器socket被關閉或服務器進程終止后馬上釋放該服務器的端口,否則操作系統會保留幾分鍾該端口。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM