目录

Python网络编程(3)web服务器

Python网络编程(3)web服务器

学习目标

  • 了解静态Web服务器
  • 静态Web服务器-返回固定页面数据
  • 静态Web服务器-返回指定页面数据
  • 静态Web服务器-多任务版
  • 静态Web服务器-面向对象开发
  • 静态Web服务器-命令行启动动态绑定端口号

了解静态Web服务器

静态服务器就是可以为发出请求的浏览器提供静态文档(数据不会变化)的服务器应用程序

浏览百度新闻的时候,每天的新闻数据会发生变化,那访问这个页面就是动态的。而我们开发的是静态的,即页面的数据不会发生变化。

使用Python自带的静态Web服务器

搭建Python自带的静态Web服务器使用Python3 -m http.server 端口号命令(http包里的server模块,端口号默认8000)。-m选项表示运行包里面的模块,执行这个命令的时候,需要进入自己指定的静态文件的目录,然后通过浏览器就能访问对应的html文件了,这样一个静态的web服务器就搭建好了。

运行命令:

/../../images/搭建web服务器-0596235.png

查看通信过程:

/../../images/静态web服务器的通信过程-0596251.png


静态Web服务器-返回固定页面数据

自己开发一个Web服务器应用程序,不使用自带的静态Web服务器,运行起来可以能够组装固定页面数据。实现步骤如下:

  • 编写一个tcp服务端程序
  • 获取浏览器发送的http请求报文数据
  • 读取固定页面数据,把页面数据组装成http响应报文数据发送给浏览器
  • http响应报文数据发送完成以后,关闭服务于客户端的套接字。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import socket
# 判断主模块

if __name__ == '__main__':
    tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
    # 绑定端口号
    tcp_server_socket.bind(('',8000))
    tcp_server_socket.listen(128)   # 最大等待连接数128
    while True:
        new_socket,ip_port = tcp_server_socket.accept()  # 开始接受连接
        recv_data = new_socket.recv(4096).decode('UTF-8')
        print(recv_data)
        with open('index.html') as f:
            file_data = f.read()
        # with open 关闭文件的操作会自动执行不用编写close语句,出现异常也会执行close语句。
        # 把数据封装成http响应报文的数据格式(服务器->浏览器)
        # 响应行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 响应头
        response_header = "Server: PWS/1.0\r\n"
        # 空行
        # 响应体
        response_body = file_data
        response = response_line + response_header + "\r\n"+ response_body
        new_socket.send(response.encode('UTF-8'))
        new_socket.close()

⚠️注意:无论url资源路径如何变化,都只会返回"index.html"里的数据。


静态Web服务器-返回指定页面数据

根据URL指定的路径返回指定的数据,我们要要根据请求的路径打开响应的文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import socket
# 判断主模块

def recv(new_socket):
    recv_data = new_socket.recv(4096).decode('UTF-8')
    # 如果数据为空,关闭连接终止程序
    if len(recv_data) == 0:
        new_socket.close()
        return
    # 得到的数据进行分割
    path = recv_data.split(' ', maxsplit=2)[1]
    print(recv_data)
    # rb模式兼容打开图片文件
    if path == '/':
        path = '/index.html'
    with open('web'+ path, 'rb') as f:
        file_data = f.read()
    # with open 关闭文件的操作会自动执行不用编写close语句,出现异常也会执行close语句。
    # 把数据封装成http响应报文的数据格式(服务器->浏览器)
    # 响应行
    response_line = "HTTP/1.1 200 OK\r\n"
    # 响应头
    response_header = "Server: PWS/1.0\r\n"
    # 空行
    # 响应体
    response_body = file_data
    response = (response_line + response_header + "\r\n").encode('UTF-8') + response_body
    new_socket.send(response)
    new_socket.close()


if __name__ == '__main__':
    tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
    # 绑定端口号
    tcp_server_socket.bind(('',8000))
    tcp_server_socket.listen(128)   # 最大等待连接数128
    while True:
        new_socket,ip_port = tcp_server_socket.accept()  # 开始接受连接
        recv(new_socket)

静态Web服务器-返回404页面数据

