Tornado 系列学习笔记(一)

本文是Tornado学习笔记的第一部分,主要介绍了Tornado的应用场景,特别是针对C10k问题的解决方案。讨论了Tornado的异步特性,以及epoll在系统层网络通信中的作用。此外,还详细阐述了通过HTTP协议传递参数的不同方式,以及在处理请求参数时的注意事项。同时,文章提及了Tornado的RequestHandler类的关键方法和特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

友情提示:
1.文中大多是总结性的文字, 涉及很多省略的前置基础知识,可以自行搜索酌情补充;
2.另,个人水平有限,难免出错误导,请谨慎阅读
3.可以参考github上详细的代码笔记, 一定会有收获

谈谈我在学习Tornado时的一些理解:

1.应用场景和C10k问题

1.常见的两种不适合采用多线程的应用场景:

  • 1.用户量大,高并发; (如, 秒杀|抢票)
  • 2.大量的HTTP持久连接; (有些网页"挂机"行为)
    2.C10k(略,现在早已开始探讨C10M等了)

2.Tornado 特点:

  • 1…一个轻量级的Web框架,其拥有异步非阻塞IO的处理方式。类似:Python web框架Web.py
  • 2.作为Web服务器,Tornado有较为出色的抗负载能力.
  • 3.诞生是为了提供一种 C10K问题 的解决方案, 注重性能
  • 4.Tornado框架和服务器一起组成一个WSGI的全栈替代品。单独在WSGI容器中使用tornado网络框架或者tornaod http服务器,有一定的局限性,为了最大化的利用tornado的性能,官方推荐同时使用tornaod的网络框架和HTTP服务器.(注: 生产环境亦如此建议)
  • 5.目前其他两大主流python web框架逐步的往支持异步的方向发展,且python3.x的版本开始提供异步的实现,这些都开始挤压Tornado的市场;

3.关于epoll

epoll 是一种实现 系统层网络通信的 解决方案.相比之前的解决方案, epoll有两大特点:

  • 1.使用事件通知来代替遍历轮询;(只通知那些活跃的socket相关信号)
  • 2.通过文件描述符fd来标记socket,降低复制socket资源的开销;
  • 3.是Linux 内核2.6+之后才具有的功能, 利用CPU和内存共享的那一块儿特殊内存来实现底层事件通知.
  • 4.epoll主要是用来解决网络IO的并发问题,所以Tornado的异步编程也主要体现在网络IO的异步上,即异步Web请求。

4.利用HTTP协议向服务器传递参数有那几种途径?

  • 1.URL路径参数;
  • 2.URL查询参数;
  • 3.请求头;(cookies, 一般会把cookies单独拿出来讲解)
  • 4.请求体;(一般又细分为: 1.普通表单,媒体文件,原生字符串; 2.以及form表单, json,xml; 可能处理起来有细微差别, django和tornado都是)
    补充: 1.多值参数取值的问题? 取单个存在后面覆盖前面的特点; 一般取多个和取单个会提供不同的方法;

5.关于传递参数中的几个小问题:

  • 1.什么时候设置default,什么时候不设置default?

具体看需求. 比如,如果是对返回的列表进行分页,前端没有传递per_page或page_num等约定的参数的时候, 我们应该设置默认值.
当某些参数是前端必传的,而没有传递的时候我们可以采用不设置默认值转而抛出异常的方式,进入异常处理的页面逻辑, 比如404自定义页面;

  • 2.default的默认值_ARG_DEFAULT是什么?

翻看源码发现: _ARG_DEFAULT=object() , 至于为什么这样设置呢? 主要是为了后面在方法内部进行逻辑校验等处理的时候,能更好的区分
该内容是由前端参数传递过来的呢还是该方法取到的默认值. 所以必须要 _ARG_DEFAULT 和 传过来的arg 有一定的区分度. 但是, 我们知道
我们没法控制前端通过该参数传递什么值到后端, 但是又要做到区分两者是来自哪里. 所以解决方案一般是: is 身份判断符来判断. 所以, 这里就
不能采用 不可变类型来做区分.

  • 3.什么时候使用strip,亦即什么时候要截断空白字符,什么时候不需要?

