0%

在文章开始之前,首先简单介绍一下什么是MEF,MEF,全称Managed Extensibility Framework(托管可扩展框架)。单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架,MSDN中对MEF有这样一段说明:

Managed Extensibility Framework 或 MEF 是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。

  废话不多说了,想了解更多关于MEF的内容,到百度上面查吧!

MEF的使用范围广泛,在Winform、WPF、Win32、Silverlight中都可以使用,我们就从控制台说起,看看控制台下如何实现MEF,下面先新建一个win32控制台项目MEFDemo,添加一个IBookService接口,写一个简单的Demo:

IBookService内容如下:

复制代码

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MEFDemo
{ public interface IBookService
{ string BookName { get; set; } string GetBookName();
}
}

复制代码

下面添加对System.ComponentModel.Composition命名空间的引用,由于在控制台程序中没有引用这个DLL,所以要手动添加:

点击OK,完成添加引用,新建一个Class,如:MusicBook继承IBookService接口,代码如下:

复制代码

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.Composition; namespace MEFDemo
{
[Export(typeof(IBookService))] public class MusicBook : IBookService
{ public string BookName { get; set; } public string GetBookName()
{ return “MusicBook”;
}
}
}

复制代码

Export(typeof(IBookService)) 这句话将类声明导出为IBookService接口类型。

然后修改Porgram中的代码如下:

复制代码

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; namespace MEFDemo
{ class Program
{
[Import] public IBookService Service { get; set; } static void Main(string[] args)
{
Program pro = new Program();
pro.Compose(); if (pro.Service != null)
{
Console.WriteLine(pro.Service.GetBookName());
}
Console.Read();
} private void Compose()
{ var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
}

复制代码

这里使用[Import]导入刚刚导出的MusicBook,下面的Compose方法,实例化CompositionContainer来实现组合,点击F5运行,结果如下:

可以看到调用了MusicBook类的GetBookName方法,但是我们并没有实例化MusicBook类,是不是很神奇呢???

这一就是实现了主程序和类之间的解耦,大大提高了代码的扩展性和易维护性!

MEF系列文章:

 C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo

C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)

C#可扩展编程之MEF学习笔记(三):导出类的方法和属性

C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

C#可扩展编程之MEF学习笔记(五):MEF高级进阶

本文汇总了C#启动外部程序的几种常用方法,非常具有实用价值,主要包括如下几种方法:

1. 启动外部程序,不等待其退出。
2. 启动外部程序,等待其退出。
3. 启动外部程序,无限等待其退出。
4. 启动外部程序,通过事件监视其退出。

实现代码如下:

复制代码

// using System.Diagnostics;
private string appName = “calc.exe”; ///


/// 1. 启动外部程序,不等待其退出 ///

private void button1_Click(object sender, EventArgs e)
{
Process.Start(appName);
MessageBox.Show(String.Format(“外部程序 {0} 启动完成!”, this.appName), this.Text,
MessageBoxButtons.OK, MessageBoxIcon.Information);
} ///
/// 2. 启动外部程序,等待其退出 ///

private void button2_Click(object sender, EventArgs e)
{ try {
Process proc = Process.Start(appName); if (proc != null)
{
proc.WaitForExit(3000); if (proc.HasExited) MessageBox.Show(String.Format(“外部程序 {0} 已经退出!”, this.appName), this.Text,
MessageBoxButtons.OK, MessageBoxIcon.Information); else { // 如果外部程序没有结束运行则强行终止之。
proc.Kill();
MessageBox.Show(String.Format(“外部程序 {0} 被强行终止!”, this.appName), this.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
} catch (ArgumentException ex)
{
MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
} ///
/// 3. 启动外部程序,无限等待其退出 ///

private void button3_Click(object sender, EventArgs e)
{ try {
Process proc = Process.Start(appName); if (proc != null)
{
proc.WaitForExit();
MessageBox.Show(String.Format(“外部程序 {0} 已经退出!”, this.appName), this.Text,
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
} catch (ArgumentException ex)
{
MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
} ///
/// 4. 启动外部程序,通过事件监视其退出 ///

private void button4_Click(object sender, EventArgs e)
{ try { //启动外部程序
Process proc = Process.Start(appName); if (proc != null)
{ //监视进程退出
proc.EnableRaisingEvents = true; //指定退出事件方法
proc.Exited += new EventHandler(proc_Exited);
}
} catch (ArgumentException ex)
{
MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
} ///
///启动外部程序退出事件 ///

void proc_Exited(object sender, EventArgs e)
{
MessageBox.Show(String.Format(“外部程序 {0} 已经退出!”, this.appName), this.Text,
MessageBoxButtons.OK, MessageBoxIcon.Information);

复制代码

1、概念

1.0 线程的和进程的关系以及优缺点

  windows系统是一个多线程的操作系统。一个程序至少有一个进程,一个进程至少有一个线程。进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线程称为主线程。例如当我们创建一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要 是产生新的线程和执行程序。C#是一门支持多线程的编程语言,通过Thread类创建子线程,引入using System.Threading命名空间。 

_多线程的优点_:

1

2

1、 多线程可以提高CPU的利用率,因为当一个线程处于等待状态的时候,CPU会去执行另外的线程

2、 提高了CPU的利用率,就可以直接提高程序的整体执行速度

多线程的缺点:

1

2

3

1、线程开的越多,内存占用越大

2、协调和管理代码的难度加大,需要CPU时间跟踪线程

3、线程之间对资源的共享可能会产生可不遇知的问题

     1.1 前台线程和后台线程

C#中的线程分为前台线程和后台线程,线程创建时不做设置默认是前台线程。即线程属性IsBackground=false。

Thread.IsBackground = false;//false:设置为前台线程,系统默认为前台线程。

区别以及如何使用:

    这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序。

线程是寄托在进程上的,进程都结束了,线程也就不复存在了!

只要有一个前台线程未退出,进程就不会终止!即说的就是程序不会关闭!(即在资源管理器中可以看到进程未结束。)

1.3 多线程的创建

下面的代码创建了一个子线程,作为程序的入口mian()函数所在的线程即为主线程,我们通过Thread类来创建子线程,Thread类有

复制代码

class Program
{ //我们的控制台程序入口是main函数。它所在的线程即是主线程
static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = “子线程”; //thread.Start(“王建”); //在此方法内传递参数,类型为object,发送和接收涉及到拆装箱操作
thread.Start();
Console.ReadKey();
} public static void ThreadMethod(object parameter) //方法内可以有参数,也可以没有参数
{
Console.WriteLine(“{0}开始执行。”, Thread.CurrentThread.Name);
}
}

复制代码

首先使用new Thread()创建出新的线程,然后调用Start方法使得线程进入就绪状态,得到系统资源后就执行,在执行过程中可能有等待、休眠、死亡和阻塞四种状态。正常执行结束时间片后返回到就绪状态。如果调用Suspend方法会进入等待状态,调用Sleep或者遇到进程同步使用的锁机制而休眠等待。具体过程如下图所示:

2、线程的基本操作

线程和其它常见的类一样,有着很多属性和方法,参考下表:

2.1 线程的相关属性

我们可以通过上面表中的属性获取线程的一些相关信息,下面是代码展示和输出结果:

复制代码

static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = “子线程”;
thread.Start();
StringBuilder threadInfo = new StringBuilder();
threadInfo.Append(“ 线程当前的执行状态: “ + thread.IsAlive);
threadInfo.Append(“\n 线程当前的名字: “ + thread.Name);
threadInfo.Append(“\n 线程当前的优先级: “ + thread.Priority);
threadInfo.Append(“\n 线程当前的状态: “ + thread.ThreadState);
Console.Write(threadInfo);
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{
Console.WriteLine(“{0}开始执行。”, Thread.CurrentThread.Name);
}

复制代码

 输输出结果: 

2.2 线程的相关操作

  2.2.1 Abort()方法

     Abort()方法用来终止线程,调用此方法强制停止正在执行的线程,它会抛出一个ThreadAbortException异常从而导致目标线程的终止。下面代码演示:

复制代码

static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = “小A”;
thread.Start();
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{
Console.WriteLine(“我是:{0},我要终止了”, Thread.CurrentThread.Name); //开始终止线程
Thread.CurrentThread.Abort(); //下面的代码不会执行
for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name,i);
}
}

复制代码

执行结果:和我们想象的一样,下面的循环没有被执行

2.2.2 ResetAbort()方法

Abort方法可以通过跑出ThreadAbortException异常中止线程,而使用ResetAbort方法可以取消中止线程的操作,下面通过代码演示使用 ResetAbort方法。

复制代码

static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = “小A”;
thread.Start();
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{ try {
Console.WriteLine(“我是:{0},我要终止了”, Thread.CurrentThread.Name);
         //开始终止线程
Thread.CurrentThread.Abort();
} catch(ThreadAbortException ex)
{
Console.WriteLine(“我是:{0},我又恢复了”, Thread.CurrentThread.Name);
         //恢复被终止的线程
Thread.ResetAbort();
} for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name,i);
}
}

复制代码

执行结果:

  2.2.3 Sleep()方法 

       Sleep()方法调已阻塞线程,是当前线程进入休眠状态,在休眠过程中占用系统内存但是不占用系统时间,当休眠期过后,继续执行,声明如下:  

    public static void Sleep(TimeSpan timeout);          //时间段 public static void Sleep(int millisecondsTimeout);   //毫秒数

  实例代码: 

复制代码

static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “小A”;
threadA.Start();
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{ for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name,i);
Thread.Sleep(300); //休眠300毫秒
}
}

复制代码

将上面的代码执行以后,可以清楚的看到每次循环之间相差300毫秒的时间。

2.2.4 join()方法

      Join方法主要是用来阻塞调用线程,直到某个线程终止或经过了指定时间为止。官方的解释比较乏味,通俗的说就是创建一个子线程,给它加了这个方法,其它线程就会暂停执行,直到这个线程执行完为止才去执行(包括主线程)。她的方法声明如下:

public void Join(); public bool Join(int millisecondsTimeout); //毫秒数 public bool Join(TimeSpan timeout);       //时间段

为了验证上面所说的,我们首先看一段代码:  

复制代码

static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “小A”;
Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “小B”;
threadA.Start();
       //threadA.Join(); 
threadB.Start();
       //threadB.Join(); for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:主线程,我循环{1}次”, Thread.CurrentThread.Name, i);
Thread.Sleep(300); //休眠300毫秒
}
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{ for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name,i);
Thread.Sleep(300); //休眠300毫秒
}
}

复制代码

因为线程之间的执行是随机的,所有执行结果和我们想象的一样,杂乱无章!但是说明他们是同时执行的。

     现在我们把代码中的  ThreadA.join()方法注释取消,首先程序中有三个线程,ThreadA、ThreadB和主线程,首先主线程先阻塞,然后线程ThreadB阻塞,ThreadA先执行,执行完毕以后ThreadB接着执行,最后才是主线程执行。

看执行结果:

blob.png

2.2.5 Suspent()和Resume()方法

其实在C# 2.0以后, Suspent()和Resume()方法已经过时了。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend()。

复制代码

static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “小A”;
threadA.Start();
Thread.Sleep(3000); //休眠3000毫秒
threadA.Resume(); //继续执行已经挂起的线程
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{
Thread.CurrentThread.Suspend(); //挂起当前线程
for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name, i);
}
}

复制代码

       执行上面的代码。窗口并没有马上执行 ThreadMethod方法输出循环数字,而是等待了三秒钟之后才输出,因为线程开始执行的时候执行了Suspend()方法挂起。然后主线程休眠了3秒钟以后又通过Resume()方法恢复了线程threadA。

2.2.6 线程的优先级

如果在应用程序中有多个线程在运行,但一些线程比另一些线程重要,这种情况下可以在一个进程中为不同的线程指定不同的优先级。线程的优先级可以通过Thread类Priority属性设置,Priority属性是一个ThreadPriority型枚举,列举了5个优先等级:AboveNormal、BelowNormal、Highest、Lowest、Normal。公共语言运行库默认是Normal类型的。见下图:

