0%

方法一:采用正则表达式获取地址栏参数:( 强烈推荐,既实用又方便!)

function GetQueryString(name)

{

var reg = new RegExp(``"(^|&)"``+ name +``"=([^&]*)(&|$)"``);

var r = window.location.search.substr(1).match(reg);

if``(r!=``null``)``return  unescape(r[2]); return null``;

}

alert(GetQueryString(``"参数名1"``));

alert(GetQueryString(``"参数名2"``));

alert(GetQueryString(``"参数名3"``));

下面举一个例子:

若地址栏URL为:abc.html?id=123&url=http://www.maidq.com

那么,但你用上面的方法去调用:alert(GetQueryString(“url”));

则会弹出一个对话框:内容就是 http://www.maidq.com

如果用:alert(GetQueryString(“id”));那么弹出的内容就是 123 啦;

当然如果你没有传参数的话,比如你的地址是 abc.html 后面没有参数,那强行输出调用结果有的时候会报错:

所以我们要加一个判断 ,判断我们请求的参数是否为空,首先把值赋给一个变量:

var myurl=GetQueryString(``"url"``);

if``(myurl !=``null && myurl.toString().length>1)

{

alert(GetQueryString(``"url"``));

}

这样就不会报错了!

方法二:传统方法

比如说把这个代码存为1.html

那么我要访问1.html?id=test

这个时候就取到test的值了

在html里调用

sadfsdfas

stringvar.substr(start [, length ]

返回一个从指定位置开始的指定长度的子字符串。

stringvar

必选项。要提取子字符串的字符串文字或 String 对象。

start

必选项。所需的子字符串的起始位置。字符串中的第一个字符的索引为 0。

length

可选项。在返回的子字符串中应包括的字符个数。

如果 length 为 0 或负数,将返回一个空字符串。如果没有指定该参数,则子字符串将延续到 stringvar 的最后。

下面列举出一些相关的参数:

str.toLowerCase()   转换成小写  
str.toUpperCase()   字符串全部转换成大写

URL即:统一资源定位符 (Uniform Resource Locator, URL)
完整的URL由这几个部分构成:
scheme://host:port/path?query#fragment
scheme:通信协议
常用的http,ftp,maito等

host:主机
服务器(计算机)域名系统 (DNS) 主机名或 IP 地址。

port:端口号
整数,可选,省略时使用方案的默认端口,如http的默认端口为80。

path:路径
由零或多个”/“符号隔开的字符串,一般用来表示主机上的一个目录或文件地址。

query:查询
可选,用于给动态网页(如使用CGI、ISAPI、PHP/JSP/ASP/ASP.NET等技术制作的网页)传递参数,可有多个参数,用”&”符号隔开,每个参数的名和值用”=”符号隔开。

fragment:信息片断
字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。(也称为锚点.)

对于这样一个URL

http://www.maidq.com/index.html?ver=1.0&id=6#imhere

我们可以用javascript获得其中的各个部分
1, window.location.href
整个URl字符串(在浏览器中就是完整的地址栏)
本例返回值: http://www.maidq.com/index.html?ver=1.0&id=6#imhere

2,window.location.protocol
URL 的协议部分
本例返回值:http:

3,window.location.host
URL 的主机部分
本例返回值:www.maidq.com

4,window.location.port
URL 的端口部分
如果采用默认的80端口(update:即使添加了:80),那么返回值并不是默认的80而是空字符
本例返回值:””

5,window.location.pathname
URL 的路径部分(就是文件地址)
本例返回值:/fisker/post/0703/window.location.html

6,window.location.search
查询(参数)部分
除了给动态语言赋值以外,我们同样可以给静态页面,并使用javascript来获得相信应的参数值
本例返回值:?ver=1.0&id=6

7,window.location.hash
锚点
本例返回值:#imhere

一、前言

这篇文章本来是继续分享IdentityServer4 的相关文章,由于之前有博友问我关于微服务相关的问题,我就先跳过IdentityServer4的分享,进行微服务相关的技术学习和分享。微服务在我的分享目录里面是放到四月份开始系列文章分享的,这里就先穿越下,提前安排微服务应用的开篇文章 电商系统升级之微服务架构的应用
本博客以及公众号坚持以架构的思维来分享技术,不仅仅是单纯的分享怎么使用的Demo

二、场景

先来回顾下我上篇文章 Asp.Net Core 中IdentityServer4 授权中心之应用实战 中,电商架构由单体式架构拆分升级到多网关架构

升级之前

升级之后:

然而升级之后问题又来了,由于之前增加了代理商业务并且把授权中心支付网关单独拆出来了,这使得公司的业务订单量翻了几十倍,这个时候整个电商系统达到了瓶颈,如果再不找解决方案系统又得宕机了。

2.1 问题及解决方案

经过技术的调研及问题分析,导致这个瓶颈的问题主要有以下几个原因,只需要把下面问题解决就可以得到很大的性能提升

  • 每天的订单量暴增,导致订单数据太大,然而整个电商系统数据存储在一个数据库中,并且是单表单数据库(未进行读写分离),以致于订单数据持续暴增。
  • 相关业务需要依赖订单查询,订单数据查询慢以至于拖垮数据库
  • 整个电商系统连接数达到瓶颈(已经分布式部署,在多加服务器会损耗更多的经费而达不到最佳性价比)

为了一劳永逸的解决以上问题,经过技术的调研,决定对订单业务做如下升级改造:

  • 拆分独立的订单微服务(本章节着重分享)
  • 使用ES进行数据迁移(按年进行划分,并且进行读写分离,这里就不着重讲,下次来跟大家一起学习和分享)
  • 增加分布式缓存 (也不是本次的重点,后续再来跟大家学习和分享)

经过升级后的架构图如下:

架构图说明:

  • 右边同一颜色的代表还是原先电商系统的单体式架构,为拆分的单体架构业务,其中在业务处理上夹杂了一层分布式缓存的处理
  • 左边的是微服务的架构,是这次升级拆分后的架构,其中数据库也已经从原有的数据库拆分并且数据迁移到了ES集群中,并进行了读写分离。
  • 订单服务可以随意扩容成分布式服务,通过一些工具动态扩展服务及服务器的支持。
  • 右边的业务后续也可以进行拆分,拆分成不同的业务服务。
  • 后续升级还可以考虑消息队列等相关方面,架构图中未构思(后续再来分享升级用到的相关技术,这里还是回归到本文的核心微服务

三、微服务概述

微服务的相关概念我就不多说了,以下就先简单概况下微服务带来的利和弊。

3.1 微服务的优势

  • 使大型的复杂应用程序可以持续交付和持续部署:持续交付和持续部署是DevOps的一部分,DevOps是一套快速、频繁、可靠的软件交付实践。高效的DevOps组织通常将软件部署到生产环境时面临更少的问题和故障。DevOps工具有DockerKubernetsJenkinsGit等。
  • 每个服务相对较小并容易维护:微服务架构相比单体应用要小的多,开发者理解服务中的逻辑代码更容易。代码库小,打包,启动服务速度也快。
  • 服务可以独立部署:每个服务都可以独立于其他服务进行部署
  • 服务可以独立扩展:服务可以独立扩展,不论是采用X轴扩展的实例克隆,还是Z轴的流量分区方式。此外每个服务都可以部署到适合它们需求的硬件之上
  • 微服务架构可以实现团队的自治:可以根据服务来把开发团队拆分。每个团队都有自己负责的微服务,而不用关心不属于他们负责的服务。
  • 更容易实验和采纳新的技术:最后,微服务可以消除对某个技术栈的长期依赖。因为服务更小,使用更换的编程语言和技术来重写一项服务变得有可能,这也意味着,对一项新技术尝试失败后,可以直接丢弃这部分工作而不至于给整个应用带来失败的风险。
  • 更好的容错性:微服务架构也可以实现更换的故障隔离。例如,某个服务引发的致命错误,不会影响其他服务。其他服务仍然正常运行。
  • 服务可以独立扩容:对于整个架构来说,可以随意选择相关业务进行扩容和负载,通过相关技术工具动态进行随意扩容

3.2 微服务的劣势

  • 服务拆分和定义是一项挑战:采用微服务架构首当其冲的问题,就是根本没有一个具体的、良好定义的算法可以完成服务的拆分工作。与软件开发一样,服务的拆分和定义更像一门艺术。更糟糕的是,如果对系统的服务拆分出现了偏差,很有可能会构建出一个分布式的单体应用;一个包含了一大堆互相之间紧耦合的服务,却又必须部署在一起的所谓分布式系统。这将会把单体架构和微服务架构两者的弊端集于一身。
  • 分布式系统带来的各种复杂性、使开发、测试和部署变得更困难:使用微服务架构的另一个问题是开发人员必须处理创建分布式系统的额外复杂性。服务必须是进程间通信。这比简单的方法调用要复杂的多。
  • 当部署跨越多个服务的功能时需要谨慎地协调更多的开发团队:使用微服务架构的另外一项挑战在于当部署跨越多个服务的功能时需要谨慎地协调更多开发团队。必须制定一个发布计划,把服务按照依赖关系进行排序。这跟单体架构下部署多个组件的方式截然不同。
  • 开发者需要思考到底应该在应用的什么阶段使用微服务架构:使用微服务架构的另一个问题是决定在应用程序生命周期的哪个阶段开始使用这种架构。
  • 跨服务数据的问题:在单体应用中,所有的数据都在一个数据库中,而在微服务架构中,每个服务都有自己的数据库,想要获取,操作其他服务的数据,只能通过该服务提供API进行调用,这样就带来一个问题,进程通信的问题,如果涉及到事务,那么还需要使用Saga来管理事务,增加了开发的难度。

3.3 微服务拆分原则

说到单体架构拆分,那也不是随意拆分,是要有一定的原则,拆分的好是优势,拆分的不好是混乱。以下是我查阅资料以及我的经验总结出来的拆分原则

  • 1、单一职责、高内聚低耦合
  • 2、微服务粒度适中
  • 3、考虑团队结构
  • 4、以业务模型切入
  • 5、演进式拆分
  • 6、避免环形依赖与双向依赖
  • 7、DDD(可以考虑使用领域驱动设计去进行底层服务的设计,后续会单独分析该设计的相关文章)

四、微服务实战

好了,到这里大家已经对微服务有了一定的理解,就不继续详细概述相关理念的东西,下面来直接撸代码,让大家熟悉微服务的应用。这里我使用 莫堇蕈 在github 上开源的微服务框架,框架源代码地址 :https://github.com/overtly/core-grpc我这里强烈推荐该框架,目前已经比较成熟的用于公司生产环境

为了更好的维护开源项目以及技术交流,特意创建了一个交流群,群号:1083147206 有兴趣者开源加入交流

4.1 core-grpc 微服务框架的优势:

  • 集成Consul 实现服务发现和注册以及健康检查等机制
  • 实时监听服务状态
  • 多节点 轮询机制
  • 故障转移,拉入黑名单
  • 支持.Net Core 和Framework 两种框架
  • 实现基于Grpc的微服务
  • 部署支持环境变量

4.2 实战

创建Jlion.NetCore.OrderService 订单微服务

我们用vs2019 创建控制台应用程序 选择框架.Net Core 3.1 命名为Jlion.NetCore.OrderService 后面简称订单服务,创建完后我们通过nuget包引入 core-grpc微服务框架,如下图:

目前core-grpc微服务框架,最新正式发布版本是 1.0.3

引用了core-grpc 后我们还需要安装一个工具VS RPC Menu,这个工具也是大神免费提供的,图片如下:

由于微软官方下载比较慢,我这里共享到 百度网盘,百度网盘下载地址如下:

链接: https://pan.baidu.com/s/1twpmA4_aErrsg-m0ICmOPw 提取码: cshs

如果通过下载后安装不是vs 集成安装方式,下载完成后需要关闭vs 2019相关才能正常安装。

VS RPC Menu 工具说明如下:

OrderRequest.proto代码如下:

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
syntax = "proto3";
package Jlion.NetCore.OrderService.Service.Grpc;


//定义订单查找参数实体
message OrderSearchRequest{
string OrderId = 1; //定义订单ID
string Name = 2;
}

//定义订单实体
message OrderRepsonse{
string OrderId = 1;
string Name = 2;
double Amount = 3;
int32 Count = 4;
string Time = 5;
}

//定义订单查找列表
message OrderSearchResponse{
bool Success = 1;
string ErrorMsg = 2;
repeated OrderRepsonse Data = 3;
}

上面主要是定义了几个消息实体,
我们再创建JlionOrderService.proto,代码如下:

1
2
3
4
5
6
7
8
9
syntax = "proto3";
package Jlion.NetCore.OrderService.Service.Grpc;

import "OrderRequest.proto";

service JlionOrderService{
rpc Order_Search(OrderSearchRequest) returns (OrderSearchResponse){}
}

上面的代码中都可以看到最上面有 package Jlion.NetCore.OrderService.Service.Grpc 代码,这是声明包名也就是后面生成代码后的命名空间,这个很重要
同时定义了JlionOrderService服务入口,并且定义了一个订单搜索的方法Order_Search,到这里我们已经完成了一小部分了。

生成客户端代码

再在JlionOrderService.proto文件里面右键 》选择Grpc代码生成》Grpc 代码 会自动生存微服务客户端代码 。
生存工具中具有如下功能:

  • 生存Grpc客户端代码
  • Grpc 编译(不常用)
  • Grpc 打包(常用,用来把客户端dll发布到nuget服务器上)
  • 还可以对Thrift 代码进行生成和打包

创建Jlion.NetCore.OrderService.Grpc 类库

把刚刚通过工具生成的Grpc客户端代码直接copy到 Jlion.NetCore.OrderService.Grpc这个类库中(必须和上面Grpc 的代码声明的package 一致)以下简称订单服务客户端,并且需要通过Nuget包添加Overt.Core.Grpc 的依赖,代码结构如下:

Jlion.NetCore.OrderService.Grpc类库已经构建完成,现在让 Jlion.NetCore.OrderService 服务引用Jlion.NetCore.OrderService.Grpc 类库

订单服务中 实现自己的IHostedService

创建HostService类,继承IHostedService代码如下:

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
public class HostedService : IHostedService
{
readonly ILogger _logger;
readonly JlionOrderServiceBase _grpcServImpl;
public HostedService(
ILogger<HostedService> logger,
JlionOrderServiceBase grpcService)
{
_logger = logger;
_grpcServImpl = grpcService;
}


public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
var channelOptions = new List<ChannelOption>()
{
new ChannelOption(ChannelOptions.MaxReceiveMessageLength, int.MaxValue),
new ChannelOption(ChannelOptions.MaxSendMessageLength, int.MaxValue),
};
GrpcServiceManager.Start(BindService(_grpcServImpl), channelOptions: channelOptions, whenException: (ex) =>
{
_logger.LogError(ex, $"{typeof(HostedService).Namespace.Replace(".", "")}开启失败");
throw ex;
});
System.Console.WriteLine("服务已经启动");
_logger.LogInformation($"{nameof(Jlion.NetCore.OrderService.Service).Replace(".", "")}开启成功");
}, cancellationToken);
}