当对传入参数值包含空格\t\tab等空白字符需要忽略的时候则需要截断, 处理误输入;
当对传入的参数值有严格完整性的要求时,就不截断空白字符.

6.其他请求信息(一般python web都会对一些常用的特殊的请求信息, 做一个单独的归类)

RequestHandler.request 对象存储了关于请求的相关信息,具体属性有:

  • method HTTP的请求方式,如GET或POST;
  • host 被请求的主机名;
  • uri 请求的完整资源标示,包括路径和查询字符串;
  • path 请求的路径部分;
  • query 请求的查询字符串部分;
  • version 使用的HTTP版本;
  • headers 请求的协议头,是类字典型的对象,支持关键字索引的方式获取特定协议头信息,例如:request.headers["Content-Type"]
  • body 请求体数据;
  • remote_ip 客户端的IP地址;
  • files 用户上传的文件,为字典类型
{
  "form_filename_1":[<tornado.httputil.HTTPFile>, <tornado.httputil.HTTPFile>],
  "form_filename_2":[<tornado.httputil.HTTPFile>,],
}

tornado.httputil.HTTPFile是接收到的文件对象,它有三个属性:

  • filename 文件的实际名字,与form_filename1不同,字典中的键名代表的是表单对应项的名字;
  • body 文件的数据实体;
  • content_type 文件的类型。 这三个对象属性可以像字典一样支持关键字索引,如request.files["form_filename1"][0]["body"]

7.关于字符串文本和二进制文本在不同操作系统下处理的情况

  • 1.python写入文件的操作 mode 有 wb 和 w两种.
  • 2.在不同OS下有不同的方式表示换行符, Linux(\n), MacOS(\n, 备注:之前是\r), Windows(\r\n)
  • 3.所以在二进制写的情况下, 不同操作系统都会把\n解析成\n本身, 这样导致 aaa\nbbb 这样的写法在windows下也是不换行的.
  • 4.在普通文本写的情况下, windows看到\n的时候自动解析成\r\n, 会有换行的效果.
  • 5.可以在不同OS下使用python解释器来验证上述情况

补充: python操作文件, 在调用 close的方法的时候才将内存中的内容写入磁盘保存起来; 但是我们可以调用flush方法,在一个文件句柄在写的时候, 另外一个文件句柄就可以同步查看了.

8.tornado的RequestHandler类封装方法的一些说明

  • 1.self.write(chunk)方法
    • 1.self.write(chunk)该方法是默认封装的方法, 不过该方法的作用是将 chunk 数据写入 缓冲区, 并不是直接做为response,这一点要注意;
    • 2.基于上述1的描述, 可以推出在同一个请求的method中可以多次(连续或非连续)使用 write 方法来追加数据;
    • 3.那么多次追加的时候是否有先后顺序呢? 答案显然是有, 但是需要先划分write是写入响应头还响应体.
      • 3.1 对于写入响应头write(header)或响应体write(body)的数据,write的先后顺序并不会影响最终整个响应报文的顺序.
# 1 写法一
write(header)
write(body)
# 2 写法二
write(body)
write(header)
# 注: 这两者的先后对报文最终"组装"并无影响;
    - 3.2 对于写入响应头不同item的顺序, 会影响报文的顺序
        # 1.写法一
          write(header1)
          write(header2)
        # 2.写法二
          write(headr2)
          write(header2)
    - 3.2 对于写入响应体不同item的顺序, 会影响报文的顺序
        # 略
  • 2.操作json数据(最好的方式, 看write源码)

    • 1.可以直接给write传入dict, 会自动帮我们设置响应头的Content-Type: application/json; charset=UTF-8
    • 2.如果手动转换成json数据然后传入write, 则设置响应头的Content-Type: text/html; charset=UTF-8
    • 3.顺便补充一下, 平时我们更多的时候只关注Content-Type的前半部分, 但是不要忘了 charset=UTF-8 也可能是 Content-Type一部分;
    • 4.再补充一下, 一般说响应状态码,我们更多的是关注 code的number部分, 但是Enum Name,如400 BAD_REQUEST才是完整的
  • 3.set_header(name, value)set_default_headers()

    • 3.1 注意:在HTTP处理方法中使用set_header()方法会覆盖掉在set_default_headers()方法中设置的同名header。
  • 4.set_status(status_code, reason=None)

    • 4.1 当前浏览器比较智能,在自定义不存在的状态码的时候, 浏览器会补充上 xxx Unknown
    • 4.2 两个参数, 一个是 int, 一个是 str
  • 5.redirect

  • 6.send_error(status_code=500, **kwargs)

    • 抛出HTTP错误状态码status_code,默认为500,kwargs为可变命名参数。使用send_error抛出错误后tornado会调用write_error()方法进行
      处理,并返回给浏览器处理后的错误页面。

