RabbitMQ从信息接收者角度可以看做三种模式,一对一,一对多(此一对多并不是发布订阅,而是每条信息只有一个接收者)发布订阅。其中一对一是简单队列模式,一对多是Worker模式,而发布订阅包括发布订阅模式,路由模式和通配符模式,为什么说发布订阅模式包含三种模式呢,其实发布订阅,路由,通配符,Headers 四种模式都是使用只是交换机(Exchange)类型不一致

1 简单队列

  首先,我们需要创建两个控制台项目.Send(发送者)和Receive(接收者),然后为两个项目安装RabbitMQ.Client驱动

install-package rabbitmq.client

  然后在Send和Receive项目中编写我们的消息队列代码

  发送者代码

 // <summary>
/// 一对一
/// 发送
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.WriteLine("Start");
IConnectionFactory conFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection con = conFactory.CreateConnection())//创建连接对象
{
using (IModel channel = con.CreateModel())//创建连接会话对象
{
String queueName = "OneToOneQueue"; //声明一个队列
channel.QueueDeclare(
queue: queueName,//消息队列名称
durable: false,//是否缓存
exclusive: false,
autoDelete: false,
arguments: null
);
while (true)
{
Console.WriteLine("消息内容:");
String message = Console.ReadLine();
//消息内容
byte[] body = Encoding.UTF8.GetBytes(message);
//发送消息
channel.BasicPublish(exchange: "", routingKey: queueName, basicProperties: null, body: body);
Console.WriteLine("成功发送消息:" + message);
}
}
}
}

可以看到RabbitMQ使用了IConnectionFactory,IConnection和IModel来创建链接和通信管道,IConnection实例对象只负责与Rabbit的连接,而发送接收这些实际操作全部由会话通道进行,

  而后使用QueneDeclare方法进行创建消息队列,创建完成后可以在RabbitMQ的管理工具中看到此队列,QueneDelare方法需要一个消息队列名称的必须参数.后面那些参数则代表缓存,参数等信息.

  最后使用BasicPublish来发送消息,在一对一中routingKey必须和 queueName一致

  接收者代码

 class Program
{
/// <summary>
/// 一对一
/// 接受
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.WriteLine("Start");
IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
String queueName = "OneToOneQueue"; //声明一个队列
channel.QueueDeclare(
queue: queueName,//消息队列名称
durable: false,//是否缓存
exclusive: false,
autoDelete: false,
arguments: null
);
//创建消费者对象
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message));
};
//消费者开启监听
channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.ReadKey();
}
}
}

在接收者中是定义一个EventingBasicConsumer对象的消费者(接收者),这个消费者与会话对象关联,

  然后定义接收事件,输出从消息队列中接收的数据,

  最后使用会话对象的BasicConsume方法来启动消费者监听.消费者的定义也是如此简单.

  不过注意一点,可以看到在接收者代码中也有声明队列的方法,其实这句代码可以去掉,但是如果去掉的话接收者在程序启动时监听队列,而此时这个队列还未存在,所以会出异常,所以往往会在消费者中也添加一个声明队列方法

此时,简单消息队列传输就算写好了,我们可以运行代码就行测试

  

  

2 Worker模式

  Worker模式其实是一对多的模式,但是这个一对多并不是像发布订阅那种,而是信息以顺序的传输给每个接收者,我们可以使用上个例子来运行worker模式甚至,只需要运行多个接收者即可

  

  

  

  可以看到运行两个接收者,然后发送者发送了1-5这五个消息,第一个接收者接收的是奇数,而第二个接收者接收的是偶数,但是现在的worker存在这很大的问题,

    1.丢失数据:一旦其中一个宕机,那么另外接收者的无法接收原本这个接收者所要接收的数据

    2.无法实现能者多劳:如果其中的接收者接收的较慢,那么便会极大的浪费性能,所以需要实现接收快的多接收

  下面针对上面的两个问题进行处理,首先我们先来看一下所说的宕机丢失数据一说,我们在上个例子Receive接收事件中添加线程等待

  consumer.Received += (model, ea) =>
{
Thread.Sleep();//等待1秒,
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message));
};