直接上代码来看效果:

static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “A”;
Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “B”;
threadA.Priority = ThreadPriority.Highest;
threadB.Priority = ThreadPriority.BelowNormal;
threadB.Start();
threadA.Start();
Thread.CurrentThread.Name = “C”;
ThreadMethod(new object());
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{ for (int i = 0; i < 500; i++)
{
Console.Write(Thread.CurrentThread.Name);
}
}

View Code

执行结果:

上面的代码中有三个线程,threadA,threadB和主线程,threadA优先级最高,threadB优先级最低。这一点从运行结果中也可以看出,线程B 偶尔会出现在主线程和线程A前面。当有多个线程同时处于可执行状态,系统优先执行优先级较高的线程,但这只意味着优先级较高的线程占有更多的CPU时间,并不意味着一定要先执行完优先级较高的线程,才会执行优先级较低的线程。

优先级越高表示CPU分配给该线程的时间片越多,执行时间就多

优先级越低表示CPU分配给该线程的时间片越少,执行时间就少

3、线程同步

什么是线程安全:

线程安全是指在当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

   线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

为什么要实现同步呢,下面的例子我们拿著名的单例模式来说吧。看代码

复制代码

public class Singleton
{ private static Singleton instance; private Singleton() //私有函数,防止实例
{

    } public static Singleton GetInstance()
    { if (instance == null)
        {
            instance \= new Singleton();
        } return instance;
    }
}

复制代码

       单例模式就是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例,并为客户程序提供一个获取该实例的全局访问点。但上面代码有一个明显的问题,那就是假如两个线程同时去获取这个对象实例,那。。。。。。。。

我们队代码进行修改:

复制代码

public class Singleton
{ private static Singleton instance; private static object obj=new object(); private Singleton()        //私有化构造函数
{

   } public static Singleton GetInstance()
   { if(instance==null)
           { lock(obj)      //通过Lock关键字实现同步
                  { if(instance==null)
                         {
                                 instance\=new Singleton();
                         }
                  }
           } return instance;
   }

}

复制代码

经过修改后的代码。加了一个 lock(obj)代码块。这样就能够实现同步了,假如不是很明白的话,咱们看后面继续讲解~

3.0 使用Lock关键字实现线程同步

首先创建两个线程,两个线程执行同一个方法,参考下面的代码:

复制代码

static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “王文建”;
Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “生旭鹏”;
threadA.Start();
threadB.Start();
Console.ReadKey();
} public static void ThreadMethod(object parameter)
{ for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name, i);
Thread.Sleep(300);
}
}

复制代码

执行结果:

通过上面的执行结果,可以很清楚的看到,两个线程是在同时执行ThreadMethod这个方法,这显然不符合我们线程同步的要求。我们对代码进行修改如下:

static void Main(string[] args)
{
Program pro = new Program();
Thread threadA = new Thread(pro.ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “王文建”;
Thread threadB = new Thread(pro.ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “生旭鹏”;
threadA.Start();
threadB.Start();
Console.ReadKey();
} public void ThreadMethod(object parameter)
{ lock (this) //添加lock关键字
{ for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name, i);
Thread.Sleep(300);
}
}
}

View Code

执行结果:

我们通过添加了 lock(this) {…}代码,查看执行结果实现了我们想要的线程同步需求。但是我们知道this表示当前类实例的本身,那么有这么一种情况,我们把需要访问的方法所在的类型进行两个实例A和B,线程A访问实例A的方法ThreadMethod,线程B访问实例B的方法ThreadMethod,这样的话还能够达到线程同步的需求吗。

static void Main(string[] args)
{
Program pro1 = new Program();
Program pro2 = new Program();
Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “王文建”;
Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “生旭鹏”;
threadA.Start();
threadB.Start();
Console.ReadKey();
} public void ThreadMethod(object parameter)
{ lock (this)
{ for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name, i);
Thread.Sleep(300);
}
}
}

View Code

执行结果:

我们会发现,线程又没有实现同步了!lock(this)对于这种情况是不行的!所以需要我们对代码进行修改!修改后的代码如下: 

private static object obj = new object(); static void Main(string[] args)
{
Program pro1 = new Program();
Program pro2 = new Program();
Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “王文建”;
Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “生旭鹏”;
threadA.Start();
threadB.Start();
Console.ReadKey();
} public void ThreadMethod(object parameter)
{ lock (obj)
{ for (int i = 0; i < 10; i++)
{
Console.WriteLine(“我是:{0},我循环{1}次”, Thread.CurrentThread.Name, i);
Thread.Sleep(300);
}
}
}

View Code

通过查看执行结果。会发现代码实现了我们的需求。那么 lock(this) 和lock(Obj)有什么区别呢? 

lock(this) 锁定 当前实例对象,如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。所有不推荐使用。
lock(typeof(Model))锁定的是model类的所有实例。
lock(obj)锁定的对象是全局的私有化静态变量。外部无法对该变量进行访问。
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
所以,lock的结果好不好,还是关键看锁的谁,如果外边能对这个谁进行修改,lock就失去了作用。所以一般情况下,使用私有的、静态的并且是只读的对象。

总结:

1、lock的是必须是引用类型的对象,string类型除外。

2、lock推荐的做法是使用静态的、只读的、私有的对象。

3、保证lock的对象在外部无法修改才有意义,如果lock的对象在外部改变了,对其他线程就会畅通无阻,失去了lock的意义。

不能锁定字符串,锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步的效果。lock(typeof(Class))与锁定字符串一样,范围太广了。

3.1 使用Monitor类实现线程同步      

      Lock关键字是Monitor的一种替换用法,lock在IL代码中会被翻译成Monitor. 

     lock(obj)

              {
                 //代码段
             } 
    就等同于 
    Monitor.Enter(obj); 
                //代码段
    Monitor.Exit(obj);  

          ** Monitor的常用属性和方法:**

    Enter(Object) 在指定对象上获取排他锁。

    Exit(Object) 释放指定对象上的排他锁。 

    Pulse 通知等待队列中的线程锁定对象状态的更改。

    PulseAll 通知所有的等待线程对象状态的更改。

    TryEnter(Object) 试图获取指定对象的排他锁。

    TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。

    Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。

      常用的方法有两个,Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个方法,在使用过程中为了避免获取锁之后因为异常,致锁无法释放,所以需要在try{} catch(){}之后的finally{}结构体中释放锁(Monitor.Exit())。

Enter(Object)的用法很简单,看代码

复制代码

static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “A”;
Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “B”;
threadA.Start();
threadB.Start();
Thread.CurrentThread.Name = “C”;
ThreadMethod();
Console.ReadKey();
} static object obj = new object(); public static void ThreadMethod()
{
Monitor.Enter(obj); //Monitor.Enter(obj) 锁定对象 try { for (int i = 0; i < 500; i++)
{
Console.Write(Thread.CurrentThread.Name);
}
} catch(Exception ex){ } finally {
Monitor.Exit(obj); //释放对象
}
}

复制代码

TryEnter(Object)TryEnter() 方法在尝试获取一个对象上的显式锁方面和 Enter() 方法类似。然而,它不像Enter()方法那样会阻塞执行。如果线程成功进入关键区域那么TryEnter()方法会返回true. 和试图获取指定对象的排他锁。看下面代码演示:

      我们可以通过Monitor.TryEnter(monster, 1000),该方法也能够避免死锁的发生,我们下面的例子用到的是该方法的重载,Monitor.TryEnter(Object,Int32),。 

复制代码

static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = “A”;
Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = “B”;
threadA.Start();
threadB.Start();
Thread.CurrentThread.Name = “C”;
ThreadMethod();
Console.ReadKey();
} static object obj = new object(); public static void ThreadMethod()
{ bool flag = Monitor.TryEnter(obj, 1000); //设置1S的超时时间,如果在1S之内没有获得同步锁,则返回false
       //上面的代码设置了锁定超时时间为1秒,也就是说,在1秒中后,
       //lockObj还未被解锁,TryEntry方法就会返回false,如果在1秒之内,lockObj被解锁,TryEntry返回true。我们可以使用这种方法来避免死锁 try { if (flag)
{ for (int i = 0; i < 500; i++)
{
Console.Write(Thread.CurrentThread.Name); }
}
} catch(Exception ex)
{

        } finally { if (flag)
                Monitor.Exit(obj);
        } 
    } 

复制代码

 Monitor.Wait和Monitor()Pause()

Wait(object)方法:释放对象上的锁并阻止当前线程,直到它重新获取该锁,该线程进入等待队列。
 Pulse方法:只有锁的当前所有者可以使用 Pulse 向等待对象发出信号,当前拥有指定对象上的锁的线程调用此方法以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不一定是接收到脉冲的线程)将获得该锁。
另外

Wait 和 Pulse 方法必须写在 Monitor.Enter 和Moniter.Exit 之间****。

上面是MSDN的解释。不明白看代码:

 首先我们定义一个攻击类,

复制代码

///


/// 怪物类 ///

internal class Monster
{ public int Blood { get; set; } public Monster(int blood)
{ this.Blood = blood;
Console.WriteLine(“我是怪物,我有{0}滴血”,blood);
}
}

复制代码

然后在定义一个攻击类

复制代码

///


/// 攻击类 ///

internal class Play
{ ///
/// 攻击者名字 ///

public string Name { get; set; } ///
/// 攻击力 ///

public int Power{ get; set; } ///
/// 法术攻击 ///

public void magicExecute(object monster)
{
Monster m = monster as Monster;
Monitor.Enter(monster); while (m.Blood>0)
{
Monitor.Wait(monster);
Console.WriteLine(“当前英雄:{0},正在使用法术攻击打击怪物”, this.Name); if(m.Blood>= Power)
{
m.Blood -= Power;
} else {
m.Blood = 0;
}
Thread.Sleep(300);
Console.WriteLine(“怪物的血量还剩下{0}”, m.Blood);
Monitor.PulseAll(monster);
}
Monitor.Exit(monster);
} ///
/// 物理攻击 ///

///
public void physicsExecute(object monster)
{
Monster m = monster as Monster;
Monitor.Enter(monster); while (m.Blood > 0)
{
Monitor.PulseAll(monster); if (Monitor.Wait(monster, 1000)) //非常关键的一句代码
{
Console.WriteLine(“当前英雄:{0},正在使用物理攻击打击怪物”, this.Name); if (m.Blood >= Power)
{
m.Blood -= Power;
} else {
m.Blood = 0;
}
Thread.Sleep(300);
Console.WriteLine(“怪物的血量还剩下{0}”, m.Blood);
}
}
Monitor.Exit(monster);
}
}

复制代码

执行代码:

复制代码

static void Main(string[] args)
{ //怪物类
Monster monster = new Monster(1000); //物理攻击类
Play play1 = new Play() { Name = “无敌剑圣”, Power = 100 }; //魔法攻击类
Play play2 = new Play() { Name = “流浪法师”, Power = 120 };
Thread thread_first = new Thread(play1.physicsExecute); //物理攻击线程
Thread thread_second = new Thread(play2.magicExecute); //魔法攻击线程
thread_first.Start(monster);
thread_second.Start(monster);
Console.ReadKey();
}

复制代码

输出结果:

总结:

  第一种情况:

  1. thread_first首先获得同步对象的锁,当执行到 Monitor.Wait(monster);时,thread_first线程释放自己对同步对象的锁,流放自己到等待队列,直到自己再次获得锁,否则一直阻塞。
  2. 而thread_second线程一开始就竞争同步锁所以处于就绪队列中,这时候thread_second直接从就绪队列出来获得了monster对象锁,开始执行到Monitor.PulseAll(monster)时,发送了个Pulse信号。
  3. 这时候thread_first接收到信号进入到就绪状态。然后thread_second继续往下执行到 Monitor.Wait(monster, 1000)时,这是一句非常关键的代码,thread_second将自己流放到等待队列并释放自身对同步锁的独占,该等待设置了1S的超时值,当B线程在1S之内没有再次获取到锁自动添加到就绪队列。
  4. 这时thread_first从Monitor.Wait(monster)的阻塞结束,返回true。开始执行、打印。执行下一行的Monitor.Pulse(monster),这时候thread_second假如1S的时间还没过,thread_second接收到信号,于是将自己添加到就绪队列。
  5. thread_first的同步代码块结束以后,thread_second再次获得执行权, Monitor.Wait(m_smplQueue, 1000)返回true,于是继续从该代码处往下执行、打印。当再次执行到Monitor.Wait(monster, 1000),又开始了步骤3。
  6. 依次循环。。。。

