【学习路径】PHP学习路径图

【学习路径】编程语言大体总述图




【学习路径】运维开发从入门到精通技能图

来源: .NET C#杂谈(1):变体 – 协变、逆变与不变 – HiroMuraki – 博客园
介绍变体的概念,并介绍其对C#的意义
了解C#进阶语言功能的使用(尤其是泛型、委托、接口)
变体这一概念用于描述存在继承关系的类型间的转化,这一概念并非只适用于C#,在许多其他的OOP语言中也都有变体概念。变体一共有三种:协变、逆变与不变。其中协变与逆变这两个词来自数学领域,但是其含义和数学中的含义几乎没有关系(就像编程语言的反射和光的反射之间的关系)。从字面上来看这三种变体的名字多少有点唬人,但其实际意思并不难理解。广泛来说,三种变体的意思如下:
或者换一种更具体的说法:
(注意是‘协变/量’而不是‘协/变量’)
为了方便说明三者的含义,先定义两个类:
class Cat { }
class SuperCat : Cat { }
上述代码定义了一个Cat类,并从Cat类派生出一个SupreCat类,如无特殊说明,后文的所有代码都会假设这两个类存在。下面利用这两个类逐一说明三种变体的含义。
例如,对于下列代码:
Cat cat = new SuperCat();
cat是一个引用Cat对象的变量,从类型安全的角度来说,它应该只能引用Cat对象,但是由于通常子类总是可以安全地转化为其某一基类,因此你也可以让其引用一个SuperCat对象。要实现这种用子类代替基类的操作就需要支持协变,由于OOP语言基本都支持子类向基类安全转化,所以协变在很多人看来是很十分自然的,也容易理解。
逆变有时也被称为抗变,你可能会觉得逆变的含义非常让人迷惑,因为通常来说基类是不能安全转化为其子类的,从类型安全的角度来看,这一概念应该似乎没有实际的应用场合,尤其是对于静态类型的语言。然而,考虑以下代码:
delegate void Action<T>();
void Feed(Cat cat)
{
...
}
Action<SuperCat> f = Feed;
Feed是一个‘参数为Cat对象的方法’,而f是一个引用‘参数为SuperCat对象的方法’的委托。从类型安全的角度来说,委托f应该只能引用参数为SuperCat对象的方法。然而如果你仔细思考上述代码,就会意识到既然委托f在调用时需要传入的是一个SuperCat对象,那么可以处理Cat类型的Feed方法显然也可以处理SuperCat(因为SuperCat可以安全转化为Cat),因此上面的代码从逻辑上来说是可以正常运行的。那么也就是说,本来需要SuperCat类型的地方(这里是委托的参数类型)现在实际给的却是Cat类型,要实现这种用基类代替子类的操作就需要逆变。
不过,结合上述,你会发现所谓逆变实际还是依靠‘子类可以向基类安全转化’这一原则,只是因为我们是从委托f的角度去考虑而已。
相比逆变和协变,不变更容易理解:只接受指定类型,不接受其基类或者子类。比如如果Cat类型具有不变性,那么下述代码将无法通过编译:
Cat cat = new SuperCat(); // 错误,cat只能引用Cat类型
显然不变从表现上来说是理所当然与符合常识的,故本文主要阐述协变与抗变。
同大多数语言一样,C#同样遵循‘基类引用可以指向子类’这一基本原则,因此对C#来说协变是普遍存在的:
Feed(Cat cat)
{
...
}
Cat cat = new SuperCat(); // 本来需要指向Cat对象的变量cat被指向了SuperCat对象,利用了协变性
SuperCat superCat = new SuperCat();
Feed(superCat); // 同理,Feed方法需要Cat对象但是传入的是SuperCat对象,利用了协变性
C#中的不变体现在值类型上,这是因为值类型都不允许继承与被继承,自然也不存在基类或子类的概念,也不存在类型间通过继承转化的情况。
C#中的逆变在一般情况下没有体现,因为将基类转化为派生类是不安全的,C#不支持这种操作。所以逆变对C#来说很多时候其实只是概念上的认识,真正让逆变对C#有意义的情况是使用泛型的场合,这在接下来就会提到。
从学习语言语法的角度来说,了解变体对学习C#的帮助其实不大,但如果想更进一步理解C#中泛型的设计原理,就有必要理解变体了。
理解变体对理解C#的泛型设计原理有重要意义,C#中泛型的类型参数默认为不变量,但可以是out与in关键字来指示类型为参数为协变量或者逆变量。简单来说,in关键字用于修饰输入参数的兼容性,out关键字用于修饰输出参数的兼容性。这一节会通过具体的泛型使用示例来解释变体概念对C#泛型的意义。
(1)输入参数的兼容性:逆变
考虑下面的泛型委托声明:
delegate void Action<T>(T arg);
上述委托可以接受一个参数类型为T,返回类型为TReturn的委托。下面来定义一个方法:
void Feed(Cat cat)
{
}
Foo是一个接受一个Cat对象,并返回一个SuperCat对象的方法。因此,下面的代码是理所当然的:
Action<Cat> act = Feed;
然而,从逻辑上来讲,下面的代码也应该是合法的:
Action<SuperCat> act = Feed;
委托act接受的参数类型为SuperCat,也就是说当调用委托act的时候传入的将会是一个SuperCat对象,显然SuperCat对象可以安全地转换为Foo所需要的Cat对象,因此这一转变是安全的。我们以委托act的视角来看:本来act应该引用的是一个‘参数类型为SuperCat’的方法,然而我们却把一个‘参数类型为Cat的’Feed方法赋值给了它,但结合上面的分析我们知道这一赋值行为是安全的。也就是说,本来此时泛型委托Action<T>中泛型类型参数T需要的类型是SuperCat,但现在实际给的类型却是Cat:

(红色是方法参数类型)
Cat是SuperCat的基类,也就是说这时候泛型委托Action<T>的类型参数T这个位置上出现了逆变。尽管从逻辑上来说这是合理的,但是C#中泛型类型参数默认具有不变性,因此如果要使上述代码通过编译,还需要将泛型委托Func的类型参数T声明为逆变量,在C#中,可以通过在泛型类型参数前添加in关键字将泛型参数声明为逆变量:
delegate void Action<in T>(T arg);
(2):输出参数的兼容性:协变
另一方面,下面的代码从逻辑上说也应该是合法的:
delegate T Func<T>();
SuperCat GetSuperCat()
{
...
}
Func<Cat> func = GetSuperCat;
委托func被调用时需要返回一个Cat对象,而GetSuperCat返回的是一个SuperCat对象,这显然是满足func的要求的:

同样以委托func的视角来看,本来需要类型Cat的地方现在实际给的类型是SuperCat,也就是说,此时出现了协变。同样的,如果要使上述代码通过编译,应该需要将Func的类型参数T声明为协变量,可以在泛型参数前添加out关键字将泛型类型参数声明为协变量:
delegate T Func<out TReturn>();
(1)输出参数的兼容性:协变
假设现有以下用于表示集合的接口声明与实现该接口的泛型类:
interface ICollection<T>
{
}
class Collection<T> : ICollection<T>
{
}
根据上述定义,理所当然的,下面的语句是合法的:
ICollection<Cat> cats = new Collection<Cat>();
然而,从逻辑上讲,下面的语句也应该是合法的:
ICollection<Cat> cats = new Collection<SuperCat>();
原因如下:既然SuperCat是Cat的子类,那么Collection中的任意一个SuperCat对象都应该可以安全转化为Cat对象,那么SuperCat的集合也应该视为Cat的集合。从事实上讲,若对任何一个需要Cat对象集合的方法,即便传入的是一个SuperCat对象的集合也应该可以正常工作。同样以类型为ICollection<Cat>的接口变量cats的视角来看,ICollection<Cat>类型上本来应该为Cat类型的地方现在被SuperCat类型所替代:

SuperCat代替了Cat,也就是说出现了协变,那么如果要使上述代码通过编译,则需要将类型参数T声明为协变量:
interface ICollection<out T>
{
}
C#中的IEnumerable接口就将其类型参数T声明为了协变量,因此下面的代码可以正常运行:
IEnumerable<Cat> cats = new List<SuperCat>();
(2)输入参数的兼容性:逆变
接着再来考虑一个接口与实现类:
interface IHand<T>
{
void Pet(T animal);
}
class Hand<T> : IHand<T>
{
void Pet(T animal) { ... }
}
下面的代码应该是合理的:
SuperCat cat = new SuperCat();
IHand<SuperCat> hand = new Hand<Cat>();
hand.Pet(cat);
原因如下:实现IHand<Cat>接口的Hand<Cat>的Pet方法可以处理Cat类型,显然其应该也可以处理作为Cat子类的SuperCat。同样的,以类型为IHand<SuperCat>的接口变量hand来看,本来应该需要类型为SuperCat的地方现在实际却是Cat类型:

Cat替代了SuperCat,也就是说此时发生了逆变。同样的,如果要让上述代码通过编译,需要将IHand<>的类型参数T声明为逆变量:
interface IHand<in T>
{
void Pet(T animal);
}
这样下述代码就可以通过编译:
IHand<SuperCat> hand = new Hand<Cat>();
与泛型委托和泛型接口不同的是,泛型方法不允许修改类型参数的变体类型,泛型方法的类型参数只能是不变量,因为让泛型方法的类型参数为变体没有意义。一方面,泛型方法的类型参数会在方法被调用时直接使用目标类型,因此不存在需要变体的情况:
void Pet<T>(T cat)
{
...
}
Pet(new Cat()); // 此时T为Cat
Pet(new SuperCat()); // 此时T为SuperCat
另一方面,你不能给一个方法赋值。
TReturn Foo<T, TReturn>(T t)
{
...
}
Foo = ...; // ???
显然上述代码是无法通过编译的。综上,给泛型方法的类型参数定义为协变量或者逆变量是没有意义的,因此也没有必要提供这一功能。
C#中的泛型类的类型参数同样只允许为不变量,这里以常用的泛型List<>为例,下面的代码是不允许的:
List<Cat> cats = new List<SuperCat>();
哪怕从概念上说一个SuperCat的对象的集合用于需要Cat对象的集合的场景是合法的,但是这一行为确实是不允许的,原因是CLR不支持。此外,C#限制协变量只能为方法的返回类型(后文会解释),所以下面的类定义是不可行的:
class Foo<out T>
{
public T Get() { } // 可以,协变量用于返回类型
public Set(T arg) { } // 错误,协变量不可用于方法参数
public T Field; // 错误,参数类型T既不是作为方法的返回类型,也不是作为方法的参数
}
既然连字段的类型都不能是协变的泛型类型,那么显然这样的类没有太大的意义。由于以上原因,泛型变体对于定义泛型类的意义不大。
C#对泛型中允许变体的类型参数有严格的使用限制,主要限制如下:
上述限制也说明了为何C#选择用out关键字来修饰协变量,in关键字来修饰逆变量。如果没有以上限制,可能出现一些很奇怪的操作,例如:
(1)假设:协变量可用于输入参数:
delegate void Action<out T>(T arg); // 此处协变量T作为了方法参数
void Call(SuperCat cat)
{
}
Action<Cat> f = GetCat;
上述代码中当委托f被调用时可能会传入一个Cat对象,然而其引用Call方法需要的是一个SuperCat对象,此时Cat类型无法安全转化为SuperCat类型,因此会出现运行时错误。
(2)假设:逆变量可用于方法的输出参数
delegate T Func<in T>(); // 此处类型参数T作为了方法返回类型
Cat GetCat()
{
...
}
Func<SuperCat> f = GetCat;
上述代码中当委托f被调用后,应当返回一个SuperCat对象,然而其引用的GetCat方法返回的只是一个Cat对象,同样,会出现运行时错误。
从上述例子中可以看出,对变体的适用范围进行限制显然有助于提高编写更安全的代码。
C#的数组支持协变,也就是说下面的代码是允许的:
Cat[] cats = new SuperCat[10];
咋一看没什么问题,SuperCat的数组当然可以安全转化为Cat数组使用,然而这意味着下述代码也能通过编译:
object[] objs = new Cat[10];
objs[0] = new Dog();
但显然这会在运行时出现错误。数组协变在某些场合下可能有用,但很多时候错误的使用或者误用会导致没必要的运行时错误,因此应当尽可能避免使用这一特性。
使用变体要求类型可以在引用类型的层面上进行转换,简单来说就是变体只作用于引用类型之间。因此尽管object是所有类型的基类,但是下述代码依然无法通过编译:
IEnumerable<object> data = new List<int>();
这是由于int为值类型,显然值类型无法在引用类型层面转化为object。
来源: 浏览器报Mixed Content错误的解决 – 简书
Mixed Content: The page at ‘https://xxx.cn’ was loaded over HTTPS, but requested an insecure script ‘http://res.wx.qq.com/open/js/jweixin-1.6.0.js’. This request has been blocked; the content must be served over HTTPS.

