1. HTTP协议
1.1 什么是url
URL又称统一资源定位符,是资源在网络上存放的地址。由协议+域名+路径组成。例如:http://www.shanshimantang.com/index.html。其中http是协议,www.shanshimantang.com是域名,index.html就是路径。
1.2 浏览器访问Web服务器的过程
- 首先通过DNS将域名解析成对应的IP地址。
- 然后与IP地址建立连接
- 发送HTTP请求数据
- 服务器程序根据请求去获取资源数据
- 然后将获取到资源返给Web服务器。
1.3 请求方式和状态码
http请求方式分为get请求和post请求
每个请求都会返回状态码,常见的状态码有以下几种:
状态码 | 描述 |
---|---|
200 | 请求成功 |
400 | 地址参数有误 |
404 | 请求资源不存在 |
500 | 服务器内部源代码错误 |
2. 静态服务器
2.1 Python自带的静态服务器
在想要启动服务的目录下,打开cmd里面执行命令:python3 -m http.server 端口号
可以启动python自带的静态服务器。
2.2 tcp实现服务器返回
使用socket实现浏览器访问的返回,分为以下几步:
- 创建socket服务器对象
- 绑定ip和端口号
- 设置端口复用
- 设置监听
- 等待客户端连接
- 接收客户端发送的消息
- 给客户端发送消息
- 关闭连接
import socket
if __name__ == '__main__':
# 创建socket对象
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip和端口
tcp_server.bind(("", 6699))
# 设置端口复用
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置监听
tcp_server.listen(128)
# 等待客户端连接
print("正在等待客户端连接")
while True:
conn, addr = tcp_server.accept()
print("已经连接到:", addr)
# 接收消息
recv_data = conn.recv(124).decode()
if not recv_data:
print("没有收到消息")
break
print(f"收到的消息内容:{recv_data}")
# 发送消息
resp_data = conn.send("".encode())
# 关闭连接
conn.close()
tcp_server.close()
按照以上代码的设计,客户端访问后不会显示任何东西,原因是:没有遵从http协议去返回消息。因此还需要构建符合协议规则的响应消息去返回。
# 发送消息
with open("./static/index.html", "rb") as f:
file_data = f.read()
# 构造响应码
resp_line = "HTTP/1.1 200 OK\r\n"
# 构造响应头
resp_header = "Content-Type: text/html\r\n\r\n"
# 构造响应体
resp_body = file_data
# 组建响应消息
resp_data = (resp_line + resp_header).encode() + resp_body
conn.send(resp_data)
至此,就实现了静态服务器的基本功能了,但是目前还有一个缺陷就是,只能返回固定数据,无论我是去访问/index.html还是访问/123.action,返回的都是固定的数据。所以下面还需要对请求内容进行区分,访问不存在的资源时,返回响应的404界面。
实现思路:通过执行的打印内容来看,收到的消息,里面第二个就是请求的内容,因此可以将请求的内容串分割,然后取指定的数据来进行判断区分。
正在等待客户端连接
已经连接到: ('127.0.0.1', 59863)
收到的消息内容:GET /index.html HTTP/1.1
Host: 127.0.0.1:6699
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Not_A Brand";v
已经连接到: ('127.0.0.1', 59864)
收到的消息内容:GET /index.html HTTP/1.1
Host: 127.0.0.1:6699
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Not_A Brand";v
已经连接到: ('127.0.0.1', 59865)
没有收到消息
进程已结束,退出代码为 0
代码实现:
import socket
if __name__ == '__main__':
# 创建socket对象
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip和端口
tcp_server.bind(("", 6699))
# 设置端口复用
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置监听
tcp_server.listen(128)
# 等待客户端连接
print("正在等待客户端连接")
while True:
conn, addr = tcp_server.accept()
print("已经连接到:", addr)
# 接收消息
recv_data = conn.recv(124).decode()
if not recv_data:
print("没有收到消息")
break
print(f"收到的消息内容:{recv_data}")
# 增加判断请求内容
recv_data_list = recv_data.split(" ")
print(f"请求内容是{recv_data_list[1]}")
if recv_data_list[1] == "" or recv_data_list[1] == "/index.html":
with open("./static/index.html", "rb") as f:
file_data = f.read()
else:
with open("./static/404.html", "rb") as f:
file_data = f.read()
# 构造响应码
resp_line = "HTTP/1.1 200 OK\r\n"
# 构造响应头
resp_header = "Content-Type: text/html\r\n\r\n"
# 构造响应体
resp_body = file_data
resp_data = (resp_line + resp_header).encode() + resp_body
# 发送消息
conn.send(resp_data)
# 关闭连接
conn.close()
tcp_server.close()
2.3 多任务处理
多任务可以实现多个客户端的请求。
import socket
import threading
def tcp_server_handle(connection):
# 接收消息
recv_data = connection.recv(124).decode()
if not recv_data:
print("没有收到消息")
return
print(f"收到的消息内容:{recv_data}")
# 增加判断请求内容
recv_data_list = recv_data.split(" ")
print(f"请求内容是{recv_data_list[1]}")
"""
根据请求的内容,去对应的目录下面读取指定的文件,如果文件不存在就抛出404
"""
req_file_path = recv_data_list[1]
if req_file_path == "/":
req_file_path = "/index.html"
try:
with open("./static" + req_file_path, "rb") as f:
file_data = f.read()
except FileNotFoundError:
print("请求内容不存在")
with open("./static/404.html", "rb") as f:
file_data = f.read()
# 构造响应码
resp_line = "HTTP/1.1 200 OK\r\n"
# 构造响应头
resp_header = "Content-Type: text/html\r\n\r\n"
# 构造响应体
resp_body = file_data
resp_data = (resp_line + resp_header).encode() + resp_body
# 发送消息
connection.send(resp_data)
# 关闭连接
connection.close()
if __name__ == '__main__':
# 创建socket对象
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip和端口
tcp_server.bind(("", 6699))
# 设置端口复用
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置监听
tcp_server.listen(128)
# 等待客户端连接
print("正在等待客户端连接")
while True:
conn, addr = tcp_server.accept()
print("已经连接到:", addr)
# 创建线程,实现多任务处理
child_thread = threading.Thread(target=tcp_server_handle, args=(conn,))
child_thread.start()
# tcp_server.close()
2.4 面向对象版
使用面向对象可以提高复用性,上面多任务版本中,端口号和ip是固定的,在实际应用中的使用性不强,因此需要对外提供自定义端口和ip。
import socket
import sys
import threading
class MyServer(object):
def __init__(self, ip, port):
self.IP = ip
self.Port = port
# 创建socket对象
self.tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip和端口
self.tcp_server.bind((self.IP, self.Port))
# 设置端口复用
self.tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置监听
self.tcp_server.listen(128)
def __server_handle(self, connection):
# 接收消息
recv_data = connection.recv(124).decode()
if not recv_data:
print("没有收到消息")
return
print(f"收到的消息内容:{recv_data}")
# 增加判断请求内容
recv_data_list = recv_data.split(" ")
req_file_path = recv_data_list[1]
print(f"请求内容是{recv_data_list[1]}")
"""
根据请求的内容,去对应的目录下面读取指定的文件,如果文件不存在就抛出404
"""
if req_file_path == "/":
req_file_path = "/index.html"
try:
with open("./static" + req_file_path, "rb") as f:
file_data = f.read()
except FileNotFoundError:
print("请求内容不存在")
with open("./static/404.html", "rb") as f:
file_data = f.read()
# 构造响应码
resp_line = "HTTP/1.1 200 OK\r\n"
# 构造响应头
resp_header = "Content-Type: text/html\r\n\r\n"
# 构造响应体
resp_body = file_data
resp_data = (resp_line + resp_header).encode() + resp_body
# 发送消息
connection.send(resp_data)
# 关闭连接
connection.close()
def my_tcp_server(self):
# 等待客户端连接
print("正在等待客户端连接")
while True:
conn, addr = self.tcp_server.accept()
print("已经连接到:", addr)
# 创建线程,实现多任务处理
child_thread = threading.Thread(target=self.__server_handle, args=(conn,))
child_thread.start()
if __name__ == '__main__':
# 一般都在Linux上使用命令行输入参数
params_list = sys.argv # 获取执行python程序的终端命令行参数
# isdigit() 方法判断字符串是否是整型字符串
if len(params_list) == 2 and not params_list[0].isdigit() and params_list[1].isdigit():
static_server = MyServer(params_list[0], params_list[1])
static_server.my_tcp_server()
elif len(params_list) == 1:
static_server = MyServer("127.0.0.1", 6699)
static_server.my_tcp_server()
else:
print("启动命令为:python object_static_server.py <port>\t例如:python object_static_server.py 8080")
3. 闭包
3.1 函数参数
函数名存放的时函数所在空间的地址。
函数名也可以像普通变量一样赋值
执行函数名()就是执行函数名所存放空间地址中的代码。
3.2 闭包
3.2.1 闭包的简介
闭包可以保存函数内的变量,不会随着函数调用完而销毁。闭包的构成:
- 在函数嵌套(函数里面再定义函数)的前提下
- 内部函数使用了外部函数的变量(还包括外部函数的参数)
- 外部函数返回了内部函数
其中使用外部函数变量的内部函数称为闭包。调用闭包就相当于调用内部函数。
例如以下代码中,out_func函数中嵌套了inner_func函数,且inner_func函数中还用了out_func函数的参数num1,且out_func函数返回了inner_func,所以inner_func函数就是个闭包。
def out_func(num1):
def inner_func(num2):
print('{num1 + num2}')
return inner_func
3.2.2 闭包的参数
使用nolocal可以修改闭包内的外部变量。以下代码中:
- 先执行out_func(“wang”),此时会执行函数out_func,且入参name = “wang”
- 遇到def 先不执行,继续向下执行
print(f'{name}:{new_name}')
,即输出wang:newname - 然后执行return,即此时 wang = inner_func
- 然后执行
wang("欲买桂花同载酒")
,即 执行inner_func(“欲买桂花同载酒”) - 然后执行到new_name = "ceyyen"时,此时变量被修改,然后继续执行
- 输出wang:欲买桂花同载酒 和 wang:ceyyen
def out_func(name):
new_name = "newname"
info = "终不似,少年游"
def inner_func(info):
nonlocal new_name
new_name = "ceyyen"
print(f'{name}:{info}')
print(f'{name}:{new_name}')
print(f'{name}:{new_name}')
return inner_func
wang = out_func("wang")
wang("欲买桂花同载酒")
4. 装饰器
4.1 什么是装饰器
装饰器就是把一个函数当做参数传递给闭包中的外部函数,同时在内部函数中使用这个函数,并给他添加新的功能。
def out_func(fn):
def inner_func():
print('Hello')
fn()
return inner_func
例如上面的代码中,把函数fn当做入参传给了外部函数out_func,同时在内部函数inner_func中使用了这个函数,同时inner_func函数还增加了print(‘Hello’) 的功能。
4.2 作用
装饰器的作用是:在不改变原有函数的源代码的情况下,给函数增加新的功能。
例如:有一个聊天系统,可以聊天,但是在使用聊天功能前,要先登录系统。
以上就可以使用装饰器来实现,下面是两种方法
4.2.1 常规代码
"""
def my_decorator(func):
def inner():
print("正在检查登录状态")
func()
return inner
# 例如:有一个聊天系统,可以聊天,但是在使用聊天功能前,要先登录系统。
def chatting():
print("ceyyen:你好")
chat = my_decorator(chatting)
chat()
4.2.2 装饰器语法糖实现功能
def my_decorator(func):
def inner():
print("正在检查登录状态")
func()
return inner
# 例如:有一个聊天系统,可以聊天,但是在使用聊天功能前,要先登录系统。
@my_decorator
def talking():
print("ceyyen:你好")
talking()
4.3 使用装饰器计算函数执行时间
def reckon_time(func):
def inner():
# 定义开始时间
start_time = time.time()
func()
# 定义结束时间
end_time = time.time()
print(f"执行时间是:{end_time - start_time}")
return inner
@reckon_time
def total():
num = 0
for i in range(10):
time.sleep(1)
num += i
print(num)
4.4 通用装饰器
通用装饰器有以下几个特征:
- 函数有参数,则装饰器中的内部函数和外部函数入参都要有参数,且参数要一致
- 函数有返回值,则在内部函数中要加返回值
- 如果参数为不定长参数,则装饰器的内部函数和外部函数也要改为不定长参数
def my_current_decorator(func):
def inner(*args, **kwargs):
print("新功能添加此处")
return func(*args, **kwargs)
return inner
@my_current_decorator
def playing(*args, **kwargs):
print(args)
print(kwargs)
playing("20240101", name="ceyyen")
4.5 多个装饰器
多个装饰器的装饰顺序是:就近原则,
多个装饰器的执行顺序是:就远原则
例如下面的例子:
先装饰current_time_decorator,再去装饰my_current_decorator
执行顺序则是:先执行my_current_decorator,再去执行current_time_decorator
即:playing("20240101", name="ceyyen") = my_current_decorator(current_time_decorator(playing))("20240101", name="ceyyen")
def current_time_decorator(func):
def inner(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print(f"执行时间是:{end_time - start_time}")
return inner
def my_current_decorator(func):
def inner(*args, **kwargs):
print("新功能添加此处")
return func(*args, **kwargs)
return inner
@my_current_decorator
@current_time_decorator
def playing(*args, **kwargs):
print(args)
time.sleep(2)
print(kwargs)
playing("20240101", name="ceyyen")
4.6 带参数的装饰器
我想使用装饰器实现传入运算符,识别两个数字的运算。
即在原有装饰器外层加一层,用来传递参数。
def number_crunching_decorator(flag):
def out_func(func):
def inner(*args, **kwargs):
if flag == "+":
print("加法运算")
elif flag == "-":
print("减法运算")
return func(*args, **kwargs)
return inner
return out_func
@number_crunching_decorator("+")
def numbers(a, b):
print(a + b)
numbers(10, 3)
4.7 类装饰器
4.7.1 __call__方法
一旦类中有__call__方法,那么这个类创建的对象就是一个可调用的对象,可以像调用函数一样进行调用。
4.7.2 案例
还是以聊天系统为例。
例如:有一个聊天系统,可以聊天,但是在使用聊天功能前,要先登录系统。
class CheckLogin(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
print("正在检查登录状态")
self.fn(*args, **kwargs)
@CheckLogin
def saying(): # saying = CheckLogin(saying)
print("hello")
saying()
4.8 property属性
property属性就是负责把类中的每一个方法当做属性进行使用。
定义property属性有两种方式,分别是使用装饰器、类属性。
4.8.1 装饰器
@property表示把方法当做属性使用,表示获取属性时会执行下面修饰的方法。
@方法名.setter表示把方法当做属性使用,表示当设置属性时,会执行下面修饰的方法。
使用装饰器方式要保证以上两个装饰器修饰的方法名一定要一样。
class Person(object):
def __init__(self, name):
self.name = name
self.__age = 18
@property
def get_age(self):
return self.__age
@get_age.setter
def get_age(self, age):
self.__age = age
stu = Person("ceyyen")
print(stu.get_age)
stu.get_age = 24
print(stu.get_age)
4.8.2 类属性方式
用法:age = property(获取方法,设置方法)
class Person(object):
def __init__(self, name):
self.name = name
self.__age = 18
self.__money = 10000
def get_money(self):
return self.__money
def set_money(self, money):
self.__money = money
money = property(fget=get_money, fset=set_money)
stu = Person("ceyyen")
print(stu.get_age)
stu.get_age = 24
print(stu.get_age)