Python3实现websocket和rabbitmq的简单连接

websocket和rabbitmq的简单连接

近来无事,回头整理了一下以前写的项目。这个所谓的连接websocket和rabbitmq的“桥梁”其实只是一个大项目的小小模块而已。因为设计之初的缺陷,所以不得已使用这个小型“服务器”来完成需求。这里说一句,web端是使用django-channels写的基于websocket的web应用,因为数据的特殊性,所以需要与一个基于WPF的桌面应用程序数据对接。但是由于这个WPF程序完全是依靠rabbitmq消息中间件来传递数据的,所以无论这个WPF的数据接受还是发送,都完全依赖rabbitmq。话再说回来,虽然当初花了点时间写出了这个,但是因为事出仓促,而且功力不到家,所以运行一个礼拜后,正式被枪毙。因此这个只是一个“半成功”的服务程序。因为能用,而且效果还行,所以是成功的;因为效率不高,而且不稳定,所以是失败的。

大致的设计

1、接收与发送

对于连接websocket和rabbitmq,自然两端的接收与发送都是必要的。
为了便于区别,面向websocket的是publisher(因为这个服务是部署在与rabbitmq同一服务器上的),面向rabbitmq的则是consumer。
publisher与consumer有极其相似的结构与功能。

1、consumer

首先定义Rounting_key

EXCHANGE = 'example'
EXCHANGE_TYPE = 'direct'
QUEUE = 'listener'

ROUTING_KEYS = [
    "A_new_example",
    "A_update_example",
    "A_delete_example",
    "B_new_example",
    "B_update_example",
    "B_delete_example",
]

然后运用pika模块的ioloop进行监听循环

# consumer可以封装为一个类
def __init__(self, amqp_url):
    self._connection = None
    self._channel = None
    self._closing = False
    self._consumer_tag = None
    self._url = amqp_url

def connect(self):
    return pika.SelectConnection(pika.URLParameters(self._url),
                                 self.on_connection_open,
                                 stop_ioloop_on_close=False)

def run(self):
    self._connection = self.connect()
    self._connection.ioloop.start()

然后定义on_message作为监听回调函数

def start_consuming(self):
    self.add_on_cancel_callback()
    self._consumer_tag = self._channel.basic_consume(self.on_message, self.QUEUE)

def on_message(self, unused_channel, basic_deliver, properties, body):
    routing_key = basic_deliver.routing_key
    # desk为websocket端一个重要数据,乃ROUTING_KEYS上的A、B
    desk = routing_key.split("_")[0].upper() 
    type_ = "_".join(routing_key.split("_")[1:]).upper()

    body = json.loads(body)
    
    # 因为数据在两端都有传递和接收,所以from_web和is_bridge都是用于两端的验证
    if body.get("from_web"):
        content = body.get("orig_content")
        content["is_bridge"] = True
        try:
            # 此处监听到的是web端传来后传进rabbitmq然后又由rabbitmq广播的数据
            # 打上is_bridge的标签,传回
            # websocket端会进行验证、补充或是丢弃
            ws = websocket.create_connection(config.websocket_address)
            ws.send(json.dumps(content))
            ws.close()
        except Exception as err:
            LOGGER.exception("WebSocket connect FAILED")
    else:
        # 阻止new_bwic重复发送数据
        # ...验证方式省略
        # is_new代表之前的验证通过
        # 此处将从rabbitmq监听到的新数据发送给websocket端
        if is_new:
            if not body.get("Desk"):
                body["Desk"] = desk
            try:
                # 此处将数据进行了转换
                context = DataManager(SEND).dispatch(body, type_)
                # 这里对websocket使用短连接,但是对rabbitmq是长连接
                ws = websocket.create_connection(config.websocket_address)
                ws.send(json.dumps(context))
                result = json.loads(ws.recv())
                self.acknowledge_message(basic_deliver.delivery_tag)
                ws.close()
            except Exception as err:
                LOGGER.exception("WebSocket connect FAILED")
    # 因为ioloop依旧是死循环,所以sleep1秒充当心跳
    time.sleep(1)
2、publisher

publisher与consumer结构几乎一样,我为了方便,将其中重要的recive方法提出。

也是因为方便,将循环写成了死循环。依旧设置心跳包。

因为websocket没有pika的ioloop底层,所以写一个ioloop还是比较复杂的,可以使用flask或是直接使用tornado来实现,将服务挂载在tornado上即可。

另外,这个publisher连接websocket和rabbitmq都是使用短连接,这也是不稳定和开销大的原因之一。下一篇我将给出使用 C# 实现这些功能的设计与代码。

