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()注释掉之后,这个错误就没有了,但是产生错误具体的原因至今未明,如果有知道的同学欢迎在评论区告诉我。

  • 相关阅读:
    一本通1273货币系统(方案数背包)
    背包体积循环正序和逆序的区别
    Python字典的底层原理和优缺点
    Linux各目录及每个目录的详细介绍
    openwrt 下python程序后台运行,并将打印信息保存文件
    pycharm同一目录下无法import其他文件
    python sqlite3学习笔记
    python sqlite3查询表记录
    Pycharm快捷键的使用
    Python3 Address already in use 解决方法
  • 原文地址:https://www.cnblogs.com/marvin-wen/p/15173148.html
Copyright © 2011-2022 走看看