阅读目录

1.Web静态服务器-5-非堵塞模式

2.Web静态服务器-6-epoll

3.Web静态服务器-7-gevent版

4.知识扩展-C10K问题

一.Web静态服务器-5-非堵塞模式

单进程非堵塞 模型

#coding=utf-8
from socket import *
import time # 用来存储所有的新链接的socket
g_socket_list = list() def main():
server_socket = socket(AF_INET, SOCK_STREAM)
server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
server_socket.bind(('', 7890))
server_socket.listen(128)
# 将套接字设置为非堵塞
# 设置为非堵塞后,如果accept时,恰巧没有客户端connect,那么accept会
# 产生一个异常,所以需要try来进行处理
server_socket.setblocking(False) while True: # 用来测试
time.sleep(0.5) try:
newClientInfo = server_socket.accept()
except Exception as result:
pass
else:
print("一个新的客户端到来:%s" % str(newClientInfo))
newClientInfo[0].setblocking(False) # 设置为非堵塞
g_socket_list.append(newClientInfo) for client_socket, client_addr in g_socket_list:
try:
recvData = client_socket.recv(1024)
if recvData:
print('recv[%s]:%s' % (str(client_addr), recvData))
else:
print('[%s]客户端已经关闭' % str(client_addr))
client_socket.close()
g_socket_list.remove((client_socket,client_addr))
except Exception as result:
pass print(g_socket_list) # for test if __name__ == '__main__':
main()

web静态服务器-单进程非堵塞

