Python的asyncore使用变量超时定期发送数据。有没有更好的办法?

时间:2009-06-24 06:11:05

标签: python sockets asynchronous

我想编写一个客户端可以连接到的服务器并接收定期更新而无需轮询。我在asyncore中遇到的问题是,如果在调用dispatcher.writable()时没有返回true,则必须等到asyncore.loop超时后(默认为30秒)。

我尝试解决此问题的两种方法是:1)将超时减少到较低值或2)查询连接,以便下次更新并生成足够的超时值。但是,如果你在'man 2 select_tut'中引用'Select Law',它会说:“你应该总是尝试使用select()而不会超时。”

有更好的方法吗?可能扭曲?我想尝试避免额外的线程。我将在这里包含变量超时示例:

#!/usr/bin/python

import time
import socket
import asyncore


# in seconds
UPDATE_PERIOD = 4.0

class Channel(asyncore.dispatcher):

    def __init__(self, sock, sck_map):
        asyncore.dispatcher.__init__(self, sock=sock, map=sck_map)
        self.last_update = 0.0  # should update immediately
        self.send_buf = ''
        self.recv_buf = ''

    def writable(self):
        return len(self.send_buf) > 0

    def handle_write(self):
        nbytes = self.send(self.send_buf)
        self.send_buf = self.send_buf[nbytes:]

    def handle_read(self):
        print 'read'
        print 'recv:', self.recv(4096)

    def handle_close(self):
        print 'close'
        self.close()

    # added for variable timeout
    def update(self):
        if time.time() >= self.next_update():
            self.send_buf += 'hello %f\n'%(time.time())
            self.last_update = time.time()

    def next_update(self):
        return self.last_update + UPDATE_PERIOD


class Server(asyncore.dispatcher):

    def __init__(self, port, sck_map):
        asyncore.dispatcher.__init__(self, map=sck_map)
        self.port = port
        self.sck_map = sck_map
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind( ("", port))
        self.listen(16)
        print "listening on port", self.port

    def handle_accept(self):
        (conn, addr) = self.accept()
        Channel(sock=conn, sck_map=self.sck_map)

    # added for variable timeout
    def update(self):
        pass

    def next_update(self):
        return None


sck_map = {}

server = Server(9090, sck_map)
while True:
    next_update = time.time() + 30.0
    for c in sck_map.values():
        c.update()  # <-- fill write buffers
        n = c.next_update()
        #print 'n:',n
        if n is not None:
            next_update = min(next_update, n)
    _timeout = max(0.1, next_update - time.time())

    asyncore.loop(timeout=_timeout, count=1, map=sck_map)

5 个答案:

答案 0 :(得分:4)

“选择法”并不适用于您的情况,因为您不仅有客户端触发的(纯服务器)活动,还有时间触发的活动 - 这正是选择超时的目的。法律应该真正说的是“如果你指定一个超时,确保你在超时到来时确实必须做一些有用的事情”。法律旨在防止忙碌等待;你的代码不忙 - 等待。

我不会将_timeout设置为最大值0.1和下一个更新时间,但最大值为0.0和下一个超时。 IOW,如果您在更新期间更新期限已过期,则应立即执行该特定更新。

不是每次询问每个频道是否要更新,而是可以将所有频道存储在优先级队列中(按下次更新时间排序),然后只对最早的频道运行更新(直到找到更新的频道)时间尚未到来)。您可以使用heapq模块。

您也可以通过不让每个频道询问当前时间来保存一些系统调用,但只调查当前时间一次,并将其传递给.update。

答案 1 :(得分:4)

也许你可以用sched.scheduler这样做(n.b.未经测试):

import sched, asyncore, time

# Create a scheduler with a delay function that calls asyncore.loop
scheduler = sched.scheduler(time.time, lambda t: _poll_loop(t, time.time()) )

# Add the update timeouts with scheduler.enter
# ...

def _poll_loop(timeout, start_time):  
  asyncore.loop(timeout, count=1)
  finish_time = time.time()
  timeleft = finish_time - start_time
  if timeleft > timeout:  # there was a message and the timeout delay is not finished
    _poll_loop(timeleft, finish_time) # so wait some more polling the socket

