gevent For the Working Python Developer的翻译

246 阅读4分钟

第三部分

文章的地址:sdiehl.github.io/gevent-tuto…

Real World Applications

Gevent ZeroMQ

ZeroMQ is described by its authors as "a socket library that acts as a concurrency framework". It is a very powerful messaging layer for building concurrent and distributed applications.

ZeroMQ根据其作者的描述是"一个socket库作为一个并发性的框架".它是非常强大的消息传送层在建立并发性结构和分布式应用的时候.

ZeroMQ provides a variety of socket primitives, the simplest of which being a Request-Response socket pair. A socket has two methods of interest send and recv, both of which are normally blocking operations. But this is remedied by a briliant library by Travis Cline which uses gevent.socket to poll ZeroMQ sockets in a non-blocking manner. You can install gevent-zeromq from PyPi via: pip install gevent-zeromq

ZeroMQ提供了各种socket基元,最简单的就是一对Request-Response socket. 一个socket有2个有用方法 sendrecv,两者一般都会有阻塞操作.但这已经被一个作者叫Travis Cline,基于gevent 写的briliant库补救了.socket 属于 ZeroMQ sockets 是一种不会阻塞的方式.你可以安装 gevent-zeromq 从 PyPi 取到: pip install gevent-zeromq

# Note: Remember to ``pip install pyzmq gevent_zeromq``
import gevent
from gevent_zeromq import zmq

# Global Context
context = zmq.Context()

def server():
    server_socket = context.socket(zmq.REQ)
    server_socket.bind("tcp://127.0.0.1:5000")

    for request in range(1,10):
        server_socket.send("Hello")
        print('Switched to Server for ', request)
        # Implicit context switch occurs here
        server_socket.recv()

def client():
    client_socket = context.socket(zmq.REP)
    client_socket.connect("tcp://127.0.0.1:5000")

    for request in range(1,10):

        client_socket.recv()
        print('Switched to Client for ', request)
        # Implicit context switch occurs here
        client_socket.send("World")

publisher = gevent.spawn(server)
client    = gevent.spawn(client)

gevent.joinall([publisher, client])

"""
Switched to Server for  1
Switched to Client for  1
Switched to Server for  2
Switched to Client for  2
Switched to Server for  3
Switched to Client for  3
Switched to Server for  4
Switched to Client for  4
Switched to Server for  5
Switched to Client for  5
Switched to Server for  6
Switched to Client for  6
Switched to Server for  7
Switched to Client for  7
Switched to Server for  8
Switched to Client for  8
Switched to Server for  9
Switched to Client for  9
"""

Simple Telnet Servers

# On Unix: Access with ``$ nc 127.0.0.1 5000`` 
# On Window: Access with ``$ telnet 127.0.0.1 5000``

from gevent.server import StreamServer

def handle(socket, address):
    socket.send("Hello from a telnet!\n")
    for i in range(5):
        socket.send(str(i) + '\n')
    socket.close()

server = StreamServer(('127.0.0.1', 5000), handle)
server.serve_forever()

WSGI Servers

Gevent provides two WSGI servers for serving content over HTTP. Henceforth called wsgi and pywsgi:

Gevent提供2个WSGI服务器用于HTTP.称为wsgipywsgi:

  • gevent.wsgi.WSGIServer
  • gevent.pywsgi.WSGIServer

In earlier versions of gevent before 1.0.x, gevent used libevent instead of libev. Libevent included a fast HTTP server which was used by gevent's wsgi server.

在1.0x前的gevent版本,gevent用libevent代替libev.Libevent包含一个快的HTTP服务用于gevent的wsgi服务器.

In gevent 1.0.x there is no http server included. Instead gevent.wsgi it is now an alias for the pure Python server in gevent.pywsgi.

在gevent1.0.x这里没有http服务器包含,gevent.wsgi现在已经是纯Python服务器gevent.pywsgi的别名.

Streaming Servers 流式服务器

If you are using gevent 1.0.x, this section does not apply

如果你用的是gevent1.0.x,这部分是不适用的.

For those familiar with streaming HTTP services, the core idea is that in the headers we do not specify a length of the content. We instead hold the connection open and flush chunks down the pipe, prefixing each with a hex digit indicating the length of the chunk. The stream is closed when a size zero chunk is sent.

和那些流式HTTP服务器相似,核心意见是在headers中,我们不指定内容的长度.我们用保持连接打到管道接收缓冲块,在每一块的前缀用十六进制表明块的长度,当块的长度是0发送的时候,流就会关闭.

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

8
<p>Hello

9
World</p>

0

The above HTTP connection could not be created in wsgi because streaming is not supported. It would instead have to buffered.

以上的HTTP连接,不能用于创建wsgi,因为流是不支持的,我们用缓冲的方法.

from gevent.wsgi import WSGIServer

def application(environ, start_response):
    status = '200 OK'
    body = '<p>Hello World</p>'

    headers = [
        ('Content-Type', 'text/html')
    ]

    start_response(status, headers)
    return [body]

WSGIServer(('', 8000), application).serve_forever()

Using pywsgi we can however write our handler as a generator and yield the result chunk by chunk.

使用pywsgi我们把处理当作一个发生器,一块接一块的返回结果.

from gevent.pywsgi import WSGIServer