第二种情况:thread_second首先获得同步锁对象,首先执行到Monitor.PulseAll(monster),因为程序中没有需要等待信号进入就绪状态的线程,所以这一句代码没有意义,当执行到 Monitor.Wait(monster, 1000),自动将自己流放到等待队列并在这里阻塞,1S 时间过后thread_second自动添加到就绪队列,线程thread_first获得monster对象锁,执行到Monitor.Wait(monster);时发生阻塞释放同步对象锁,线程thread_second执行,执行Monitor.PulseAll(monster)时通知thread_first。于是又开始第一种情况…

Monitor.Wait是让当前进程睡眠在临界资源上并释放独占锁,它只是等待,并不退出,当等待结束,就要继续执行剩下的代码。

3.0 使用Mutex类实现线程同步

      Mutex的突出特点是可以跨应用程序域边界对资源进行独占访问,即可以用于同步不同进程中的线程,这种功能当然这是以牺牲更多的系统资源为代价的。

  主要常用的两个方法:

public virtual bool WaitOne()   阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号获取互斥锁。

public void ReleaseMutex()     释放 System.Threading.Mutex 一次。

  使用实例:

复制代码

static void Main(string[] args)
{
Thread[] thread = new Thread[3]; for (int i = 0; i < 3; i++)
{
thread[i] = new Thread(ThreadMethod1);
thread[i].Name = i.ToString();
} for (int i = 0; i < 3; i++)
{
thread[i].Start();
}
Console.ReadKey();
} public static void ThreadMethod1(object val)
{
mutet.WaitOne();    //获取锁 for (int i = 0; i < 500; i++)
{
Console.Write(Thread.CurrentThread.Name);
}
mutet.ReleaseMutex(); //释放锁
}

复制代码

2、线程池

上面介绍了介绍了平时用到的大多数的多线程的例子,但在实际开发中使用的线程往往是大量的和更为复杂的,这时,每次都创建线程、启动线程。从性能上来讲,这样做并不理想(因为每使用一个线程就要创建一个,需要占用系统开销);从操作上来讲,每次都要启动,比较麻烦。为此引入的线程池的概念。

  好处:

  1.减少在创建和销毁线程上所花的时间以及系统资源的开销 
  2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。

在什么情况下使用线程池?

    1.单个任务处理的时间比较短 
    2.需要处理的任务的数量大 

线程池最多管理线程数量=“处理器数 * 250”。也就是说,如果您的机器为2个2核CPU,那么CLR线程池的容量默认上限便是1000

通过线程池创建的线程默认为后台线程,优先级默认为Normal。

代码示例:

复制代码

static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadMethod1), new object()); //参数可选
Console.ReadKey();
} public static void ThreadMethod1(object val)
{ for (int i = 0; i <= 500000000; i++)
{ if (i % 1000000 == 0)
{
Console.Write(Thread.CurrentThread.Name);
}
}
}

复制代码

有关线程池的解释请参考:

http://www.cnblogs.com/JeffreyZhao/archive/2009/07/22/thread-pool-1-the-goal-and-the-clr-thread-pool.html

C#如何动态设置屏幕分辨率 - jack_Meng - 博客园

Excerpt

C#如何动态设置屏幕分辨率 作者:Learning hard 这篇文章主要为大家详细介绍了C#动态设置屏幕分辨率的方法,我们可以使用Screen类设置屏幕分辨率,感兴趣的小伙伴们可以参考一下 下面就不啰嗦了,直接看代码如何解决这个问题的。 首先,大家应该明确,现在没有可用的API来给我们动态地设置屏


C#如何动态设置屏幕分辨率

作者:Learning hard 

这篇文章主要为大家详细介绍了C#动态设置屏幕分辨率的方法,我们可以使用Screen类设置屏幕分辨率,感兴趣的小伙伴们可以参考一下

下面就不啰嗦了,直接看代码如何解决这个问题的。

首先,大家应该明确,现在没有可用的API来给我们动态地设置屏幕分辨率,我们要实现这个需求,我们只能在C#程序中调用Win32 API 函数来解决这个问题的,这里用C#代码调用Win32 API 就涉及到一个问题的,即.NET 互操作性的问题,关于这个大家可以参考我的互操作性系列文章。这里我就不过多解释了。

我们要解决这个问题,首先大家肯定也会遇到一个经常遇到的问题,即如何获得用户的分辨率,对于这个问题,.NET中提供的单独的类给我们调用,我们可以使用Screen这个类,具体看下面的示例代码:

1

2

3

Screen screen = Screen.PrimaryScreen;

     int screenWidth= screen.Bounds.Width;

     int screenHeight = screen.Bounds.Height;

然后就是如何改变屏幕的分辨率呢?要更改显示设置可以通过使用两个 Win32 API 来完成,这两个 API 都具有指向 DEVMODE 结构的指针,它们分别包含与显示设置有关的所有信息:

使用 EnumDisplaySettings 读取当前显示设置,并枚举所有受支持的显示设置。

使用 ChangeDisplaySettings 切换到新的显示设置。

第一步、我们要先定义DEVMODE 结构体,该结构的结构必须与DEVMODE的结构一致,下面是C#中对DEVMODE 结构体的定义代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

  public struct DEVMODE

  {

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

    public string dmDeviceName;

    public short dmSpecVersion;

    public short dmDriverVersion;

    public short dmSize;

    public short dmDriverExtra;

    public int dmFields;

    public int dmPositionX;

    public int dmPositionY;

    public int dmDisplayOrientation;

    public int dmDisplayFixedOutput;

    public short dmColor;

    public short dmDuplex;

    public short dmYResolution;

    public short dmTTOption;

    public short dmCollate;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

    public string dmFormName;

    public short dmLogPixels;

    public short dmBitsPerPel;

    public int dmPelsWidth;

    public int dmPelsHeight;

    public int dmDisplayFlags;

    public int dmDisplayFrequency;

    public int dmICMMethod;

    public int dmICMIntent;

    public int dmMediaType;

    public int dmDitherType;

    public int dmReserved1;

    public int dmReserved2;

    public int dmPanningWidth;

    public int dmPanningHeight;

  };

第二步、在托管环境下对Win 32 函数进行声明:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

  public class NativeMethods

  {

    [DllImport("user32.dll")]

    public static extern int EnumDisplaySettings(

     string deviceName, int modeNum, ref DEVMODE devMode);

    [DllImport("user32.dll")]

    public static extern int ChangeDisplaySettings(

       ref DEVMODE devMode, int flags);

    public const int ENUM_CURRENT_SETTINGS = -1;

    public const int CDS_UPDATEREGISTRY = 0x01;

    public const int CDS_TEST = 0x02;

    public const int DISP_CHANGE_SUCCESSFUL = 0;

    public const int DISP_CHANGE_RESTART = 1;

    public const int DISP_CHANGE_FAILED = -1;

    public const int DMDO_DEFAULT = 0;

    public const int DMDO_90 = 1;

    public const int DMDO_180 = 2;

    public const int DMDO_270 = 3;

  }

第三步、调用EnumDisplaySettings和ChangeDisplaySettings这两个函数来实现动态改变屏幕分辨率,具体代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

  public ChangeResolution(int width, int height)

  {

    DEVMODE devmode = new DEVMODE();

    devmode.dmDeviceName = new String(new char[32]);

    devmode.dmFormName = new String(new char[32]);

    devmode.dmSize = (short)Marshal.SizeOf(devmode);

    if (0 != NativeMethods.EnumDisplaySettings(null, NativeMethods.ENUM_CURRENT_SETTINGS, ref devmode))

    {

      devmode.dmPelsWidth = width;

      devmode.dmPelsHeight = height;

      int iRet = NativeMethods.ChangeDisplaySettings(ref devmode, NativeMethods.CDS_TEST);

      if (iRet == NativeMethods.DISP_CHANGE_FAILED)

      {

        MessageBox.Show("不能执行你的请求", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);

      }

      else

      {

        iRet = NativeMethods.ChangeDisplaySettings(ref devmode, NativeMethods.CDS_UPDATEREGISTRY);

        switch (iRet)

        {

          case NativeMethods.DISP_CHANGE_SUCCESSFUL:

            {

              break;

            }

          case NativeMethods.DISP_CHANGE_RESTART:

            {

              MessageBox.Show("你需要重新启动电脑设置才能生效", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);

              break;

            }

          default:

            {

              MessageBox.Show("改变屏幕分辨率失败", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);

              break;

            }

        }

      }

    }

  }

为了大家更加形象地看到程序的运行结果,下面是一个演示效果:

以上就是本文的全部内容,希望对大家的学习有所帮助。

出处:http://www.jb51.net/article/82013.htm

https://zhidao.baidu.com/question/1544149072026431227.html

对于以上第一步、第二步、第三步中的代码可以单独保存在一个类中,这样使用比较方便,你在使用的时候,可能还需要做一些简单的引用或调整。下面有时间了我会把我整理的代码发出来。

  在企业应用系统领域,会面对不同系统之间的通信、集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要。其次,系统中一般会有很多对实时性要求不高的但是执行起来比较较耗时的地方,比如发送短信,邮件提醒,更新文章阅读计数,记录用户操作日志等等,如果实时处理的话,在用户访问量比较大的情况下,对系统压力比较大。

面对这些问题,我们一般会将这些请求,放在消息队列MQ中处理;异构系统之间使用消息进行通讯。

    MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

  MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取或者订阅队列中的消息。

   RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。 

  消息传递相较文件传递与远程过程调用(RPC)而言,似乎更胜一筹,因为它具有更好的平台无关性,并能够很好地支持并发与异步调用。所以如果系统中出现了如下情况:

  • 对操作的实时性要求不高,而需要执行的任务极为耗时;
  • 存在异构系统间的整合;

  一般的可以考虑引入消息队列。对于第一种情况,常常会选择消息队列来处理执行时间较长的任务。引入的消息队列就成了消息处理的缓冲区。消息队列引入的异步通信机制,使得发送方和接收方都不用等待对方返回成功消息,就可以继续执行下面的代码,从而提高了数据处理的能力。尤其是当访问量和数据流量较大的情况下,就可以结合消息队列与后台任务,通过避开高峰期对大数据进行处理,就可以有效降低数据库处理数据的负荷。 

  本文简单介绍在RabbitMQ这一消息代理工具,以及在.NET中如何使用RabbitMQ.

  2.1 安装Erlang语言运行环境

  由于RabbitMQ使用Erlang语言编写,所以先安装Erlang语言运行环境。具体移步博客:windows配置Erlang环境

  2.2 安装RabbitMQ服务端

  地址 http://www.rabbitmq.com/

  下载安装。

  使RabbitMQ以Windows Service的方式在后台运行:打开cmd切换到sbin目录下执行

rabbitmq-service install
rabbitmq-service enable
rabbitmq-service start

   现在RabbitMQ的服务端已经启动起来了。

  要查看和控制RabbitMQ服务端的状态,可以用rabbitmqctl这个脚本。

  比如查看状态:

  

  假如显示node没有连接上,需要到C:\Windows目录下,将.erlang.cookie文件,拷贝到用户目录下 C:\Users\{用户名},这是Erlang的Cookie文件,允许与Erlang进行交互。

   使用命令查看用户:

  RabbitMQ会为我们创建默认的用户名guest和密码guest,guest默认拥有RabbitMQ的所有权限。

  一般的,我们需要新建一个我们自己的用户,设置密码,并授予权限,并将其设置为管理员,可以使用下面的命令来执行这一操作:

