使用asyncore创建与客户端/服务器模型的交互式会话

时间:2014-06-18 14:52:43

标签: python session asynchronous twisted asyncore

我正在尝试创建一个允许许多客户端同时连接到1台服务器的程序。这些连接应该在服务器端是交互式的,这意味着我可以在客户端连接后从服务器向客户端发送请求。

以下asyncore示例代码只是回复一个echo,我需要一个echo而不是echo来交互式访问每个会话。以某种方式背景每个连接,直到我决定与它进行交互。如果我有100个会话,我想选择一个特定的会话,或者选择所有会话或其中的一部分来发送命令。此外,我并非100%确定asyncore lib是这里的方式,任何帮助都表示赞赏。

import asyncore
import socket

class EchoHandler(asyncore.dispatcher_with_send):

    def handle_read(self):
        data = self.recv(8192)
        if data:
            self.send(data)

class EchoServer(asyncore.dispatcher):

    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)

    def handle_accept(self):
        pair = self.accept()
        if pair is not None:
            sock, addr = pair
            print 'Incoming connection from %s' % repr(addr)
            handler = EchoHandler(sock)

server = EchoServer('localhost', 8080)
asyncore.loop()

2 个答案:

答案 0 :(得分:1)

这是一个Twisted服务器:

import sys

from twisted.internet.task import react
from twisted.internet.endpoints import serverFromString
from twisted.internet.defer import Deferred
from twisted.internet.protocol import Factory

from twisted.protocols.basic import LineReceiver

class HubConnection(LineReceiver, object):
    def __init__(self, hub):
        self.name = b'unknown'
        self.hub = hub

    def connectionMade(self):
        self.hub.append(self)

    def lineReceived(self, line):
        words = line.split(" ", 1)
        if words[0] == b'identify':
            self.name = words[1]
        else:
            for connection in self.hub:
                connection.sendLine("<{}> {}".format(
                    self.name, line
                ).encode("utf-8"))

    def connectionLost(self, reason):
        self.hub.remove(self)

def main(reactor, listen="tcp:4321"):
    hub = []
    endpoint = serverFromString(reactor, listen)
    endpoint.listen(Factory.forProtocol(lambda: HubConnection(hub)))
    return Deferred()

react(main, sys.argv[1:])

和命令行客户端:

import sys

from twisted.internet.task import react
from twisted.internet.endpoints import clientFromString
from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.internet.protocol import Factory
from twisted.internet.stdio import StandardIO

from twisted.protocols.basic import LineReceiver
from twisted.internet.fdesc import setBlocking

class HubClient(LineReceiver):
    def __init__(self, name, output):
        self.name = name
        self.output = output

    def lineReceived(self, line):
        self.output.transport.write(line + b"\n")

    def connectionMade(self):
        self.sendLine("identify {}".format(self.name).encode("utf-8"))

    def say(self, words):
        self.sendLine("say {}".format(words).encode("utf-8"))

class TerminalInput(LineReceiver, object):
    delimiter = "\n"
    hubClient = None
    def lineReceived(self, line):
        if self.hubClient is None:
            self.output.transport.write("Connecting, please wait...\n")
        else:
            self.hubClient.sendLine(line)

@inlineCallbacks
def main(reactor, name, connect="tcp:localhost:4321"):
    endpoint = clientFromString(reactor, connect)
    terminalInput = TerminalInput()
    StandardIO(terminalInput)
    setBlocking(0)
    hubClient = yield endpoint.connect(
        Factory.forProtocol(lambda: HubClient(name, terminalInput))
    )
    terminalInput.transport.write("Connecting...\n")
    terminalInput.hubClient = hubClient
    terminalInput.transport.write("Connected.\n")
    yield Deferred()

react(main, sys.argv[1:])

实现基本聊天服务器。希望代码是相当不言自明的;您可以在一个终端中使用python hub_server.py进行测试,一秒钟python hub_client.py alice,第三个python hub_client.py bob进行测试;然后键入alice和bob的会话,你可以看到它的作用。