上面的程序改善了无论用户请求什么资源路径,页面均只显示index.html页面的弊端,但不足之处在于请求一个不存在的路径资源服务端程序会报错断开,那么我们为了解决这个问题可以采用引入404页面的方式改善,两种方法添加:

  • 第一种是使用os.path.exists()判断请求资源是否存在,不存在则直接返回404页面:
1
2
    if os.path.exists('web'+ path) == False:
        path = '/404.html'
  • 第二种可以将打开用户请求的资源路径写入异常,如果异常抛出则返回404页面:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    try:
        with open('web'+ path, 'rb') as f:
            file_data = f.read()
    except Exception as e:
        response_line = "HTTP/1.1 404 Not Found\r\n"
        # 响应头
        response_header = "Server: PWS/1.0\r\n"
        with open('web/404.html', 'rb') as f:
            file_data = f.read()
        response_body = file_data
        response = (response_line + response_header + "\r\n").encode('UTF-8') + response_body
        new_socket.send(response)
    else:
        # with open 关闭文件的操作会自动执行不用编写close语句,出现异常也会执行close语句。
        # 把数据封装成http响应报文的数据格式(服务器->浏览器)
        # 响应行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 响应头
        response_header = "Server: PWS/1.0\r\n"
        # 空行
        # 响应体
        response_body = file_data
        response = (response_line + response_header + "\r\n").encode('UTF-8') + response_body
        new_socket.send(response)
    finally:
        new_socket.close()

静态Web服务器-多任务版

现在服务器是单任务服务器,不能支持多用户同时访问,只能一个一个处理客户端的请求,使用多线程去开发,实现步骤就是:当客户端和服务端建立连接成功创建子线程,子线程来专门处理客户端的请求,防止主线程阻塞,创建子线程要设置为守护主线程。代码实现如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import socket
import threading
import os

def client_tcp_handler(new_socket,ip_port):
    # 接受请求报文
    recv_data = new_socket.recv(4096).decode('UTF-8')
    print(recv_data)
    # 判断请求是否为空
    if len(recv_data) == 0:
        new_socket.close()
        return
    path = recv_data.split(' ',maxsplit=2)[1]
    # 默认访问根目录
    if path == '/':
        path = '/index.html'
    # 访问路径不存在
    if os.path.exists('web'+path) == False:
        with open('web/404.html','rb') as f:
            response_body = f.read()
            response_line = 'HTTP/1.1 404 Not Found\r\n'
            response_handers = "Server: PythonServer\r\n"
        new_socket.send((response_line+response_handers+'\r\n').encode('UTF-8')+response_body)
    else:
        with open('web'+path,'rb') as f:
            response_body = f.read()
            response_line = 'HTTP/1.1 200 OK\r\n'
            response_handers = "Server: PythonServer\r\n"
        new_socket.send((response_line + response_handers + '\r\n').encode('UTF-8') + response_body)
    new_socket.close()

if __name__ == '__main__':
    server_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_tcp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)  # 设置端口复用
    server_tcp_socket.bind(('',8080))
    server_tcp_socket.listen(128)
    while True:
        newsocket,ip_port = server_tcp_socket.accept()
        request_thread = threading.Thread(target=client_tcp_handler,args=(newsocket,ip_port))
        request_thread.setDaemon(True)
        request_thread.start()
    # server_tcp_socket.close()

静态Web服务器-面向对象开发

面向对象版的静态Web服务器开发步骤:1. 把提供的Web服务器抽象为一个类(HTTPWebServer),2.提供Web服务器的初始化方法,在初始化方法里面创建socket对象,3.提供一个开启运行Web服务器的方法,让Web服务器处理客户端的请求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import socket
import threading
import os