def application(environ, start_response):
    status = '200 OK'

    headers = [
        ('Content-Type', 'text/html')
    ]

    start_response(status, headers)
    yield "<p>Hello"
    yield "World</p>"

WSGIServer(('', 8000), application).serve_forever()

But regardless, performance on Gevent servers is phenomenal compared to other Python servers. libev is a very vetted technology and its derivative servers are known to perform well at scale.

To benchmark, try Apache Benchmark ab or see this Benchmark of Python WSGI Servers for comparison with other servers.

但是不管怎样,gevent跟其他python服务器对比是非凡的.libev是一个非常vetted的技术,和它派生出来的服务器已被认可是表现良好的.

用基准问题测试,尝试 Apache Benchmark ab 看这个 Benchmark of Python WSGI Servers 和其他服务的比较.

$ ab -n 10000 -c 100 http://127.0.0.1:8000/

Long Polling

import gevent
from gevent.queue import Queue, Empty
from gevent.pywsgi import WSGIServer
import simplejson as json

data_source = Queue()

def producer():
    while True:
        data_source.put_nowait('Hello World')
        gevent.sleep(1)

def ajax_endpoint(environ, start_response):
    status = '200 OK'
    headers = [
        ('Content-Type', 'application/json')
    ]

    try:
        datum = data_source.get(timeout=5)
    except Empty:
        datum = []

    start_response(status, headers)
    return json.dumps(datum)

gevent.spawn(producer)

WSGIServer(('', 8000), ajax_endpoint).serve_forever()

Websockets

Websocket example which requires gevent-websocket.

Websocket例子需要 gevent-websocket.

# Simple gevent-websocket server
import json
import random

from gevent import pywsgi, sleep
from geventwebsocket.handler import WebSocketHandler

class WebSocketApp(object):
    '''Send random data to the websocket'''

    def __call__(self, environ, start_response):
        ws = environ['wsgi.websocket']
        x = 0
        while True:
            data = json.dumps({'x': x, 'y': random.randint(1, 5)})
            ws.send(data)
            x += 1
            sleep(0.5)

server = pywsgi.WSGIServer(("", 10000), WebSocketApp(),
    handler_class=WebSocketHandler)
server.serve_forever()

HTML Page:

<html>
    <head>
        <title>Minimal websocket application</title>
        <script type="text/javascript" src="jquery.min.js"></script>
        <script type="text/javascript">
        $(function() {
            // Open up a connection to our server
            var ws = new WebSocket("ws://localhost:10000/");

            // What do we do when we get a message?
            ws.onmessage = function(evt) {
                $("#placeholder").append('<p>' + evt.data + '</p>')
            }
            // Just update our conn_status field with the connection status
            ws.onopen = function(evt) {
                $('#conn_status').html('<b>Connected</b>');
            }
            ws.onerror = function(evt) {
                $('#conn_status').html('<b>Error</b>');
            }
            ws.onclose = function(evt) {
                $('#conn_status').html('<b>Closed</b>');
            }
        });
    </script>
    </head>
    <body>
        <h1>WebSocket Example</h1>
        <div id="conn_status">Not Connected</div>
        <div id="placeholder" style="width:600px;height:300px;"></div>
    </body>
</html>

Chat Server

The final motivating example, a realtime chat room. This example requires Flask ( but not neccesarily so, you could use Django, Pyramid, etc ). The corresponding Javascript and HTML files can be found here.

最后一个激励的例子,一个实时的聊天室.这个例子需要Flask(但不是必须的,你可以用Django, Pyramid等等).相应的Javascript 和 HTML 文件可以在这里找到.

# Micro gevent chatroom.
# ----------------------

from flask import Flask, render_template, request

from gevent import queue
from gevent.pywsgi import WSGIServer

import simplejson as json

app = Flask(__name__)
app.debug = True

rooms = {
    'topic1': Room(),
    'topic2': Room(),
}

users = {}

class Room(object):

    def __init__(self):
        self.users = set()
        self.messages = []

    def backlog(self, size=25):
        return self.messages[-size:]

    def subscribe(self, user):
        self.users.add(user)

    def add(self, message):
        for user in self.users:
            print user
            user.queue.put_nowait(message)
        self.messages.append(message)

class User(object):

    def __init__(self):
        self.queue = queue.Queue()

@app.route('/')
def choose_name():
    return render_template('choose.html')

@app.route('/<uid>')
def main(uid):
    return render_template('main.html',
        uid=uid,
        rooms=rooms.keys()
    )

@app.route('/<room>/<uid>')
def join(room, uid):
    user = users.get(uid, None)

    if not user:
        users[uid] = user = User()

    active_room = rooms[room]
    active_room.subscribe(user)
    print 'subscribe', active_room, user

    messages = active_room.backlog()

    return render_template('room.html',
        room=room, uid=uid, messages=messages)

@app.route("/put/<room>/<uid>", methods=["POST"])
def put(room, uid):
    user = users[uid]
    room = rooms[room]

    message = request.form['message']
    room.add(':'.join([uid, message]))

    return ''

@app.route("/poll/<uid>", methods=["POST"])
def poll(uid):
    try:
        msg = users[uid].queue.get(timeout=10)
    except queue.Empty:
        msg = []
    return json.dumps(msg)

if __name__ == "__main__":
    http = WSGIServer(('', 5000), app)
    http.serve_forever()