rabbitmqctl add_user JC JayChou //创建用户JC密码为JayChou
rabbitmqctl set_permissions JC “.*“ “.*“ “.*“ //赋予JC读写所有消息队列的权限 rabbitmqctl set_user_tags JC administrator //分配用户组

  修改JC密码为123:

rabbitmqctl change_password JC 123

  删除用户JC:

rabbitmqctl delete_user JC

  也可以开启rabbitmq_management插件,在web界面查看和管理RabbitMQ服务

rabbitmq-plugins enable rabbitmq_management

  2.3下载RabbitMQ的Client端dll

  下载地址:http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/

  本人下载了这个 rabbitmq-dotnet-client-3.6.6-dotnet-4.5.zip

   解压,我们需要的是这个文件,以后会引用到vs的项目中:

  3.1在使用RabitMQ之前,先对几个概念做一下说明

  RabbitMQ是一个消息代理。他从消息生产者(producers)那里接收消息,然后把消息送给消息消费者(consumer)在发送和接受之间,他能够根据设置的规则进行路由,缓存和持久化。

  一般提到RabbitMQ和消息,都用到一些专有名词。

  • 生产(Producing)意思就是发送。发送消息的程序就是一个生产者(producer)。我们一般用”P”来表示:

       producer

  • 队列(queue)就是邮箱的名称。消息通过你的应用程序和RabbitMQ进行传输,它们只能存储在队列(queue)中。 队列(queue)容量没有限制,你要存储多少消息都可以——基本上是一个无限的缓冲区。多个生产者(producers)能够把消息发送给同一个队列,同样,多个消费者(consumers)也能从同一个队列(queue)中获取数据。队列可以画成这样(图上是队列的名称):

     queue

  • 消费(Consuming)和获取消息是一样的意思。一个消费者(consumer)就是一个等待获取消息的程序。我们把它画作”C”:

     consumer

  通常,消息生产者,消息消费者和消息代理不在同一台机器上。

3.2 Hello Word

  下面来展示简单的RabbitMQ的使用:

      rabbitmq hello world

 3.2.1 首先创建名为ProjectSend的控制台项目,需要引用RabbitMQ.Client.dll。这个程序作为Producer生产者,用来发送数据:

复制代码

static void Main(string[] args)
{ var factory = new ConnectionFactory();
factory.HostName = “localhost”;//RabbitMQ服务在本地运行
factory.UserName = “guest”;//用户名
factory.Password = “guest”;//密码 using (var connection = factory.CreateConnection())
{ using (var channel = connection.CreateModel())
{
channel.QueueDeclare(“hello”, false, false, false, null);//创建一个名称为hello的消息队列 string message = “Hello World”; //传递的消息内容 var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(“”, “hello”, null, body); //开始传递
Console.WriteLine(“已发送: {0}”, message);
          Console.ReadLine();
}
}
}

复制代码

  首先,需要创建一个ConnectionFactory,设置目标,由于是在本机,所以设置为localhost,如果RabbitMQ不在本机,只需要设置目标机器的IP地址或者机器名称即可,然后设置前面创建的用户名和密码。

  紧接着要创建一个Channel,如果要发送消息,需要创建一个队列,然后将消息发布到这个队列中。在创建队列的时候,只有RabbitMQ上该队列不存在,才会去创建。消息是以二进制数组的形式传输的,所以如果消息是实体对象的话,需要序列化和然后转化为二进制数组。

  现在客户端发送代码已经写好了,运行之后,消息会发布到RabbitMQ的消息队列中,现在需要编写服务端的代码连接到RabbitMQ上去获取这些消息。

3.2.2创建名为ProjectReceive的控制台项目,引用RabbitMQ.Client.dll。作为Consumer消费者,用来接收数据:

复制代码

static void Main(string[] args)
{ var factory = new ConnectionFactory();
factory.HostName = “localhost”;
factory.UserName = “guest”;
factory.Password = “guest”; using (var connection = factory.CreateConnection())
{ using (var channel = connection.CreateModel())
{
channel.QueueDeclare(“hello”, false, false, false, null); var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(“hello”, false, consumer);
consumer.Received += (model, ea) => { var body = ea.Body; var message = Encoding.UTF8.GetString(body);
Console.WriteLine(“已接收: {0}”, message);
}; Console.ReadLine(); }
}
}

复制代码

   和发送一样,首先需要定义连接,然后声明消息队列。要接收消息,需要定义一个Consume,然后在接收消息的事件中处理数据。

 3.2.3 现在发送和接收的客户端都写好了,让我们编译执行起来

  发送消息:

  现在,名为hello的消息队列中,发送了一条消息。这条消息存储到了RabbitMQ的服务器上了。使用rabbitmqctl 的list_queues可以查看所有的消息队列,以及里面的消息个数,可以看到,目前Rabbitmq上只有一个消息队列,里面只有一条消息:

  也可以在web管理界面查看此queue的相关信息:

 

  接收消息:

   既然消息已经被接收了,那我们再来看queue的内容:

  可见,消息中的内容在接收之后已被删除了。

3.3 工作队列

  前面的例子展示了如何在指定的消息队列发送和接收消息。

  现在我们创建一个工作队列(work queue)来将一些耗时的任务分发给多个工作者(workers):

   rabbitmq-work queue

  工作队列(work queues, 又称任务队列Task Queues)的主要思想是为了避免立即执行并等待一些占用大量资源、时间的操作完成。而是把任务(Task)当作消息发送到队列中,稍后处理。一个运行在后台的工作者(worker)进程就会取出任务然后处理。当运行多个工作者(workers)时,任务会在它们之间共享。

  这个在网络应用中非常有用,它可以在短暂的HTTP请求中处理一些复杂的任务。在一些实时性要求不太高的地方,我们可以处理完主要操作之后,以消息的方式来处理其他的不紧要的操作,比如写日志等等。

准备

  在第一部分,发送了一个包含“Hello World!”的字符串消息。现在发送一些字符串,把这些字符串当作复杂的任务。这里使用time.sleep()函数来模拟耗时的任务。在字符串中加上点号(.)来表示任务的复杂程度,一个点(.)将会耗时1秒钟。比如”Hello…”就会耗时3秒钟。

对之前示例的send.cs做些简单的调整,以便可以发送随意的消息。这个程序会按照计划发送任务到我们的工作队列中。

复制代码

static void Main(string[] args)
{ var factory = new ConnectionFactory();
factory.HostName = “localhost”;
factory.UserName = “yy”;
factory.Password = “hello!”; using (var connection = factory.CreateConnection())
{ using (var channel = connection.CreateModel())
{
channel.QueueDeclare(“hello”, false, false, false, null); string message = GetMessage(args); var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(“”, “hello”, properties, body);
Console.WriteLine(“ set {0}”, message);
}
}

Console.ReadKey();

} private static string GetMessage(string[] args)
{ return ((args.Length > 0) ? string.Join(“ “, args) : “Hello World!”);
}

复制代码

接着我们修改接收端,让他根据消息中的逗点的个数来Sleep对应的秒数:

复制代码

static void Main(string[] args)
{ var factory = new ConnectionFactory();
factory.HostName = “localhost”;
factory.UserName = “yy”;
factory.Password = “hello!”; using (var connection = factory.CreateConnection())
{ using (var channel = connection.CreateModel())
{
channel.QueueDeclare(“hello”, false, false, false, null); var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(“hello”, true, consumer); while (true)
{ var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue(); var body = ea.Body; var message = Encoding.UTF8.GetString(body); int dots = message.Split(‘.’).Length - 1;
Thread.Sleep(dots * 1000);

            Console.WriteLine("Received {0}", message);
            Console.WriteLine("Done");
        }
    }
}

}

复制代码

轮询分发

  使用工作队列的一个好处就是它能够并行的处理队列。如果堆积了很多任务,我们只需要添加更多的工作者(workers)就可以了,扩展很简单。

现在,我们先启动两个接收端,等待接受消息,然后启动一个发送端开始发送消息。

Send message queue 

  在cmd条件下,发送了5条消息,每条消息后面的逗点表示该消息需要执行的时长,来模拟耗时的操作。

  然后可以看到,两个接收端依次接收到了发出的消息:

receive message queue 

默认,RabbitMQ会将每个消息按照顺序依次分发给下一个消费者。所以每个消费者接收到的消息个数大致是平均的。 这种消息分发的方式称之为轮询(round-robin)。

3.4 消息响应

当处理一个比较耗时得任务的时候,也许想知道消费者(consumers)是否运行到一半就挂掉。在当前的代码中,当RabbitMQ将消息发送给消费者(consumers)之后,马上就会将该消息从队列中移除。此时,如果把处理这个消息的工作者(worker)停掉,正在处理的这条消息就会丢失。同时,所有发送到这个工作者的还没有处理的消息都会丢失。

我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望该消息会重新发送给其他的工作者(worker)。

为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)_机制_。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ才会释放并删除这条消息。

如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,即使工作者(workers)偶尔的挂掉,也不会丢失消息。

消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。

消息响应默认是开启的。在之前的例子中使用了no_ack=True标识把它关闭。是时候移除这个标识了,当工作者(worker)完成了任务,就发送一个响应。

复制代码

channel.BasicConsume(“hello”, false, consumer); while (true)
{ var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue(); var body = ea.Body; var message = Encoding.UTF8.GetString(body); int dots = message.Split(‘.’).Length - 1;
Thread.Sleep(dots * 1000);

Console.WriteLine("Received {0}", message);
Console.WriteLine("Done");

channel.BasicAck(ea.DeliveryTag, false);

}

复制代码

现在,可以保证,即使正在处理消息的工作者被停掉,这些消息也不会丢失,所有没有被应答的消息会被重新发送给其他工作者.

一个很常见的错误就是忘掉了BasicAck这个方法,这个错误很常见,但是后果很严重. 当客户端退出时,待处理的消息就会被重新分发,但是RabitMQ会消耗越来越多的内存,因为这些没有被应答的消息不能够被释放。调试这种case,可以使用rabbitmqct打印messages_unacknoledged字段。

rabbitmqctl list_queues name messages_ready messages_unacknowledged
Listing queues …
hello 0 0 …done.

3.5 消息持久化

前面已经搞定了即使消费者down掉,任务也不会丢失,但是,如果RabbitMQ Server停掉了,那么这些消息还是会丢失。

当RabbitMQ Server 关闭或者崩溃,那么里面存储的队列和消息默认是不会保存下来的。如果要让RabbitMQ保存住消息,需要在两个地方同时设置:需要保证队列和消息都是持久化的。

首先,要保证RabbitMQ不会丢失队列,所以要做如下设置:

bool durable = true;
channel.QueueDeclare(“hello”, durable, false, false, null);

虽然在语法上是正确的,但是在目前阶段是不正确的,因为我们之前已经定义了一个非持久化的hello队列。RabbitMQ不允许我们使用不同的参数重新定义一个已经存在的同名队列,如果这样做就会报错。现在,定义另外一个不同名称的队列:

bool durable = true;
channel.queueDeclare(“task_queue”, durable, false, false, null);

queueDeclare 这个改动需要在发送端和接收端同时设置。

现在保证了task_queue这个消息队列即使在RabbitMQ Server重启之后,队列也不会丢失。 然后需要保证消息也是持久化的, 这可以通过设置IBasicProperties.SetPersistent 为true来实现:

var properties = channel.CreateBasicProperties();
properties.SetPersistent(true);

需要注意的是,将消息设置为持久化并不能完全保证消息不丢失。虽然他告诉RabbitMQ将消息保存到磁盘上,但是在RabbitMQ接收到消息和将其保存到磁盘上这之间仍然有一个小的时间窗口。 RabbitMQ 可能只是将消息保存到了缓存中,并没有将其写入到磁盘上。持久化是不能够一定保证的,但是对于一个简单任务队列来说已经足够。如果需要消息队列持久化的强保证,可以使用publisher confirms

3.6 公平分发