public Task StopAsync(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
GrpcServiceManager.Stop();

_logger.LogInformation($"{typeof(HostedService).Namespace.Replace(".", "")}停止成功");
}, cancellationToken);
}
}

上面代码主要是创建宿主机并且实现了StartAsync 服务启动及StopAsync 服务停止方法。
我们创建完HostedServicce代码再来创建之前定义的Grpc服务的方法实现类JlionOrderServiceImpl,代码如下:

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
public partial class JlionOrderServiceImpl : JlionOrderServiceBase
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;

public JlionOrderServiceImpl(ILogger<JlionOrderServiceImpl> logger, IServiceProvider provider)
{
_logger = logger;
_serviceProvider = provider;
}

public override async Task<OrderSearchResponse> Order_Search(OrderSearchRequest request, ServerCallContext context)
{



var response = new OrderSearchResponse();
try
{
response.Data.Add(new OrderRepsonse()
{
Amount = 100.00,
Count = 10,
Name = "订单名称测试",
OrderId = DateTime.Now.ToString("yyyyMMddHHmmss"),
Time = DateTime.Now.ToString()
});

response.Data.Add(new OrderRepsonse()
{
Amount = 200.00,
Count = 10,
Name = "订单名称测试2",
OrderId = DateTime.Now.ToString("yyyyMMddHHmmss"),
Time = DateTime.Now.ToString()
});

response.Data.Add(new OrderRepsonse()
{
Amount = 300.00,
Count = 10,
Name = "订单名称测试2",
OrderId = DateTime.Now.ToString("yyyyMMddHHmmss"),
Time = DateTime.Now.ToString()
});
response.Success = true;
}
catch (Exception ex)
{
response.ErrorMsg = ex.Message;
_logger.LogWarning("异常");
}
return response;
}
}

再修改Program代码,并把HostedServiceJlionOrderServiceImpl 注入到容器中,代码如下:

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
class Program
{
static void Main(string[] args)
{
var host = new HostBuilder()
.UseConsoleLifetime()
.ConfigureAppConfiguration((context, configuration) =>
{
configuration
.AddJsonFile("appsettings.json", optional: true)
.AddEnvironmentVariables();
})
.ConfigureLogging(logger =>
{
logger.AddFilter("Microsoft", LogLevel.Critical)
.AddFilter("System", LogLevel.Critical);
})
.ConfigureServices(ConfigureServices)
.Build();

AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
var logFactory = host.Services.GetService<ILoggerFactory>();
var logger = logFactory.CreateLogger<Program>();
logger.LogError(e.ExceptionObject as Exception, $"UnhandledException");
};

host.Run();
}






private static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{

services.AddSingleton<IHostedService, HostedService>();
services.AddTransient<JlionOrderServiceBase, JlionOrderServiceImpl>();
}
}

到了这里简单的微服务已经编码完成,但是还缺少两个配置文件,我们创建appsettings.json配置文件和consulsettings.json 服务注册发现的配置文件
consulsettings.json配置文件如下:

1
2
3
4
5
6
7
{
"ConsulServer": {
"Service": {
"Address": "127.0.0.1:8500"
}
}
}

上面的地址配置只是简单的例子,我这里假定我的Consul服务地址是 127.0.0.1:8500 等下服务启动是会通过这个地址进行注册。

appsettings.json配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
{
"GrpcServer": {
"Service": {
"Name": "JlionOrderService",
"Port": 10001,
"HostEnv": "serviceaddress",
"Consul": {
"Path": "dllconfigs/consulsettings.json"
}
}
}
}

我这里服务监听了10001 端口,后面注册到Consul中也会看到该端口
官方完整的配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"GrpcServer": {
"Service": {
"Name": "OvertGrpcServiceApp",
"Host": "service.g.lan",
"HostEnv": "serviceaddress",
"Port": 10001,
"Consul": {
"Path": "dllconfigs/consulsettings.json"
}
}
}
}

好了,订单服务已经全部完成了,订单服务服务整体结构图如下:

好了,我们这里通过命令行启动下JlionOrderService服务,生产环境你们可以搭建在Docker 容器里面

我们可以来看下我之前搭建好的Consul服务 ,打开管理界面,如图:

图片中可以发现刚刚启动的服务已经注册进去了,但是里面有一个健康检查未通过,主要是由于服务端不能访问我本地的订单服务,所有健康检查不能通过。你可以在你本地搭建 Consul服务用于测试。

我本地再来开启一个服务,配置中的的端口号由10001 改成10002,再查看下Consul的管理界面,如下图:

发现已经注册了两个服务,端口号分别是10001 和10002,这样可以通过自定化工具自动添加服务及下架服务,分布式服务也即完成。
到这里订单服务的启动已经完全成功了,我们接下来是需要客户端也就是上面架构图中的电商业务网关或者支付网关等等要跟订单服务进行通讯了。

创建订单网关(跟订单服务进行通信)

创建订单网关之前我先把上面的 订单服务客户端 类库发布到我的nuget包上,这里就不演示了。我发布的测试包名称JlionOrderServiceDemo nuget官方可以搜索找到。你们也可以直接搜索添加到你们的Demo中进行测试。
我通过VS 2019 创建Asp.Net Core 3.1 框架的WebApi 取名为Jlion.NetCore.OrderApiService 下面简称订单网关服务
现在我把前面发布的微服务客户端依赖包 JlionOrderServiceDemo 添加到订单网关服务中,如下图:

现在在订单网关服务中添加OrderController api控制器,代码如下:

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
namespace Jlion.NetCore.OrderApiService.Controllers
{
[Route("[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
private readonly IGrpcClient<OrderService.Service.Grpc.JlionOrderService.JlionOrderServiceClient> _orderService;
public OrderController (IGrpcClient<OrderService.Service.Grpc.JlionOrderService.JlionOrderServiceClient> orderService)
{
_orderService = orderService;
}

[HttpGet("getlist")]
public async Task<List<OrderRepsonse>> GetList()
{
var respData =await _orderService.Client.Order_SearchAsync(new OrderService.Service.Grpc.OrderSearchRequest()
{
Name = "test",
OrderId = "",
});

if ((respData?.Data?.Count ?? 0) <= 0)
{
return new List<OrderRepsonse>();
}

return respData.Data.ToList();
}
}
}

代码中通过构造函数注入 OrderService 并且提供了一个GetList的接口方法。接下来我们还需要把OrderService.Service.Grpc.JlionOrderService注入到容器中,代码如下:

1
2
3
4
5
6
7
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();


services.AddGrpcClient();
}

现在整个订单网关服务项目结构如下图:

项目中有两个最重要的配置dllconfig//Jlion.NetCore.OrderService.Grpc.dll.jsonconsulsettings.json 他们分别是干什么的呢?我们先分别来看我本地这两个配置的内容
Jlion.NetCore.OrderService.Grpc.dll.json 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"GrpcClient": {
"Service": {
"Name": "JlionOrderService",
"MaxRetry": 0,
"Discovery": {
"Consul": {
"Path": "dllconfigs/consulsettings.json"
},
"EndPoints": [
{
"Host": "127.0.0.1",
"Port": 10001
}]
}
}
}
}

Jlion.NetCore.OrderService.Grpc.dll.json 配置主要是告诉订单网关服务订单服务应该怎样进行通信,以及通信当中的一些参数配置。我为了测试,本地使用单点模式,不使用Consul模式
consulsettings.json 配置如下:

1
2
3
4
5
6
7
{
"ConsulServer": {
"Service": {
"Address": "127.0.0.1:8500"
}
}
}

有没有发现这个配置和之前服务端的配置一样,主要是告诉订单网关服务(客户端调用者)和订单服务服务端服务发现的集群地址,如果上面的配置是单点模式则这个配置不会起作用。

到这里订单网关服务 (客户调用端)编码完成,我们开始启动它:

我这里固定5003端口,现在完美的启动了,我们访问下订单接口,看下是否成功。访问结果如下图:

微服务完美的运行成功。

上面的构建微服务还是比较麻烦,官方提供了比较快速构建你需要的微服务方式,不需要写上面的那些代码,那些代码全部通过模板的方式进行构建你的微服务,有需要学习的可以到点击
微服务项目构建模板使用教程
教程地址:https://www.cnblogs.com/jlion/p/12494525.html

文章中的Demo 代码已经提交到github 上,代码地址:https://github.com/a312586670/NetCoreDemo
微服务框架开源项目地址:https://github.com/overtly/core-grpc

矩阵的坐标变换(转) - Danny Chen - 博客园

Excerpt

http://learn.gxtc.edu.cn/NCourse/jxcamcad/cadcam/Mains/main11-2.htm 2.3.3 基本二维变换 基本二维变换有比例变换(Scaling)、旋转变换(Rotating)、错切变换(Shearing)和平移变换(Translating)


http://learn.gxtc.edu.cn/NCourse/jxcamcad/cadcam/Mains/main11-2.htm

