本文会结合一个例子简要的说明activemq的c++客户端的使用方法
Apache ActiveMQ是一个开源的消息服务器,实现了JMS规范,支持多语言客户端和协议。
ActiveMQ-CPP是c++客户端,目前支持 OpenWire and Stomp协议,两者都可以通过TCP和SSL,目前支持failover
说明:本段内容主要摘自《ActiveMQ in Action》一书
MOM是一组中间件,用来构建分布式系统,它的特性包括:异步,松耦合,可靠,安全和可扩展等等。发送者和接收者进行异步通讯,发送者只需要把消息发送到MOM,不需要知道谁是接收者,MOM保证消息的送达;同样的,接收者也不需要关心发送者。
在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的实现者,比如ActiveMQ
消息包括消息头(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)也能正确处理。
代表一个客户端到JSM Provider的连接,一般包括一个TCP的socket,因此比较”重“,建议同一个进程的不同线程共用一个Connection(使用一个Connection创建出不同的session)。当然如果你觉得一个Socket不能发挥网络的性能,那么可以 多使用一些Connection,比如每10个线程共用一个Connection
一个”会话“,不是线程安全的,因此每个线程都必须有自己的Session,一般通过Connection创建出来
目的地,比如某个队列,一般通过session来创建或者获得
生产者,我们这里只介绍P2P的模式,所以可以等价为消息发送者(Sender)
消费者,我们这里只关心P2P的模式,所以可以等价为消息接收者(Receiver)
具体请阅读文档 我这里只是介绍一下安装编译和运行一个简单的例子
具体参考文档,我这里只是介绍我的环境(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
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#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 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(); }
编译配置参考上面 本例使用的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#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 ( 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(); }