先來看一個簡單的示例:
html
#!/usr/bin/env python #coding:utf8 import socket def run(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('127.0.0.1',8008)) sock.listen(5) while True: connection,address = sock.accept() handle_request(connection) connection.close() def handle_request(client): but = client.recv(1024) client.send("HTTP/1.1 200 OK \r\n\r\n") client.send("Hello everyone!") if __name__ == "__main__": run()
上述分析:python
一、瀏覽器其實就是一個socket客戶端,而web應用其實就是一個socket服務端,而且web應用在服務器上一直在監聽某個端口。ios
二、當瀏覽器請求某個web應用時,須要指定服務器的IP(DNS解析)和端口創建一個socket鏈接。web
三、創建連接後,web應用根據請求的不一樣,給用戶返回相應的數據。正則表達式
四、斷開socket鏈接。(之因此說http是短連接,其實就是由於每次請求完成後,服務器就會斷開socket連接)瀏覽器
對於Web框架來講,通常分爲兩類,其中一類則是包含上述 4部分 內容的框架,另一類就是隻包含 第3部分 功能的框架。tornado就是一中屬於前者的框架。tornado 是一個基於 Python 開發的web框架,較其餘 Web 框架的區別是:採用了非阻塞的方式和對epoll的應用。這意味着對於實時 Web 服務來講,Tornado 是一個理想的 Web 框架。服務器
tornado經典官網DEMO
app
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") application = tornado.web.Application([ (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
運行該腳本,依次執行:框架
建立一個Application對象,並把一個正則表達式'/'和類名MainHandler傳入構造函數:tornado.web.Application(...) dom
執行Application對象的listen(...)方法,即:application.listen(8888)
執行IOLoop類的類的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()
整個過程其實就是在建立一個socket服務端並監聽8888端口,當請求到來時,根據請求中的url和請求方式(post、get或put等)來指定相應的類中的方法來處理本次請求,在上述demo中只爲url爲http://127.0.0.1:8888/index的請求指定了處理類MainHandler。因此,在瀏覽器上訪問:http://127.0.0.1:8888/index,則服務器給瀏覽器就會返回 Hello,world ,不然返回 404: Not Found(tornado內部定義的值), 即完成一次http請求和響應。
tornado處理請求的步驟大致能夠分爲兩個部分:啓動程序階段和處理請求階段
一、在啓動程序階段,第一步,獲取配置文件而後生成url映射(即:一個url對應一個XXRequestHandler,從而讓XXRequestHandler來處理指定url發送的請求);第二步,建立服務器socket對象並添加到epoll中;第三步,建立無線循環去監聽epoll。
二、在接收並處理請求階段,第一步,接收客戶端socket發送的請求(socket.accept);第二步,從請求中獲取請求頭信息,再而後根據請求頭中的請求url去匹配某個XXRequestHandler;第三步,匹配成功的XXRequestHandler處理請求;第四步,將處理後的請求發送給客戶端;第五步,關閉客戶端socket。
啓動程序階段:
一、執行Application類的構造函數,並傳入一個列表類型的參數,這個列表裏保存的是url規則和對應的處理類,即:當客戶端的請求url能夠配置這個規則時,那麼該請求就交由對應的Handler(繼承自RequestHandler的全部類)去執行。
Application.__init__:
class Application(object): def __init__(self, handlers=None, default_host="", transforms=None,wsgi=False, **settings): #設置響應的編碼和返回方式,對應的http相應頭:Content-Encoding和Transfer-Encoding #Content-Encoding:gzip 表示對數據進行壓縮,而後再返回給用戶,從而減小流量的傳輸。 #Transfer-Encoding:chunck 表示數據的傳送方式經過一塊一塊的傳輸。 if transforms is None: self.transforms = [] if settings.get("gzip"): self.transforms.append(GZipContentEncoding) self.transforms.append(ChunkedTransferEncoding) else: self.transforms = transforms #將參數賦值爲類的變量 self.handlers = [] self.named_handlers = {} self.default_host = default_host self.settings = settings #ui_modules和ui_methods用於在模版語言中擴展自定義輸出 #這裏將tornado內置的ui_modules和ui_methods添加到類的成員變量self.ui_modules和self.ui_methods中 self.ui_modules = {'linkify': _linkify, 'xsrf_form_html': _xsrf_form_html, 'Template': TemplateModule, } self.ui_methods = {} self._wsgi = wsgi #獲取獲取用戶自定義的ui_modules和ui_methods,並將他們添加到以前建立的成員變量self.ui_modules和self.ui_methods中 self._load_ui_modules(settings.get("ui_modules", {})) self._load_ui_methods(settings.get("ui_methods", {})) #設置靜態文件路徑,設置方式則是經過正則表達式匹配url,讓StaticFileHandler來處理匹配的url if self.settings.get("static_path"): #從settings中讀取key爲static_path的值,用於設置靜態文件路徑 path = self.settings["static_path"] #獲取參數中傳入的handlers,若是空則設置爲空列表 handlers = list(handlers or []) #靜態文件前綴,默認是/static/ static_url_prefix = settings.get("static_url_prefix","/static/") #在參數中傳入的handlers前再添加三個映射: #【/static/.*】 --> StaticFileHandler #【/(favicon\.ico)】 --> StaticFileHandler #【/(robots\.txt)】 --> StaticFileHandler handlers = [ (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,dict(path=path)), (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)), (r"/(robots\.txt)", StaticFileHandler, dict(path=path)), ] + handlers #執行本類的Application的add_handlers方法 #此時,handlers是一個列表,其中的每一個元素都是一個對應關係,即:url正則表達式和處理匹配該正則的url的Handler if handlers: self.add_handlers(".*$", handlers) # Automatically reload modified modules #若是settings中設置了 debug 模式,那麼就使用自動加載重啓 if self.settings.get("debug") and not wsgi: import autoreload autoreload.start()
二、Application.add_Handlers:
class Application(object): def add_handlers(self, host_pattern, host_handlers): #若是主機模型最後沒有結尾符,那麼就爲他添加一個結尾符。 if not host_pattern.endswith("$"): host_pattern += "$" handlers = [] #對主機名先作一層路由映射,例如:http://www.5ihouse.com 和 http://safe.5ihouse.com #即:safe對應一組url映射,www對應一組url映射,那麼當請求到來時,先根據它作第一層匹配,以後再繼續進入內部匹配。 #對於第一層url映射來講,因爲.*會匹配全部的url,所將 .* 的永遠放在handlers列表的最後,否則 .* 就會先匹配了... #re.complie是編譯正則表達式,之後請求來的時候只須要執行編譯結果的match方法就能夠去匹配了 if self.handlers and self.handlers[-1][0].pattern == '.*$': self.handlers.insert(-1, (re.compile(host_pattern), handlers)) else: self.handlers.append((re.compile(host_pattern), handlers)) #遍歷咱們設置的和構造函數中添加的【url->Handler】映射,將url和對應的Handler封裝到URLSpec類中(構造函數中會對url進行編譯) #並將全部的URLSpec對象添加到handlers列表中,而handlers列表和主機名模型組成一個元祖,添加到self.Handlers列表中。 for spec in host_handlers: if type(spec) is type(()): assert len(spec) in (2, 3) pattern = spec[0] handler = spec[1] if len(spec) == 3: kwargs = spec[2] else: kwargs = {} spec = URLSpec(pattern, handler, kwargs) handlers.append(spec) if spec.name: #未使用該功能,默認spec.name = None if spec.name in self.named_handlers: logging.warning("Multiple handlers named %s; replacing previous value",spec.name) self.named_handlers[spec.name] = spec
三、URLspec:
class URLSpec(object): def __init__(self, pattern, handler_class, kwargs={}, name=None): if not pattern.endswith('$'): pattern += '$' self.regex = re.compile(pattern) self.handler_class = handler_class self.kwargs = kwargs self.name = name self._path, self._group_count = self._find_groups()
上述三個步驟主要完成了:
一、靜態文件路徑設置
二、ui_modules和ui_methods(模板語言中使用)
三、是否debug模式運行
四、生成URL映射
五、封裝數據,將配置信息和url映射關係封裝到Application對象中
六、保存編碼和返回方式信息(self.transforms)
七、self.settings 保存配置信息
八、self.Handler 保存着全部的主機名對應的Handlers,每一個handlers則是url正則對應的Handler
以上爲application = tornado.web.Application([(r"/index",MainHandler),])完成的工做
四、application.listen(8888)
1-3步的操做將配置和url映射等信息封裝到了application對象中,而這第二步執行application對象的listen方法,該方法內部又把以前包含各類信息的application對象封裝到了一個HttpServer對象中,而後繼續調用HttpServer對象的liseten方法。
class Application(object): #建立服務端socket,並綁定IP和端口並添加相應設置,注:未開始經過while監聽accept,等待客戶端鏈接 def listen(self, port, address="", **kwargs): from tornado.httpserver import HTTPServer server = HTTPServer(self, **kwargs) server.listen(port, address)
五、HTTPServer類代碼
class HTTPServer(object): def __init__(self, request_callback, no_keep_alive=False, io_loop=None,xheaders=False, ssl_options=None): #Application對象 self.request_callback = request_callback #是否長鏈接 self.no_keep_alive = no_keep_alive #IO循環 self.io_loop = io_loop self.xheaders = xheaders #Http和Https self.ssl_options = ssl_options self._socket = None self._started = False def listen(self, port, address=""): self.bind(port, address) self.start(1) def bind(self, port, address=None, family=socket.AF_UNSPEC): assert not self._socket #建立服務端socket對象,IPV4和TCP鏈接 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD) flags |= fcntl.FD_CLOEXEC fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags) #配置socket對象 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._socket.setblocking(0) #綁定IP和端口 self._socket.bind((address, port)) #最大阻塞數量 self._socket.listen(128) def start(self, num_processes=1): assert not self._started self._started = True if num_processes is None or num_processes <= 0: num_processes = _cpu_count() if num_processes > 1 and ioloop.IOLoop.initialized(): logging.error("Cannot run in multiple processes: IOLoop instance " "has already been initialized. You cannot call " "IOLoop.instance() before calling start()") num_processes = 1 #若是進程數大於1 if num_processes > 1: logging.info("Pre-forking %d server processes", num_processes) for i in range(num_processes): if os.fork() == 0: import random from binascii import hexlify try: # If available, use the same method as # random.py seed = long(hexlify(os.urandom(16)), 16) except NotImplementedError: # Include the pid to avoid initializing two # processes to the same value seed(int(time.time() * 1000) ^ os.getpid()) random.seed(seed) self.io_loop = ioloop.IOLoop.instance() self.io_loop.add_handler( self._socket.fileno(), self._handle_events, ioloop.IOLoop.READ) return os.waitpid(-1, 0) #進程數等於1,默認 else: if not self.io_loop: #設置成員變量self.io_loop爲IOLoop的實例,注:IOLoop使用methodclass完成了一個單例模式 self.io_loop = ioloop.IOLoop.instance() #執行IOLoop的add_handler方法,將socket句柄、self._handle_events方法和IOLoop.READ當參數傳入 self.io_loop.add_handler(self._socket.fileno(), self._handle_events, ioloop.IOLoop.READ) def _handle_events(self, fd, events): while True: try: #====important=====# connection, address = self._socket.accept() except socket.error, e: if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): return raise if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: #====important=====# connection = ssl.wrap_socket(connection,server_side=True,do_handshake_on_connect=False,**self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = iostream.SSLIOStream(connection, io_loop=self.io_loop) else: stream = iostream.IOStream(connection, io_loop=self.io_loop) #====important=====# HTTPConnection(stream, address, self.request_callback,self.no_keep_alive, self.xheaders) except: logging.error("Error in connection callback", exc_info=True)
六、IOloop類代碼:
class IOLoop(object): # Constants from the epoll module _EPOLLIN = 0x001 _EPOLLPRI = 0x002 _EPOLLOUT = 0x004 _EPOLLERR = 0x008 _EPOLLHUP = 0x010 _EPOLLRDHUP = 0x2000 _EPOLLONESHOT = (1 << 30) _EPOLLET = (1 << 31) # Our events map exactly to the epoll events NONE = 0 READ = _EPOLLIN WRITE = _EPOLLOUT ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP def __init__(self, impl=None): self._impl = impl or _poll() if hasattr(self._impl, 'fileno'): self._set_close_exec(self._impl.fileno()) self._handlers = {} self._events = {} self._callbacks = [] self._timeouts = [] self._running = False self._stopped = False self._blocking_signal_threshold = None # Create a pipe that we send bogus data to when we want to wake # the I/O loop when it is idle if os.name != 'nt': r, w = os.pipe() self._set_nonblocking(r) self._set_nonblocking(w) self._set_close_exec(r) self._set_close_exec(w) self._waker_reader = os.fdopen(r, "rb", 0) self._waker_writer = os.fdopen(w, "wb", 0) else: self._waker_reader = self._waker_writer = win32_support.Pipe() r = self._waker_writer.reader_fd self.add_handler(r, self._read_waker, self.READ) @classmethod def instance(cls): if not hasattr(cls, "_instance"): #單例模式 cls._instance = cls() return cls._instance def add_handler(self, fd, handler, events): """Registers the given handler to receive the given events for fd.""" self._handlers[fd] = stack_context.wrap(handler) self._impl.register(fd, events | self.ERROR)
上述代碼本質上就幹了如下這麼四件事:
把包含了各類配置信息的application對象封裝到了HttpServer對象的request_callback字段中
建立了服務端socket對象
單例模式建立IOLoop對象,而後將socket對象句柄做爲key,被封裝了的函數_handle_events做爲value,添加到IOLoop對象的_handlers字段中
向epoll中註冊監聽服務端socket對象的讀可用事件
目前,咱們只是看到上述代碼大體幹了這四件事,而其目的有什麼?他們之間的聯繫又是什麼呢?
答:如今不妨先來作一個猜測,待以後再在源碼中確認驗證是否正確!猜測:經過epoll監聽服務端socket事件,一旦請求到達時,則執行3中被封裝了的_handle_events函數,該函數又利用application中封裝了的各類配置信息對客戶端url來指定斷定,而後指定對應的Handler處理該請求。