008_Queue消息模式发送映射消息

本文介绍了如何使用Java通过Apache ActiveMQ实现MapMessage的发送与接收,包括创建连接工厂、连接、会话、队列和消息的生产和消费。通过实例展示了如何设置MapMessage内容并进行客户端确认。

1. 新建一个名为JMSActiveMQMapMessage的Java项目, 同时拷入相关jar包

2. 编辑QueueMapMessageProducer.java

package com.jms.activemq.qmm;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

public class QueueMapMessageProducer {
	// 默认连接用户名
	private static final String dftUsr = ActiveMQConnection.DEFAULT_USER;
	// 默认用户密码
	private static final String dftPwd = ActiveMQConnection.DEFAULT_PASSWORD;
	// 默认连接地址
	private static final String dftUrl = ActiveMQConnection.DEFAULT_BROKER_URL;
	// 队列名称
	private static final String queueName = "queueMapMsg";
	
	public static void main(String[] args) {
		// 1. 创建一个连接工厂
		QueueConnectionFactory cf = new ActiveMQConnectionFactory(dftUsr, dftPwd, dftUrl);
		// 连接对象
		QueueConnection conn = null;
		// 会话对象
		QueueSession session = null;
		
		try {
			// 2. 创建连接
			conn = cf.createQueueConnection();
			// 3. 启动连接
			conn.start();
			// 4. 创建会话
			session = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
			// 5. 创建消息目的地。如果是点对点, 那么它的实现是Queue; 如果是订阅模式, 那它的实现是Topic。这里我们创建一个名为queueMapMsg的消息队列。
			Queue queue = session.createQueue(queueName);
			// 6. 消息生产者
			QueueSender sender = session.createSender(null);
			// 7. 创建文本消息和发送消息
			for(int i = 0; i < 2; i++) {
				MapMessage message = session.createMapMessage();
				message.setLong("id", 100000000000L + i);
				message.setString("name", "lisi");
				sender.send(queue, message);
				
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} catch (JMSException e) {
			e.printStackTrace();
		} finally {
			try {
				if (session != null) {
					session.close();
				}
			} catch (JMSException e1) {
				e1.printStackTrace();
			} finally {
				if (conn != null) {
					try {
						conn.close();
					} catch (JMSException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

3. 编辑QueueMapMessageConsumer.java

package com.jms.activemq.qmm;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSession;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

public class QueueMapMessageConsumer {
	// 默认连接用户名
	private static final String dftUsr = ActiveMQConnection.DEFAULT_USER;
	// 默认用户密码
	private static final String dftPwd = ActiveMQConnection.DEFAULT_PASSWORD;
	// 默认连接地址
	private static final String dftUrl = ActiveMQConnection.DEFAULT_BROKER_URL;
	// 队列名称
	private static final String queueName = "queueMapMsg";
		
	public static void main(String[] args) {
		// 1. 创建一个连接工厂
		QueueConnectionFactory cf = new ActiveMQConnectionFactory(dftUsr, dftPwd, dftUrl);
		// 连接对象
		QueueConnection conn = null;
		// 会话对象
		QueueSession session = null;
		
		try {
			// 2. 创建连接
			conn = cf.createQueueConnection();
			// 3. 创建会话
			session = conn.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);
			// 4. 创建消息目的地。如果是点对点, 那么它的实现是Queue; 如果是订阅模式, 那它的实现是Topic。这里我们创建一个名为queueMapMsg的消息队列。
			Queue queue = session.createQueue(queueName);
			// 5. 消息消费者
			QueueReceiver receiver = session.createReceiver(queue);
			// 6. 启动连接, 准备开始接收消息
			conn.start();
			// 7. 接收消息
			MapMessage message = (MapMessage) receiver.receive(1000 * 3);
			System.out.println("接收: id = " + message.getLong("id") + ", name = " + message.getString("name"));
			MapMessage message1 = (MapMessage) receiver.receive(1000 * 3);
			System.out.println("接收: id = " + message1.getLong("id") + ", name = " + message1.getString("name"));
			
			// 8. 客户端主动确认消息已成功接收。进行了一次确认, 却确认了2条消息的消费, 因为这种确认方式是会话级别的。
			message.acknowledge(); 
			
			System.out.println("JMSDestination = " + message.getJMSDestination() + ", JMSDeliveryMode = " + message.getJMSDeliveryMode()
			 + ", JMSExpiration = " + message.getJMSExpiration() + ", JMSPriority = " + message.getJMSPriority()
			 + ",\r\nJMSMessageID = " + message.getJMSMessageID() + ", JMSTimestamp = " + message.getJMSTimestamp()
			 + ", JMSCorrelationID = " + message.getJMSCorrelationID() + ",\r\nJMSReplyTo = " + message.getJMSReplyTo()
			 + ", JMSType = " + message.getJMSType() + ", JMSRedelivered = " + message.getJMSRedelivered());
		} catch (JMSException e) {
			e.printStackTrace();
		} finally {
			try {
				if (session != null) {
					session.close();
				}
			} catch (JMSException e1) {
				e1.printStackTrace();
			} finally {
				if (conn != null) {
					try {
						conn.close();
					} catch (JMSException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

4. 运行QueueMapMessageProducer.java

5. 注释掉确认消息代码, 运行QueueMapMessageConsumer.java

6. 放开确认消息代码, 再次运行QueueMapMessageConsumer.java

<think>我们正在分析Linux内核网络栈中,当网桥(bridge)调用`dev_queue_xmit`发送数据包时,数据包在哪个步骤被发送到物理接口。关键点在于理解`dev_queue_xmit`内部的执行流程,特别是它如何经过排队规则(qdisc)以及最终如何到达设备驱动。 ### 详细步骤分析: 1. **入口函数 `dev_queue_xmit`**: - 位于`net/core/dev.c`,是内核发送数据包的主要入口。 - 函数首先检查设备是否处于运行状态(`__dev_xmit_skb`的前置检查),然后进入排队规则处理。 2. **排队规则(qdisc)处理**: - 如果设备关联了qdisc(默认是`pfifo_fast`),则调用`__dev_xmit_skb`(即上一个问题中分析的函数)。 - 在`__dev_xmit_skb`中,根据qdisc的状态和标志,数据包可能: - 被直接发送(通过bypass路径调用`sch_direct_xmit`)。 - 或者被加入队列,然后在适当的时候被出队发送。 3. **实际发送到物理接口的关键函数 `sch_direct_xmit`**: - 在`__dev_xmit_skb`的bypass路径或标准路径中的出队过程中,会调用`sch_direct_xmit`(定义在`net/sched/sch_generic.c`)。 - 该函数的核心操作: ```c int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, struct net_device *dev, struct netdev_queue *txq, spinlock_t *root_lock, bool validate) { // 1. 如果设备队列已停止,则重新入队并返回1(表示需要再次运行qdisc) if (likely(txq->dev->flags & IFF_UP)) { // 2. 获取设备队列的锁 HARD_TX_LOCK(dev, txq, smp_processor_id()); if (!netif_tx_queue_frozen(txq) && !netif_tx_queue_stopped(txq)) { // 3. 调用设备驱动的发送函数:dev_hard_start_xmit ret = dev_hard_start_xmit(skb, dev, txq); // ... 处理返回值 ... } // 4. 释放设备队列的锁 HARD_TX_UNLOCK(dev, txq); } else { // 设备未开启,丢弃数据包 kfree_skb(skb); } return ret; } ``` 4. **设备驱动发送函数 `dev_hard_start_xmit`**: - 位于`net/core/dev.c`,是内核发送到设备驱动的统一接口。 - 该函数会调用具体设备的`ndo_start_xmit`操作(定义在`net_device_ops`中): ```c static int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq) { // ... 准备 ... // 调用设备驱动的发送函数 rc = netdev_start_xmit(skb, dev, txq, more); // netdev_start_xmit 内部会调用 dev->netdev_ops->ndo_start_xmit // ... 错误处理 ... } ``` - `ndo_start_xmit`是设备驱动实现的函数,负责将数据包传递给硬件(例如,通过DMA将数据包放入硬件发送环)。 5. **物理接口发送完成**: - 设备驱动通过`ndo_start_xmit`将数据包放入硬件发送队列后,硬件会异步地发送数据包。 - 发送完成后,硬件会产生中断,驱动在中断处理程序中释放已发送的数据包(skb)并更新统计信息。 ### 关键结论: - **物理接口发送发生在 `dev_hard_start_xmit` 调用设备驱动的 `ndo_start_xmit` 时**。 - 该调用位于 `sch_direct_xmit` 函数内部,而 `sch_direct_xmit` 又在 `__dev_xmit_skb` 中被调用。 - 因此,网桥通过 `dev_queue_xmit` 发送数据包,最终在 `dev_hard_start_xmit` 中通过设备驱动的发送操作将数据包交给物理接口。 §§相关问题§§ 1. 在 `sch_direct_xmit` 函数中,如何处理设备发送队列被停止(stopped)的情况? 2. 当设备驱动无法立即发送数据包(例如硬件队列满)时,`ndo_start_xmit` 函数应返回什么状态?内核后续如何处理? 3. 物理接口完成数据包发送后,设备驱动如何通知内核释放相关的 `sk_buff` 资源? 4. 在网桥转发数据包时,如果目的接口处于混杂模式(promiscuous mode),会对 `dev_queue_xmit` 的流程产生哪些影响? 5. 在多队列网络设备中,`dev_queue_xmit` 如何选择正确的发送队列(txq)? </think>### `dev_queue_xmit` 发送数据包到物理接口的关键步骤分析 在 Linux 网络栈中,当网桥调用 `dev_queue_xmit` 发送数据包时,数据包最终到达物理接口的完整路径如下: ```mermaid graph LR A[dev_queue_xmit] --> B{设备是否运行} B -- 是 --> C[__dev_xmit_skb] B -- 否 --> D[丢弃数据包] C --> E[排队规则处理<br/>qdisc->enqueue] E --> F{是否可绕过队列} F -- 是 --> G[sch_direct_xmit] F -- 否 --> H[入队等待] G --> I[dev_hard_start_xmit] I --> J[设备驱动发送<br/>ndo_start_xmit] ``` #### **物理接口发送的关键步骤:** 1. **排队规则处理(qdisc 层)**: - 在 `__dev_xmit_skb` 函数中(如前分析),根据 qdisc 状态决定发送路径 - 若满足 bypass 条件(空队列 + TCQ_F_CAN_BYPASS),直接进入发送路径 2. **直接发送函数 `sch_direct_xmit`**: ```c // net/sched/sch_generic.c if (sch_direct_xmit(skb, q, dev, txq, root_lock, true)) { // 发送未完成则重新调度 } ``` - 此函数是连接 qdisc 和设备驱动的桥梁 - 内部获取设备发送队列锁,确保发送原子性 3. **设备驱动接口 `dev_hard_start_xmit`**: ```c // net/core/dev.c static int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) { // 调用设备驱动的发送函数 rc = netdev_start_xmit(skb, dev, txq, more); } ``` - 此函数直接调用网络设备注册的发送操作 4. **物理接口发送(驱动层)**: ```c // 最终执行的设备驱动函数 dev->netdev_ops->ndo_start_xmit(skb, dev); ``` - `ndo_start_xmit` 是设备驱动实现的硬件发送函数 - 例如 Intel e1000 驱动的 `e1000_xmit_frame`: ```c // drivers/net/ethernet/intel/e1000/e1000_main.c static netdev_tx_t e1000_xmit_frame(struct sk_buff *skb, struct net_device *netdev) { // 1. 设置硬件描述符 // 2. 将skb数据映射到DMA区域 // 3. 触发硬件发送 writel(txd_upper, hw->hw_addr + tx_ring->tail); } ``` - 此调用将数据包推入硬件发送队列,由网卡DMA引擎完成物理发送 #### **完整路径总结**: 1. 网桥调用 `dev_queue_xmit` 2. 进入排队规则处理 (`__dev_xmit_skb`) 3. 通过 `sch_direct_xmit` 进入设备驱动层 4. `dev_hard_start_xmit` 调用驱动的 `ndo_start_xmit` 5. **物理发送发生在驱动实现的 `ndo_start_xmit` 函数中**,此时: - 数据包被映射到DMA内存区域 - 硬件发送描述符被配置 - 网卡DMA引擎被触发执行物理发送
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值