你可能会注意到,消息的分发可能并没有如我们想要的那样公平分配。比如,对于两个工作者。当奇数个消息的任务比较重,但是偶数个消息任务比较轻时,奇数个工作者始终处理忙碌状态,而偶数个工作者始终处理空闲状态。但是RabbitMQ并不知道这些,他仍然会平均依次的分发消息。

为了改变这一状态,我们可以使用basicQos方法,设置perfetchCount=1 。这样就告诉RabbitMQ 不要在同一时间给一个工作者发送多于1个的消息,或者换句话说。在一个工作者还在处理消息,并且没有响应消息之前,不要给他分发新的消息。相反,将这条新的消息发送给下一个不那么忙碌的工作者。

channel.BasicQos(0, 1, false);

3.7 完整实例

现在将所有这些放在一起:

发送端代码如下:

复制代码

static void Main(string[] args)
{ var factory = new ConnectionFactory();
factory.HostName = “localhost”;
factory.UserName = “yy”;
factory.Password = “hello!”; using (var connection = factory.CreateConnection())
{ using (var channel = connection.CreateModel())
{ bool durable = true;
channel.QueueDeclare(“task_queue”, durable, false, false, null); string message = GetMessage(args); var properties = channel.CreateBasicProperties();
properties.SetPersistent(true); var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(“”, “task_queue”, properties, body);
Console.WriteLine(“ set {0}”, message);
}
}

Console.ReadKey();

} private static string GetMessage(string[] args)
{ return ((args.Length > 0) ? string.Join(“ “, args) : “Hello World!”);
}

复制代码

接收端代码如下:

复制代码

static void Main(string[] args)
{ var factory = new ConnectionFactory();
factory.HostName = “localhost”;
factory.UserName = “yy”;
factory.Password = “hello!”; using (var connection = factory.CreateConnection())
{ using (var channel = connection.CreateModel())
{ bool durable = true;
channel.QueueDeclare(“task_queue”, durable, false, false, null);
channel.BasicQos(0, 1, false); var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(“task_queue”, false, consumer); while (true)
{ var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue(); var body = ea.Body; var message = Encoding.UTF8.GetString(body); int dots = message.Split(‘.’).Length - 1;
Thread.Sleep(dots * 1000);

            Console.WriteLine("Received {0}", message);
            Console.WriteLine("Done");

            channel.BasicAck(ea.DeliveryTag, false);
        }
    }
}

}

复制代码

RabbitMQ管理界面,通过该界面可以查看RabbitMQ Server 当前的状态,该界面是以插件形式提供的,并且在安装RabbitMQ的时候已经自带了该插件。需要做的是在RabbitMQ控制台界面中启用该插件,命令如下:

rabbitmq-plugins enable rabbitmq_management

rabbitmq management

现在,在浏览器中输入 http://server-name:15672/ server-name换成机器地址或者域名,如果是本地的,直接用localhost(RabbitMQ 3.0之前版本端口号为55672)在输入之后,弹出登录界面,使用我们之前创建的用户登录。

RabbitMQ Web management .

在该界面上可以看到当前RabbitMQServer的所有状态。

本文简单介绍了消息队列的相关概念,并介绍了RabbitMQ消息代理的基本原理以及在Windows 上如何安装RabbitMQ和在.NET中如何使用RabbitMQ。消息队列在构建分布式系统和提高系统的可扩展性和响应性方面有着很重要的作用,希望本文对您了解消息队列以及如何使用RabbitMQ有所帮助。

C#中二进制、十进制和十六进制互相转换的方法 - 乌鸦4ay - 博客园

Excerpt

二进制在C#中无法直接表示,我们一般用0和1的字符串来表示一个数的二进制形式。比如4的二进制为“100”。下面介绍C#里面用于进制转换的方法。 十进制转换为二进制(int–>string) System.Convert.ToString(d, 2);// d为int类型 以4为例,输出为10


二进制在C#中无法直接表示,我们一般用0和1的字符串来表示一个数的二进制形式。比如4的二进制为“100”。下面介绍C#里面用于进制转换的方法。

十进制转换为二进制(int–>string)

System.Convert.ToString(d, 2);// d为int类型 以4为例,输出为100

十六进制转换为二进制(int–>string)

System.Convert.ToString(d, 2);// d为int类型 以0X14为例,输出为10100

上面ToString函数的第二个参数表示目标数字系统的基数。

二进制转换为十进制(string–>int)

System.Convert.ToInt32(s, 2);// d为string类型 以“1010”为例,输出为10

十六进制转换为十进制(string–>int、int–>int)****

方法一:

System.Convert.ToString(0xa,10);// 以0XA为例,输出为10

或者

System.Convert.ToInt32(“0x41”, 16);//以”0x41”为例,输出为65

方法二:

System.Int32.Parse(s, System.Globalization.NumberStyles.HexNumber);//s为string类型,以“41”为例,输出为65

注意这里的s中不能带有“0X”或者“0x”,区别于上面的方法。

二进制转换为十六进制(string–>int)

先转换为十进制,在转换为十六进制

string.Format(“{0:X}”,System.Convert.ToInt32(s, 2));// s为string类型 以“1010”为例,输出为A

十进制转换为十六进制

十进制转换为十六进制不需要特殊的方法,只要对输出格式进行控制一下就可以。

或者使用string.Format(“{0:x}”, dec)方法进行控制,也可以用s.ToString(“X”)方式进行控制。

除了数制转换之外,数据类型的转换也比较重要。对于基本数据类型,byte、char、int、string以及它们的数组形式的转换虽然简单,但是比较基础的知识。网上也有很多详解,用到的时候自行查阅即可。

在最近的项目中有用到PLC与上位机通信的指令转换,用了各种方法,很是头疼,在网上搜集了和自己试着写了一下转换函数,分享给有需要的朋友。

复制代码

1 ///

Convert a string of hex digits (ex: E4 CA B2) to a byte array.
2 /// The string containing the hex digits (with or without spaces).
3 /// Returns an array of bytes.
4 public byte[] HexStringToByteArray(string s) 5 {
6 s = s.Replace(“ “, “”);
7 byte[] buffer = new byte[s.Length / 2];
8 for (int i = 0; i < s.Length; i += 2)
9 {
10 buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
11 }
12
13 return buffer; 14 }
15
16 /// Converts an array of bytes into a formatted string of hex digits (ex: E4 CA B2)
17 /// The array of bytes to be translated into a string of hex digits.
18 /// Returns a well formatted string of hex digits with spacing.
19 public string ByteArrayToHexString(byte[] data)
20 {
21 StringBuilder sb = new StringBuilder(data.Length * 3);
22 foreach (byte b in data) 23 {
24 sb.Append(Convert.ToString(b, 16).PadLeft(2, ‘0’).PadRight(3, ‘ ‘));
25 }
26
27 return sb.ToString().ToUpper(); 28 }
29
30 ///
31 /// 将一条十六进制字符串转换为ASCII 32 ///

33 /// 一条十六进制字符串
34 /// 返回一条ASCII码
35 public static string HexStringToASCII(string hexstring) 36 {
37 byte[] bt = HexStringToBinary(hexstring); 38 string lin = “”;
39 for (int i = 0; i < bt.Length; i++)
40 {
41 lin = lin + bt[i] + “ “;
42 }
43
44
45 string[] ss = lin.Trim().Split(new char[] { ‘ ‘ }); 46 char[] c = new char[ss.Length];
47 int a; 48 for (int i = 0; i < c.Length; i++)
49 {
50 a = Convert.ToInt32(ss[i]); 51 c[i] = Convert.ToChar(a); 52 }
53
54 string b = new string(c);
55 return b; 56 }
57
58
59 /**/
60 ///
61 /// 16进制字符串转换为二进制数组 62 ///

63 /// 用空格切割字符串
64 /// 返回一个二进制字符串
65 public static byte[] HexStringToBinary(string hexstring) 66 {
67
68 string[] tmpary = hexstring.Trim().Split(‘ ‘);
69 byte[] buff = new byte[tmpary.Length];
70 for (int i = 0; i < buff.Length; i++)
71 {
72 buff[i] = Convert.ToByte(tmpary[i], 16);
73 }
74 return buff; 75 }
76
77
78 ///
79 /// 将byte型转换为字符串 80 ///

81 /// byte型数组
82 /// 目标字符串
83 private string ByteArrayToString(byte[] arrInput)
84 {
85 int i; 86 StringBuilder sOutput = new StringBuilder(arrInput.Length); 87 for (i = 0; i < arrInput.Length; i++)
88 {
89 sOutput.Append(arrInput[i].ToString(“X2”));
90 }
91 //将此实例的值转换为System.String
92 return sOutput.ToString(); 93 }
94
95
96
97 ///
98 /// 对接收到的数据进行解包(将接收到的byte型数组解包为Unicode字符串) 99 ///

100 /// byte型数组
101 /// Unicode编码的字符串
102 public string disPackage(byte[] recbytes) 103 { 104 string temp = “”; 105 foreach (byte b in recbytes) 106 temp += b.ToString(“X2”) + “ “;//ToString(“X2”) 为C#中的字符串格式控制符
107 return temp; 108 } 109
110 /** 111 * int转byte[] 112 * 该方法将一个int类型的数据转换为byte[]形式,因为int为32bit,而byte为8bit所以在进行类型转换时,知会获取低8位, 113 * 丢弃高24位。通过位移的方式,将32bit的数据转换成4个8bit的数据。注意 &0xff,在这当中,&0xff简单理解为一把剪刀, 114 * 将想要获取的8位数据截取出来。 115 * @param i 一个int数字 116 * @return byte[] 117 */
118 public static byte[] int2ByteArray(int i) 119 { 120 byte[] result = new byte[4]; 121 result[0] = (byte)((i >> 24) & 0xFF); 122 result[1] = (byte)((i >> 16) & 0xFF); 123 result[2] = (byte)((i >> 8) & 0xFF); 124 result[3] = (byte)(i & 0xFF); 125 return result; 126 } 127 /** 128 * byte[]转int 129 * 利用int2ByteArray方法,将一个int转为byte[],但在解析时,需要将数据还原。同样使用移位的方式,将适当的位数进行还原, 130 * 0xFF为16进制的数据,所以在其后每加上一位,就相当于二进制加上4位。同时,使用|=号拼接数据,将其还原成最终的int数据 131 * @param bytes byte类型数组 132 * @return int数字 133 */
134 public static int bytes2Int(byte[] bytes) 135 { 136 int num = bytes[3] & 0xFF; 137 num |= ((bytes[2] << 8) & 0xFF00); 138 num |= ((bytes[1] << 16) & 0xFF0000); 139 num |= ((bytes[0] << 24) & 0xFF0000); 140 return num; 141 } 142
143 public static string Int2String(int str) 144 { 145 string S = Convert.ToString(str); 146 return S; 147 } 148
149 public static int String2Int(string str) 150 { 151 int a; 152 int.TryParse(str, out a); 153 int a1 = Convert.ToInt32(str); 154 return a1; 155 } 156
157
158 /*将int转为低字节在后,高字节在前的byte数组 159 b[0] = 11111111(0xff) & 01100001 160 b[1] = 11111111(0xff) & 00000000 161 b[2] = 11111111(0xff) & 00000000 162 b[3] = 11111111(0xff) & 00000000 163 */
164 public byte[] IntToByteArray2(int value) 165 { 166 byte[] src = new byte[4]; 167 src[0] = (byte)((value >> 24) & 0xFF); 168 src[1] = (byte)((value >> 16) & 0xFF); 169 src[2] = (byte)((value >> 8) & 0xFF); 170 src[3] = (byte)(value & 0xFF); 171 return src; 172 } 173 //将高字节在前转为int,低字节在后的byte数组(与IntToByteArray2想对应)
174 public int ByteArrayToInt2(byte[] bArr) 175 { 176 if (bArr.Length != 4) 177 { 178 return -1; 179 } 180 return (int)((((bArr[0] & 0xff) << 24) 181 | ((bArr[1] & 0xff) << 16) 182 | ((bArr[2] & 0xff) << 8) 183 | ((bArr[3] & 0xff) << 0))); 184 } 185
186 public static string StringToHexArray(string input) 187 { 188 char[] values = input.ToCharArray(); 189 StringBuilder sb = new StringBuilder(input.Length * 3); 190 foreach (char letter in values) 191 { 192 // Get the integral value of the character.
193 int value = Convert.ToInt32(letter); 194 // Convert the decimal value to a hexadecimal value in string form.
195 string hexOutput = String.Format(“{0:X}”, value); 196 sb.Append(Convert.ToString(value, 16).PadLeft(2, ‘0’).PadRight(3, ‘ ‘)); 197 } 198
199 return sb.ToString().ToUpper(); 200
201 #endregion
202
203 }

