如何在与主程序不同的线程中编写套接字服务器(使用gevent)?

时间:2014-03-27 15:10:21

标签: python multithreading sockets gevent greenlets

我正在开发一个Flask / gevent WSGIserver网络服务器,需要使用XML通过两个套接字与硬件设备进行通信(在后台)。

一个套接字由客户端(我的应用程序)启动,我可以向设备发送XML命令。设备在不同的端口上应答,并发回我的应用程序必须确认的信息。所以我的应用程序必须听第二个端口。

到目前为止,我已发出命令,将第二个端口作为服务器打开,等待来自设备的响应并关闭第二个端口。

问题是设备可能会发送我必须确认的多个响应。所以我的解决方案是保持端口打开并继续响应传入的请求。但是,最终设备完成了发送请求,我的应用程序仍在监听(我不知道设备何时完成),从而阻止了其他所有内容。

这似乎是一个线程的完美用例,因此我的应用程序在一个单独的线程中启动一个监听服务器。因为我已经将gevent用作Flask的WSGI服务器,所以我可以使用greenlets。

问题是,我已经找到了这样一个很好的例子,但我能找到的只是单个套接字服务器的多线程处理程序的例子。我不需要在套接字服务器上处理很多连接,但我需要在单独的线程中启动它,以便它可以监听和处理传入的消息,而我的主程序可以继续发送消息。 我遇到的第二个问题是在服务器中,我需要使用我的“main”类中的一些方法。对于Python来说比较新,我不确定如何以一种方式构建它。

class Device(object):

def __init__(self, ...):
    self.clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

def _connect_to_device(self):
    print "OPEN CONNECTION TO DEVICE"
    try:
        self.clientsocket.connect((self.ip, 5100))
    except socket.error as e:
        pass

def _disconnect_from_device(self):
    print "CLOSE CONNECTION TO DEVICE"
    self.clientsocket.close()

def deviceaction1(self, ...):
    # the data that is sent is an XML document that depends on the parameters of this method.
    self._connect_to_device()
    self._send_data(XMLdoc)
    self._wait_for_response()
    return True

def _send_data(self, data):
    print "SEND:"
    print(data)
    self.clientsocket.send(data)

def _wait_for_response(self):
    print "WAITING FOR REQUESTS FROM DEVICE (CHANNEL 1)"
    self.serversocket.bind(('10.0.0.16', 5102))
    self.serversocket.listen(5)                         # listen for answer, maximum 5 connections
    connection, address = self.serversocket.accept()
    # the data is of a specific length I can calculate
    if len(data) > 0:
        self._process_response(data)
        self.serversocket.close()

def _process_response(self, data):
    print "RECEIVED:"
    print(data)
    # here is some code that processes the incoming data and
    # responds to the device
    # this may or may not result in more incoming data



if __name__ == '__main__':
    machine = Device(ip="10.0.0.240")
    Device.deviceaction1(...)

这是(全球范围内,我遗漏了敏感信息)我现在在做什么。正如你所看到的,一切都是顺序的。 如果任何人都可以在一个单独的线程中提供监听服务器的示例(最好使用greenlets)以及从监听服务器返回到产生线程的通信方式,那将会有很大的帮助。

感谢。

修改 在尝试了几种方法后,我决定使用Pythons默认的select()方法来解决这个问题。这很有用,所以我关于线程使用的问题已经不再适用了。感谢为您的时间和精力提供意见的人。

1 个答案:

答案 0 :(得分:1)

希望它可以提供一些帮助,在示例类中,如果我们将调用tenMessageSender函数,那么它将启动异步线程而不阻塞主循环,然后_zmqBasedListener将开始侦听单独的端口,直到线程还活着。以及我们tenMessageSender函数将发送的任何消息,客户都会收到这些消息并回复zmqBasedListener

服务器端

import threading
import zmq
import sys

class Example:
    def __init__(self):
        self.context = zmq.Context()
        self.publisher = self.context.socket(zmq.PUB)
        self.publisher.bind('tcp://127.0.0.1:9997')
        self.subscriber = self.context.socket(zmq.SUB)
        self.thread = threading.Thread(target=self._zmqBasedListener)

    def _zmqBasedListener(self):
        self.subscriber.connect('tcp://127.0.0.1:9998')
        self.subscriber.setsockopt(zmq.SUBSCRIBE, "some_key")
        while True:
            message = self.subscriber.recv()
            print message
        sys.exit()

    def tenMessageSender(self):
        self._decideListener()
        for message in range(10):
            self.publisher.send("testid : %d: I am a task" %message)

    def _decideListener(self):
        if not self.thread.is_alive():
            print "STARTING THREAD"
            self.thread.start()

<强>客户端

import zmq
context = zmq.Context()

subscriber = context.socket(zmq.SUB)
subscriber.connect('tcp://127.0.0.1:9997')
publisher = context.socket(zmq.PUB)
publisher.bind('tcp://127.0.0.1:9998')
subscriber.setsockopt(zmq.SUBSCRIBE, "testid")
count = 0
print "Listener"
while True:
    message = subscriber.recv()
    print message
    publisher.send('some_key : Message received %d' %count)
    count+=1

而不是线程,你可以使用greenlet等。