MyException - 我的异常网
当前位置:我的异常网» 软件架构设计 » 基于activeMQ broker cluster 集群 的高可用 多协

基于activeMQ broker cluster 集群 的高可用 多协议 物联网讯息的架构设计

www.MyException.Cn  网友分享于:2013-11-16  浏览:0次
基于activeMQ broker cluster 集群 的高可用 多协议 物联网消息的架构设计

activeMQ是一款功能十分强大的消息中间件。
支持包括MQTT NIO 在内的多种协议,而且是jms的完美实现。当有数以百万计的终端设备需要连接到服务器时,适当处理和架构就可以对外提供功能强劲的服务能力。

首先需要解决activeMQ 单节点服务性能问题,切不可直接使用默认配置上生产。
可以自己百度 或者 参照 日志
http://m.blog.csdn.net/truong/article/details/73718621
http://blog.csdn.net/yinwenjie/article/details/50955502
http://blog.csdn.net/yinwenjie/article/details/50991443
http://blog.csdn.net/yinwenjie/article/details/51064242

单节点完成  则进行 集群搭建 一样参照博文
http://www.cnblogs.com/leihenqianshang/articles/5623858.html

搭建完成之后,高性能的服务架构已经出来了。这时候需要写一些测试代码。




1 先写针对客户端和消费端都基于activeMQ API 的情况。生产者代码如下:


package com.sunshine.mq;
import javax.jms.Connection;  
import javax.jms.DeliveryMode;  
import javax.jms.JMSException;  
import javax.jms.MessageProducer;  
import javax.jms.Session;  
import javax.jms.TextMessage;  
import javax.jms.Topic;  
  
import org.apache.activemq.ActiveMQConnectionFactory;  
  
public class Producer {  
  
    public static void main(String[] args) throws JMSException {  
        // 连接到ActiveMQ服务器  
        //ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.15:61616)");
        //这里可以使用 failover 机制 进行多节点配置 达到高可用和后端负载均衡  
    	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.15:1883)");
        Connection connection = factory.createConnection();  
        connection.start();  
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 创建主题  
        Topic topic = session.createTopic("VirtualTopic.virtual-T.100990");  
        MessageProducer producer = session.createProducer(topic);  
        // NON_PERSISTENT 非持久化 PERSISTENT 持久化,发送消息时用使用持久模式  
        //producer.setDeliveryMode(DeliveryMode.PERSISTENT);  
        TextMessage message = session.createTextMessage();
        for(int i = 0;i<10;i++){
        	 message.setText("topic 消息。"+i);  
             message.setStringProperty("property", "消息Property");  
             // 发布主题消息  
             producer.send(message);  
             System.out.println("Sent message: " + message.getText()); 
        }
        
        session.close();  
        connection.close();  
    }  
} 



我们需要多个消费者来消费消息以达到 分布式部署的目的
消费者1


package com.sunshine.mq;
import javax.jms.Connection;  
import javax.jms.JMSException;  
import javax.jms.Message;  
import javax.jms.MessageConsumer;  
import javax.jms.MessageListener;  
import javax.jms.Queue;  
import javax.jms.Session;  
import javax.jms.TextMessage;  
  


import org.apache.activemq.ActiveMQConnectionFactory;  
  
public class ConsumerA {  
  
    public static void main(String[] args) throws JMSException, InterruptedException {  
        // 连接到ActiveMQ服务器  
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1888,tcp://172.16.7.18:1889)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.15:1887)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:61616,tcp://172.16.7.18:61616)");
    	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1883,tcp://172.16.7.18:1883)");
        Connection connection = factory.createConnection();  
        connection.start();  
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 创建主题   
        Queue topicA = session.createQueue("Consumer.GG.VirtualTopic.virtual-T.*");  
        // 消费者A组创建订阅  
        MessageConsumer consumerA1 = session.createConsumer(topicA);
        Integer count = 0;
        while(true){
        	Message message = consumerA1.receive();
        	count++;
        	TextMessage msg = (TextMessage)message;
			final String messageText = msg.getText();
        	System.out.println(messageText +",消费消息总数为:"+count);
        }
        /**consumerA1.setMessageListener(new MessageListener() {  
            // 订阅接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("A 收到消息: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });**/  
          
        //session.close();  
        //connection.close();  
    }  
}  


消费者2

package com.sunshine.mq;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;
  
public class ConsumerB {  
  
    public static void main(String[] args) throws JMSException, InterruptedException {  
        // 连接到ActiveMQ服务器  
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(nio://172.16.7.17:1888,nio://172.16.7.18:1889)");
//    	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:61616,tcp://172.16.7.18:61616)");
        Connection connection = factory.createConnection();  
        connection.start(); 
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 创建主题   
        Queue topicA = session.createQueue("Consumer.GG.VirtualTopic.virtual-T.*");  
        // 消费者A组创建订阅  
        MessageConsumer consumerA1 = session.createConsumer(topicA);
        Integer count = 0;
        while(true){
        	Message message = consumerA1.receive();
        	count++;
        	TextMessage msg = (TextMessage)message;
			final String messageText = msg.getText();
        	System.out.println(messageText +",消费消息总数为:"+count);
        	
        }
        /**consumerA1.setMessageListener(new MessageListener() {  
            // 订阅接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("B 收到消息: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });**/  
        //session.close();  
        //connection.close();  
    }  
}  


消费者3

package com.sunshine.mq;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;
  
public class ConsumerC {  
  
