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# 重新实现这个需求。