然后再次启动两个接收者进行测试

      

  

  

  可以看到发送者发送了1-9的数字,第二个接收者在接收数据途中宕机,第一个接收者也并没有去接收第二个接收者宕机后的数据,有的时候我们会有当接收者宕机后,其余数据交给其它接收者进行消费,那么该怎么进行处理呢,解决这个问题得方法就是改变其消息确认模式

  在Rabbit中存在两种消息确认模式,

    自动确认:只要消息从队列获取,无论消费者获取到消息后是否成功消费,都认为是消息成功消费,也就是说上面第二个接收者其实已经消费了它所接收的数据

    手动确认:消费从队列中获取消息后,服务器会将该消息处于不可用状态,等待消费者反馈

  也就是说我们只要将消息确认模式改为手动即可,改为手动确认方式只需改两处,1.开启监听时将autoAck参数改为false,2.消息消费成功后返回确认

 consumer.Received += (model, ea) =>
{
Thread.Sleep();//等待1秒,
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message));
//返回消息确认
channel.BasicAck(ea.DeliveryTag, true);
};
//消费者开启监听
//将autoAck设置false 关闭自动确认
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);

然后再次测试便会出现下面结果

  

  

  能者多劳是建立在手动确认基础上,下面修改一下代码中等待的时间

 consumer.Received += (model, ea) =>
{
Thread.Sleep((new Random().Next(,))*);//随机等待,实现能者多劳,
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message));
//返回消息确认
channel.BasicAck(ea.DeliveryTag, true);
};

然后只需要再添加BasicQos方法即可

 //声明一个队列
channel.QueueDeclare(
queue: queueName,//消息队列名称
durable: false,//是否缓存
exclusive: false,
autoDelete: false,
arguments: null
);
//告诉Rabbit每次只能向消费者发送一条信息,再消费者未确认之前,不再向他发送信息
channel.BasicQos(, , false);

     

  

  可以看到此时已实现能者多劳

3 Exchange模式(发布订阅模式,路由模式,通配符模式,Headers模式)

  前面说过发布,路由,通配符这三种模式其实可以算为一种模式,区别仅仅是交互机类型不同.在这里出现了一个交换机的东西,发送者将消息发送发送到交换机,接收者创建各自的消息队列绑定到交换机,

       发布订阅模式

     路由模式

     通配符模式

  通过上面三幅图可以看出这三种模式本质就是一种订阅模式,路由,通配符模式只是订阅模式的变种模式。使其可以选择发送订阅者中的接收者。

  注意:交换机本身并不存储数据,数据存储在消息队列中,所以如果向没有绑定消息队列的交换机中发送信息,那么信息将会丢失

  下面依次来看一下这三种模式

  发布订阅模式(fanout)

  发送者代码

  /// <summary>
/// 发布订阅
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.WriteLine("Start");
IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubFanOutExchange";
//声明交换机
channel.ExchangeDeclare(exchange: exchangeName, type: "fanout");
while (true)
{
Console.WriteLine("消息内容:");
String message = Console.ReadLine();
//消息内容
byte[] body = Encoding.UTF8.GetBytes(message);
//发送消息
channel.BasicPublish(exchange: exchangeName, routingKey: "", basicProperties: null, body: body);
Console.WriteLine("成功发送消息:" + message);
}
}
}
}