2.3.3 基本二维变换 
    基本二维变换有比例变换(Scaling)、旋转变换(Rotating)、错切变换(Shearing)和平移变换(Translating)。 
1)比例变换 
比例变换就是将平面上任意一点的横坐标放大或缩小S11倍,纵坐标放大或缩小S22倍,即 
image 
其中S称为比例变换矩阵。图2.24是比例变换的几个例子。图中(b)是S11=S22的情况,(C)是S11≠S21的情况 
image

2)旋转变换 
    旋转变换就是将平面上任意一点绕原点旋转θ角,一般规定逆时针方向为正,顺时针方向为负。从图2.25可推出变换公式: 
image 
3)错切变换 
    在旋转变换矩阵中,非对角线元素有何几何意义?观察图2.26中的例子。变换矩阵中元素S21起作把图形沿X方向“错切”的作用,Y值越小,错切量越小。S12则有将图形向Y方向“错切”的作用,同样其作用的大小与X值成正比。 
image 
4)平移变换 
    平移交换指的是将平面上任意一点沿X方向移动C。,沿Y方向移动ty(图2.27),其变换公式为 
image 
由上式可见,平移交换不能直接用2X2矩阵来表示。下述齐次坐标变换矩阵则可解决这个问题。

注意:这句话关键(疑问点在于为什么二位转换需要3x3的矩阵)

2.3.4 齐次坐标 
    如把平面上的点P=[Xy]放到空间去表示为[X Y H],使得x= X/H, y=Y/H 则称[X Y H」是点 P的齐次坐标。如规定齐次坐标的第三个分量H必须是 1,则称为规范齐次坐标。P=[xy」的规范齐次坐标是[x y 1]。显然,二维空间中描述的点与齐次坐标空间描述的点是一对多的关系。使用齐次坐标之后,平移交换可用矩阵乘法表示如下:

image

注意:现在可以看到平移的时候x1=x*1+x*0+x*tx,y1=y*0+y*1+y*ty即等于相加的做法,现在所有的转换都可以使用矩阵乘法了

2.3.5 复合变换 

    实际问题中常遇到的是较为复杂的变换,但这些均可通过一系列的基本变换复合而成。下面举例说明。 
例1 绕任意点C=[Cx Cy]的旋转变换。图2.28总的变换可通过三个基本变换复合而成。先进行平移交换,平移量为-Cx和-Cy,然后绕原点旋转θ角,最后再进行平移量为Cx和Cy的平移变换。因此,任一点P经过逐次变换后的齐次坐标为

image

image

变换矩阵称为复合变换矩阵。

例 2相对于任意点 C=[Cx Cy]的比例变换

 image 
与例1其复合变换阵三个变换复合而成。即为  
image 
由上述计算过程知,一个简单比例变换需要有三个计算步骤。对第一次平移,可看成是将变换物移动到坐标系的原点,第二次平移则可看成将变换物移回原位。 
例3 相对于直线 ax+by+c=0 进行对称变换  
image 
此例可由五个基本变换复合而成,复合变换矩阵可按下式进行计算  
image  
image  
image

【荐】牛逼的WPF动画库:XamlFlair-CSDN博客

Excerpt

文章浏览阅读3.5k次。【荐】牛逼的WPF动画库:XamlFlair原文链接:https://github.com/XamlFlair/XamlFlair翻译:沙漠尽头的狼(本文未全文翻译,建议阅读原文了解更多)..._wpf动画库xamlflair


【荐】牛逼的WPF动画库:XamlFlair

原文链接:https://github.com/XamlFlair/XamlFlair

翻译:沙漠尽头的狼(本文未全文翻译,建议阅读原文了解更多)

XamlFlair

XamlFlair库的目标是简化常见动画的实现,并允许开发人员使用几行Xaml轻松地添加单个或组合的动画集。

展示

Sekuence Puzzle Game[1]

|

支持作者

如果你想用一些咖啡来支持我的工作,你可以在这里做:给我买杯咖啡[2]。你的帮助让我有动力继续花时间在这个项目上,并继续维护和更新它的新功能。提前谢谢!

内容

  • Install from Nuget[3]

  • Features Overview[4]

  • Basic Concepts[5]

  • Usage[6]

  • Base Animation Types[7]

  • Color Animations (*WPF And Uno Only*)[8]

  • Overriding the Global Default Values[9]

  • Using a `ResourceDictionary` for Base Settings[10]

  • Default Animations (*WPF Only*)[11]

  • `TransformOn` Property (*WPF Only*)[12]

  • Perspective Rotations (*UWP Only*)[13]

  • Combining Animations[14]

  • Overriding Values[15]

  • Relative Translations on X and Y Axes[16]

  • Compound Animations[17]

  • Repeating Animations[18]

  • Events and Bindings[19]

  • Primary and Secondary Completion Commands[20]

  • Using the `StartWith` Property[21]

  • Using the `AllowOpacityReset` Property (*WPF Only*)[22]

  • Using the `ClipToBounds` Property (*UWP And Uno Only*)[23]

  • Debugging Animations[24]

  • Logging Animations[25]

  • `ListViewBase` (_UWP and Uno_) and `ListBox`-based (_WPF_) Animations[26]

Nuget中下载

Platform Package NuGet
UWP [XamlFlair.UWP][UWPNuGet] [![UWPNuGetShield]][UWPNuGet]
WPF [XamlFlair.WPF][WPFNuGet] [![WPFNuGetShield]][WPFNuGet]
Uno [XamlFlair.Uno][UNONuGet] [![UNONuGetShield]][UNONuGet]

使用以下命令从Package Manager Console下载XamlFlair:

UWP:

1
Install-Package XamlFlair.UWP

您的应用程序必须至少针对Windows 10版本1809(内部版本17763)

WPF:

1
Install-Package XamlFlair.WPF

Uno:

1
Install-Package XamlFlair.Uno

您的UWP应用程序必须至少针对Windows 10版本1809(构建18362)

功能概述(Features Overview)

Feature UWP WPF UWP (Uno) iOS (Uno) Android (Uno) Wasm (Uno) EXPERIMENTAL
Animation System Composition Storyboards Storyboards Storyboards Storyboards Storyboards
Transform Type N/A TransformGroup CompositeTransform CompositeTransform CompositeTransform CompositeTransform
DefaultAnimations.xaml - - - - -
TransformOn - - - - -
Compound Animations
Relative Translations
Repeating Animations
Events & Bindings
Primary/Secondary Completion Commands
StartWith
AllowOpacityReset - - - - -
ClipToBounds N/A
Animated Lists
Blur Effect - - - -
Saturation Effect - - - - -
Tint Effect - - - - -
Color Animations -
Perspective Rotations (Swivel) - - - - -
Debugging Animations -

基本概念(Basic Concepts)

XamlFlair的基本概念是基于From和To的动画。由From动画组成的任何UI元素都将以一个或多个任意值开始,并使用相应属性的默认值完成。由To动画组成的任何UI元素都将以其当前状态开始,并设置为一个或多个任意值

From动画的示例(一个移动到Translation(0)的UI元素):

From动画

To动画示例(从当前状态滑出的UI元素):

To动画

注意:需要注意的是,对于彩色动画,此规则有一个例外,这在“基本动画类型”部分中进行了说明。

使用

首先,需要添加以下Xaml命名空间引用:

UWP and Uno:

1
xmlns:xf="using:XamlFlair"

WPF:

1
xmlns:xf="clr-namespace:XamlFlair;assembly=XamlFlair.WPF"

给任何需要动画的UI元素FrameworkElement添加附加属性:

1
<Border xf:Animations.Primary="{StaticResource FadeIn}" />

注意:如果FrameworkElement在Xaml中定义了CompositeTransform,则它将在动画过程中更改。

注意StaticResource的用法是引用全局通用动画,这将在下一节中讨论。

基本动画类型(Base Animation Types)

淡入淡出(Fade)

淡入淡出动画

警告:设置FadeTo动画时要小心,因为如果VisibilityVisible,元素将保留在可视树中。在某些情况下,您可能需要手动管理IsHitTestVisible,以允许用户点击元素。

移动(Translate)

移动动画

缩放(Scale)

缩放动画

旋转(Rotate)

旋转动画

模糊 (Blur,只支持UWP 和 WPF)

模糊动画

饱和度 (Saturate,只支持UWP)

饱和度动画

色调(Tint)(只支持UWP)

色调动画

色彩 (Color,只支持WPF和Uno)

色彩动画

注意:重要的是要注意,当使用From动画设置色彩动画时,颜色将从指定值设置为其当前状态,而不是默认值。

旋轴 (Swivel,只支持UWP)

旋轴动画

注意:请阅读Perspective Rotations (*UWP Only*)[27]一节了解更多详细信息。

下面列出了使用XamlFlair时一些值得注意的默认值

  • Kind: FadeTo

  • Duration (milliseconds): 500

  • Easing: Cubic

  • Easing Mode: EaseOut

  • TransformCenterPoint: (0.5, 0.5)

  • Event: Loaded

  • InterElementDelay (milliseconds): 25 (List controls only)

  • TransformOn: Render (WPF only)

  • Saturation: 0.5 (UWP only)

  • Tint: Transparent (UWP only)

色彩动画 (Color Animations,只支持WPF和Uno)

使用色彩动画时需要注意,因为它们与其他基本类型动画略有不同。使用ColorToColorFrom时,必须执行以下操作:

  • 只能设置以下属性的动画:Control.Background, Control.Foreground, Control.BorderBrush, Border.Background, Border.BorderBrush, TextBlock.Foreground, Shape.Fill, Shape.Stroke

  • 确保在要设置动画的相应属性上设置brush

  • 还必须使用ColorOn指定目标属性

以下示例将为Rectangle的Fill属性设置从RoyalBlue到DarkGreen的动画:

1
2
3
4
5
6
7
8
9
10
11
<xf:AnimationSettings x:Key="SampleColorAnimation"

                      Kind="ColorTo"

                      Color="DarkGreen"

                      ColorOn="Fill" />

<Rectangle Fill="RoyalBlue"

           xf:Animations.Primary="{StaticResource SampleColorAnimation}" />

覆盖全局默认值

如果需要全局更改默认动画值之一(例如,默认Duration为750而不是500),则可以在应用程序的初始化代码中调用OverrideDefaultSettings函数。以下示例更改DurationEasing的默认值:

1
2
3
4
5
XamlFlair.Animations.OverrideDefaultSettings(

    duration: 750,

    easing: EasingType.Quadratic);

因此,在上面的示例代码中,每个动画都将以二次缓和的方式运行750ms。

使用ResourceDictionary进行基本设置

所有常见动画都应该放在全局ResourceDictionary(例如:Animations.xaml)中,并在应用程序中需要时使用。目标是将所有动画合并为一个具有有意义名称的文件,以便任何开发人员都能准确地了解将动画应用到FrameworkElement中的内容。下面是一个小例子:

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
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                    xmlns:xf="using:XamlFlair">

    <x:Double x:Key="PositiveOffset">50</x:Double>

    <x:Double x:Key="NegativeOffset">-50</x:Double>

    <x:Double x:Key="SmallScaleFactor">0.75</x:Double>

    <x:Double x:Key="LargeScaleFactor">1.25</x:Double>

    <xf:AnimationSettings x:Key="FadeIn"

                          Kind="FadeFrom"

                          Opacity="0" />

    <xf:AnimationSettings x:Key="FadeOut"

                          Kind="FadeTo"

                          Opacity="0" />

    <!-- Scale to a larger value -->

    <xf:AnimationSettings x:Key="Expand"

                          Kind="ScaleXTo,ScaleYTo"

                          ScaleX="{StaticResource LargeScaleFactor}"

                          ScaleY="{StaticResource LargeScaleFactor}" />

    <!-- Scale from a larger value -->

    <xf:AnimationSettings x:Key="Contract"

                          Kind="ScaleXFrom,ScaleYFrom"

                          ScaleX="{StaticResource LargeScaleFactor}"

                          ScaleY="{StaticResource LargeScaleFactor}" />

    .

    .

    .

    </ResourceDictionary>

要设置应用程序中已有的这组预配置AnimationSettings,请执行以下步骤:

  1. 项目工程点击右键菜单,点击Add > New Item…

  2. 选择 Resource Dictionary 并命名为 Animations.xaml

  3. App.xaml内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
    <Application.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="Animations.xaml" />

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Application.Resources>
  1. Animations.xaml中,复制粘贴以下相应链接中的内容
  • Animation settings for UWP[28]

  • Animation settings for WPF[29]

  • Animation settings for Uno[30]

你的应用程序现在有一组通用动画可以使用了。

默认动画 (只支持WPF)

除了创建包含自定义AnimationSettingsResourceDictionary之外,XamlFlair还提供一些默认动画。

要在应用程序中引用这些默认动画,请在App.xaml中执行以下步骤:

  1. 顶部添加XamlFlair.WPF命名空间
1
    xmlns:xf="clr-namespace:XamlFlair;assembly=XamlFlair.WPF"
  1. 更新应用程序资源(Application Resources):
1
2
3
4
5
6
7
8
9
10
11
12
13
    <Application.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <xf:XamlFlairResources />

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Application.Resources>

您的应用程序现在有一系列全局默认的动画可以使用了。

如果Visual Studio Intellisense在使用<xf:XamlFlairResources />时不起作用,您可能需要尝试以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
    <Application.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="pack://application:,,,/XamlFlair.WPF;component/DefaultAnimations.xaml"/>

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Application.Resources>

TransformOn 属性 (只支持WPF)

RenderTransform可使用TransformOn属性应用动画。可用选项为RenderLayout。未指定任何内容时,默认为Render。以下是关于两个选项的示例:

注意:非常重要的是要注意WPF的LayoutTransform不支持任何TranslateTransform,因此translate动画永远不会生效。您可以在这里[31]的备注部分了解更多信息。

原文readme.md太长了,翻译累了,大家有兴趣看原文吧,最后上一图:

参考资料

[1]

Sekuence Puzzle Game: https://sekuence.fun

[2]

给我买杯咖啡: https://www.buymeacoffee.com/xamlflair

[3]

Install from Nuget: #install-from-nuget

[4]

Features Overview: #features-overview

[5]

Basic Concepts: #basic-concepts

[6]

Usage: #usage

[7]

Base Animation Types: #base-animation-types

[8]

Color Animations (WPF And Uno Only): #color-animations-wpf-and-uno-only

[9]

Overriding the Global Default Values: #overriding-the-global-default-values

[10]

Using a ResourceDictionary for Base Settings: #using-a-resourcedictionary-for-base-settings

[11]

Default Animations (WPF Only): #default-animations-wpf-only

[12]

TransformOn Property (WPF Only): #transformon-property-wpf-only

[13]

Perspective Rotations (UWP Only): #perspective-rotations-uwp-only

[14]

Combining Animations: #combining-animations

[15]

Overriding Values: #overriding-values

[16]

Relative Translations on X and Y Axes: #relative-translations-on-x-and-y-axes

[17]

Compound Animations: #compound-animations

[18]

Repeating Animations: #repeating-animations

[19]

Events and Bindings: #events-and-bindings

[20]

Primary and Secondary Completion Commands: #primary-and-secondary-completion-commands

[21]

Using the StartWith Property: #using-the-startwith-property

[22]

Using the AllowOpacityReset Property (WPF Only): #using-the-allowopacityreset-property-wpf-only

[23]

Using the ClipToBounds Property (UWP And Uno Only): #using-the-cliptobounds-property-uwp-and-uno-only

[24]

Debugging Animations: #debugging-animations

[25]

Logging Animations: #logging-animations

[26]

ListViewBase (UWP and Uno) and ListBox-based (WPF) Animations: #listviewbase-uwp-and-uno-and-listbox-based-wpf-animations

[27]

Perspective Rotations (UWP Only): #perspective-rotations-uwp-only

[28]

Animation settings for UWP: https://github.com/XamlFlair/XamlFlair/blob/master/Samples/XamlFlair.Samples.UWP/Animations.xaml

[29]

Animation settings for WPF: https://github.com/XamlFlair/XamlFlair/blob/master/Samples/XamlFlair.Samples.WPF/Animations.xaml

[30]

Animation settings for Uno: https://github.com/XamlFlair/XamlFlair/blob/master/Samples/Uno/XamlFlair.Samples.Uno.Shared/Animations.xaml

[31]

这里: https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.layouttransform?redirectedfrom=MSDN&view=net-5.0#remarks

在上一篇文章《物联网常见通信协议与通讯协议梳理【上】-通讯协议》中,对物联网常用通信协议和通讯协议作了区分,并对通讯协议进行了分享;本文将对常用的通信协议进行剖析,重点面向市场上使用率较高的,且又不是诸如TCP/IP之类老生常谈的。

2.1  RFID

RFID的空中接口通信协议规范基本决定了RFID的工作类型,RFID读写器和相应类型RFID标签之间的通讯规则,包括:频率、调制、位编码及命令集。ISO/IEC制定五种频段的空中接口协议。

(1)ISO/IEC 18000-1《信息技术-基于单品管理的射频识别-第1部分:参考结构和标准化的参数定义》。它规范空中接口通信协议中共同遵守的读写器与标签的通信参数表、知识产权基本规则等内容。这样每一个频段对应的标准不需要对相同内容进行重复规定。

(2)ISO/IEC 18000-2《信息技术-基于单品管理的射频识别-第2部分:135KHz以下的空中接口通信用参数》。它规定在标签和读写器之间通信的物理接口,读写器应具有与Type A(FDX)和Type B(HDX)标签通信的能力;规定协议和指令再加上多标签通信的防碰撞方法。

(3)ISO/IEC 18000-3《信息技术-基于单品管理的射频识别-第3部分:参数空中接口通信在13.56MHz》。它规定读写器与标签之间的物理接口、协议和命令再加上防碰撞方法。关于防碰撞协议可以分为两种模式,而模式1又分为基本型与两种扩展型协议(无时隙无终止多应答器协议和时隙终止自适应轮询多应答器读取协议)。模式2采用时频复用FTDMA协议,共有8个信道,适用于标签数量较多的情形。

(4)ISO/IEC 18000-4《信息技术-基于单品管理的射频识别-第4部分:2.45 GHz空中接口通信用参数》。它规定读写器与标签之间的物理接口、协议和命令再加上防碰撞方法。该标准包括两种模式,模式1是无源标签工作方式是读写器先讲;模式2是有源标签,工作方式是标签先讲。

(5)ISO/IEC 18000-6《信息技术-基于单品管理的射频识别-第6部分:860 MHz - 960 MHz空中接口通信参数》。它规定读写器与标签之间的物理接口、协议和命令再加上防碰撞方法。它包含TypeA、TypeB和TypeC三种无源标签的接口协议,通信距离最远可以达到10m。其中TypeC是由EPCglobal起草的,并于2006年7月获得批准,它在识别速度、读写速度、数据容量、防碰撞、信息安全、频段适应能力、抗干扰等方面有较大提高。2006年递交V4.0草案,它针对带辅助电源和传感器电子标签的特点进行扩展,包括标签数据存储方式和交互命令。带电池的主动式标签可以提供较大范围的读取能力和更强的通信可靠性,不过其尺寸较大,价格也更贵一些。

(6)ISO/IEC 18000-7《信息技术-基于单品管理的射频识别-第7部分:433 MHz有源空中接口通信参数》。它规定读写器与标签之间的物理接口、协议和命令再加上防碰撞方法。有源标签识读范围大,适用于大型固定资产的跟踪。属于有源电子标签。

此外,还有3个常用的RFID协议:

(1)ISO/IEC 14443《识别卡—无触点集成电路卡—邻近卡》

国际标准ISO 14443定义了两种信号接口:TypeA和TypeB。ISO 14443A和B互不兼容。

一、ISO 14443 TypeA

ISO 14443 TypeA

(也称ISO 14443A)一般用于门禁卡、公交卡和小额储值消费卡等,具有较高的市场占有率。

举例:

*1***)MIFARE ULtralight(MFO ICU1X):**国内常称U10。此芯片没有加密功能,只能系统加密,内存是64个字节,典型应用:广深高速火车票。(另:MIFARE ULtralight C,也叫U20,此芯片可以加密,内存是192个字节)。这两个芯片的内码位数都是一样的,不过内码数据时不同的。(国内兼容芯片有FM11RF005内存64个字节、BL75R12内存64个字节等)

2****)MIFARE Std 1k(MF1 IC S50):国内常称MF1 S50。主要应用在一卡通方面。内存1KB,有16个扇区,每个扇区有4个块,每个块16个字节。初始密码是12个F。(国内兼容芯片有FM11R08、ISSI4439、TKS50、BL75R06等)