答案 1 :(得分:0)

审核要求

你想要

  • 以客户端/服务器方式进行远程调用
  • 可能使用TCP通信
  • 在通话中使用会话

目前还不是很清楚,你真的想如何使用会话,所以我会考虑,那个会话只是一个调用参数,它在服务器和客户端都有一些意义,并且会跳过实现它。

zmq作为简单可靠的远程消息传递平台

ZeroMQ是轻量级消息传递平台,不需要复杂的服务器基础架构。它可以处理许多消息传递模式,下面的示例显示了使用多部分消息的请求/回复模式。

有许多替代方案,您可以使用编码为某种格式(如JSON)的简单消息,并跳过使用多部分消息。

server.py

import zmq

class ZmqServer(object):
    def __init__(self, url="tcp://*:5555"):
        context = zmq.Context()
        self.sock = context.socket(zmq.REP)
        self.sock.bind(url)
        self.go_on = False

    def echo(self, message, priority=None):
        priority = priority or "not urgent"
        msg = "Echo your {priority} message: '{message}'"
        return msg.format(**locals())

    def run(self):
        self.go_on = True
        while self.go_on:
            args = self.sock.recv_multipart()
            if 1 <= len(args) <= 2:
                code = "200"
                resp = self.echo(*args)
            else:
                code = "401"
                resp = "Bad request, 1-2 arguments expected."
            self.sock.send_multipart([code, resp])
    def stop(self):
        self.go_on = False

if __name__ == "__main__":
    ZmqServer().run()

client.py

import zmq
import time

class ZmqClient(object):
    def __init__(self, url="tcp://localhost:5555"):
        context = zmq.Context()
        self.socket = context.socket(zmq.REQ)
        self.socket.connect(url)
    def call_echo(self, message, priority=None):
        args = [message]
        if priority:
            args.append(priority)
        self.socket.send_multipart(args)
        code, resp = self.socket.recv_multipart()
        assert code == "200"
        return resp
    def too_long_call(self, message, priority, extrapriority):
        args = [message, priority, extrapriority]
        self.socket.send_multipart(args)
        code, resp = self.socket.recv_multipart()
        assert code == "401"
        return resp


    def test_server(self):
        print "------------------"
        rqmsg = "Hi There"
        print "rqmsg", rqmsg
        print "response", self.call_echo(rqmsg)
        print "------------------"
        time.sleep(2)
        rqmsg = ["Hi There", "very URGENT"]
        print "rqmsg", rqmsg
        print "response", self.call_echo(*rqmsg)
        print "------------------"
        time.sleep(2)
        time.sleep(2)
        rqmsg = []
        print "too_short_call"
        print "response", self.too_long_call("STOP", "VERY URGENT", "TOO URGENT")
        print "------------------"

if __name__ == "__main__":
    ZmqClient().test_server()

玩玩具

启动服务器:

$ python server.py 

现在它运行并等待请求。

现在启动客户端:

$ python client.py 
------------------
rqmsg Hi There
response Echo your not urgent message: 'Hi There'
------------------
rqmsg ['Hi There', 'very URGENT']
response Echo your very URGENT message: 'Hi There'
------------------
too_short_call
response Bad request, 1-2 arguments expected.
------------------

现在试验一下:

  • 首先启动客户端,然后启动服务器
  • 在处理期间停止服务器,稍后重启
  • 启动多个客户

所有这些场景都应由zmq处理,而无需添加额外的Python代码行。

结论

ZeroMQ提供非常方便的远程消息传递解决方案,尝试计算与消息相关的代码行,并与任何其他解决方案进行比较,提供相同级别的稳定性。

会话(属于OP的一部分)可以被视为呼叫的额外参数。如我们所见,多个参数不是问题。

维护会话,可以使用不同的后端,它们可以存在于内存(对于单个服务器实例),数据库或memcache或Redis中。这个答案没有进一步阐述会议,因为它不太清楚,预期会有什么用途。