发送者代码与上面没有什么差异,只是由上面的消息队列声明变成了交换机声明(交换机类型为fanout),也就说发送者发送消息从原来的直接发送消息队列变成了发送到交换机

  接收者代码

  class Program
{
/// <summary>
/// 发布订阅
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
//创建一个随机数,以创建不同的消息队列
int random = new Random().Next(, );
Console.WriteLine("Start" + random.ToString());
IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubFanOutExchange";
//声明交换机
channel.ExchangeDeclare(exchange: exchangeName, type: "fanout");
//消息队列名称
String queueName = exchangeName + "_" + random.ToString();
//声明队列
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
//将队列与交换机进行绑定
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: "");
//声明为手动确认
channel.BasicQos(, , false);
//定义消费者
var consumer = new EventingBasicConsumer(channel);
//接收事件
consumer.Received += (model, ea) =>
{
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message));
//返回消息确认
channel.BasicAck(ea.DeliveryTag, true);
};
//开启监听
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
Console.ReadKey();
}
}
}

 可以看到接收者代码与上面有些差异

  首先是声明交换机(同上面一样,为了防止异常)

  然后声明消息队列并对交换机进行绑定,在这里使用了随机数,目的是声明不重复的消息队列,如果是同一个消息队列,则就变成worker模式,也就是说对于发布订阅模式有多少接收者就有多少个消息队列,而这些消息队列共同从一个交换机中获取数据

  然后同时开两个接收者,结果就如下

  

  

  

  路由模式(direct)

  上面说过路由模式是订阅模式的一个变种模式,以路由进行匹配发送,例如将消息1发送给A,B两个消息队列,或者将消息2发送给B,C两个消息队列,路由模式的交换机是direct

  发送者代码

  static void Main(string[] args)
{
if (args.Length == ) throw new ArgumentException("args");
Console.WriteLine("Start");
IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubDirectExchange";
//路由名称
String routeKey = args[];
//声明交换机 路由交换机类型direct
channel.ExchangeDeclare(exchange: exchangeName, type: "direct");
while (true)
{
Console.WriteLine("消息内容:");
String message = Console.ReadLine();
//消息内容
byte[] body = Encoding.UTF8.GetBytes(message);
//发送消息 发送到路由匹配的消息队列中
channel.BasicPublish(exchange: exchangeName, routingKey: routeKey, basicProperties: null, body: body);
Console.WriteLine("成功发送消息:" + message);
}
}
}
}

发送者代码相比上面只改了两处

    1.将交换机类型改为了direct类型

    2.将运行时的第一个参数改成了路由名称,然后发送数据时由指定路由的消息队列进行获取数据

  接收者代码

  static void Main(string[] args)
{
if (args.Length == ) throw new ArgumentException("args");
//创建一个随机数,以创建不同的消息队列
int random = new Random().Next(, );
Console.WriteLine("Start" + random.ToString());
IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubDirectExchange";
//声明交换机
channel.ExchangeDeclare(exchange: exchangeName, type: "direct");
//消息队列名称
String queueName = exchangeName + "_" + random.ToString();
//声明队列
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
//将队列与交换机进行绑定 foreach (var routeKey in args)
{//匹配多个路由
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: routeKey);
}
//声明为手动确认
channel.BasicQos(, , false);
//定义消费者
var consumer = new EventingBasicConsumer(channel);
//接收事件
consumer.Received += (model, ea) =>
{
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message));
//返回消息确认
channel.BasicAck(ea.DeliveryTag, true);
};
//开启监听
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
Console.ReadKey();
}
}

在接收者代码中的改动点也是与发送者一致,但是一个接收者消息队列可以声明多个路由与交换机进行绑定

  运行情况如下

  

  

  

  通配符模式(topic)

  通配符模式与路由模式一致,只不过通配符模式中的路由可以声明为模糊查询,RabbitMQ拥有两个通配符

    #:匹配0-n个字符语句

    *:匹配一个字符语句

    注意:RabbitMQ中通配符并不像正则中的单个字符,而是一个以“.”分割的字符串,如 ”topic1.*“匹配的规则以topic1开始并且"."后只有一段语句的路由  例:“topic1.aaa”,“topic1.bb”

  发送者代码

  static void Main(string[] args)
{
if (args.Length == ) throw new ArgumentException("args");
Console.WriteLine("Start");
IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubTopicExchange";
//路由名称
String routeKey = args[];
//声明交换机 通配符类型为topic
channel.ExchangeDeclare(exchange: exchangeName, type: "topic");
while (true)
{
Console.WriteLine("消息内容:");
String message = Console.ReadLine();
//消息内容
byte[] body = Encoding.UTF8.GetBytes(message);
//发送消息 发送到路由匹配的消息队列中
channel.BasicPublish(exchange: exchangeName, routingKey: routeKey, basicProperties: null, body: body);
Console.WriteLine("成功发送消息:" + message);
}
}
}
}

