基于ActiveMQ 的发布/订阅(Pub/Sub) Chat 示例,上传了源码

转自:http://www.iteye.com/topic/836509

 

 

环境需求:

1. JDK 1.5 或者以上

2. Apache Ant, 在写本文时,用的是 Ant 1.7.1

3. ActiveMQ, 在写本文时,用的是 Apache ActiveMQ 5.4.1

技术需求:

1. JMS(Java Message Service)

2. JNDI(Java Naming and Directory Interface)

在JMS的“发布/订阅(pub/sub)”模型中,消息的发布者(Publisher)通过主题(Topic)发布消息,订阅者(Subscriber)通过订阅主题获取消息。 一个主题可以同时有多个订阅者. 通过这种方式我们可以实现广播式(broadcast)消息。

为了更好的理解"发布/订阅(Pub/Sub)"模式,我在《Java消息服务器 第二版》上找到了一个很好的例子来说明他的使用。不过书上只提供了相关代码供我们理解,没有讲述整个创建过程,在这里打算记录下整个构建实例的过程:

1.  创建项目目录入下图所示,并将activemq-all-*.jar 复制到项目的classpath中:

2. 编写Chat代码:

Java代码 复制代码 收藏代码
  1. public class Chat implements MessageListener { 
  2.     private TopicSession pubSession; 
  3.     private TopicPublisher pub; 
  4.     private TopicConnection conn; 
  5.     private String username; 
  6.  
  7.     public Chat(String topicFactory, String topicName, String username) 
  8.             throws NamingException, JMSException { 
  9.  
  10.         // 创建 JNDI context 
  11.         InitialContext ctx = new InitialContext(); 
  12.  
  13.         //1. 创建 TopicConnectionFacotry 
  14.         TopicConnectionFactory factory = (TopicConnectionFactory) ctx 
  15.                 .lookup(topicFactory); 
  16.         //2. 创建 TopicConnection 
  17.         TopicConnection connection = factory.createTopicConnection(); 
  18.  
  19.         //3. 根据 Connection 创建 JMS 会话 
  20.         TopicSession pubSession = (TopicSession) connection.createSession( 
  21.                 false, Session.AUTO_ACKNOWLEDGE); 
  22.         TopicSession subSession = (TopicSession) connection.createSession( 
  23.                 false, Session.AUTO_ACKNOWLEDGE); 
  24.  
  25.         //4. 创建 Topic 
  26.         Topic topic = (Topic) ctx.lookup(topicName); 
  27.  
  28.         //5. 创建 发布者 和 订阅者 
  29.         TopicPublisher pub = pubSession.createPublisher(topic); 
  30.         TopicSubscriber sub = subSession.createSubscriber(topic, null, true); 
  31.  
  32.         //6. 为发布者设置消息监听 
  33.         sub.setMessageListener(this); 
  34.  
  35.         this.conn = connection; 
  36.         this.pub = pub; 
  37.         this.pubSession = pubSession; 
  38.         this.username = username; 
  39.  
  40.         //7. 开启JMS连接 
  41.         connection.start(); 
  42.     } 
  43.  
  44.     protected void writeMessage(String txt) { 
  45.         try
  46.             TextMessage message = pubSession.createTextMessage(); 
  47.             message.setText(username + ": " + txt); 
  48.  
  49.             pub.publish(message); 
  50.         } catch (JMSException e) { 
  51.             e.printStackTrace(); 
  52.         } 
  53.  
  54.     } 
  55.  
  56.     public void onMessage(Message msg) { 
  57.         TextMessage txtMsg = (TextMessage) msg; 
  58.         try
  59.             System.out.println(txtMsg.getText()); 
  60.         } catch (JMSException e) { 
  61.             e.printStackTrace(); 
  62.         } 
  63.     } 
  64.  
  65.     public void close() throws JMSException { 
  66.         this.conn.close(); 
  67.     } 
  68.  
  69.     public static void main(String[] args) throws NamingException, 
  70.             JMSException, IOException { 
  71.         if (args.length != 3) { 
  72.             System.out.println("Factory, Topic, or username missing"); 
  73.         } 
  74.  
  75.         Chat chat = new Chat(args[0], args[1], args[2]); 
  76.  
  77.         BufferedReader cmd = new BufferedReader( 
  78.                 new InputStreamReader(System.in)); 
  79.  
  80.         while (true) { 
  81.             String s = cmd.readLine(); 
  82.  
  83.             if (s.equalsIgnoreCase("exit")) { 
  84.                 chat.close(); 
  85.                 System.exit(0); 
  86.             } else
  87.                 chat.writeMessage(s); 
  88.             } 
  89.         } 
  90.     } 
