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

多任务版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
# 响应体(正文)

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

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

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


    利用Python搭建简单的多线程Web服务器-代码示例(基于TCP服务端)_第1张图片
    使用的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

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


利用Python搭建简单的多线程Web服务器-代码示例(基于TCP服务端)_第2张图片
image.png

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


利用Python搭建简单的多线程Web服务器-代码示例(基于TCP服务端)_第3张图片
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服务器-代码示例(基于TCP服务端))