ActiveMQ C++ tutorial

8 篇文章 0 订阅

说明[编辑]

 本文会结合一个例子简要的说明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();
}

参考文献[编辑]

  1. ActiveMQ首页
  2. ActiveMQ-CPP首页
  3. JMS 1.1
  4. JMS 2.0
  5. JMS 2.0新feature Part1
  6. JMS 2.0新feature Part2
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值