复制代码

在这个过程中有参考网上大神的代码:

和谐:https://www.cnblogs.com/wxbug/p/6991445.html

王思明:https://www.cnblogs.com/maanshancss/p/4074524.html

C#位运算 - Danny Chen - 博客园

Excerpt

在C#中可以对整型运算对象按位进行逻辑运算。按位进行逻辑运算的意义是:依次取被运算对象的每个位,进行逻辑运算,每个位的逻辑运算结果是结果值的每个位。C#支持的位逻辑运算符如表2.9所示。 运算符号 意义 运算对象类型 运算结果类型 对象数 实例 ~ 位逻辑非运算 整型,字符型 整型 1 ~a &am


在进行位与、或、异或运算时,如果两个运算对象的类型一致,则运算结果的类型就是运算对象的类型。比如对两个int变量a和b做与运算,运算结果的类型还是int型。如果两个运算

一、“按位与”运算符(&) 
   1、运算规则
     参加运算的两个数据,按二进位进行“与”运算,如果两个相应的二进位都为1,则该位的结果值为1,否则为0,即:
    0&0=0,0&1=0,1&0=0,1&1=1.
   2、用途
     (1)清零
        运算对象:原来的数中为1的位,新数中相应位为0。
    (2)取一个数中某些指定位。
       如想要取一个整数a(占2个字节)的低(高)字节,只需将a与八进制的377(177400)按位与即可。
    (3)保留某一个数的某一位。
        与一个数进行&运算,此数在该位取1。
    3、例如:9&5可写算式如下: 00001001 (9的二进制补码)&00000101 (5的二进制补码) 00000001 (1的二进制补码)可见9&5=1。
  按位与运算通常用来对某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 运算 ( 255 的二进制数为0000000011111111)。
main(){
int a=9,b=5,c;
c=a&b;
printf(“a=%d\nb=%d\nc=%d\n”,a,b,c);
}

二、 按位或运算符(|)
   1、运算规则
     参加运算的两个数据,按二进位进行“或”运算,如果两个相应的二进位都为0,则该位的结果值为0,否则为1,即:
    0|0=0,0|1=1,1|0=1,1|1=1。
   2、用途
     对一个数据的某些位定值为1。
3.例如:9|5可写算式如下: 00001001|00000101
00001101 (十进制为13)可见9|5=13
main(){
int a=9,b=5,c;
c=a|b;
printf(“a=%d\nb=%d\nc=%d\n”,a,b,c);
}

 三、“异或”运算符(^)
   也称XOR运算符。
   1、运算规则
     若参加运算的两个二进位同号,则结果为0(假);异号则为1(真),即:
    0^0=0,0^1=1,1^0=1,1^1=0.
   2、用途
     (1)使特定位翻转
        假设有01111010,想使其低4位翻转,可以将它与00001111进行^运算。
    (2)与0相^,保留原值
    (3)交换两个值,不用临时变量
        假如a=3,b=4。想将a和b的值互换,可以用以下赋值语句实现:
        a=a^b; b=b^a;     a=a^b;
 3、例如9^5可写成算式如下: 00001001^00000101 00001100 (十进制为12)
main(){
int a=9;
a=a^15;
printf(“a=%d\n”,a);
}

四、“取反”运算符(~)
   1、运算规则
   是一个单目(元)运算符,用来对一个二进制数按位取反,即将0变1,1变0。
   2、用途
    使一个整数a的最低位为0,可以用:a=a&
1;
 3、例如~9的运算为: ~(0000000000001001)结果为:1111111111110110

五、 左移运算符(<<)
   1、运算规则
   用来将一个数的各二进位全部左移若干位,右补0,高位左移后溢出,舍弃不起作用。
   2、用途
    左移一位相当于乘以2
3、例如:设 a=15,a>>2 表示把000001111右移为00000011(十进制3)。 应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时, 最高位补0,而为负数时,符号位为1,最高位是补0或是补1 取决于编译系统的规定。Turbo C和很多系统规定为补1。
main(){
unsigned a,b;
printf(“input a number: “);
scanf(“%d”,&a);
b=a>>5;
b=b&15;
printf(“a=%d\tb=%d\n”,a,b);
}
请再看一例!
main(){
char a=’a’,b=’b’;
int p,c,d;
p=a;
p=(p<<8)|b;
d=p&0xff;
c=(p&0xff00)>>8;
printf(“a=%d\nb=%d\nc=%d\nd=%d\n”,a,b,c,d);
}

   六、 右移运算符(>>)
   1、运算规则
   用来将一个数的各二进位全部右移若干位,移到右端的低位被舍弃,对无符号数,高位补0;
    对有符号数,左边移入0(“逻辑右移”)或1(“算术右移”)
   2、用途
    右移一位相当于除以2

    七、位运算赋值运算符
     位运算符与赋值运算符可以组成复合赋值运算符,如:
     &=,|=,>>=,<<=,^=

 八、不同长度的数据进行位运算
    如果两个数据长度不同,进行位运算时(如:a&b,而a为long型,b为int型),系统会将二者按右端对齐。如果b为正数,则左侧16位补满0,若b为负数,左端应补满1,如果b为无符号整数型,则左端填满0。

位域

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为: 
struct 位域结构名 
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度

例如: 
struct bs
{
int a:8;
int b:2;
int c:6;
};
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如: 
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:

1、一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如: 
struct bs
{
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*从下一单元开始存放*/
unsigned c:4
}
在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。

2、由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。

3、位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如: 
struct k
{
int a:1
int :2 /*该2位不能使用*/
int b:3
int c:2
};
从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。

