美文网首页
利用Python搭建简单的多线程Web服务器-代码示例(基于TC

利用Python搭建简单的多线程Web服务器-代码示例(基于TC

作者: 越大大雨天 | 来源:发表于2019-04-24 12:52 被阅读0次

    多任务版web服务器程序的实现:

    web服务器基于TCP服务端开发,其基本构成都是相同的,但有最大一点的差异是:
    客户端请求及服务端响应的内容,必须符合html协议,否则将无法获取数据。

    • 客户端请求报文格式示例:

    # 请求行(还有POST请求方式)
    GET / HTTP/1.1\r\n
    # 请求体
    Host: www.itcast.cn\r\n
    Connection: keep-alive\r\n
    Upgrade-Insecure-Requests: 1\r\n
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept-Language: zh-CN,zh;q=0.9\r\n
    Cookie: pgv_pvi=1246921728; \r\n
    # 空行(不能省略)
    \r\n

    • 服务端响应报文格式示例:

    # 响应行
    HTTP/1.1 200 OK\r\n
    # 响应体
    Server: Tengine\r\n
    Content-Type: text/html; charset=UTF-8\r\n
    Transfer-Encoding: chunked\r\n
    Connection: keep-alive\r\n
    Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n
    # 空行(不能省略)
    \r\n
    # 响应体(正文)
    <!DOCTYPE html><html lang=“en”> …</html>

    特别说明:
    每项数据之后都要使用:\r\n(见格式示例)

    以上内容为web传输与普通TCP传输代码的差异。
    本文以多线程web客户端作为代码示例。
    当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
    把创建的子线程设置成为守护主线程,防止主线程无法退出。

    • 代码示例
      注:代码中使用的html源文件是提前放在代码文件同级目录中static目录下的,你可以在网上找到用于练习的html文件。


      使用的html文件
    import threading
    import socket
    
    def handle_recv(new_socket, ip_port):
        print("客户端已连接:", ip_port)
        # 获取浏览器发送的http请求报文数据并解码
        recieve_content = new_socket.recv(4096).decode("utf-8")
        print(recieve_content)
        # 获取用户请求资源的路径(根据浏览器发送的请求行提取)
        path = recieve_content.split(" ",maxsplit=3)[1]
        # 指定如果访问根目录时,返回index.html内容
        if path == "/":
            path = "/index.html"
        # 响应行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 响应头
        response_headers = "Content-Type: text/html;charset=utf-8\r\n"
        # 加入判断,如请求的路径存在,则正常返回,当客服端请求的路径不存在时,返回错误页面
        try:
            with open("./static%s" % path, "rb") as f:
                response_body = f.read()
        except FileNotFoundError:
            response_line = "HTTP/1.1 404 NOT FOUND\r\n"
            path = "/error.html"
            with open("./static%s" % path, "rb") as f:
                response_body = f.read()
        # 服务端发送符合html要求的数据给客服端
        new_socket.send((response_line + response_headers + "\r\n").encode("utf-8") + response_body)
        new_socket.close()
    
    if __name__ == '__main__':
        # 创建服务端tcp socket对象
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 程序退出端口号立即释放
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定端口号
        server_socket.bind(("", 8090))
        # 设置监听
        server_socket.listen(128)
        # 循环等待接受客户端的连接请求
        while True:
            # 主线程负责创建新的套接字接口
            new_socket, ip_port = server_socket.accept()
            # 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
            sub_thread = threading.Thread(target=handle_recv,args=(new_socket, ip_port))
            # 把创建的子线程设置成为守护主线程,防止主线程无法退出。
            sub_thread.setDaemon(True)
            # 启动线程执行对应的任务
            sub_thread.start()
    

    运行呜呜段程序,在浏览器中访问http://127.0.0.1:8090/index2.html

    服务端窗口返回接收到的浏览器请求数据如下,提取的请求文件路径即是通过第一行的请求行提取:

    客户端已连接: ('127.0.0.1', 53729)
    客户端已连接: ('127.0.0.1', 53730)
    GET /index2.html HTTP/1.1 #请求行
    Host: 127.0.0.1:8090
    Connection: keep-alive
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9

    GET /web.jpg HTTP/1.1
    Host: 127.0.0.1:8090
    Connection: keep-alive
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
    Accept: image/webp,image/apng,image/,/*;q=0.8
    Referer: http://127.0.0.1:8090/index2.html
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9

    客服端浏览器返回的页面如下:


    image.png

    当输入不存在的页面时:
    将返回设定好的404错误页面,可以看到,页面的响应体和状态信息和我们代码中设置的一致。


    image.png

    将代码改写为面向对象编程结构

    构造面向对象的代码块,将可以在复用时非常方便的创建新服务器对象。

    import threading
    import socket
    
    class HTTPWebServer(object):
        def __init__(self,port):
            server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            server_socket.bind(("", port))
            server_socket.listen(128)
            self.server_socket = server_socket
        @staticmethod
        def handle_recv(new_socket, ip_port):
            print("客户端已连接:", ip_port)
            recieve_content = new_socket.recv(1024).decode("utf-8")
            print(recieve_content)
            path = recieve_content.split(" ",maxsplit=2)[1]
            if path == "/":
                path = "/index.html"
            response_line = "HTTP/1.1 200 OK\r\n"
            response_headers = "Content-Type: text/html;charset=utf-8\r\n"
            try:
                with open("./static%s" % path, "rb") as f:
                    response_body = f.read()
            except FileNotFoundError:
                response_line = "HTTP/1.1 404 NOT FOUND\r\n"
                path = "/error.html"
                with open("./static%s" % path, "rb") as f:
                    response_body = f.read()
            new_socket.send((response_line + response_headers + "\r\n").encode("utf-8") + response_body)
            new_socket.close()
        def start(self):
            while True:
                new_socket, ip_port = self.server_socket.accept()
                sub_thread = threading.Thread(target=self.handle_recv,args=(new_socket, ip_port))
                sub_thread.setDaemon(True)
                sub_thread.start()
    
    
     
    
    if __name__ == '__main__':
        port = 8080
        server = HTTPWebServer(port)
        server.start()
    

    小结

    这样就完成了一个简单的web服务器,可用于浏览器访问。

    1. 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
    2. 使用.setDaemon(True)设置主线程守护,防止主线程无法退出。

    相关文章

      网友评论

          本文标题:利用Python搭建简单的多线程Web服务器-代码示例(基于TC

          本文链接:https://www.haomeiwen.com/subject/bjzwgqtx.html