简体   繁体   中英

Gevent async server with blocking requests

I have what I would think is a pretty common use case for Gevent. I need a UDP server that listens for requests, and based on the request submits a POST to an external web service. The external web service essentially only allows one request at a time.

I would like to have an asynchronous UDP server so that data can be immediately retrieved and stored so that I don't miss any requests (this part is easy with the DatagramServer gevent provides). Then I need some way to send requests to the external web service serially, but in such a way that it doesn't ruin the async of the UDP server.

I first tried monkey patching everything and what I ended up with was a quick solution, but one in which my requests to the external web service were not rate limited in any way and which resulted in errors.

It seems like what I need is a single non-blocking worker to send requests to the external web service in serial while the UDP server adds tasks to the queue from which the non-blocking worker is working.

What I need is information on running a gevent server with additional greenlets for other tasks (especially with a queue). I've been using the serve_forever function of the DatagramServer and think that I'll need to use the start method instead, but haven't found much information on how it would fit together.

Thanks,

EDIT

The answer worked very well. I've adapted the UDP server example code with the answer from @mguijarr to produce a working example for my use case:

from __future__ import print_function
from gevent.server import DatagramServer
import gevent.queue
import gevent.monkey
import urllib

gevent.monkey.patch_all()

n = 0

def process_request(q):
    while True:
        request = q.get()
        print(request)
        print(urllib.urlopen('https://test.com').read())


class EchoServer(DatagramServer):
    __q = gevent.queue.Queue()
    __request_processing_greenlet = gevent.spawn(process_request, __q)

    def handle(self, data, address):
        print('%s: got %r' % (address[0], data))
        global n
        n += 1
        print(n)
        self.__q.put(n)
        self.socket.sendto('Received %s bytes' % len(data), address)


if __name__ == '__main__':
    print('Receiving datagrams on :9000')
    EchoServer(':9000').serve_forever()

Here is how I would do it:

  1. Write a function taking a "queue" object as argument; this function will continuously process items from the queue. Each item is supposed to be a request for the web service. This function could be a module-level function, not part of your DatagramServer instance:

     def process_requests(q): while True: request = q.get() # do your magic with 'request' ... 
  2. in your DatagramServer, make the function running within a greenlet (like a background task):

     self.__q = gevent.queue.Queue() self.__request_processing_greenlet = gevent.spawn(process_requests, self.__q) 
  3. when you receive the UDP request in your DatagramServer instance, you push the request to the queue

     self.__q.put(request) 

This should do what you want. You still call 'serve_forever' on DatagramServer, no problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM