第十八篇:简易版web服务器开发
在上篇有实现了一个静态的web服务器,可以接收web浏览器的请求,随后对请求消息进行解析,获取客户想要文件的文件名,随后根据文件名返回响应消息;那么这篇我们对该web服务器进行改善,通过多任务、非阻塞以及epoll(重点)的方式来对该服务器进行改善。
01-多任务-阻塞式-简易版web服务器开发
#!/usr/bin/env python
# -*- coding:utf-8 -*- import socket
import re def server_client(new_socket):
"""服务客户端"""
# 接收客户端请求消息,并对接收的消息进行解析--> GET /index.html HTTP/1.1
request = new_socket.recv(1024).decode("utf-8")
request_list = request.splitlines() # 对消息进行解析,并且返回列表
print(request_list)
filename = re.match(r"[^/]+(/[^ ]*)",request_list[0]).group(1)
print(filename) # 发送响应消息
# 指定只输入域名时,默认响应的网页
if filename == "/":
filename = "/index.html" # 尝试打开匹配的文件,若该服务器内没有该文件,则发送状态码404,若有则响应的内容
try:
f = open("html"+ filename,"rb")
except Exception as e:
print(e)
respone_header = "HTTP/1.1 404 File No Found\r\n"
respone_header += "\r\n"
respone_body = "<h1>No Found File</h1>"
respone = respone_header + respone_body
respone = respone.encode("utf-8")
else:
respone_body = f.read()
f.close()
respone_header = "HTTP/1.1 200 OK \r\n"
respone_header += "\r\n"
respone = respone_header.encode("utf-8") + respone_body
finally:
new_socket.send(respone)
new_socket.close() def main():
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind(("",6969))
server_socket.listen(128) # 通过while循环,可以接收多个客户端的连接
while True:
new_socket,client_addr = server_socket.accept()
server_client(new_socket) server_socket.close() if __name__ == "__main__":
main()
以上就单任务-阻塞式-简易版的web服务器,即整个程序只有一条主进程主线程,且需要等待客户端的接入、等待接收客户端的发送过来的消息等,而在这个等待的过程中,整段程序均是阻塞的转态。那么接下来我们就这两个问题来进行优化;
02-多任务-阻塞式-简易版web服务器开发
#!/usr/bin/env python
# -*- coding:utf-8 -*- import socket
import re
import multiprocessing def serve_client(new_socket):
"""服务客户端"""
# 接收客户端请求消息 GET /index.html HTTP/1.1
request = new_socket.recv(1024).decode("utf-8")
request_list = request.splitlines()
print(request_list)
filename = re.match(r"[^/]+(/[^ ]*)",request_list[0]).group(1)
print(filename) # 发送响应消息
if filename == "/":
filename = "/index.html"
try:
f = open("html"+ filename,"rb")
except Exception as e:
print(e)
respone_header = "HTTP/1.1 404 File No Found\r\n"
respone_header += "\r\n"
respone_body = "<h1>No Found File</h1>"
respone = respone_header + respone_body
respone = respone.encode("utf-8")
else:
respone_body = f.read()
f.close()
respone_header = "HTTP/1.1 200 OK \r\n"
respone_header += "\r\n"
respone = respone_header.encode("utf-8") + respone_body
finally:
new_socket.send(respone)
new_socket.close() def main():
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind(("",6969))
server_socket.listen(128) while True:
new_socket,client_addr = server_socket.accept()
# 允许立即使用上次绑定的port
self.listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 每当有一个客户端接入,创建一个进程为其服务
p = multiprocessing.Process(target=serve_client,args=(new_socket,))
p.start()
p.join() server_socket.close() if __name__ == "__main__":
main()
多进程-简易版web服务器
#!/usr/bin/env python
# -*- coding:utf-8 -*- import socket
import re
import threading def serve_client(new_socket):
"""服务客户端""" # 接收客户端请求消息 GET /index.html HTTP/1.1
request = new_socket.recv(1024).decode("utf-8")
request_list = request.splitlines()
print(request_list)
filename = re.match(r"[^/]+(/[^ ]*)",request_list[0]).group(1)
print(filename) # 发送响应消息
if filename == "/":
filename = "/index.html"
try:
f = open("html"+ filename,"rb")
except Exception as e:
print(e)
respone_header = "HTTP/1.1 404 File No Found\r\n"
respone_header += "\r\n"
respone_body = "<h1>No Found File</h1>"
respone = respone_header + respone_body
respone = respone.encode("utf-8")
else:
respone_body = f.read()
f.close()
respone_header = "HTTP/1.1 200 OK \r\n"
respone_header += "\r\n"
respone = respone_header.encode("utf-8") + respone_body
finally:
new_socket.send(respone)
new_socket.close() def main():
"""主函数"""
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind(("",6969))
server_socket.listen(128) while True:
new_socket,client_addr = server_socket.accept() # 每接收到一个客户端的连接,便创建一个线程执行该工作函数
t = threading.Thread(target=serve_client,args=(new_socket,))
t.start()
t.join() server_socket.close() if __name__ == "__main__":
main()
多线程-简易版web服务器
从上面的程序中,我们每接收到一个客户端的连接便创建一个线程或者进程来执行serve_client函数,即为该客户端服务---接收请求消息或者发送响应消息;
创建多任务的好处就是:相当于给原本只有一个窗口的银行再多开了窗口,不需要再等待上一个客户结束了服务才能服务下一个 客户,故 可以一次接受多个客户端的连接,并且一次服务多个客户端,每次服务客户端的过程互不影响。
03-单任务-非阻塞式-简易版web服务器开发
#!/usr/bin/env python
# -*- coding:utf-8 -*- import time
import socket
import sys
import re class WSGIServer(object):
"""定义一个WSGI服务器的类""" def __init__(self, documents_root): # 1. 创建套接字
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定本地信息
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind(("", 6868))
# 3. 变为监听套接字
self.server_socket.listen(128) # 设置套接字为非阻塞式,即遇到需要等待阻塞的即抛出异常
self.server_socket.setblocking(False) # 创建一个列表用来存储,服务端没接收到一个客户端连接时创建的通信套接字(引用)
self.client_socket_list = list() self.documents_root = documents_root def run_forever(self):
"""运行服务器""" # 等待对方链接
while True: # time.sleep(0.5) # for test # 判断有没有客户端连接
try:
new_socket, new_addr = self.server_socket.accept()
except Exception as ret:
print("-----1----", ret) # for test
else:
new_socket.setblocking(False)
self.client_socket_list.append(new_socket) # 判断已连接的客户端 有没有发送消息过来
for client_socket in self.client_socket_list:
try:
request = client_socket.recv(1024).decode('utf-8')
except Exception as ret:
print("------2----", ret) # for test
else:
if request:
self.deal_with_request(request, client_socket)
else:
client_socket.close()
self.client_socket_list.remove(client_socket) print(self.client_socket_list) def deal_with_request(self, request, client_socket):
"""为这个浏览器服务器"""
if not request:
return request_lines = request.splitlines()
for i, line in enumerate(request_lines):
print(i, line) # 提取请求的文件(index.html)
# GET /a/b/c/d/e/index.html HTTP/1.1
ret = re.match(r"[^/]*([^ ]+)", request_lines[0])
if ret:
print("正则提取数据:", ret.group(1))
file_name = ret.group(1)
# 当客户只输入域名时,设置默认浏览网页
if file_name == "/":
file_name = "/index.html" # 读取文件数据
try:
f = open(self.documents_root+file_name, "rb")
except:
response_body = "file not found, 请输入正确的url"
response_header = "HTTP/1.1 404 not found\r\n"
response_header += "Content-Type: text/html; charset=utf-8\r\n"
response_header += "Content-Length: %d\r\n" % (len(response_body))
response_header += "\r\n" # 将header返回给浏览器
client_socket.send(response_header.encode('utf-8')) # 将body返回给浏览器
client_socket.send(response_body.encode("utf-8"))
else:
content = f.read()
f.close() response_body = content
response_header = "HTTP/1.1 200 OK\r\n"
response_header += "Content-Length: %d\r\n" % (len(response_body))
response_header += "\r\n" # 将header返回给浏览器
client_socket.send( response_header.encode('utf-8') + response_body) # 设置服务器服务静态资源时的路径
DOCUMENTS_ROOT = "./html" def main():
"""控制web服务器整体""" http_server = WSGIServer(DOCUMENTS_ROOT)
http_server.run_forever() if __name__ == "__main__":
main()
在上面这个demo里面,由于如果用面向过程编程进行实现则会显得比较杂乱,这里我们用面向过程进行了封装;该类WSGIServer,定义了一个初始化__init__函数(用来执行类中的主程序)、run_forever函数(用来运行服务器,判断是否有客户端接入、是否有客户端发送消息过来)、以及deal_with_request()函数(用来接收并处理客户端的请求消息、并且发送响应消息。)而这个demo想对于第一个的优势在于:
这个demo使得服务器不断的在运行,检测是否有客户端接入、检测是否已连接的客户端是否有请求消息发送过来,不会因为没有客户端链接进来或者客户端没有发送消息过来而使得整段程序阻塞在此处而使得其他的客户端无法连接或者无法接收消息。
但是这种方法的缺点也特别明显:
通过不断循环的方式判断是有客户端接入和是否有请求消息发送过来,这种方式服务器处于不停的运作状态,占用CPU的资源十分庞大。
1、列表越长遍历所用时间越长
2、且将列表数据从用户态拷贝到内核态需花费不少的时间,即挨个的问---轮询
04 -epoll-简易版web服务器
在看这个demo之前我们先对epoll进行了解。优势:select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。epoll相对于上面demo优化之处,这是它是通过两种机制实现的:
共享内存空间:
在创建一个epoll对象相当于在操作系统中开辟一个用户态和内核态中间的共用特殊内存,epoll对象则相当于这个内存空间的管家,而该内存空间存储着套接字的文件描述符fd和事件,便不需花很多的时间去遍历,不用去copy该列表中的值,而在该内存空间中无论是用户程序还是内核在处理数据时均可以直接调用。
事件通知:
上面那个demo采用的是轮询的方式,即对每个套接字进行询问,是否有客户端连接进来、是否有请求消息进来等,这样将会消耗大量的时间,而epoll采用的是事件通知,即若某个套接字有时间发生,则通知内核态去对该套接字进行操作。
#!/usr/bin/env python
# -*- coding:utf-8 -*- import socket
import re
import select def server_client(new_socket,request):
"""服务客户端"""
# 接收客户端请求消息 GET /index.html HTTP/1.1
request_list = request.splitlines()
print(request_list)
filename = re.match(r"[^/]+(/[^ ]*)",request_list[0]).group(1)
print(filename) # 发送响应消息 if filename == "/":
filename = "/index.html"
try:
f = open("html"+ filename,"rb")
except Exception as e:
print(e)
respone_header = "HTTP/1.1 404 File No Found\r\n"
respone_header += "\r\n"
respone_body = "<h1>No Found File</h1>"
respone = respone_header + respone_body
respone = respone.encode("utf-8")
else:
respone_body = f.read()
f.close()
respone_header = "HTTP/1.1 200 OK \r\n"
respone_header += "\r\n"
respone = respone_header.encode("utf-8") + respone_body
finally:
new_socket.send(respone)
new_socket.close() def main():
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind(("",6969))
server_socket.listen(128) # 1、创建epoll对象
epl = select.epoll()
# 2、将监听套接字注册到epoll对象管理的内存空间
epl.register(server_socket.fileno(),select.EPOLLIN)
# 3、由于epoll管理的内存空间处于用户程序和操作系统之间,故需要fd和socket来回转换,故创建一个字典 # 使用epoll来监听是否有客户端连接、是否客户端有请求消息发送过来
# 4、创建文件描述符fd:套接字引用的字典,--》由于操作系统有用户态(只能处理socket套接字)和内核态(只能处理文件描述符fd)
fd_socket ={ } while True:
# 创建一个列表,存储有事件发生的套接字
fd_event_list = epl.poll()
for fd,event in fd_event_list:
# 5、判断是否为监听套接字发生变化
if fd == server_socket.fileno():
new_socket, client_addr = server_socket.accept()
epl.register(new_socket.fileno(),select.EPOLLIN)
fd_socket[new_socket.fileno()] = new_socket
# 6、否则为通信套接字有事件发送
else:
request = fd_socket[fd].recv(1024).decode("utf-8")
# 如果接收的请求消息不为空即客户端没有断开连接
if request:
server_client(fd_socket[fd],request)
# 否则客户端断开连接:
else:
fd_socket[fd].close()
epl.unregister(fd_socket[fd])
del fd_socket[fd] # server_client(new_socket) server_socket.close() if __name__ == "__main__":
main()
解析:
1、创建epoll对象:相当于在操作系统中开辟一个用户态和内核态中间的共用特殊内存,而管理员就是epoll对象;
2、将监听套接字注册到epoll对象:相当于将监听套接字的文件描述符和事件添加到epoll对象管理的特殊内存空间,该内存空间无论用户程序还是内核均可操作。
3、创建一个fd_socket字典:即由于需对套接字进行操作,而此时只有文件描述符fd,而两者是一一映射的关系,故可以通过fd获取对应的socket套接字;
4、通过不断循环,查看监听和通信套接字是否有事件发生;
5、首先判断监听套接字是否有反应,若有则创建新的套接字去服务客户端,同时将新的套接字注册到epoll对象;并且将其与文件描述符fd映射关系添加到fd_socket字典中的;
6、随后检测通信套接字是否有反应,接收请求消息;
第十八篇:简易版web服务器开发的更多相关文章
- Python之路【第十八篇】:Web框架们
Python之路[第十八篇]:Web框架们 Python的WEB框架 Bottle Bottle是一个快速.简洁.轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除了Pytho ...
- C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载
基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...
- 超简易静态Web服务器
使用 HttpListener 写的一个超简易静态Web服务器 开发环境:VS2010 + .NET2.0 http://files.cnblogs.com/zjfree/EasyIIS.rar
- Python之路【第十五篇】:Web框架
Python之路[第十五篇]:Web框架 Web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. 1 2 3 4 5 6 ...
- 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息
第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...
- Python开发【第二十二篇】:Web框架之Django【进阶】
Python开发[第二十二篇]:Web框架之Django[进阶] 猛击这里:http://www.cnblogs.com/wupeiqi/articles/5246483.html 博客园 首页 ...
- Egret入门学习日记 --- 第十八篇(书中 8.5~8.7 节 内容)
第十八篇(书中 8.5~8.7 节 内容) 其实语法篇,我感觉没必要写录入到日记里. 我也犹豫了好久,到底要不要录入. 这样,我先读一遍语法篇的所有内容,我觉得值得留下的,我就录入日记里. 不然像昨天 ...
- Python开发【第十八篇】Web框架之Django【基础篇】
一.简介 Python下有许多款不同的 Web 框架,Django 是重量级选手中最有代表性的一位,许多成功的网站和APP都基于 Django. Django 是一个开放源代码的Web应用框架,由 P ...
- Python开发【第十五篇】:Web框架之Tornado
概述 Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本.这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了 ...
随机推荐
- [ kvm ] 学习笔记 5:QEMU-KVM 命令详解
1. QEMU.KVM .QEMU-KVM QEMU 提供了一系列的硬件模拟设备(cpu.网卡.磁盘等),客户机指令都需要QEMU翻译,因此性能较差.KVM 是Linux 内核提供的虚拟化模块,负责C ...
- vue骨架屏以及seo优化
参考文档 vue骨架屏 https://blog.csdn.net/ly124100427/article/details/81168908 vue seo优化 1.SSR服务器渲染: 2.静态化: ...
- AWS 架构最佳实践概述(十一)
AWS 架构最佳实践 AWS合理架构的框架支柱 安全性 - 保护并监控系统 能够保护信息.系统和资产 通过风险评估和缓解策略 可靠性 - 从故障中恢复并减少中断 从基础设施或服务故障中恢复 动态获取计 ...
- 最近邻与K近邻算法思想
在关于径向基神经网络的一篇博文机器学习之径向基神经网络(RBF NN)中已经对最近邻思想进行过描述,但是写到了RBF中有些重点不够突出,所以,这里重新对最近邻和K近邻的基本思想进行介绍,简洁扼要的加以 ...
- CSS 按钮水波纹特效
/* 按钮反馈之波纹 */ .ripple { position: relative; /* overflow:hidden */ 打开注释及效果不扩散在外 } .ripple:focus{ out ...
- rdkafka swoole
1.yum install php-devel php-pear 2. wget http://pear.php.net/go-pear.phar 3.PHP go-pear.phar 4.cp /r ...
- java抽象类及接口
Java抽象类: 抽象类特点:抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量.成员方法和构造方法的访问方式和普通类一样. 由于抽象类不能实例化对象,所以抽象类必须被extends [抽象 ...
- Spring Security 官网文档学习
文章目录 通过`maven`向普通的`WEB`项目中引入`spring security` 配置 `spring security` `configure(HttpSecurity)` 方法 自定义U ...
- python使用ORM之如何调用多对多关系
在models.py中,我创建了两张表,他们分别是作者表和书籍表,且之间的关系是多对多. # 书 class Book(models.Model): id = models.AutoField(pri ...
- 『Python基础』第8节:格式化输出
现在有一个需求, 询问用户的姓名, 年龄, 工作, 爱好, 然后打印成以下格式 ************ info of Conan ************ name: Conan age: 23 ...