def recive():
    global reconnect_count
    while True:
        try:
            # 这里依旧是短连接,其实不可。
            # 下一篇我将给出C#实现的方法
            ws = websocket.create_connection(address, sslopt={"check_hostname": False})
            try:
                result = json.loads(ws.recv())
                if result.get("is_web", None) is True:
                    if not result.get("is_bridge", None):
                        # 数据的转换
                        context, routing_key = DataManager(RECEIVE).dispatch(result)
                       
                       	# 连接rabbitmq,短连接
                        connection = pika.BlockingConnection(parameters)
                        channel = connection.channel()
                        channel.exchange_declare(exchange='bwic', exchange_type='direct')
                        channel.basic_publish(exchange='bwic', routing_key=routing_key, body=json.dumps(context))

                        connection.close()
                        ws.close()
                    else:
                        LOGGER.warning("This Message is from bridge, so ignore it")
                        ws.close()
                else:
                    LOGGER.warning("This Message is not from WEB, so ignore it")
                    ws.close()
            except Exception as err:
                ws.close()

        except Exception as err:
            LOGGER.exception("WebSocket connect FAILED")
            # 连接失败便加一,三次失败连接以上,将停止服务器
            reconnect_count = reconnect_count + 1
            if reconnect_count > 3:
                LOGGER.warning("SHOULD Notice the PERSON in Charge that THE CONNECTION HAS SOMETHING WRONG")
                LOGGER.warning("The Server is CLOSING DOWN...")
                break
                pass  # 停止连接,发送消息给负责人
2、数据的转换

数据的转换就不多说了,因为两端因为设计的原因,其字段、字段类型等等都不尽相同,所以需要对其进行二次处理。这里贴出了基本的代码,是为了探讨一下类的设计。父类不仅仅是作为抽象(因为可以抽象的东西没什么,最多提供接口),而是作为“转发”,在调用阶段将子类隐藏,而直接使用父类。虽说有些多此一举,但是在某些特殊情况下还是有好处的。

SEND = "DataManager-Send"
RECEIVE = "DataManager-Receive"


class DataManager(object):
    def __init__(self, TYPE):
        self.TYPE = TYPE

    def dispatch(self, content, *k):
        if self.TYPE == SEND:
            return DataManagerSend.dispatch(content, *k)
        elif self.TYPE == RECEIVE:
            return DataManagerReceive.dispatch(content)
        else:
            pass

    def DELETE_EXAMPLE(self, *k):
        pass

    def NEW_EXAMPLE(self, *k):
        pass

    def UPDATE_EXAMPLE(self, *k):
        pass


class DataManagerSend(DataManager):
    def __init__(self, content, TYPE=None):
        super().__init__(TYPE)
        self.content = content

    @classmethod
    def dispatch(cls, content, *k, ):
        manager = cls(content)

        if hasattr(manager, k[0]):
            method = getattr(manager, k[0])

            return method(content)
        else:
            raise NotImplementedError('{} not implemented'.format(type_))

    def DELETE_EXAMPLE(self, content):
        pass

    def NEW_EXAMPLE(self, content):
        pass

    def UPDATE_EXAMPLE(self, content):
        pass


class DataManagerReceive(DataManager):
    def __init__(self, content, TYPE=None):
        super().__init__(TYPE)
        self.content = content

    @classmethod
    def dispatch(cls, content, *k):
        manager = cls(content)

        type_ = content.get('type').upper()
        
        if hasattr(manager, type_):
            method = getattr(manager, type_)
            return method(content)
        else:
            raise NotImplementedError('{} not implemented'.format(type_))

    def DELETE_EXAMPLE(self, *k):
        pass

    def NEW_EXAMPLE(self, *k):
        pass

    def UPDATE_EXAMPLE(self, *k):
        pass

结语

这篇文章完全是为了记录一下当时的需求实现,没有其他意义。

不过反思过后发现原先的方法极其不稳定也不健壮。

因此在下篇,我将使用 C# 重新实现这个需求。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebSocket 和 RabbitMQ 可以结合使用来实现实时消息推送。WebSocket 是一种基于 TCP 的协议,它允许在客户端和服务器之间建立持久的双向通信通道。而 RabbitMQ 是一个消息代理和队列管理系统,可以实现消息的可靠传输和分发。 下面是使用 WebSocket 和 RabbitMQ 实现实时消息推送的一般步骤: 1. 配置 WebSocket 服务器:在后端应用程序中,你需要配置一个 WebSocket 服务器,用于接收和处理客户端的 WebSocket 连接请求。可以使用 Spring Boot 中的 Spring WebSocket 或其他 WebSocket 框架进行配置。 2. 配置 RabbitMQ:在后端应用程序中,你需要配置 RabbitMQ 的连接信息,并创建一个或多个交换机和队列。可以使用 RabbitMQ 的 Java 客户端库进行配置。 3. 监听 RabbitMQ 消息:在后端应用程序中,你需要监听 RabbitMQ 中指定队列的消息。当有新的消息到达时,通过 WebSocket 服务器将消息推送给客户端。 4. 前端连接 WebSocket:在前端应用程序中,你需要使用 JavaScript 的 WebSocket API 连接到后端的 WebSocket 服务器。 5. 接收消息并更新 UI:在前端应用程序中,当接收到 WebSocket 服务器推送的消息时,你可以在界面上实时展示或处理这些消息。 通过结合使用 WebSocket 和 RabbitMQ,你可以实现实时、双向的消息通信,并将消息推送给多个客户端。这种方式适用于需要实时更新消息的应用场景,如聊天应用、实时监控等。需要根据具体的技术栈和需求进行相应的配置和开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值