• tornado的http服务器实现


    使用tornado实现的一个简单http服务器:只需要定义自己的处理方法,其他的东西全部交给tornado完成.

    #coding:utf-8
    
    import tornado.httpserver
    import tornado.ioloop
    
    def handle_request(request):
        message = "Hello World from Tornado Http Server"
        request.write("HTTP/1.1 200 OK
    Content-Length: %d
    
    %s" % (len(message), message))
        request.finish()
    
    http_server = tornado.httpserver.HTTPServer(handle_request)
    http_server.bind(8080)
    http_server.start()
    #启动事件循环,开始监听网络事件,主要是socket的读和写 tornado.ioloop.IOLoop.instance().start()

    1.socket、bind及listen函数(httpserver中实现)

    #getaddrinfo返回服务器的所有网卡信息, 每块网卡上都要创建监听客户端的socket.
    for res in socket.getaddrinfo(address, port, family, socket.SOCK_STREAM,0, socket.AI_PASSIVE | socket.AI_ADDRCONFIG):
      af, socktype, proto, canonname, sockaddr = res
      
      # 创建listen socket
      sock = socket.socket(af, socktype, proto)
    
      # 设置socket的属性 
      flags = fcntl.fcntl(sock.fileno(), fcntl.F_GETFD)
      flags |= fcntl.FD_CLOEXEC
      fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, flags)
      sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      if af == socket.AF_INET6:
          if hasattr(socket, "IPPROTO_IPV6"):
              sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
      sock.setblocking(0)
    
      # bind 和 listen
      sock.bind(sockaddr)
      sock.listen(128)
    
      # 加入ioloop
      #ioloop可以理解为一个容器,用户把socket和回调函数注册到容器中, 容器内部会轮询socket, 一旦某个socket可以读写, 就调用回调函数来处理socket的读写事件.
      self._sockets[sock.fileno()] = sock
      if self._started:
        #监听listen_socket的读事件, 回调函数为_handle_events,一旦listen socket可读, 说明客户端请求到来, 然后调用_handle_events接受客户端的请求
        self.io_loop.add_handler(sock.fileno(), self._handle_events,ioloop.IOLoop.READ)

    2.accept函数(httpserver中实现)

    def _handle_events(self, fd, events):  
      while True:  
          try:  
              #accept方法返回客户端的socket(注意connection的类型是socket), 以及客户端的地址
              connection, address = self._sockets[fd].accept()  
          except socket.error, e:  
              if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):  
                  return  
              raise  
          try:  
              #创建IOStream对象, 用来处理socket的异步读写. 这一步会调用ioloop.add_handler把client socket加入ioloop,然后创建HTTPConnection, 处理用户的请求.
              stream = iostream.IOStream(connection, io_loop=self.io_loop)  
              HTTPConnection(stream, address, self.request_callback,  
                             self.no_keep_alive, self.xheaders)  
          except:  
              logging.error("Error in connection callback", exc_info=True)  

    3.IOStream

    IOStream对socket的读写做了一层封装, 通过使用两个缓冲区, 实现对socket的异步读写.

    为了实现对client socket的异步读写, 我们为client socket创建两个缓冲区: _read_buffer和_write_buffer, 写: 先写到_write_buffer, 读: 从_read_buffer读. 这样我们就不用直接读写socket, 进而实现异步读写. 这些操作都封装在IOStream类中

    IOStream的初始化:IOStream与socket是一一对应的, 初始化主要做4个工作

    (1) 初始化IOStream对应的socket

    (2) 分配输入缓冲区_write_buffer

    (3) 分配输出缓冲区_read_buffer

    (4) 把socket加入ioloop, 这样当socket可读写的时候, 调用回调函数_handle_events把数据从socket读入buffer, 或者把数据从buffer发送给socket

    #IOStream的__init__方法
    self.socket = socket
    self.io_loop = io_loop or ioloop.IOLoop.instance()
    self._read_buffer = collections.deque()
    self._write_buffer = collections.deque()
    self.io_loop.add_handler(self.socket.fileno(),self._handle_events,self._state)

    IOStream提供的接口:

    (1) write(data):把数据写入IOStream的_write_buffer

    (2) read_until(delimiter, callback):从_read_buffer读取数据, delimiter作为读取结束符, 完了调用callback

    (3) read_bytes(num_of_bytes, callback):从_read_buffer读取指定大小的数据, 完了调用callback

    异步IO的例子:一系列调用都是通过回调函数实现的,这就是异步的处理方式

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    from tornado import ioloop
    from tornado import iostream
    import socket
    
    def send_request():
        stream.write("GET /index.html HTTP/1.0
    Host: nginx.net
    
    ")
        #回调on_headers解析协议头
        stream.read_until("
    
    ", on_headers)
    
    def on_headers(data):
        headers = {}
        for line in data.split("
    "):
           parts = line.split(":")
           if len(parts) == 2:
               headers[parts[0].strip()] = parts[1].strip()
        #回调on_body把数据打印出来
        stream.read_bytes(int(headers["Content-Length"]), on_body)
    
    def on_body(data):
        print data
        stream.close()
        ioloop.IOLoop.instance().stop()
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    stream = iostream.IOStream(s)
    #调用connect连接服务器, 完成后回调send_request发出请求, 并读取服务器返回的http协议头
    stream.connect(("nginx.net", 80), send_request)
    ioloop.IOLoop.instance().start()

    4.处理请求:HTTPConnection

    HttpConnection类专门用来处理http请求, 处理http请求的一般流程是:

    HTTPConnection实现了一系列的函数用来处理这些流程, 参见下图:

    5.IOLoop:基于epoll

    在Tornado服务器中, IOLoop是调度的核心模块, Tornado服务器回把所有的socket都注册到IOLoop, 注册的时候指明回调处理函数, IOLoop内部不断的监听IO事件, 一旦发现某个socket可读写, 就调用其注册时指定的回调函数. 

    IOLoop的结构图如下所示:

    使用IOLoop的一个例子:

    from tornado import ioloop
    from tornado import iostream
    import socket
    import errno
    import functools
    
    def handle_connection(client, address):
      client.send("Hello World from A Simple TCP Server")
      client.close()
    //该函数作用是接受客户端新的连接
    def connection_ready(sock, fd, events):
        while True:
            try:
                connection, address = sock.accept()
            except socket.error, e:
                if e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                    raise
                return
            connection.setblocking(0)
         //此处其实应该设置为异步的,将相应的套接字加入到事件循环并注册相应的回调函数,只是没这样做 handle_connection(connection, address)

    //下面的代码可以直接调用httpserver或者tcpserver实现 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    //一般来说一个端口释放后会等待两分钟之后才能再被使用SO_REUSEADDR是让端口释放后立即就可以被再次使用
    //server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
    1)
    //启用套接字的非阻塞模式 sock.setblocking(0) sock.bind((
    "localhost", 8080))
    //128是连接队列的最大长度 sock.listen(
    128)
    //创建一个ioloop实例 io_loop = ioloop.IOLoop.instance()
    callback
    = functools.partial(connection_ready, sock)
    //fileno将sock转换为标准的描述符,add_handler向ioloop中注册socket以及对应的回调函数、监听事件类型 io_loop.add_handler(sock.fileno(), callback, io_loop.READ) io_loop.start()

     IOLoop的单例模式:

    #coding:utf-8
    import os
    
    class IOLoop(object):
        @classmethod
        #cls和self一样,是python的built-in变量,self表示类的实例,cls表示类
        #cls一般用于static method,因为static method无须实例化就可以调用,所以传递cls给static method,然后调用cls()可以创建对象
        def instance(cls):
            if not hasattr(cls,"_instance"):
                cls._instance = cls()
            return cls._instance
        @classmethod
        def initialized(cls):
            return hasattr(cls,"_instance")
    
        def service(self):
            print "hello world"
    
    print IOLoop.initialized(),
    ioloop = IOLoop.instance()
    ioloop.service()
    
    if os.fork() == 0:
        print IOLoop.initialized(),
        ioloop = IOLoop.instance()
        ioloop.service()

    Always use 'self' for the first argument to instance methods.

    Always use 'cls' for the first argument to class methods.

  • 相关阅读:
    简单使用GDB
    【老人孟岩经验谈】如何学习一本新的编程语言
    【做存档】如何争取到真正有用的人脉?
    回头来看C语言里的static
    Spring MVC 教程,快速入门,深入分析[1220]
    eclipse ibabis代码生成插件abator功能扩展
    在MyEclipse中配置Tomcat服务器
    将远程调试的控制台信息输出至Eclipse
    LOG4J.PROPERTIES配置详解
    java Map 怎么遍历
  • 原文地址:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/5643830.html
Copyright © 2020-2023  润新知