昨日回顾
http 协议
- 超文本传输协议
- request 请求
四次挥手:客户端 close ,服务器close,释放资源,断开链接
今日内容
localhost = 127.0.0.1 表示本地地址
url:Uniform Resource Locators 统一资源定位符
多任务版本的http服务器
多进程版本:
多进程最耗费资源
子进程会复制主进程的一份资源
fd:文件描述符,就是一个数字,对应一个特殊的文件,例如网络接口
所有的Linux里的东西都对应一个文件
import socket
import re
import multiprocessing
def server_client(client_socket):
"""实现接收消息和回送消息"""
# 接收消息
# request 请求
request = client_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
print("")
print(">" * 20)
print(request_lines)
# GET /index.html HTTP/1.1
# 使用正则取出用户请求的数据名
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
file_name = ""
if ret:
file_name = ret.group(1)
print("请求的名字是:%s" % file_name)
if file_name == "/":
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "-----没有要找的页面----"
client_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 回送消息, 返回 http 格式的数据给浏览器
# 回应给浏览器的数据 header reponse 回应
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n" # 浏览器中的回车时 \r 空行是 \n
# 发送数据 header
client_socket.send(response.encode("utf-8"))
# 发送数据 body
client_socket.send(html_content)
# 关闭套接字
client_socket.close()
def main():
# 创建服务器套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 可以让端口重复使用
# 绑定本地信息
tcp_server_socket.bind(("", 8888))
# 监听消息
tcp_server_socket.listen()
while True:
# 等待客户的连接
new_socket, client_addr = tcp_server_socket.accept()
# 服务客户端
# server_client(new_socket)
# 使用多进程来实现
p = multiprocessing.Process(target=server_client, args=(new_socket,)) # 创建多进程
p.start() # 启动子进程
# 在这里要关闭套接字,子进程有自己的资源,创建子进程会给这个新套接字上来个新的硬链接,由子进程最后来关闭这个套接字
# 主进程中不关闭的话服务器会认为一直在请求数据,
new_socket.close()
# 关闭监听
tcp_server_socket.close()
if __name__ == '__main__':
main()
多线程版本:
多线程创建和多进程创建差不多,区别在于:在多进程中,每个进程有自己的资源,所以需要在主进程中关闭客户端套接字,当子进程执行完毕,由进程来最后关闭自己的套接字;而多线程共享资源,所以不需要在主线程中关闭套接字。
import socket
import re
import threading
def server_client(client_socket):
"""实现接收消息和回送消息"""
# 接收消息
# request 请求
request = client_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
print("")
print(">" * 20)
print(request_lines)
# GET /index.html HTTP/1.1
# 使用正则取出用户请求的数据名
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
file_name = ""
if ret:
file_name = ret.group(1)
print("请求的名字是:%s" % file_name)
if file_name == "/":
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "-----没有要找的页面----"
client_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 回送消息, 返回 http 格式的数据给浏览器
# 回应给浏览器的数据 header reponse 回应
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n" # 浏览器中的回车时 \r 空行是 \n
# 发送数据 header
client_socket.send(response.encode("utf-8"))
# 发送数据 body
client_socket.send(html_content)
# 关闭套接字
client_socket.close()
def main():
# 创建服务器套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 可以让端口重复使用
# 绑定本地信息
tcp_server_socket.bind(("", 8888))
# 监听消息
tcp_server_socket.listen()
while True:
# 等待客户的连接
new_socket, client_addr = tcp_server_socket.accept()
# 服务客户端
# server_client(new_socket)
# 使用多进程来实现
t = threading.Thread(target=server_client, args=(new_socket,)) # 创建多进程
t.start() # 启动子进程
# 线程中共享资源,所以不要提前关闭套接字
# new_socket.close()
# 关闭监听
tcp_server_socket.close()
if __name__ == '__main__':
main()
协程版本:
协程多任务消耗资源最少
import socket
import re
import gevent
def server_client(client_socket):
"""实现接收消息和回送消息"""
# 接收消息
# request 请求
request = client_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
print("")
print(">" * 20)
print(request_lines)
# GET /index.html HTTP/1.1
# 使用正则取出用户请求的数据名
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
file_name = ""
if ret:
file_name = ret.group(1)
print("请求的名字是:%s" % file_name)
if file_name == "/":
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "-----没有要找的页面----"
client_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 回送消息, 返回 http 格式的数据给浏览器
# 回应给浏览器的数据 header reponse 回应
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n" # 浏览器中的回车时 \r 空行是 \n
# 发送数据 header
client_socket.send(response.encode("utf-8"))
# 发送数据 body
client_socket.send(html_content)
# 关闭套接字
client_socket.close()
def main():
# 创建服务器套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 可以让端口重复使用
# 绑定本地信息
tcp_server_socket.bind(("", 8888))
# 监听消息
tcp_server_socket.listen()
while True:
# 等待客户的连接
new_socket, client_addr = tcp_server_socket.accept()
# 服务客户端
# server_client(new_socket)
# 创建协程
g = gevent.spawn(server_client, new_socket)
g.join()
# 关闭监听
tcp_server_socket.close()
if __name__ == '__main__':
main()
单进程、单线程、长链接、非阻塞版本的http服务器
长连接:建立好连接后,多次收发数据,不传递数据了断开即可
短连接:收发一次数据后,就建立连接和断开连接
import socket
import re
def server_client(client_socket, request):
"""实现接收消息和回送消息"""
# 接收消息
# request 请求
# request = client_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines() # 将客户端传递过来的请求转换为列表
print("")
print(">" * 20)
print(request_lines)
# GET /index.html HTTP/1.1
# 使用正则取出用户请求的数据名
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
file_name = ""
if ret: # 进行判断,如果得到请求的数据名就进行赋值
file_name = ret.group(1)
print("请求的名字是:%s" % file_name)
if file_name == "/": # 当用户只输入ip和端口的时候,请求的只是 / ,此时给用户返回默认页面
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try: # 进行异常判断
f = open("./html" + file_name, "rb") # 打开用户请求的文件名
except: # r如果没有用户请求的内容,返回 404
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response = "<h1>FILE NOT FOUND</h1>"
client_socket.send(response.encode("utf-8"))
else: # 有用户请求的内容
html_content = f.read() # 读取文件内容
f.close() # 关闭文件
response_body = html_content # 将读取出来的文件赋值给 响应的内容
# print(len(response_body))
# 回送消息, 返回 http 格式的数据给浏览器
# 回应给浏览器的数据 header reponse 回应
response_header = "HTTP/1.1 200 OK\r\n" # 1.1 是长链接 1.0 是短链接
# 在响应头中告诉用户浏览器本次发送的内容大小,当本次数据接收完毕会结束,
# 并进行下一次请求,这就是长链接
response_header += "Content-Length:%d\r\n" % len(response_body)
response_header += "\r\n" # 浏览器中的回车时 \r 空行是 \n
# 将响应头和响应内容合并成响应 response 发送过去,响应头需要编码
response = response_header.encode("utf-8") + response_body
# 发送数据
client_socket.send(response)
def main():
# 创建服务器套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 让端口可以重复使用
tcp_server_socket.bind(("", 8888)) # 绑定本地信息
tcp_server_socket.listen(128) # 监听客户端,最大连接数为128
tcp_server_socket.setblocking(False) # 将套接字设置为非阻塞
client_socket_list = list() # 创建一个客户端套接字列表
while True:
try: # 因为是非阻塞,所以如果没有客户端链接的情况下一定会报错,需要进行异常处理
new_socket, client_addr = tcp_server_socket.accept() # 等待客户端链接
except Exception as ret: # 出现错误就忽略
pass
else: # 有客户端链接
new_socket.setblocking(False) # 先将新套接字设置为非阻塞
client_socket_list.append(new_socket) # 将新套接字的引用放到客户端套接字列表中
for client_socket in client_socket_list: # 遍历得到每个客户端套接字
try: # 因客户端套接字已经设置为非阻塞,所有在没有消息发送过来的时候会报错,进行异常判断
recv_data = client_socket.recv(1024).decode("utf-8") # 需要进行传参,所以直接进行转码
except: # 忽略没有消息的情况
pass
else: # 有消息过来
if recv_data: # 进行判断,有数据就调用 服务客户端 的函数,将客户端套接字和接收到的数据传参过去
server_client(client_socket, recv_data)
else: # 发送过来的数据为空,说明客户端已经关闭
client_socket.close() # 此时将该客户端对应的套接字关闭
client_socket_list.remove(client_socket) # 并且将该客户端的引用从客户端套接字列表中中删除,防止资源占用
# 关闭监听
tcp_server_socket.close()
if __name__ == '__main__':
main()
epoll 版本的 http 服务器
作用:提高单进程、单线程传输数据的效率
如何实现:开辟一个单独的空间出来
- 操作系统搞一块公共内存,减少了复制的次数
- 用了事件通知机制,比轮循快多了
import socket
import re
import select
def server_client(client_socket, request):
"""实现接收消息和回送消息"""
# 接收消息
# request 请求
# request = client_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines() # 将客户端传递过来的请求转换为列表
print("")
print(">" * 20)
print(request_lines)
# GET /index.html HTTP/1.1
# 使用正则取出用户请求的数据名
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
file_name = ""
if ret: # 进行判断,如果得到请求的数据名就进行赋值
file_name = ret.group(1)
print("请求的名字是:%s" % file_name)
if file_name == "/": # 当用户只输入ip和端口的时候,请求的只是 / ,此时给用户返回默认页面
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try: # 进行异常判断
f = open("./html" + file_name, "rb") # 打开用户请求的文件名
except: # r如果没有用户请求的内容,返回 404
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response = "<h1>FILE NOT FOUND</h1>"
client_socket.send(response.encode("utf-8"))
else: # 有用户请求的内容
html_content = f.read() # 读取文件内容
f.close() # 关闭文件
response_body = html_content # 将读取出来的文件赋值给 响应的内容
# 回送消息, 返回 http 格式的数据给浏览器
# 回应给浏览器的数据 header reponse 回应
response_header = "HTTP/1.1 200 OK\r\n" # 1.1 是长链接 1.0 是短链接
# 在响应头中告诉用户浏览器本次发送的内容大小,当本次数据接收完毕会结束,
# 并进行下一次请求,这就是长链接
response_header += "Content-Length:%d\r\n" % len(response_body)
response_header += "\r\n" # 浏览器中的回车时 \r 空行是 \n
# 将响应头和响应内容合并成响应 response 发送过去,响应头需要编码
response = response_header.encode("utf-8") + response_body
# 发送数据
client_socket.send(response)
def main():
# 创建服务器套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket.SO_REUSEADDR 在同一端口上可以运行多个应用程序,但是只有第一次绑定这个端口的进程才能使用
# 如果多个进程要使用同一端口,必须设置 socket.SO_REUSEADDR ,起到排队的作用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 让端口可以重复使用
tcp_server_socket.bind(("", 8888)) # 绑定本地信息
tcp_server_socket.listen(128) # 监听客户端,最大连接数为128
tcp_server_socket.setblocking(False) # 将套接字设置为非阻塞
client_socket_list = list() # 创建一个客户端套接字列表
# 创建一个epoll 对象
epl = select.epoll()
# 将监听套接字对应的fd注册到epoll中
epl.register(tcp_server_socket.fileno(), select.EPOLLIN)
fd_event_dict = dict()
while True:
# 默认会堵塞,直到 os监测到数据到来 通过事件通知方式 告诉这个程序,此时才会解堵塞
fd_event_list = epl.poll()
# [(fd, event), (套接字对应的文件描述符, 这个文件描述符到底是什么事件 例如 可以调用recv接收等)]
for fd, event in fd_event_list:
# 等待新客户端的链接
if fd == tcp_server_socket.fileno():
new_socket, client_addr = tcp_server_socket.accept()
epl.register(new_socket.fileno(), select.EPOLLIN) # 将新套接字也注册到epoll中
fd_event_dict[new_socket.fileno()] = new_socket # 将新套接字存入字典,key键是新套接字的文件描述符,value值是新套接字的引用
elif event == select.EPOLLIN:
# 判断已经链接的客户端是否有数据发送过来
recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
if recv_data:
server_client(fd_event_dict[fd], recv_data) # 有数据就调用服务客户端的函数
else:
# 没有数据过来说明客户端已经关闭
fd_event_dict[fd].close() # 关掉这个套接字
epl.unregister(fd) # 从epoll中删除
del fd_event_dict[fd] # 从字典中删除
# 关闭监听
tcp_server_socket.close()
if __name__ == '__main__':
main()