Chemmy's Blog

chengming0916@outlook.com

Prism之Region(1)中,介绍了一些Prism中页面组织以及切换的方式。这一篇将以一个很简单的示例程序来实践一下。

下面是效果图:

image

先说Log,Prism内置了ILogFacade接口,在Prism提供的QuickStart项目里的Modularity中,有一个CallbackLogger,这里我们直接拿过来使用。然后在PrismRegionShell中放一个TextBox,将log的内容显示在这个TextBox中。值得一提的是,为了让输出新log的直接显示出来,需要将TextBox的滚动条滚动到最下面。这里采用的是如下方法:

1
2
3
4
5
6
public void Log(string message, Category category, Priority priority)
{
this.LogContainer.Text += string.Format(CultureInfo.CurrentUICulture, "[{0}][{1}] {2}\r\n", category, priority, message);
// 这段代码的作用是让文本框的滚动条滚动到最底部
LogContainer.Select(LogContainer.Text.Length, LogContainer.Text.Length);
}

然后说一下左边的导航区。这里放置了一个ItemsControl,并将其设为Region。里面的两个按钮并不是直接写死到xaml里的,而是在两个Module初始化时动态添加进来的。也就是说,这个ItemsControl并不知道自己将要包含哪些项。这里我们使用IRegionManager.RegisterViewWithRegion(RegionNames.NavRegion, typeof(EmptyNavigationItem));这种方式来将视图注册到Region中。这样当该Region显示的时候两个视图才会被初始化。这里需要注意的是,一个Region里需要同时显示多个视图时,视图的顺序问题。比如ItemsControl,哪个先被注册就哪个显示在上面,但是由于Module的加载速度等原因,所以这时两个视图不一定谁在上面。现在我需要指定[导航示例]这个按钮在上,那么Prism为我们提供了ViewSortHintAttribute来解决这个问题。在需要进行排序的视图上添加上相应的attribute就可以了。

1
2
3
4
5
[ViewSortHint("01")]
public partial class NavigationItem : UserControl

[ViewSortHint("02")]
public partial class EmptyNavigationItem : UserControl

在初始化导航实例的Module时,将导航示例的视图注册到内容区的Region,这时[上一个]按钮依然处于灰色状态,因为通过RegisterViewWithRegion方法显示的页面是不被记录的。当点击[ViewA][ViewB][ViewC]这三个按钮时,会采用RequestNavigate方法来进行页面的跳转,这时页面跳转的过程会被记录下来,此时就可以通过[上一个]和[下一个]按钮进行页面的前进和后退。

1
2
3
4
5
6
7
8
9
10
11
12
void ToSpecifiedView(string viewName)
{
UriQuery query = new UriQuery();
if (viewName == ViewNames.ViewA)
{
query.Add("Time", DateTime.Now.ToShortTimeString());
}
Uri uri = new Uri(viewName + query.ToString(), UriKind.Relative);
_regionManager.RequestNavigate(RegionNames.NavDemoShowRegion, uri);
logger.Log("跳转到视图 [" + viewName + "]", Category.Info, Priority.Low);
ResetNavigationButtonState();
}

注意这三个视图已经在初始化Module的时候使用IUnityContainer.RegisterType<object, ViewA>(ViewNames.ViewA)方法注册过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void Initialize()
{
logger.Log("初始化Navigation模块", Category.Debug, Priority.Low);
_regionManager.RegisterViewWithRegion(RegionNames.NavRegion, typeof(NavigationItem));
_regionManager.RegisterViewWithRegion(RegionNames.MainRegion,
() => _container.Resolve<NavigationContainer>() );
_regionManager.RegisterViewWithRegion(RegionNames.NavDemoActionRegion, typeof(ActionController));

// 注意注册的类型的必须是object,因为Prism无法确定视图的类型,所以就用了object
_container.RegisterType<object, ViewA>(ViewNames.ViewA);
_container.RegisterType<object, ViewB>(ViewNames.ViewB);
_container.RegisterType<object, ViewC>(ViewNames.ViewC);
}

ViewA和ViewB都实现了INavigationAware接口,不同之处在于ViewA是在其对应的ViewModel ViewAViewModel类中实现的,而ViewB则直接在Code Behind中实现的。Prism对MVVM提供了良好的支持,因此既可以选择在视图中实现该接口也可以在对应的ViewModel中实现。

1
2
3
4
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}

在ViewB中,IsNavigationTarget方法返回了false,而ViewA中则返回了true。可以通过点击三个按钮进行页面跳转,观察log可以发现,ViewA只创建了一次,而ViewB则每次都要重新创建。还有就是在跳转到ViewA的时候传递了参数,可以在OnNavigatedTo方法中取出参数。

1
2
3
4
5
6
public void OnNavigatedTo(NavigationContext navigationContext)
{
UriQuery query = navigationContext.Parameters;
string time = query["Time"];
logger.Log(string.Format("ViewA: 现在时间 {0}", time), Category.Info, Priority.Medium);
}

Prism可以帮助我们开发模块化程序,将程序分割成一个个独立的Module,分别进行开发。然后在程序运行的时候,将各个Module组合到一起,为程序提供各种各样的功能。通常来说,Module是一些视图和功能的集合,那么就需要一种办法来将这些视图以某种形式,在特定的时间展现出来。Prism通过Shell + Region来组织视图的布局,完成视图间的转换等。

image

如上图所示,Shell相当于ASP.NET中的母版页,它定义了页面的布局、主题等。其中的导航区和内容区是预留出来的需要进行填充内容的部分,也就是Region,起到占位符的作用,程序会在运行时动态地向Region中填充内容。

那么如何将一个区域定义为Region呢?

首先在引入Prism的命名空间

xmlns:prism="http://www.codeplex.com/prism" 如果IDE无法找到这个命名空间的话,需要先注册Prism

然后在需要定义为Region的控件上加上Attached Property。

<ContentControl prism:RegionManager.RegionName="MainRegion" />

并不是所有的控件都可以作为Region的,需要为需要定义为Region的控件添加RegionAdapter。RegionAdapter的作用是为特定的控件创建相应的Region,并将控件与Region进行绑定,然后为Region添加一些行为。一个RegionAdapter需要实现IRegionAdapter接口,如果你需要自定义一个RegionAdapter,可以通过继承RegionAdapterBase类来省去一些工作。Prism为Silverlight提供了几个RegionAdapter:

  • ContentControlRegionAdapter: 创建一个SingleActiveRegion并将其与ContentControl绑定
  • ItemsControlRegionAdapter: 创建一个AllActiveRegion并将其与ItemsControl绑定
  • SelectorRegionAdapter: 创建一个Region并将其与Selector绑定
  • TabControlRegionAdapter: 创建一个Region并将其与TabControl绑定

从图中可以看到,导航区对应的NavigationRegion中四个视图都是亮着的,而内容区对应的ContentRegion中四个视图只有一个是亮着的(橘黄色代表显示在页面中)。ItemsControl本来就是由许多个Item组成的,因此ItemsControlRegionAdapter会创建AllActiveRegion,这种类型的Region中所有Active的视图都会显示在ItemsControl中;而ContentControl只能容纳一个Content,所以ContentControlRegionAdapter创建了一个SingleActiveRegion,其中的视图只有一个是处于Active状态的,会显示在ContentControl中,其它的都是不可见的,需要将它们激活(Active),才能使其显示。

通常我们并不直接和Region打交道,而是通过RegionManager,它实现了IRegionManager接口。IRegionManager接口包含一个只读属性Regions,是Region的集合,还有一个CreateRegionManager方法。Prism通过RegionManagerExtensions类使用扩展方法为IRegionManager添加了更多的功能。

  • AddToRegion: 将一个视图添加到一个Region中。
  • RegisterViewWithRegion: 将一个视图和一个Region进行关联。当Region显示的时候,关联的视图才会显示,也就是说,在这个Region显示之前,关联的视图是不会被创建的。
  • RequestNavigate: 进行页面切换,将指定的Region中显示的视图切换为指定的视图。

本文开头说过,需要在运行时将分散在各个Module的视图显示在页面特定的位置上。那么首先就需要定义页面显示的地方,即Region。然后就是要定义创建视图的时机和方式。在Prism中有两种方式来定义视图与Region之间的映射关系——View Discovery和View Injection。

View Discovery是以声明式的方式来建立Region和视图之间的关系。如上图中的导航区,需要在导航区显示的时候就将各个导航视图填充到其中。而内容区中也需要一个默认显示的内容视图。因此也可以这样理解View Discovery,就是指定一个Region的默认视图。我们可以使用IRegionManager.RegisterViewWithRegion方法来声明某个Region默认应该显示哪个视图。注意这里是Register,是注册,也就是说不会马上创建该视图。当Region显示在页面中的时候,它会去寻找与自己相关联的视图,并对其进行初始化。