3****)MIFARE Std 4k(MF1 IC S70):国内常称为MF1 S70。主要应用在一卡通方面。内存4KB,共40个扇区,前面32个扇区跟S50一样,每个扇区有4个块,后面8个扇区是16个块,每个块都是16个字节。初始密码是12个F。(国内兼容芯片有ISSI4469、FM11RF32以及华大的S70)。

4****)Mifare DESFire 4k(MF3 IC D41/D40):国内常称为MF3。典型应用:南京地铁。

5****)SHC1102:上海华虹生产。典型应用:上海一卡通。

二、ISO 14443 TypeB

ISO14443B由于加密系数比较高,更适合于CPU卡,一般用于身份证、护照、银联卡等,目前的第二代电子身份证采用的标准是ISO 14443 TypeB协议。

举例:

1****)SR176:瑞士意法半导体(ST)生产。

2****)SRIX4K:瑞士意法半导体(ST)生产。

*3***)THR1064:**北京同方生产。典型应用:奥运门票。

*4***)AT88RF020:**美国爱特梅尔(ATMIL)生产。典型应用:广州地铁卡。

5****)第二代居民身份证:上海华虹、北京同方THR9904、天津大塘和北京华大生产。

(2)ISO/IEC 15693《识别卡—无接触点集成电路卡—近距卡》

ISO 14443A/B的读写距离通常在10cm以内,应用较广。但ISO 15693的读写距离可以达到1m,应用较灵活,与ISO 18000-3兼容(我国的国家标准很多与ISO 18000大部分兼容)。

举例:

*1***)ICODE SLI(SL2ICS20):**国内常称ICODE 2(内存是1Kbit),此型号常用。国内兼容有BL75R05、FM1302N。(另:ICODE SLI-S内存是2048bit,ICODE SLI-L内存是512bit,这两款芯片在国内不常用。)

*2***)Tag-it HF-1 Plus:**国内常称Tl2048,美国德州仪器公司(简称TI公司)生产。

3****)EM4135:瑞士EM生产。

4****)BL75R04:上海贝岭生产以及FM1302T(复旦生产),兼容TI公司的Tag-it HF-1 Plus。

(3)ISO 18092《信息技术系统间近距离无线通信及信息交换的接口和协议》

NFC协议,对近距离无线通信技术进行了一些规范。NFC属于RFID范畴,但又与RFID有一些区别,因此本文将单独一小节对NFC进行阐述。

2.2  NFC

2.2.1  NFC概述

近距离无线通信NFC是Near Field Communication缩写,即近距离无线通讯技术,是一种短距离的高频无线通信技术,允许电子设备之间进行非接触式点对点数据传输(在10厘米内)交换数据。

NFC提供了一种简单、触控式的解决方案,可以让消费者简单直观地交换信息、访问内容与服务。这个技术由免接触式射频识别(RFID)演变而来,并向下兼容RFID,最早由Philips、Nokia和Sony主推,主要可能用于手机等手持设备中。由于近场通讯具有天然的安全性,因此,NFC技术被认为在手机支付等领域具有很大的应用前景。NFC将非接触读卡器、非接触卡和点对点(Peer-to-Peer)功能整合进一块单芯片,为消费者的生活方式开创了不计其数的全新机遇。

