Python网络编程(3)web服务器
学习目标
- 了解静态Web服务器
- 静态Web服务器-返回固定页面数据
- 静态Web服务器-返回指定页面数据
- 静态Web服务器-多任务版
- 静态Web服务器-面向对象开发
- 静态Web服务器-命令行启动动态绑定端口号
了解静态Web服务器
静态服务器就是可以为发出请求的浏览器提供静态文档(数据不会变化)的服务器应用程序。
浏览百度新闻的时候,每天的新闻数据会发生变化,那访问这个页面就是动态的。而我们开发的是静态的,即页面的数据不会发生变化。
使用Python自带的静态Web服务器
搭建Python自带的静态Web服务器使用Python3 -m http.server 端口号
命令(http包里的server模块,端口号默认8000)。-m选项表示运行包里面的模块,执行这个命令的时候,需要进入自己指定的静态文件的目录,然后通过浏览器就能访问对应的html文件了,这样一个静态的web服务器就搭建好了。
运行命令:

查看通信过程:

静态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.argv
(sys
模块中的argv
属性,数据类型为列表,第一个元素为当前文件路径,第二个元素为命令行参数)获取。实现步骤:
- 获取执行python程序的终端命令行参数。
- 判断参数的类型,设置端口号必须是整型。
- 给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()
|
总结: