Python Web服务器笔记(5)
一、网络编程
IP地址的介绍
IP地址就是表是网络中设备的一个地址,好比现实生活中的家庭地址
IP地址的表现形式
说明:
- IP地址分为两类:IPv4 和IPv6
- IPv4是目前使用的IP地址
- IPv6是未来使用的IP地址
- IPv4是由点分十进制组成
- IPv6是由冒号十六进制组成
IP地址的作用
IP地址的作用是标识网络中唯一的一台设备的,也就是说通过IP地址能够找到网络中的某台设备
查看IP地址
- Linux和macOS使用
ifconfig
这个命令 - Windows使用
ipconfig
这个命令
ifconfig 和 ipconfig 都是查看网卡信息的,网卡信息中包括这个设备对应的IP地址
域名就是IP地址的别名,通过域名能解析出来一个对应的IP地址
检查网络是否正常
- 检查网络是否正常使用 ping 命令
说明:
- ping www.baidu.com 检查是否能上公网
- ping 当前局域网的IP地址 检查是否在同一个局域网内
- ping 127.0.0.1 检查本地网卡是否正常
端口和端口号的介绍
不同电脑上的飞秋之间进行数据通信,他是如何保证吧数据给飞秋而不是给其他软件呢?
其实,每运行一个网络程序都会有个端口,想要给对应的程序发送数据,找到对应的端口即可
什么是端口
端口是传输数据的通道,是数据传输必经之路
每一个端口都会对应一个端口号,想要找到端口通过端口号即可
什么是端口号
操作系统为了统一管理这么多端口,就对端口进行了编号,这就是端口号,端口号就是一个数字,好比现实生活中的门牌号
端口和端口号的关系
端口号可以标识唯一的一个端口
端口号的分类
- 知名端口号
- 动态端口号
知名端口号:
知名端口号是指众所周知的端口号,范围从0到1023
- 这些端口号一般固定分配给一些服务,比如21端口分配给FTP(文件传输协议)服务,25端口分配给SMTP(简单邮件传输协议)服务,80端口分配给HTTP服务
动态端口号:
一般程序员开发应用程序使用的端口号称为动态端口号,范围从1024到65535
- 如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发的应用程序使用
- 当运行一个程序默认会有一个端口号,当这个程序退出时,所占用的这个端口号就会被释放
小结
- 端口的作用就是给运行的程序提供传输数据的通道
- 端口号的作用是用来区分和管理不同端口的,通过端口号能找到唯一一个端口
- 端口可以分为两类:知名端口号 和 动态端口号
- 知名端口号的范围是0到1023
- 动态端口号的范围是1023到65535
TCP的介绍
网络应用程序之间的通信流程
通过IP地址能够找到对应的设备,然后再通过端口号找到对应的端口,再通过端口把数据传输给应用程序,这里要注意,数据不能随便发送,在发送之前还需要一个对应的传输协议,保证程序之间按照指定的传输规则进行数据的通信,而这个传输协议就是TCP
TCP的概念
TCP的英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议
面向有连接型:在通信传输之前,先打开一个连接。连接被关闭时无法发送数据
TCP通信步骤
- 创建连接
- 传输数据
- 关闭连接
TCP的特点
- 面向连接
- 通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,以释放系统资源
- 可靠传输
- TCP采用发送应答机制
- 超时重传
- 错误校验
- 流量控制和阻塞管理
总结
TCP是一个稳定可靠的传输协议,常用于对数据进行准确无误的传输,比如:文件下载,浏览器上网。
socket的介绍
socket的概念
socket(简称 套接字)是进程之间通信一个工具,好比现实生活中的插座,所有的家用电器想要工作都是基于插座进行,进程之间想要进行网络通信需要基于这个socket
socket的作用
负责进程之间的网络数据传输,好比数据的搬运工
socket 使用场景
不夸张的说,只要跟网络相关的应用程序或者软件都使用到了socket
TCP网络应用程序开发流程
TCP网络应用程序开发流程的介绍
TCP网络应用程序开发分为:
- TCP客户端程序开发
- TCP服务端程序开发
说明:
客户端程序是指运行在用户设备上的程序 服务端程序是指运行在服务器设备上的程序,专门为客户端提供数据服务
TCP客户端程序开发流程
左边为客户端
步骤说明:
- 创建客户端套接字对象
- 和服务器套接字建立连接
- 发送数据
- 接受数据
- 关闭客户端套接字
TCP服务端程序开发流程
右边为服务端
步骤说明:
- 创建服务端套接字对象
- 绑定端口号
- 设置监听
- 等待客户端的连接请求
- 接收数据
- 发送数据
- 关闭套接字
小结
- TCP网络应用程序开发分为客户端程序开发和服务端程序开发
- 主动发起请求的是客户端程序
- 等待接受连接请求的是服务端程序
TCP客户端程序开发流流程
开发TCP客户端程序开发步骤回顾
- 创建客户端套接字对象
- 和服务端套接字建立连接
- 发送数据
- 接受数据
- 关闭客户端套接字
socket类的介绍
导入socket模块
import socket
创建客户端socket对象
socket.socket(AddressFamily, Type)
参数说明:
- AddressFamily 表示IP地址类型,分为IPv4和IPv6
- Type 表示传输协议类型
方法说明:
- connect((host, port))表示和服务端套接字建立连接,host是服务器IP地址,port是应用程序的端口号
- send(data) 表示发送数据,data是二进制数据
TCP客户端程序开发示例代码
import socket
if __name__ == '__main__':
# 1.创建tcp客户端套接字
# AF_INET:ipv4地址类型
# SOCK_STREAM:tcp传输协议类型
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.和服务端套接字建立连接
tcp_client_socket.connect(("192.168.75.131", 8080))
# 3.发送数据到服务端
send_content = "你好,我是客户端小白!"
send_data = send_content.encode('utf-8')
tcp_client_socket.send(send_data)
# 4.接受服务端的数据
# 1024表示每次接收的最大字节数
recv_data = tcp_client_socket.recv(1024)
# 对二进制数据进行解码
recv_content = recv_data.decode('utf-8')
print(recv_content)
# 5.关闭套接字
tcp_client_socket.close()
TCP服务端程序开发流程
开发TCP服务端程序开发步骤回顾
- 创建服务端套接字对象
- 帮定端口号
- 设置监听
- 等待接受客户端的连接请求
- 接收数据
- 发送数据
- 关闭套接字
socket类的介绍
导入socket模块
import socket
创建服务端socket对象
socket.socket(AddressFamily, Type)
参数说明:
- AddressFamily 表示IP地址类型,分为IPv4和IPv6
- Type表示传输协议
方法说明:
- bind((host, port)) 表示绑定端口号,host是IP地址,port是端口号,IP地址一般不指定,表示本机的任何一个IP地址都可以
- listen(backlog) 表示设置监听,backlog参数设置表示最大等待建立连接的个数
- accept() 表示等待接收客户端的连接请求
- send(data) 表示发送数据,data是二进制数据
- recv(buffersize) 表示接收数据,buffersize是每次接收数据的长度
TCP服务端程序开发的示例代码
import socket
if __name__ == '__main__':
# 1. 创建TCP服务端套接字
# socket.AF_INET: ipv4, socket.AF_INET6: ipv6
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定端口号
# 第一个参数表示IP地址,一般不用指定,表示本机的任何一个IP即可
# 第二个参数表示端口号
tcp_server_socket.connect(("", 9090))
# 3. 设置监听
# 128表示最大等待建立连接的个数
tcp_server_socket.listen(128)
# 4. 等待接受客户端的连接请求
# 每次当客户端和服务端建立连接成功都会返回一个新的套接字
# tcp_server_socket 只负责等待接收客户端的连接请求,收发消息不使用套接字
new_client, ip_port = tcp_server_socket.accept()
# 代码执行到此,说明连接客户端和服务端建立连接成功
print("客户端的IP地址和端口号为", ip_port)
# 5. 接受客户端的数据
# 收发消息都使用返回的新的套接字
recv_data = new_client.recv(1024)
# 对二进制数据进行解码变成字符串
recv_content = recv_data.decode("utf-8")
print("接受客户端的数据为:", recv_content)
# 6. 发送数据到客户端
send_content = "问题正在处理中..."
send_data = send_content.encode("utf-8")
new_client.send(send_data)
# 关闭服务于客户端套接字,表示和客户端终止通信
new_client.close()
# 7. 关闭服务端套接字,表示服务端以后不在等待接受客户端的连接请求
tcp_server_socket.close()
说明
当客户端和服务端建立连接后,服务端程序退出端口号不会立即释放,需要等待大概1-2分钟。
解决办法有:
- 更换端口号服务
- 设置端口号复用,也就是说让服务端程序退出后端口号立即释放
设置端口号复用的代码如下:
# SOL_SOCKET: 表示当前端口号
# SO_REUSEADDR : 表示复用端口号的选项
# True: 确定复用
tcp_server_socket.setsocket(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
TCP网络应用程序的注意点
- 当TCP客户端程序想要和TCP服务端程序进行通信的时候必须要先建立连接
- TCP客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的
- TCP服务端程序必须绑定端口号,否则客户端找不到这个TCP服务端程序
- listen后的套接字是被动套接字,只负责接受新的客户端的连接请求,不能收发消息
- 当TCP客户端程序和TCP服务端程序连接成功后,TCP服务端程序会产生一个新的套接字,收发客户端消息使用该套接字
- 关闭 accept 返回的套接字意味着和这个客户端已经通信完毕
- 关闭 listen 后的套接字意味着服务端的套接字关闭了,会导致新的客户端不能连接服务端,但是之前已经连接成功的客户端还能正常通信
- 当客户端的套接字调用 close 后,服务器端的 recv 会解阻塞,返回的数据长度为0,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv也会解阻塞,返回的数据长度也为0。
案例—多任务版TCP服务端程序开发
import socket
import threading
# 处理客户端请求的任务
def handle_client_requests(ip_port, new_client):
print("客户端的IP地址和端口号为:", ip_port)
# 5. 接受客户端数据
# 收发消息都使用返回的这个新的套接字
while True:
recv_data = new_client.recv(1024)
if recv_data:
print("接收的数据长度是:", len(recv_data))
# 对二进制数据进行解码变成字符串
recv_content = recv_data.decode("utf-8")
print("接受客户端的数据为:", recv_content)
send_content = "问题正在处理中..."
# 对字符串进行编码
send_data = send_content.encode("utf-8")
# 6. 发送数据到客户端
new_client.send(send_data)
else:
# 客户端关闭连接
print("客户端下线了:", ip_port)
break
# 关闭服务与客户端套接字,表示和客户端终止通信
new_client.close()
if __name__ == '__main__':
# 1. 创建TCP服务端套接字
# socket.AF_INET: ipv4, socket.AF_INET6: ipv6
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定端口号
# 第一个参数表示IP地址,一般不用指定,表示本机的任何一个IP即可
# 第二个参数表示端口号
tcp_server_socket.bind(("", 9090))
# 3. 设置监听
# 128表示最大等待建立连接的个数
tcp_server_socket.listen(128)
# 4. 等待接受客户端的连接请求
# 每次当客户端和服务端建立连接成功都会返回一个新的套接字
# tcp_server_socket 只负责等待接收客户端的连接请求,收发消息不使用套接字
# 循环等待接受客户端的连接请求
while True:
new_client, ip_port = tcp_server_socket.accept()
# 代码执行到此,说明连接客户端和服务端建立连接成功
# 当客户端和服务端建立连接成功,创建子线程,让子线程专门负责接收客户端的消息
sub_thread = threading.Thread(target=handle_client_requests, args=(ip_port, new_client))
# 设置守护主线程,主线程退出子线程直接销毁
sub_thread.setDaemon(True)
# 启动子线程,执行对应任务
sub_thread.start()
# 7. 关闭服务端套接字,表示服务端以后不在等待接受客户端的连接请求
# tcp_server_socket.close() # 因为服务端的程序需要一直运行,所以关闭套接字的代码可以省略不写
socket之send和recv原理剖析
认识TCP socket的发送和接受缓冲区
当创建一个TCP socket对象的时候会有一个发送缓冲区
和一个接收缓冲区
,这个发送和接受缓冲区指的就是内存中的一片空间。
send原理剖析
send要想发送数据,必须通过网卡发送数据,应用程序是无法直接通过网卡发送数据,他需要调节操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区(内存中的一片空间),再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡
recv原理剖析
应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接受的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓冲区获取客户端发送的数据。
send和recv原理剖析图
说明:
- 发送数据是发送到发送缓冲区
- 接收数据是从接收缓冲区 获取
小结
不管是recv和send都不是直接接收到对方的数据和发送数据到对方,发送数据会写入到发送缓冲区,接收数据是从接收缓冲区来读取,发送数据和接收数据最终是由操作系统控制网卡来完成
二、http协议
HTTP协议
HTTP协议全称是(HyperText Transfer Protocol),就是超文本传输协议
超文本就是超级文本的缩写,是指超越文本限制或者超链接,比如:图片、音乐、视频、超链接等都输入超文本
HTTP协议之前目的是传输网页数据的,现在允许传输任意类型的数据
传输HTTP协议格式的数据是基于TCP传输协议的,发送数据之前需要先建立连接
HTTP协议的作用
它规定了浏览器和Web服务器通信数据的格式,也就是说浏览器和web服务器通信需要使用http协议
浏览器访问web服务器的通信过程
通信效果图:
小结
- HTTP协议是一个超文本传输协议
- HTTP协议是一个基于TCP传输协议传输数据的
- HTTP协议规定了浏览器和Web服务器通信数据的格式
URL
URL的概念
URL的英文全拼是(Uniform Resource Locator),表达的意思是统一资源定位符,通俗理解就是网络资源地址,也就是我们常说的网址
URL的组成
URL的样子:
https://news.163.com/18/1122/10/E178J2O4000189FH.html
URL的组成部分:
- 协议部分: https://、http://、ftp://
- 域名部分:news.163.com
- 资源路径部分:/18/1122/10/E178J2O4000189FH.html
域名:
域名就是IP地址的别名,它是用点进行分割使用英文字母和数字组成的名字,使用域名目的就是方便的记住某台主机IP地址
URL的扩展:
https://news.163.com/hello.html?page=1&count=10
- 查询参数部分:?page=1&count=10
参数说明:
- ?后面的page表示第一个参数,后面的参数都使用&进行连接
小结
- URL就是网络资源网址,简称网址,通过URL能够找到网络中对应的资源数据
- URL组成部分
- 协议部分
- 域名部分
- 资源路径部分
- 查询参数部分[可选]
查看HTTP协议的通信过程
谷歌浏览器开发者工具的使用
Windows和Linux平台按F12调出开发中工具,macOS选择 视图-> 开发者 -> 开发者工具或者直接使用 alt + command + i 这个快捷键,多平台通用操作在网页右击选择检查
开发者工具的标签选项说明:
- 元素(Elements):用于查看或修改HTML标签
- 控制台(Console):执行js代码
- 源代码(Sources):查看静态资源文件,断点调试JS代码
- 网络(Netwo):查看http协议的通信过程
HTTP请求报文
浏览器发送给web服务器程序的HTTP协议数据
HTTP请求报文介绍
- GET 方式的请求
- POST 方式的请求
说明:
- GET: 获取web服务器数据
- POST: 获取web服务器提交数据
HTTP GET 请求报文分析
http get 请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)
每行信息之间都需要一个\r\n,是要http协议规定
HTTP POST 请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)
请求体
请求体就是浏览器发送给服务器的数据
小结
- 一个HTTP请求报文可以有请求头、请求行、空行和请求体 4个部分组成
- 请求体是有三部分组成:
- 请求方式
- 请求资源路径
- HTTP协议版本
- GET方式请求报文没有请求体,只有请求行、请求头、空行组成。
- POST方式的请求报文可以有请求行、请求头、空行、请求体四部分组成,注意:POST方式可以允许请求没有请求体,但是这种格式很少见
HTTP响应报文
Web服务器程序发送给浏览器程序的数据
HTTP响应报文格式
响应行\r\n
响应头\r\n
空行\r\n
响应体\r\n
提示: 每行数据之后都要有一个\r\n
HTTP状态码介绍
HTTP状态码是用于表示web服务器响应状态的3位数字代码
状态码 | 说明 |
---|---|
200 | 请求成功 |
307 | 重定向 |
400 | 错误的请求,请求地址或参数有误 |
404 | 请求资源不在服务器中 |
500 | 服务器内部晕啊代码出现错误 |
小结
- 一个http报文是由 响应行、响应头、空行、响应体 四部分组成
- 响应行是由三部分组成: HTTP协议版本 状态码 状态描述
三、web服务器
搭建Python自带静态Web服务器
静态web服务器是什么
可以为发出请求的浏览器提供静态文档的程序
页面的数据不会发生变化的
如何搭建Python自带的静态web服务器
搭建Python自带的静态web服务器使用 python3 -m http.server 端口号
-m 表示运行包里面的模块,执行这个命令的时候,需要进入你自己指定的静态文件的目录,然后通过浏览器就能访问对应的html文件,这样一个静态的web服务器就搭建好了
访问搭建好的静态web服务器的通信过程
python3 -m http.server 端口号
小结
- 静态web服务器是为发出请求的浏览器提供静态文档程序
- 搭建python自带的web服务器使用 python3 -m http.server 端口号 这个命令即可,端口号不指定默认8000
静态Web服务器-返回固定页面的数据
开发自己的静态web服务器
- 编写一个TCP服务端程序
- 获取浏览器发送的http请求报文数据
- 读取固定一页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器
- HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字
import socket
if __name__ == '__main__':
tcp_server_socker = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socker.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
tcp_server_socker.bind(("", 8000))
tcp_server_socker.listen(128)
while True:
new_socket, ip_port = tcp_server_socker.accept()
recv_data = new_socket.recv(4096)
print(recv_data)
with open('static/index.html', 'r') as file:
file_data = file.read()
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("gbk"))
new_socket.close()
静态Web服务器-返回指定页面的数据
import socket
def main():
tcp_server_socker = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socker.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
tcp_server_socker.bind(("", 8000))
tcp_server_socker.listen(128)
while True:
new_socket, ip_port = tcp_server_socker.accept()
recv_data = new_socket.recv(4096)
if len(recv_data) == 0:
new_socket.close()
return
recv_content = recv_data.decode('utf-8')
print(recv_content)
request_list = recv_content.split(" ", maxsplit=2)
request_path = request_list[1]
print(request_path)
if request_path == "/":
request_path = "/index.html"
# 1. os.path.exists("static"+request_path)
# 2. try-except
# 使用rb模式兼容图片
try:
with open('static' + request_path, 'rb') as file:
file_data = file.read()
except Exception as e:
response_line = "HTTP/1.1 200 OK\r\n"
response_header = "Server: PWS/1.0\r\n"
with open("static/error.html", 'rb') as file:
file_data = file.read()
response_body = file_data
response = (response_line + response_header + "\r\n").encode('utf-8') + response_body
new_socket.send(response)
else:
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()
if __name__ == '__main__':
main()
静态Web服务器-多任务版
静态web服务器的问题
不能支持多用户同时访问,只能一个一个的处理客户端的请求
可以使用多线程,比进程更节省内存资源
多任务版web服务器程序的实现步骤
- 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞
- 把创建的子线程设置成为守护主线程,防止主线程无法退出
import socket
import threading
def handle_client_request(new_socket):
recv_data = new_socket.recv(4096)
if len(recv_data) == 0:
new_socket.close()
return
recv_content = recv_data.decode('utf-8')
print(recv_content)
request_list = recv_content.split(" ", maxsplit=2)
request_path = request_list[1]
print(request_path)
if request_path == "/":
request_path = "/index.html"
# 1. os.path.exists("static"+request_path)
# 2. try-except
# 使用rb模式兼容图片
try:
with open('static' + request_path, 'rb') as file:
file_data = file.read()
except Exception as e:
response_line = "HTTP/1.1 200 OK\r\n"
response_header = "Server: PWS/1.0\r\n"
with open("static/error.html", 'rb') as file:
file_data = file.read()
response_body = file_data
response = (response_line + response_header + "\r\n").encode('utf-8') + response_body
new_socket.send(response)
else:
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()
def main():
tcp_server_socker = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socker.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
tcp_server_socker.bind(("", 8000))
tcp_server_socker.listen(128)
while True:
new_socket, ip_port = tcp_server_socker.accept()
sub_thread = threading.Thread(target=handle_client_request, args=(new_socket,))
sub_thread.setDaemon(True)
sub_thread.start()
if __name__ == '__main__':
main()
静态Web服务器-面向对象开发
以面向对象的方式开发静态web服务器
- 把提供服务的web服务器抽象成一个类(HTTPWebServer)
- 把提供Web服务器的初始化方法,在初始化方法里面创建socket对象
- 提供一个开启web服务器的方法,让web服务器处理客户端请求操作
import socket
import threading
class HttpWebServer(object):
def __init__(self):
tcp_server_socker = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socker.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
tcp_server_socker.bind(("", 8000))
tcp_server_socker.listen(128)
self.tcp_server_socker = tcp_server_socker
@staticmethod
def handle_client_request(new_socket):
recv_data = new_socket.recv(4096)
if len(recv_data) == 0:
new_socket.close()
return
recv_content = recv_data.decode('utf-8')
print(recv_content)
request_list = recv_content.split(" ", maxsplit=2)
request_path = request_list[1]
print(request_path)
if request_path == "/":
request_path = "/index.html"
# 1. os.path.exists("static"+request_path)
# 2. try-except
# 使用rb模式兼容图片
try:
with open('static' + request_path, 'rb') as file:
file_data = file.read()
except Exception as e:
response_line = "HTTP/1.1 200 OK\r\n"
response_header = "Server: PWS/1.0\r\n"
with open("static/error.html", 'rb') as file:
file_data = file.read()
response_body = file_data
response = (response_line + response_header + "\r\n").encode('utf-8') + response_body
new_socket.send(response)
else:
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()
def start(self):
while True:
new_socket, ip_port = self.tcp_server_socker.accept()
sub_thread = threading.Thread(target=self.handle_client_request, args=(new_socket,))
sub_thread.setDaemon(True)
sub_thread.start()
def main():
web_server = HttpWebServer()
web_server.start()
if __name__ == '__main__':
main()
小结
把提供的Web服务器抽象成一个类(HTTPWebServer)
class HttpWebServer(object):
提供Web服务器的初始化方法,在初始化方法里面创建socket对象
def __init__(self):
# 初始化服务端套接字,设置监听
提供一个开启Web服务器的方法,让Web服务器处理客户端请求操作
def start(self):
while True:
new_socket, ip_port = self.tcp_server_socker.accept()
sub_thread = threading.Thread(target=self.handle_client_request, args=(new_socket,))
sub_thread.setDaemon(True)
sub_thread.start()
静态Web服务器-命令行启动动态绑定端口号
import socket
import threading
import sys
class HttpWebServer(object):
def __init__(self, port):
tcp_server_socker = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socker.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
tcp_server_socker.bind(("", port))
tcp_server_socker.listen(128)
self.tcp_server_socker = tcp_server_socker
@staticmethod
def handle_client_request(new_socket):
recv_data = new_socket.recv(4096)
if len(recv_data) == 0:
new_socket.close()
return
recv_content = recv_data.decode('utf-8')
print(recv_content)
request_list = recv_content.split(" ", maxsplit=2)
request_path = request_list[1]
print(request_path)
if request_path == "/":
request_path = "/index.html"
# 1. os.path.exists("static"+request_path)
# 2. try-except
# 使用rb模式兼容图片
try:
with open('static' + request_path, 'rb') as file:
file_data = file.read()
except Exception as e:
response_line = "HTTP/1.1 200 OK\r\n"
response_header = "Server: PWS/1.0\r\n"
with open("static/error.html", 'rb') as file:
file_data = file.read()
response_body = file_data
response = (response_line + response_header + "\r\n").encode('utf-8') + response_body
new_socket.send(response)
else:
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()
def start(self):
while True:
new_socket, ip_port = self.tcp_server_socker.accept()
sub_thread = threading.Thread(target=self.handle_client_request, args=(new_socket,))
sub_thread.setDaemon(True)
sub_thread.start()
def main():
params = sys.argv
if len(params) != 2:
print("执行的命令格式如下:python3 xxx.py 9000")
return
if not params[1].isdigit():
print("执行的命令格式如下:python3 xxx.py 9000")
return
port = int(params[1])
web_server = HttpWebServer(port)
web_server.start()
if __name__ == '__main__':
main()