二、位域的使用位域的使用和结构成员的使用相同,其一般形式为: 位域变量名·位域名 位域允许用各种格式输出。
main(){
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf(“%d,%d,%d\n”,bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf(“%d,%d,%d\n”,pbit->a,pbit->b,pbit->c);
}

上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。
程序的9、10、11三行分别给三个位域赋值。( 应注意赋值不能超过该位域的允许范围)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针方式给位域a重新赋值,赋为0。第15行使用了复合的位运算符”&=”, 该行相当于: pbit->b=pbit->b&3位域b中原有值为7,与3作按位与运算的结果为3(111&011=011,十进制值为3)。同样,程序第16行中使用了复合位运算”|=”, 相当于: pbit->c=pbit->c|1其结果为15。程序第17行用指针方式输出了这三个域的值。

类型定义符typedef

C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。 类型定义符typedef即可用来完成此功能。例如,有整型量a,b,其说明如下: int aa,b; 其中int是整型变量的类型说明符。int的完整写法为integer,

为了增加程序的可读性,可把整型说明符用typedef定义为: typedef int INTEGER 这以后就可用INTEGER来代替int作整型变量的类型说明了。 例如: INTEGER a,b;它等效于: int a,b; 用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。例如:
typedef char NAME[20]; 表示NAME是字符数组类型,数组长度为20。
然后可用NAME 说明变量,如: NAME a1,a2,s1,s2;完全等效于: char a1[20],a2[20],s1[20],s2[20]
又如: 
typedef struct stu{ char name[20];
int age;
char sex;
} STU;
定义STU表示stu的结构类型,然后可用STU来说明结构变量: STU body1,body2;
typedef定义的一般形式为: typedef 原类型名 新类型名 其中原类型名中含有定义部分,新类型名一般用大写表示, 以
便于区别。在有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。

例子:
例如有中种颜色选择,1代表红,2代表蓝,4带表黑,8带表白
1=0000 0001
2=0000 0010
4=0000 0100
8=0000 1000

如果你选择了2和4(既红和黑)则1(0000 0001)或2(0000 0010)或4(0000 0100)=7(0000 0101)
如果提供7给你,你怎么知道选择了1和2和4呢?答案是:7跟四个数1,2,4,8分别做或结果还是7,则说明某个被选择了
如:7或2=7,所以1被选择了   7或8=15,不等于7哦,所以8没被选择了   ,这样应该知道用途了吧

具体的位运算方式如下:

 运算名称意义
运算对象类型
 运算结果类型 对象数 实例 ~位逻辑非运算
 整型或字符型整型
 1 ~a & 位逻辑与运算整型或字符型
 整型 2 a&b | 位逻辑或运算整型或字符型整型 2 a|b^
 位逻辑异或运算 整型或字符型 整型 1 ^a << 位左移运算 整型或字符型 整型 2 a<<4 >> 位又移运算 整型或字符型 整型 2 a>>2

C#使用Consul集群进行服务注册与发现 - kiba518 - 博客园

Excerpt

前言 我个人觉得,中间件的部署与使用是非常难记忆的;也就是说,如果两次使用中间件的时间间隔比较长,那基本上等于要重新学习使用。 所以,我觉得学习中间件的文章,越详细越好;因为,这对作者而言也是一份珍贵的备忘资料。 Consul简介 Consul一个什么,我想大家通过搜索引擎一定可以搜索到;所以,我就


前言

我个人觉得,中间件的部署与使用是非常难记忆的;也就是说,如果两次使用中间件的时间间隔比较长,那基本上等于要重新学习使用。

所以,我觉得学习中间件的文章,越详细越好;因为,这对作者而言也是一份珍贵的备忘资料。

Consul简介

Consul一个什么,我想大家通过搜索引擎一定可以搜索到;所以,我就不在重复他的官方描述了。

这里,我为大家提供一个更加好理解的描述。

Consul是什么?

Consul本质上是一个Socket通信中间件。

它主要实现了两个功能,服务注册与发现与自身的负载均衡的集群。

我们可以把他理解为一个没有界面的应用程序,因为没有界面,所以想启动Consul就只能使用命令行了;也因为没有界面,一旦使用命令行启动了Consul,那么,执行该命令行的cmd.exe程序,就成了Consul的宿主了;一旦关闭Cmd窗口,Consul就停止运行了。

服务注册与发现的本质是什么?

其实服务注册与发现的原理很简单。

当我们在本机运行Consul时,他会自动监听8500端口;然后我们通过一个开源类库(这个开源类库可以在nuget上检索到,文章下面会介绍),调用其下不同的方法来向这个Consul进程发送TCP消息,来注册服务或者发现服务。

Consul进程在接收到注册消息时,就把注册的服务信息存储到本地磁盘或内存(因为我没有具体去调查Consul存储数据是否使用了数据库,但我们都知道数据库的数据也是保存在本地磁盘的,所以,它肯定是把数据存进磁盘或者内存中了)。

数据中心

Consul存储数据的地方,官方为其命名为数据中心,也就是上面说的保存我们注册的服务信息的本地磁盘或者内存。

Consul提供负载均衡的集群

Consul的集群也很好理解,在我们成功启动Consul以后,它除了监听8500端口以外,它还监听了一个8031端口。

这个8031端口就是用于Consul集群相互通信的。

我们都知道集群是要两台以上的电脑的,所以,我们就必须找到两台或以上的电脑安装Consul中间件。

然后,使用Consul的命令行,将两台电脑连接到一起,这样集群就形成了。

在集群内每台电脑上安装的Consul中间件,我们统称为服务器代理(Agent);当集群启动后,会在多个代理服务器之间选举出一个Leader。

选举Leader自然就是服务器代理之间的通信了,也就是通过上面提到的8031端口通信的。

选举了Leader,服务器代理就可以将自身的负载信息发送给Leader了,这样客户端调用Consul检索服务数据时,就可以去性能最优的那台机器上获取信息了。(注:这个就是举例说明,并非Consul的负载均衡的真实处理模式)

Consul代理服务器安装

首先,去官网下载Consul,官网下载地址https://www.consul.io/downloads.html

拉到网站的最下方,选择Window64-bit的Consul下载,如下图:

下载完成后,我们得到一个压缩包consul_1.6.2_windows_amd64.zip;解压缩后,得到consul.exe文件,如下图:

因为我们要使用命令行来运行consul,所以,我们将consul.exe所在的目录添加进环境变量,这样,当我们在CMD窗口中执行consul的相关命令时,系统就会自动将这个些命令发送给consul.exe文件执行了。

配置环境变量如下图所示:

 配置完环境变量,我们打开一个cmd的命令行窗口,然后输入consul来确认我们的环境变量是否配置成功,如下图:

看到图中的信息,就代表我们的consul的环境变量配置成功了,已经可以运行了。

接下来,我们在这个cmd窗体中输入consul的命令来启动consul服务器代理,命令如下:

1

consul agent -server -ui -bootstrap-expect=1 -data-dir=/tmp/consul -node=consul-1 -client=0.0.0.0 -bind=192.168.1.111 -datacenter=dc1

命令解释如下:

其实consul命令大家是可以在网络上搜到它们的定义的,不过我觉得解释的还是太官方,所以,我在这里提供了一份我认为更好的解释。

consul agent:命令头,必须要有。

-server:表面我们现在要启动服务器代理(agent)是服务模式的。Consul Agent的运行模式有两种,Server模式和Client模式。其区别简单来说就是Server模式的Agent可以被选举为Leader,而Client模式的不可以,当然还有其他区别,有兴趣大家可以自行了解。

-ui:consul运行后,会提供一个http://127.0.0.1:8500/ui/的网站,里面存储了Consul Agent各个节点以及注册的服务等相关信息,即数据中心的网页形式体现。这个参数代表是否创建这个网站,这个参数与这个数据中心网站有关。

bind:本机的ip地址,集群内其他代理服务器可以通过这个ip来访问这台电脑的consul代理服务器。

bootstrap-expect:是集群启动条件,指当服务器端模式(Server模式)的代理达到这个数目后,才开始运行。

data-dir:是存放数据中心数据的,该目录必须是稳定的,系统重启后也继续存在的。

datacenter:当前agent的中心数据的名称,默认是dc1。

node:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名(代表一个机器)。

client:本地ip地址,这里使用 0.0.0.0 ,就表示这个服务器所有IP都可以,即当这台电脑有俩ip,192.168.1.111和192.168.1.112,那么通过这俩IP都可以访问到这台机器的consul代理服务器。

-—————————————————————————————————

运行该命令,如下图所示:

可以看到,我们的Consul代理服务已经成功运行了。

现在,我们在去另一台电脑,打开cmd窗口,运行如下consul命令:

1
consul agent -server -ui -bootstrap-expect=<span>1</span> -data-dir=d:\consul -node=consul-<span>2</span> -client=<span>0.0</span>.<span>0.0</span> -bind=<span>192.168</span>.<span>80.112</span> -datacenter=dc1&nbsp;-join 192.168.80.111

可以看到,我们在命令行最后面追加了一个join 192.168.80.111;通过这个命令,我们把这台电脑的代理服务器成功的加入到了上文中的consul集群。

服务注册与发现

Consul的服务注册

首先,我们创建一个WebAPI,这里为使用了Core框架创建了一个Web API,为了方便测试,我就直接拿本地的VisualStudio启动测试了。

创建WebAPI后,我们在Nuget中查找Consul的Net版本类库。

在Nuget中搜索Consul,然后选中下图中的选项进行安装。

然后,我们在Startup文件中,增加一个函数,如下:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<span>public</span> <span>static</span> <span>void</span><span> RegisterConsul()
{
</span><span>var</span> consulClient = <span>new</span> ConsulClient(p =&gt; { p.Address = <span>new</span> Uri($<span>"</span><span>http://127.0.0.1:8500</span><span>"</span>); });<span>//</span><span>请求注册的 Consul 地址
</span><span>//</span><span>这里的这个ip 就是本机的ip,这个端口8500 这个是默认注册服务端口 </span>
<span>var</span> httpCheck = <span>new</span><span> AgentServiceCheck()
{
DeregisterCriticalServiceAfter </span>= TimeSpan.FromSeconds(<span>5</span>),<span>//</span><span>服务启动多久后注册</span>
Interval = TimeSpan.FromSeconds(<span>10</span>),<span>//</span><span>间隔固定的时间访问一次,</span><span>https://localhost</span><span>:44308/api/Health</span>
HTTP = $<span>"</span><span>https://localhost:44308/api/Health</span><span>"</span>,<span>//</span><span>健康检查地址 &nbsp;44308是visualstudio启动的端口</span>
Timeout = TimeSpan.FromSeconds(<span>5</span><span>)
};

</span><span>var</span> registration = <span>new</span><span> AgentServiceRegistration()
{
Checks </span>= <span>new</span><span>[] { httpCheck },
ID </span>=<span> Guid.NewGuid().ToString(),
Name </span>= <span>"</span><span>test1</span><span>"</span><span>,
Address </span>= <span>"</span><span>https://localhost/</span><span>"</span><span>,
Port </span>= <span>44308</span><span>,

};

consulClient.Agent.ServiceRegister(registration).Wait();</span><span>//</span><span>注册服务

</span><span>//</span><span>consulClient.Agent.ServiceDeregister(registration.ID).Wait();</span><span>//</span><span>registration.ID是guid
</span><span>//</span><span>当服务停止时需要取消服务注册,不然,下次启动服务时,会再注册一个服务。
</span><span>//</span><span>但是,如果该服务长期不启动,那consul会自动删除这个服务,大约2,3分钟就会删了 </span>
<span>
}</span>

复制代码

然后在Configure中调用这个方法,这样,当我们调试或运行这个项目时,就会自动将这个Webapi注册到Consul里了。

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<span>public</span> <span>void</span><span> Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
</span><span>if</span><span> (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints </span>=&gt;<span>
{
endpoints.MapControllers();
});
RegisterConsul();</span><span>//</span><span>注册本服务到consul集群</span>
<span>
}</span>

复制代码

服务注册完后,可以访问本地数据中心的网站【http://127.0.0.1:8500/ui/dc1/services】来查看注册服务的状态。

Consul服务发现

服务注册完成后,我们再创建一个控制台项目来进行服务发现。

创建完成项目后,也需要引用consul类库,同服务端一样在Nuget中搜索。

编写代码如下:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<span>static</span> <span>void</span> Main(<span>string</span><span>[] args)
{
</span><span>var</span> consulClient = <span>new</span> ConsulClient(x =&gt; x.Address = <span>new</span> Uri($<span>"</span><span>http://192.168.1.178:8500</span><span>"</span>));<span>//</span><span>请求注册的 Consul 地址</span>
<span>var</span> ret =<span> consulClient.Agent.Services();

</span><span>var</span> allServer =<span> ret.GetAwaiter().GetResult();
</span><span>//</span><span>这个是个dictionary的返回值,他的key是string类型,就是8500/ui上services的instance的id</span>
<span>var</span> allServerDic =<span> allServer.Response;
</span><span>var</span> test1 =<span> allServerDic.First();
</span><span>string</span> name = test1.Value.Service;<span>//</span><span>服务名,就是注册的那个test1</span>
<span>string</span> serverAddress =<span> test1.Value.Address;
</span><span>int</span> serverPort =<span> test1.Value.Port;
Console.WriteLine($</span><span>"</span><span>serverAddress:{serverAddress}==serverPort{serverPort}</span><span>"</span><span>);
</span><span>//</span><span>我们可以在客户端启动的时候,调用一下consul来查找服务
</span><span>//</span><span>比如,我们可以在服务集合里查找 服务名叫test1的服务 然后在调用它
</span><span>//</span><span>这样,当服务器改变了test1的ip和端口,我们依然可以在集群里找他test1新的ip和端口了</span>
<span> Console.ReadKey();
}</span>

复制代码

运行结果如下:

可以看到,我们已经成功调用了Consul,也成功的获取到了服务信息。 

-—————————————————————————————————

其实Consul除了服务注册与查询,还可以进行Key-Value存储,也就是说,这个是一个分布式Key-Value存储集群。

Key-Value存储的用法在Github已经有例子了,网址:https://github.com/PlayFab/consuldotnet

-—————————————————————————————————

C#使用Consul进行服务注册与发现就讲完了。

代码已经传到Github上了,欢迎大家下载。

Github地址:https://github.com/kiba518/KibaConsul

-—————————————————————————————————

注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

https://www.cnblogs.com/kiba/p/11703073.html

 

一、yum 简介

  yum,是Yellow dog Updater, Modified 的简称,是杜克大学为了提高RPM 软件包安装性而开发的一种软件包管理器。起初是由yellow dog 这一发行版的开发者Terra Soft 研发,用python 写成,那时还叫做yup(yellow dog updater),后经杜克大学的Linux@Duke 开发团队进行改进,遂有此名。yum 的宗旨是自动化地升级,安装/移除rpm 包,收集rpm 包的相关信息,检查依赖性并自动提示用户解决。yum 的关键之处是要有可靠的repository,顾名思义,这是软件的仓库,它可以是http 或ftp 站点,也可以是本地软件池,但必须包含rpm 的header,header 包括了rpm 包的各种信息,包括描述,功能,提供的文件,依赖性等。正是收集了这些header 并加以分析,才能自动化地完成余下的任务。

  yum 的理念是使用一个中心仓库(repository)管理一部分甚至一个distribution 的应用程序相互关系,根据计算出来的软件依赖关系进行相关的升级、安装、删除等等操作,减少了Linux 用户一直头痛的dependencies 的问题。这一点上,yum 和apt 相同。apt 原为debian 的deb 类型软件管理所使用,但是现在也能用到RedHat 门下的rpm 了。

  yum 主要功能是更方便的添加/删除/更新RPM 包,自动解决包的倚赖性问题,便于管理大量系统的更新问题。

  yum 可以同时配置多个资源库(Repository),简洁的配置文件(/etc/yum.conf),自动解决增加或删除rpm 包时遇到的依赖性问题,保持与RPM 数据库的一致性。

二、yum 安装

CentOS 默认已经安装了yum,不需要另外安装,这里为了实验目的,先将yum 卸载再重新安装。

1、查看系统默认安装的yum

# rpm -qa|grep yum

2、卸载yum

# rpm -e yum-fastestmirror-1.1.16-14.el5.centos.1 yum-metadata-parser-1.1.2-3.el5.centos yum-3.2.22-33.el5.centos

3、重新安装yum

这里可以通过wget 从网上下载相关包安装,也可以挂载系统安装光盘进行安装,这里选择挂载系统安装光盘进行安装。

# mount /dev/cdrom /mnt/cdrom/

# rpm -ivh yum-3.2.22-33.el5.centos.noarch.rpm yum-fastestmirror-1.1.16-14.el5.centos.1.noarch.rpm yum-metadata-parser-1.1.2-3.el5.centos.i386.rpm

# yum -v

yum 的基础安装包包括:

  • yum  //RPM installer/updater
  • yum-fastestmirror  //Yum plugin which chooses fastest repository from a mirrorlist
  • yum-metadata-parser  //A fast metadata parser for yum

其他安装包根据自己需要安装。

三、yum 配置

yum 的配置文件分为两部分:main 和repository

  • main 部分定义了全局配置选项,整个yum 配置文件应该只有一个main。常位于/etc/yum.conf 中。
  • repository 部分定义了每个源/服务器的具体配置,可以有一到多个。常位于/etc/yum.repo.d 目录下的各文件中。

yum.conf 文件一般位于/etc目录下,一般其中只包含main部分的配置选项。

# cat /etc/yum.conf

复制代码

[main]
cachedir=/var/cache/yum
  //yum 缓存的目录,yum 在此存储下载的rpm 包和数据库,默认设置为/var/cache/yum
keepcache=0
  //安装完成后是否保留软件包,0为不保留(默认为0),1为保留