public class Chat implements MessageListener {
	private TopicSession pubSession;
	private TopicPublisher pub;
	private TopicConnection conn;
	private String username;

	public Chat(String topicFactory, String topicName, String username)
			throws NamingException, JMSException {

		// 创建 JNDI context
		InitialContext ctx = new InitialContext();

		//1. 创建 TopicConnectionFacotry
		TopicConnectionFactory factory = (TopicConnectionFactory) ctx
				.lookup(topicFactory);
		//2. 创建 TopicConnection
		TopicConnection connection = factory.createTopicConnection();

		//3. 根据 Connection 创建 JMS 会话
		TopicSession pubSession = (TopicSession) connection.createSession(
				false, Session.AUTO_ACKNOWLEDGE);
		TopicSession subSession = (TopicSession) connection.createSession(
				false, Session.AUTO_ACKNOWLEDGE);

		//4. 创建 Topic
		Topic topic = (Topic) ctx.lookup(topicName);

		//5. 创建 发布者 和 订阅者
		TopicPublisher pub = pubSession.createPublisher(topic);
		TopicSubscriber sub = subSession.createSubscriber(topic, null, true);

		//6. 为发布者设置消息监听
		sub.setMessageListener(this);

		this.conn = connection;
		this.pub = pub;
		this.pubSession = pubSession;
		this.username = username;

		//7. 开启JMS连接
		connection.start();
	}

	protected void writeMessage(String txt) {
		try {
			TextMessage message = pubSession.createTextMessage();
			message.setText(username + ": " + txt);

			pub.publish(message);
		} catch (JMSException e) {
			e.printStackTrace();
		}

	}