1-30-2011 5-03-27 PM

1-30-2011 5-04-22 PM

这样做的好处是我们不必关注在什么时候创建视图,一切都会自动完成。缺点就是默认视图是确定的,当需要进行视图转换的时候,这种方式就行不通了。这时候就需要View Injection。

View Injection可以让我们对于Region中显示的视图有更精确的控制。通常可以通过调用IRegionManager.AddToRegion方法或者是IRegionManager.Regions[“RegionName”].Add方法来向一个Region中添加一个视图的实例。对于SingleActiveRegion(ContentControlRegionAdapter会创建这种类型的Region),可以通过IRegion.Activate方法将一个已经添加到Region中的视图显示出来。当然也可以通过IRegion.Deactivate方法来将视图状态置为非激活或者干脆调用IRegion.Remove方法将视图移除。可以看到,因为要添加的是视图的实例,所以需要仔细地设计在什么时候使用View Injection,以免造成不必要的开销。

在Prism 4.0中新添加了一些导航API,这套API大大地简化了View Injection的流程,它使用URI来进行Region中视图的导航,然后会根据URI来创建视图,并将其添加到Region中,然后激活该视图。导航API的出现不只是为了简化View Injection的过程,它还提供了前进、后退的功能,并且对MVVM模式下的导航有良好的支持,还能够在进行导航的时候传递参数等等。所以推荐的方式是使用新的导航API,也就是使用IRegionManager.RequestNavigate方法。

如果一个页面相对来说不大变化,如导航区,在程序初始化的过程完成后就不会轻易地变动,这时候就较适合于使用RegisterViewWithRegion方法,通常可以在Module的Initialize方法中完成这个过程。

1
2
3
4
5
6
7
8
public void Initialize()
{
logger.Log("初始化Navigation模块", Category.Debug, Priority.Low);
_regionManager.RegisterViewWithRegion(RegionNames.NavRegion, typeof(NavigationItem));
_regionManager.RegisterViewWithRegion(RegionNames.MainRegion, // 两种方式都可以
() => _container.Resolve<NavigationContainer>() );
_regionManager.RegisterViewWithRegion(RegionNames.NavDemoActionRegion, typeof(ActionController));
}

如果一个区域需要频繁地切换页面的话,如主内容区,可以使用View Injection的方式。

1
2
3
4
IRegionManager regionManager = ...;
IRegion mainRegion = regionManager.Regions["MainRegion"];
InboxView view = this.container.Resolve<InboxView>();
mainRegion.Add(view);

可以看到,这时候已经生成了视图的实例。之前提到过,一个Region可以包含多个视图,这些视图会处于不同的状态,对于ItemsControl类型的Region来说,里面会显示很多个Item,所以添加进去就可以了;但是对于ContentControl这种Region,同一时刻只能显示一个视图,所以在添加进去之后还需要有一个Activate的过程。

使用URI来进行导航只需要提供需要切换的视图的名称就可以,并不需要了解视图的类型,从而达到解耦的目的,并且可以通过URI来进行参数传递。

1
2
3
4
5
6
7
8
public void Initialize()
{
// 因为Prism无法确定每个视图都是什么类型,所以就使用了Object,
// 因此在根据ViewName获取实例时,会使用IServiceLocator.GetInstance<Object>(ViewName)
_container.RegisterType<object, ViewA>(ViewNames.ViewA);
_container.RegisterType<object, ViewB>(ViewNames.ViewB);
_container.RegisterType<object, ViewC>(ViewNames.ViewC);
}

首先注册一下视图的类型,其实就是将视图的名称与视图类型进行一下关联。在导航的时候调用RequestNavigate方法就可以了。

1
2
3
4
5
6
void ToSpecifiedView(string viewName)
{
Uri uri = new Uri(viewName, UriKind.Relative);
_regionManager.RequestNavigate(RegionNames.NavDemoShowRegion, uri);
logger.Log("跳转到视图 [" + viewName + "]", Category.Info, Priority.Low);
}

Prism提供了UriQuery类来帮助我们在导航的时候传递参数。

1
2
3
4
5
6
7
8
9
10
void ToSpecifiedView(string viewName)
{
UriQuery query = new UriQuery();
if (viewName == ViewNames.ViewA)
{
query.Add("Time", DateTime.Now.ToShortTimeString());
}
Uri uri = new Uri(viewName + query.ToString(), UriKind.Relative);
_regionManager.RequestNavigate(RegionNames.NavDemoShowRegion, uri, CallbackHandler); // 回调方法可加可不加
}

上面的代码判断当跳转到ViewA时,传递一个叫做Time的参数。那么怎样在视图中获取传递的参数呢?这里就要提一下INavigationAware接口了。这个接口使视图或者其对应的ViewModel也可以参与到页面导航的过程中来。所以这个接口既可以由视图来实现,也可以由视图的DataContext——通常指的就是ViewModel,来实现。

1
2
3
4
5
6
public interface INavigationAware
{
bool IsNavigationTarget(NavigationContext navigationContext);
void OnNavigatedTo(NavigationContext navigationContext);
void OnNavigatedFrom(NavigationContext navigationContext);
}

当从本页面转到其它页面的时候,会调用OnNavigatedFrom方法,navigationContext会包含目标页面的URI。

当从其它页面导航至本页面的时候,首先会调用IsNavigationTarget,IsNavigationTarget返回一个bool值,简单地说这个方法的作用就是告诉Prism,是重复使用这个视图的实例还是再创建一个。然后调用OnNavigatedTo方法。在导航到本页面的时候,就可以从navigationContext中取出传递过来的参数。

image

使用导航API的另一个优点就是可以进行页面的前进和后退,一切由Prism完成。这个功能是由IRegionNavigationJournal接口提供的。

1
2
3
4
5
6
7
8
9
10
11
public interface IRegionNavigationJournal
{
bool CanGoBack { get; }
bool CanGoForward { get; }
IRegionNavigationJournalEntry CurrentEntry { get; }
INavigateAsync NavigationTarget { get; set; }
void Clear();
void GoBack();
void GoForward();
void RecordNavigation(IRegionNavigationJournalEntry entry);
}

其中CanGoBack和CanGoForward属性表示当前是否可以后退或前进。如果可以的话,可以使用GoBack和GoForward方法进行前进和后退。

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
public class ActionControllerViewModel : NotificationObject
{
private IRegion _demoShowRegion;
public bool CanGoBack
{
get
{
return _demoShowRegion.NavigationService.Journal.CanGoBack;
}
}

public bool CanGoForward
{
get
{
return _demoShowRegion.NavigationService.Journal.CanGoForward;
}
}

void ToPrevious()
{
_demoShowRegion.NavigationService.Journal.GoBack();
ResetNavigationButtonState();
}

void ToNext()
{
_demoShowRegion.NavigationService.Journal.GoForward();
ResetNavigationButtonState();
}

void ResetNavigationButtonState()
{
RaisePropertyChanged(() => this.CanGoBack);
RaisePropertyChanged(() => this.CanGoForward);
}
}

image

导航API还可以控制视图的生命周期,在页面跳转时进行确认拦截(Confirming or Cancelling Navigation)以及其它功能,可以参考 Developer’s Guide to Microsoft Prism

Prism的核心功能之一就是支持模块化应用程序开发(Modular Application Development),并且在运行时对各个模块进行动态管理。

使用Prism进行模块化开发首先要了解几个概念:

1.Module: Module是一些逻辑上相关的程序集或者资源文件的集合,在Silverlight程序中通常以xap文件为单位存在。而每一个Module中都需要有一个负责进行初始化工作以及与系统进行集成的角色,它需要实现IModule接口。IModule接口中只有一个Initialize方法,一方面这个接口将这个工程标记为一个Module,另一方面你可以在Initialize方法中实现一些逻辑,比如向容器中注册一些Service,或者将视图集成到程序中等等。

2.ModuleInfo: 在创建了一个Module之后,需要通知Prism这个Module的存在,也就是要注册一下。在Prism中,Module是以ModuleInfo的形式存在的。ModuleInfo记录了Module的信息,ModuleName属性是Module的标识符,相当于Module的ID;ModuleType是Module的AssemblyQualifiedName;DependsOn属性是该Module依赖的其它Module的ModuleName的集合,在加载该Module时,如果有依赖项没有加载的话,会先将依赖项加载;InitializationMode,有两种情况——WhenAvailable和OnDemand,当选择了WhenAvailable时,该Module会在程序启动时自动加载,如果选择了OnDemand,则会按需加载,默认情况下是WhenAvailable;Ref,存储该Module的位置,如XXX.xap;State,定义了Module从注册到加载到初始化的整个过程中的状态。

