zoukankan      html  css  js  c++  java
  • 从零搭建简易的Web服务器

    本文将使用python套接字编程从零搭建一个简易的web服务器,对应于教材《计算机网络:自顶向下方法》第二章后面套接字编程作业,我们先来看一看客户机(浏览器)和服务器交互的过程中在服务器端发生了哪些事情:

    1. 当一个客户(浏览器)联系服务器时创建一个连接套接字;
    2. 服务器从这个连接接受HTTP请求;
    3. 解释该请求以确定所请求的特定文件;
    4. 从服务器的文件系统获得请求的文件;
    5. 创建一个由请求的文件组成的HTTP响应报文,报文前有首部行;
    6. 经TCP连接向请求的浏览器发送响应;如果文件不存在,则返回404 Not Found差错报文。

    1.Web服务器

    假设我们通过浏览器向服务器请求的文件是HelloWorld.html,文件内容自定(我这里写的内容就是一句话:太棒了,服务器正常工作!),我们需要将该文件放在与服务器同级的目录下,然后通过浏览器向服务器发起请求,服务器按照上述步骤进行响应。服务器端的全部代码如下:

    from socket import *
    
    serverSocket = socket(AF_INET, SOCK_STREAM)
    serverSocket.bind(('', 6789))
    serverSocket.listen(1)
    while True:
        print('服务器已就位')
        connectionSocket, addr = serverSocket.accept()
        try:
            message = connectionSocket.recv(1024).decode()
            filename = message.split()[1]
            f = open(filename[1:], encoding='utf-8')
            outputdata = f.read()
            header = 'HTTP/1.1 200 OK
    Connection: close
    Content-Type: text/html
    Content-Length: %d
    
    ' % (len(outputdata)+24)
            connectionSocket.send(header.encode())
            for i in range(0, len(outputdata)):
                connectionSocket.send(outputdata[i].encode())
            connectionSocket.send("
    ".encode())
            connectionSocket.close()
        except IOError:
            header = 'HTTP/1.1 404 Not Found'
            connectionSocket.send(header.encode())
            connectionSocket.close()
    

    bind(('', 6789))指定了套接字与端口号6789绑定,如果代码是运行在本地的,那么只需在浏览器中输入http://localhost:6789/HelloWorld.html即可访问页面;如果代码是部署在云服务器上的,就需要将IP改为服务器的公网IP,通过这种方式,我们可以很容易地在服务器上部署类似个人简介这样的静态网页。

    listen(1)指定了服务器在同一时刻只接受一个请求,后续我们将通过多线程编码来同时处理多个请求。

    在构造的头部信息中,我们通过Content-Length指定了实体(封装的TCP报文)的长度,即等于数据长度 + TCP头部信息长度,通常TCP的头部信息长度是20字节,但是通过实际观察网页源代码我发现少了四个字节,不难猜测这是因为TCP的选项字段占用了四个字节,因此这里头部信息长度就是24字节。我们甚至不需要自己指定报文长度,只需要返回最基本的HTTP/1.1 200 OK即可。

    这里插入一个题外话,关于Content-Length的使用,通过实践我发现存在以下四种情况:

    1. 不显式指定Content-Length,前端页面显示完好,数据完整;
    2. 显示指定Content-Length且小于实体的长度,前端页面显示不完好,数据缺失;
    3. 显示指定Content-Length且等于实体的长度,前端页面显示完好,数据完整;
    4. 显示指定Content-Length且大于实体的长度,前端页面不显示,浏览器控制台报错ERR_CONTENT_LENGTH_MISMATCH

    也就是说,最糟糕的情况是指定的长度大于实体的长度,由于长度不匹配,浏览器会报错且前端不显示任何东西。如果指定的长度小于实体长度,浏览器只取消息实体的前面一部分,则前端页面显示不完好。在效果上,不显示指定和显式指定为实体长度都是一样的,如果怕麻烦可以不指定。

    2.多线程Web服务器

    参照上面的代码,一个最基本的简易Web服务器就搭建好了,但是它在同一时刻只能处理一个请求,现在我们给它升下级,我们使用多线程的方式让它能够同时处理多个请求。具体代码如下:

    from socket import *
    import threading
    
    
    def tcp_process(connectionSocket):
        print(threading.current_thread())
        try:
            message = connectionSocket.recv(1024).decode()
            print(repr(message))
            print(message)
            filename = message.split()[1]
            f = open(filename[1:], encoding='utf-8')
            outputdata = f.read()
            header = 'HTTP/1.1 200 OK
    Connection: close
    Content-Type: text/html
    Content-Length: %d
    
    ' % (len(outputdata)+24)
            connectionSocket.send(header.encode())
            for i in range(0, len(outputdata)):
                connectionSocket.send(outputdata[i].encode())
            connectionSocket.send("
    ".encode())
            connectionSocket.close()
        except IOError:
            header = 'HTTP/1.1 404 Not Found'
            connectionSocket.send(header.encode())
            connectionSocket.close()
    
    
    if __name__ == "__main__":
        serverSocket = socket(AF_INET, SOCK_STREAM)
        serverSocket.bind(('', 6789))
        serverSocket.listen(10)
        while True:
            print('服务器已就位')
            connectionSocket, addr = serverSocket.accept()
            thread = threading.Thread(target=tcp_process, args=(connectionSocket, ))
            thread.start()
    

    从上面可以看出,我们只是将connectionSocket交给一个具体的线程来执行,该线程负责为具体的客户服务,而主进程不必等待它服务完这个用户就可以接受下一个用户的请求,这样就大大提高了服务器的工作效率。

    3.客户端

    最后我们来看看客户端的代码,通过客户端可以不经过浏览器直接向服务器发起请求。

    from socket import *
    
    clientSocket = socket(AF_INET, SOCK_STREAM)
    clientSocket.connect(('localhost', 6789))
    while True:
        header = 'GET /HelloWorld.html HTTP/1.1
    Host: localhost:6789
    Connection: keep-alive
    User-Agent: Mozilla/5.0
    
    '
        clientSocket.send(header.encode())
        message = clientSocket.recv(1024)
        print(message.decode())
    

    当我通过运行客户端代码向服务器发起请求时,虽然数据成功获取了,但是在客户端也收到了以下错误:

    ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

    这个错误一般出现在爬虫过程中,因为抓取信息太过频繁,而被服务器认定为恶意攻击。但是这里显然不是这个原因,在我将服务器代码中的connectionSocket.close()注释掉之后,这个错误就没有了,但是产生错误具体的原因至今未明,如果有知道的同学欢迎在评论区告诉我。

  • 相关阅读:
    URAL 2067 Friends and Berries (推理,数学)
    URAL 2070 Interesting Numbers (找规律)
    URAL 2073 Log Files (模拟)
    URAL 2069 Hard Rock (最短路)
    URAL 2068 Game of Nuts (博弈)
    URAL 2066 Simple Expression (水题,暴力)
    URAL 2065 Different Sums (找规律)
    UVa 1640 The Counting Problem (数学,区间计数)
    UVa 1630 Folding (区间DP)
    UVa 1629 Cake slicing (记忆化搜索)
  • 原文地址:https://www.cnblogs.com/marvin-wen/p/15173148.html
Copyright © 2011-2022 走看看