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被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。

 

posted @ 2020-10-31 14:44  YIYUYI  阅读(1658)  评论(1编辑  收藏  举报