3.ModuleCatalog: ModuleCatalog实现了IModuleCatalog接口,它是ModuleInfo的容器,保存着系统中所有Module的信息,不仅会管理哪些Module需要加载,什么时候加载以什么顺序加载等问题,还要检查各个Module之间是否存在着循环依赖、是否有重复的Module等等。ModuleCatalog提供了含参构造方法和AddModule方法,可以通过代码将Module注册进去,同时也可以在xaml文件中配置好Module,然后通过ModuleCatalog.CreateFromXaml方法来加载。

4.ModuleManager: ModuleManager实现了IModuleManager接口。顾名思义就是管理Module的类。IModuleManager中含有两个方法和两个事件:Run方法会将所有InitializationMode为WhenAvailable的Module加载,然后进行初始化,初始化的工作委托给了IModuleInitializer来完成,它会获取到Module类(上面提到的实现了IModule接口的类)的实例,然后调用其Initialize方法。LoadModule方法用来加载InitializationMode为OnDemand的Module。两个事件分别用来通知下载Module的进度变化以及Module加载完成。

下面用一个示例程序来说明如何在Prism中进行模块化程序开发。

1.创建一个Silverlight Application,叫做PrismModule。

2.在Solution中添加三个Silverlight Application,分别叫做ModuleA, ModuleB, ModuleC。然后删除这三个工程中的App文件和MainPage文件。

3.在ModuleA工程下添加一个UserControl,叫做ViewA,然后再添加一个类,叫做ModuleA。并添加Microsoft.Practices.Prism和Microsoft.Practices.ServiceLocation引用。下面是ViewA和ModuleA的代码:

1
2
3
4
5
6
7
8
9
10
11
12
<UserControl x:Class="ModuleA.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="Module A" FontSize="22" />
</Grid>
</UserControl>
1
2
3
4
5
6
public class ModuleA : IModule
{
public void Initialize()
{
}
}

4.对ModuleB和ModuleC重复做步骤3的操作,只是将文本改成相应模块。

5.在PrismModule中添加对ModuleA、ModuleB、ModuleC、Prism、UnityExtensions还有Unity for Silverlight的引用,然后创建Shell和Bootstrapper。添加一个UserControl,叫做Shell;再添加一个类,叫做Bootstrapper。

Shell代码如下:

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
<UserControl x:Class="PrismModule.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800">

<StackPanel Margin="50">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Border VerticalAlignment="Top" BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
<ContentControl prism:RegionManager.RegionName="RegionA" />
</Border>
<Border VerticalAlignment="Top" BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
<ContentControl prism:RegionManager.RegionName="RegionB" />
</Border>
<StackPanel>
<Border BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
<ContentControl prism:RegionManager.RegionName="RegionC" />
</Border>
<Button Content="Load Module C" Click="LoadModuleC" Width="120" Height="25" />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

Shell.xaml.cs代码如下:

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
<UserControl x:Class="PrismModule.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800">

<StackPanel Margin="50">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Border VerticalAlignment="Top" BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
<ContentControl prism:RegionManager.RegionName="RegionA" />
</Border>
<Border VerticalAlignment="Top" BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
<ContentControl prism:RegionManager.RegionName="RegionB" />
</Border>
<StackPanel>
<Border BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
<ContentControl prism:RegionManager.RegionName="RegionC" />
</Border>
<Button Content="Load Module C" Click="LoadModuleC" Width="120" Height="25" />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

Bootstrapper代码如下:

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
47
48
49
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return this.Container.TryResolve<Shell>();
}

protected override void InitializeShell()
{
App.Current.RootVisual = (UIElement)this.Shell;
}

protected override IModuleCatalog CreateModuleCatalog()
{
return new ModuleCatalog();
}

protected override void ConfigureModuleCatalog()
{
Type typeA = typeof(ModuleA.ModuleA);
ModuleInfo moduleA = new ModuleInfo
{ // ModuleA没有设置InitializationMode,默认为WhenAvailable
ModuleName = typeA.Name,
ModuleType = typeA.AssemblyQualifiedName,
};

Type typeB = typeof(ModuleB.ModuleB);
ModuleInfo moduleB = new ModuleInfo
{
ModuleName = typeB.Name,
ModuleType = typeB.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand,
};

Type typeC = typeof(ModuleC.ModuleC);
ModuleInfo moduleC = new ModuleInfo
{
ModuleName = typeC.Name,
ModuleType = typeC.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand,
// ModuleC依赖于ModuleB
DependsOn = new Collection<string> { moduleB.ModuleName },
};

this.ModuleCatalog.AddModule(moduleA);
this.ModuleCatalog.AddModule(moduleB);
this.ModuleCatalog.AddModule(moduleC);
}
}

将App.xaml.cs中的Application_Startup方法改为

1
2
3
4
5
private void Application_Startup(object sender, StartupEventArgs e)
{
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
}

6.现在已经有了Region,需要将各个Module中的View填充到Region中。修改ModuleA,ModuleB和ModuleC的Initialize方法。

1
2
3
4
5
public void Initialize()
{
ServiceLocator.Current.GetInstance<IRegionManager>().
RegisterViewWithRegion("RegionA", typeof(ViewA));
}

将其中的A改为相应的字母。运行程序,结果如下:

image

我们点击按钮来加载ModuleC,因为ModuleC依赖于ModuleB,所以ModuleB也一块儿加载出来了。但是这与我们预期的效果不太一致。因为一共只load了一个xap文件,用WinRAR打开看一下,发现三个Module的程序集都在其中。

image

在Silverlight程序中,模块化程序开发应该不仅仅体现在开发时的模块化,运行时也应该是模块化的。比如ModuleA在程序加载时就load出来,但是ModuleB和ModuleC则是在点击了按钮后才load出来的,换句话说,在没点按钮前就不应该将ModuleB和ModuleC的程序集加载进来。现在由于PrismModule项目引用了三个Module,所以程序集会被一块打包进xap文件中。我们修改一下,将对ModuleB和ModuleC的引用的Copy Local属性设置为false:

imageimage

重新编译一下,再次查看xap文件,发现已经没有了ModuleB和ModuleC。

image

运行程序,报错。很简单,因为我们在Bootstrapper中用到了ModuleB和ModuleC,缺少了这两个dll,程序没法运行。为了解决这个问题,我们把初始化ModuleCatalog的过程改一下,不使用代码,而是使用配置文件。在Silverlight中,Prism支持使用xaml文件作为配置文件。下面在PrismModule工程下新建一个资源文件,ModuleCatalog.xaml。内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">

<Modularity:ModuleInfo Ref="ModuleA.xap" ModuleName="ModuleA"
ModuleType="ModuleA.ModuleA, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

<Modularity:ModuleInfo Ref="ModuleB.xap" ModuleName="ModuleB" InitializationMode="OnDemand"
ModuleType="ModuleB.ModuleB, ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

<Modularity:ModuleInfo Ref="ModuleC.xap" ModuleName="ModuleC" InitializationMode="OnDemand"
ModuleType="ModuleC.ModuleC, ModuleC, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<Modularity:ModuleInfo.DependsOn>
<sys:String>ModuleB</sys:String>
</Modularity:ModuleInfo.DependsOn>
</Modularity:ModuleInfo>
</Modularity:ModuleCatalog>

这里大体和用代码写一致,只不过Ref属性里要写明该Module对应的是哪个xap包。Prism在Silverlight程序中使用一个叫做XapModuleTypeLoader的类来加载Module,在将Module下载之后会获取AppManifest.xaml文件,也就是说如果你的Module是个类库工程的话,会在加载时产生错误。可以将几个类库的程序集文件包装在一个xap文件中作为一个Module来使用,或者自定义一个ModuleTypeLoader。

定义完Module的配置文件后,要改写Bootstrapper。首先删除用代码配置Module的方法ConfigureModuleCatalog,然后在CreateModuleCatalog方法中替换成一下内容:

1
2
3
4
5
protected override IModuleCatalog CreateModuleCatalog()
{
return Microsoft.Practices.Prism.Modularity.ModuleCatalog.CreateFromXaml(
new Uri("/PrismModule;component/ModuleCatalog.xaml", UriKind.Relative));
}

再次运行程序,正常运行。

image

这样就达到了按需加载的目的。节约带宽是一个好处,如果产品是分模块往外卖的时候,可以由客户按需定制。

不过再打开ModuleB和ModuleC的xap文件看一下,发现里面不仅有Module本身的程序集,还包括了引用的Prism的程序集等。而这些程序集其实已经在PrismModule.xap中包含了。完全没有必要重复下载。所以可以将多余的程序集的引用的Copy Local属性设置为false,这样就瘦身成功了。(想要避免重复加载相同的文件,也可以通过在项目的Properties面板中勾选Reduce XAP size by using application library caching选项)

