activemq的几种基本通信方式总结

2023-05-16

简介

     在前面一篇文章里讨论过几种应用系统集成的方式,发现实际上面向消息队列的集成方案算是一个总体比较合理的选择。这里,我们先针对具体的一个消息队列Activemq的基本通信方式进行探讨。activemq是JMS消息通信规范的一个实现。总的来说,消息规范里面定义最常见的几种消息通信模式主要有发布-订阅、点对点这两种。另外,通过结合这些模式的具体应用,我们在处理某些应用场景的时候也衍生出来了一种请求应答的模式。下面,我们针对这几种方式一一讨论一下。

 

基础流程

    在讨论具体方式的时候,我们先看看使用activemq需要启动服务的主要过程。

    按照JMS的规范,我们首先需要获得一个JMS connection factory.,通过这个connection factory来创建connection.在这个基础之上我们再创建session, destination, producer和consumer。因此主要的几个步骤如下:

1. 获得JMS connection factory. 通过我们提供特定环境的连接信息来构造factory。

2. 利用factory构造JMS connection

3. 启动connection

4. 通过connection创建JMS session.

5. 指定JMS destination.

6. 创建JMS producer或者创建JMS message并提供destination.

7. 创建JMS consumer或注册JMS message listener.

8. 发送和接收JMS message.

9. 关闭所有JMS资源,包括connection, session, producer, consumer等。

 

publish-subscribe

     发布订阅模式有点类似于我们日常生活中订阅报纸。每年到年尾的时候,邮局就会发一本报纸集合让我们来选择订阅哪一个。在这个表里头列了所有出版发行的报纸,那么对于我们每一个订阅者来说,我们可以选择一份或者多份报纸。比如北京日报、潇湘晨报等。那么这些个我们订阅的报纸,就相当于发布订阅模式里的topic。有很多个人订阅报纸,也有人可能和我订阅了相同的报纸。那么,在这里,相当于我们在同一个topic里注册了。对于一份报纸发行方来说,它和所有的订阅者就构成了一个1对多的关系。这种关系如下图所示:

     现在,假定我们用前面讨论的场景来写一个简单的示例。我们首先需要定义的是publisher.

publisher

     publisher是属于发布信息的一方,它通过定义一个或者多个topic,然后给这些topic发送消息。

    publisher的构造函数如下:

public Publisher() throws JMSException {
    	factory = new ActiveMQConnectionFactory(brokerURL);
    	connection = factory.createConnection();
    	try {
        connection.start();
    	} catch (JMSException jmse) {
    		connection.close();
    		throw jmse;
    	}
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        producer = session.createProducer(null);
    }

     我们按照前面说的流程定义了基本的connectionFactory, connection, session, producer。这里代码就是主要实现初始化的效果。

    接着,我们需要定义一系列的topic让所有的consumer来订阅,设置topic的代码如下:

protected void setTopics(String[] stocks) throws JMSException {
	destinations = new Destination[stocks.length];
	for(int i = 0; i < stocks.length; i++) {
		destinations[i] = session.createTopic("STOCKS." + stocks[i]);
	}
}

     这里destinations是一个内部定义的成员变量Destination[]。这里我们总共定义了的topic数取决于给定的参数stocks。

     在定义好topic之后我们要给这些指定的topic发消息,具体实现的代码如下:

protected void sendMessage(String[] stocks) throws JMSException {
	for(int i = 0; i < stocks.length; i++) {
		Message message = createStockMessage(stocks[i], session);
		System.out.println("Sending: " + ((ActiveMQMapMessage)message).getContentMap() + " on destination: " + destinations[i]);
		producer.send(destinations[i], message);
	}
}

protected Message createStockMessage(String stock, Session session) throws JMSException {
    MapMessage message = session.createMapMessage();
	message.setString("stock", stock);
	message.setDouble("price", 1.00);
	message.setDouble("offer", 0.01);
	message.setBoolean("up", true);
		
	return message;
}

     前面的代码很简单,在sendMessage方法里我们遍历每个topic,然后给每个topic发送定义的Message消息。

    在定义好前面发送消息的基础之后,我们调用他们的代码就很简单了:

public static void main(String[] args) throws JMSException {
	if(args.length < 1)
		throw new IllegalArgumentException();
	
        // Create publisher		
        Publisher publisher = new Publisher();
        
        // Set topics
	publisher.setTopics(args);
		
	for(int i = 0; i < 10; i++) {
		publisher.sendMessage(args);
		System.out.println("Publisher '" + i + " price messages");
		try {
			Thread.sleep(1000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
    }
    // Close all resources
    publisher.close();
}

     调用他们的代码就是我们遍历所有topic,然后通过sendMessage发送消息。在发送一个消息之后先sleep1秒钟。要注意的一个地方就是我们使用完资源之后必须要使用close方法将这些资源关闭释放。close方法关闭资源的具体实现如下:

public void close() throws JMSException {
    if (connection != null) {
        connection.close();
     }
}

 

consumer

    Consumer的代码也很类似,具体的步骤无非就是1.初始化资源。 2. 接收消息。 3. 必要的时候关闭资源。

    初始化资源可以放到构造函数里面:

public Consumer() throws JMSException {
    	factory = new ActiveMQConnectionFactory(brokerURL);
    	connection = factory.createConnection();
        connection.start();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    }

     接收和处理消息的方法有两种,分为同步和异步的,一般同步的方式我们是通过MessageConsumer.receive()方法来处理接收到的消息。而异步的方法则是通过注册一个MessageListener的方法,使用MessageConsumer.setMessageListener()。这里我们采用异步的方式实现:

public static void main(String[] args) throws JMSException {
    Consumer consumer = new Consumer();
    for (String stock : args) {
	Destination destination = consumer.getSession().createTopic("STOCKS." + stock);
	MessageConsumer messageConsumer = consumer.getSession().createConsumer(destination);
	messageConsumer.setMessageListener(new Listener());
    }
}
	
public Session getSession() {
	return session;
}

     在前面的代码里我们先找到同样的topic,然后遍历所有的topic去获得消息。对于消息的处理我们专门通过Listener对象来负责。

    Listener对象的职责很简单,主要就是处理接收到的消息:

public class Listener implements MessageListener {

	public void onMessage(Message message) {
		try {
			MapMessage map = (MapMessage)message;
			String stock = map.getString("stock");
			double price = map.getDouble("price");
			double offer = map.getDouble("offer");
			boolean up = map.getBoolean("up");
			DecimalFormat df = new DecimalFormat( "#,###,###,##0.00" );
			System.out.println(stock + "\t" + df.format(price) + "\t" + df.format(offer) + "\t" + (up?"up":"down"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

    它实现了MessageListener接口,里面的onMessage方法就是在接收到消息之后会被调用的方法。

    现在,通过实现前面的publisher和consumer我们已经实现了pub-sub模式的一个实例。仔细回想它的步骤的话,主要就是要两者设定一个共同的topic,有了这个topic之后他们可以实现一方发消息另外一方接收。另外,为了连接到具体的message server,这里是使用了连接tcp://localhost:16161作为定义ActiveMQConnectionFactory的路径。在publisher端通过session创建producer,根据指定的参数创建destination,然后将消息和destination作为producer.send()方法的参数发消息。在consumer端也要创建类似的connection, session。通过session得到destination,再通过session.createConsumer(destination)来得到一个MessageConsumer对象。有了这个MessageConsumer我们就可以自行选择是直接同步的receive消息还是注册listener了。

p2p

    p2p的过程则理解起来更加简单。它好比是两个人打电话,这两个人是独享这一条通信链路的。一方发送消息,另外一方接收,就这么简单。在实际应用中因为有多个用户对使用p2p的链路,它的通信场景如下图所示:

    我们再来看看一个p2p的示例:

    在p2p的场景里,相互通信的双方是通过一个类似于队列的方式来进行交流。和前面pub-sub的区别在于一个topic有一个发送者和多个接收者,而在p2p里一个queue只有一个发送者和一个接收者。

发送者

    和前面的示例非常相似,我们构造函数里需要初始化的内容基本上差不多:

 

public Publisher() throws JMSException {
    factory = new ActiveMQConnectionFactory(brokerURL);
    connection = factory.createConnection();
    connection.start();
    session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    producer = session.createProducer(null);
}
     发送消息的方法如下:

 

 

public void sendMessage() throws JMSException {
    for(int i = 0; i < jobs.length; i++)
    {
       	String job = jobs[i];
       	Destination destination = session.createQueue("JOBS." + job);
       	Message message = session.createObjectMessage(i);
       	System.out.println("Sending: id: " + ((ObjectMessage)message).getObject() + " on queue: " + destination);
       	producer.send(destination, message);
    }
}
     这里我们定义了一个jobs的数组,通过遍历这个数组来创建不同的job queue。这样就相当于建立了多个点对点通信的链路。

 

    消息发送者的启动代码如下:

 

public static void main(String[] args) throws JMSException {
    Publisher publisher = new Publisher();
    for(int i = 0; i < 10; i++) {
        publisher.sendMessage();
        System.out.println("Published " + i + " job messages");
	try {
            Thread.sleep(1000);
        } catch (InterruptedException x) {
	    e.printStackTrace();
        }
    }
    publisher.close();
}
     我们在这里发送10条消息,当然,在每个sendMessage的方法里实际上是针对每个queue发送了10条。

 

 

接收者

     接收者的代码很简单,一个构造函数初始化所有的资源:

 

public Consumer() throws JMSException {
    	factory = new ActiveMQConnectionFactory(brokerURL);
    	connection = factory.createConnection();
        connection.start();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    }
    还有一个就是注册消息处理的对象:

 

 

public static void main(String[] args) throws JMSException {
    	Consumer consumer = new Consumer();
    	for (String job : consumer.jobs) {
    		Destination destination = consumer.getSession().createQueue("JOBS." + job);
    		MessageConsumer messageConsumer = consumer.getSession().createConsumer(destination);
    		messageConsumer.setMessageListener(new Listener(job));
    	}
    }
	
	public Session getSession() {
		return session;
	}
     具体注册的对象处理方法和前面还是类似,实现MessageListener接口就可以了。
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;

public class Listener implements MessageListener {

	private String job;
	
	public Listener(String job) {
		this.job = job;
	}

	public void onMessage(Message message) {
		try {
			//do something here
			System.out.println(job + " id:" + ((ObjectMessage)message).getObject());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
     这里代码和前面pub-sub的具体实现代码非常相似,就不再赘述。

 

     现在如果我们比较一下pub-sub和p2p模式的具体实现步骤的话,我们会发现他们基本的处理流程都是类似的,除了在pub-sub中要通过createTopic来设置topic,而在p2p中要通过createQueue来创建通信队列。他们之间存在着很多的重复之处,在具体的开发过程中,我们是否可以进行一些工程上的优化呢?别急,后面我们会讨论到的。

request-response

    和前面两种方式比较起来,request-response的通信方式很常见,但是不是默认提供的一种模式。在前面的两种模式中都是一方负责发送消息而另外一方负责处理。而我们实际中的很多应用相当于一种一应一答的过程,需要双方都能给对方发送消息。于是请求-应答的这种通信方式也很重要。它也应用的很普遍。 

     请求-应答方式并不是JMS规范系统默认提供的一种通信方式,而是通过在现有通信方式的基础上稍微运用一点技巧实现的。下图是典型的请求-应答方式的交互过程:

     在JMS里面,如果要实现请求/应答的方式,可以利用JMSReplyTo和JMSCorrelationID消息头来将通信的双方关联起来。另外,QueueRequestor和TopicRequestor能够支持简单的请求/应答过程。

    現在,如果我们要实现这么一个过程,在发送请求消息并且等待返回结果的client端的流程如下:

// client side
Destination tempDest = session.createTemporaryQueue();
MessageConsumer responseConsumer = session.createConsumer(tempDest);
...

// send a request..
message.setJMSReplyTo(tempDest)
message.setJMSCorrelationID(myCorrelationID);

producer.send(message);

     client端创建一个临时队列并在发送的消息里指定了发送返回消息的destination以及correlationID。那么在处理消息的server端得到这个消息后就知道该发送给谁了。Server端的大致流程如下:

 

public void onMessage(Message request) {

  Message response = session.createMessage();
  response.setJMSCorrelationID(request.getJMSCorrelationID())

  producer.send(request.getJMSReplyTo(), response)
}

    这里我们是用server端注册MessageListener,通过设置返回信息的CorrelationID和JMSReplyTo将信息返回。

    以上就是发送和接收消息的双方的大致程序结构。具体的实现代码如下:

 Client:

public Client() {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection;
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            Session session = connection.createSession(transacted, ackMode);
            Destination adminQueue = session.createQueue(clientQueueName);

            //Setup a message producer to send message to the queue the server is consuming from
            this.producer = session.createProducer(adminQueue);
            this.producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

            //Create a temporary queue that this client will listen for responses on then create a consumer
            //that consumes message from this temporary queue...for a real application a client should reuse
            //the same temp queue for each message to the server...one temp queue per client
            Destination tempDest = session.createTemporaryQueue();
            MessageConsumer responseConsumer = session.createConsumer(tempDest);

            //This class will handle the messages to the temp queue as well
            responseConsumer.setMessageListener(this);

            //Now create the actual message you want to send
            TextMessage txtMessage = session.createTextMessage();
            txtMessage.setText("MyProtocolMessage");

            //Set the reply to field to the temp queue you created above, this is the queue the server
            //will respond to
            txtMessage.setJMSReplyTo(tempDest);

            //Set a correlation ID so when you get a response you know which sent message the response is for
            //If there is never more than one outstanding message to the server then the
            //same correlation ID can be used for all the messages...if there is more than one outstanding
            //message to the server you would presumably want to associate the correlation ID with this
            //message somehow...a Map works good
            String correlationId = this.createRandomString();
            txtMessage.setJMSCorrelationID(correlationId);
            this.producer.send(txtMessage);
        } catch (JMSException e) {
            //Handle the exception appropriately
        }
    }

    这里的代码除了初始化构造函数里的参数还同时设置了两个destination,一个是自己要发送消息出去的destination,在session.createProducer(adminQueue);这一句设置。另外一个是自己要接收的消息destination, 通过Destination tempDest = session.createTemporaryQueue(); responseConsumer = session.createConsumer(tempDest); 这两句指定了要接收消息的目的地。这里是用的一个临时队列。在前面指定了返回消息的通信队列之后,我们需要通知server端知道发送返回消息给哪个队列。于是txtMessage.setJMSReplyTo(tempDest);指定了这一部分,同时txtMessage.setJMSCorrelationID(correlationId);方法主要是为了保证每次发送回来请求的server端能够知道对应的是哪个请求。这里一个请求和一个应答是相当于对应一个相同的序列号一样。

    同时,因为client端在发送消息之后还要接收server端返回的消息,所以它也要实现一个消息receiver的功能。这里采用实现MessageListener接口的方式:

public void onMessage(Message message) {
        String messageText = null;
        try {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                messageText = textMessage.getText();
                System.out.println("messageText = " + messageText);
            }
        } catch (JMSException e) {
            //Handle the exception appropriately
        }
    }

 

Server:

     这里server端要执行的过程和client端相反,它是先接收消息,在接收到消息后根据提供的JMSCorelationID来发送返回的消息:

public void onMessage(Message message) {
        try {
            TextMessage response = this.session.createTextMessage();
            if (message instanceof TextMessage) {
                TextMessage txtMsg = (TextMessage) message;
                String messageText = txtMsg.getText();
                response.setText(this.messageProtocol.handleProtocolMessage(messageText));
            }

            //Set the correlation ID from the received message to be the correlation id of the response message
            //this lets the client identify which message this is a response to if it has more than
            //one outstanding message to the server
            response.setJMSCorrelationID(message.getJMSCorrelationID());

            //Send the response to the Destination specified by the JMSReplyTo field of the received message,
            //this is presumably a temporary queue created by the client
            this.replyProducer.send(message.getJMSReplyTo(), response);
        } catch (JMSException e) {
            //Handle the exception appropriately
        }
    }

    前面,在replyProducer.send()方法里,message.getJMSReplyTo()就得到了要发送消息回去的destination。

    另外,设置这些发送返回信息的replyProducer的信息主要在构造函数相关的方法里实现了:

public Server() {
        try {
            //This message broker is embedded
            BrokerService broker = new BrokerService();
            broker.setPersistent(false);
            broker.setUseJmx(false);
            broker.addConnector(messageBrokerUrl);
            broker.start();
        } catch (Exception e) {
            //Handle the exception appropriately
        }

        //Delegating the handling of messages to another class, instantiate it before setting up JMS so it
        //is ready to handle messages
        this.messageProtocol = new MessageProtocol();
        this.setupMessageQueueConsumer();
    }

    private void setupMessageQueueConsumer() {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(messageBrokerUrl);
        Connection connection;
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            this.session = connection.createSession(this.transacted, ackMode);
            Destination adminQueue = this.session.createQueue(messageQueueName);

            //Setup a message producer to respond to messages from clients, we will get the destination
            //to send to from the JMSReplyTo header field from a Message
            this.replyProducer = this.session.createProducer(null);
            this.replyProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

            //Set up a consumer to consume messages off of the admin queue
            MessageConsumer consumer = this.session.createConsumer(adminQueue);
            consumer.setMessageListener(this);
        } catch (JMSException e) {
            //Handle the exception appropriately
        }
    }

    总体来说,整个的交互过程并不复杂,只是比较繁琐。对于请求/应答的方式来说,这种典型交互的过程就是Client端在设定正常发送请求的Queue同时也设定一个临时的Queue。同时在要发送的message里头指定要返回消息的destination以及CorelationID,这些就好比是一封信里面所带的回执。根据这个信息人家才知道怎么给你回信。对于Server端来说则要额外创建一个producer,在处理接收到消息的方法里再利用producer将消息发回去。这一系列的过程看起来很像http协议里面请求-应答的方式,都是一问一答。

一些应用和改进

    回顾前面三种基本的通信方式,我们会发现,他们都存在着一定的共同点,比如说都要初始化ConnectionFactory, Connection, Session等。在使用完之后都要将这些资源关闭。如果每一个实现它的通信端都这么写一通的话,其实是一种简单的重复。从工程的角度来看是完全没有必要的。那么,我们有什么办法可以减少这种重复呢?

    一种简单的方式就是通过工厂方法封装这些对象的创建和销毁,然后简单的通过调用工厂方法的方式得到他们。另外,既然基本的流程都是在开头创建资源在结尾销毁,我们也可以采用Template Method模式的思路。通过继承一个抽象类,在抽象类里提供了资源的封装。所有继承的类只要实现怎么去使用这些资源的方法就可以了。Spring中间的JMSTemplate就提供了这种类似思想的封装。具体的实现可以参考这篇文章。

总结

     activemq默认提供了pub-sub, p2p这两种通信的方式。同时也提供了一些对request-response方式的支持。实际上,不仅仅是activemq,对于所有其他实现JMS规范的产品都能够提供类似的功能。这里每种方式都不太复杂,主要是创建和管理资源的步骤显得比较繁琐。

 

参考资料

activemq in action 

http://activemq.apache.org/how-should-i-implement-request-response-with-jms.html

http://zorro.blog.51cto.com/2139862/831986

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

activemq的几种基本通信方式总结 的相关文章

  • ubuntu16.04下利用ROS启动LPMS-CURS、CURS2等型号IMU;用imu控制turtlesim--教程

    文章目录 IMU型号及协议第一步 下载安装 LPsensor library第二步 设置ROS和carkin工作空间第三步 下载编译LPMS IMU的ROS驱动第四步 启动IMU xff08 可能也会遇到问题 xff09 遇到的问题1 ub
  • PixHawk飞控和Mission Planner地面站安装调试

    PixHawk飞控和Mission Planner地面站安装调试 PixHawk是著名飞控厂商3DR推出的新一代独立 开源 高效的飞行控制器 xff0c 前身为APM飞控 xff0c 不仅提供了丰富的外设模块和可靠的飞行体验 xff0c 有
  • 飞越650四轴无人机安装全程详解(多图)

    飞越650四轴无人机安装全程详解 xff08 多图 xff09 本文根据自己的安装实际过程 xff0c 总结了开箱后一个比较合理的650四轴无人机安装顺序 xff0c 以及各个步骤的注意事项 xff0c 主要内容包括 xff1a 系统基本配
  • DIY一个基于树莓派和Python的无人机视觉跟踪系统

    DIY 一个基于树莓派和Python的无人机视觉跟踪系统 无人机通过图传将航拍到的图像存储并实时传送回地面站几乎已经是标配 如果想来点高级的 在无人机上直接处理拍摄的图像并实现自动控制要怎么实现呢 xff1f 其实视觉跟踪已经在一些高端的消
  • windows环境下的Anaconda安装与OpenCV机器视觉环境搭建

    windows环境下的Anaconda安装与OpenCV机器视觉环境搭建 本文介绍win7和win10系统下通过Anaconda配置基于python语言的机器视觉编程环境 xff08 博主测试了两个系统下的安装基本相同 xff09 xff0
  • OpenCV—轮廓操作一站式详解:查找/筛选/绘制/形状描述与重心标注(C++版)

    OpenCV 轮廓操作一站式详解 xff1a 查找 筛选 绘制 形状描述与重心标注 C 43 43 版 轮廓 是定义或限定形状或对象的边或线 xff0c 是机器视觉中的常用的概念 xff0c 多用于目标检测 识别 等任务 关于OpenCV轮
  • 正太分布函数和反函数 标量值函数 (借鉴)

    标准正态分布函数 CREATE function dbo normcdf 64 p decimal 28 18 AS begin
  • 离散时间傅里叶变换(一)

    一 非周期信号的表示 xff1a 离散时间博里叶变换 1 1 离散时间傅里叶变换的导出 1 离散时间傅里叶变换对 要清楚推导过程 X ejw 称为离散时间傅里叶变换 xff0c 这一对式子就是离散时间傅里叶变换对 上式称为综合公式 xff0
  • HuskyLens摄像头系列 | 写给小学生看的视觉PID巡线算法

    Hello xff0c 大家好 xff0c 光天化日之下我又来撸狗了 距离上次撸狗已经过去了个把月时间了 xff0c 那么这次又有什么新惊喜呢 xff1f 先来看一下本期的演示视频吧 https www bilibili com video
  • 时空行为检测数据集 JHMDB & UCF101_24 详解

    文章目录 0 前言1 JHMDB1 1 基本情况1 2 数据准备以及标签详解 2 UDF101 242 1 基本情况2 2 数据准备与标签详解 3 数据集可视化代码 0 前言 现在常用的时空行为检测数据集只有AVA JHMDB UCF101
  • Lock与RLock的区别

    目录 往期推荐介绍区别一区别二 往期推荐 Python多线程的使用 Python线程池的使用 Python多线程的安全问题 B站同名 有温度的算法 已经上线 想观看视频讲解的同学 点击此处直达B站 介绍 在上节中为大家说明了线程访问临界资源
  • ROS 小技巧 - OpenCV4 与 CV_Bridge 配合使用

    1 现象 ROS默认的Python版本是3 3 xff0c 但我系统安装的是OpenCV4 5 如果直接在pkg中使用cv bridge和opencv4 5就会有问题 会有一些undefined reference问题 参考资料 xff1a
  • 【做题系统】后端设计

    目录 一 设计思路 1 项目背景 2 技术栈选择 二 系统设计 1 系统结构图 2 项目结构 3 数据建模 4 数据流图 5 主要流程图 三 问题及解决办法 1 实现安全登录 访问 2 数据库中的信息安全问题 3 Mybatis plus如
  • C/C++字符串查找函数

    C C 43 43 string库 xff08 string h xff09 提供了几个字符串查找函数 xff0c 如下 xff1a memchr在指定内存里定位给定字符strchr在指定字符串里定位给定字符strcspn返回在字符串str
  • ssh命令-manpage

    SSH Section User Commands 1 Index Return to Main Contents BSD mandoc NAME ssh OpenSSH SSH 客户端 远程登录程序 总览 SYNOPSIS ssh l l
  • 一小时做出Java实战项目——飞翔的小鸟

    学姐又来啦 xff0c 今日分享一个Java实战项目 飞翔的小鸟 相信大家都玩过这个游戏 xff0c 这个游戏陪伴了我们整整一个童年 xff0c 是我们青春的回忆 飞翔的小鸟 xff0c 游戏中玩家只需通过点击方向键操纵让小鸟避开绿色管道等
  • 搭建本地仓库源

    一 如何搭建仓库源 之前讲了定制ISO的方法 xff1a 使用chroot定制系统 xff0c 但有时候我们想自定义的安装包不在上游的仓库源中 xff0c 在我们本地应该怎么办呢 xff1f 如果我们将deb包拷贝到iso目录再安装有点过于
  • 节点操作案例

    1 下拉菜单 xff08 仿微博 xff09 lt DOCTYPE html gt lt html lang 61 34 en 34 gt lt head gt lt meta charset 61 34 UTF 8 34 gt lt me
  • document获取对象的三种三方法

    Document对象中有几个常用的方法 xff0c 我们在Dom简介中提到过 说到获取JavaScript对象的方法 xff0c 最常用的可能就是getElementById了 xff0c 它是Document中最常用的获取对象的方式之一
  • 程序员,最关键的跨越是什么?做到了月薪可能翻上几番~

    黑马程序员视频库 播妞微信号 xff1a boniu236 传智播客旗下互联网资讯 学习资源免费分享平台 作为一名程序员 xff0c 最关键的跨越是什么 xff1f 从普通程序员进阶为熟练开发者 xff0c 从熟练开发者跃升到技术专家或架构

随机推荐

  • 黑马程序员:3分钟带你读懂C/C++学习路线

    随着互联网及互联网 43 深入蓬勃的发展 xff0c 经过40余年的时间洗礼 xff0c C C 43 43 俨然已成为一门贵族语言 xff0c 出色的性能使之成为高级语言中的性能王者 而在今天 xff0c 它又扮演着什么样重要的角色呢 x
  • 数据归一化

    原文链接 xff1a 从公式出发 xff1a 什么是模型收敛的有效方法 xff1f 大家好 xff0c 我是泰哥 数据归一化在模型收敛中起着至关重要的作用 xff0c 从经典机器学习到深度学习的数据归一化方法是如何一步步演变的呢 xff1f
  • 【Python面试】 说说Python变量、函数、类的命名规则?

    最近公众号新增加了一个栏目 xff0c 就是每天给大家解答一道Python常见的面试题 xff0c 反正每天不贪多 xff0c 一天一题 xff0c 正好合适 xff0c 只希望这个面试栏目 xff0c 给那些正在准备面试的同学 xff0c
  • ​LeetCode刷题实战46:全排列

    算法的重要性 xff0c 我就不多说了吧 xff0c 想去大厂 xff0c 就必须要经过基础知识和业务逻辑面试 43 算法面试 所以 xff0c 为了提高大家的算法能力 xff0c 这个公众号后续每天带大家做一道算法题 xff0c 题目就从
  • Android硬件访问服务-Service

    Android有四大组件 xff1a 一 Activity 二 Service 三 Broadcast Receiver 四 Content Provider Service是Android中一个类 xff0c 它是Android四大组件之
  • android6.0第三方APP获得设备节点的访问权限

    之前使用android4 4的系统进行开发时 system app xff08 系统自带APP xff09 目录下的 app 可以直接访问 dev 目录下的设备节点 xff0c Android 5 0 以后 xff0c 因为采取了 SEAn
  • U-boot取消或修改启动延时bootdelay

    在我们的实际项目中都希望uboot尽量能够快速启动 xff0c 这就涉及到uboot的裁剪工作 xff0c 由于裁剪的工作量和内容比较多 xff0c 这里暂不描述 但是uboot有个启动延时bootdelay xff0c 在我们进入linu
  • uboot启动分析第一阶段(start.S)

    前面分析了启动脚本 Makefile mkconfig xff0c 接下来就是uboot的start S这个启动代码了 xff0c 下面是本章的平台介绍 xff1a 单板 xff1a 迅为4412开发板 Exynos 4412 SDRAM
  • Android使用串口(基于android-serialport-api)

    运行平台 xff1a CPU xff1a 全志V40 Android版本 xff1a 6 0 1 关于安卓设备上使用串口 xff0c 谷歌官方在github上有提供代码实例 xff0c 里面有JNI的代码和串口API的java文件 xff0
  • FreeRTOS-启动任务调度器源码分析

    本章基于FreeRTOS的启动任务调度器源码分析 xff0c 后续将会上传其它我对FreeRTOS的源码分析过程及理解 xff0c 首先来认识一下任务调度器 任务调度器 xff1a 任务调度器主要用于实现任务的切换 xff0c 任务并不是我
  • FreeRTOS-任务创建源码分析

    任务创建是FreeRTOS系统启动的第一个步骤 xff0c 前面在启动调度器的时候先创建了空闲任务 xff0c 然后再由调度器跳到任务里面去执行 任务创建函数里面做了很多的工作 xff0c 先会为任务堆栈和任务控制块分配内存并初始化它们 x
  • FreeRTOS-任务通知源码分析

    任务通知可用来代替信号量 消息队列 事件标志位 xff0c 而且使用任务通知的形式效率会更高 xff0c 它不需要像信号量那样创建队列和操作队列 xff0c 任务通知的存储变量来自任务控制块中 xff0c 当宏 configUSE TASK
  • Anaconda出现Collecting package metadata (current_repodata.json): failed错误

    安装包时出现这个错误 xff0c 个人怀疑是梯子使用出错 网上很多教程是换源等均没有解决问题 最后解决方法 xff1a 1 卸载clash for Windows xff08 不确定这一步是不是必须的 xff09 2 直接删除C Users
  • FreeRTOS-空闲任务、低功耗源码分析

    FreeRTOS在启动任务调度时会自动创建一个空闲任务 xff0c 空闲任务主要在系统没有其它任务或任务都处于挂起状态时执行 xff0c 它被系统设置为最低优先级 xff0c 不会去抢占其它高优先级的任务 xff0c 从而既能保证系统总有至
  • FreeRTOS-内存管理源码分析

    FreeRTOS 总共提供了5种内存分配方法 xff1a heap 1 c heap 2 c heap 3 c heap 4 c heap 5 c 这五种分配方式各有各的优势 xff0c 用户可根据应用情况按需使用 xff0c 在分析源码之
  • 在线词典项目进展近况

    作者 xff1a 王盛武 最近BLOG虽然没更新 xff0c 但项目正在紧张进行当中 xff0c 大家都在做着自己的任务 xff0c 大家都有一个信念 xff0c 要把事情做把 xff0c 不会半图而废 我主要负责的是RIA前端编写和Web
  • 基于HTML5实现的在线3D虚拟试衣系统(试衣间)解决方案

    3D虚拟试衣系统的使用场景主要是在线电商或数字营销 xff0c 为品牌服装 服饰 饰品添加高端3D虚拟购物动效 xff0c 提升用户感官体验和交互体验 要研发这样的在线系统 xff0c 有2个方向 xff0c 一个是使用Flash或Unit
  • 2011移动开发者大会亮点之一:六大精彩主题论坛抢鲜看

    2011中国移动开发者大会将是2011年度中国最大规模的移动盛事 本次大会全面覆盖软件应用和服务 平台 运营商 终端 芯片等移动产业链各个领域 xff0c 与此同时 xff0c 专注于产业链中最活跃的因素 应用软件的研发 创新与商业模式 x
  • IT毕业生给学弟学妹们的真心话——离校座谈记录

    活动 xff1a 2009级毕业生离校前座谈 地点 xff1a 烟台大学计算机学院4409学业指导工作室 组织 xff1a 烟台大学CSDN高校俱乐部 时间 xff1a 2013年6月1日 上午8 30 参加人员 xff1a 主持人 xff
  • activemq的几种基本通信方式总结

    简介 在前面一篇文章里讨论过几种应用系统集成的方式 xff0c 发现实际上面向消息队列的集成方案算是一个总体比较合理的选择 这里 xff0c 我们先针对具体的一个消息队列Activemq的基本通信方式进行探讨 activemq是JMS消息通