import time
import socket
import sys
import re class WSGIServer(object):
"""定义一个WSGI服务器的类""" def __init__(self, port, 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(("", port))
# 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))
print("正则提取数据:", ret.group(2))
file_name = ret.group(2)
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服务器整体"""
# python3 xxxx.py 7890
if len(sys.argv) == 2:
port = sys.argv[1]
if port.isdigit():
port = int(port)
else:
print("运行方式如: python3 xxx.py 7890")
return print("http服务器使用的port:%s" % port)
http_server = WSGIServer(port, DOCUMENTS_ROOT)
http_server.run_forever() if __name__ == "__main__":
main()

二.Web静态服务器-6-epoll

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]

说明

  • EPOLLIN (可读)
  • EPOLLOUT (可写)
  • EPOLLET (ET模式)

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。

ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。

web静态服务器-epool

以下代码,支持http的长连接,即使用了Content-Length

import socket
import time
import sys
import re
import select class WSGIServer(object):
"""定义一个WSGI服务器的类""" def __init__(self, port, 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(("", port))
# 3. 变为监听套接字
self.server_socket.listen(128) self.documents_root = documents_root # 创建epoll对象
self.epoll = select.epoll()
# 将tcp服务器套接字加入到epoll中进行监听
self.epoll.register(self.server_socket.fileno(), select.EPOLLIN|select.EPOLLET) # 创建添加的fd对应的套接字
self.fd_socket = dict() def run_forever(self):
"""运行服务器""" # 等待对方链接
while True:
# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
epoll_list = self.epoll.poll() # 对事件进行判断
for fd, event in epoll_list:
# 如果是服务器套接字可以收数据,那么意味着可以进行accept
if fd == self.server_socket.fileno():
new_socket, new_addr = self.server_socket.accept()
# 向 epoll 中注册 连接 socket 的 可读 事件
self.epoll.register(new_socket.fileno(), select.EPOLLIN | select.EPOLLET)
# 记录这个信息
self.fd_socket[new_socket.fileno()] = new_socket
# 接收到数据
elif event == select.EPOLLIN:
request = self.fd_socket[fd].recv(1024).decode("utf-8")
if request:
self.deal_with_request(request, self.fd_socket[fd])
else:
# 在epoll中注销客户端的信息
self.epoll.unregister(fd)
# 关闭客户端的文件句柄
self.fd_socket[fd].close()
# 在字典中删除与已关闭客户端相关的信息
del self.fd_socket[fd] 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))
print("正则提取数据:", ret.group(2))
file_name = ret.group(2)
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" # 将数据返回给浏览器
client_socket.send(response_header.encode("utf-8")+response_body) # 设置服务器服务静态资源时的路径
DOCUMENTS_ROOT = "./html" def main():
"""控制web服务器整体"""
# python3 xxxx.py 7890
if len(sys.argv) == 2:
port = sys.argv[1]
if port.isdigit():
port = int(port)
else:
print("运行方式如: python3 xxx.py 7890")
return print("http服务器使用的port:%s" % port)
http_server = WSGIServer(port, DOCUMENTS_ROOT)
http_server.run_forever() if __name__ == "__main__":
main()

小总结

I/O 多路复用的特点:

通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 '监视' 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。

当然也可以多线程/多进程方式,一个连接过来开一个进程/线程处理,这样消耗的内存和进程切换页会耗掉更多的系统资源。 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给进程池/线程池来处理逻辑。

参考资料

三.Web静态服务器-7-gevent版

from gevent import monkey
import gevent
import socket
import sys
import re monkey.patch_all() class WSGIServer(object):
"""定义一个WSGI服务器的类""" def __init__(self, port, 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(("", port))
# 3. 变为监听套接字
self.server_socket.listen(128) self.documents_root = documents_root def run_forever(self):
"""运行服务器""" # 等待对方链接
while True:
new_socket, new_addr = self.server_socket.accept()
gevent.spawn(self.deal_with_request, new_socket) # 创建一个协程准备运行它 def deal_with_request(self, client_socket):
"""为这个浏览器服务器"""
while True:
# 接收数据
request = client_socket.recv(1024).decode('utf-8')
# print(gevent.getcurrent())
# print(request) # 当浏览器接收完数据后,会自动调用close进行关闭,因此当其关闭时,web也要关闭这个套接字
if not request:
new_socket.close()
break 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))
print("正则提取数据:", ret.group(2))
file_name = ret.group(2)
if file_name == "/":
file_name = "/index.html" file_path_name = self.documents_root + file_name
try:
f = open(file_path_name, "rb")
except:
# 如果不能打开这个文件,那么意味着没有这个资源,没有资源 那么也得需要告诉浏览器 一些数据才行
# 404
response_body = "没有你需要的文件......".encode("utf-8") response_headers = "HTTP/1.1 404 not found\r\n"
response_headers += "Content-Type:text/html;charset=utf-8\r\n"
response_headers += "Content-Length:%d\r\n" % len(response_body)
response_headers += "\r\n" send_data = response_headers.encode("utf-8") + response_body client_socket.send(send_data) else:
content = f.read()
f.close() # 响应的body信息
response_body = content
# 响应头信息
response_headers = "HTTP/1.1 200 OK\r\n"
response_headers += "Content-Type:text/html;charset=utf-8\r\n"
response_headers += "Content-Length:%d\r\n" % len(response_body)
response_headers += "\r\n"
send_data = response_headers.encode("utf-8") + response_body
client_socket.send(send_data) # 设置服务器服务静态资源时的路径
DOCUMENTS_ROOT = "./html" def main():
"""控制web服务器整体"""
# python3 xxxx.py 7890
if len(sys.argv) == 2:
port = sys.argv[1]
if port.isdigit():
port = int(port)
else:
print("运行方式如: python3 xxx.py 7890")
return print("http服务器使用的port:%s" % port)
http_server = WSGIServer(port, DOCUMENTS_ROOT")
http_server.run_forever() if __name__ == "__main__":
main()

四.知识扩展-C10K问题

参考文章 :

《单台服务器并发TCP连接数到底可以有多少》 http://www.52im.net/thread-561-1-1.html

《上一个10年,著名的C10K并发连接问题》 http://www.52im.net/thread-566-1-1.html

web服务器-并发服务器2的更多相关文章

  1. 14_Web服务器-并发服务器

    1.服务器概述 1.硬件服务器(IBM,HP): 主机 集群 2.软件服务器(HTTPserver Django flask): 网络服务器,在后端提供网络功能逻辑处理数据处理的程序或者架构等 3.服 ...

  2. Python复习笔记(十)Http协议--Web服务器-并发服务器

    1. HTTP协议(超文本传输协议) 浏览器===>服务器发送的请求格式如下:(浏览器告诉服务器,浏览器的信息) GET / HTTP/1.1 Host: www.baidu.com Conne ...

  3. Web服务器-并发服务器-Epoll(3.4.5)

    @ 目录 1.介绍 2.代码 关于作者 1.介绍 epoll是一种解决方案,nginx就是用的这个 中心思想:不要再使用多进程,多线程了,使用单进程,单线程去实现并发 在上面博客实现的代码中使用过的轮 ...

  4. Web服务器-并发服务器-长连接(3.4.4)

    @ 目录 1.说明 2.代码 关于作者 1.说明 每次new_socket都被强制关闭,造成短连接 所提不要关闭套接字 但是不关闭的话,浏览器不知道发完没有啊 此时用到header的属性Content ...

  5. Web服务器-并发服务器-单进程单线程非堵塞方式(3.4.3)

    @ 目录 1.分析 2.代码 关于作者 1.分析 当socket去监听的时候,是堵塞的状态 通过tcp_sever_socket.setblocking(False)去设置不堵塞 当socket发现没 ...

  6. Web服务器-并发服务器-协程 (3.4.2)

    @ 目录 1.分析 2.代码 关于作者 1.分析 随着网站的用户量越来愈多,通过多进程多线程的会力不从心 使用协程可以缓解这一问题 只要使用gevent实现 2.代码 from socket impo ...

  7. Web服务器-并发服务器-多进程(3.4.1)

    @ 目录 1.优化分析 2.代码 3. 关于作者 1.优化分析 在单进程的时候,相当于 是来一个客户,派一个人去服务一下 效率低,现在使用多进程来服务 假设场景 100个人同时访问页面 单进程:一次处 ...

  8. linux学习之多高并发服务器篇(一)

    高并发服务器 高并发服务器 并发服务器开发 1.多进程并发服务器 使用多进程并发服务器时要考虑以下几点: 父最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符) 系统内创建进程 ...

  9. 手把手让你实现开源企业级web高并发解决方案(lvs+heartbeat+varnish+nginx+eAccelerator+memcached)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://freeze.blog.51cto.com/1846439/677348 此文凝聚 ...

随机推荐

  1. Baby-step giant-step算法

    写在前面: 学习笔记,方便复习,学习资料来自网络,注明出处 我们都在努力奔跑,我们都是追梦人 结论 In group theory, a branch of mathematics, the baby ...

  2. 分块 && 例题 I Hate It HDU - 1754

    分块算法: 分块就是对暴力方法的一种优化:                          _ 假设我们总共的序列长度为n,然后我们把它切成√n 块,然后把每一块里的东西当成一个整体来看,完整块:被 ...

  3. Pdf和Office相关归集

    Spire 支持Pdf.Office等的诸多操作,使用方便,需收费,免费版本仅支持10页以内的操作,在 这里 可以下载库. 优点 测试过打印效果佳,操作简便. 缺点 PDF打印慢,免费版本仅支持10页 ...

  4. NFS 共享存储

    目录 环境准备 NFS服务端 NFS客户端 部署时常见报错 httpd服务 NFS 共享存储的坑 环境准备 主机名 WanIP(Wide Area Network) LanIP(Local Area ...

  5. js 拖拽排序

    See alsoe: https://www.runoob.com/html/html5-draganddrop.html https://developer.mozilla.org/zh-CN/do ...

  6. MySQL的简单使用方法备忘

    这只是一篇我的个人备忘录,写的是我常用的命令.具体可以参考"菜鸟教程" https://www.runoob.com/mysql/mysql-tutorial.html 登录(用户 ...

  7. 一文读懂网管协议 - SNMP,NETCONF,RESTCONF

    本文篇幅较长,主要涉及以下内容: 介绍传统 CLI 配置网络设备存在的挑战,网管协议出现的背景 SNMP 原理,交互过程,以及 trade-off NETCONF 架构,交互过程 RESTCONF 架 ...

  8. frame 和 iframe

    1.frame不能脱离frameSet单独使用,iframe可以: 2.frame不能放在body中:如下可以正常显示: <!--<body>--> <frameset ...

  9. Error Code: 1366. Incorrect DECIMAL value: '0' for column '' at row -1 0.266 sec;

    Reference: https://stackoverflow.com/questions/35037288/incorrect-decimal-integer-value-mysql     Er ...

  10. Python中的sklearn--KFold与StratifiedKFold

    KFold划分数据集的原理:根据n_split直接进行划分 StratifiedKFold划分数据集的原理:划分后的训练集和验证集中类别分布尽量和原数据集一样 #导入相关packages from s ...