twsited(5)--不同模块用rabbitmq传递消息
上一章,我们讲到,用redis共享数据,以及用redis中的队列来实现一个简单的消息传递。其实在真实的过程中,不应该用redis来传递,最好用专业的消息队列,我们python中,用到最广泛的就是rabbitmq,虽然它是用erlang开发的,但真的非常好用,经过无数次验证。如果大家不会安装rabbitmq,请看我这篇文章,http://www.cnblogs.com/yueerwanwan0204/p/5319474.html 这篇文章讲解了怎么安装rabbitmq以及简单的使用它。
我们把上一章的图再稍微修改一下,

其实在真实的项目中,也这样,一般来说,利用redis在不同模块之间共享数据,利用rabbitmq来进行消息传递。我们这个项目只做到从web到flask,再到rabbitmq,传递给tcpserver,再下放给具体的tcpclient客户端;其实还可以反向传递,即从tcp的client到tcp服务器,再到rabbitmq,到前端tcp或者前端http,但是这个前端tcp或者http要基于循环模式的,flask肯定不行。我们从下一章开始讲tornado,用tornado来接受,并且做一个websocket,就可以下放下去。
好了,说了这么多,我们来看一下代码,首先,tcpserver这块,我们之前用redis的队列做消息队列,现在修改一下,修改的大概代码如下:
import pika
from pika.adapters import twisted_connection RABBITMQ_HOST = 'localhost'
RABBITMQ_PORT = 5672
RABBITMQ_USERNAME = 'rabbitmq01'
RABBITMQ_PASSWORD = 'rabbitmq01' class RabbitMQ(object):
_connection = None
_channel_receive_from_http = None @staticmethod
@defer.inlineCallbacks
def init_mq(ip_address, port):
credentials = pika.PlainCredentials(RABBITMQ_USERNAME, RABBITMQ_PASSWORD)
parameters = pika.ConnectionParameters(credentials=credentials)
cc = protocol.ClientCreator(reactor, twisted_connection.TwistedProtocolConnection, parameters)
RabbitMQ._connection = yield cc.connectTCP(ip_address, port)
defer.returnValue(1) @staticmethod
@defer.inlineCallbacks
def set_channel_receive_from_back(user_factory):
"""
设置rabbitmq消息接受队列的channel,并且做好循环任务
"""
RabbitMQ._channel_receive_from_http = yield RabbitMQ._connection.channel()
yield RabbitMQ._channel_receive_from_http.queue_declare(queue='front_tcp')
queue_object, consumer_tag = yield
RabbitMQ._channel_receive_from_http.basic_consume(queue='front_tcp', no_ack=True)
l = task.LoopingCall(RabbitMQ.read_from_mq, queue_object, user_factory)
l.start(0.5)
defer.returnValue(1) @staticmethod
@defer.inlineCallbacks
def read_from_mq(queue_object, chat_factory):
"""
读取接受到的消息队列消息,并且处理
"""
ch, method, properties, body = yield queue_object.get() if body:
log.msg('Accept data from http successful!')
chat_factory.process_data_from_mq(body)
defer.returnValue(1)
defer.returnValue(0)
首先,大家要注意一下,由于twisted是异步的,所以不能采用原先阻塞的函数,连接或者接受或者发送消息,所有跟rabbitmq的连接,发送,接受,都要异步化,即都要返回defer对象。因为连接rabbitmq的本质,其实就是socket的网络行为,任何网络行为都有可能被阻塞,一旦阻塞,异步的效率会极其低下。(以后我们写tornado也是这样,一定要返回future对象)。
我看到网上还有很多博客,在接受rabbitmq的消息的时候,居然开了另外一个进程或者线程,有时候这么做,程序运行起来没问题,但涉及到异步的时候,还是会影响效率。都已经用异步的代码了,就不应该大量使用多进程或者多线程。多进程或者多线程,会让cpu调度频繁切换,大量并发的时候,严重影响效率。
详细看上面的代码,简单的解释一下,
init_mq就是初始化消息队列,先加入用户名,密码,返回一个类似与token的东西,然后用twisted客户端来连接rabbitmq,其实就是socket行为,返回一个connection。
set_channel_receive_from_back设置channel,其实就是定义一个管道,我从这个管道接受东西。接受并读取的过程其实就是写一个循环任务,这个循环任务每0.5秒执行一次,你也可以写小一点,0.1秒执行一次,具体的看你需要设置。
read_from_mq就是真正的读取并处理的函数,我这边在read_from_mq中,加了一个参数,就是这个工厂对象,因为接受的时候,一个工厂,就产生一个接受函数。然后读取到消息以后,把消息传递到这个工厂对象的处理方法中,整个环节就完整了。
RabbitMQ的3个方法全是静态方法,所以我没有生成RabbitMQ对象,直接使用这个类本身就可以了。所以在运行的时候,又加了如下代码。
cf = ChatFactory() task1 = task.LoopingCall(cf.check_users_online)
task1.start(3, now=False) task_receive_data_from_mq = task.LoopingCall(cf.receive_from_mq)
task_receive_data_from_mq.start(0.1, now=False) reactor.callLater(0.1, RabbitMQ.init_mq, RABBITMQ_HOST, RABBITMQ_PORT) reactor.callLater(0.5, RabbitMQ.set_channel_receive_from_back, cf) reactor.listenTCP(8124, cf)
reactor.run()
看见我加的代码没有,一个init_mq,一个set_channel_receive_from_back。一个初始化消息队列,初始化好以后,再设置channel,并且开始接受消息。
整个tcpserver这块就算完成了,下面是整个tcpserver的代码
# coding:utf-8
from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor, task, defer, protocol
import struct
import json
from twisted.python import log
import sys
import time
import txredisapi as redis
import pika
from pika.adapters import twisted_connection
log.startLogging(sys.stdout) REDIS_HOST = 'localhost'
REDIS_PORT = 6380
REDIS_DB = 4
REDIS_PASSWORD = 'dahai123' RABBITMQ_HOST = 'localhost'
RABBITMQ_PORT = 5672
RABBITMQ_USERNAME = 'rabbitmq01'
RABBITMQ_PASSWORD = 'rabbitmq01' redis_store = redis.lazyConnectionPool(dbid=4, host='localhost', port=6380, password='dahai123') @defer.inlineCallbacks
def check_token(phone_number, token):
token_in_redis = yield redis_store.hget('user:%s' % phone_number, 'token')
if token != token_in_redis:
defer.returnValue(False)
else:
defer.returnValue(True) class RabbitMQ(object):
_connection = None
_channel_receive_from_http = None @staticmethod
@defer.inlineCallbacks
def init_mq(ip_address, port):
credentials = pika.PlainCredentials(RABBITMQ_USERNAME, RABBITMQ_PASSWORD)
parameters = pika.ConnectionParameters(credentials=credentials)
cc = protocol.ClientCreator(reactor, twisted_connection.TwistedProtocolConnection, parameters)
RabbitMQ._connection = yield cc.connectTCP(ip_address, port)
defer.returnValue(1) @staticmethod
@defer.inlineCallbacks
def set_channel_receive_from_back(user_factory):
"""
设置rabbitmq消息接受队列的channel,并且做好循环任务
"""
RabbitMQ._channel_receive_from_http = yield RabbitMQ._connection.channel()
yield RabbitMQ._channel_receive_from_http.queue_declare(queue='front_tcp')
queue_object, consumer_tag = yield RabbitMQ._channel_receive_from_http.basic_consume(queue='front_tcp', no_ack=True)
l = task.LoopingCall(RabbitMQ.read_from_mq, queue_object, user_factory)
l.start(0.5)
defer.returnValue(1) @staticmethod
@defer.inlineCallbacks
def read_from_mq(queue_object, chat_factory):
"""
读取接受到的消息队列消息,并且处理
"""
ch, method, properties, body = yield queue_object.get() if body:
log.msg('Accept data from http successful!')
chat_factory.process_data_from_mq(body)
defer.returnValue(1)
defer.returnValue(0) class Chat(Protocol):
def __init__(self, factory):
self.factory = factory
self.phone_number = None
self.state = "VERIFY"
self.version = 0
self.last_heartbeat_time = 0
self.command_func_dict = {
1: self.handle_verify,
2: self.handle_single_chat,
3: self.handle_group_chat,
4: self.handle_broadcast_chat,
5: self.handle_heartbeat
}
self._data_buffer = bytes() def connectionMade(self):
log.msg("New connection, the info is:", self.transport.getPeer()) def connectionLost(self, reason):
log.msg("[%s]:断线" % self.phone_number.encode('utf-8'))
if self.phone_number in self.factory.users:
del self.factory.users[self.phone_number] def dataReceived(self, data):
"""
接受到数据以后的操作
"""
self._data_buffer += data while True:
length, self.version, command_id = struct.unpack('!3I', self._data_buffer[:12]) if length > len(self._data_buffer):
return content = self._data_buffer[12:length] if command_id not in [1, 2, 3, 4, 5]:
return if self.state == "VERIFY" and command_id == 1:
self.handle_verify(content) if self.state == "DATA":
self.handle_data(command_id, content) self._data_buffer = self._data_buffer[length:] if len(self._data_buffer) < 12:
return def handle_heartbeat(self, content):
"""
处理心跳包
"""
self.last_heartbeat_time = int(time.time()) @defer.inlineCallbacks
def handle_verify(self, content):
"""
验证函数
"""
content = json.loads(content)
phone_number = content.get('phone_number')
token = content.get('token') result = yield check_token(phone_number, token) if not result:
send_content = json.dumps({'code': 0})
self.send_content(send_content, 101, [phone_number])
length = 12 + len(send_content)
version = self.version
command_id = 101
header = [length, version, command_id]
header_pack = struct.pack('!3I', *header)
self.transport.write(header_pack + send_content)
return if phone_number in self.factory.users:
log.msg("电话号码<%s>存在老的连接." % phone_number.encode('utf-8'))
self.factory.users[phone_number].connectionLost("")
self.factory.users.pop(phone_number) log.msg("欢迎, %s!" % (phone_number.encode('utf-8'),))
self.phone_number = phone_number
self.factory.users[phone_number] = self
self.state = "DATA" send_content = json.dumps({'code': 1}) self.send_content(send_content, 101, [phone_number]) def handle_data(self, command_id, content):
"""
根据command_id来分配函数
"""
self.command_func_dict[command_id](content) def handle_single_chat(self, content):
"""
单播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_to = content.get('chat_to')
chat_content = content.get('chat_content')
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) self.send_content(send_content, 102, [chat_to]) def handle_group_chat(self, content):
"""
组播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_to = content.get('chat_to')
chat_content = content.get('chat_content')
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) phone_numbers = chat_to
self.send_content(send_content, 103, phone_numbers) def handle_broadcast_chat(self, content):
"""
广播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_content = content.get('chat_content')
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) phone_numbers = self.factory.users.keys()
self.send_content(send_content, 104, phone_numbers) def send_content(self, send_content, command_id, phone_numbers):
"""
发送函数
"""
length = 12 + len(send_content)
version = self.version
command_id = command_id
header = [length, version, command_id]
header_pack = struct.pack('!3I', *header)
for phone_number in phone_numbers:
if phone_number in self.factory.users.keys():
self.factory.users[phone_number].transport.write(header_pack + send_content)
else:
log.msg("Phone_number:%s 不在线." % phone_number.encode('utf-8')) class ChatFactory(Factory):
def __init__(self):
self.users = {} def buildProtocol(self, addr):
return Chat(self) def check_users_online(self):
for key, value in self.users.items():
if value.last_heartbeat_time != 0 and int(time.time()) - value.last_heartbeat_time > 4:
log.msg("[%s]没有检测到心跳包,主动切断" % key.encode('utf-8'))
value.transport.abortConnection() @defer.inlineCallbacks
def receive_from_mq(self):
data = yield redis_store.rpop('front_tcp')
if data:
log.msg("接受到来自消息队列的消息:", data)
self.process_data_from_mq(data) def process_data_from_mq(self, data):
loads_data = json.loads(data)
command_id = loads_data.get('command_id')
phone_numbers = loads_data.get('chat_to')
chat_from = loads_data.get('chat_from')
chat_content = loads_data.get('chat_content') content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) self.send_content(content, command_id, phone_numbers) def send_content(self, send_content, command_id, phone_numbers):
"""
发送函数
"""
length = 12 + len(send_content)
version = 1100
command_id = command_id
header = [length, version, command_id]
header_pack = struct.pack('!3I', *header)
for phone_number in phone_numbers:
if phone_number in self.users.keys():
self.users[phone_number].transport.write(header_pack + send_content)
else:
log.msg("Phone_number:%s 不在线." % phone_number.encode('utf-8')) cf = ChatFactory() task1 = task.LoopingCall(cf.check_users_online)
task1.start(3, now=False) task_receive_data_from_mq = task.LoopingCall(cf.receive_from_mq)
task_receive_data_from_mq.start(0.1, now=False) reactor.callLater(0.1, RabbitMQ.init_mq, RABBITMQ_HOST, RABBITMQ_PORT) reactor.callLater(0.5, RabbitMQ.set_channel_receive_from_back, cf) reactor.listenTCP(8124, cf)
reactor.run()
下面是web方面的代码,web也是,之前用redis很简单的做,现在换到rabbitmq,由于这个例子很简单,所以我就在request过程中初始化rabbitmq了,整个代码就非常简单了,就是一个发送函数而已。
# coding:utf-8
from flask import Flask, request, jsonify, g, render_template, redirect, url_for, session, current_app
from app.model import User, db_session
import json
from . import web
import pika RABBITMQ_HOST = 'localhost'
RABBITMQ_PORT = 5672
RABBITMQ_USERNAME = 'rabbitmq01'
RABBITMQ_PASSWORD = 'rabbitmq01' @web.teardown_request
def handle_teardown_request(exception):
db_session.remove() @web.route('/send-command', methods=['GET', 'POST'])
def send_command():
if request.method == 'GET':
users = User.query.all()
return render_template('web/send-command.html', users=users)
else:
data = request.get_json()
command_id = data.get('command_id')
chat_from = ''
chat_to = data.get('chat_to')
chat_content = data.get('content') if not chat_to or not chat_content or not command_id:
return jsonify({'code': 0, 'message': '信息不完整'}) send_data = json.dumps(dict(command_id=command_id, chat_from=chat_from, chat_to=chat_to, chat_content=chat_content))
# current_app.redis.lpush('front_tcp', send_data) credentials = pika.PlainCredentials(RABBITMQ_USERNAME, RABBITMQ_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(host=RABBITMQ_HOST, credentials=credentials, port=RABBITMQ_PORT))
channel = connection.channel()
channel.queue_declare(queue='front_tcp') channel.basic_publish(exchange='',
routing_key='front_tcp',
body=send_data) print "send json_data to front_tcp, the data is ", send_data connection.close() return jsonify({'code': 1, 'message': '发送成功'})
所有代码更换完成,看一下具体效果吧

web上先发送一个消息。
随便启动一个客户端,看看接受吧。

看见没有,整个过程就全部打通了。
总结:整个twisted就讲到这了,大家可以看到,twisted我也不是特别熟悉,所以我一共就用了5章把它讲完。从下一章开始,我开始讲tornado,利用tornado做tcpserver,tcpclient,websocket服务器,因为tornado的源码比较好读,所以我重点也会放在tornado上。最近我在看reactjs,届时我会用稍微好看一点的图形界面,来做websocket页面,tornado这个库真正做到small strong smart,我一直喜欢小而精的库。总之,我重点会放在tornado上,希望大家到时候会喜欢。
twsited(5)--不同模块用rabbitmq传递消息的更多相关文章
- rabbitmq(中间消息代理)在python中的使用
在之前的有关线程,进程的博客中,我们介绍了它们各自在同一个程序中的通信方法.但是不同程序,甚至不同编程语言所写的应用软件之间的通信,以前所介绍的线程.进程队列便不再适用了:此种情况便只能使用socke ...
- python的pika模块操作rabbitmq
上一篇博文rabbitmq的构架和原理,了解了rabbitmq的使用原理,接下来使用python的pika模块实现使用rabbitmq. 环境搭建 安装python,不会的请参考Linux安装配置py ...
- rabbitmq高级消息队列
rabbitmq使用 什么是消息队列 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串,也可以很复杂,可以包含嵌入对象. 消息队列是一种应用间的通信方式,消息发送后 ...
- .Net RabbitMQ之消息通信 构建RPC服务器
1.消息投递服务 RabbitMQ是一种消息投递服务,怎么理解这句话呢?即RabbitMQ即不是消息的生产者,也是消息的消费者.他就像现实生活中快递模式,消费者在电商网站上下单买了一件商品,此时对应的 ...
- RabbitMQ的消息确认机制
一:确认种类 RabbitMQ的消息确认有两种. 一种是消息发送确认.这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递.发送确认分为两步,一是确认是否到达交换器,二是 ...
- RabbitMq初探——消息持久化
消息持久化 前言 通过上一节,我们知道,有消息确认机制,保证了当消费者进程挂掉后,消息的不丢失. 但是如果rabbitmq挂掉呢?它的队列和消息都会丢失的.为了保证消息在rabbitmq挂掉重启后不丢 ...
- 【python】-- RabbitMQ 队列消息持久化、消息公平分发
RabbitMQ 队列消息持久化 假如消息队列test里面还有消息等待消费者(consumers)去接收,但是这个时候服务器端宕机了,这个时候消息是否还在? 1.队列消息非持久化 服务端(produc ...
- RabbitMQ (十一) 消息确认机制 - 消费者确认
由于生产者和消费者不直接通信,生产者只负责把消息发送到队列,消费者只负责从队列获取消息(不管是push还是pull). 消息被"消费"后,是需要从队列中删除的.那怎么确认消息被&q ...
- C#调用RabbitMQ实现消息队列
前言 我在刚接触使用中间件的时候,发现,中间件的使用并不是最难的,反而是中间件的下载,安装,配置才是最难的. 所以,这篇文章我们从头开始学习RabbitMq,真正的从头开始. 关于消息队列 其实消息队 ...
随机推荐
- 【UIView与控件】
- cryptopp开源库的使用(一):md5加密
项目总是各种新需求,最近遇到需要对字符串进行md5加密,确保传输字符串的有效性. 考虑到跨平台性和通用性,选择了cryptopp开源库,这里主要是用静态库调用. 1.引入头文件和lib库 #inclu ...
- 项目管理模式之如何去除SVN标记
原问地址:http://blog.csdn.net/djcken/article/details/7916986 当项目不需要SVN标志的时候,我们一般怎么办哪??可能很多人设置Window ...
- A - 敌兵布阵 - hdu 1166
Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些 ...
- xshell中启动linux图形界面
使用root用户执行xhost + IP为客户端机器IP,使用远程登录用户执行 DISPLAY=IP:0.0;export DISPLAY; 使用远程登录的用户执行: xhost +
- SpringMVC+SwfUpload进行多文件同时上传
由于最近项目需要做一个多文件同时上传的功能,所以好好的看了一下各种上传工具,感觉uploadify和SwfUpload的功能都比较强大,并且使用起来也很方便.SWFUpload是一个flash和js相 ...
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- SQL基础--> 约束(CONSTRAINT)
--============================= --SQL基础--> 约束(CONSTRAINT) --============================= 一.几类数据完 ...
- 混血儿爹妈要混的远,数据库与WEB分离,得混的近
最近搞了个漫画网站,放在香港VPS,由于内存不够,把数据库移到了阿里云,混的远了点,没缓存的时候网站打开速度慢了1秒左右.笨狗漫画:http://www.bengou8.com 底部有sql时间cop ...
- 动态规划+滚动数组 -- POJ 1159 Palindrome
给一字符串,问最少加几个字符能够让它成为回文串. 比方 Ab3bd 最少须要两个字符能够成为回文串 dAb3bAd 思路: 动态规划 DP[i][j] 意味着从 i 到 j 这段字符变为回文串最少要几 ...