这是一个开放接口平台,可以对无线网络进行快速、主动设置,也是虚拟连接器,服务于现有蜂窝状网络、蓝牙和无线802.11设备。和RFID不同,NFC采用了双向的识别和连接。在20cm距离内工作于13.56MHz频率范围。它能快速自动地建立无线网络,为蜂窝设备、蓝牙设备、Wi-Fi设备提供一个“虚拟连接”,使电子设备可以在短距离范围进行通讯。

2.2.2  NFC技术原理

NFC的设备可以在主动或被动模式下交换数据。

在被动模式下,启动NFC通信的设备,也称为NFC发起设备(主设备),在整个通信过程中提供射频场。它可以选择106kbps、212kbps或424kbps其中一种传输速度,将数据发送到另一台设备。另一台设备称为NFC目标设备(从设备),不必产生射频场,而使用负载调制(load modulation)技术,即可以相同的速度将数据传回发起设备。 移动设备主要以被动模式操作,可以大幅降低功耗,并延长电池寿命。电池电量较低的设备可以要求以被动模式充当目标设备,而不是发起设备。

在主动模式下,每台设备要向另一台设备发送数据时,都必须产生自己的射频场。这是对等网络通信的标准模式,可以获得非常快速的连接设置。

2.2.3   NFC应用

NFC技术的应用可以分为四种基本的类别:

(1)接触通过(TouchandGo),如门禁管制、车票和门票等,使用者只需携带储存着票证或门控密码的移动设备靠近读取装置即可。

(2)接触确认(TouchandConfirm),如移动支付,用户通过输入密码或者仅是接受交易,确认该次交易行为。

(3)接触连接(TouchandConnect),如把两个内建NFC的装置相连接,进行点对点数据传输,例如下载音乐、图片互传和同步交换通讯簿等。

(4)接触浏览(TouchandExplore),NFC设备可以提供一种以上有用的功能,消费者将能够通过浏览一个NFC设备,了解提供的是何种功能和服务。

2.3  Bluetooth

2.3.1  起源

蓝牙的名字来源于10世纪丹麦国王Harald Blatand -英译为Harold Bluetooth(因为他十分喜欢吃蓝梅,所以牙齿每天都带着蓝色)。他将当时的瑞典、芬兰与丹麦统一起来。用他的名字来命名这种新的技术标准,含有将四分五裂的局面统一起来的意思。

1998年5月,爱立信、诺基亚、东芝、IBM和英特尔公司等五家著名厂商,在联合开展短程无线通信技术的标准化活动时提出了蓝牙技术,其宗旨是提供一种短距离、低成本的无线传输应用技术。

2.3.2  蓝牙技术的规范及特点

蓝牙技术是一种无线数据与语音通信的开放性全球规范,工作在全球通用的2.4GHz ISM(即工业、科学、医学)频段,标准是IEEE802.15,工作在2.4GHz频带,带宽为1Mb/s。

它以低成本的近距离无线连接为基础,为固定与移动设备通信环境建立一个特别连接。以时分方式进行全双工通信,其基带协议是电路交换和分组交换的组合。一个跳频频率发送一个同步分组,每个分组占用一个时隙,使用扩频技术也可扩展到5个时隙。 同时,蓝牙技术支持1个异步数据通道或3个并发的同步话音通道,或1个同时传送异步数据和同步话音的通道。每一个话音通道支持64kb/s的同步话音;异步通道支持最大速率为721kb/s,反向应答速率为57. 6 kb/s的非对称连接,或者是432. 6 kb/s的对称连接。

依据发射输出电平功率不同,蓝牙传输有3种距离等级:

Class1为100m左右;

Class2约为10m;

Class3约为2-3m。

一般情况下,其正常的工作范围是10m半径之内。在此范围内,可进行多台设备间的互联。

2.3.3  蓝牙版本

目前蓝牙发展到了蓝牙5.0:

下面对现阶段主要版本蓝牙技术的特性做一个详细的介绍:

一、版本1.1

传输率约在748~810kb/s,因是早期设计,容易受到同频率之间的类似通信产品干扰,影响通讯质量。这个初始版本支持Stereo音效的传输要求,但只能够以(单工)方式工作,加上带宽频率响应等指标不理想,并未算是最好的Stereo传输工具。

二、版本1.2

同样是只有748~810kb/s的传输率,但增加了(改善Software)抗干扰跳频功能。支持Stereo音效的传输要求,但只能够作(单工)方式工作,加上带宽频率响应还是不理想,也不能作为立体声(Stereo)传输工具。

三、版本2.0

2.0是1.2的改良提升版,传输率约在1.8M/s~2.1M/s,可以有(双工)的工作方式。即一边作语音通讯,同时亦可以传输档案/高质素图片,2.0版本当然也支持Stereo运作。随后蓝牙2.0版本的芯片,增加了Stereo译码芯片,则连A2DP(Advanced Audio Distribution Profile)也可以不需要了。

四、版本2.1

为了改善蓝牙技术存在的问题,蓝牙SIG组织(Special Interest Group)推出了Bluetooth 2.1+EDR版本的蓝牙技术。改善装置配对流程:以往在连接过程中,需要利用个人识别码来确保连接的安全性,而改进过后的连接方式则是会自动使用数字密码来进行配对与连接,举例来说,只要在手机选项中选择连接特定装置,在确定之后,手机会自动列出当前环境中可使用的设备,并且自动进行连结;而短距离的配对方面:也具备了在两个支持蓝牙的手机之间互相进行配对与通讯传输的NFC(Near Field Communication)机制;更佳的省电效果:蓝牙2.1版加入了SniffSubrating的功能,透过设定在2个装置之间互相确认讯号的发送间隔来达到节省功耗的目的。蓝牙2.1将装置之间相互确认的讯号发送时间间隔从旧版的0.1秒延长到0.5秒左右,如此可以让蓝牙芯片的工作负载大幅降低,也可让蓝牙可以有更多的时间可以彻底休眠。根据官方的报告,采用此技术之后,蓝牙装置在开启蓝牙联机之后的待机时间可以有效延长5倍以上,开始支持全双工通信模式。

五、版本3.0+HS

2009年4月21日,蓝牙技术联盟(BluetoothSIG)正式颁布了新一代标准规范”BluetoothCoreSpecificationVersion3.0HighSpeed”(蓝牙核心规范3.0版高速),蓝牙3.0的核心是”Generic Alternate MAC/PHY”(AMP),这是一种全新的交替射频技术,允许蓝牙协议栈针对任一任务动态地选择正确射频。最初被期望用于新规范的技术包括802.11以及UMB,但是新规范中取消了UMB的应用。作为新版规范,蓝牙3.0的传输速度自然会更高,而秘密就在802.11无线协议上。通过集成”802.11PAL”(协议适应层),蓝牙3.0的数据传输率提高到了大约24Mbps(即可在需要的时候调用802.11WI-FI用于实现高速数据传输),是蓝牙2.0的八倍,可以轻松用于录像机至高清电视、PC至PMP、UMPC至打印机之间的资料传输。功耗方面,通过蓝牙3.0高速传送大量数据自然会消耗更多能量,但由于引入了增强电源控制(EPC)机制,再辅以802.11,实际空闲功耗会明显降低,蓝牙设备的待机耗电问题有望得到初步解决。此外,新的规范还具备通用测试方法(GTM)和单向广播无连接数据(UCD)两项技术,并且包括了一组HCI指令以获取密钥长度。据称,配备了蓝牙2.1模块的PC理论上可以通过升级固件让蓝牙2.1设备也支持蓝牙3.0。联盟成员已经开始为设备制造商研发蓝牙3.0解决方案。

六、蓝牙4.0

(1)简介

蓝牙4.0为蓝牙3.0的升级标准蓝牙4.0最重要的特性是省电,极低的运行和待机功耗可以使一粒纽扣电池连续工作数年之久。此外,低成本和跨厂商互操作性,3毫秒低延迟、AES-128加密等诸多特色,可以用于计步器、心律监视器、智能仪表、传感器物联网等众多领域,大大扩展蓝牙技术的应用范围。

(2)主要特点

蓝牙4.0是蓝牙3.0+HS规范的补充,专门面向对成本和功耗都有较高要求的无线方案,可广泛用于卫生保健、体育健身、家庭娱乐、安全保障等诸多领域。它支持两种部署方式:双模式和单模式。双模式中,低功耗蓝牙功能集成在现有的经典蓝牙控制器中,或再在现有经典蓝牙技术(2.1+EDR/3.0+HS)芯片上增加低功耗堆栈,整体架构基本不变,因此成本增加有限。

Single mode只能与BT4.0互相传输无法向下兼容(与3.0/2.1/2.0无法相通);Dual mode可以向下兼容可与BT4.0传输也可以跟3.0/2.1/2.0传输。单模式面向高度集成、紧凑的设备,使用一个轻量级连接层(Link Layer)提供超低功耗的待机模式操作、简单设备恢复和可靠的点对多点数据传输,还能让联网传感器在蓝牙传输中安排好低功耗蓝牙流量的次序,同时还有高级节能和安全加密连接。

(3)优点

蓝牙4.0将三种规格集一体,包括传统蓝牙技术、高速技术和低耗能技术,与3.0版本相比最大的不同就是低功耗。“4.0版本的功耗较老版本降低了90%,更省电,“随着蓝牙技术由手机、游戏、耳机、便携电脑和汽车等传统应用领域向物联网、医疗等新领域的扩展,对低功耗的要求会越来越高。4.0版本强化了蓝牙在数据传输上的低功耗性能。”

七、蓝牙4.1

(1)简介

如果说蓝牙4.0主打的是省电特性的话,那么此次升级蓝牙4.1的关键词应当是IOT(全联网),也就是把所有设备都联网的意思。为了实现这一点,对通讯功能的改进是蓝牙4.1最为重要的改进之一。

(2)主要特点

1)批量数据的传输速度

首当其冲的就是批量数据的传输速度,大家知道蓝牙的传输速率一直非常渣,与已经跨入千兆的Wi-Fi相比毫无可比性。所以蓝牙4.1在已经被广泛使用的蓝牙4.0 LE基础上进行了升级,使得批量数据可以以更高的速率传输。当然这并不意味着可以用蓝牙高速传输流媒体视频,这一改进的主要针对的还是刚刚兴起的可穿戴设备。例如已经比较常见的健康手环,其发送出的数据流并不大,通过蓝牙4.1能够更快速地将跑步、游泳、骑车过程中收集到的信息传输到手机等设备上,用户就能更好地实时监控运动的状况,这是很有用处的。在蓝牙4.0时代,所有采用了蓝牙4.0 LE的设备都被贴上了“Bluetooth Smart”和“Bluetooth SmartReady”的标志。其中Bluetooth Smart Ready设备指的是PC、平板、手机这样的连接中心设备,而Bluetooth Smart设备指的是蓝牙耳机、键鼠等扩展设备。之前这些设备之间的角色是早就安排好了的,并不能进行角色互换,只能进行1对1连接。而在蓝牙4.1技术中,就允许设备同时充当“Bluetooth Smart”和“Bluetooth Smart Ready”两个角色的功能,这就意味着能够让多款设备连接到一个蓝牙设备上。举个例子,一个智能手表既可以作为中心枢纽,接收从健康手环上收集的运动信息的同时,又能作为一个显示设备,显示来自智能手机上的邮件、短信。借助蓝牙4.1技术智能手表、智能眼镜等设备就能成为真正的中心枢纽。

2)通过IPV6连接到网络

