一、前述
在IOStream之上,区分为服务端和客户端的不同实现,对于服务端,实现方向是TCPServer–> HTTPServer --> web应用实现;对于客户端,实现方向是TCPClient --> HTTPClient;在本系列的源码分析上,优先以服务器方向为准。
TCPServer类的使用方法是,继承该类并实现其中的handle_stream方法,这个handle_stream方法,就是对于的具体的网络连接,被包装实例化为IOStream(或SSLIOStream)后,协议层(如http层)需要完成的实现工作。
对于TCPServer类的初始化,有三种方法,这里先简单过一个眼熟,知道有这三种形式即可。
- 方法一:适用于简单的单进程程序
server = TCPServer() # 初始化一个实例
server.listen(8888) # 设置监听端口和添加IOLoop监听事件和回调处理函数,以便于等待处理客户端的连接
IOLoop.current().start() # IOLoop开启事件监听,这里才是真正的开始了网络监听
- 方法二:使用bind/start方法,适用于简单的多进程程序
server = TCPServer()
# 区别于方法一,是把listen方法,拆分成bind/start方法组合,其中,对于start方法,可以通过参数指定
# 需要生成的子进程的数量,在该方法内部,会调用process.fork_processes去生成子进程。
server.bind(8888)
server.start(0)
IOLoop.current().start()
- 方法三:拆分socket的创建绑定过程和多进程的使用,适用于需要灵活创建sockets的高级多进程程序
sockets = bind_sockets(8888)
tornado.process.fork_processes(0)
server = TCPServer()
server.add_sockets(sockets)
IOLoop.current().start()
通过进一步分析源码可以知道的是,对于TCPServer的初始化过程,核心相关的方法调用主要是涉及了
tornado.netutil.bind_sockets方法和tornado.netutil.add_accept_handler方法,对于TCPServer内部相关方法,则涉及到_handle_connection方法和预留给上层协议层定义的handle_stream方法。在下面的分析中,我们重点以方法一的初始化形式来查看这些核心方法是如何被组合在一起的。
二、TCPServer.listen方法
def listen(self, port: int, address: str = "") -> None:
"""Starts accepting connections on the given port.
This method may be called more than once to listen on multiple ports.
`listen` takes effect immediately; it is not necessary to call
`TCPServer.start` afterwards. It is, however, necessary to start
the `.IOLoop`.
"""
# 直白的翻译下上面的函数说明
# 在特定的端口上,开始监听接受网络连接
# 本方法可以被多次调用,以便于监听多个不同的端口。本方法一旦调用后,监听功能是立即生效的,不需要
# 再去调用TCPServer.start方法,但是,在调用本方法后,开启IOLoop事件循环监听是必须要有的。
sockets = bind_sockets(port, address=address)
self.add_sockets(sockets)
代码也是简单,就是通过调用bind_sockets,解析获取特定端口地址相关的socket事例,再进一步通过
add_sockets方法添加对这些sockets的监听处理过程。
查看bind_sockets方法,可以看到其内部是很经典的socket创建过程,解析获取特定端口地址信息,并依次在对应的端口和地址上创建网络socket,设置该socket的网络选项、绑定端口地址、设置监听长度等工作。可以看出,到这里,只是创建了相应的socket,但是还没有添加这些socket的网络事件监听。
1 socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags)
获取与给定的端口地址相关的所有网络地址信息
2 sock = socket.socket(af, socktype, proto)
对于每个网络地址,创建一个网络socket
3 sock.setsockopt
设置相应的网络选项,如端口可利用socket.SO_REUSEPORT等
4 sock.bind(sockaddr)
每个sock实例,绑定特定的端口地址
5 sock.listen(backlog)
设置监听长度
6 sockets.append(sock),最后return sockets
而self.add_sockets方法,就是对这些sockets添加相应的网络事件监听。
def add_sockets(self, sockets: Iterable[socket.socket]) -> None:
"""Makes this server start accepting connections on the given sockets.
"""
for sock in sockets:
# self._sockets字典,保存每个sock实例
self._sockets[sock.fileno()] = sock
# self._handlers字典,保存每个socket实例的处理函数,这个处理函数主要是用在
# selt.stop方法中,用于把对应的sock从IOLoop循环中去掉事件监听。
self._handlers[sock.fileno()] = add_accept_handler(
sock, self._handle_connection
)
从def add_accept_handler(sock: socket.socket, callback: Callable[[socket.socket, Any], None])的定义中可以看出,就是给定一个socket和在该socket有网络连接上时,一个回调处理函数。接下来我们先看下add_accept_handler内部实现。
def add_accept_handler(
sock: socket.socket, callback: Callable[[socket.socket, Any], None]
) -> Callable[[], None]:
_loop = IOLoop.current()
removed = [False]
# 内部定义了一个sock的处理accept的回调函数,用于io loop的事件监听注册
def accept_handler(fd: socket.socket, events: int) -> None:
for i in range(_DEFAULT_BACKLOG):
if removed[0]:
return
try:
connection, address = sock.accept()
except BlockingIOError:
return
except ConnectionAbortedError:
continue
set_close_exec(connection.fileno())
# 一旦获取连接了客户端的connection连接,提交给原来传参进来的回调处理函数去处理
# 该连接后续的事情,如数据的读写等。
callback(connection, address)
# 内部定义一个去掉网络监听注册的函数,外部程序可以在适当调用,去掉该sock的网络监听功能。
def remove_handler() -> None:
io_loop.remove_handler(sock)
removed[0] = True
# 对该sock向io loop循环添加一个可读状态的事件监听,在有客户端连接上来时,会触发可读状态,从而回调
# accept_handler函数,而accept_handler在成功获取了客户端的socket连接后,
# 提交给callback(connection, address),由上层进一步处理客户端的网络连接。
io_loop.add_handler(sock, accept_handler, IOLoop.READ)
return remove_handler
概括一下add_accept_handler的异步处理过程:
(1)对给定的sock,向io loop事件循环中,设置监听读状态,并添加一个内部定义的回调处理函数accept_handler,功能就是接收客户端的连接。
(2)返回一个内部定义的函数对象remove_handler,由外层在适当的时候调用,去掉该sock的io loop监听。
现在再看下add_sockets中对add_accept_handler的调用。
self._handlers[sock.fileno()] = add_accept_handler(
sock, self._handle_connection)
可以看到,对于返回的函数对象,统一保存在self._handlers字典中,留在后续调用(其实就是stop方法),再看下TCPServer中在接收一个客户端连接时的回调处理函数self._handle_connection。
try:
# 前面是对SSL的连接的处理
# 对于连接上来的客户端连接connection,依据TCPServer是否需要SSL连接,实例化出IOStream类或者SSLIOStream类对象。
if self.ssl_options is not None:
stream = SSLIOStream(
connection,
max_buffer_size=self.max_buffer_size,
read_chunk_size=self.read_chunk_size,
) # type: IOStream
else:
stream = IOStream(
connection,
max_buffer_size=self.max_buffer_size,
read_chunk_size=self.read_chunk_size,
)
# 调用self.handle_stream处理,注意,这个方法对于TCPServer是缺省的,需要由其子类来实现。
# 也就是说,走到调用这一步时,如何进一步处理客户端连接的IOStream实例,则子类自行定义,把控制权交给
# 子类了。
future = self.handle_stream(stream, address)
# 如何返回的是异步的future对象,则添加到io loop循环事件中,异步执行。
if future is not None:
IOLoop.current().add_future(
gen.convert_yielded(future), lambda f: f.result()
)
except Exception:
app_log.error("Error in connection callback", exc_info=True)