msdn windows server 按电源事件api_【tornado源码分析】I/O事件循环机制与多进程

tornado是一个异步非阻塞的web框架,由Facebook开源,源代码用的python语言。之前也用tornado做过几个项目,实习的时候公司也是用的tornado来做工业物联网的后端。空余时间研究一下tornado的源码,毕竟是Facebook的代码,还是有学习的价值。以下为学习的一点笔记。


一.socket编程

现代计算机网络中使用TCP/IP协议架构:物理链路层,ip层,TCP/UDP层,应用层。

6662fa53da024f3d6c882c72afa5b598.png

socket层则是对TCP/UDP层的一层简要封装,能够方便开发者定义自己的高层应用协议。在python中,使用内置的socket模块能够进行socket编程。该模块提供的api有:

29bcb16eab152e181e9bd5715ac32780.png

5345e5e73583570e0cfab64ef4661753.png

利用提供的这些基础api,能够很方便的自行编写高层协议,比如,你可以自己实现一个HTTP server。 HTTP协议基于TCP协议,是面向连接的,构造一条虚电路,保证数据包的顺序,不丢包,可靠的发送。基于TCP的socket逻辑架构如下:

34328642240f3206b9342dc51dc6df69.png

socket在类unix系统中均可以看成是一个特殊文件,

sock=socket.socket()

返回一个句柄sock,sock.fileno()即为该句柄的编号,为int类型。考虑一下在tornado中tcpserver如何在ioloop注册事件。在ioloop中提供了一个方法:

def add_handler(  # noqa: F811
        self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int
    ) -> None

add_handler函数用来绑定socket对应的回调函数,具体来说:当服务器端socket对应的’读事件‘(IOOP.READ,即为参数event)被触发时,将调用handler函数进行处理。以上理解比较抽象,下面写个例子,理解下:

首先写个server:

from tornado import httpserver, ioloop, tcpserver
import socket

def a(A, B):
    print("====开始测试=====")
    print(A)
    print(B)
    print("我是服务器socket的回调函数")

if __name__ == '__main__':
    loop = ioloop.IOLoop.current()
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('127.0.0.1', 8881))
    loop.add_handler(server.fileno(), a, ioloop.IOLoop.READ)
    server.listen()
    #conn, addr = server.accept()
    #a = conn.recv(1024)
    #print("服务器收到的数据", repr(a))
    #loop.spawn_callback(c, conn)
    loop.start()

以上server用socket编程来实现,功能为:首先定义一个socket,变量名为server,该socket绑定在8881端口上。然后我们利用add_handler函数将该socket注册到ioloop上,注册的这一步在平时的开发中的写法为:

server = TCPServer()
            server.bind(8881)
            server.start(0)  # Forks multiple sub-processes
            IOLoop.current().start()

在注册该socket之后,ioloop.start()开始事件循环。

再写一个client:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 8881))
sock.sendall(b"hello,worldfwefewfwefwefwefwefwefwefw")
print(sock.fileno())
# a = sock.recv(1024)
# print(repr(a))
sock.close()

该client,依然是创建一个socket,然后请求连接8881端口的服务(模拟发送HTTP请求)。当该数据被发送之后,可以看见server端的输出为:

99e4906581394e6d6f56ff33728dd889.png

可以看见ioloop监听到了client对server发送的请求,进行了回调。具体来说:client向server发送了一个请求,server端的socket在收到该请求后,会触发READ事件,此时ioloop会触发该socket对应的回调函数。


二.tornado中的多进程

对于tornado这种单线程的web框架,如果遇到cpu bound类型的任务,虽然及时能够通过coroutine模式保证不会丢失服务,但是由于CPU被占用,所以响应时间一点也不会减少。此时可以考虑使用多进程tornado,或者多tornado实例,然后再用nginx做负载均衡(这个还没试过)。

我们通过time.sleep(20)来模拟CPU的计算过程,原因是在这20s内,server不能响应其他用户的请求(cpu一直在计算)。

代码如下:

class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        print("睡眠10s")
        time.sleep(20)
        print("pid", os.getpid())
        print("睡眠结束")
        self.write(str(os.getpid()))


class WebHandler(tornado.web.RequestHandler):
    async def get(self):
        print("hello,world123")


def make_app():
    return tornado.web.Application([
        tornado.web.url(r"/123", MainHandler, name="hello"),
        tornado.web.url(r"/456", WebHandler, name="web"),
    ])
if __name__ == "__main__":
    app = make_app()
    # server = tornado.httpserver.HTTPServer(app)
    # server.bind(8888, '127.0.0.1')
    # server.start(0)
    sockets = tornado.netutil.bind_sockets(8888)
    tornado.process.fork_processes(2)
    server = tornado.httpserver.HTTPServer(app)
    server.add_sockets(sockets)
    loop1 = tornado.ioloop.IOLoop()
    loop = loop1.current()
    # loop.add_handler(1, a, 0x001)
    # loop.add_handler(2, b, 0x004)
    # loop.add_callback(d)
    # print(loop1._ioloop_for_asyncio)
    loop.start()

由于我的机器有四个核,所以输出如下:

9b8f403b91da2eeb9405211bd7435c26.png

在tornado中,多进程是通过os.fork函数来实现(windows无法实现)。父进程在fork子进程之后,子进程会和父进程同时执行剩下的代码,需要注意os.fork有两个返回值,如果返回值为0,则表明当前这个进程是子进程,如果返回值为int,则int是子进程的pid号。那么通过这种方式在执行cpubound的任务时,可以充分利用多核。在tornado中的内部实现为tornado.netutil.add_accept_handler函数:

def add_accept_handler(
    sock: socket.socket, callback: Callable[[socket.socket, Any], None]
) -> Callable[[], None]:
    io_loop = IOLoop.current()
    removed = [False]

    def accept_handler(fd: socket.socket, events: int) -> None:
        for i in range(_DEFAULT_BACKLOG):
            if removed[0]:
                # The socket was probably closed
                return
            try:
                connection, address = sock.accept()
            except socket.error as e:
                if errno_from_exception(e) in _ERRNO_WOULDBLOCK:
                    return
                if errno_from_exception(e) == errno.ECONNABORTED:
                    continue
                raise
            set_close_exec(connection.fileno())
            callback(connection, address)

    def remove_handler() -> None:
        io_loop.remove_handler(sock)
        removed[0] = True

    io_loop.add_handler(sock, accept_handler, IOLoop.READ)
    return remove_handler

关键在于内部函数accept_handler,当使用多进程时会产生惊群效应,那么tornado是如何防止这种情况的呢?关键在:

    if errno_from_exception(e) in _ERRNO_WOULDBLOCK:

在异步函数中(errno.EWOULDBLOCK,errno.EAGAIN)可以不被当做错误,用来避免客户端的socket被重复处理。


欢迎关注微信公众号:生物信息与python,及时更新与分享~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值