除此之外,可穿戴设备上网不易的问题,也可以通过蓝牙4.1进行解决。新标准加入了专用通道允许设备通过IPv6联机使用。举例来说,如果有蓝牙设备无法上网,那么通过蓝牙4.1连接到可以上网的设备之后,该设备就可以直接利用IPv6连接到网络,实现与WiFi相同的功能。尽管受传输速率的限制,该设备的上网应用有限,不过同步资料、收发邮件之类的操作还是完全可以实现的。这个改进的好处在于传感器、嵌入式设备只需蓝牙便可实现连接手机、连接互联网,相对而言WiFi多用于连接互联网,在连接设备方面效果一般,无法做到蓝牙的功能。未来随着物联网逐渐走进我们的生活,无线传输在日常生活中的地位也会越来越高,蓝牙作为普及最广泛的传输方式,将在“物联网”中起到不可忽视的作用。不过,蓝牙完全适应IPv6则需要更长的时间,所以就要看芯片厂商如何帮助蓝牙设备增加IPv6的兼容性了。

3)简化设备连接

在各大手机厂商以及PC厂商的推动下,几乎所有的移动设备和笔记本电脑中都装有蓝牙的模块,用户对于蓝牙的使用也比较多。不过仍有大量用户觉得蓝牙使用起来很麻烦,归根结底还是蓝牙设备较为复杂的配对、连接造成的。试想一下,如果与手机连接的智能手表,每次断开连接后,都得在设置界面中手动选择一次才能重新连接,这就非常麻烦了。之前解决这一问题的方法是厂商在两个蓝牙设备中都加入NFC芯片,通过NFC近场通讯的方式来简化重新配对的步骤,这本是个不错的思路。只是搭载NFC芯片的产品不仅数量少,而且价格偏高,非常小众。

蓝牙4.1针对这点进行了改进,对于设备之间的连接和重新连接进行了很大幅度的修改,可以为厂商在设计时提供更多的设计权限,包括设定频段创建或保持蓝牙连接,这以改变使得蓝牙设备连接的灵活性有了非常明显的提升。两款带有蓝牙4.1的设备之前已经成功配对,重新连接时只要将这两款设备靠近,即可实现重新连接,完全不需要任何手动操作。举个例子,以后使用蓝牙4.1的耳机时,只要打开电源开关就行了,不需要在手机上进行操作,非常的简单。

4)与4G和平共处

在移动通信领域,近期最火的话题莫过于4G了,已经成为全球无线通信网络一个不可逆转的发展趋势。而蓝牙4.1也专门针对4G进行了优化,确保可以与4G信号和平共处,这个改进被蓝牙技术联盟称为“共存性”。可能大家会觉得疑惑,手机网络信号和蓝牙不是早就共存了么,为什么蓝牙4.1还要特别针对这点改进呢?这是因为在实际的应用中,如果这两者同时传输数据,那么蓝牙通信就可能受到手机网络信号的干扰,导致传输速率的下降。因此在全新的蓝牙4.1标准中,一旦遇到蓝牙4.1和4G网络同时在传输数据的情况,那么蓝牙4.1就会自动协调两者的传输信息,从而减少其它信号对蓝牙4.1的干扰,用户也就不用担心传输速率下降的问题了。

5)蓝牙4.1提供的增强功能

包括:

AES加密技术提供更安全的连接。该功能使无线耳机更加适用于政府、医疗及银行等安全至上的应用领域。

可通过专属Bluetooth Smart远程遥控器操控耳机、扬声器及条形音箱,并支持同步播放源于另一个完全不同设备的音频流。

八、蓝牙4.2标准

2014年12月4日,蓝牙4.2标准颁布,改善了数据传输速度和隐私保护程度,可直接通过IPv6和6LoWPAN接入互联网。在新的标准下蓝牙信号想要连接或者追踪用户设备必须经过用户许可,否则蓝牙信号将无法连接和追踪用户设备。

速度方面变得更加快速,两部蓝牙设备之间的数据传输速度提高了2.5倍,因为蓝牙智能(Bluetooth Smart)数据包的容量提高,其可容纳的数据量相当于此前的10倍左右。

九、蓝牙5.0协议

于美国时间2016年6月16日在伦敦正式发布,为现阶段最高级的蓝牙协议标准。蓝牙5.0有以下特点:

(1)更快的传输速度

蓝牙5.0的开发人员称,新版本的蓝牙传输速度上限为2Mbps,是之前4.2LE版本的两倍。当然,你在实际生活中是不太可能达到这个极限速度的,但是仍然可以体验到显著的速度提升。

(2)更远的有效距离

蓝牙5.0的另外一个重要改进是,它的有效距离是上一版本的4倍,因此在理论上,当你拿着手机站在距离蓝牙音箱300米的地方,它还是会继续放着你爱的歌。也就是说,理论上,蓝牙发射和接收设备之间的有效工作距离可达300米。当然,实际的有效距离还取决于你使用的电子设备。

(3)导航功能

蓝牙5.0将添加更多的导航功能,因此该技术可以作为室内导航信标或类似定位设备使用,结合Wi-Fi可以实现精度小于1米的室内定位。举个例子,如果你是路痴,你仍可以使用蓝牙技术,在诺大的商业中心找到路。

(4)物联网功能

物联网还在持续火爆,因此,蓝牙5.0针对物联网进行了很多底层优化,力求以更低的功耗和更高的性能为智能家居服务。

(5)升级硬件

此前的一些蓝牙版本更新只要求升级软件,但蓝牙5.0很可能要求升级到新的芯片。不过,旧的硬件仍可以兼容蓝牙5.0,你就无法享用其新的性能了。搭载蓝牙5.0芯片的旗舰级手机将于2017年问世,相信中低端手机也将陆陆续续内置蓝牙5芯片。苹果将为成为第一批使用该项技术的厂商之一。

(6)更多的传输功能

全新的蓝牙5.0能够增加更多的数据传输功能,硬件厂商可以通过蓝牙5.0创建更复杂的连接系统,比如Beacon或位置服务。因此通过蓝牙设备发送的广告数据可以发送少量信息到目标设备中,甚至无需配对。

(7)更低的功耗

众所周知,蓝牙是智能手机的必备功能,随着智能设备和移动支付等越来越多需要打开蓝牙,才能享受便利功能逐渐融入人们的生活之中,蓝牙的功耗成为了智能手机待机时间的一大杀手。为此蓝牙5.0将大大降低了蓝牙的功耗,使人们在使用蓝牙的过程中再也不必担心待机时间短的问题。

(8)真正支持无损传输

支持24bit/192KHz的无损音源传输,对现有的Wi-Fi高保真无损音频传输形成有效威胁。

2.3.4蓝牙匹配规则

两个蓝牙设备在进行通讯前,必须将其匹配在一起,以保证其中一个设备发出的数据信息只会被经过允许的另一个设备所接受。蓝牙技术将设备分为两种:主设备和从设备。

(1)蓝牙主设备

主设备一般具有输入端。在进行蓝牙匹配操作时,用户通过输入端可输入随机的匹配密码来将两个设备匹配。

蓝牙手机、安装有蓝牙模块的PC等都是主设备。(例如:蓝牙手机和蓝牙PC进行匹配时,用户可在蓝牙手机上任意输入一组数字,然后在蓝牙PC上输入相同的一组数字,来完成这两个设备之间的匹配。)

(2)蓝牙从设备

从设备一般不具备输入端。因此从设备在出厂时,在其蓝牙芯片中,固化有一个4位或6位数字的匹配密码。蓝牙耳机、UD数码笔等都是从设备。(例如:蓝牙PC与UD数码笔匹配时,用户将UD笔上的蓝牙匹配密码正确的输入到蓝牙PC上,完成UD笔与蓝牙PC之间的匹配。)

注意事项:

主设备与主设备之间、主设备与从设备之间,是可以互相匹配在一起的;而从设备与从设备是无法匹配的。

例如:蓝牙PC与蓝牙手机可以匹配在一起;蓝牙PC也可以与UD笔匹配在一起;而UD笔与UD笔之间是不能匹配的。

一个主设备,可匹配一个或多个其他设备。例如:一部蓝牙手机,一般只能匹配7个蓝牙设备。而一台蓝牙PC,可匹配十多个或数十个蓝牙设备。

在同一时间,蓝牙设备之间仅支持点对点通讯。

2.3.5  蓝牙应用

蓝牙技术可以应用于日常生活的各个方面,例如,引入蓝牙技术,就可以去掉移动电话与膝上型电脑之间的令人讨厌的连接电缆而而通过无线使其建立通信。

打印机、PDA、桌上型电脑、传真机、键盘、游戏操纵杆以及所有其它的数字设备都可以成为蓝牙系统的一部分。

远距离蜂窝通信协议主要是2/3/4/5G、NB-IoT等技术下的各电信运营商采用的制式、协议,在这里就不再多为电信运营商和大设备商们摇旗了。

4.1  ZigBee

4.1.1  ZigBee简介

ZigBee这个名字来源于蜂群的通信方式:蜜蜂之间通过跳Zigzag形状的舞蹈来交互消息,以便共享食物源的方向、位置和距离等信息。借此意义Zigbee作为新一代无线通讯技术的命名。

ZigBee是一种高可靠的无线数传网络,类似于CDMA和GSM网络。ZigBee数传模块类似于移动网络基站。ZigBee是一个由可多到65000个无线数传模块组成的一个无线网络平台,在整个网络范围内,每一个网络模块之间可以相互通信,每个网络节点间的距离可以从标准的75m无限扩展。通讯距离从标准的75m到几百米、几公里,并且支持无限扩展(依靠节点数增加)。与移动通信的CDMA网或GSM网不同的是,ZigBee网络主要是为工业现场自动化控制数据传输而建立,因而,它必须具有简单,使用方便,工作可靠,价格低的特点;而移动通信网主要是为语音通信而建立,每个基站价值一般都在几十万甚至上百万元人民币,而每个ZigBee网络“基站”(节点)却不到1000元人民币。

4.1.2  技术特点

ZigBee是一种无线连接,可工作在2.4GHz(全球流行)、868MHz(欧洲流行)和915 MHz(美国流行)3个频段上,分别具有最高250kbit/s、20kbit/s和40kbit/s的传输速率,它的传输距离在10-75m的范围内,但可以继续增加。

作为一种无线通信技术,ZigBee具有如下特点:

(1)低功耗

(2)成本低

(3)时延短

(4)网络容量大

(5)可靠

(6)安全

4.1.3  ZigBee协议栈

ZigBee协议栈结构是基于标准OSI七层模型的,包括高层应用规范、应用汇聚层、网络层、媒体接入层和物理层,如下图所示。

IEEE 802.15.4定义了两个物理层标准,分别是2.4GHz物理层和868/915MHz物理层。两者均基于直接序列扩频(DSSS)技术。

868MHz只有一个信道,传输速率为20kb/s;902MHz~928MHZ频段有10个信道,信道间隔为2MHz,传输速率为40kb/s。以上这两个频段都采用BPSK调制。

2.4GHz~2.4835 GHz频段有16个信道,信道间隔为5MHz,能够提供250kb/s的传输速率,采用O-QPSK调制。

为了提高传输数据的可靠性,IEEE 802.15.4定义的媒体接入控制(MAC)层采用了CSMA-CA和时隙CSMA-CA信道接入方式和完全握手协议。

应用汇聚层主要负责把不同的应用映射到ZigBee网络上,主要包括安全与鉴权、多个业务数据流的会聚、设备发现和业务发现。

4.1.4  应用领域

(1)家庭和建筑物的自动化控制:照明、空调、窗帘等家具设备的远程控制;

(2)消费性电子设备:电视、DVD、CD机等电器的远程遥控。

(3)PC外设:无线键盘、鼠标、游戏操纵杆等;

(4)工业控制:使数据的自动采集、分析和处理变得更加容易;

(5) 医疗设备控制:医疗传感器、病人的紧急呼叫按钮等;

(6)交互式玩具。

4.2  LoRa

LoRa™(Long Range,远距离)是一种调制技术,与同类技术相比,提供更远的通信距离。由于LoRa调制是物理层(PHY),因此也可将其用于不同的协议和不同网络架构(如Mesh、Star、点对点)等等。可以将LoRa概括为以下几种协议:

(1)LoRaWAN协议

(2)CLAA网络协议

(3)LoRa私有网络协议

(4)LoRa数据透传

LoRa的协议不同,其产品和业务形态也有所不同。

4.2.1  LoRaWAN协议