class HTTPWebServer(object):
    # 初始化实例方法用来创建套接字
    def __init__(self):
        tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # 设置端口号复用,程序退出端口立即释放
        tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
        tcp_server_socket.bind(('',80))
        tcp_server_socket.listen(128)
        # 成为WebServer的属性
        self.tcp_server_socket = tcp_server_socket


    # WebServer处理客户端请求方法不需要当前对象和当前类,定义为静态方法
    @staticmethod
    def handle_client_request(new_socket):
        # 接受请求报文
        recv_data = new_socket.recv(4096).decode('UTF-8')
        print(recv_data)
        # 判断请求是否为空
        if len(recv_data) == 0:
            new_socket.close()
            return
        path = recv_data.split(' ', maxsplit=2)[1]
        # 默认访问根目录
        if path == '/':
            path = '/index.html'
        # 访问路径不存在
        if os.path.exists('web' + path) == False:
            with open('web/404.html', 'rb') as f:
                response_body = f.read()
                response_line = 'HTTP/1.1 404 Not Found\r\n'
                response_handers = "Server: PythonServer\r\n"
            new_socket.send((response_line + response_handers + '\r\n').encode('UTF-8') + response_body)
        else:
            with open('web' + path, 'rb') as f:
                response_body = f.read()
                response_line = 'HTTP/1.1 200 OK\r\n'
                response_handers = "Server: PythonServer\r\n"
            new_socket.send((response_line + response_handers + '\r\n').encode('UTF-8') + response_body)
        new_socket.close()


    # 服务器运行的方法
    def run(self):
        while True:
            new_socket, ip_port = self.tcp_server_socket.accept()
            sub_thread = threading.Thread(target=self.handle_client_request,args=(new_socket,))
            sub_thread.setDaemon(True)
            sub_thread.start()

def main():
    python_server = HTTPWebServer()
    python_server.run()

if __name__ == '__main__':
    main()

开启运行的方法需要调用一个静态方法,负责处理请求并发送响应报文。


静态Web服务器-命令行启动动态绑定端口号

为了写出直接获取命令行参数动态绑定端口号的web服务器程序,我们得需要把端口号取到,获取终端命令行中输入的参数我们需要引入sys.argvsys模块中的argv属性,数据类型为列表,第一个元素为当前文件路径,第二个元素为命令行参数)获取。实现步骤:

  1. 获取执行python程序的终端命令行参数。
  2. 判断参数的类型,设置端口号必须是整型。
  3. 给Web服务器类初始化方法添加一个端口号参数,用于绑定端口号。

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import socket
import sys
import threading
import os


class HTTPWebServer(object):
    # 初始化实例方法用来创建套接字
    def __init__(self,port):
        tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # 设置端口号复用,程序退出端口立即释放
        tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
        tcp_server_socket.bind(('',port))
        tcp_server_socket.listen(128)
        # 成为WebServer的属性
        self.tcp_server_socket = tcp_server_socket


    # WebServer处理客户端请求方法不需要当前对象和当前类,定义为静态方法
    @staticmethod
    def handle_client_request(new_socket):
        # 接受请求报文
        recv_data = new_socket.recv(4096).decode('UTF-8')
        print(recv_data)
        # 判断请求是否为空
        if len(recv_data) == 0:
            new_socket.close()
            return
        path = recv_data.split(' ', maxsplit=2)[1]
        # 默认访问根目录
        if path == '/':
            path = '/index.html'
        # 访问路径不存在
        if os.path.exists('web' + path) == False:
            with open('web/404.html', 'rb') as f:
                response_body = f.read()
                response_line = 'HTTP/1.1 404 Not Found\r\n'
                response_handers = "Server: PythonServer\r\n"
            new_socket.send((response_line + response_handers + '\r\n').encode('UTF-8') + response_body)
        else:
            with open('web' + path, 'rb') as f:
                response_body = f.read()
                response_line = 'HTTP/1.1 200 OK\r\n'
                response_handers = "Server: PythonServer\r\n"
            new_socket.send((response_line + response_handers + '\r\n').encode('UTF-8') + response_body)
        new_socket.close()


    # 服务器运行的方法
    def run(self):
        while True:
            new_socket, ip_port = self.tcp_server_socket.accept()
            sub_thread = threading.Thread(target=self.handle_client_request,args=(new_socket,))
            sub_thread.setDaemon(True)
            sub_thread.start()

def main():
    # 获取终端命令行参数,命令行满足两个调节,只要两个参数,且端口号参数为数字
    if len(sys.argv) != 2:
        print("执行命令格式如下: python3 xxx.py 8000")
        return
    if not sys.argv[1].isdigit():
        print("执行命令格式如下: python3 xxx.py 8000")
        return
    port = int(sys.argv[1])
    python_server = HTTPWebServer(port)
    python_server.run()

if __name__ == '__main__':
    main()

总结: