说明[编辑]
本文会结合一个例子简要的说明activemq的c++客户端的使用方法
Apache ActiveMQ简介[编辑]
Apache ActiveMQ是一个开源的消息服务器,实现了JMS规范,支持多语言客户端和协议。
ActiveMQ-CPP[编辑]
ActiveMQ-CPP是c++客户端,目前支持 OpenWire and Stomp协议,两者都可以通过TCP和SSL,目前支持failover
JMS简介[编辑]
说明:本段内容主要摘自《ActiveMQ in Action》一书
什么是消息中间件(Message-Oriented Middleware, MOM)?[编辑]
MOM是一组中间件,用来构建分布式系统,它的特性包括:异步,松耦合,可靠,安全和可扩展等等。发送者和接收者进行异步通讯,发送者只需要把消息发送到MOM,不需要知道谁是接收者,MOM保证消息的送达;同样的,接收者也不需要关心发送者。
什么是JMS[编辑]
在JMS出现之前,出现了很多MOM,它们各自采用自己的协议通讯,这就导致客户端代码与特定的MOM绑定。如果要切换到其它的MOM,那么需要重写客户端代码。因此Sun联合了很多MOM的提供商制定了一套Java的API规范,所有与之兼容的实现都必须实现这个API。 这样客户端代码不需要任何修改就可以从一个MOM移植到另外一个MOM(前提当然是只使用标准JMS的API,如果你使用了某个MOM特殊的feature,那么就没法保证移植)。最早的版本是1998年完成的1.0,之后2002年修订出了1.1,这个版本是目前最流行的版本。 2013年发布了最新的2.0版,不过目前ActiveMQ还未支持。
JMS基本概念[编辑]
Producer[编辑]
消息发送者
Consumer[编辑]
消息接收者
JMS Provider[编辑]
JMS的实现者,比如ActiveMQ
Message[编辑]
消息包括消息头(Header)和内容 1. 头部包括JMS标准定义的header和用户自定义的属性(Property)
有些头部是客户端代码的send方法自动设置的,比如:
1.1 JMSDestination
消息发送的地方,比如一个队列。
1.2 JMSDeliveryMode
Persistent -- 这样的消息会持久化到磁盘,即使ActiveMQ挂掉也不会丢失消息。
Nonpersistent 非持久化,性能更快,但是有丢消息的风险。
1.3 JMSExpiration
可以通过MessageProducer.setTimeToLive() 来设置消息的TTL,如果消息超过TTL还没被消费,那么就会被丢弃。
1.4 JMSMessageID
唯一ID
1.5 JMSPriority
消息优先级,0-4是普通优先级,5-9是高优先级。JMS规范并没有强制JMS Provider实现优先级,但大部分Provider都实现了。参考ActiveMQ的优先级
有些头部是可选的:
1.6 JMSCorrelationID
这个头部经常用来把响应消息和请求消息关联起来,比如请求者发送一个消息(比如有一个唯一的JMSMessageID=req001),响应者处理完这个消息后生成一条响应消息,把JMSCorrelationID设置成req001,那么请求者就可以从响应队列中筛选出
自己的响应。
1.7 JMSReplyTo
这个头部一般也用于请求响应,并且提示响应者把响应发送到指定的JMSDestination。注意,这只是一个提示,响应者完全可以不理会,而把响应发到其它的地方。
【注:为什么需要JMSReplyTo?对于有些请求响应任务,比如P2P的,我们可以在请求者和响应者之间约定,比如请求者把请求发送到名称为”request“的队列,响应者把响应发送的”response“的队列,然后双方通过JSMCorrelationID唯一标识
一对请求/响应。但有的情况,比如请求者不一定自己接受消息,它可能让别人接收消息,那么它可以知道JMSReplyTo】
1.8 JMSType
消息的类型,指定消息是什么类型的,比如是个字符串,比如是个整数。注意:JMS规范没有规定JMSType一定和实际内容匹配,有可能JMSType和实际内容不符合。
还有一下头部是非规范的,有些JMS Provider提供了,有些没有。
1.9 JMSRedelivered
告诉消费者,这个消息是一个重发的消息,比如某个消息发送给了一个消费者,但是在它确认之前这个消费者挂了,那么这个消息就可能发送给其它的消费者。这个头部提醒消费者如果操作不是幂等的,那么就要注意。比如消费者的操作是把某个全局计数器
加1,如果这个消息先发给消费者1,它成功加1,然后还没确认就挂了,然后这个消息重发给消费者2,它又加1,那么就出现错误。
消息头可以用来筛选消息,比如我需要JMSCorrelationID等于某个值的消息。但是没有办法根据消息内容来筛选消息。具体参考:此页面的Message Selectors部分
2. 消息内容(Body)就是实际的内容
JMS定义了:
2.1 Message
无任何内容,可以设置头部。一般用于事件通知。
2.2 TextMessage
字符串
2.3 MapMessage
Java Map,Key是String,value是java原始类型,比如int,float,也可以是字符串。
2.4 BytesMessage
字节数组,具体意义需要双方约定。【对于不同语言的通讯,使用这个很合适,比如使用Protobuf,Java消息发送者把消息内容变成字节数组,c++消息接收者把字节数组反序列化成对象】
2.5 StreamMessage
Java.io.InputStream/OutputStream
2.6 ObjectMessage
负责的Java对象,需要实现java.io.serializable。由于Java的序列化很臃肿,所以效率不高,但是跨平台(双方都是java),对象直接有复杂的循环引用关系(对应c++的术语是Deep Copy)也能正确处理。
JMS客户端基本概念[编辑]
JMSConnection[编辑]
代表一个客户端到JSM Provider的连接,一般包括一个TCP的socket,因此比较”重“,建议同一个进程的不同线程共用一个Connection(使用一个Connection创建出不同的session)。当然如果你觉得一个Socket不能发挥网络的性能,那么可以 多使用一些Connection,比如每10个线程共用一个Connection
Session[编辑]
一个”会话“,不是线程安全的,因此每个线程都必须有自己的Session,一般通过Connection创建出来
Destination[编辑]
目的地,比如某个队列,一般通过session来创建或者获得
Producer[编辑]
生产者,我们这里只介绍P2P的模式,所以可以等价为消息发送者(Sender)
Consumer[编辑]
消费者,我们这里只关心P2P的模式,所以可以等价为消息接收者(Receiver)
CMS ActiveMQ的c++客户端[编辑]
具体请阅读文档 我这里只是介绍一下安装编译和运行一个简单的例子
Build[编辑]
具体参考文档,我这里只是介绍我的环境(Ubuntu)的安装,其它*nix应该是类似,依赖可以使用包管理器安装,比如debian和yum/rpm,如果没有可以从源代码按照。其他系统比如Windows请参考文档。 1. 下载并解压源代码
请在这里下载最新版本,我这里使用的是3.8.2 我的源代码解压到/home/lili/soft/activemq-cpp-library-3.8.2/,以后用环境变量$ACTIVEMQ-CPP表示
2. 依赖
2.1 libuuid
直接 apt-get install
2.2 CppUnit
直接 apt-get install
2.3 APR
我下载1.5.0的源代码并且使用了默认的安装流程:./configure && make && sudo make install【由于默认的会把lib放到/usr/lib下所以需要root权限】,如果你没有root权限,./configure --prefix=/some/path
3. 编译
3.1 生产configure脚本
./autogen.sh
3.2 ./configure
3.3 make 如果cpu较多,可以用make -j4加速
3.4 make install
简单的Sender的例子[编辑]
1. 头文件
需要引用 $ACTIVEMQ-CPP/src/main,比如在gcc下是 -I/home/lili/soft/activemq-cpp-library-3.8.2/src/main
2. 链接
需要链接activemq-cpp库,比如在gcc下是 -lactivemq-cpp,如果so不在标准搜索路径下,可能要-L指定动态库的所在目录。
3. 运行
如果找不到动态库,可能需要增加环境变量 LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib,因为我的系统默认安装到/usr/local/lib下了,运行时却不会去这里找。
4. 源代码
我这里使用的是linux35上的一个activemq,你也可以自己安装activemq 核心的代码其实非常少,都在run函数里面
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <decaf/lang/Thread.h>
#include <decaf/lang/Runnable.h>
#include <decaf/util/concurrent/CountDownLatch.h>
#include <decaf/lang/Long.h>
#include <decaf/util/Date.h>
#include <activemq/core/ActiveMQConnectionFactory.h>
#include <activemq/util/Config.h>
#include <activemq/library/ActiveMQCPP.h>
#include <cms/Connection.h>
#include <cms/Session.h>
#include <cms/TextMessage.h>
#include <cms/BytesMessage.h>
#include <cms/MapMessage.h>
#include <cms/ExceptionListener.h>
#include <cms/MessageListener.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <memory>
using namespace activemq;
using namespace activemq::core;
using namespace decaf;
using namespace decaf::lang;
using namespace decaf::util;
using namespace decaf::util::concurrent;
using namespace cms;
using namespace std;
////////////////////////////////////////////////////////////////////////////////
class SimpleProducer : public Runnable {
private:
Connection* connection;
Session* session;
Destination* destination;
MessageProducer* producer;
bool useTopic;
bool clientAck;
unsigned int numMessages;
std::string brokerURI;
std::string destURI;
private:
SimpleProducer( const SimpleProducer& );
SimpleProducer& operator= ( const SimpleProducer& );
public:
SimpleProducer( const std::string& brokerURI, unsigned int numMessages,
const std::string& destURI, bool useTopic = false, bool clientAck = false ) :
connection(NULL),
session(NULL),
destination(NULL),
producer(NULL),
useTopic(useTopic),
clientAck(clientAck),
numMessages(numMessages),
brokerURI(brokerURI),
destURI(destURI) {
}
virtual ~SimpleProducer(){
cleanup();
}
void close() {
this->cleanup();
}
virtual void run() {
try {
// Create a ConnectionFactory
auto_ptr<ActiveMQConnectionFactory> connectionFactory(
new ActiveMQConnectionFactory( brokerURI ) );
// Create a Connection
try{
connection = connectionFactory->createConnection();
connection->start();
} catch( CMSException& e ) {
e.printStackTrace();
throw e;
}
// Create a Session
if( clientAck ) {
session = connection->createSession( Session::CLIENT_ACKNOWLEDGE );
} else {
session = connection->createSession( Session::AUTO_ACKNOWLEDGE );
}
// Create the destination (Topic or Queue)
if( useTopic ) {
destination = session->createTopic( destURI );
} else {
destination = session->createQueue( destURI );
}
// Create a MessageProducer from the Session to the Topic or Queue
producer = session->createProducer( destination );
producer->setDeliveryMode( DeliveryMode::NON_PERSISTENT );
// Create the Thread Id String
string threadIdStr = Long::toString( Thread::currentThread()->getId() );
// Create a messages
string text = (string)"Hello world! from thread " + threadIdStr;
for( unsigned int ix=0; ix<numMessages; ++ix ){
TextMessage* message = session->createTextMessage( text );
message->setIntProperty( "Integer", ix );
// Tell the producer to send the message
printf( "Sent message #%d from thread %s\n", ix+1, threadIdStr.c_str() );
producer->send( message );
delete message;
}
}catch ( CMSException& e ) {
e.printStackTrace();
}
}
private:
void cleanup(){
// Destroy resources.
try{
if( destination != NULL ) delete destination;
}catch ( CMSException& e ) { e.printStackTrace(); }
destination = NULL;
try{
if( producer != NULL ) delete producer;
}catch ( CMSException& e ) { e.printStackTrace(); }
producer = NULL;
// Close open resources.
try{
if( session != NULL ) session->close();
if( connection != NULL ) connection->close();
}catch ( CMSException& e ) { e.printStackTrace(); }
try{
if( session != NULL ) delete session;
}catch ( CMSException& e ) { e.printStackTrace(); }
session = NULL;
try{
if( connection != NULL ) delete connection;
}catch ( CMSException& e ) { e.printStackTrace(); }
connection = NULL;
}
};
////////////////////////////////////////////////////////////////////////////////
int main(int argc AMQCPP_UNUSED, char* argv[] AMQCPP_UNUSED) {
activemq::library::ActiveMQCPP::initializeLibrary();
std::cout << "=====================================================\n";
std::cout << "Starting the example:" << std::endl;
std::cout << "-----------------------------------------------------\n";
// Set the URI to point to the IPAddress of your broker.
// add any optional params to the url to enable things like
// tightMarshalling or tcp logging etc. See the CMS web site for
// a full list of configuration options.
//
// http://activemq.apache.org/cms/
//
// Wire Format Options:
// =====================
// Use either stomp or openwire, the default ports are different for each
//
// Examples:
// tcp://127.0.0.1:61616 default to openwire
// tcp://127.0.0.1:61616?wireFormat=openwire same as above
// tcp://127.0.0.1:61613?wireFormat=stomp use stomp instead
//
std::string brokerURI =
"failover://(tcp://linux35:61616"
// "?wireFormat=openwire"
// "&connection.useAsyncSend=true"
// "&transport.commandTracingEnabled=true"
// "&transport.tcpTracingEnabled=true"
// "&wireFormat.tightEncodingEnabled=true"
")";
//============================================================
// Total number of messages for this producer to send.
//============================================================
unsigned int numMessages = 2000;
//============================================================
// This is the Destination Name and URI options. Use this to
// customize where the Producer produces, to have the producer
// use a topic or queue set the 'useTopics' flag.
//============================================================
std::string destURI = "TEST.FOO";
//============================================================
// set to true to use topics instead of queues
// Note in the code above that this causes createTopic or
// createQueue to be used in the producer.
//============================================================
bool useTopics = false;
// Create the producer and run it.
SimpleProducer producer( brokerURI, numMessages, destURI, useTopics );
// Publish the given number of Messages
producer.run();
// Before exiting we ensure that all CMS resources are closed.
producer.close();
std::cout << "-----------------------------------------------------\n";
std::cout << "Finished with the example." << std::endl;
std::cout << "=====================================================\n";
activemq::library::ActiveMQCPP::shutdownLibrary();
}
简单的Receiver的例子[编辑]
编译配置参考上面 本例使用的onMessage的回掉函数,也就是通过consumer->setMessageListener( this );告诉客户端lib,如果有消息就调用this.onMessage。 this(SimpleAsyncConsumer需要继承MessageListener。这种方式是异步的,当然也可以使用简单的同步方法。请参考$ACTIVEMQ-CPP/src/examples里的更多例子。
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <decaf/lang/Thread.h>
#include <decaf/lang/Runnable.h>
#include <decaf/util/concurrent/CountDownLatch.h>
#include <activemq/core/ActiveMQConnectionFactory.h>
#include <activemq/core/ActiveMQConnection.h>
#include <activemq/transport/DefaultTransportListener.h>
#include <activemq/library/ActiveMQCPP.h>
#include <decaf/lang/Integer.h>
#include <activemq/util/Config.h>
#include <decaf/util/Date.h>
#include <cms/Connection.h>
#include <cms/Session.h>
#include <cms/TextMessage.h>
#include <cms/BytesMessage.h>
#include <cms/MapMessage.h>
#include <cms/ExceptionListener.h>
#include <cms/MessageListener.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace activemq;
using namespace activemq::core;
using namespace activemq::transport;
using namespace decaf::lang;
using namespace decaf::util;
using namespace decaf::util::concurrent;
using namespace cms;
using namespace std;
////////////////////////////////////////////////////////////////////////////////
class SimpleAsyncConsumer : public ExceptionListener,
public MessageListener,
public DefaultTransportListener {
private:
Connection* connection;
Session* session;
Destination* destination;
MessageConsumer* consumer;
bool useTopic;
std::string brokerURI;
std::string destURI;
bool clientAck;
private:
SimpleAsyncConsumer( const SimpleAsyncConsumer& );
SimpleAsyncConsumer& operator= ( const SimpleAsyncConsumer& );
public:
SimpleAsyncConsumer( const std::string& brokerURI,
const std::string& destURI,
bool useTopic = false,
bool clientAck = false ) :
connection(NULL),
session(NULL),
destination(NULL),
consumer(NULL),
useTopic(useTopic),
brokerURI(brokerURI),
destURI(destURI),
clientAck(clientAck) {
}
virtual ~SimpleAsyncConsumer() {
this->cleanup();
}
void close() {
this->cleanup();
}
void runConsumer() {
try {
// Create a ConnectionFactory
ActiveMQConnectionFactory* connectionFactory =
new ActiveMQConnectionFactory( brokerURI );
// Create a Connection
connection = connectionFactory->createConnection();
delete connectionFactory;
ActiveMQConnection* amqConnection = dynamic_cast<ActiveMQConnection*>( connection );
if( amqConnection != NULL ) {
amqConnection->addTransportListener( this );
}
connection->start();
connection->setExceptionListener(this);
// Create a Session
if( clientAck ) {
session = connection->createSession( Session::CLIENT_ACKNOWLEDGE );
} else {
session = connection->createSession( Session::AUTO_ACKNOWLEDGE );
}
// Create the destination (Topic or Queue)
if( useTopic ) {
destination = session->createTopic( destURI );
} else {
destination = session->createQueue( destURI );
}
// Create a MessageConsumer from the Session to the Topic or Queue
consumer = session->createConsumer( destination );
consumer->setMessageListener( this );
} catch (CMSException& e) {
e.printStackTrace();
}
}
// Called from the consumer since this class is a registered MessageListener.
virtual void onMessage( const Message* message ) {
static int count = 0;
try
{
count++;
const TextMessage* textMessage =
dynamic_cast< const TextMessage* >( message );
string text = "";
if( textMessage != NULL ) {
text = textMessage->getText();
} else {
text = "NOT A TEXTMESSAGE!";
}
if( clientAck ) {
message->acknowledge();
}
printf( "Message #%d Received: %s\n", count, text.c_str() );
} catch (CMSException& e) {
e.printStackTrace();
}
}
// If something bad happens you see it here as this class is also been
// registered as an ExceptionListener with the connection.
virtual void onException( const CMSException& ex AMQCPP_UNUSED ) {
printf("CMS Exception occurred. Shutting down client.\n");
exit(1);
}
virtual void transportInterrupted() {
std::cout << "The Connection's Transport has been Interrupted." << std::endl;
}
virtual void transportResumed() {
std::cout << "The Connection's Transport has been Restored." << std::endl;
}
private:
void cleanup(){
//*************************************************
// Always close destination, consumers and producers before
// you destroy their sessions and connection.
//*************************************************
// Destroy resources.
try{
if( destination != NULL ) delete destination;
}catch (CMSException& e) {}
destination = NULL;
try{
if( consumer != NULL ) delete consumer;
}catch (CMSException& e) {}
consumer = NULL;
// Close open resources.
try{
if( session != NULL ) session->close();
if( connection != NULL ) connection->close();
}catch (CMSException& e) {}
// Now Destroy them
try{
if( session != NULL ) delete session;
}catch (CMSException& e) {}
session = NULL;
try{
if( connection != NULL ) delete connection;
}catch (CMSException& e) {}
connection = NULL;
}
};
////////////////////////////////////////////////////////////////////////////////
int main(int argc AMQCPP_UNUSED, char* argv[] AMQCPP_UNUSED) {
activemq::library::ActiveMQCPP::initializeLibrary();
std::cout << "=====================================================\n";
std::cout << "Starting the example:" << std::endl;
std::cout << "-----------------------------------------------------\n";
// Set the URI to point to the IPAddress of your broker.
// add any optional params to the url to enable things like
// tightMarshalling or tcp logging etc. See the CMS web site for
// a full list of configuration options.
//
// http://activemq.apache.org/cms/
//
// Wire Format Options:
// =====================
// Use either stomp or openwire, the default ports are different for each
//
// Examples:
// tcp://127.0.0.1:61616 default to openwire
// tcp://127.0.0.1:61616?wireFormat=openwire same as above
// tcp://127.0.0.1:61613?wireFormat=stomp use stomp instead
//
std::string brokerURI =
"failover:(tcp://linux35:61616"
// "?wireFormat=openwire"
// "&connection.useAsyncSend=true"
// "&transport.commandTracingEnabled=true"
// "&transport.tcpTracingEnabled=true"
// "&wireFormat.tightEncodingEnabled=true"
")";
//============================================================
// This is the Destination Name and URI options. Use this to
// customize where the consumer listens, to have the consumer
// use a topic or queue set the 'useTopics' flag.
//============================================================
std::string destURI = "TEST.FOO?consumer.prefetchSize=1";
//============================================================
// set to true to use topics instead of queues
// Note in the code above that this causes createTopic or
// createQueue to be used in the consumer.
//============================================================
bool useTopics = false;
//============================================================
// set to true if you want the consumer to use client ack mode
// instead of the default auto ack mode.
//============================================================
bool clientAck = false;
// Create the consumer
SimpleAsyncConsumer consumer( brokerURI, destURI, useTopics, clientAck );
// Start it up and it will listen forever.
consumer.runConsumer();
// Wait to exit.
std::cout << "Press 'q' to quit" << std::endl;
while( std::cin.get() != 'q') {}
// All CMS resources should be closed before the library is shutdown.
consumer.close();
std::cout << "-----------------------------------------------------\n";
std::cout << "Finished with the example." << std::endl;
std::cout << "=====================================================\n";
activemq::library::ActiveMQCPP::shutdownLibrary();
}

本文介绍如何使用 ActiveMQ 的 C++ 客户端进行消息发送与接收,涵盖编译安装步骤及简单示例代码。适用于希望了解 ActiveMQ C++ 客户端基本操作的开发者。
613

被折叠的 条评论
为什么被折叠?