如果你对Module的加载到执行的整个过程感兴趣,那么Prism本身提供了一个QuickStart,既有Unity版本也有Mef版本,不要错过。

在程序中使用框架必然要有一个切入点,框架会在这里进行初始化,处理相关配置信息等。在Prism中扮演这一角色的就是Bootstrapper。

Prism提供了一个抽象基类Bootstrapper,这个类里面包含了包含了许多空的虚方法,可以重写它们添加自己的逻辑。这个基类与任何容器无关,所以可以通过继承它来实现基于特定容器的Bootstrapper,不过通常我们大可不必这样做,因为Prism默认提供了两个基于特定容器的Bootstrapper——UnityBootstrapper和MefBootstrapper,分别使用Unity和Mef来实现依赖注入。而我们需要做的工作就是在这两个类之间选择一个适合自己的,稍微配置一下就可以了。当然如果你不喜欢这两个容器或者已有的程序使用了其它容器(如Spring.Net, Castle等),也可以通过继承Boostrapper抽象基类来实现自己的SpringBootstrapper和CastleBootstrapper。虽然UnityBootstrapper的代码看起来挺简单的,但是如果仿照这个来实现CastleBootstrapper却并不是那么容易的一件事(不信你可以试试),所以更好的办法是用现成的。

那么Bootstrapper都做了些什么呢?
  1. 创建Logger:

    执行CreateLogger方法,默认创建一个EmptyLogger,不会在任何地方输出log。当然是可以扩展的,比如你可以使用Clog来做一个适配器。

  2. 创建并配置ModuleCatalog

执行CreateModuleCatalog方法,默认创建一个空的ModuleCatalog。然后执行ConfigureModuleCatalog方法,默认情况下这个方法是空的。可以重写这两个方法,加入自定义的获取ModuleCatalog的逻辑,比如在CreateModuleCatalog中可以从一个xaml文件中读取Module信息。

1
2
3
4
5
protected override IModuleCatalog CreateModuleCatalog()
{
return ModuleCatalog.CreateFromXaml(new Uri("/AssemblyName;component/ModulesCatalog.xaml", UriKind.Relative));
}

  1. 创建并配置依赖注入容器

Prism中使用依赖注入来管理各个组件,你可以使用任何你熟悉的容器,比如Castle, Unity等。Prism中内置了对Unity以及Mef的支持,所以有两种预定义好的Bootstrapper: UnityBootstrapper和MefBootstrapper,其中分别采用UnityContainer和CompositionContainer作为依赖注入容器。以UnityBootstrapper为例,在这一步中会先调用CreateContainer方法,返回一个UnityContainer;然后调用ConfigureContainer方法,在这个方法中主要是将一些常用的类注册到容器中。

  1. 配置默认的Region适配器映射

为了使xaml中的UI控件可以使用Region,需要先注册一下。Prism默认支持Region的控件类型有:TabControl, Selector, ItemsControl, ContentControl。当然你也可以通过实现IRegionAdapter接口或者直接继承RegionAdapterBase来使其它控件也支持Region。

  1. 配置默认的Region 行为(Behavior)

为RegionBehaviorFactory添加一些默认的行为。这样可以扩展Region的行为。可以通过实现IRegionBehavior接口或继承RegionBehavior来自定义Region的行为,并重写ConfigureDefaultRegionBehaviors方法添加到Region。

  1. 注册框架异常类型

Prism提供了ExceptionExtensions类来帮助开发人员定位异常发生的根异常。在这一步通过调用RegisterFrameworkExceptionTypes方法向ExceptionExtensions中添加新的Root Exception。

  1. 创建并初始化Shell

首先调用CreateShell方法来创建一个Shell,这是一个抽象方法,通常这个方法中就是返回作为整个网站容器的页面。之后会将RegionManager attach到Shell上,然后更新定义的Regions,最后调用InitializeShell方法来初始化Shell。默认情况下这是个空方法,可以通过重写这个方法加入自定义的逻辑,可以在这个方法中将Shell作为Silverlight程序的根容器页面显示出来。