修改了两点:交换机名称(每个交换机只能声明一种类型,如果还用exchang2的话就会出异常),交换机类型改为topic

  接收者代码

 static void Main(string[] args)
{
if (args.Length == ) throw new ArgumentException("args");
//创建一个随机数,以创建不同的消息队列
int random = new Random().Next(, );
Console.WriteLine("Start" + random.ToString());
IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "localhost",//IP地址
Port = ,//端口号
UserName = "guest",//用户账号
Password = "guest"//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubTopicExchange";
//声明交换机 通配符类型为topic
channel.ExchangeDeclare(exchange: exchangeName, type: "topic");
//消息队列名称
String queueName = exchangeName + "_" + random.ToString();
//声明队列
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
//将队列与交换机进行绑定
foreach (var routeKey in args)
{//匹配多个路由
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: routeKey);
}
//声明为手动确认
channel.BasicQos(, , false);
//定义消费者
var consumer = new EventingBasicConsumer(channel);
//接收事件
consumer.Received += (model, ea) =>
{
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message));
//返回消息确认
channel.BasicAck(ea.DeliveryTag, true);
};
//开启监听
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
Console.ReadKey();
}
}
}

接收者修改与发送者一致

  运行结果如下

  

  

  

  Headers 模式

  Headers 类型的Exchanges是不处理路由键的,而是根据发送的消息内容中的headers属性进行匹配。在绑定Queue与Exchange时指定一组键值对;当消息发送到RabbitMQ时会取到该消headers    与Exchange绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers属性是一个键值对,可以是Hashtable,键值对的值可以是任何类型。而fanout,direct,topic 的路由键都需要要字符串形式的。

  匹配规则x-match有下列两种类型:

    x-match = all   :表示所有的键值对都匹配才能接受到消息

    x-match = any :表示只要有键值对匹配就能接受到消息

  发送者代码  

  IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "192.168.1.107",//IP地址
Port = ,//端口号
UserName = "ztb",//用户账号
Password = ""//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubHeardesExchange"; //设置headers
var properties = channel.CreateBasicProperties();
properties.Headers = new Dictionary<string, object>();
properties.Headers.Add("username", "jack"); while (true)
{
Console.WriteLine("消息内容:");
String message = Console.ReadLine();
//消息内容
byte[] body = Encoding.UTF8.GetBytes(message);
//发送消息
channel.BasicPublish(exchange: exchangeName, routingKey: string.Empty, basicProperties: properties, body: body);
Console.WriteLine("成功发送消息:" + message);
}
}
}

  

  接收者代码

  IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
{
HostName = "192.168.1.107",//IP地址
Port = ,//端口号
UserName = "ztb",//用户账号
Password = ""//用户密码
};
using (IConnection conn = connFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//交换机名称
String exchangeName = "pubSubHeardesExchange";
//声明交换机
channel.ExchangeDeclare(exchange: exchangeName, type: "headers");
//消息队列名称
String queueName = "myHeadersQueue";
//声明队列
channel.QueueDeclare(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null); //匹配路由
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: string.Empty, arguments: new Dictionary<string, object>()
{
{"x-match", "any"},
{"username", "jack"},
{"password", "" }
}); //定义消费者
var consumer = new EventingBasicConsumer(channel);
//接收事件
consumer.Received += (model, ea) =>
{
byte[] message = ea.Body;//接收到的消息
Console.WriteLine("接收到信息为:" + Encoding.UTF8.GetString(message)); };
//开启监听
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
Console.ReadKey();
}

  运行结果如下

  

源代码下载地址:https://github.com/zhenglong2015/RabbitMqDemo