小部分代码示例, 更多代码请点击这里:

# file: 08-response_.py
# *-* coding:utf8 *-*
"""
涉及知识点:
1.write顺序的讲解
2.操作json响应
3.设置响应头
4.状态码

5.redirect, 访问: /login/
6.重点: 异常处理

"""


import json

import tornado.web
import tornado.httpserver
import tornado.ioloop
from tornado.web import RequestHandler
from tornado.options import options

options.define("port", default=8000, type=int, help="run server on the given port")


class IndexHandler(RequestHandler):

    def get(self):

        print "enter into func: %s" % "get"

        # 1.顺序的讲解
        # self.write("response 1 </br>")
        # self.write("response 2 </br>")
        # self.write("response 3 </br>")

        # 2.操作json
        user = {
            "nick_name": "laowang",
            "age": 18
        }
        # 自己处理成json数据在传递给write(),看看有什么不同?
        # 自己处理成json, 则 会帮我们设置成 ,  响应头: Content-Type: text/html; charset=UTF-8
        json_str = json.dumps(user)
        self.write(json_str)

        # write 是可以直接接收字典数据的, 会自动帮我们处理成json数据. 配置响应头: Content-Type: application/json; charset=UTF-8
        # self.write(user)

        # 3.设置响应头
        # 会先调用 set_default_headers 方法再调用 HTTP的method方法, 如果在 HTTP的method方法中操作了 set_header 方法,且该方法和
        # set_default_headers 方法都操作了 相同的 响应头的值, 那么 set_header 会覆盖 set_default_headers 的相关设置.
        self.set_header("Content-Type", "application/json; charset=UTF-32")  # UTF-32

        # 4.状态码
        # self.set_status(666)
        self.set_status(200)  # 自己测试了一下, 会自动帮我们补充上

    def set_default_headers(self):
        print "enter into func: %s" % "set_default_headers"

        self.set_header("Content-Type", "application/json; charset=UTF-8")  # UTF-8


class LoginHandler(RequestHandler):

    def get(self):
        self.write('<form method="post"><input type="submit" value="登陆"></form>')

    def post(self):
        self.redirect("/")  # 一般不直接写path, 而是通过reverse_url来转换


class MyErrorDemoHandler(RequestHandler):

    def get(self):
        """
        self.write("error index")
        # 注意:默认的write_error()方法不会处理send_error抛出的kwargs参数,即上面的代码中content="出现404错误"是没有意义的。
        self.send_error(404, content="出现404错误")
        # 注意:使用send_error()方法后就不要再向输出缓冲区写内容了!
        self.write("error end")  # 不会处理
        """
        err_code = self.get_argument("code", u"")  # 注意返回的是unicode字符串,下同
        err_title = self.get_argument("title", u"")
        err_content = self.get_argument("content", u"")
        if err_code:
            try:
                err_code = int(err_code)
            except Exception:
                err_code = 400
            self.send_error(err_code, title=err_title, content=err_content)
        else:
            self.write("error index")

    def write_error(self, status_code, **kwargs):
        self.write(u"<h1>你来到一片无人区</h1>")
        self.write(u"<p>错误名:%s</p>" % kwargs["title"])
        self.write(u"<p>错误详情:%s</p>" % kwargs["content"])


if __name__ == '__main__':
    options.parse_command_line()
    app = tornado.web.Application(
        [
            (r"/", IndexHandler),
            (r'/login/', LoginHandler),
            (r'/error/', MyErrorDemoHandler),
        ],
        debug=True
    )
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值