1
2
3
4
5
protected override void InitializeShell()`
{
Application.Current.RootVisual = Shell;
}

  1. 初始化Modules

调用InitializeModules方法,实际上就是调用ModuleManager.Run方法,会调用ModuleCatalog中的所有InitializationMode为WhenAvailable的Module的Initialize方法。

至此,整个容器的初始化过程就完毕了。

值得一提的还有CommonServiceLocator,这同样是Patterns & Practices小组的产品。它的作用很简单,就是统一依赖注入容器的接口,使程序不必依赖于特定的容器,只需要使用ServiceLocator,然后去间接地使用其它各种各样的容器。在Prism的内部就是使用ServiceLocator来进行管理的。所以不管使用什么样的容器,都需要提供一个实现了IServiceLocator接口的适配器,如使用Unity要提供UnityServiceLocatorAdapter,使用Mef要提供MefServiceLocatorAdapter。这样不管外部使用什么容器,内部都不需要改变。所以如果要使用Prism重头开始构架一个程序,那么在整个程序中不依赖于特定的依赖注入容器接口,而是使用ServiceLocator是一个不错的选择,这样可以在需要的情况下很容易地更换容器,只需要重写一个Bootstrapper和一个ServiceLocatorAdapter就可以了。

Prism是由微软Patterns & Practices团队开发的项目,目的在于帮助开发人员构建松散耦合的、更灵活、更易于维护并且更易于测试的WPF应用或是Silverlight应用以及Windows Phone 7应用。使用Prism可以使程序开发更趋于模块化,整个项目将由多个离散的、松耦合的模块组成,而各个模块又可以又不同的开发者或团队进行开发、测试和部署。目前Prism的最新版本是Prism 4,于2010年11月12日发布。Prism有很完整的文档以及丰富的示例程序。在这里我们仅针对于Silverlight程序的开发。

在下载Prism安装包并安装完成后,会在目标文件夹中发现很多文件。

推荐首先运行RegisterPrismBinaries.bat文件,这样在开发基于Prism的程序时可以更方便地添加引用程序集。

使用Prism之前,需要了解一些概念,下面通过一个非常简单的小程序来了解一下Prism。

1.打开Visual Studio 2010,新建一个Silverlight Application项目,并添加对Prism的引用。再创建三个Silverlight类库工程。

2.在Contract工程下新建一个接口,叫做ITextProvider。

1
2
3
4
5

public interface ITextProvider
{
string GetText();
}

3.在其它的三个项目中都引用Contract项目。

4.在PrismStarter工程下新建一个TextProvider类并实现ITextProvider接口。

1
2
3
4
5
6
7
8
9
10
11
public class TextProvider : ITextProvider
{
private int i = 0;

public string GetText()
{
i++;
return string.Format("From TextProvider [{0}]", i);
}
}

5.删除PrismStarter项目中自动生成的MainPage.xaml,创建一个新的UserControl,叫做Shell。页面代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<UserControl x:Class="PrismStarter.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="100" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>

<TextBlock FontSize="30" VerticalAlignment="Center" HorizontalAlignment="Center" Text="Prism Starter" />

<ContentControl Grid.Row="1" HorizontalContentAlignment="Stretch" prism:RegionManager.RegionName="RegionA" />

<ContentControl Grid.Row="2" HorizontalContentAlignment="Stretch" prism:RegionManager.RegionName="RegionB" />
</Grid>
</UserControl>

6.在ModuleA工程中添加对Prism程序集的引用。并添加一个UserControl叫做ViewA,页面代码为:

1
2
3
<Grid :Name="LayoutRoot" Background="White">
<TextBlock x:Name="textModuleA" FontSize="30" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>

CodeBehind中的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
public partial class ViewA : UserControl
{
public ViewA(ITextProvider textProvider)
{
InitializeComponent();

this.Loaded += (s, e) =>
{
textModuleA.Text = string.Format("Module A {0}", textProvider.GetText());
};
}
}

7.在ModuleA工程中添加一个类叫做ModuleA,并实现接口IModule。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ModuleA : IModule
{
private IRegionManager _regionManager;

public ModuleA(IRegionManager regionManager)
{
_regionManager = regionManager;
}

public void Initialize()
{
_regionManager.RegisterViewWithRegion("RegionA", typeof(ViewA));
}
}

注意这里的RegionA对应于Shell页面中的RegionName。

8.在ModuleB工程中重复6、7过程,只是将A替换为B。

9.在PrismStarter工程中添加对ModuleA和ModuleB的引用。

10.在PrismStarter工程中添加一个PrismStarterBootstrapper类,并继承UnityBootstrapper。

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
public class PrismStarterBootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return this.Container.TryResolve<Shell>();
}

protected override void InitializeShell()
{ // 控制页面在初始化时显示Shell页面
App.Current.RootVisual = (UIElement)this.Shell;
}

protected override void ConfigureModuleCatalog()
{ // 注册Module。在实际开发中可以使用xaml做配置文件,
// 这样就可以将PrismStarter与ModuleA和ModuleB完全解耦,也就不再需要引用这两个项目
Type moduleAType = typeof(ModuleA.ModuleA);
ModuleInfo moduleA = new ModuleInfo
{
ModuleName = moduleAType.Name,
ModuleType = moduleAType.AssemblyQualifiedName,
};

Type moduleBType = typeof(ModuleB.ModuleB);
ModuleInfo moduleB = new ModuleInfo
{
ModuleName = moduleBType.Name,
ModuleType = moduleBType.AssemblyQualifiedName,
};

this.ModuleCatalog.AddModule(moduleA);
this.ModuleCatalog.AddModule(moduleB);
}

protected override void ConfigureContainer()
{ // 注册一下TextProvider,这样在通过容器请求ITextProvider时会返回TextProvider实例
base.ConfigureContainer();
this.Container.RegisterInstance<ITextProvider>(new TextProvider());
}
}

11.最后一步,打开App.xaml.cs,修改Application_Startup方法

1
2
3
4
5
private void Application_Startup(object sender, StartupEventArgs e)
{
PrismStarterBootstrapper bootstrapper = new PrismStarterBootstrapper();
bootstrapper.Run();
}

运行程序,结果如下:

image

下面简单介绍一下这个小例子中涉及到的一些概念。

Bootstrapper: 在程序中使用框架需要找到一个切入点,将框架植入进去,将一部分功能委托给框架来实现。在Silverlight中使用Prism的切入点就是App.xaml.cs中的Application_Startup方法。一般来说,这个方法中只是指定页面最先加载的页面,但是我们把默认的逻辑去掉,取而代之的是Bootstrapper(在本例中就是PrismStarterBootstrapper)。当调用Bootstrapper.Run方法时,它会完成一些准备工作,如一些配置等。因此你会发现,使用Prism后,启动程序时会比正常启动要慢一些,就是因为Bootstrapper做了许多工作。

Container: 依赖注入容器。在程序中使用依赖注入的好处到处都可以找的到。在Silverlight中使用容器来管理各个组件的一个很明显的好处就是使用单例来降低内存使用。否则每次加载一个页面都需要重新创建一个也很耗费资源的。当然好处不只这些,通过容器来注入一些服务(如本例中的IRegionManager和ITextProvider)显得相当方便。

Module: Prism帮助我们把程序分解成一个个功能模块,这些功能模块就叫做Module,通常一个工程就是一个Module。由于Module彼此是独立的,但是在运行时需要将它们整合到一起,因此Prism需要知道Module的存在,这里就涉及到了ModuleCatalog, ModuleCatalog就是Module的容器,里面包含了所有Module的信息,以ModuleInfo的形式存在。ModuleInfo就是对Module的抽象,包含Module的名字,类型,依赖等一些信息。

Shell: 相当于程序的入口,初始界面,还能够提供类似ASP.Net中的母版页的功能。Shell必须由Bootstrapper创建,因为Shell需要使用的一些service,比如RegionManager等,需要在Shell显示前注册。

Region: 相当于ASP.Net中的ContentPlaceHolder(是这么叫的吧?),起到占位符的作用,如本例中Shell中有两个Region——RegionA和RegionB,定义了两块区域。在Module的初始化过程中,通过IRegionManager将Module中的页面放进了定义好的Region中。IRegionManager负责管理Region,可以通过它向Region中注册View,进行导航等。

Prism的功能当然远不止这么简单,它还提供对MVVM模式的支持,对导航的支持等,在后续文章中会逐步介绍。希望能够通过本文让大家对Prism有一定的了解。

代码下载

.NET 中定时器的使用方法

在.NET 开发中,定时器是实现周期性任务的核心组件。根据是否依赖 UI 线程,可将其分为 “与 UI 无关联” 和 “与 UI 相关” 两大类,不同类型的定时器适用场景、线程模型及使用方式差异显著。本文将详细讲解各类定时器的特点、核心属性、正确用法及注意事项。

一、与 UI 无关联的定时器

此类定时器运行于非 UI 线程(默认使用线程池),适用于后台计算、数据同步等无需操作 UI 的场景,时间精度相对较高。

1. System.Timers.Timer

核心特点

  • 支持线程同步配置,可通过SynchronizingObject指定执行线程;

  • 事件驱动模式(通过Elapsed事件触发任务);

  • 适用于对执行精度有一定要求的后台任务。

关键属性修正

  • AutoReset:原始描述颠倒,正确逻辑为:

    • true(默认):定时器触发后自动重置,持续周期性执行;

    • false:定时器仅触发一次,执行后自动停止。

  • SynchronizingObject:默认值为null,任务运行于线程池(CPU 自动分配线程);若指定为 UI 控件(如 WinForms 的 Form),则任务会切换到 UI 线程执行。

  • Interval:任务执行间隔(单位:毫秒),最小值为 1。

  • Enabled:控制定时器是否启用(true启用,false禁用)。

正确代码示例

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
using System;

using System.Threading;

public class TimersTimerExample

{

// 声明定时器与线程同步锁(确保多线程安全)

private readonly System.Timers.Timer \_timer = new System.Timers.Timer();

private readonly object \_syncRoot = new object();

public TimersTimerExample()

{

// 绑定Elapsed事件(任务执行逻辑)

\_timer.Elapsed += OnTimerTick;

// 设置执行间隔为100毫秒

\_timer.Interval = 100;

// 配置为持续执行(AutoReset=true)

\_timer.AutoReset = true;

// 初始禁用,需手动启动

\_timer.Enabled = false;

}

// 启动定时器

public void StartTimer()

{

\_timer.Enabled = true;

// 或使用\_timer.Start()(与Enabled=true等效)

}

// 停止定时器

public void StopTimer()

{

\_timer.Enabled = false;

// 或使用\_timer.Stop()

}

// Elapsed事件回调(默认运行于线程池)

private void OnTimerTick(object sender, System.Timers.ElapsedEventArgs e)

{

// 加锁确保多线程下任务逻辑安全(避免并发问题)

lock (\_syncRoot)

{

Console.WriteLine(\$"后台任务执行:{DateTime.Now:HH:mm:ss.fff}");

// TODO:添加实际业务逻辑(如数据同步、日志记录)

}

}

}

注意事项

  • 若任务逻辑涉及共享资源,需通过lock等方式保证线程安全;

  • 若需在 UI 线程更新内容,需将SynchronizingObject指定为 UI 控件(如_timer.SynchronizingObject = this,需在 WinForms 环境中)。

2. System.Threading.Timer

核心特点

  • 轻量级定时器,完全基于线程池实现;

  • 无事件模型,通过回调函数(TimerCallback)执行任务;

  • 不支持直接指定同步对象,需手动处理线程切换。

关键参数

构造函数System.Threading.Timer(TimerCallback callback, object state, int dueTime, int period)参数说明:

  • callback:定时器触发时执行的回调函数;

  • state:传递给回调函数的参数(无需参数时设为null);

  • dueTime:定时器启动延迟时间(单位:毫秒),0表示立即启动;

  • period:任务执行间隔(单位:毫秒),Timeout.Infinite表示仅执行一次。

正确代码示例

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
47
48
49
50
51
52
53
54
55
56
57
using System;

using System.Threading;

public class ThreadingTimerExample

{

// 声明线程定时器(需注意释放资源)

private System.Threading.Timer \_timer;

public ThreadingTimerExample()

{

// 初始化定时器:立即启动(dueTime=0),间隔1000毫秒执行一次

\_timer = new System.Threading.Timer(

callback: TimerCallback,

state: null,

dueTime: 0,

period: 1000

);

}

// 定时器回调函数(运行于线程池)

private void TimerCallback(object state)

{

Console.WriteLine(\$"轻量级后台任务:{DateTime.Now:HH:mm:ss}");

// TODO:添加轻量级业务逻辑(如心跳检测、缓存清理)

}

// 释放定时器资源(避免内存泄漏)

public void DisposeTimer()

{

\_timer?.Change(Timeout.Infinite, Timeout.Infinite); // 先停止定时器

\_timer?.Dispose(); // 释放资源

}

}

注意事项

  • 若任务执行时间超过period,线程池会分配新线程执行下一次任务,需自行控制并发;

  • 不再使用时必须调用Dispose释放资源,避免线程泄漏;

  • 无法直接更新 UI,需通过Dispatcher(WPF)或Invoke(WinForms)切换到 UI 线程。

二、与 UI 相关的定时器

此类定时器绑定 UI 线程,适用于 WinForms、WPF 等桌面应用的 UI 更新场景,无需手动处理线程安全,但时间精度较低(受 UI 线程繁忙程度影响)。

1. System.Windows.Forms.Timer(WinForms 专用)

核心特点

  • 仅适用于 WinForms 应用,支持可视化拖拽(从工具箱拖到 Form 上);

  • 任务运行于 UI 线程,可直接更新 UI 控件(如 Label、TextBox);

  • 精度较低(约 10-55 毫秒),不适用于高时效任务。

关键属性

  • Interval:执行间隔(单位:毫秒),最小值为 1;

  • Enabled:控制定时器启用 / 禁用(true启用,false禁用);

  • Tick:定时器触发时执行的事件(运行于 UI 线程)。

正确代码示例

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
47
48
49
50
51
52
53
using System;

using System.Windows.Forms;

public partial class TimerForm : Form

{

// 声明WinForms定时器(可通过设计器拖拽生成)

private readonly System.Windows.Forms.Timer \_uiTimer;

public TimerForm()

{

InitializeComponent();

// 初始化定时器(代码方式,非拖拽)

\_uiTimer = new System.Windows.Forms.Timer();

\_uiTimer.Interval = 1000; // 1秒更新一次UI

\_uiTimer.Tick += UITimer\_Tick;

\_uiTimer.Enabled = true; // 启用定时器

}

// Tick事件(运行于UI线程,可直接操作控件)

private void UITimer\_Tick(object sender, EventArgs e)

{

// 直接更新Label文本(无需线程同步)

lblTime.Text = \$"当前时间:{DateTime.Now:HH:mm:ss}";

}

// 关闭窗体时释放定时器

private void TimerForm\_FormClosing(object sender, FormClosingEventArgs e)

{

\_uiTimer?.Dispose();

}

}

注意事项

  • 仅能在 WinForms 项目中使用,无法跨框架(如 WPF);

  • Tick事件中执行耗时操作(如循环计算),会导致 UI 卡顿;

  • 无需手动处理线程安全,因事件始终在 UI 线程执行。

2. System.Windows.DispatcherTimer(WPF 专用)

核心特点

  • 仅适用于 WPF 应用,基于Dispatcher(WPF 线程调度器)实现;

  • 任务运行于 UI 线程,支持直接更新 WPF 控件(如 TextBlock);

  • 可通过DispatcherPriority调整任务优先级(默认Normal)。

关键属性与方法

  • Interval:执行间隔(类型:TimeSpan,支持秒、毫秒等单位);

  • Tick:定时器触发事件(运行于 UI 线程);

  • **Start()/Stop()**:启动 / 停止定时器(替代Enabled属性);

  • DispatcherPriority:任务在 UI 线程中的执行优先级(如Background表示后台优先级,不阻塞 UI)。

正确代码示例

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using System;

using System.Windows;

using System.Windows.Threading;

public partial class MainWindow : Window

{

// 声明WPF调度定时器

private readonly DispatcherTimer \_dispatcherTimer;

public MainWindow()

{

InitializeComponent();

// 初始化定时器

\_dispatcherTimer = new DispatcherTimer();

// 设置间隔为1秒(使用TimeSpan)

\_dispatcherTimer.Interval = TimeSpan.FromSeconds(1);

// 绑定Tick事件

\_dispatcherTimer.Tick += DispatcherTimer\_Tick;

// 启动定时器

\_dispatcherTimer.Start();

}

// Tick事件(运行于UI线程,可直接更新WPF控件)

private void DispatcherTimer\_Tick(object sender, EventArgs e)

{

// 直接更新TextBlock内容

tbTime.Text = \$"当前时间:{DateTime.Now:HH:mm:ss.fff}";

}

// 关闭窗口时停止定时器

private void MainWindow\_Closing(object sender, System.ComponentModel.CancelEventArgs e)

{

\_dispatcherTimer?.Stop();

}

}

注意事项

  • 仅能在 WPF 项目中使用,依赖System.Windows程序集;

  • 若需降低任务对 UI 的影响,可设置_dispatcherTimer.DispatcherPriority = DispatcherPriority.Background

  • 若任务耗时较长,仍会导致 UI 响应缓慢,需结合Task开启后台线程处理(处理完后通过Dispatcher.Invoke更新 UI)。

三、定时器选择指南

定时器类型 适用框架 线程模型 精度 核心场景
System.Timers.Timer 通用(非 UI) 线程池(可指定同步对象) 较高 后台任务、数据同步
System.Threading.Timer 通用(非 UI) 线程池 轻量级后台任务(如心跳检测)
System.Windows.Forms.Timer WinForms UI 线程 较低 WinForms UI 更新
System.Windows.DispatcherTimer WPF UI 线程 较低 WPF UI 更新

总结:需根据项目框架(WinForms/WPF/ 控制台)、是否操作 UI、精度要求三大因素选择定时器,避免因线程模型不匹配导致 UI 卡顿或数据安全问题。

好的,我将为您整理这篇文章,使其结构更清晰、行文更连贯,同时保持原意不变。


WebRTC视频聊天系统快速搭建指南

概述

本文旨在指导读者在短时间内(约5分钟)搭建一个功能完整的WebRTC视频聊天系统。WebRTC(Web Real-Time Communication)是一项支持网页浏览器进行实时语音或视频对话的技术。

系统架构

一个能在公网运行的视频通信系统需要三个核心组件:

  1. NAT穿透服务器(ICE Server):解决内网穿透和网络地址转换问题。
  2. 基于WebSocket的信令服务器(Signaling Server):用于建立点对点连接前的信令交换。
  3. Web客户端:通过HTML5的WebRTC API调用摄像头并实现用户交互。

这三个部分可以部署在同一台或多台服务器上。演示环境需要开放以下端口:347888888080

系统架构图如下:
![[WebRTC视频聊天系统/IMG-20260417144637992.png]]

一、准备工作

1.1 服务器环境

  • 操作系统:CentOS 7.3
  • 必备工具:Node.js、Git(请预先安装)

1.2 客户端环境

  • 推荐浏览器:Firefox(桌面版或手机版)
  • 注意:Chrome浏览器要求HTTPS环境,需要部署SSL证书。本文演示程序仅支持Firefox。

二、安装NAT穿透服务器(ICE Server)

2.1 安装coturn

我们使用coturn来搭建TURN服务器,以支持STUN和TURN两种内网穿透方式。

1
2
3
4
5
6
7
8
# 克隆coturn仓库
git clone https://github.com/coturn/coturn
cd coturn

# 配置、编译并安装
./configure
make
make install

2.2 安装依赖

如果./configure失败,请安装以下依赖:

1
yum install -y openssl openssl-devel libevent-devel

2.3 配置coturn

1
2
# 复制示例配置文件
cp examples/etc/turnserver.conf bin/turnserver.conf

编辑 bin/turnserver.conf 文件,关键配置如下:

1
2
3
4
listening-port=3478
listening-ip=10.214.31.57 # 服务器内网IP
external-ip=118.24.78.34 # 服务器公网IP
user=yubao:000000 # 访问用户名和密码

2.4 启动服务

1
2
cd bin
turnserver -v -r 118.24.78.34:3478 -a -o

2.5 测试ICE服务器

访问 WebRTC官方ICE测试页面,添加你的TURN服务器信息进行测试。
![[WebRTC视频聊天系统/IMG-20260417144638024.png]]

2.6 查看运行日志

1
tail -f /var/log/turn_12447_2018-04-20.log

三、安装信令服务器(Signaling Server)

3.1 安装signalmaster

1
2
3
git clone https://github.com/andyet/signalmaster.git
cd signalmaster
npm install express yetify getconfig node-uuid socket.io

3.2 修改源码以支持TURN认证

signalmaster默认不支持带用户名/密码的TURN服务器,需要修改 sockets.js 第110行附近的代码:

1
2
3
4
5
6
7
8
9
if (!config.turnorigins || config.turnorigins.indexOf(origin) !== -1) {
config.turnservers.forEach(function (server) {
credentials.push({
username: server.username,
credential: server.credential,
urls: server.urls || server.url
});
});
}

3.3 配置信令服务器

修改 config/production.json 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"isDev": true,
"server": {
"port": 8888,
"secure": false
},
"rooms": {
"maxClients": 0
},
"stunservers": [
{ "urls": "stun:stun.ekiga.net:3478" }
],
"turnservers": [
{
"urls": ["turn:qq.openauth.me:3478"],
"username": "yubao",
"credential": "000000",
"expiry": 86400
}
]
}

3.4 启动信令服务器

1
nohup node server.js &

四、部署Web客户端

4.1 获取客户端代码

可以直接使用文末提供的完整HTML代码。

4.2 配置客户端

在客户端代码中,修改信令服务器地址和用户昵称:

1
2
3
4
5
6
7
var webrtc = new SimpleWebRTC({
localVideoEl: 'localVideo',
remoteVideosEl: 'remoteVideos',
autoRequestMedia: true,
url: 'http://qq.openauth.me:8888', // 改为你自己的信令服务器地址
nick: 'yubaolee' // 用户昵称
});

4.3 部署并访问

将客户端文件(HTML、JS等)部署到Nginx或Apache等静态Web服务器。访问地址示例:http://qq.openauth.me:8080/baortc/index.html

五、测试效果

5.1 单用户访问

使用Firefox浏览器访问客户端地址,效果如下:
![[WebRTC视频聊天系统/IMG-20260417144638253.png]]

5.2 多用户视频聊天

使用另一台设备(电脑或手机)的Firefox浏览器访问同一地址,即可看到两个视频窗口,并可以进行文本和视频通信。
![[WebRTC视频聊天系统/IMG-20260417144638274.png]]

总结

通过以上步骤,我们快速搭建了一个包含三个核心组件的WebRTC视频聊天系统:

  1. NAT穿透服务器:基于coturn的TURN服务器。
  2. 信令服务器:基于signalmaster的WebSocket服务器。
  3. Web客户端:基于SimpleWebRTC库的HTML5客户端。

该系统虽然简洁,但完整实现了WebRTC的核心通信功能,可作为进一步开发和优化的基础。


附录:简易客户端完整代码

以下是一个基于SimpleWebRTC库的简易视频聊天室完整代码,复制保存为HTML文件即可使用(需将信令服务器地址url和昵称nick改为你自己的值)。

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
47
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://simplewebrtc.com/latest.js"></script>
<script>
var webrtc = new SimpleWebRTC({
localVideoEl: 'localVideo',
remoteVideosEl: 'remoteVideos',
autoRequestMedia: true,
url: 'http://your-signal-server:8888', // 请修改为你的信令服务器地址
nick: 'YourName' // 请修改为你的昵称
});

webrtc.on('readyToCall', function () {
webrtc.joinRoom('room1');

// 发送文本消息
$('#send').click(function () {
var msg = $('#text').val();
webrtc.sendToAll('chat', { message: msg, nick: webrtc.config.nick });
$('#messages').append('<br>You:<br>' + msg + '\n');
$('#text').val('');
});
});

// 接收文本消息
webrtc.connection.on('message', function (data) {
if (data.type === 'chat') {
$('#messages').append('<br>' + data.payload.nick + ':<br>' + data.payload.message + '\n');
}
});
</script>
<style>
#remoteVideos video, #localVideo {
height: 150px;
}
</style>
</head>
<body>
<textarea id="messages" rows="5" cols="20"></textarea><br />
<input id="text" type="text" />
<input id="send" type="button" value="send" /><br />
<video id="localVideo"></video>
<div id="remoteVideos"></div>
</body>
</html>

说明:SimpleWebRTC是一个优秀的WebRTC封装库,它简化了建立连接、交换信令等复杂流程,并屏蔽了不同浏览器间的API差异,让开发者能更专注于应用逻辑。


参考资料

本文介绍了四种常用的开源弱口令检查工具:SNETCracker、hydra、aircrack-ng和winDecrypt。这些工具分别适用于不同的场景和协议,能够帮助安全研究人员进行弱口令安全审计和密码破解。

1. SNETCracker(超级弱口令检查工具)

1.1 工具简介

SNETCracker是一款开源的Windows平台弱口令安全审计工具,采用C#开发,需要安装.NET Framework 4.0运行环境。该工具支持批量多线程检查,可快速发现弱密码、弱口令账号,支持密码和用户名结合检查,大大提高成功率。

1.2 支持的服务协议

  • SSH、RDP(远程桌面)、VNC
  • MySQL、SQLServer、Oracle、PostgreSQL
  • MongoDB、Memcached、Redis
  • SMTP、POP3、IMAP(支持SSL加密版本)、SVN、FTP、Telnet

1.3 主要特点

  1. 多服务支持:支持多种常见服务的弱口令破解,特别是RDP(3389远程桌面)弱口令检查
  2. 批量处理:支持批量导入IP地址或设置IP段,同时进行多个服务的弱口令检查
  3. 集成功能:程序自带端口扫描功能,无需借助第三方端口扫描工具
  4. 高度可定制:支持自定义检查的口令字典和端口配置

1.4 下载地址

https://github.com/shack2/SNETCracker/releases

1.5 使用演示

测试环境:

  • 目标靶机:Windows XP,IP:192.168.106.138
  • 攻击主机:Windows 11,IP:192.168.106.1

操作步骤:

  1. 打开SNETCracker工具
  2. 配置目标IP地址和端口
  3. 选择要检查的服务类型
  4. 设置用户名和密码字典
  5. 开始扫描并查看结果

结果验证:
成功获取RDP远程桌面账号密码后,可使用该凭证进行远程登录验证。

2. hydra(九头蛇)

2.1 工具简介

Hydra是一款非常强大的暴力破解工具,由著名的黑客组织THC开发并开源。它是一个验证性质的工具,主要目的是展示安全研究人员如何从远程获取系统认证权限。

2.2 支持协议

Hydra支持超过50种协议,主要包括:

  • 网络服务:FTP、HTTP/HTTPS、SMTP、POP3、IMAP
  • 数据库:MySQL、Oracle、PostgreSQL、MS-SQL
  • 远程访问:SSH、Telnet、RDP、VNC
  • 其他:LDAP、SMB、SNMP、SIP等

2.3 基本语法

1
hydra [参数] [IP地址] [服务]

2.4 常用参数

参数 说明
-l login 指定用户名进行破解
-L file 指定用户名字典文件
-p pass 指定密码进行破解
-P file 指定密码字典文件
-e ns 额外选项:n-空密码试探,s-使用指定账户和密码试探
-M file 指定目标IP列表文件,用于批量破解
-o file 指定结果输出文件
-f 找到第一对有效凭证时停止破解
-t tasks 设置同时运行的线程数(默认16)
-v / -V 显示详细过程
-R 恢复中断的破解任务

2.5 使用演示

测试环境:

  • 目标靶机:Windows XP,IP:192.168.106.138
  • 攻击主机:Kali Linux,IP:192.168.106.140

示例命令:

1
2
3
4
5
# 对FTP服务进行暴力破解
hydra -L users.txt -P passwords.txt ftp://192.168.106.138

# 对SSH服务进行破解,使用指定用户
hydra -l admin -P passwords.txt ssh://192.168.106.138

3. aircrack-ng(无线网络破解)

3.1 工具简介

Aircrack-ng是破解WEP/WPA/WPA2加密的主流工具之一,目前在该领域排名第一。该套件包含的工具可用于捕获数据包、握手验证,支持暴力破解和字典攻击。

3.2 硬件要求

  • 支持监听模式的USB无线网卡(推荐使用3370芯片)
  • 网卡需支持双向监听和免驱动

3.3 破解步骤详解

3.3.1 准备工作

  1. 连接支持监听模式的无线网卡到Kali Linux
  2. 检查网卡状态和连接情况

3.3.2 清除干扰进程

1
airmon-ng check kill

此命令会检查并终止可能干扰airmon-ng的进程。

3.3.3 开启监听模式

1
airmon-ng start wlan0

开启后网卡名称可能变为wlan0mon(监控模式)。

3.3.4 扫描附近WiFi

1
airodump-ng wlan0mon

显示信息包括:

  • BSSID:路由器MAC地址
  • PWR:信号强度
  • Data:数据流量(有流量表示有人使用)
  • CH:信道
  • ESSID:WiFi名称

3.3.5 抓取握手包

1
airodump-ng -c [信道] --bssid [MAC地址] -w [保存路径] wlan0mon

示例:

1
airodump-ng -c 1 --bssid 28:6C:07:53:53:80 -w ~/Desktop/cap/2.4G wlan0mon

3.3.6 进行洪水攻击

1
aireplay-ng -0 0 -a [MAC地址] wlan0mon

此命令会使连接该路由器的设备断开连接,在重连过程中抓取握手包。

3.3.7 破解密码

1
aircrack-ng -w [密码字典路径] [抓包文件路径]

示例:

1
aircrack-ng -w ~/Desktop/passwd/408.txt ~/Desktop/cap/2.4G-01.cap

3.3.8 停止攻击

1
airmon-ng stop wlan0mon

4. winDecrypt(PDF密码破解工具)

4.1 工具简介

winDecrypt是一款专门用于移除PDF文档密码保护的工具,支持破解加密的PDF文件,使其可以正常复制、编辑和打印。

4.2 使用步骤

  1. 打开加密PDF:点击”打开PDF”按钮,选择需要解密的PDF文档
  2. 自动解密:工具会自动处理加密的PDF文件
  3. 保存解密文件:系统会弹出另存对话框,选择保存位置即可

4.3 注意事项

  • 该工具仅用于合法用途,如恢复自己忘记密码的PDF文档
  • 请勿用于侵犯他人隐私或违反法律法规的行为

工具对比与选择建议

工具 适用平台 主要用途 特点
SNETCracker Windows 多服务弱口令检查 图形界面,支持批量扫描,集成端口扫描
hydra Linux/Windows 多协议暴力破解 命令行工具,支持协议广泛,功能强大
aircrack-ng Linux 无线网络破解 专业无线安全工具,支持多种加密方式
winDecrypt Windows PDF密码移除 简单易用,专门处理PDF加密

安全与法律声明

合法使用原则

  1. 授权测试:仅在对自有系统或获得明确授权的系统上进行测试
  2. 遵守法律:严格遵守当地法律法规,不得用于非法入侵
  3. 保护隐私:不得侵犯他人隐私和合法权益
  4. 教育目的:这些工具主要用于安全研究和教育目的

安全建议

  1. 强密码策略:使用复杂密码,定期更换
  2. 多因素认证:重要系统启用多因素认证
  3. 访问控制:实施最小权限原则
  4. 安全审计:定期进行安全检查和漏洞扫描

总结

本文介绍的四款开源弱口令检查工具各有特色,适用于不同的安全测试场景。SNETCracker适合Windows环境下的多服务批量检查,hydra是功能全面的命令行破解工具,aircrack-ng专注于无线网络安全,winDecrypt则专门处理PDF加密问题。

安全研究人员应根据实际需求选择合适的工具,并始终遵守法律法规和职业道德,将这些工具用于合法的安全测试和教育目的。同时,系统管理员也应了解这些工具的工作原理,以便更好地防御潜在的安全威胁。

一、概述

远程桌面连接是系统管理和日常运维中的重要工具。通常,我们使用VNC远程连接Linux系统,使用Windows自带的远程桌面连接Windows系统。本文将详细介绍如何在Linux中远程连接Windows,以及在Windows中远程连接Linux,实现跨平台远程桌面访问。

本文以CentOS 6.6为例,其他Linux发行版的操作类似,可根据实际情况调整。

二、Windows远程桌面连接Linux

2.1 环境准备

检查GNOME桌面环境

1
2
# 检查是否已安装GNOME桌面
rpm -qa | grep gnome-desktop

说明:如果查询结果中未显示gnome-desktop,则需要安装GNOME图形界面。

安装GNOME桌面环境

1
2
# 安装GNOME桌面环境
yum -y groupinstall "X Window System" "Chinese Support" "Desktop"

2.2 安装和配置VNC Server

安装VNC Server

1
2
# 安装VNC Server
yum -y install tigervnc-server

配置VNC Server

1
2
3
4
5
6
7
8
# 复制配置文件模板
cp /usr/share/doc/tigervnc-server-1.1.0/example-config/vncserver@.service /etc/systemd/system/

# 编辑配置文件
vim /etc/systemd/system/vncserver@.service

# 修改配置文件中的用户设置
# 将<USER>替换为实际用户名

设置VNC密码

1
2
# 设置VNC连接密码
vncpasswd

2.3 配置防火墙

1
2
3
4
5
6
# 放行VNC端口(5900-5905)和RDP端口(3389)
iptables -I INPUT -p tcp --dport 5900:5905 -j ACCEPT
iptables -I INPUT -p tcp --dport 3389 -j ACCEPT

# 保存防火墙规则
service iptables save

注意:实际放行的VNC端口需要根据配置进行调整。

2.4 安装xrdp服务

安装EPEL仓库

1
2
# 安装EPEL仓库
yum -y install epel-release

安装xrdp

1
2
# 安装xrdp
yum -y install xrdp

配置xrdp

1
2
3
4
5
# 编辑xrdp配置文件
vim /etc/xrdp/xrdp.ini

# 修改显示设置
max_bpp=32

启动xrdp服务

1
2
3
4
5
# 设置xrdp开机自启
chkconfig xrdp on

# 启动xrdp服务
service xrdp start

离线安装选项:如需离线安装,可访问http://www.rpmfind.net/下载适合系统的xrdp RPM包,然后使用以下命令安装:

1
rpm -ivh xrdp-0.6.1-5.el6.x86_64.rpm

2.5 测试连接

在Windows系统中使用远程桌面连接工具,输入Linux服务器的IP地址进行连接测试。

三、Linux远程桌面连接Windows

3.1 环境准备

  1. Linux端:确认已安装GNOME图形桌面环境
  2. Windows端:配置允许远程桌面连接
  3. 防火墙配置:在Windows防火墙中放行远程桌面端口(默认3389)

3.2 安装远程连接工具

1
2
3
# 安装远程桌面客户端
yum -y install rdesktop
yum -y install tsclient

3.3 配置连接

  1. 在Linux系统中打开应用程序Internet终端服务器客户机
  2. 点击Add Connection,选择Windows Terminal Service
  3. 填写连接信息:
    • Name:连接名称
    • Host:Windows主机IP地址
    • Username:Windows用户名
    • Password:Windows密码

注意:如果Windows远程桌面端口不是默认的3389,填写格式为IP:端口

3.4 建立连接

保存配置后,在主界面双击刚才保存的会话配置即可建立连接。

四、Linux远程桌面连接Linux

4.1 环境准备

  1. 本地Linux:确认已安装GNOME图形桌面环境
  2. 远程Linux:安装并配置VNC Server
  3. 防火墙配置:在远程Linux防火墙中放行VNC端口(默认5900+连接ID)

4.2 安装远程连接工具

1
2
3
# 安装VNC客户端
yum -y install vnc
yum -y install tsclient

4.3 配置连接

  1. 在Linux系统中打开应用程序Internet终端服务器客户机
  2. 点击Add Connection,选择VNC
  3. 填写连接信息:
    • Name:连接名称
    • Host:远程Linux主机IP地址和连接ID(格式:IP:连接ID
    • Password:VNC密码

4.4 建立连接

保存配置后,在主界面双击刚才保存的会话配置即可建立连接。

五、全屏模式退出方法

5.1 Linux远程Linux

  • 按下F8键,弹出VNC菜单
  • 点击”Exit Viewer”或取消”Full Screen”选项

5.2 Linux远程Windows

  • 使用组合键Ctrl + Alt + Enter退出全屏模式

六、常见问题与解决方案

6.1 登录失败错误

问题现象:输入正确的用户名和密码,但登录时提示”xrdp_mm_process_login_response: login failed”

查看日志

1
vim /var/log/xrdp-sesman.log

日志内容

1
2
3
4
[20181107-09:17:36] [INFO ] scp thread on sck 7 started successfully
[20181107-09:17:36] [INFO ] ++ created session (access granted): username root, ip x.x.x.x:57714 - socket: 7
[20181107-09:17:36] [INFO ] starting Xvnc session...
[20181107-09:17:36] [ERROR] X server -- no display in range is available

原因分析:超过最大连接数限制

6.2 解决方案

方案一:清理现有连接并调整配置

1
2
3
4
5
6
7
8
9
10
11
# 清理现有xrdp进程
ps -ef | grep xrdp | grep -v grep | awk '{print $2}' | xargs kill -9

# 编辑配置文件
vim /etc/xrdp/sesman.ini

# 修改最大会话数
MaxSessions=100

# 重启服务
service xrdp restart

方案二:优化会话管理配置

1
2
3
4
5
6
7
8
9
10
# 编辑配置文件
vim /etc/xrdp/sesman.ini

# 修改以下配置
MaxSessions=10
KillDisconnected=1
IdleTimeLimit=1800

# 重启服务
service xrdp restart

七、安全建议

7.1 端口安全

  1. 修改默认端口,避免使用3389和5900等常见端口
  2. 使用防火墙限制访问源IP
  3. 定期检查端口扫描和异常连接

7.2 认证安全

  1. 使用强密码策略
  2. 考虑使用SSH隧道加密连接
  3. 定期更换连接密码

7.3 会话管理

  1. 设置合理的会话超时时间
  2. 及时清理闲置会话
  3. 监控异常登录行为

八、总结

通过本文介绍的方法,可以实现Windows与Linux系统之间的相互远程桌面连接,为跨平台系统管理提供了便利。关键要点包括:

  1. Windows连接Linux:需要安装xrdp和VNC Server
  2. Linux连接Windows:使用rdesktop或tsclient工具
  3. Linux连接Linux:使用VNC客户端
  4. 故障排除:关注连接数限制和配置参数

在实际使用中,建议根据具体需求和安全要求,调整配置参数和连接方式,确保远程连接的安全性和稳定性。


注意事项

  1. 本文基于CentOS 6.6编写,其他版本可能需要调整命令
  2. 生产环境中建议使用更安全的连接方式,如SSH隧道
  3. 定期更新相关软件包,修复安全漏洞
0%