Web服务器
HTTP
HTTP协议
在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP
- HTML是一种用来定义网页的文本,会HTML,就可以编写网页
- HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信
HTTP请求
- 这就是浏览器发送给服务器的数据,而这些数据的格式,就是 HTTP协议
说明:
GET / HTTP/1.1
GET表示一个读取请求,
/
表示URL的路径,URL总是以 / 开头, / 就表示首页,最后的HTTP/1.1指示采用的HTTP协议版本是1.1,目前HTTP协议的版本就是1.1
HTTP响应
- HTTP响应分为Header和Body两部分(Body是可选项)
说明:
HTTP/1.1 200 OK
200表示一个成功的响应,后面的OK是说明。
如果返回的不是200,那么往往有其他的功能,例如:
- 失败的响应有404 Not Found:网页不存在
- 500 Internal Server Error:服务器内部出错
- …等等…
响应格式
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
注意header 空一行后,就意味着header 的结束,下面是 body HTTP里换行是用
\r\n
也就是说header和body之间隔了 两个
\r\n
简单的HTTP服务器
Web静态服务器-2-显示需要的页面
import socket
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1.接受浏览器发送来的请求,即http请求
request = new_socket.recv(1024)
print(request)
# 2.返回http格式的数据给浏览器
# >>>>准备发送----header
respeonse = "HTTP/1.1 200 OK\r\n"
respeonse += "\r\n"
# >>>>准备发送----body
respeonse += "<h1>hello world</h1>"
new_socket.send(respeonse.encode("utf-8"))
# 关闭套接字
new_socket.close()
def main():
"""用来完整体的控制"""
# 1.创建套接字
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重复使用端口,以便服务器先关闭导致等待,端口被占用
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.绑定
tcp_server.bind(("", 7080))
# 3.变为监听套接字
tcp_server.listen(128)
while True:
# 4.等待客户端的链接
new_socket, clien_addr = tcp_server.accept()
# 5.为之歌客户端服务
service_client(new_socket)
if __name__ == '__main__':
main()
Web静态服务器-2-显示需要的页面
import socket
import re
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1.接受浏览器发送来的请求,即http请求
request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
print("")
print(request_lines)
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
file_name = ""
if ret:
file_name = ret.group(1)
if file_name == "/":
file_name = "/index.html"
# 2.返回http格式的数据给浏览器
# >>>>----获取----body----内容
try:
f = open("." + file_name, "rb")
except:
respeonse = "HTTP/1.1 404 NOT FOUND\r\n"
respeonse += "\r\n"
respeonse += "-----找不到该网页-----"
new_socket.send(respeonse.encode("gbk"))
else:
html_content = f.read()
f.close()
# >>>>----header内容
respeonse = "HTTP/1.1 200 OK\r\n"
respeonse += "\r\n"
# >>>>发送----header
new_socket.send(respeonse.encode("utf-8"))
# >>>>发送----body
new_socket.send(html_content)
# 关闭套接字
new_socket.close()
def main():
"""用来完整体的控制"""
# 1.创建套接字
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重复使用端口,以便服务器先关闭导致等待,端口被占用
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.绑定
tcp_server.bind(("", 7080))
# 3.变为监听套接字
tcp_server.listen(128)
while True:
# 4.等待客户端的链接
new_socket, clien_addr = tcp_server.accept()
# 5.为之歌客户端服务
service_client(new_socket)
if __name__ == '__main__':
main()
多进程http服务器
import socket
import re
import multiprocessing
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1.接受浏览器发送来的请求,即http请求
request = new_socket.recv(1024).decode("gbk")
request_lines = request.splitlines()
print("")
print(request_lines)
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
file_name = ""
if ret:
file_name = ret.group(1)
if file_name == "/":
file_name = "/index.html"
# 2.返回http格式的数据给浏览器
# >>>>----获取----body----内容
try:
f = open("." + file_name, "rb")
except:
respeonse = "HTTP/1.1 404 NOT FOUND\r\n"
respeonse += "\r\n"
respeonse += "-----找不到该网页-----"
new_socket.send(respeonse.encode("gbk"))
else:
html_content = f.read()
f.close()
# >>>>----header内容
respeonse = "HTTP/1.1 200 OK\r\n"
respeonse += "\r\n"
# >>>>发送----header
new_socket.send(respeonse.encode("utf-8"))
# >>>>发送----body
new_socket.send(html_content)
# 关闭套接字
new_socket.close()
def main():
"""用来完整体的控制"""
# 1.创建套接字
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重复使用端口,以便服务器先关闭导致等待,端口被占用
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.绑定
tcp_server.bind(("", 7080))
# 3.变为监听套接字
tcp_server.listen(128)
while True:
# 4.等待客户端的链接
new_socket, clien_addr = tcp_server.accept()
# 5.为多个客户服务 多进程
p = multiprocessing.Process(target=service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
tcp_server.socket.close()
if __name__ == '__main__':
main()
多线程http服务器
- 多线程只需把上面的模块换一下,线程创建改一下即可
p = threading.Thread(target=service_client, args=(new_socket,))
p.start()
值得注意的是,因为多进程会复制全部资源,服务结束使需要
new_socket.close()
而多线程就不能关闭,不然无法正常工作
gevent(协程)实现http服务器
- 协程实现的服务器,相比于进程和线程,非常的节省资源,访问人数越多,相比于前两种,协程就越占优势~
- 这个也只需简单修改即可,把模块换一下
gevent.spawn(service_client, new_socket)
- 记得在前面加上这句话
monkey.patch_all()
并发服务器
非堵塞模式
- 想让多人同时访问一个服务器,并不是只有多进程、线程、协程才能完成
- 单进程-单线程同样可以完成多人访问,只需把套接字手动设为非堵塞状态
import socket
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server.bind(("", 7890))
tcp_server.listen(128)
tcp_server.setblocking(False) # 设置套接字为非堵塞方式
client_socket_list = list()
while True:
try:
new_socket, new_addr = tcp_server.accept()
except Exception as ret:
print("----没有新用户的到来---")
else:
print("---只要没有异常,那么就相当于一个新的客户端到来")
new_socket.setblocking(False) # 设置套接字为非堵塞方式
client_socket_list.append(new_socket)
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024)
except Exception as ret:
print("----这个客户端没有发送来数据----")
else:
if recv_data:
# 对方发送来数据
print("---客户端发送过来了数据---")
else:
# 对方调用了 close 导致了 recv返回
client_socket_list.remove(client_socket)
client_socket.close()
print("客户端已经关闭")
epoll
- epoll多用于Linux、Ulinix、Mac,故一般大型服务器都是用他们的
- IO 多路复用
就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。
select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。
它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
- epoll简单模型
import socket
import select
# 创建套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置可以重复使用绑定的信息
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
# 绑定本机信息
s.bind(("",7788))
# 变为被动
s.listen(10)
# 创建一个epoll对象
epoll = select.epoll()
# 测试,用来打印套接字对应的文件描述符
# print(s.fileno())
# print(select.EPOLLIN|select.EPOLLET)
# 注册事件到epoll中
# epoll.register(fd[, eventmask])
# 注意,如果fd已经注册过,则会发生异常
# 将创建的套接字添加到epoll的事件监听中
epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET)
connections = {}
addresses = {}
# 循环等待客户端的到来或者对方发送数据
while True:
# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
epoll_list = epoll.poll()
# 对事件进行判断
for fd, events in epoll_list:
# print fd
# print events
# 如果是socket创建的套接字被激活
if fd == s.fileno():
new_socket, new_addr = s.accept()
print('有新的客户端到来%s' % str(new_addr))
# 将 conn 和 addr 信息分别保存起来
connections[new_socket.fileno()] = new_socket
addresses[new_socket.fileno()] = new_addr
# 向 epoll 中注册 新socket 的 可读 事件
epoll.register(new_socket.fileno(), select.EPOLLIN|select.EPOLLET)
# 如果是客户端发送数据
elif events == select.EPOLLIN:
# 从激活 fd 上接收
recvData = connections[fd].recv(1024).decode("utf-8")
if recvData:
print('recv:%s' % recvData)
else:
# 从 epoll 中移除该 连接 fd
epoll.unregister(fd)
# server 侧主动关闭该 连接 fd
connections[fd].close()
print("%s---offline---" % str(addresses[fd]))
del connections[fd]
del addresses[fd]
- I/O 多路复用的特点:
通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 ‘监视’ 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。
当然也可以多线程/多进程方式,一个连接过来开一个进程/线程处理,这样消耗的内存和进程切换页会耗掉更多的系统资源。 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给进程池/线程池来处理逻辑。
- 关于epoll的更多资料请参考:
http://blog.youkuaiyun.com/xiajun07061225/article/details/9250579
END…