上一章,我们讲到,用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. 数据库安全之TDE列加密

    透明数据加密(Transparent Data Encryption) TDE - 基于列的加密 由于有了Oracle的TDE-基于列的加密,你所要做的只是定义需要加密的列,Oracle将为包含加密列 ...

  2. motan源码分析六:客户端与服务器的通信层分析

    本章将分析motan的序列化和底层通信相关部分的代码. 1.在上一章中,有一个getrefers的操作,来获取所有服务器的引用,每个服务器的引用都是由DefaultRpcReferer来创建的 pub ...

  3. XTU1199:Number Game

    题目描写叙述 给你一个有N个数的集合S和一个数X,推断是否存在S的一个子集,子集里的数的最小公倍数正好是X. 输入 第一行是数据组数T. 接下来有多组数据,每组数据包括两行: 第一行有2个数N和X,1 ...

  4. 招一位安防软件project师,嵌入式开发project师

    岗位职责 1.负责海思平台IPC产品应用层软件设计及维护 2.私有平台协议对接及为第三方提供技术支持. 任职资格: 1.较强的学习.领悟能力,能够高速熟悉公司现有代码. 2.熟练掌握C.C++开发语言 ...

  5. C#&Sql获取中文字符拼音首字母的方法

    C#获取字符拼音首字母,可以存储在数据库中以备将来按字母搜索的需求. public static string GetAc(string s) { try { string temp = Servic ...

  6. 【网络流#3】hdu 1532 - Dinic模板题

    输入为m,n表示m条边,n个结点 记下来m行,每行三个数,x,y,c表示x到y的边流量最大为c 这道题的模板来自于网络 http://blog.csdn.net/sprintfwater/articl ...

  7. tomcat结合nginx使用 基础教程

    相信很多人都听过nginx,这个小巧的东西慢慢地在吞食apache和IIS的份额.那究竟它有什么作用呢?可能很多人未必了解. 说到反向代理,可能很多人都听说,但具体什么是反向代理,很多人估计就不清楚了 ...

  8. Apache调优

    1.调整文件描述符大小,默认Linux系统的文件描述符是1024,对于squid等一些服务来说                在高负载的情况下这些文件描述符是远远不够的,所以在部署该类服务器时修改文件 ...

  9. 安装Microsoft oneDrive(原skyDrive)

    oneDrive下载地址:https://onedrive.live.com/about/zh-cn/download/ 安装时报错:Error 0x80040ca0 解决方案:关闭安装程序,按下面的 ...

  10. javascript:void(0)知多少

    在做页面时,如果想做一个链接点击后不做任何事情,或者响应点击而完成其他事情,可以设置其属性 href = "#",但是,这样会有一个问题,就是当页面有滚动条时,点击后会返回到页面顶 ...