第一种
1.首先确定引入的资源可以在http和https下都能访问
比如:http://res.wx.qq.com/open/js/jweixin-1.6.0.js —> https://res.wx.qq.com/open/js/jweixin-1.6.0.js
2.在head里面写类似相对路径的形式
<script src="//res.wx.qq.com/open/js/jweixin-1.6.0.js" type="text/javascript"></script>
第二种
1.在页面中加入(meta)头中添加upgrade-insecure-requests
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
2.这将会把http请求转化为https请求。这样就不会再出现Mixed Content的错误了。
作者:jia林
链接:https://www.jianshu.com/p/0530240043e8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
HTTPS页面里动态的引入HTTP资源,比如引入一个js文件,会被直接block掉的.在HTTPS页面里通过AJAX的方式请求HTTP资源,也会被直接block掉的。
Mixed Content: The page at ‘xxx’ was loaded over HTTPS, but requested an insecure resource ‘xxx’. This request has been blocked; the content must be served over HTTPS.
解决办法:
页面的head中加入:
<meta http-equiv=”Content-Security-Policy” content=”upgrade-insecure-requests”>
意思是自动将http的不安全请求升级为https
好在 W3C 工作组考虑到了我们升级 HTTPS 的艰难,在 2015 年 4 月份就出了一个 Upgrade Insecure Requests 的草案,他的作用就是让浏览器自动升级请求。
在我们服务器的响应头中加入:
header("Content-Security-Policy: upgrade-insecure-requests");
转载:https://blog.csdn.net/u012259256/article/details/72039950
https://www.cnblogs.com/hustskyking/p/upgrade-insecure-requests.html
https://blog.csdn.net/haibo0668/article/details/82947917
来源: 百度地图引用报错A parser-blocking, cross site (i.e. different eTLD+1) script_雨中畅游的博客-CSDN博客
最近公司在搞vue项目,自己独立学习从头开始呀,都是泪。踩了好多坑,正好今天做到一个地方引入百度地图,之前做angular也做过,但是没有遇到这类问题。
百度地图引入首先要申请ak哈。这个网上很多东西。
自己遇到的是一个警告,看到控制台有东西就不爽,强迫症,看图:
反正后面一大串,这个在控制台很难看,虽然不影响效果。
解决办法:
直接将我们引入的api地址:
<script src=”http://api.map.baidu.com/api?v=2.0&ak=bUqWGO221Psm3MKPP3a7dKW7SepaA”></script>
后面ak是不正确的,自行注册哈,这里面的东西改一下,也就是api改成getscript就行
<script src=”http://api.map.baidu.com/getscript?v=2.0&ak=bUqWGO221Psm3MKPP3a7dKW7SepaA”></script>
然后刷新,瞬间控制台就干净了,舒服多了…
原因:其实就是说页面渲染使用了document.write(),感兴趣可以查询
参考大神链接:https://blog.csdn.net/b809220024/article/details/72565978
————————————————
版权声明:本文为CSDN博主「雨中畅游」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/viewyu12345/article/details/80705114
使用环境:
windows 7 下
wampserver 3.2.0-64bit
mySQL版本8.0.18 端口号:3308
php版本7.3.12
测试的源码:
<?php
$servername = “localhost”;
$username = “root”;
$password = “root”;
try {
$conn = new PDO(“mySQL:host=$servername;”, $username, $password);
echo “连接成功”;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
报错内容:
SQLSTATE[HY000] [1045] Access denied for user ‘root’@‘localhost’ (using password: YES)
网上找到很多关于修改 mi.ini ,httpd.conf ,httpd-vhost.conf,等文件的文章,最终测试的都不行。
以下是举例:
1.确认database.php文件配置正确。
2.检查.env文件
1.APP_ENV=local
2.APP_KEY=
3.APP_Debug=true
4.APP_LOG_LEVEL=Debug
5.APP_URL=http://localhost
6.DB_CONNECTION=mysql
7.DB_HOST=127.0.0.1
8.DB_PORT=3308
9.DB_DATABASE=homestead
10.DB_USERNAME=homestead
11.DB_PASSWORD=secret
12.BROADCAST_DRIVER=log
13.CACHE_DRIVER=file
14.SESSION_DRIVER=file
15.QUEUE_DRIVER=sync
16.REDIS_HOST=127.0.0.1
17.REDIS_PASSWORD=null
18.REDIS_PORT=6379
19.MAIL_DRIVER=smtp
20.MAIL_HOST=mailtrap.io
21.MAIL_PORT=2525
22.MAIL_USERNAME=null
23.MAIL_PASSWORD=null
24.MAIL_ENCRYPTION=null
25.PUSHER_APP_ID=
26.PUSHER_KEY=
27.PUSHER_SECRET=
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
取对应的数据库部分
“`php
1.DB_CONNECTION=mysql
2.DB_HOST=127.0.0.1
3.DB_PORT=3308
4.DB_DATABASE=homestead
5.DB_USERNAME=homestead
6.DB_PASSWORD=secret
1
2
3
4
5
6
7
8
3.修改.env文件。
1.DB_CONNECTION=mysql
2.DB_HOST=[你的数据库地址]
3.DB_PORT=[端口(3308)]
4.DB_DATABASE=[数据库]
5.DB_USERNAME=[用户名]
6.DB_PASSWORD=[密码]
1
2
3
4
5
6
4、检查httpd.conf。
5、httpd-vhost.conf
以上方法都不能解决问题,后来换了一串代码,如下:
<?php
$serve = ‘mysql:host=localhost:3308;dbname=hzpccc;charset=utf8’;
$username = ‘root’;
$password = ‘root’;
try{ // PDO连接数据库若错误则会抛出一个PDOException异常
$PDO = new PDO($serve,$username,$password);
$result = $PDO->query(‘select * from hydt’);
$data = $result->fetchAll(PDO::FETCH_ASSOC); // PDO::FETCH_ASSOC表示将对应结果集中的每一行作为一个由列名索引的数组返回
print_r($data);
} catch (PDOException $error){
echo ‘connect failed:’.$error->getMessage();
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
带上端口号就没问题了,怎么回事?我是新手,感觉端口不对才找到这个代码,大神可以指点下什么原因。
————————————————
版权声明:本文为CSDN博主「哦卖糕」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/omaigao/article/details/104476516
来源: 使用宝塔创建PHP网站,出现”no input file specified”怎么办? – 知乎
使用宝塔建站也有很长一段时间了,一般情况都很正常,偶尔也会出现一些莫名其妙的问题,都能快速解决。
但是今天建站的时候却遇到了一个很奇怪的问题:先是出现”no input file specified”错误,后来出现500错误。百度、搜狗搜索之后,发现很多人都遇到过相似的问题,一堆专家在下面回答,却几乎没有可用的解决方案,提出的问题也没有得到最终的解决。
后来经过对各种线索和日志的分析,已经解决了这个问题,这里整理出来。也许这是全网唯一的可用方案,不是说技术独此一家,别人都不会,我还没有这么高傲自大;而是也许有人会,但不愿或懒得做这种编写文档的事情,那么现在遇到这种问题的人,真是只能绕弯想其他办法了。
此文编写+实验环境重建,费时2小时,如果你有缘看到,并且觉得有用,希望花一秒钟时间在下面点个赞再走吧!
因为问题已经解决,那么只能重新试图还原问题的现场。于是从头做了一次,把详细的过程写下来,为大家避坑。

1、进入域名管理,把这个test解析到服务器的IP地址。
2、进入服务器管理,把http://test.xxx.com添加进白名单。
请注意,第3,4两步不在同一界面!
因为每个ISP界面可能不一样,此处就不截图了。如果有问题,同样可以私信询问!
3、此时,刚刚添加的网站已经可以通过二级域名访问了。

4、下面上传网站文件到根目录,删除创建网站时自动生成的index.html和404.html,因为我们有了自己的入口文件index.php。
5、尝试打开网站http://test.xxx.com,果然再次出现了错误提示

此时,我要说两个重要的东西:.user.ini 和 .htaccess ,这两个文件也是在创建网站时自动生成的。
.htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置;.user.ini则指定了PHP中的basedir,个人理解就是网站的运行目录。
这两个文件不能删
“No input file specified.”意思即没有指定输入文件。接下来排错。
1、首先想到的是没有运行index.php,于是把文件中内容修改为只显示一行文本,果然刷新后依然没有显示,说明确实没有运行index.php,也就是说可能问题出在Apache、php或网站的配置上。
2、排除了PHP版本的问题
3、打开宝塔中此网站设置,这里主要看了伪静态和配置文件两个板块。创建网站后伪静态里面其实是空白的,没有什么设置;而配置文件中看起来也没有什么 问题。

其实在解决问题的过程中还是乱填了一些东西,因为都是从网上搜索出来的,所以都没有成功,最后还是恢复成了默认的样子。
4、在设置里面没有实质性的突破后,回到服务器上查找问题。/www/下找到wwwlogs文件夹,必定是保存的运行日志,大概可以从这里去找找问题

5、ls下来,可以看到里面是所有已建网站的运行日志和错误日志。
6、找到不能打开的网站的错误日志,more一下。发现里面讲到“is not within the allowed path(s):”这个目录不在允许的路径中。

日志中提到这个目录不在允许的路径中。其实很说明问题。
为什么不被允许?允许的路径是哪里?是否加入到允许的路径就可以被访问?
带着这个问题进行思考,我们通常会发现,出现问题时走了很多弯路,而在通过对各种线索的分析后,真相会逐渐浮出水面。
既然错误提示我们权限的问题,那么就从权限上去找答案。
宝塔里面所建立的网站,是可以被访问的,这在建站完成时证明是成功的,说明宝塔系统没有问题。
then?
Apache管理WEB服务器的环境,也是没有问题。
PHP56管理PHP环境,这就是经过推理后的症结所在。
这里讲的当然是宝塔里面的PHP56。
我们知道PHP的配置文件是php.ini,可是宝塔里的PHP配置文件在哪里呢?

进入到PHP的配置界面

按ctrl+f,打开窗口搜索界面,搜索什么内容呢?
从前面错误日志的分析来看,新网站的目录没有加进open_basedir,也就是说其他能访问的网站都在这个文件夹里!
所以搜索open_basedir,或者/www/wwwroot/,也就是www的根目录。结果非常理想,很快就得到我想寻找的内容 。

果然在open_basedir下没有新建网站的目录。注意一下“open_basedir=”后面的路径是用 : 来分隔,所以在最后加上”:/www/wwwroot/http://test.xxx.com“这个路径,重启PHP,再次测试,正常打开网站。
至此,这个问题完美解决。
可能解决的方法非常简单,但在处理的过程中,其实经历了很多曲折。
码字不易,如果你有缘看到,并且觉得有用,希望花一秒钟时间在下面点个赞再
上位机:
上位机指可以直接发送操作指令的计算机或单片机,一般提供用户操作交互界面并向用户展示反馈数据。
典型设备类型:电脑,手机,平板,面板,触摸屏
下位机:
下位机指直接与机器相连接的计算机或单片机,一般用于接收和反馈上位机的指令,并且根据指令控制机器执行动作以及从机器传感器读取数据。
典型设备类型:PLC,STM32,51,FPGA,ARM等各类可编程芯片
上位机软件:
用于完成上位机操作交互的软件被定义为“上位机软件”;
为了实现以上过程,上位机和下位机都需要单独编程,都需要专门的开发人员在各自两个平台编写代码。
上位机与下位机关系示意图:

实现上下位机之间的通信需要了解以下2个概念:
这个通信协议(通信方式)是实现上位机与下位机之间数据交换的基本通道。
这就意味着,只要通信协议(通信方式)可以建立,上位机软件可以是任意开发语言和任意开发平台,下位机也可以使用任意类型的单片机
开发上位机软件与其他软件最大的区别就是“上位软件要连接设备并与之通信”。因此引发了上位机软件开发的各项技术:通信方式多样性、私有协议定制、通信框架的产生。
1、应用的场合有:
2、PC软件的没落与前景
随着智能手机和平板电脑的普及,现在大家都很少用电脑了,因为手机给用户提供了很大的使用自由,随时随地使用种类繁多的app。不仅方便高效还非常美观。现在能用手机app代替的软件,都没有人用相应电脑软件了。因此整体上PC软件在持续走衰,买台式机电脑的人也因此大量流失了。
那么PC和PC软件就会没落甚至消失吗?就现在来看是不会(现在2020年)不仅不会还会持续好一段时间。为什么说暂时不会消失呢?是因为还有好些软件手机app和平板电脑无法替代。
app无法代替PC软件的一些原因:
这里列举一些app无法替代电脑软件的例子:
视频剪辑软件、photoshop、CAD、绘制电路板软件、绘制机械三维图软件,彩超等医疗器械对应的上位机软件
行业专用设备配的上位机软件、军工设备配备的上位机软件、程序猿编程软件
除去如上所列的这些特殊软件需要使用PC的优势,剩余的软件均被手机软件所占领。手机软最大胜利就是他的便携性。
当然这也合情合理,出现了新的设备平台瓜分了PC软件的天下。技术总是要前进嘛。未来发展手机也可能会被抛弃。
因此呢,根据现在的情况来看pc软件还有好长一段时间会持续存在。
现在遍地是手机app程序猿,对于pc软件开发者建议是,要么转行要么在特定行业中开发PC软件,这样技术积累才能持久,也能持久发展。
使用PC软件的行业主要有:医疗器械、实验室器械、军工、电力行业。
为了追求便携性方便用户使用,其实很多设备专用软件也尝试开发了app端上位机软件。
但是经实际检验遇到如下方面的问题:
1、操作不方便,对于操作复杂的设备来说,使用app上位机软件时,还要腾出一只手或两只手来操作反而不方便了
2、接外设不方便,遇到最多的是接打印机不方便(虽然能接网络打印机,但实际体验差)
3、通信不方便,虽然可以使用蓝牙或无线网络来控制下位机设备,但是不够稳定,尤其特定行业还使用不了任何无线连接
4、一般工业设备的上位机软件会有多人交替操作,由于手机太便携,因此也同意丢失或被盗。
因此对于特殊行业又想用移动设备的便携性又想获得相对好的操作体验,可以使用运行Android系统的平板。
肯德基店的点餐大屏幕(Android)、公交车上的广告屏幕(Android)、车站的导航屏幕(PC+win7)、移动手机充电站、自助贩卖机。
来源: .NET Core微服务之服务间的调用方式(REST and RPC) – EdisonZhou – 博客园
Tip: 此篇已加入.NET Core微服务基础系列文章索引
微服务之间的接口调用通常包含两个部分,序列化和通信协议。常见的序列化协议包括json、xml、hession、protobuf、thrift、text、bytes等;通信比较流行的是http、soap、websockect,RPC通常基于TCP实现,常用框架例如dubbo,netty、mina、thrift。
REST:严格意义上说接口很规范,操作对象即为资源,对资源的四种操作(post、get、put、delete),并且参数都放在URL上,但是不严格的说Http+json、Http+xml,常见的http api都可以称为Rest接口。
RPC:即我们常说的远程过程调用,就是像调用本地方法一样调用远程方法,通信协议大多采用二进制方式。
HTTP相对更规范,更标准,更通用,无论哪种语言都支持HTTP协议。如果你是对外开放API,例如开放平台,外部的编程语言多种多样,你无法拒绝对每种语言的支持,相应的,如果采用HTTP,无疑在你实现SDK之前,支持了所有语言,所以,现在开源中间件,基本最先支持的几个协议都包含RESTful。
RPC协议性能要高的多,例如Protobuf、Thrift、Kyro等,(如果算上序列化)吞吐量大概能达到http的二倍。响应时间也更为出色。千万不要小看这点性能损耗,公认的,微服务做的比较好的,例如,netflix、阿里,曾经都传出过为了提升性能而合并服务。如果是交付型的项目,性能更为重要,因为你卖给客户往往靠的就是性能上微弱的优势。
所以,最佳实践一般是对外REST,对内RPC,但是追求极致的性能会消耗很多额外的成本,所以一般情况下对内一般也REST,但对于个别性能要求较高的接口使用RPC。

这里假设有两个服务,一个ClinetService和一个PaymentService,其中PaymentService有两部分,一部分是基于REST风格的WebApi部分,它主要是负责一些对性能没有要求的查询服务,另一部分是基于TCP的RPC Server,它主要是负责一些对性能要求高的服务,比如支付和支出等涉及到钱的接口。假设User在消费ClientService时需要调用PaymentService根据客户账户获取Payment History(走REST)以及进行交易事务操作(走RPC)。
使用过Java Feign Client的人都知道,一个好的声明式REST客户端可以帮我们省不少力。在.NET下,园子里的大大老九就写了一款类似于Feign Client的REST Client:WebApiClient。WebApiClient是开源在github上的一个httpClient客户端库,内部基于HttpClient开发,是一个只需要定义C#接口(interface),并打上相关特性,即可异步调用http-api的框架 ,支持.net framework4.5+、netcoreapp2.0和netstandard2.0。它的GitHub地址是:https://github.com/dotnetcore/WebApiClient
如何安装?
NuGet>Install-Package WebApiClient-JIT
Step1.定义HTTP接口
[HttpHost("http://yourgateway:5000")]
public interface IPaymentWebApi: IHttpApi
{
// GET api/paymentservice/history/edisonzhou
// Return 原始string内容
[HttpGet("/api/paymentservice/history/{account}")]
ITask<IList<string>> GetPaymentHistoryByAccountAsync(string account);
}
这里需要注意的是,由于我们要走API网关,所以这里定义的HttpHost地址是一个假的,后面具体调用时会覆盖掉,当然你也可以直接把地址写在这里,不过我更倾向于写到配置文件中,然后把这里的HttpHost设置注释掉。
Step2.在Controller中即可异步调用:
[Route("api/[controller]")]
public class PaymentController : Controller
{
private readonly string gatewayUrl;public PaymentController(IConfiguration _configuration)
{
gatewayUrl = _configuration["Gateway:Uri"];
}
[HttpGet("{account}")]
public async Task<IList<string>> Get(string account)
{
using (var client = HttpApiClient.Create<IPaymentWebApi>(gatewayUrl))
{
var historyList = await client.GetPaymentHistoryByAccountAsync(account);
// other business logic code here
// ......
return historyList;
}
}
}
当然你也可以在Service启动时注入一个单例的IPaymentServiceWebApi实例,然后直接在各个Controller中直接使用,这样更加类似于Feign Client的用法:
(1)StartUp类注入
public void ConfigureServices(IServiceCollection services)
{
// IoC - WebApiClient
services.AddSingleton(HttpApiClient.Create<IPaymentServiceWebApi>(Configuration["PaymentService:Url"]));
}
(2)Controller中直接使用
[HttpPost]
public async Task<string> Post([FromBody]ModelType model, [FromServices]IPaymentServiceWebApi restClient)
{
......
var result = await restClient.Save(model);
......
}
这里PaymentService的实现很简单,就是返回了一个String集合:
// GET api/history/{account}
[HttpGet("{account}")]
public IList<string> Get(string account)
{
// some database logic
// ......
IList<string> historyList = new List<string>
{
"2018-06-10,10000RMB,Chengdu",
"2018-06-11,11000RMB,Chengdu",
"2018-06-12,12000RMB,Beijing",
"2018-06-13,10030RMB,Chengdu",
"2018-06-20,10400RMB,HongKong"
};
return historyList;
}
最终调用结果如下:

在服务众多,且单个服务就部署了多个实例的情况下,我们可以通过API网关进行中转,但是当部分场景我们不需要通过API网关进行中转的时候,比如:性能要求较高,负载压力较小单个实例足够等,我们可以直接与要通信的服务进行联接,也就不用从API网关绕一圈。
Step1.改一下HTTP接口:
[HttpHost("http://paymentservice:8880")]
public interface IPaymentDirectWebApi: IHttpApi
{
// GET api/paymentservice/history/edisonzhou
// Return 原始string内容
[HttpGet("/api/history/{account}")]
ITask<IList<string>> GetPaymentHistoryByAccountAsync(string account);
}
同理,这里的HttpHost也是后面需要被覆盖的,原因是我们将其配置到了配置文件中。
Step2.改一下调用代码:
[Route("api/[controller]")]
public class PaymentController : Controller
{
private readonly string gatewayUrl;
private readonly string paymentServiceUrl;
public PaymentController(IConfiguration _configuration)
{
gatewayUrl = _configuration["Gateway:Uri"];
paymentServiceUrl = _configuration["PaymentService:Uri"];
}
[HttpGet("{account}")]
public async Task<IList<string>> Get(string account)
{
#region v2 directly call PaymentService
using (var client = HttpApiClient.Create<IPaymentDirectWebApi>(paymentServiceUrl))
{
var historyList = await client.GetPaymentHistoryByAccountAsync(account);
// other business logic code here
// ......
return historyList;
}
#endregion
}
最终调用结果如下:


Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
当然,还有gRPC也可以选择,不过从网上的性能测试来看,Thrift性能应该优于gRPC 2倍以上,但是gRPC的文档方面要比Thrift友好很多。
(1)下载Thrift (这里选择Windows版)

下载完成后解压,这里我将其改名为thrift.exe(去掉了版本号),一会在命令行敲起来更方便一点。
(2)编写一个PaymentService.thrift,这是一个IDL中间语言
namespace csharp Manulife.DNC.MSAD.Contracts
service PaymentService {
TrxnResult Save(1:TrxnRecord trxn)
}
enum TrxnResult {
SUCCESS = 0,
FAILED = 1,
}
struct TrxnRecord {
1: required i64 TrxnId;
2: required string TrxnName;
3: required i32 TrxnAmount;
4: required string TrxnType;
5: optional string Remark;
}
(3)根据thrift语法规则生成C#代码
cmd>thrift.exe -gen csharp PaymentService.thrift

(4)创建一个Contracts类库项目,将生成的C#代码放进去

(1)新增一个控制台项目,作为我们的Payment Service RPC Server,并引用Contracts类库项目

(2)引入thrift-netcore包:
NuGet>Install-Package apache-thrift-netcore
(3)加入一个新增的PaymentService实现类
public class PaymentServiceImpl : Manulife.DNC.MSAD.Contracts.PaymentService.Iface
{
public TrxnResult Save(TrxnRecord trxn)
{
// some business logic here
//Thread.Sleep(1000 * 1);
Console.WriteLine("Log : TrxnName:{0}, TrxnAmount:{1}, Remark:{2}", trxn.TrxnName, trxn.TrxnAmount, trxn.Remark);
return TrxnResult.SUCCESS;
}
}
这里输出日志仅仅是为了测试。
(4)编写启动RPC Server的主程序
public class Program
{
private const int port = 8885;
public static void Main(string[] args)
{
Console.WriteLine("[Welcome] PaymentService RPC Server is lanuched...");
TServerTransport transport = new TServerSocket(port);
var processor = new Manulife.DNC.MSAD.Contracts.PaymentService.Processor(new PaymentServiceImpl());
TServer server = new TThreadedServer(processor, transport);
// lanuch
server.Serve();
}
}
(5)如果是多个服务实现的话,也可以如下这样启动:
public static void Main(string[] args)
{
Console.WriteLine("[Welcome] PaymentService RPC Server is lanuched...");
TServerTransport transport = new TServerSocket(port);
var processor1 = new Manulife.DNC.MSAD.Contracts.PaymentService.Processor(new PaymentServiceImpl());
var processor2 = new Manulife.DNC.MSAD.Contracts.PayoutService.Processor(new PayoutServiceImpl());
var processorMulti = new Thrift.Protocol.TMultiplexedProcessor();
processorMulti.RegisterProcessor("Service1", processor1);
processorMulti.RegisterProcessor("Service2", processor2);
TServer server = new TThreadedServer(processorMulti, transport);
// lanuch
server.Serve();
}
在ClientService中也引入apache-thrift-netcore包,然后在调用的地方修改如下:
[HttpPost]
public string Post([FromBody]TrxnRecordDTO trxnRecordDto)
{
// RPC - use Thrift
using (TTransport transport = new TSocket(
configuration["PaymentService:RpcIP"],
Convert.ToInt32(configuration["PaymentService:RpcPort"])))
{
using (TProtocol protocol = new TBinaryProtocol(transport))
{
using (var serviceClient = new PaymentService.Client(protocol))
{
transport.Open();
TrxnRecord record = new TrxnRecord
{
TrxnId = GenerateTrxnId(),
TrxnName = trxnRecordDto.TrxnName,
TrxnAmount = trxnRecordDto.TrxnAmount,
TrxnType = trxnRecordDto.TrxnType,
Remark = trxnRecordDto.Remark
};
var result = serviceClient.Save(record);
return Convert.ToInt32(result) == 0 ? "Trxn Success" : "Trxn Failed";
}
}
}
}
private long GenerateTrxnId()
{
return 10000001;
}
最终测试结果如下:

本篇简单的介绍了下微服务架构下服务之间调用的两种常用方式:REST与RPC,另外前面介绍的基于消息队列的发布/订阅模式也是服务通信的方式之一。本篇基于WebApiClient这个开源库介绍了如何进行声明式的REST调用,以及Thrift这个RPC框架介绍了如何进行RPC的通信,最后通过一个小例子来结尾。最后,服务调用的最佳实践一般是对外REST,对内RPC,但是追求极致的性能会消耗很多额外的成本,所以一般情况下对内一般也REST,但对于个别性能要求较高的接口使用RPC。
远方的行者,《微服务 RPC和REST》
杨中科,《.NET Core微服务课程:Thrift高效通讯》
醉眼识朦胧,《Thrift入门初探–thrift安装及java入门实例》
focus-lei,《.net core下使用Thrift》
宝哥在路上,《Thrift性能测试与分析》
来源: .NetCore使用protobuf 生成C#代码(Grpc) – zeran – 博客园
我使用vs2019,需要安装几个nuget包
Google.protobuf
Google.protobuf.Tools
Grpc.AspnetCore
Grpc.Net.Client
Grpc.Tools
编写.proto文件
syntax ="proto3";
option csharp_namespace="WeService01.Controllers";
package WeService01.Controllers;
message users{
int32 ID=1;
string name=2;
string login_name=3;
int32 roleid=4;
bool is_man=5;
}
message getusers{
int32 ID=1;
string name=2;
}
message getusersresponse{
int32 code=1;
string msg=2;
users usermodel =3;
}
service userservice{
rpc Getuser(getusers) returns (getusersresponse);
rpc Add(users) returns (getusersresponse);
};
下载proto的是生成包
https://github.com/protocolbuffers/protobuf/releases
下载protoc编译工具并解压

然后执行命令,也可以在vs的“程序包管理器控制台”执行(E:\protoc-3.15.2-win64\bin\protoc 可以看命令参数)
E:\protoc-3.15.2-win64\bin\protoc user.proto –csharp_out=E:\gitee_public\WeService01\WeService01\proto –proto_path=E:\gitee_public\WeService01\WeService01\proto
E:\protoc-3.15.2-win64\bin\为protoc.exe所在位置,–csharp_out 为生成文件的位置,–proto_path为需要编译的.proto文件的目录