上一章,我们讲到,用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传递消息的更多相关文章

  1. rabbitmq(中间消息代理)在python中的使用

    在之前的有关线程,进程的博客中,我们介绍了它们各自在同一个程序中的通信方法.但是不同程序,甚至不同编程语言所写的应用软件之间的通信,以前所介绍的线程.进程队列便不再适用了:此种情况便只能使用socke ...

  2. python的pika模块操作rabbitmq

    上一篇博文rabbitmq的构架和原理,了解了rabbitmq的使用原理,接下来使用python的pika模块实现使用rabbitmq. 环境搭建 安装python,不会的请参考Linux安装配置py ...

  3. rabbitmq高级消息队列

    rabbitmq使用 什么是消息队列 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串,也可以很复杂,可以包含嵌入对象. 消息队列是一种应用间的通信方式,消息发送后 ...

  4. .Net RabbitMQ之消息通信 构建RPC服务器

    1.消息投递服务 RabbitMQ是一种消息投递服务,怎么理解这句话呢?即RabbitMQ即不是消息的生产者,也是消息的消费者.他就像现实生活中快递模式,消费者在电商网站上下单买了一件商品,此时对应的 ...

  5. RabbitMQ的消息确认机制

    一:确认种类 RabbitMQ的消息确认有两种. 一种是消息发送确认.这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递.发送确认分为两步,一是确认是否到达交换器,二是 ...

  6. RabbitMq初探——消息持久化

    消息持久化 前言 通过上一节,我们知道,有消息确认机制,保证了当消费者进程挂掉后,消息的不丢失. 但是如果rabbitmq挂掉呢?它的队列和消息都会丢失的.为了保证消息在rabbitmq挂掉重启后不丢 ...

  7. 【python】-- RabbitMQ 队列消息持久化、消息公平分发

    RabbitMQ 队列消息持久化 假如消息队列test里面还有消息等待消费者(consumers)去接收,但是这个时候服务器端宕机了,这个时候消息是否还在? 1.队列消息非持久化 服务端(produc ...

  8. RabbitMQ (十一) 消息确认机制 - 消费者确认

    由于生产者和消费者不直接通信,生产者只负责把消息发送到队列,消费者只负责从队列获取消息(不管是push还是pull). 消息被"消费"后,是需要从队列中删除的.那怎么确认消息被&q ...

  9. C#调用RabbitMQ实现消息队列

    前言 我在刚接触使用中间件的时候,发现,中间件的使用并不是最难的,反而是中间件的下载,安装,配置才是最难的. 所以,这篇文章我们从头开始学习RabbitMq,真正的从头开始. 关于消息队列 其实消息队 ...

随机推荐

  1. Java---IO加强(2)

    转换流 ★转换流功能1:充当字节流与字符流之间的桥梁 需求:模拟英文聊天程序,要求: (1) 从键盘录入英文字符,每录一行就把它转成大写输出到控制台: (2) 保存聊天记录到字节流文件. 要求1的设计 ...

  2. LinGo:装货问题——线性规划,整数规划,1988年美国数模B题

    7种规格的包装箱要装有两辆铁路平板车上去,包装箱的宽和高相同,但厚度(t,以cm计)和重量(以kg计)不同, 表A-1给出了每包装箱的厚度.重量和数量,每辆车有10.2m长的地方用来装包装箱(像面包片 ...

  3. Java中String的哈希值计算

    下面都是从String类的源码中粘贴出来的 private int hash; // Default to 0 public int hashCode() { int h = hash; if (h ...

  4. Matlab绘制三维图形以及提示框

    1.首先,在编辑区输入如下代码 >> [x,y] = meshgrid([-100,0.1,100]); >> z = sqrt(x.^2 + y.^2); >> ...

  5. Selenium 初见

    Selenium名字的来源 在这里,我还想说一下关于Selenium名字的来源,很有意思的: >:Selenium的中文名为“硒”,是一种化学元素的名字,它对汞 (Mercury)有天然的解毒作 ...

  6. PHPExcell单元格中某些时间格式的内容不能正确获得的处理办法

    今天在写导入功能的时候某个时间格式的单元格内容不能正确获得,得出的是一串非时间戳的数字. 此时可以使用PHPExcell中自带的方法进行处理:PHPExcel_Shared_Date::ExcelTo ...

  7. wxPython学习笔记(初识)

    今天正式开始学习wxPython,基于对类的不熟悉,理解有点生硬,但还是做了些笔记. 1.是什么组成了一个wxpython程序? 一个wxpython程序必须有一个application(wx.App ...

  8. 一不小心写了个bootstrap风格下拉控件 JqueryUI + bootstrap

    受够了EasyUI的封闭,Bootstrap虽然华丽但是功能太渣,闲着无聊写个下拉控件玩玩吧,不喜勿喷哈... 第一步:先设计下我的下拉控件的样子 1.既然是bootstrap风格的,我想应该是这样的 ...

  9. ionic上拉加载更多解决方法

    第一步: $scope.hasmore = true;//是否允许上拉加载 $scope.num = 8;//显示条数 第二步://查询显示内容,查出所有的 $scope.Group = functi ...

  10. IBinder对象在进程间传递的形式(一)

    命题 当service经常被远程调用时,我们经常常使用到aidl来定一个接口供service和client来使用,这个事实上就是使用Binder机制的IPC通信.当client bind servic ...