import socket, sys, time, re, select
class WSGIServer(object):
def __init__(self):
# 创建套接字
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 地址复用
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定地址和端端口
self.s.bind(("", 8080))
# 主动连接改为被动监听
self.s.listen(128)
# 创建epoll对象
self.epoll = select.epoll()
# 将tcp服务器套接字加入到epoll中监听
self.epoll.register(self.s.fileno(), select.EPOLLIN|select.EPOLLET)
# 创建添加的fd对应的套接字
self.fdSocket = dict()
def runServer(self):
while True:
# 扫描中epoll
epollList = self.epoll.poll()
# 对事件进行判断
for fd, event in epollList:
# 判断服务器套接字可以接受数据,就进行accept
if fd == self.s.fileno():
# 接受客户端的数据和地址端口
newSocket, _ = self.s.accept()
# 向epoll中注册newsocketde可读事件
self.epoll.register(newSocket.fileno(), select.EPOLLIN|select.EPOLLET)
# 记录这个信息
self.fdSocket[newSocket.fileno()] = newSocket
# 判断数据是可读的
elif event == select.EPOLLIN:
# 接受数据
request = self.fdSocket[fd].recv(1024).decode("utf-8")
# 判断数据存在
if request:
# 处理存在的数据
self.dealWithRequest(request, self.fdSocket[fd])
else:
# 关闭数据
self.epoll.unregister(fd)
# 关闭字典中存储的数据
self.fdSocket[fd].close()
# 删除连接
del self.fdSocket[fd]
def dealWithRequest(self, request, newSocket):
# 数据不存在直接返回
if not request:
return
# 对key进行行切片
requestList = request.splitlines()
# 列出行表,并进行遍历
for i, line in enumerate(requestList):
print(i, line)
# 正则匹配第一行
ret = re.match(r"([^/]*)([^ ]+)", requestList[0])
# 判断数据
if ret:
# print("正则提取数据:", ret.group(1))
# print("正则提取数据:", ret.group(2))
# 判断网站根目录
fileName = ret.group(2)
if fileName == "/":
fileName = "/index.html"
# 异常处理
try:
# 打开文件
f = open(self.documentsRoot + fileName, "rb")
except:
# 打开文件失败就返回以下代码
responseBody = "file not found, 请输入正确的url"
responseHeader = "HTTP/1.1 404 not found\r\n"
responseHeader += "Content-Type: text/html; charset=utf-8\r\n"
responseHeader += "Content-Length: %d\r\n" % len(responseBody)
responseHeader += "\r\n"
response = responseHeader + responseBody
newSocket.send(response.encode("utf-8"))
else:
# 成功打开文件就执行以下代码
# 读取数据
content = f.read()
关闭文件
f.close()
# 读取的数据赋值给请求体
responseBody = content
responseHeader = "HTTP/1.1 200 Ok\r\n"
responseHeader += "Content-Length: %d\r\n" % len(responseBody) + "\r\n"
# 拼接请求头和请求提
response = responseHeader + responseBody
# 向客户端发送数据
newSocket.send(response.encode("utf-8"))
def main():
http = WSGIServer()
http.runServer()
if __name__ == "__main__":
main()
EPOLLIN (可读)
EPOLLOUT (可写).
EPOLLET (ET模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式。
LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
I/O 多路复用的特点:
通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 '监视' 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。
当然也可以多线程/多进程方式,一个连接过来开一个进程/线程处理,这样消耗的内存和进程切换页会耗掉更多的系统资源。 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给进程池/线程池来处理逻辑
参考资料:http://blog.youkuaiyun.com/xiajun07061225/article/details/9250579