def main_loop():
  while True:
    if scheduler.empty():
      asyncore.loop(30.0, count=1) # just default timeout, use what suits you
      # add other work that might create scheduled events here
    else:
      scheduler.run()

答案 2 :(得分:2)

这基本上是demiurgus'解决方案,粗糙的边缘做成圆形。它保留了他的基本想法,但阻止了RuntimeErrors和繁忙的循环并进行了测试。 [编辑:解决了在_delay期间修改调度程序的问题]

class asynschedcore(sched.scheduler):
    """Combine sched.scheduler and asyncore.loop."""
    # On receiving a signal asyncore kindly restarts select. However the signal
    # handler might change the scheduler instance. This tunable determines the
    # maximum time in seconds to spend in asycore.loop before reexamining the
    # scheduler.
    maxloop = 30
    def __init__(self, map=None):
        sched.scheduler.__init__(self, time.time, self._delay)
        if map is None:
            self._asynmap = asyncore.socket_map
        else:
            self._asynmap = map
        self._abort_delay = False

    def _maybe_abort_delay(self):
        if not self._abort_delay:
            return False
        # Returning from this function causes the next event to be executed, so
        # it might be executed too early. This can be avoided by modifying the
        # head of the queue. Also note that enterabs sets _abort_delay to True.
        self.enterabs(0, 0, lambda:None, ())
        self._abort_delay = False
        return True

    def _delay(self, timeout):
        if self._maybe_abort_delay():
            return
        if 0 == timeout:
            # Should we support this hack, too?
            # asyncore.loop(0, map=self._asynmap, count=1)
            return
        now = time.time()
        finish = now + timeout
        while now < finish and self._asynmap:
            asyncore.loop(min(finish - now, self.maxloop), map=self._asynmap,
                          count=1)
            if self._maybe_abort_delay():
                return
            now = time.time()
        if now < finish:
            time.sleep(finish - now)

    def enterabs(self, abstime, priority, action, argument):
        # We might insert an event before the currently next event.
        self._abort_delay = True
        return sched.scheduler.enterabs(self, abstime, priority, action,
                                        argument)

    # Overwriting enter is not necessary, because it is implemented using enter.

    def cancel(self, event):
        # We might cancel the next event.
        self._abort_delay = True
        return sched.scheduler.cancel(self, event)

    def run(self):
        """Runs as long as either an event is scheduled or there are
        sockets in the map."""
        while True:
            if not self.empty():
                sched.scheduler.run(self)
            elif self._asynmap:
                asyncore.loop(self.maxloop, map=self._asynmap, count=1)
            else:
                break

答案 3 :(得分:1)

我会使用Twisted,因为我使用asyncore很长时间,但我认为这应该是扭曲的等价物(未测试,从内存写入):

from twisted.internet import reactor, protocol
import time

UPDATE_PERIOD = 4.0

class MyClient(protocol.Protocol):

    def connectionMade(self):
        self.updateCall = reactor.callLater(UPDATE_PERIOD, self.update)

    def connectionLost(self, reason):
        self.updateCall.cancel()

    def update(self):
        self.transport.write("hello %f\n" % (time.time(),))

    def dataReceived(self, data):
        print "recv:", data


f = protocol.ServerFactory()
f.protocol = MyClient

reactor.listenTCP(9090, f)
reactor.run()

答案 4 :(得分:0)

也许我不明白OP想要完成什么,但我刚刚使用1个线程解决了这个问题,该线程获得了每个Channel(asyncore.dispatcher)对象的弱参数。该线程确定自己的时序,并使用该通道中的队列定期向Channel发送更新。它通过调用getQueue从Channel对象获取Queue。

我使用weakref的原因是因为客户端是瞬态的。如果通道死亡,则weakref返回None。这样,计时线程不会保留旧对象,因为它引用它们。

我知道OP想要避免线程,但这个解决方案非常简单。它只创建一个线程,并且它与任何创建的Channel进行通信,因为Server对象将它们添加到要监视的对象的线程列表中。