debuglevel=2
  //Debug 信息输出等级,范围为0-10,缺省为2
logfile=/var/log/yum.log
  //yum 日志文件位置。用户可以到/var/log/yum.log 文件去查询过去所做的更新。
pkgpolicy=newest
  //包的策略。一共有两个选项,newest 和last,这个作用是如果你设置了多个repository,而同一软件在不同的repository 中同时存在,yum 应该安装哪一个,如果是newest,则yum 会安装最新的那个版本。如果是last,则yum 会将服务器id 以字母表排序,并选择最后的那个服务器上的软件安装。一般都是选newest。
distroverpkg=redhat-release
  //指定一个软件包,yum 会根据这个包判断你的发行版本,默认是redhat-release,也可以是安装的任何针对自己发行版的rpm 包。
tolerant=1
  //有1和0两个选项,表示yum 是否容忍命令行发生与软件包有关的错误,比如你要安装1,2,3三个包,而其中3此前已经安装了,如果你设为1,则yum 不会出现错误信息。默认是0。
exactarch=1
  //有1和0两个选项,设置为1,则yum 只会安装和系统架构匹配的软件包,例如,yum 不会将i686的软件包安装在适合i386的系统中。默认为1。
retries=6
  //网络连接发生错误后的重试次数,如果设为0,则会无限重试。默认值为6.
obsoletes=1
  //这是一个update 的参数,具体请参阅yum(8),简单的说就是相当于upgrade,允许更新陈旧的RPM包。
plugins=1
  //是否启用插件,默认1为允许,0表示不允许。我们一般会用yum-fastestmirror这个插件。
bugtracker_url=http://bugs.centos.org/set\_project.php?project\_id=16&ref=http://bugs.centos.org/bug\_report\_page.php?category=yum

Note: yum-RHN-plugin doesn’t honor this.

metadata_expire=1h

installonly_limit = 5

PUT YOUR REPOS HERE OR IN separate files named file.repo

in /etc/yum.repos.d

复制代码

除了上述之外,还有一些可以添加的选项,如:

  exclude=selinux*  // 排除某些软件在升级名单之外,可以用通配符,列表中各个项目要用空格隔开,这个对于安装了诸如美化包,中文补丁的朋友特别有用。
  gpgcheck=1  // 有1和0两个选择,分别代表是否是否进行gpg(GNU Private Guard) 校验,以确定rpm 包的来源是有效和安全的。这个选项如果设置在[main]部分,则对每个repository 都有效。默认值为0。

四、配置本地yum源

1、挂载系统安装光盘

# mount /dev/cdrom /mnt/cdrom/

2、配置本地yum源

# cd /etc/yum.repos.d/

# ls

会看到四个repo 文件

CentOS-Base.repo 是yum 网络源的配置文件

CentOS-Media.repo 是yum 本地源的配置文件

修改CentOS-Media.repo

# cat CentOS-Media.repo

复制代码

# CentOS-Media.repo
#

This repo is used to mount the default locations for a CDROM / DVD on

CentOS-5. You can use this repo and yum to install items directly off the

DVD ISO that we release.

To use this repo, put in your DVD and use it with the other repos too:

yum –enablerepo=c5-media [command]

or for ONLY the media repo, do this:

yum –disablerepo=\* –enablerepo=c5-media [command]

[c5-media]
name=CentOS-$releasever - Media
baseurl=file:///media/CentOS/
file:///mnt/cdrom/
file:///media/cdrecorder/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

复制代码

在baseurl 中修改第2个路径为/mnt/cdrom(即为光盘挂载点)

将enabled=0改为1

3、禁用默认的yum 网络源

将yum 网络源配置文件改名为CentOS-Base.repo.bak,否则会先在网络源中寻找适合的包,改名之后直接从本地源读取。

4、执行yum 命令

# yum install postgresql

关于repo 文件的格式

所有repository 服务器设置都应该遵循如下格式:

[serverid]
name=Some name for this server
baseurl=url://path/to/repository/

  • serverid 是用于区别各个不同的repository,必须有一个独一无二的名称;
  • name 是对repository 的描述,支持像$releasever $basearch这样的变量;
  • baseurl 是服务器设置中最重要的部分,只有设置正确,才能从上面获取软件。它的格式是:

baseurl=url://server1/path/to/repository/
     url://server2/path/to/repository/
     url://server3/path/to/repository/

其中url 支持的协议有 http:// ftp:// file:// 三种。baseurl 后可以跟多个url,你可以自己改为速度比较快的镜像站,但baseurl 只能有一个,也就是说不能像如下格式:

baseurl=url://server1/path/to/repository/
baseurl=url://server2/path/to/repository/
baseurl=url://server3/path/to/repository/

其中url 指向的目录必须是这个repository header 目录的上一级,它也支持$releasever $basearch 这样的变量。
url 之后可以加上多个选项,如gpgcheck、exclude、failovermethod 等,比如:

复制代码

[updates-released]
name=Fedora Core $releasever - $basearch - Released Updates
baseurl=http://download.atrpms.net/mirrors/fedoracore/updates/$releasever/$basearch
     http://redhat.linux.ee/pub/fedora/linux/core/updates/$releasever/$basearch
     http://fr2.rpmfind.net/linux/fedora/core/updates/$releasever/$basearch
gpgcheck=1
exclude=gaim
failovermethod=priority

复制代码

其中gpgcheck,exclude 的含义和[main] 部分相同,但只对此服务器起作用,failovermethode 有两个选项roundrobin 和priority,意思分别是有多个url可供选择时,yum 选择的次序,roundrobin 是随机选择,如果连接失败则使用下一个,依次循环,priority 则根据url 的次序从第一个开始。如果不指明,默认是roundrobin。

五、配置国内yum源

系统默认的yum 源速度往往不尽人意,为了达到快速安装的目的,在这里修改yum源为国内源。

上海交通大学yum源

a. 修改/etc/yum.repos.d/CentOS-Base.repo为:

复制代码

# CentOS-Base.repo
#

The mirror system uses the connecting IP address of the client and the

update status of each mirror to pick mirrors that are updated to and

geographically close to the client. You should use this for CentOS updates

unless you are manually picking other mirrors.

If the mirrorlist= does not work for you, as a fall back you can try the

remarked out baseurl= line instead.

[base]
name=CentOS-$releasever - Base
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
baseurl=http://ftp.sjtu.edu.cn/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#released updates
[updates]
name=CentOS-$releasever - Updates
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates
baseurl=http://ftp.sjtu.edu.cn/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras
baseurl=http://ftp.sjtu.edu.cn/centos/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus
baseurl=http://ftp.sjtu.edu.cn/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#contrib - packages by Centos Users
[contrib]
name=CentOS-$releasever - Contrib
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=contrib
baseurl=http://ftp.sjtu.edu.cn/centos/$releasever/contrib/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

复制代码

关于变量

  • $releasever:代表发行版的版本,从[main]部分的distroverpkg获取,如果没有,则根据redhat-release包进行判断。
  • $arch:cpu体系,如i686,athlon等
  • $basearch:cpu的基本体系组,如i686和athlon同属i386,alpha和alphaev6同属alpha。

b. 导入GPG KEY

yum 可以使用gpg 对包进行校验,确保下载包的完整性,所以我们先要到各个repository 站点找到gpg key,一般都会放在首页的醒目位置,一些名字诸如RPM-GPG-KEY-CentOS-5 之类的纯文本文件,把它们下载下来,然后用rpm –import RPM-GPG-KEY-CentOS-5 命令将key 导入。

c. 执行yum 命令

其他国内yum源列表如下:

1. 企业贡献:
搜狐开源镜像站:http://mirrors.sohu.com/
网易开源镜像站:http://mirrors.163.com/

2. 大学教学:
北京理工大学:
http://mirror.bit.edu.cn (IPv4 only)
http://mirror.bit6.edu.cn (IPv6 only)
北京交通大学:
http://mirror.bjtu.edu.cn (IPv4 only)
http://mirror6.bjtu.edu.cn (IPv6 only)
http://debian.bjtu.edu.cn (IPv4+IPv6)
兰州大学:http://mirror.lzu.edu.cn/
厦门大学:http://mirrors.xmu.edu.cn/
清华大学:
http://mirrors.tuna.tsinghua.edu.cn/ (IPv4+IPv6)
http://mirrors.6.tuna.tsinghua.edu.cn/ (IPv6 only)
http://mirrors.4.tuna.tsinghua.edu.cn/ (IPv4 only)
天津大学:http://mirror.tju.edu.cn/
中国科学技术大学:
http://mirrors.ustc.edu.cn/ (IPv4+IPv6)
http://mirrors4.ustc.edu.cn/
http://mirrors6.ustc.edu.cn/
东北大学:
http://mirror.neu.edu.cn/ (IPv4 only)
http://mirror.neu6.edu.cn/ (IPv6 only)
电子科技大学:http://ubuntu.uestc.edu.cn/

六、使用第三方软件库

Centos/RHEL默认的yum软件仓库非常有限,仅仅限于发行版本那几张盘里面的常规包和一些软件包的更新,利用RpmForge,可以增加非常多的第三方rpm软件包。RpmForge库现在已经拥有超过10000种的CentOS的软件包,被CentOS社区认为是最安全也是最稳定的一个第三方软件库。

1、安装yum-priorities插件

这个插件是用来设置yum在调用软件源时的顺序的。因为官方提供的软件源,都是比较稳定和被推荐使用的。因此,官方源的顺序要高于第三方源的顺序。如何保证这个顺序,就需要安装yum-priorities这插件了。

# yum -y install yum-priorities

2、安装完yum-priorities插件后需要设置/etc/yum.repos.d/ 目录下的.repo相关文件(如CentOS-Base.repo),在这些文件中插入顺序指令:priority=N (N为1到99的正整数,数值越小越优先)

一般配置[base], [addons], [updates], [extras] 的priority=1,[CentOSplus], [contrib] 的priority=2,其他第三的软件源为:priority=N (推荐N>10)

以CentOS-Base.repo 为例:

复制代码

[base]
name=CentOS-$releasever - Base
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
baseurl=http://ftp.sjtu.edu.cn/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5
priority=1

复制代码

3、下载与安装相应rpmforge的rpm文件包

# wget http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el5.rf.i386.rpm

4、安装DAG的PGP Key

# rpm –import http://apt.sw.be/RPM-GPG-KEY.dag.txt

5、验证rpmforge的rpm文件包

# rpm -K rpmforge-release-0.5.2-2.el5.rf.*.rpm

6、安装rpmforge的rpm文件包

# rpm -i rpmforge-release-0.5.2-2.el5.rf.i386.rpm

7、设置/etc/yum.repos.d/rpmforge.repo文件中源的级别

[root@TS-DEV yum.repos.d]# cat rpmforge.repo 

复制代码

### Name: RPMforge RPM Repository for RHEL 5 - dag

URL: http://rpmforge.net/

[rpmforge]
name = RHEL $releasever - RPMforge.net - dag
baseurl = http://apt.sw.be/redhat/el5/en/$basearch/rpmforge
mirrorlist = http://apt.sw.be/redhat/el5/en/mirrors-rpmforge
#mirrorlist = file:///etc/yum.repos.d/mirrors-rpmforge
enabled = 1
protect = 0
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmforge-dag
gpgcheck = 1
priority=12

复制代码

8、测试安装

# yum install htop

参考 http://wiki.centos.org/AdditionalResources/Repositories/RPMForge#head-5aabf02717d5b6b12d47edbc5811404998926a1b

其他第三方软件库如 EPEL(Extra Packages for Enterprise Linux ) 和 RPMFusion 的安装与使用和RPMForge类似,可自行查找资料安装。


David Camp

  • 技术交流,请加QQ群:

    系统运维技术分享Ⅲ:416491168

  • 业务合作,请联系作者QQ:562866602
  • 我的微信号:mchina_tang
  • 给我写信:mchina_tang@qq.com
  • 我的地址:江苏·苏州

我们永远相信,分享是一种美德 | We Believe, Great People Share Knowledge…