LoRaWAN协议是由LoRa联盟推动的一种低功耗广域网协议,针对低成本、电池供电的传感器进行了优化,包括不同类别的节点,优化了网络延迟和电池寿命。LoRa联盟标准化了LoRaWAN,以确保不同国家的LoRa网络是可以互操作的。

LoRaWAN构建的是一个运营商级的大网,覆盖地区乃至全国的网络。经过几年的发展,目前已建立起了较为完整的生态链:LoRa芯片→模组→传感器→基站或网关→网络服务→应用服务。

在芯片方面,Semtech授权了多家公司做芯片,如ST、Micorochip、华普等,使得芯片产品更为丰富,一芯多源,产品不再受限于一家供应商。未来或许会有更多的厂家授权,生产出满足物联网市场多样化需求的产品来。

在LoRaWAN网络还没有部署好的时候,符合LoRaWAN协议的模组还不能像2G/3G/4G模组等一样自由销售。一般地LoRaWAN模组是与网关或基站的产品搭配一起销售。部分厂家也开源了终端部分,提供网关和网络服务部分的解决方案。

在LoRaWAN的产品中,多数厂家是以提供(云)端到(终)端的解决方案为主,包括模组、网关和网络服务器(Network Server),如NPLINK、八月科技、华立、唯传、门思、未来宽带等公司。由于对设备数据的要求不同,LoRaWAN网络服务(NS)有的是私有化部署,有的是部署在公有云或第三方网络服务器上。

LoRaWAN目前还基本上是面向toB的市场,还没有普及到toC市场。一些具有行业或市场资源的公司会较早地部署LoRaWAN网络,改变原有或创造新的应用系统,而低功耗广域网市场的创新活力也在于此。

4.2.2  CLAA协议

“中国LoRa应用联盟(China Lora Application Alliance,简称CLAA)是在LoRa Alliance支持下,由中兴通讯发起,各行业物联网应用创新主体广泛参与、合作共建的技术联盟,旨在共同建立中国LoRa应用合作生态圈,推动LoRa产业链在中国的应用和发展,建设多业务共享、低成本、广覆盖、可运营的LoRa物联网。中兴通讯作为LoRa Alliance(简称LoRa联盟)董事会成员,与LoRa联盟成员一起共同推动LoRa技术在全球低功耗广域网络(LPWAN)建设和产业链的发展。”

中兴通讯在LoRaWAN的基础上优化了协议,构建了共建共享的LoRa应用平台。凭借中兴通讯行业的实力和影响力,在CLAA平台上已聚集了很多公司的产品。CLAA提供网关和云化核心网服务,可快速搭建起LoRa网络的物联网系统的应用来。

CLAA有四种主要的业务合作模式:

(1)独立运营商:提供全套解决方案;支持客户建网,并与CLAA共享物联网互联互通

(2)大型合作伙伴:直接参股,CLAA负责建网,多城市大范围覆盖,享受全网整体受益,CLAA承担运维费

(3)中小型客户:直接采购设备,CLAA协助建网,城市级或区域级覆盖,享受城市级、区域级收益,客户承担运维费

(4)专业渠道商:直接采购设备,自行微客户建网,协助客户运营,客户承担运维费用

4.2.3  LoRa私有网络协议

在面向小范围节点数不多的应用中,使用LoRaWAN网关部署网络成本就显得高了。用一个或几个SX127x做一个小“网关”或“集中器”,无线连接上百个的SX127x,组建一个小的星型网络,通过自己的LoRa私有通信协议,就可以实现一个简单的LoRa私有网络,这也是一种比较灵活方式。当然,协议也可以是LoRaWAN协议。

4.2.4  LoRa数据透传

目前市面上LoRa芯片基本上源于美国SEMTECH的SX127x系列,用LoRa做成透传模块,只进行简单的发送和接收,实现点对点数据的传输,应用相对简单。

5.1  USB协议

目前USB已经发展了3代协议:

USB协议规范1.1——支持USB低速和全速规范(12Mbps)

USB协议规范2.0——支持USB高速协议规范(480Mbps)

USB协议规范3.0——支持USB超高速协议规范(5Gbps)

USB 3.0是最新的USB规范,该规范由英特尔等公司发起。USB 2.0已经得到了PC厂商普遍认可,接口更成为了硬件厂商的必备接口。USB2.0的最大传输带宽为480Mbps(60MB/s),而USB3.0的最大传输带宽高达5.0Gbps(即640MB/s)。不过注意这是理论传输值,如果几台设备共用一个USB通道,主控制芯片会对每台设备可支配的带宽进行分配、控制。如在USB1.1中,所有设备只能共享1.5MB/s的带宽。如果单一的设备占用USB接口所有带宽的话,就会给其他设备的使用带来困难。

RS232是一种异步传输标准接口协议。通常RS-232接口以9个引脚(DB-9)或是25个引脚(DB-25)的型态出现 。RS232最常用的连接方式是三根线:一条发送线、一条接收线及一条地线。

电平信号:逻辑1(MARK)=-3V~-15V,逻辑0(SPACE)=+3~+15V

传输距离:RS-232-C标准规定,驱动器允许有2500pF的电容负载,通信距离将受此电容限制,例如,采用150pF/m的通信电缆时,最大通信距离为15m;若每米电缆的电容量减小,通信距离可以增加。传输距离短的另一原因是RS-232属单端信号传送,存在共地噪声和不能抑制共模干扰(两条传输线上的信号同时变大或变小)等问题,因此一般用于20m以内的通信。

RS232不能实现多机通信。

传输速率:RS232的传输速率较慢,能够达到1Mbps的已经比较少。

5.3  RS485协议

RS485是RS232升级版的串口协议,一般采用两线制传输:A、B两条传输线。

电平信号:-2V~-6V表示“0”,+2V~+6V表示“1”,电压为A-B的电压。

传输距离:一般1Km以内都没有问题。理论上,通信速率在100Kpbs及以下时,RS485的最长传输距离可达1200米,但在实际应用中传输的距离也因芯片及电缆的传输特性而所差异。在传输过程中可以采用增加中继的方法对信号进行放大,最多可以加八个中继,也就是说理论上RS485的最大传输距离可以达到9.6公里。如果真需要长距离传输,可以采用光纤为传播介质,收发两端各加一个光电转换器,多模光纤的传输距离是5~10公里,而采用单模光纤可达50公里的传播距离。

RS485可以实现多机通信。

原因:RS485为半双工通信方式,即分时实现收和发。总线空闲的状态下需要保证状态为逻辑1,也就是A-B的电压符合逻辑1的电平值。假设此时1为主机,2和3为从机,数据线的连接方式为1,2,3的A连接在一起,1,2,3的B也连接在一起,不存在RS232连接方式的问题。

异步传输(Asynchronous Transmission): 异步传输将比特分成小组进行传送,小组可以是8位的1个字符或更长。发送方可以在任何时刻发送这些比特组,而接收方从不知道它们会在什么时候到达。一个常见的例子是计算机键盘与主机的通信。

波特率计算:如果设置波特率为115200,数据位为8bit,起始位为1bit,结束位为1bit,校验位为1bit;那1s钟不间断可传送的字符(1bit起始位+8bit数据位+1bit校验位+1bit结束位,共11bit)为115200/11=10472;10472/1024结果约为10.227所以速率约为10kB/ps。

M-BUS在本文中暂不介绍,因为笔者在后续文章中将分享远程抄表系统,而M-Bus是为远程抄表系统数据采集而诞生的,在远程抄表系统中,笔者将会对M-Bus协议进行分析。

由于前面几张的都是直接整理了下 九叔的hyper-v电子书发上来的,个人觉得他写的不是最详细,因此今天我按照自己的实际情况来写个模拟的实战演示。所有的东西都通过VMware WorkStation 12PRO来完成。如果用VMware来实验,大家可以看我以前的博客http://www.cnblogs.com/wanggege/p/4604182.html

环境介绍

这个环境通过4台服务器来完成,1台DC,1台存储,2台Hyper-v宿主机。

网络名称及用途

名称

用途

Public

用于管理主机

VM

用于虚拟机之间通信

ISCSI

用于HV主机连接存储

服务器IP

每台服务器都有一块Public网卡用于正常的管理,Stroage服务器有三块网卡建立NIC组用于提供ISCSI服务,2台Hyper-v主机个四块网卡,其中VM网卡用于虚拟机使用,Migration用于群集间的虚拟机迁移。

服务器名

Public

VM

ISCSI

Migration

DC

172.28.103.100

Storage

172.28.103.101

192.168.101.2*3

HV1

172.28.103.102

172.28.104.100

192.168.101.3

192.168.100.10

HV2

172.28.103.103

172.28.104.101

192.168.101.4

192.168.100.11

HVCluster

172.28.103.104

VMnet0是103的网段 Vmnet2是104网段

操作系统

除了Storage是使用windows 专门的存储版本,其他都使用Server 2012 Datacenter

服务器名

操作系统

DC

Windows Server 2012 R2 with Update1 Datacenter

Storage

Windows Storage Server 2012 R2 with Update1

HV1

Windows Server 2012 R2 with Update1 Datacenter

HV2

Windows Server 2012 R2 with Update1 Datacenter

域控服务器安装

具体安装可以看我以前的博客 Windows Server 2012 R2 创建AD域http://www.cnblogs.com/wanggege/p/4605678.html

硬件创建

选择克隆

选择当初做好了sysprep的快照

服务器部署

修改IP地址

安装域控服务

开始安装

创建域contoso.com

安装完成会自动重启

创建新的OU

创建三台服务器

配置组策略

开启防火墙

域控创建完成,等待其他服务器加入域

Storage服务器安装

硬件创建

服务器部署

修改IP地址

修改网卡顺序

创建网卡NIC

添加到新组

创建完成

设置IP地址,不需要网关

Ping下测试

安装iSCSI服务

创建iSCSI磁盘

选择磁盘

创建仲裁磁盘

配置iSCSI目标

创建数据磁盘

创建完成

HV服务器安装

硬件创建

添加4块网卡

选择Hyper-v

服务器部署

修改IP地址

网卡改名,方便区分

高级设置,调整网卡顺序

默认使用Public网卡

设置VM的ip地址

取消注册到DNS中

ISCSI网卡和Migration网卡不设置网关


设置完后ping一下

连接iSCSI磁盘

选择自动配置

磁盘联机并初始化

安装虚拟化服务和群集功能

存储先默认等会在修改


安装完成自动重启


修改创建好的虚拟交换机名字,2台服务器上都要改成同样的名字,否则会因为配置不同,无法创建群集。

打开故障转移群集管理器

验证配置

运行所有测试

测试时可能会跳出这个,不用当心,因为测试时会把磁盘卸载

有个网络是警报,不要紧,因为iSCSI和Migration没有设置网关

创建群集

创建完成

配置群集

添加群集共享卷

配置网络

修改虚拟机位置

至此群集创建完成,可以正常使用,以后我们开始将如何使用SCVMM等其他组件来组件私有云。

12月份项目比较忙,周末都给占了。今天终于算是比较清闲了。之前因为在DevText项目中使用Fluen NHibernate,顺便就学习了下。今天就结合官方网站的介绍来给大家分享下为什么我们要用Fluent NHibernate。

1.Fluent NHibernate是什么?

Fluent NHibernate提供了一个方法让你不再需要去写NHibernate的标准映射文件(.hbm.xml),而是可以把你的映射文件都使用C#来写。这样做,方便了我们的代码重构,提供了代码的易读性,并精简了项目代码。

它还包含了如下几个工具:

image

Fluent NHibernate是NHibernate核心代码的扩展,完全兼容NHibernate2.X。

2.为什么要Fluent NHibernate?

NHibernate就不用说了,大家都知道是一个好的ORM工具,它的mapping都是以XML格式定义的。每个类都有一个mapping文件映射到数据库对应的表。 Fluent NHibernate取消了这些xml文件。

为什么要取代XML文件呢?

a.XML不是实时编译的。当你的XML配置文件有错误时,你只有在运行时才能看到哪里出错。

