zoukankan      html  css  js  c++  java
  • 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("
    ")
        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
    " + 
                        "Server: 127.0.0.1
    " + "
    "
            response = (resHeader + content).encode(encoding="UTF-8")  # send the correct HTTP response
        except FileNotFoundError:
            content = "404 NOT FOUND
    "
            resHeader = "HTTP/1.1 404 Not Found
    " + 
                        "Server: 127.0.0.1
    " + "
    "
            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("
    ")
        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
    " + 
                             "Server: 127.0.0.1
    " + "
    "
    
            responseData = responseHeader + "No such file
    Check your input
    "
    
            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
    "
        fileContent01 = "Server: 127.0.0.1
    "
        fileContent02 = content.decode()
        response = resHeader + fileContent01 + "
    " + 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被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。

     

  • 相关阅读:
    onenote 使用手记0.3阶级
    尘埃落定:没有传说中k700i,官方只认可k700!
    【转】Ubuntu 9.10下安装Eclipse CDT 6.0
    四则运算
    文本内容统计
    《程序员修炼之道:从小工到专家》读后感(4)
    《程序员修炼之道:从小工到专家》读后感(5)
    多线程
    河北金力集团公文流转系统节选(2)
    动手动脑(6)文件和流
  • 原文地址:https://www.cnblogs.com/YuanShiRenY/p/Python_Web_Server.html
Copyright © 2011-2022 走看看