RabbitMQ使用(下)的更多相关文章

  1. RabbitMq window下配置安装

    1. 搭建环境 1.1 安装Erlang语言运行环境 由于RabbitMQ使用Erlang语言编写,所以先安装Erlang语言运行环境. 1.2 Erlang(['ə:læŋ])是一种通用的面向并发的 ...

  2. RabbitMQ Windows下安装问题解决

    一.问题描述 安装后出现如下问题描述 C:\Program Files\RabbitMQ Server\rabbitmq_server-3.4.3\sbin>rabbitmqctl status ...

  3. centos/rhel 6.5下rabbitmq安装(最简单方便的方式)

    wget -c http://apt.sw.be/redhat/el5/en/x86_64/rpmforge/RPMS/rpmforge-release-0.3.6-1.el5.rf.x86_64.r ...

  4. 【linux环境下】RabbitMq的安装和监控插件安装

    [注意安装过程中,提示某些命令not found,直接yum isntall一下就好了] 以下是我在CentOS release 6.4下亲测成功的. RabbitMq的安装:   RabbitMQ是 ...

  5. Linux下 RabbitMQ的安装与配置

    以下教程摘录自互联网并做了适当修改,测试的rabbitmq 版本为:rabbitmq-server-generic-unix-3.5.6 各版本之间会有差异!!! 一  Erlang安装 Rabbit ...

  6. Win10下安装RabbitMQ以及基本知识学习

    一.为什么选择RabbitMQ?      先说一下场景,这是我们公司遇到,当然我这里不做业务评价哈?虽然我知道他很不合理,但是我是无能为力的.APP端部分注册是Java开发的系统,然后业务端是C#开 ...

  7. Window10下RabbitMQ安装图文教程

    版权声明:本文为博主原创文章,未经博主允许欢迎转载,请注明原文链接.一起交流,共同进步. https://blog.csdn.net/newbie_907486852/article/details/ ...

  8. centos 7下rabbitmq安装(转)

    安装erlang环境 添加rabbitmq依赖的erlang yum命令repos # In /etc/yum.repos.d/rabbitmq-erlang.repo [rabbitmq-erlan ...

  9. windows环境下安装rabbitmq及配置可视化管理界面

    1.环境 windows7 64位 rabbitmq3.7.9 erlang10.22.先安装erlang,后安装rabbitmq.下载地址: rabbitmq http://www.rabbitmq ...

  10. centos/rhel 6.5(更新至centos 7)下rabbitmq安装(最简单方便的方式)

    vim /etc/hosts 增加 127.0.0.1 hostname 不然启动的时候可能会报如下错误: [root@devel2 rabbitmq]# rabbitmq-server ERROR: ...

随机推荐

  1. .NET Framework 2.0/3.0/3.5 以 v90 平台工具集为目标。请确保在计算机上安装了 Visual Studio 2008

    今天在Visual Studio2010下面开发C++应用的时候发现“.NET Framework 2.0/3.0/3.5 以 v90 平台工具集为目标.请确保在计算机上安装了 Visual Stud ...

  2. HBASE工作原理

    如上图所示:首先我们需要知道 HBase 的集群是通过 Zookeeper 来进行机器之前的协调,也就是说 HBase Master 与 Region Server 之间的关系是依赖 Zookeepe ...

  3. Rootkit XSS

    0x00 XSS Rootkit介绍 Rootkit概念: 一种特殊的恶意软件            类型: 常见为木马.后门等            特点: 隐蔽 持久控制 谈到XSS,一般都是想到 ...

  4. strcmp-sse2-unaligned.S: No such file or directory.

    __strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:3131 ../sysdeps/x8 ...

  5. [易学易懂系列|rustlang语言|零基础|快速入门|(13)|Generics泛型]

    [易学易懂系列|rustlang语言|零基础|快速入门|(13)] 有意思的基础知识 Generics泛型 我们今天来看看泛型. 什么是泛型? 我们来看看这样的情景: 我们要写一个函数,这个函数可以处 ...

  6. Mvc Excel导入加事务加导出

    导出 public FileResult Input() { DataTable dt = new DataTable("dd"); using (SqlConnection co ...

  7. outlook 使用临时邮箱 使用旧数据

    控制面板-->邮件32位 显示配置文件 删除再添加 具体可参考 https://blog.csdn.net/liuyukuan/article/details/80043840 偷懒,图片从网上 ...

  8. python基础练习题7

    1.创建Person类,属性有姓名.年龄.性别,创建方法personInfo,打印这个人的信息2.创建Student类,继承Person类,属性有学院college,班级class,重写父类perso ...

  9. 《Head First 软件开发》阅读六

    软件错误:专业排错 你编写的代码,你的责任.处理错误的方法和其他流程一样,准备好白板.让客户参与.满怀信心的估计.重构与预构. 首先是与客户加强沟通,不管是谁的代码,在自己的系统里就是自己的代码.使代 ...

  10. 【Islands and Bridges】题解

    题目 题目描述 给定一些岛屿和一些连接岛屿的桥梁,大家都知道汉密尔顿路是访问每个岛屿一次的路线,在我们这个地图中,每个岛屿有个正整数的权值,表示这个岛屿的观赏价值.假设一共有N个岛屿,用Vi表示岛屿C ...