    public static void main(String[] args) throws JMSException, InterruptedException {  
        // 连接到ActiveMQ服务器  
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(nio://172.16.7.17:1888,nio://172.16.7.18:1889)");
//    	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:61616,tcp://172.16.7.18:61616)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(mqtt://172.16.7.17:1883,mqtt://172.16.7.18:1883)");
        Connection connection = factory.createConnection();  
        connection.start(); 
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 创建主题   
        Queue topicA = session.createQueue("Consumer.GG.VirtualTopic.virtual-T.*");  
        // 消费者A组创建订阅  
        MessageConsumer consumerA1 = session.createConsumer(topicA);
        Integer count = 0;
        while(true){
        	Message message = consumerA1.receive();
        	count++;
        	TextMessage msg = (TextMessage)message;
			final String messageText = msg.getText();
        	System.out.println(messageText +",消费消息总数为:"+count);
        	
        }
        /**consumerA1.setMessageListener(new MessageListener() {  
            // 订阅接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("B 收到消息: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });**/  
        //session.close();  
        //connection.close();  
    }  
}  



测试的结果是 生产者生成的消息会负载到不同的消费者客户端,各个消费者客户端消费了不同的消息。这里不管生产者生产的消息是走队列还是走topic 其结果都是实现了消息的路由。

假设出现一种情况,设备端只支持MQTT协议,使用了MQTTClient 发送消息或者只想走MQTT,那么针对这个架构该如何应对呢?道理是一样的,由于linux-c或者C++ 客户端开发人员的尿性,所以 生产者模拟程序


package com.sunshine.mqtt;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;


public class MyMqttClient {
	private String host="tcp://172.16.7.15:1887";
	private String userName="admin";
	private String passWord = "activemq.123";
	private MqttConnectOptions options;
	private MqttClient client;
	private MqttMessage message ;
	private String[] myTopics={"test_result/20179112982783"};
	private int[] myQos={2};
	private MqttTopic topic;
	private String myTopic = "test/20179112982783";
	public MyMqttClient(){
		try {
			client=new MqttClient(host,"test99990000",new MemoryPersistence());
			options = new MqttConnectOptions(); 
			options.setCleanSession(false); 
			options.setUserName(userName); 
			options.setPassword(passWord.toCharArray()); 
			options.setConnectionTimeout(10); 
			options.setKeepAliveInterval(20);
			client.setCallback(new MqttCallback(){

				@Override
				public void connectionLost(Throwable cause) {
					
				}

				@Override
				public void messageArrived(String topicName, MqttMessage message)
						throws Exception {
					System.out.println("topicName is :"+topicName);
					System.out.println("Message is:"+message.toString());
				}

				@Override
				public void deliveryComplete(IMqttDeliveryToken token) {
					
				}});
			
			client.connect(options);
			client.subscribe(myTopics,myQos);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void sendMessage(String topicT,String messageTest){
		try {
			message = new MqttMessage();
			message.setQos(0);
			message.setRetained(true);
			message.setPayload(messageTest.getBytes());
			topic = client.getTopic(topicT);
			MqttDeliveryToken token = topic.publish(message);
			token.waitForCompletion();
			System.out.println(" 发送消息:"+messageTest);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}



package com.sunshine.mqtt;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.junit.Test;


public class MyMqttTest {
	
	@Test
	public void testMQTT(){
		MyMqtt mqtt = new MyMqtt();
		mqtt.sendMessage("我向客户端发送了一条消息");
	}
	
	@Test
	public void testMQTTClient(){
		MyMqttClient client=new MyMqttClient();
		//client.sendMessage("我向服务端发送了一条消息,我的SN是 20179112982783");
	}
	
	@Test
	public void testIotConnect() throws InterruptedException{
		MyMqttClient client=new MyMqttClient();
		for(int i=0;i<10;i++){
			client.sendMessage("VirtualTopic/100990"+i,"mqtt-msg:"+i+">"+("100990"+i));
		}
	}
}




三个消费者

package com.sunshine.mq;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.TopicConnectionFactory;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQBytesMessage;
  
public class ConsumerTopicA {  
  
    public static void main(String[] args) throws JMSException, InterruptedException {  
        // 连接到ActiveMQ服务器  
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1888,tcp://172.16.7.18:1889)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.15:1887)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:61616,tcp://172.16.7.18:61616)");
    	//ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1883,tcp://172.16.7.18:1883)");
    	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin", "failover:(tcp://172.16.7.17:1888,tcp://172.16.7.18:1889)");
        Connection connection = factory.createConnection();
        connection.start();  
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 创建主题   
        Queue topicA = session.createQueue("Consumer.GG.VirtualTopic.*");  
        // 消费者A组创建订阅  
        MessageConsumer consumerA1 = session.createConsumer(topicA);
        Integer count = 0;
        while(true){
        	//在实际生产过程中不建议使用receive 会造成阻塞并损耗大量资源 建议直接使用spring jms 监听器
        	ActiveMQBytesMessage message = (ActiveMQBytesMessage) consumerA1.receive();
        	count++;
//        	TextMessage msg = (TextMessage)message;
//			String messageText = msg.getText();
        	System.out.println(new String(message.getMessage().getContent().getData()) +",消费消息总数为:"+count);
        }
        /**consumerA1.setMessageListener(new MessageListener() {  
            // 订阅接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("A 收到消息: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });**/  
          
        //session.close();  
        //connection.close();  
    }  
}  





package com.sunshine.mq;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.TopicConnectionFactory;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQBytesMessage;
  
public class ConsumerTopicB {  
  
    public static void main(String[] args) throws JMSException, InterruptedException {  
        // 连接到ActiveMQ服务器  
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1888,tcp://172.16.7.18:1889)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.15:1887)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:61616,tcp://172.16.7.18:61616)");
    	//ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1883,tcp://172.16.7.18:1883)");
    	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin", "failover:(tcp://172.16.7.17:1888,tcp://172.16.7.18:1889)");
        Connection connection = factory.createConnection();
        connection.start();  
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 创建主题   
        Queue topicA = session.createQueue("Consumer.GG.VirtualTopic.*");  
        // 消费者A组创建订阅  
        MessageConsumer consumerA1 = session.createConsumer(topicA);
        Integer count = 0;
        while(true){
        	ActiveMQBytesMessage message = (ActiveMQBytesMessage) consumerA1.receive();
        	count++;
//        	TextMessage msg = (TextMessage)message;
//			String messageText = msg.getText();
        	System.out.println(new String(message.getMessage().getContent().getData()) +",消费消息总数为:"+count);
        }
        /**consumerA1.setMessageListener(new MessageListener() {  
            // 订阅接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("A 收到消息: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });**/  
          
        //session.close();  
        //connection.close();  
    }  
}  


package com.sunshine.mq;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.TopicConnectionFactory;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQBytesMessage;
  
public class ConsumerTopicC {  
  
    public static void main(String[] args) throws JMSException, InterruptedException {  
        // 连接到ActiveMQ服务器  
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1888,tcp://172.16.7.18:1889)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.15:1887)");
//        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:61616,tcp://172.16.7.18:61616)");
    	//ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin","failover:(tcp://172.16.7.17:1883,tcp://172.16.7.18:1883)");
    	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin", "failover:(tcp://172.16.7.17:1888,tcp://172.16.7.18:1889)");
        Connection connection = factory.createConnection();
        connection.start();  
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 创建主题   
        Queue topicA = session.createQueue("Consumer.GG.VirtualTopic.*");  
        // 消费者A组创建订阅  
        MessageConsumer consumerA1 = session.createConsumer(topicA);
        Integer count = 0;
        while(true){
        	ActiveMQBytesMessage message = (ActiveMQBytesMessage) consumerA1.receive();
        	count++;
//        	TextMessage msg = (TextMessage)message;
//			String messageText = msg.getText();
        	System.out.println(new String(message.getMessage().getContent().getData()) +",消费消息总数为:"+count);
        }
        /**consumerA1.setMessageListener(new MessageListener() {  
            // 订阅接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("A 收到消息: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });**/  
          
        //session.close();  
        //connection.close();  
    }  
}  




此时客户端只支持单个broker地址,可以通过一些策略分配给客户端相对比较空闲的broker

文章评论

做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
老程序员的下场
老程序员的下场
一个程序员的时间管理
一个程序员的时间管理
旅行,写作,编程
旅行,写作,编程
程序员应该关注的一些事儿
程序员应该关注的一些事儿
10个调试和排错的小建议
10个调试和排错的小建议
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
Java程序员必看电影
Java程序员必看电影
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
程序员和编码员之间的区别
程序员和编码员之间的区别
那些争议最大的编程观点
那些争议最大的编程观点
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
鲜为人知的编程真相
鲜为人知的编程真相
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
漫画:程序员的工作
漫画:程序员的工作
为什么程序员都是夜猫子
为什么程序员都是夜猫子
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
 程序员的样子
程序员的样子
我的丈夫是个程序员
我的丈夫是个程序员
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
每天工作4小时的程序员
每天工作4小时的程序员
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
编程语言是女人
编程语言是女人
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
代码女神横空出世
代码女神横空出世
我是如何打败拖延症的
我是如何打败拖延症的
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
程序员的鄙视链
程序员的鄙视链
中美印日四国程序员比较
中美印日四国程序员比较
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
如何成为一名黑客
如何成为一名黑客
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有