这是一个 Epoll 服务器实现(非阻塞)
http://pastebin.com/vP6KPTwH (下同,觉得这可能更容易复制)
用于python epollserver.py
启动服务器。
使用它进行测试wget localhost:8888
导入系统
导入套接字,选择
导入 fcntl
导入 email.parser
导入字符串IO
导入日期时间
"""
看:
http://docs.python.org/library/socket.html
"""
__author__ = ['Caleb Burns', 'Ben DeMott']
定义主(argv=无):
EOL1 = '\n\n'
EOL2 = '\n\r\n'
response = 'HTTP/1.0 200 OK\r\n日期:星期一,1996 年 1 月 1 日 01:01:01 GMT\r\n'
response += '内容类型:文本/纯文本\r\n内容长度:13\r\n\r\n'
响应 += '你好,世界!'
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 告诉服务器套接字文件描述符在程序结束时销毁自己。
socketFlags = fcntl.fcntl(serversocket.fileno(), fcntl.F_GETFD)
socketFlags |= fcntl.FD_CLOEXEC
fcntl.fcntl(serversocket.fileno(),fcntl.F_SETFD,socketFlags)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8888))
serversocket.listen(1)
# 使用异步套接字。
serversocket.setblocking(0)
# 允许最多 128 个请求(连接)的队列。
serversocket.listen(128)
# 在上面的 bind() 调用定义的服务器套接字上监听套接字事件。
epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN)
print "Epoll 服务器已启动..."
尝试:
#连接字典将文件描述符(整数)映射到其对应的网络连接对象。
连接 = {}
请求 = {}
响应 = {}
而真:
# 询问 epoll 是否有任何套接字有事件,如果没有事件则等待最多 1 秒。
事件 = epoll.poll(1)
# fileno 是一个文件描述符。
# event 是事件代码(类型)。
对于文件号,事件中的事件:
# 检查套接字上的读取事件,因为可能存在新连接。
如果 fileno == serversocket.fileno():
# 连接是一个新的套接字对象。
#地址是客户端IP地址。地址的格式取决于套接字的地址族(即AF_INET)。
连接,地址 = serversocket.accept()
# 将新的套接字连接设置为非阻塞模式。
连接.setblocking(0)
# 监听新套接字连接上的读取事件。
epoll.register(connection.fileno(), select.EPOLLIN)
连接[connection.fileno()] = 连接
请求[connection.fileno()] = b''
响应[connection.fileno()] = 响应
# 如果发生读取事件,则读取客户端发送的新数据。
elif 事件 & select.EPOLLIN:
请求[文件号] += 连接[文件号].recv(1024)
# 一旦我们完成读取,停止侦听读取事件并开始侦听 EPOLLOUT 事件(这将告诉我们何时可以开始将数据发送回客户端)。
如果请求 [fileno] 中的 EOL1 或请求 [fileno] 中的 EOL2:
epoll.modify(fileno, select.EPOLLOUT)
# 将请求数据打印到控制台。
epoll.modify(fileno, select.EPOLLOUT)
数据=请求[文件号]
eol = data.find("\r\n") #这是第一行的结尾
start_line = data[:eol] #获取第一行的内容(也就是协议信息)
# 方法是 POST|GET 等
方法, uri, http_version = start_line.split(" ")
# 重用 facebooks httputil 库(可以很好地规范化和解析标头)
标头 = HTTPHeaders.parse(数据[eol:])
print "\nCLIENT: FD:%s %s: '%s' %s" % (fileno, method, uri, datetime.datetime.now())
# 如果客户端准备好接收数据,则发送响应。
elif 事件和 select.EPOLLOUT:
# 一次发送一个响应,直到发送完整的响应。
# 注意:这是我们将使用 sendfile() 的地方。
byteswritten = connections[fileno].send(responses[fileno])
响应[fileno] = 响应[fileno][byteswritten:]
如果 len(responses[fileno]) == 0:
# 告诉套接字我们不再对读/写事件感兴趣。
epoll.modify(fileno, 0)
# 告诉客户端我们已经完成发送数据,它可以关闭连接。(好形式)
连接[fileno].shutdown(socket.SHUT_RDWR)
# EPOLLHUP(挂起)事件意味着客户端已断开连接,因此清理/关闭套接字。
elif 事件 & select.EPOLLHUP:
epoll.unregister(fileno)
连接[文件号].close()
删除连接[文件号]
最后:
# 程序完成后关闭剩余的打开套接字。
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()
#!/usr/bin/env python
#
# 版权所有 2009 Facebook
#
# 根据 Apache 许可证 2.0 版(“许可证”)获得许可;您可以
# 除非符合许可证,否则不要使用此文件。您可以获得
#许可证的副本在
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则软件
# 根据许可分发是在“原样”基础上分发的,没有
# 任何明示或暗示的保证或条件。见
# 特定语言管理权限和限制的许可证
# 根据许可证。
"""客户端和服务器共享的 HTTP 实用程序代码。"""
类 HTTPHeaders(dict):
"""为所有键维护 Http-Header-Case 的字典。
通过一对新方法支持每个键的多个值,
add() 和 get_list()。正则字典接口返回单个
每个键的值,多个值用逗号连接。
>>> h = HTTPHeaders({"content-type": "text/html"})
>>> h.keys()
['内容类型']
>>> h[“内容类型”]
'文本/html'
>>> h.add("Set-Cookie", "A=B")
>>> h.add("Set-Cookie", "C=D")
>>> h["set-cookie"]
'A=B,C=D'
>>> h.get_list("set-cookie")
['A=B', 'C=D']
>>> for (k,v) in sorted(h.get_all()):
... 打印 '%s: %s' % (k,v)
...
内容类型:文本/html
设置 Cookie:A=B
设置 Cookie:C=D
"""
def __init__(self, *args, **kwargs):
# 不要将 args 或 kwargs 传递给 dict.__init__,因为它会绕过
# 我们的 __setitem__
dict.__init__(self)
self._as_list = {}
self.update(*args, **kwargs)
# 新的公共方法
def add(self, name, value):
"""为给定的键添加一个新值。"""
norm_name = HTTPHeaders._normalize_name(名称)
如果在 self 中的 norm_name:
# 绕过我们对 __setitem__ 的覆盖,因为它修改了 _as_list
dict.__setitem__(self, norm_name, self[norm_name] + ',' + value)
self._as_list[norm_name].append(value)
别的:
自我[规范名称] = 价值
def get_list(自我,姓名):
"""将给定标头的所有值作为列表返回。"""
norm_name = HTTPHeaders._normalize_name(名称)
return self._as_list.get(norm_name, [])
def get_all(self):
"""返回所有(名称,值)对的可迭代对象。
如果一个标题有多个值,多对将是
以相同的名称返回。
"""
对于名称,请在 self._as_list.iteritems() 中列出:
对于列表中的值:
产量(名称,值)
def 项目(自己):
返回 [{key: value[0]} for key, value in self._as_list.iteritems()]
def get_content_type(self):
返回 dict.get(self, HTTPHeaders._normalize_name('content-type'), None)
def parse_line(self, line):
"""使用单个标题行更新字典。
>>> h = HTTPHeaders()
>>> h.parse_line("Content-Type: text/html")
>>> h.get('内容类型')
'文本/html'
"""
名称,值 = line.split(":", 1)
self.add(名称,value.strip())
@classmethod
def 解析(cls,标题):
"""从 HTTP 标头文本返回字典。
>>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n")
>>> 排序(h.iteritems())
[('Content-Length', '42'), ('Content-Type', 'text/html')]
"""
h = cls()
对于 headers.splitlines() 中的行:
如果行:
h.parse_line(线)
返回 h
# dict 实现覆盖
def __setitem__(self, name, value):
norm_name = HTTPHeaders._normalize_name(名称)
dict.__setitem__(self, norm_name, value)
self._as_list[norm_name] = [值]
def __getitem__(self, name):
返回 dict.__getitem__(self, HTTPHeaders._normalize_name(name))
def __delitem__(self, name):
norm_name = HTTPHeaders._normalize_name(名称)
dict.__delitem__(self, norm_name)
del self._as_list[norm_name]
def get(self, name, default=None):
返回 dict.get(self, HTTPHeaders._normalize_name(name), 默认)
def 更新(自我,*args,**kwargs):
# dict.update 绕过我们的 __setitem__
对于 dict(*args, **kwargs).iteritems() 中的 k,v:
自我[k] = v
@静态方法
def _normalize_name(名称):
"""将名称转换为 Http-Header-Case。
>>> HTTPHeaders._normalize_name("内容类型")
'内容类型'
"""
return "-".join([w.capitalize() for w in name.split("-")])
如果(__name__ == '__main__'):
sys.exit(主要(sys.argv))