b.XML是非常繁琐的。的确在NHibernate中的配置文件,xml节点非常简单,但是仍然掩盖不了XML文件本身的繁琐性。

c.映射文件中重复的属性设置。比如在xml中我们需要设置每个string类型的字段都不允许为空,长度大于1000,int型都得有个默认值为-1,这样最终的xml配置文件你会发现有很多的重复工作。

Fluent NHibernate如何克服这些缺陷呢?

Fluent NHibernate把这些配置为文件都转化为了C#代码,这样可以让你的mapping直接在编译时就完成。

下面是传统的HBM XML mapping文件和Fluent NHibernate的对比。

image

Fluent NHibernate的下载地址:

http://github.com/jagregory/fluent-nhibernate

3.使用Fluent NHibernate的例子

前面只是大致的说明了为什么要用Fluent NHibernate。现在我们来模拟一个场景,看看如何在项目中使用Fluent NHibernate。

我们场景中有Employee, Store何Product这几个实体,product和Store之间是多对多的关系。

File:FirstProjectSchema.png

实体的类如下:

Employee:

image

Product:

image

Store:

image

如果是使用NHibernate,那么接下来我们只能配置痛苦的XML文件,不过用FLuent NHibernate我们就不需要写配置文件了,只需要简单的写C#代码就OK了。

说实话这个和CTP4中的mapping方式特像。

先来看看Employee的mapping文件:

image

很明显这里的Map方法相当于XML配置文件的Property,而Reference相当于Many-To-One。

Product的Mapping如下:

image

这里的HasManyToMany相当于NHibernaet中的Many-To-Many。

4.下一步是创建数据库和SessionFactory:

创建一个数据库:

image

SessionFacotry:

image

这里的数据库连接我使用的是直接输入Server,db,username,pwd等,FluentNHibernate还支持其它各种数据库连接形式:

image

最后是添加记录:

image

运行程序,数据库中表会自动创建,且数据添加成功。

参考: http://wiki.fluentnhibernate.org/Getting_started#Your_first_project

          http://wiki.fluentnhibernate.org/Database_configuration

          http://wiki.fluentnhibernate.org/Fluent_configuration

Nick

序言

使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:

  • 一、基于注解(@Scheduled)
  • 二、基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。
  • 三、基于注解设定多线程定时任务

一、静态:基于注解

基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

1、创建定时器

使用SpringBoot基于注解来创建定时任务非常简单,只需几行代码便可完成。 代码如下:

复制代码

@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class SaticScheduleTask {
//3.添加定时任务
@Scheduled(cron = “0/5 * * * * ?”)
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks() {
System.err.println(“执行静态定时任务时间: “ + LocalDateTime.now());
}
}

复制代码

Cron表达式参数分别表示:

  • 秒(0~59) 例如0/5表示每5秒
  • 分(0~59)
  • 时(0~23)
  • 日(0~31)的某天,需计算
  • 月(0~11)
  • 周几( 可填1-7 或 SUN/MON/TUE/WED/THU/FRI/SAT)

@Scheduled:除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应的毫秒数即可。

复制代码

// Cron表达式范例:
每隔5秒执行一次:*/5 * * * * ? 每隔1分钟执行一次:0 */1 * * * ? 每天23点执行一次:0 0 23 * * ? 每天凌晨1点执行一次:0 0 1 * * ? 每月1号凌晨1点执行一次:0 0 1 1 * ? 每月最后一天23点执行一次:0 0 23 L * ? 每周星期天凌晨1点实行一次:0 0 1 ? * L

在26分、29分、33分执行一次:0 26,29,33 * * * ? 每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

复制代码

2、启动测试

启动应用,可以看到控制台打印出如下信息:

在这里插入图片描述

显然,使用@Scheduled 注解很方便,但缺点是当我们调整了执行周期的时候,需要重启应用才能生效,这多少有些不方便。为了达到实时生效的效果,可以使用接口来完成定时任务。

二、动态:基于接口

基于接口(SchedulingConfigurer)

1、导入依赖包:

复制代码

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.0.4.RELEASE</version>
</parent>

<dependencies>
    <dependency><!--添加Web依赖 -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency><!--添加MySql依赖 -->
         <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency><!--添加Mybatis依赖 配置mybatis的一些初始化的东西-->
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.1</version>
    </dependency>
    <dependency><!-- 添加mybatis依赖 -->
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

复制代码

2、添加数据库记录:

开启本地数据库mysql,随便打开查询窗口,然后执行脚本内容,如下:

复制代码

DROP DATABASE IF EXISTS `socks`;
CREATE DATABASE `socks`;
USE `SOCKS`;
DROP TABLE IF EXISTS `cron`;
CREATE TABLE `cron` (
`cron_id` varchar(30) NOT NULL PRIMARY KEY,
`cron` varchar(30) NOT NULL );
INSERT INTO `cron` VALUES (‘1’, ‘0/5 * * * * ?’);

复制代码

在这里插入图片描述

然后在项目中的application.yml 添加数据源:

spring:
datasource:
url: jdbc:mysql://localhost:3306/socks
username: root
password: 123456

3、创建定时器

数据库准备好数据之后,我们编写定时任务,注意这里添加的是TriggerTask,目的是循环读取我们在数据库设置好的执行周期,以及执行相关定时任务的内容。
具体代码如下:

复制代码

@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {

@Mapper
public interface CronMapper {
    @Select("select cron from cron limit 1")
    public String getCron();
}

@Autowired      //注入mapper
@SuppressWarnings("all")
CronMapper cronMapper;

/\*\*
 \* 执行定时任务.
 \*/ @Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

    taskRegistrar.addTriggerTask(
            //1.添加任务内容(Runnable)
            () -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),
            //2.设置执行周期(Trigger)
            triggerContext -> {
                //2.1 从数据库获取执行周期
                String cron = cronMapper.getCron();
                //2.2 合法性校验.
                if (StringUtils.isEmpty(cron)) {
                    // Omitted Code ..

}
//2.3 返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}

}

复制代码

4、启动测试

启动应用后,查看控制台,打印时间是我们预期的每10秒一次:
在这里插入图片描述

然后打开Navicat ,将执行周期修改为每6秒执行一次,如图:
在这里插入图片描述

查看控制台,发现执行周期已经改变,并且不需要我们重启应用,十分方便。如图:
在这里插入图片描述

注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。

三、多线程定时任务

基于注解设定多线程定时任务

1、创建多线程定时任务

复制代码

//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class MultithreadScheduleTask {

    @Async
    @Scheduled(fixedDelay = 1000)  //间隔1秒
    public void first() throws InterruptedException {
        System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\\r\\n线程 : " + Thread.currentThread().getName());
        System.out.println();
        Thread.sleep(1000 \* 10);
    }

    @Async
    @Scheduled(fixedDelay = 2000)
    public void second() {
        System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\\r\\n线程 : " + Thread.currentThread().getName());
        System.out.println();
    }
}

复制代码

注: 这里的@Async注解很关键

2、启动测试

启动应用后,查看控制台:
在这里插入图片描述

从控制台可以看出,第一个定时任务和第二个定时任务互不影响;

并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。

代码地址:https://github.com/mmzsblog/springboot-schedule

原文链接:https://www.mmzsblog.cn/articles/2019/08/08/1565247960802.html


作者:淼淼之森
欢迎任何形式的转载,但请务必注明出处。
如果你觉得本文还可以,那就点击一下推荐,让更多人看到吧!
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

昨天遇到了一个Bug,如果在浏览器中打开多个标签,并把其中一个标签拖拽到主窗口的一侧来划分出独立的一个区域,然后在新区域中通过点击加号键添加的新标签内不会被添加上WebBrowser。

说得好绕嘴啊,截张图吧:

**
**

而且没有加上WebBrowser的标签的标题是new content(我们在AvalonDock中给新标签的默认标题)而不是New Tab(客户代码中重新赋的值)。

OK,问题明了了,是不是新添加的标签的Got_Focus没能够挂到客户代码中的方法上去呢?

的确是这样,当我们把一个标签(DocumentContent)拖拽到一侧从而划分出一个新区域的时候,AvalonDock会创建一个新的DocumentPane来代表这个新区域。

我们浏览器中默认的DocumentPane是在XAML中声明的,声明时其NewContentAdded

事件就挂到了事件响应方法DocumentPane_NewContentAdded上。

而这个新区域中的DocumentPane是在AvalonDock内部的DocumentFloatingWindow类中创建的,其事件自然没有挂上来。

知道了这些,解决方案自然就有了。

在DocumentFloatingWindow中添加如下事件:

public static event EventHandler<NewDocumentPaneAddedByMouseEventArgs> NewDocumentPaneAddedByMouse;

其中用到的事件参数定义如下:

 public class NewDocumentPaneAddedByMouseEventArgs : EventArgs

    {

        public NewDocumentPaneAddedByMouseEventArgs(DocumentPane addedPane)

        {

            AddedPane = addedPane;

        }

        public DocumentPane AddedPane

        {

            get;

            private set;

        }

    }

依然尊规范在DocumentFloatingWindow中定义如下方法来触发事件:

private void OnNewDocumentPaneAddedByMouse(NewDocumentPaneAddedByMouseEventArgs args)

        {

            if (NewDocumentPaneAddedByMouse != null)

            {

                NewDocumentPaneAddedByMouse(this, args);

            }

        }

并在DocumentFloatingWindow中的ClonePane方法(鼠标拖拽时创建新DocumentPane的工作就是在这个方法中做的)中调用该方法来触发事件,修改后的ClonePane方法是这样的:

public override Pane ClonePane()

        {

            DocumentPane paneToAnchor = new DocumentPane();

            OnNewDocumentPaneAddedByMouse(new NewDocumentPaneAddedByMouseEventArgs(paneToAnchor));   

            ResizingPanel.SetEffectiveSize(paneToAnchor, new Size(Width, Height));

           while (HostedPane.Items.Count > 0)

            {

                paneToAnchor.Items.Add(

                    HostedPane.RemoveContent(0));

            }

            return paneToAnchor;

        }

我们只关心其中的OnNewDocumentPaneAddedByMouse(new NewDocumentPaneAddedByMouseEventArgs(paneToAnchor));  这一句就OK了。

好了,现在每当因鼠标拖拽而创建出一个新的DocumentPane时,都有一个事件会被触发,而且其传递的事件参数中还含有对新添加的DocumentPane实例的引用。这样订阅事件的地方(比如说我们的客户代码中)就可以通过该引用来把新添加的DocumentPane的NewContentAdded事件挂到某个方法上了(当然就是我们的DocumentPane_NewContentAdded方法了)。

接下来修改客户代码吧:

在浏览器窗口的构造方法中添加下面一句:

DocumentFloatingWindow.NewDocumentPaneAddedByMouse +=

                (object sender, NewDocumentPaneAddedByMouseEventArgs e) =>

                {

                    e.AddedPane.NewContentAdded += DocumentPane_NewContentAdded;

                };

用了lambda表达式,有点长,不过的确还只是一句啊。

这样每个通过鼠标拖拽出来的DocumentPane就和我们在XAML中声明的DocumentPane没什么两样了,它们的NewContentAdded 事件都挂到了DocumentPane_NewContentAdded方法上,这个方法做什么的来着?它做的就是给每一个新标签中置入一个新的WebBrowser。

好了,现在再运行一下,之前的问题不见了。

另外,如果你在使用Win7的话,把某个新标签拖拽出窗口,右击,选择Floating

**
**

然后就可以把这个拖拽出来的标签Dock到屏幕的一侧了

好了,到现在为止我们的多标签浏览器基本就运转起来了。如果您发现其中隐含的Bug或者不妥之处请不吝赐教哈!

另外,AvalonDock有两套Theme,我们之前的Restyle只修改了DocumentPaneStyles.xaml,要在Win7下看到想要的效果还要对aero.normalcolor.xaml做同样的修改。

好了,Over and out!

![[未整理/${filename}/Pasted image 20240920132738.png]]