	public void onMessage(Message msg) {
		TextMessage txtMsg = (TextMessage) msg;
		try {
			System.out.println(txtMsg.getText());
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}

	public void close() throws JMSException {
		this.conn.close();
	}

	public static void main(String[] args) throws NamingException,
			JMSException, IOException {
		if (args.length != 3) {
			System.out.println("Factory, Topic, or username missing");
		}

		Chat chat = new Chat(args[0], args[1], args[2]);

		BufferedReader cmd = new BufferedReader(
				new InputStreamReader(System.in));

		while (true) {
			String s = cmd.readLine();

			if (s.equalsIgnoreCase("exit")) {
				chat.close();
				System.exit(0);
			} else {
				chat.writeMessage(s);
			}
		}
	}
}

3.由于里我们使用了JNDI, 所以我们需要编辑jndi.properties。内容如下:

Java代码 复制代码 收藏代码
  1. # START SNIPPET: jndi 
  2. java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory 
  3.  
  4. # use the following property to configure the default connector 
  5. java.naming.provider.url = tcp://localhost:61616 
  6.  
  7. java.naming.security.principal=system 
  8. java.naming.security.credentials=manager 
  9.  
  10. # use the following property to specify the JNDI name the connection factory 
  11. # should appear as.  
  12. #connectionFactoryNames = connectionFactory, queueConnectionFactory, topicConnectionFactry 
  13. connectionFactoryNames = topicConnectionFactry 
  14.  
  15.  
  16. # register some queues in JNDI using the form 
  17. # queue.[jndiName] = [physicalName] 
  18. #queue.MyQueue = example.ChatQue 
  19. topic.chat = example.chat 
  20.  
  21. # register some topics in JNDI using the form 
  22. # topic.[jndiName] = [physicalName] 
  23. #topic.MyTopic = example.ChatTop 
  24.  
  25. # END SNIPPET: jndi 
# START SNIPPET: jndi
java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory

# use the following property to configure the default connector
java.naming.provider.url = tcp://localhost:61616

java.naming.security.principal=system
java.naming.security.credentials=manager

# use the following property to specify the JNDI name the connection factory
# should appear as. 
#connectionFactoryNames = connectionFactory, queueConnectionFactory, topicConnectionFactry
connectionFactoryNames = topicConnectionFactry


# register some queues in JNDI using the form
# queue.[jndiName] = [physicalName]
#queue.MyQueue = example.ChatQue
topic.chat = example.chat

# register some topics in JNDI using the form
# topic.[jndiName] = [physicalName]
#topic.MyTopic = example.ChatTop

# END SNIPPET: jndi

4. 到这里已经基本完成Chat 编码工作,使用如下指令即可运行这个示例:

《Java 消息服务》原文 写道
java com.dayang.jms.demo.Chat [topicConnectionFactory] [topicName] [userName]

不过如果没有设置相关classpath,是不可能通过这个指令来成功运行这个Demo,在这里我打算使用Ant来帮我完成这个工作

5. 编写build.xml脚本如下:

Xml代码 复制代码 收藏代码
  1. <?xml version="1.0" encoding="utf-8" ?> 
  2. <project name="chat" default="run" basedir="."> 
  3.     <property name="src.dir" value="src" /> 
  4.     <property name="build.dir" value="build" /> 
  5.     <property name="classes.dir" value="${build.dir}/classes" /> 
  6.     <property name="jar.dir" value="${build.dir}/jar" /> 
  7.     <property name="lib.dir" value="libs"/>        
  8.      
  9.     <!-- 设置main函数所在类 --> 
  10.     <property name="main-class" value="com.dayang.jms.chat.Chat" /> 
  11.      
  12.     <!-- 定义classpath --> 
  13.     <path id="classpath"> 
  14.         <fileset dir="${lib.dir}" includes="**/*.jar" /> 
  15.     </path> 
  16.      
  17.     <!-- 创建构建目录,用于存放构建生成的文件 --> 
  18.     <target name="init"> 
  19.         <mkdir dir="${build.dir}"/> 
  20.     </target> 
  21.      
  22.     <!-- 编译 --> 
  23.     <target name="compile" depends="init"> 
  24.         <mkdir dir="${classes.dir}"/> 
  25.         <javac srcdir="${src.dir}" destdir="${classes.dir}"  
  26.             classpathref="classpath"/> 
  27.         <!-- copy properties file to classpath --> 
  28.         <copy todir="${classes.dir}"> 
  29.             <fileset dir="${src.dir}" excludes="**.*.jar" /> 
  30.         </copy> 
  31.     </target> 
  32.      
  33.     <!-- 打包 --> 
  34.     <target name="jar" depends="compile"> 
  35.         <mkdir dir="${jar.dir}"/> 
  36.         <jar destfile="${jar.dir}/${ant.project.name}.jar"  
  37.                 basedir="${classes.dir}"> 
  38.             <manifest> 
  39.                 <attribute name="Main-Class" value="${main-class}" /> 
  40.             </manifest> 
  41.         </jar> 
  42.     </target> 
  43.      
  44.     <!-- 运行client1 --> 
  45.     <target name="run1" depends="jar"> 
  46.         <java fork="true" classname="${main-class}"> 
  47.             <arg value="topicConnectionFactry"/> 
  48.             <arg value="chat"/> 
  49.             <arg value="client1"/> 
  50.             <classpath> 
  51.                 <path refid="classpath"/> 
  52.                 <path location="${jar.dir}/${ant.project.name}.jar"/> 
  53.             </classpath> 
  54.         </java> 
  55.     </target> 
  56.      
  57.     <!-- 运行client2 --> 
  58.     <target name="run2" depends="jar"> 
  59.         <java fork="true" classname="${main-class}"> 
  60.             <arg value="topicConnectionFactry"/> 
  61.             <arg value="chat"/> 
  62.             <arg value="client2"/> 
  63.             <classpath> 
  64.                 <path refid="classpath"/> 
  65.                 <path location="${jar.dir}/${ant.project.name}.jar"/> 
  66.             </classpath> 
  67.         </java> 
  68.     </target> 
  69.      
  70.     <target name="clean"> 
  71.         <delete dir="${build.dir}"/> 
  72.     </target> 
  73.      
  74.     <target name="clean-build" depends="clean,jar"/> 
  75.      
  76. </project> 

6. 打开两个控制台窗口,分别使用ant run1 和 ant run2 指令来运行程序, 如果成功我们将看到如下结果:


写在最后:

这个示例仅仅简单的说了JMS 发布/订阅 API的基本使用,更多特性需要在以后的使用中进行摸索。

发布/订阅 除了能够提供“1对多”的消息专递方式之外,还提供了消息持久化的特性。他允许订阅者在上线后接收离线时的消息,关于这部分特性,以及“发布/订阅”的应用场景打算在以后的文章中慢慢阐述。

参考资料:

1. JMS: http://baike.baidu.com/view/157103.htm

2. ActiveMQ: http://baike.baidu.com/view/433374.htm

3. JNDI http://baike.baidu.com/view/209575.htm

4. 《Java消息服务器 第二版》

5. Ant Manual http://ant.apache.org/manual/index.html

2011-05-18: 新增加了Demo的源代码, 需要的可以下载附件JSMDemo.rar

JMSDemo工程目录结构如下:


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值