精华内容
下载资源
问答
  • [ 代码审计篇 ] 代码审计思路 详解(一)
    多人点赞
    2021-12-16 22:28:46

    源代码审计是网络安全技术工作的重要内容,是安全岗位必备技能。

    渗透测试岗几乎都会问道代码审计,这篇文章就来聊一聊代码审计的思路。

    这篇文章就来聊一聊什么是代码审计,什么是静态测试 ?

    重点部分:代码审计工具介绍,代码审计的思路介绍。

    不要觉得代码审计很容易,也不要觉得代码审计很难,重在坚持,重在分析。

    文中出现的细节在后续中都会有详细解读!!!

    1. 根据敏感关键字回溯参数传递过程  -->

    2. 查找可控变量,正向追踪变量传递过程  -->

    3. 寻找敏感功能点,通读功能点代码  -->

    4. 直接通读全文代码


    目录

    一、代码审计和静态测试介绍

    1. 什么是代码审计 (代码审计)

    2. 什么是静态测试(静态测试)

    3. 静态测试和代码审计的关系

    二、代码审计对象和内容介绍

    1. 代码审计对象:

    2. 代码审计内容包括

    三、代码审计要熟悉的技术

    1. 编程语言  

    2. 渗透技巧  

    1. 为什么要懂渗透技巧

    1. 工具渗透:

    2.  手工渗透

    3. 辅助技术

    1. 熟悉常见协议

    2. 程序搭建

    3. URL链接构造或者URL路由  

    4. 了解SQL语句及数据库特性

    5. 了解中间件及服务器特性

    6. 审计辅助工具

    3. 漏洞挖掘

    1. 必备知识:

    2. 技能提高过程

    三、代码审计工具

    2. PHP

    1. Seay

    2. RIPS

    3. Fortify SCA

    2. JAVA

    1. FindBugs IDEA

    2. Android Lint

    3. C++

    1.  Flawfinder

    4. JS

    5. Go

    6. Python

    7. 多种语言的安全代码检查工具

    8. 商业代码审计工具

    1. 静态分析工具

    2. 动态分析工具

    四、代码审计思路:1. 根据敏感关键字回溯参数传递过程

    1. PHP代码审计敏感函数

    1. 预定义的变量[常规外部提交的变量]

    2. regist_globle=on(未初始化的变量)

    3. 变量覆盖(未被初始化的覆盖的前定义的变量)

    4. 变量的传递存储(中转的变量)

    5. 文件包含漏洞(包含任意文件)

    6. 代码执行(执行任意代码)

    7. 命令执行(执行任意命令漏洞)

    8. 漏洞挖掘时SQL注入漏洞和XSS漏洞需注意的关键字

    1. SQL注入漏洞

    2. XSS漏洞关键字

    2. Java代码审计----敏感函数

    1. 命令执行函数

    2. 文件上传函数

    3. SSRF函数

    4. CORS跨域漏洞CORS跨域设置

    5. Jsonp劫持

    6. 反序列化

    7. XSS漏洞

    8. session失效时间问题

    9. 用户退出,session未销毁问题

    五、代码审计思路:2. 查找可控变量,正向追踪变量传递过程

    六、代码审计思路:3. 寻找敏感功能点,通读功能点代码

    1. 文件上传功能

    2. 文件管理功能

    3. 登录认证功能

    3. 找回密码功能

    七、代码审计思路:4. 直接通读全文代码

    1. 逻辑性

    2. 目的性

    1. 函数集文件

    2. 配置文件

    3. 安全过滤文件

    4. index文件

    八、专栏分享


    一、代码审计和静态测试介绍

    1. 什么是代码审计 (代码审计

    代码审计(Code audit)是一种以发现程序错误,安全漏洞和违反程序规范为目标的源代码分析。软件代码审计是对编程项目中源代码的全面分析,旨在发现错误,安全漏洞或违反编程约定。 它是防御性编程范例的一个组成部分,它试图在软件发布之前减少错误。

    说白了:代码审计检查源代码中的安全缺陷,检查程序源代码是否存在安全隐患,或者有编码不规范的地方,通过自动化工具或者人工审查的方式,对程序源代码逐条进行检查和分析,发现这些源代码缺陷引发的安全漏洞,并提供代码修订措施和建议。

    2. 什么是静态测试(静态测试

    静态测试是指不运行被测程序本身,仅通过分析或检查源程序的文法、结构、过程、接口等来检查程序的正确性,找出程序中存在的风险,例如不匹配的参数、不适当的循环嵌套和分支嵌套、不允许的递归、未使用过的变量、空指针的引用和可疑的计算等。静态测试结果可用于进一步的查错,并为测试用例选取提供指导。

    3. 静态测试和代码审计的关系

    静态测试包括代码检查、静态结构分析、代码质量度量等。静态测试主要由人工进行,充分发挥人的逻辑思维优势,同时借助软件工具自动进行。其中代码检查包括代码走查、桌面检查、代码审查等,主要检查代码和设计的一致性、代码对标准的遵循、可读性,代码的逻辑表达的正确性、代码结构的合理性等方面。

    二、代码审计对象和内容介绍

    1. 代码审计对象:

    代码审计对象包括并不限于对Windows和Linux系统环境下的以下语言进行审核:

    php,java,JavaScript、C/C++,C#,jsp,asp,net。

    2. 代码审计内容包括

    1. 前后台分离的运行架构

    2. WEB服务的目录权限分类

    3. 认证会话与应用平台的结合

    4. 数据库的配置规范

    5. SQL语句的编写规范

    6. WEB服务的权限配置

    7. 对抗爬虫引擎的处理措施

    三、代码审计要熟悉的技术

    1. 编程语言  

    1. 前端语言 html/javascript/dom 元素使用主要是为了挖掘xss漏洞 jquery 主要写一些涉及到CSRF脚本使用的或者DOM型XSS,JSON劫持等。

    2. 后端语言也要熟悉,基础语法要知道。例如:

    变量类型,常量,数组(python 是列表,元组,字典),对象,类的调用,引用等, MVC设计模式要清楚,因为大部分目标程序都是基于MVC写的,包括不限于php,python,java。

    3. 不用会写,但是一定能看懂,而且要看懂逻辑,知道哪些功能点会用什么方式去写,可能会出现什么类型漏洞,方便挖掘常规类型漏洞,更方便挖掘逻辑漏洞。

    2. 渗透技巧  

    1. 为什么要懂渗透技巧

    1. 在于你找到漏洞的时候,通常的开发功底是不足以构造 payload 的,需要一些特殊的 payload 构造方式。

    2. 你在找漏洞时,可以辅助你更快的去挖掘漏洞。

    1. 工具渗透:

    例如 sqlmap,awvs,burpsuite 等,能用工具挖掘到,就没必要人工审计了,你要做的只是辅助调试。

    2.  手工渗透

    手工渗透的情况下,如果有源代码,那我们就可以去配合一些代码审计,以便于漏洞的挖掘。

    3. 辅助技术

    1. 熟悉常见协议

    例如:HTTP,HTTPS,TCP,dict://,file://等等

    懂得 Header 头如何伪造,比如:

    XFF注入时的x-forward-for,cookie注入,CRLF身份请求伪造等。

    2. 程序搭建

    代码审计的时候要学会程序搭建,不然静态审计时,无法进行动态调试。

    方便你更快更高效挖掘漏洞。

    3. URL链接构造或者URL路由  

    后续详解

    4. 了解SQL语句及数据库特性

    这个主要涉及到SQL注入及sql注入的payload构造绕过。

    5. 了解中间件及服务器特性

    有的代码漏洞 是基于中间件及服务器特性造成的。例如:

    IIS6.0的解析漏洞,nginx的解析漏洞等。

    6. 审计辅助工具

    (后面有详解)

    1. IDE,phpstrom 审计工具追踪代码时用到,可与xdebug绑定使用方便调试。

    2. 源代码审计工具 rips,seay审计工具,辅助你更快的找到漏洞产生点。

    3. 漏洞挖掘

    1. 必备知识:

    1. 懂得漏洞类型产生原理

    2. 懂得危险函数的参数不当使用可造成的漏洞威胁。例如:

            涉及到命令执行代码执行的eval,assert,array_map,usort等

             本身函数的脆弱性,is_numeric,md5等。

    3. 晓得php函数的脆弱性。例如:

    ==与===

    ===并不是强大无比不可绕过的,也要结合代码设计逻辑。
    4. php的淫技技巧。

    5. php版本及配置不当结合函数不当利用造成的漏洞威胁。

    ......

    2. 技能提高过程

    1. demo案例练习  -->

    2. 已出漏洞代码审计案例分析  -->

    3. 小型cms单一漏洞实例练习  -->

    4. 小型cms漏洞多种类型漏洞实例挖掘练习  -->

    5. 框架漏洞挖掘实例练习  -->

    6. 技巧挖掘

    三、代码审计工具

    想做好代码审计,好的代码审计辅助工具是必不可少的,可让代码审计事半功倍。

    下面介绍一些代码审计辅助工具。(后续详细介绍工具的使用)

    2. PHP

    1. Seay

    1. 一键自动化白盒审计,当发现可以漏洞后,会在下方列表框显示漏洞信息。

    2. 代码调试,方便审计过程中测试代码。

    3. 正则编码,支持MD5、UR1、Base64、Hex、ASCII、Unicode等多种编码解码转换功能。

    4. 自定义插件及规则

    2. RIPS

    1. 能够检测XSS、SQL注入、文件泄露、本地/远程文件包含、远程命令执行以及更多种类型的漏洞。

    2. 有5种级别选项用于显示以及辅助调试扫描结果。

    3. 标记存在漏洞的代码行。

    4. 对变量高亮显示。

    5. 在用户定义函数上悬停光标可以显示函数调用。

    6. 在函数定义和调用之间灵活跳转。

    7. 详细列出所有用户定义函数(包括定义和调用)、所有程序入口点(用户输入)和所有扫描过文件(包括include的文件)。

    8. 以可视化的图表展示源代码文件、包含文件、函数及其调用。

    9. 仅用几个鼠标点击就可以使用CURL创建针对检测到漏洞的EXP实例。

    10. 详细列出每个漏洞的描述、举例、PoC、补丁和安全函数。

    11. 7种不同的语法高亮显示模式。

    12. 使用自顶向下或者自底向上的方式追溯显示扫描结果。

    13. 一个支持PHP的本地服务器和浏览器即可满足使用需求。

    14. 正则搜索功能。

    3. Fortify SCA

    分析过程中与它特有的软件安全漏洞规则进行全面的匹配、搜索,在最终的漏洞结果中,包括详细的漏洞心理,以及漏洞相关的安全知识说明

    2. JAVA

    1. FindBugs IDEA

    能发现规范和bug的,只能静态分析

    2. Android Lint

    它会给出所有在这个项目中不规范的编码、多余的资源、可能的bug、或者其它的一些问题,然后会给出修改的建议供我们参考。

    3. C++

    1. Flawfinder

    Flawfinder是一个C/C代码的开源安全审查工具,采用内建语法缺陷数据库,能够标记类似缓冲溢出、格式字符串、竞争条件、随机数获取方面的问题。Flawfinder是一个非常简单的工具,采用的算法也不复杂,可以看做是一个基于词典的源代码静态分析器。

    4. JS

    eslint、NodeJsScan

    5. Go

    goint、go tool vet、gas

    6. Python

    pylint、bandit、py-find-injection、pyt

    7. 多种语言的安全代码检查工具

    sonar

    8. 商业代码审计工具

    1. 静态分析工具

    RISP,VCG,Fortify SCA

    2. 动态分析工具

    ​360的sky wolf,鸟哥的taint

    四、代码审计思路:1. 根据敏感关键字回溯参数传递过程

    根据敏感函数来你想追踪参数的传递过程,是目前使用的最多的一种方式,因为大多数漏洞是由于函数的使用不当造成的。

    优点:只需要搜索相应的敏感关键字,即可快速地挖掘想要的漏洞,可定向挖掘、高效、高质量。

    缺点:由于没有通读代码,对程序的整体,对程序的整体框架了解不够深入,在挖掘漏洞时定位需要花费一定时间、无法挖掘逻辑漏洞

    1. PHP代码审计敏感函数

    1. 预定义的变量[常规外部提交的变量]

    除$_GET $_POST $_COOKIE的提交外,还来源于 $_SERVER $_ENV $_SESSION等

    2. regist_globle=on(未初始化的变量)

    当on的时候,传递的值会被直接注册为全局变量直接使用。而off时,我们需要到特定的数组 得到他,php>4.2.0 default 关闭。

    3. 变量覆盖(未被初始化的覆盖的前定义的变量)

    如 $$ 使用不当,遍历初始化变量,extract() ,parse_str() 等

    4. 变量的传递存储(中转的变量)

    存储于数据库,文件,(如配置,缓存文件等)

    5. 文件包含漏洞(包含任意文件)

    require,include,require_once,include_once

    6. 代码执行(执行任意代码)

    eval() 字符串当作代码来执行

    assert() 调试函数,检查第一个断言是否为FALSE。(把传入的字符串作为php代码执行)

    preg_replace()(preg_replace(/“xxxx”/e)) 执行正则表达式,实现搜索和替换功能。

    /e修正符使preg_replace()将其中的replacement参数当作PHP代码

    create_function() 创建一个匿名函数,并返回都独一无二的函数名。

    7. 命令执行(执行任意命令漏洞)

    exec() 执行一个外部程序

    passthru() 执行外部程序并显示原始输出

    proc_open() 执行一个命令,并且打开用来输入/输出的文件指针

    shell_exec() 通过shell环境执行命令,并且将完整的输出以字符串的方式返回

    system() 执行外部程序,并且显示输出

    popen() 通过popen()的参数传递一条命令,并且对popen()所打开的文件进行执行

    8. 漏洞挖掘时SQL注入漏洞和XSS漏洞需注意的关键字

    1. SQL注入漏洞

    1. 出现位置

    登录界面、获取HTTP开头(user-agent/client-ip)、订单处理等

    2. 普通注入​关键字

    select from、mysql_connect、mysql_query、mysql_fetch_row、update、insert、delete

    3. 宽字节注入出现位置

    文章发表、评论回复、留言、资料设置等

    4. 宽字节注入关键字

    SET NAMES、character_set_client=gbk、mysql_set_charset('gbk’)

    5. 二次urldecode注入关键字

    urldecode、rawurldecode

    2. XSS漏洞关键字

    print、print_r、echo、printf、sprintf、die、var_dump、var_export等。

    2. Java代码审计----敏感函数

    1. 命令执行函数

    GroovyShell.evaluate()、Runtime.getRuntime()、.exec()、ProcessBuilder()、.start()等。

    2. 文件上传函数

    ServletFileUpload()、FileItemStream()、MultipartFile()等。

    3. SSRF函数

    在Java中分为两类:

    一类只支持http,https协议,如HttpURLConnection、HttpClient、Request、okhttp。

    一类支持所有协议如URLConnection、URL。

    HttpURLConnection.getInputStream()

    URLConnection.getInputStream()

    Request.Get.execute()

    Request.Post.execute()

    URL.openStream()

    ImageIO.read()

    OkHttpClient.newCall.execute()

    HttpClents.execute()

    HttpClent.execute()

    4. CORS跨域漏洞CORS跨域设置

    response.setHeader(“Access-Control-Allow-Origin”,"*");

    response.setHeader(“Access-Control-Allow-Credentials”,“true”);

    5. Jsonp劫持

    jsonp跨域设置

    response.setHeader(“Access-Control-Allow-Origin”,"*");

    callback

    6. 反序列化

    readObject

    7. XSS漏洞

    当out.print("");可控的时候,就会导致XSS漏洞。

    Requests

    Response

    Session

    8. session失效时间问题

    session.setMaxInactiveInterval();

    9. 用户退出,session未销毁问题

    session.invalidate(); //销毁所有的Web应用

    sessionsession.removeAttribute(); //移除指定的Web应用

    sessionapplication

    pageContent

    五、代码审计思路:2. 查找可控变量,正向追踪变量传递过程

    查找可控变量,正向追踪变量传递的过程,查找可能存在安全漏洞的变量,从变量处发现安全问题。

    常见的可操控变量:name、id、password、pwd、select、search等。

    六、代码审计思路:3. 寻找敏感功能点,通读功能点代码

    通读功能点的代码,尤其关注于易出现漏洞的功能点

    如:文件上传、留言板、登录认证功能、找回密码等通过从敏感功能点入手来查找安全问题。

    1. 文件上传功能

    若后端程序没有严格地限制上传文件的格式,导致可以直接上传或者存在绕过的情况,除了文件上传漏洞。

    ​还有可能发生sql注入漏洞。因为程序员一般不会注意到对文件名进行过滤,但有需要把文件名存入到数据库内。

    2. 文件管理功能

    如果程序将文件名把或者文件路径直接在参数中传递,则很有可能存在任意文件操作的漏洞。

    还有可能发生xss漏洞,。程序会在页面中国输出文件名,而通常会疏忽对文件名的过滤,导致可能在数据库中存入带有尖括号等特殊 符号的文件名,最后显示在页面上的时候就会被执行。

    3. 登录认证功能

    登陆功能不是指一个登录过程,而是整个操作过程中的认证。

    目前的认证方式大多是基于Cookie和Session。

    如果Cookie信息没有加salt一类的东西,就有可能导致任意用户登录漏洞,只要知道用户的部分信息,即可生成认证令牌

    3. 找回密码功能

    找回密码虽然无法危害到服务器安全,但是可以重置管理员密码,也可以间接控制业务权限甚至拿到服务器权限。

    七、代码审计思路:4. 直接通读全文代码

    通读全文代码并不是逐个读完文件就可以了,而是要有逻辑性、有目的性地选择文件进行审计。

    1. 逻辑性

    ​通读全文代码首先要有一定的"开发者思维",将自己想象为此软件的开发者,如果是我来设计这款软件我要实现什么功能,利用什函数结构,调用哪些接口,根据软件的功能性来分析每一个文件的作用,可以尝试画一个树状图来辅助自己理解软件的设计逻辑。

    2. 目的性

    特别关注函数集文件、配置文件、安全过滤文件、index文件等重要文件。

    1. 函数集文件

    常见命名:fuctions、common

    ​这些文件里面是一些公共的函数,提供给其他文件统一调用,所以大多数文件都会在文件头部包含到其他文件。

    ​寻找这些文件一个比较好用的技巧是打开index.php或者一些功能性文件,在头部一般都能找到

    2. 配置文件

    常见命名:coming

    配置文件包括web程序运行必须的功能性配置选项以及数据库等配置信息。

    从这个文件可以了解程序的小部分功能,另外看这个文件的时候注意观察配置文件中参数值是用单引号还是用双引号括起来,如果用双引号,则很可能会存在代码执行漏洞。

    3. 安全过滤文件

    常见命名:filter、safe、check

    这类文件主要是对参数进行过滤,比较常见的是针对SQL注入和XSS过滤,还有文件路径、执行的系统命令的参数,其他则相对少见。

    4. index文件

    常见命名:index

    index是一个程序的入口文件,所以通常我们只要读一遍index文件就可以大致了解整个程序的架构、运行的流程、包含到的文件,其中核心的文件又有哪些。

    八、专栏分享

    每个专栏都在持续更新中~~~

    渗透测试自学篇

    网络基础

    web基础知识点

    操作系统篇

    python爬虫

    闲聊

    面试总结

    python

    通讯安全

    靶场

    系列文章:[ 渗透入门篇 ] 渗透行业必备术语大集合(一)

                      [ linux ] 文件系统和目录结构详解

                      [ 网络协议篇 ] 一篇文章让你掌握神秘的 DHCP(一)

                      [ 应急响应 ]服务器(电脑)受到攻击该如何处理?(一)

    本文到这里就结束了,相信你对 代码审计 有了更深的理解。

    下篇文章开始就会具体分享代码审计的细节部分。

    我们下篇文章见 !^.^ 

    更多相关内容
  • 网站代码与图片复制到指定文件夹里 (由于本站的前后端分开,图片站也是独立的,另外做了一个图片异步跨服务器更新程序,所以有下面四个文件夹) 打开服务器管理器,进入本地用户和组管理,为上面几个网站...

     服务器安全部署文档

    目录
    1.     前言.. 3 2.     部署环境.. 3 2.1         服务器环境信息.. 3 3.     磁盘阵列配置.. 4 4.     安装操作系统.. 4 5.     安装软件.. 4 5.1         安装磁盘碎片整理程序.. 4 5.2         安装虚拟光盘.. 6 5.3         安装IIS. 6 5.4         安装.NET Framework4. 9 5.5         安装SQL2008. 9 5.6         安装JMail 17 5.7         安装杀毒软件与防火墙.. 17 6.     服务器网站与安全配置.. 22 6.1.       修改系统默认帐户名.. 22 6.2.       配置帐户锁定策略.. 27 6.3.       服务器硬盘安全访问安全配置.. 28 6.4.       配置网站.. 29 6.5.       配置跨服务器同步更新图片网站.. 68 6.6.       屏蔽xplog70.dll漏洞.. 75 6.7.       设置网络访问策略.. 76 6.8.       设置用户权限分配策略.. 77 6.9.       关闭默认共享.. 79 6.10.         禁用不需要的和危险的服务.. 79 6.11.         修改审核策略.. 81 6.12.         系统防火墙安全设置.. 82 6.13.         开启远程桌面功能.. 83 6.14.         配置McAfee访问保护.. 90 6.15.         配置McAfee防火墙.. 97 7.     部署注意事项.. 103

     

     

    1.     前言

    其实要配置一台安全的服务器,简单来说就几句话:

    能不开放的端口和可以不运行的服务全部关闭或禁用;

    使用可进行端口通讯访问策略配置的防火墙;

    严格控制系统中各种程序对各个目录创建、修改与删除可执行脚本、动态链接库与程序的权限;

    对于网站目录,能写入的目录或文件不能有执行权限,有执行权限的目录不可以赋给写入权限。(这条最为关键)

     

    2.    部署环境 2.1    服务器环境信息 服务器硬件配置:

    服务器软件环境:

    名称 说明
    磁盘阵列RAID 10
    操作系统

    Windwos2008 R2 Enterprise 64Bit

    (其实本人最熟悉的还是win2003服务器,由于公司购买的Dell R820服务器不再支持低端系统,没办法只能将自己升级起来,当前如果你的服务器还是使用win2003的话,查看本文还是有一定帮助的,两者只是在配置某些地方有不一样,但整体的安全思想是一致的)

    IIS7.5
    SQLMSSQL2008
    .NET Framework.net 4.0
    杀毒软件与防火墙

    McaFee 64位vse880;

    HostIPS Client700;

    邮件发送软件JMail
    IP地址192.168.1.10

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    3.     磁盘阵列配置

    具体配置请查看《RAID配置中文手册》,链接地址: http://support1.ap.dell.com/cn/zh/forum/thread.asp?fid=20&tid=61244 配置前,请提前备份好硬盘里的数据,重新做过阵列后,硬盘中的数据将全部丢失。 (笔者使用的是Dell R820服务器,Dell服务器的售后服务确实不错,服务器有什么问题,直接打几个售后电话就可以解决,非常方便)

    4.    安装操作系统 具体安装操作步骤,请查看《R820服务器安装指南》 1)    通过Lifecycle 安装 windows 2008: http://zh.community.dell.com/support_forums/poweredge/f/279/t/2812.aspx 这个方法是开机直接按F10,进行安装操作系统,可以快速的安装。 其它官方支持的系统安装,只是在选择操作系统的时候,选择对应的就可以进行快速的安装。
    2)    手动安装2012 : http://zh.community.dell.com/support_forums/poweredge/f/279/t/9621.aspx 这个方法是直接通过2012的安装光盘,从光驱启动进行安装的操作方法。 在选择安装磁盘时,可将本文档所在文件夹中的RAID驱动解压到U盘中,将U盘插入服务器后手动选择载入驱动进行安装。
    其他安装过程的操作方法同平时安装操作系统一样,这里就不做详细描述了。

    5.安装软件 5.1         安装磁盘碎片整理程序

    由于服务器的相关文件、图片和各种日志文件会不停的创建与删除,服务器运行时间长了以后,磁盘上会存在很多文件碎片,这样就会降低服务器的性能,缩短硬盘寿命。 Diskeeper2011_ProPremier是一个实时碎片整理程序,它不干扰系统资源,自动化运行,可以自动防止关键系统文件产生碎片,实时监控磁盘,一旦产生碎片就进行整理,最大程度地保证系统稳定性和速度,而它的智能文件访问加速顺序技术I-FAAST2.0最高可将最常用文件的访问速度提高80%(平均10%~20%)。(具体介绍请查看官方相关文档) 运行安装:

    5.2         安装虚拟光盘

    5.3         安装IIS

    打开服务器管理器,选择角色=》点击“添加角色”

    选择Web服务器(IIS)

    点下一步后,按下图所选内容进行勾选

    5.4         安装.NET Framework4

    运行dotNetFx40_Full_x86_x64.exe安装.net4框架(这个必须在IIS安装完成后才进行安装,这样才会在IIS的相关属性中自动绑定.net4框架)

    5.5         安装SQL2008

    安装SQL2008前,首先要安装.net3.5框架,打开服务器管理器,点击功能=》添加功能=》勾选.NET Framework3.5.1运行安装

    继续安装SQL2008,请先操作第6.1修改系统默认帐户名步骤后的管理员用户再进行下面步骤

     

    按6.1操作后,将SQL2008_CHS.iso载入虚拟光盘,运行安装 选择“安装”=》 “全新SQL Server 独立安装或向现有安装添加功能”

    这里选择的帐户名是SYSTEM,密码为空

    操作到这一步时请注意: 1)身份验证模式选择混合模式 2)设置的SA密码必须为长于32位的中英文(大小写)+数字,谁也记不住的乱码,设置好后都不再使用该账号与密码。 3)指定的SQL Server管理员为当前用户(必须是操作第6.1修改系统默认帐户名步骤后的管理员用户,如果不是的话有可能导致登陆不了SQL)

     

    有时候运行到最后一步时会显示安装出错,这时重启后进入控制面板,点击卸载程序,将刚安装的SQL2008删掉后再次重启电脑,进行安装就可以了,当然我试过一次通过,也试过这样操作三四次后才成功,为什么会这样就不知道了。

    5.6         安装JMail

    5.7         安装杀毒软件与防火墙

    杀毒软件与防火墙的安装,最好将 ”6.14.配置McAfee防火墙” 之前的所有步骤全部完成后才进行,不然可能会造成一些配置或操作无法成功的情况,因为防火墙安装成功后,会阻止系统软件的运行与操作。如果已经安装好了的话,则先打开杀毒软件控制台,将“访问保护”先禁用,而防火墙则先不开启。

     

    防病毒+防火墙组合装 官网卖1年也才是80元,建议大家都购买正版来使用,由于本人是在虚拟机上进行的操作,所以使用的是免费版来做演示

    官网购买地址:http://home.mcafee.com/store/antivirus-plus

    具体也不进行细说,直接上图 安装杀毒软件:

    安装防火墙

    直接运行McAfeeHIP_ClientSetup.exe (注:正版的安装方法与下面是不一样的,有一些区别)

    运行后不会有任何安装界面出来,等待一会后再运行补丁,如下图

    双击鼠标左键就可以了,然后进入下图路径,找到已经安装好的防火墙程序

    运行McAfeeFire.exe,就可以打开防火墙软件了

    6.     服务器网站与安全配置 6.1.       修改系统默认帐户名

    修改系统默认帐户名并新建一个Administrator帐户作为陷阱帐户,设置超长密码,并去掉所有用户组。(就是在用户组那里设置为空即可.让这个帐号不属于任何用户组),同样改名禁用掉Guest用户。

    先对原Administrator用户进行重命名

    修改为你自己喜欢的名称

    然后新建一个Administrator欺骗帐户,设置混合超长密码

    对它进行编辑

    删除隶属的组

    因这个是欺骗帐户,所以充许网络策略控制访问

    将Guest也进行重命名操作

     

     

      设置完成后必须重启电脑,不然安装SQL时可能会导致安装失败,需要重新安装的问题

    6.2.       配置帐户锁定策略

    在运行中输入gpedit.msc回车,打开组策略编辑器,选择计算机配置-Windows设置-安全设置-账户策略-账户锁定策略,将账户设为“三次登陆无效”,“锁定时间30分钟”,“复位锁定计数设为30分钟”。

    6.3.       服务器硬盘安全访问安全配置

    所有磁盘除CREATOR OWNER、administrators和system的用户权限全部删除(C盘必须保留Users用户组,理论上来说是要删除Users用户组的,但很多朋友如果这里直接删除,操作不当的话,网站有可能就访问不了,必须对系统目录下的不少目录重新配置权限非常麻烦,所以本文建议保留,只要按下面的一些设置做到位,这里也不会有多大的安全隐患的)

    6.4.       配置网站

    将网站代码与图片复制到指定文件夹里

    (由于本站的前后端分开,图片站也是独立的,另外做了一个图片异步跨服务器更新程序,所以有下面四个文件夹)

    打开服务器管理器,进入本地用户和组管理,为上面几个网站添加对应的绑定用户,并分别设置超长混合密码,并记录下来,后面备用

    注:后来经同事提醒,原来win2008服务器的IIS访问可以使用应用程序池名称做为帐号来设置(大家可以参考:http://www.cnblogs.com/yjmyzz/archive/2009/10/26/1590033.html),而不用再创建多个独立的账号,不过文档都已经写了,就懒得改了,还是使用Win2003的设置模式来添加帐户

    然后将创建好的几个帐户所隶属的默认组删除,添加Guests组

    将远程控制去掉

    将拨入设置为拒绝

    打开IIS,将默认站点删除

    对网站点右键,添加网站

    创建好对应的站点

     

    点击连接为按钮,设置访问帐户,设置路径凭据为:特定用户,然后输入用户名为刚才创建好的用户名,与相应的密码

     

    设置身份验证

    点击应用程序池,将刚创建好的网站对应程序池.NET Framework版本和托管管道模式

    .NET Framework版本设置为.NET Framework v4.0.30319 托管管道模式设置为经典模式

    设置网站的默认文档为Index.aspx

    设置ISAPI和CGI限制

    将Active Server Pages设置为不允许,将ASP.NET v4.0.30319设置为充许

    进入C:\Windows\Microsoft.NET\Framework64\v4.0.30319目录,设置Temporary ASP.NET Files文件夹的访问权限

    点右键=》属性

    添加红框框住的用户,并设置为可修改

    进入C:\Windows\Microsoft.NET\Framework64\v2.0.50727目录,对Temporary ASP.NET Files文件夹做同样的操作

     

    然后为当前创建的网站设置相应的文件夹访问权限

    添加刚才创建的,并在IIS里绑定的帐户、Authenticated Users和NETWORK SERVICE三个帐号

    设置权限为默认权限(读取和执行、列出文件夹内容、读取)

    启用父路径

    双击ASP打开属性编辑,将启用父路径修改为True

    附加数据库操作 打开SQL2008

    附加数据库

    找到数据库存放位置

    删除数据库中原绑定用户

    新建登陆名

    将数据库链接的用户名与密码填写在SQL新建登陆名对应文本框中,并按下图进行设置

    然后点击用户映射,勾选数据,并设置数据库拥有db_owner角色权限(注:点击确定后最好重新检查新建用户的属性,用户映射项,查看db_owner角色权限是否赋值成功,这里经常会出现创建后没有赋值成功的情况,需要手动重新设置过后才可以)

    打开网站目录,找到Web.config文件,记事本打开,填上新创建的数据库用户名与密码

    运行ASP.NET State Service服务,并将它设置为自动运行

     其他几个网站也按上面的步骤与配置进行设置后,打开浏览器就可以正常该问了
    为可写入目录设置写入权限

    将红框框住的两个帐户设置可修改权限

    禁用可写入目录的执行权限(也可以将所有不用运行ASPX脚本的目录都禁用执行权限,比如css、js等)

     

    6.5.       配置跨服务器同步更新图片网站

    6.6.       屏蔽xplog70.dll漏洞

    进入安装好的SQL目录搜索 xplog70.dll  然后将找到的文件删除(这样操作会使SQL代理服务停止运行,所以如果使用代理功能的朋友请不要删除)

    6.7.       设置网络访问策略

    在运行中输入gpedit.msc回车,打开组策略编辑器,选择计算机配置-Windows设置-安全设置-本地策略-安全选项,将 网络访问:可匿名访问的共享; 网络访问:可匿名访问的命名管道;   网络访问:可远程访问的注册表路径; 网络访问:可远程访问的注册表路径和子路径; 以上四项清空

    6.8.       设置用户权限分配策略

    在运行中输入gpedit.msc回车,打开组策略编辑器,选择计算机配置-Windows设置-安全设置-本地策略-用户权限分配 将“从网络访问此计算机”策略中的“Everyone”删除

     

    在“拒绝通过远程桌面服务登陆”策略中,添加下面用户组和用户

    另外,在添加新站点时,也必须将创建的新用户添加到这里

    6.9.       关闭默认共享

    打开注册表 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters] 新建DOWRD值,名称为“AutoShareServer”、“AutoShareWKs”,设置值为“0”

    6.10.         禁用不需要的和危险的服务

    打开服务器管理器,进入“服务”管理,将下列服务禁用(用黄色标识的服务在2008系统中可能不存在)

    6.11.         修改审核策略

    6.12.         系统防火墙安全设置

    开启系统防火墙(控制面板=》系统和安全=》Windwos防火墙=》打开或关闭Windows防火墙),如果远程操作的话就要小心,不要将自己的连接也给禁用了

    关闭“文件和打印共享”功能

    注:如果为了更安全的话,最好是将远程桌面关闭,使用更安全的第三方远程登陆程序。或者修改远程连接端口,一般来说做了前面的设置后,就算留了“肉鸡”在,问题也不是很大。 添加新的防火墙规则,请参考6.13的相关操作

    6.13.         开启远程桌面功能

    对我的电脑点击左键=》属性,打开系统属性窗口

    修改远程桌面链接端口 点击开始菜单,输入regedit打开注册表 将HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp项的PortNumber值由3389修改为比如:12345这些高端端口

    将HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp项的PortNumber值由3389修改为12345

    修改了远程桌面端口后,原防火墙的远程桌面规则就会失效了,需要重新创建规则 打开Windows防火墙,点击“高级设置”

    设置后重新电脑就马上生效了 注:如果是远程修改端口的话,需要同时修改McAfee防火墙,添加新的端口规则,这样才不会出现无法远程登陆的情况

    6.14.         配置McAfee访问保护

    打开VirusScan控制台

    启用访问保护

    打开访问保护属性

    添加用户定义的规则

    要排除的进程:

    csc.exe, DTSWizard.exe, Explorer.EXE, FrameworkService.exe, HIPSvc.exe, McScript_InUse.exe, SqlWb.exe, Ssms.exe, vbc.exe, w3wp.exe

    要排除的进程:

    FrameworkService.exe, mmc.exe, svchost.exe

     

    开启防火墙的各项阻止规则

    按下面要求,在对应的规则里添加排除的进程

    名称 说明
    通用最大保护:禁止将程序注册为服务svchost.exe, mmc.exe, Explorer.EXE
    防病毒标准保护:禁止群发邮件蠕虫发送邮件w3wp.exe
    通用最大保护:禁止在 Windows 文件夹中创建新的可执行文件w3wp.exe, csc.exe, vbc.exe

     

     

     

     

     

     

     

    这些规则的添加,需要经常查看“访问保护日志”,看那些程序是我们请允许执行的,但却给防火墙阻止了,将它们到对应的排除进程当中(具体操作如果不懂的可以查查百度,或者查看我下一篇文章《服务器安全检查指引——日常维护说明》,它属性服务器的日常维护内容)

     

     

    6.15.         配置McAfee防火墙

    按下面路径进入防火墙文件夹

    运行McAfeeFire.exe打开防火墙软件

    点击解除锁定,密码默认为abcde12345

    解锁后对防火墙的相关选项进行设置

    启用防火墙功能——如果是远程桌面操作的话,这一步操作后远程桌面会马上无法联接,需要在服务器本地设置请允许才能再联接上

    启用后用远程桌面联接一下,并给予授权

    点击充许后,再用远程桌面联接就可以登陆了,其他端口、软件或网站的授权访问也是一样的操作,在给予授权操作时,请仔细留意一下是否是我们请允许的程序访问的,不是的话或不明白的一律给予拒绝,拒绝后发现网站某项功能无法访问或出问题时,再来这里进行排查和修改规则,以保证服务器的安全。

    7.     部署注意事项

    1、更新前一定要经过自测和测试部门人员测试通过; 2、修改网站任何配置都必须提前做好备份,方便回档; 3、修改了服务器端的任何设置都必须提交做好拷屏与记录,方便网站出现任何问题时快速的找出问题; 4、对服务器端的相关端口进行变动时,必须提前在Windows防火墙和McAfee防火墙提前开通对应的端口,修改好端口重启服务器或软件后,记得关闭原端口,并且做好测试工作,防止发生无法访问的情况,特别对于远程访问端口的修改必须小心,不然可能会造成无法远程登陆的情况; 5、当发生某功能无法运行或出错的时候,请先检查Windows防火墙、McAfee访问保护和防火墙,看看是否是给访问保护规则阻止了。 6、必须定期检查用户管理查看是否有多余的用户和用户隶属组是否改变;检查应用程序日志、安全日志、系统日志、IIS访问日志、网站后台管理记录的日志、网站目录中记录的操作日志与充值日志、McAfee访问保日志等,并做好备份工作;查看Windows防火墙、McAfee访问保护和防火墙是否运行中,有没有不小心关闭后忘记开启了;检查SQL的相关日志与记录增长量,检查SQL备份情况,备份空间是否足够等;(具体可查看我下一篇文章《服务器安全检查指引——日常维护说明》) 7、除了做好服务器安全相关配置外,代码的安全也是非常重要的,所有提交的数据必须做好过滤操作,防SQL注入和XSS攻击,客户端定期杀毒查木马,定期修改登陆密码,以保证系统安全。

    8.     结束语

    服务器的安全无小事,事事须小心,一出问题就是大问题,所以真正操作时需要非常细心+小心。

    作为一个服务器维护人员,除了日常的维护工作外,有时间的话还须学习掌握各种常用的黑客工具,熟悉各种攻击手段,多点上上乌云网等这种类型的网站,去看看别人是怎么入侵的,以做到更好的防护。

    由于公司网站的一些特殊性,所以发布的内容是经过删减过的,有一些特殊配置和设置没有发布出来,呵呵......不过基本的安全防护描述的差不多了。里面是否还存在有安全问题就不太清楚了,希望有经验的朋友多多指教。

    如果你觉本篇文章有帮到你,也请帮忙点推荐。

     

      导航


      1、关掉访问保护

      2、发布网站

      3、复制网站到服务器

      4、添加新网站

      5、设置网站访问权限

      6、设置文件夹访问权限

      7、控制可更新文件夹执行权限

      8、设置“应用程序池”.net版本与模式

      9、附加数据库

      10、添加数据库访问用户

      11、设置数据库链接

      12、部署注意事项


     

     

      对于网站的安全部署方面,大家请看我以前写的《服务器安全部署文档》里面已基本上将服务器相关的安全设置都讲到了,照着里面的操作一般的黑客是比较难攻击进来的(代码漏洞除外),而本章的部署则是是该篇设置好的基础上来创建站点的。

     

      1、关掉访问保护

      打开安全防护软件,关掉访问保护(由于做了策略,在服务器上未经授权无法创建dll和exe文件,不关掉的话就没办法拷贝网站的dll文件到服务器里)

      

      记得网站添加成功后重新开启访问保护

     

      2、发布网站

      打开解决方案,对Web项目按右键,在弹出的菜单中点击“发布”——这样生成的是Debug版部署包,如果发布到正式环境的话,可以点击“生成部署包”来发布正式版

      

      选择发布方法为文件系统,点击发布按键发布网站

      

      将红框框住的文件夹内容复制到发布成功的文件夹中(由于这些文件夹没有包含到项目中,所以发布时并没有一起发布出来——这样做是为了打开解决方案时,不用加载太多内容,而导致解决方案打开速度太慢,影响VS使用性能)

      

      这是发布后的文件夹,红色框框住的就是复制进来的文件夹

      

      

     

      3、复制网站到服务器

      将发布成功的文件夹复制到服务器中,并进入bin文件夹,删掉pdb文件

      

     

      4、添加新网站

      

      如果是在本地操作且不会修改host的话,主机名那里请不要填写,如果是在服务器上操作且你有自己的域名,请在主机名填写你自己的域名

      

     

      5、设置网站访问权限

      请双击“身份验证”

      

      在打开的窗口中选择“匿名身份验证”,然后点击“编辑”

      

      选择“应用程序池标识”

      

     

      6、设置文件夹访问权限

      对新建好的网站按右键,点击“编辑权限”

      

      选择“安全”标签,点击“编辑”

      

      然后点击添加

      

      在空白处输入应用程序池名称“IIS AppPool\www.test.com”后点确定(对于有些站点,可能还要添加Authenticated Users这个用户才能正常访问)

      

      完成应用程序池访问权限添加,在权限那里不能赋予修改权限

      

     

      7、控制可更新文件夹执行权限

      我们在做网站文件夹配置时,必须做到“可写入文件夹无可执行权限,可执行文件夹无写入权限”这个要求,这样才能令网站更加安全

      对于红色框框住的文件夹,虽然不都是可写入的文件夹,不过由于里面放置的都是不需要执行的文件,所以都将它们设置为无执行权限

      首先选择指定文件夹,然后双击右边的“处理程序映射”

      

      打开后点击右边的“编辑功能权限”

      

      将“脚本”前面的勾去掉

      

      然后对我们框架中需要有写入权限的两个文件夹Data和UploadFile赋予写入权限

      对文件夹按右键,点击”编辑权限“

      

      在”安全“标签中点”编辑“

      

      给刚才添加的应用程序池名称“www.test.com”赋予“修改”权限

      

     

      8、设置“应用程序池”.net版本与模式

      

      设置它为.net4.0+经典模式

      

     

      9、附加数据库

      添数据库复制到服务器

      

      打开数据库管理器,对“数据库”按右键,点击“附加”

      

      然后将数据库附加进来

      

     

      10、添加数据库访问用户

      打开安全性=》登陆名

      右键菜单,点击新建登录名

      

      输入登陆名、密码和选择默认数据库

      

      选择“用户映射”,对“SolutionDataBase”数据库打勾,并设置数据库角色成员身份(新建时这里经常会看到自动打勾的,实际上并没有选中,需要点击“确定”后重新打开,然后再勾上才行,这个应该是MsSql的一个Bug)

      

     

      11、设置数据库链接

      然后大家打开服务器新建网站的Web.config,将里面的数据库链接用户名与密码设置为刚刚创建的数据库访问用户名与密码后,打开浏览器就可以正常访问了。

     

      12、部署注意事项

      将服务器安全部署文档》的一些重要粘贴过来,让大家再次重温一下注意事项

      1) 更新前一定要经过自测和测试部门人员测试通过;   2) 修改网站任何配置都必须提前做好备份,方便出问题时回档;   3) 修改了服务器端的任何设置都必须提交做好拷屏与记录,方便网站出现任何问题时快速的找出问题;   4) 对服务器端的相关端口进行变动时,必须提前在Windows防火墙和McAfee防火墙提前开通对应的端口,修改好端口重启服务器或软件后,记得关闭原端口,并且做好测试工作,防止发生无法访问的情况,特别对于远程访问端口的修改必须小心,不然可能会造成无法远程登陆的情况;   5) 当发生某功能无法运行或出错的时候,请先检查Windows防火墙、McAfee访问保护和防火墙,看看是否是给访问保护规则阻止了。   6) 必须定期检查用户管理查看是否有多余的用户和用户隶属组是否改变;检查应用程序日志、安全日志、系统日志、IIS访问日志、网站后台管理记录的日志、网站目录中记录的操作日志与充值日志、McAfee访问保日志等,并做好备份工作;查看Windows防火墙、McAfee访问保护和防火墙是否运行中,有没有不小心关闭后忘记开启了;检查SQL的相关日志与记录增长量,检查SQL备份情况,备份空间是否足够等;(具体可查看我的另一篇文章《服务器安全检查指引——日常维护说明》)   7) 除了做好服务器安全相关配置外,代码的安全也是非常重要的,所有提交的数据必须做好过滤操作,防SQL注入和XSS攻击,客户端定期杀毒查木马,定期修改登陆密码,以保证系统安全。

     

      发布最后更新的解决方案:

      更新了《数据字典》的“目录与文件说明”

      并修复了下面Bug:信息列表加条件查询不出来的问题;修复ConfigHelper类(配置读取)获取布尔类型时无法辨别true字符串问题;修复新增员工即标识为离职状态的问题。

    从零开始编写自己的CSharp框架解决方案20140718.rar

    展开全文
  • c#、.NET整体认识

    2019-11-19 10:26:57
    但是,如果我想不仅仅局限于C#和VB,我还想我编写代码在.Net平台上通用的话,那么我还必须得知道.NET平台支持的每一种语言和我编写代码所使用的语言的差异,从而在编写代码中避免这些。 这几年编程语言层出不穷...

    什么是.NET?什么是.NET Framework?本文将从上往下,循序渐进的介绍一系列相关.NET的概念,先从类型系统开始讲起,我将通过跨语言操作这个例子来逐渐引入一系列.NET的相关概念,这主要包括:CLS、CTS(CLI)、FCL、Windows下CLR的相关核心组成、Windows下托管程序运行概念、什么是.NET Framework,.NET Core,.NET Standard及一些VS编译器相关杂项和相关阅读链接。完整的从上读到下则你可以理解个大概的.NET体系。

    文章是我一字一字亲手码出来的,每天下班用休息时间写一点,持续了二十来天。且对于文章上下衔接、概念引入花了很多心思,致力让很多概念在本文中显得通俗。但毕竟.NET系统很庞大,本文篇幅有限,所以在部分小节中我会给出延伸阅读的链接,在文章结尾我给出了一些小的建议,希望能对需要帮助的人带来帮助,如果想与我交流可以文章留言或者加.NET技术交流群:166843154

    目录

    .NET和C#是什么关系

    语言,是人们进行沟通表达的主要方式。编程语言,是人与机器沟通的表达方式。不同的编程语言,其侧重点不同。有的编程语言是为了科学计算而开发的,所以其语法和功能更偏向于函数式思想。有些则是为了开发应用程序而创立的,所以其语法和功能更为均衡全面。

    微软公司是全球最大的电脑软件提供商,为了占据开发者市场,进而在2002年推出了Visual Studio(简称VS,是微软提供给开发者的工具集) .NET 1.0版本的开发者平台。而为了吸引更多的开发者涌入平台,微软还在2002年宣布推出一个特性强大并且与.NET平台无缝集成的编程语言,即C# 1.0正式版。
    只要是.NET支持的编程语言,开发者就可以通过.NET平台提供的工具服务和框架支持便捷的开发应用程序。

    C#就是为宣传.NET而创立的,它直接集成于Visual Studio .NET中,VB也在.NET 1.0发布后对其进行支持, 所以这两门语言与.NET平台耦合度很高,并且.NET上的技术大多都是以C#编程语言为示例,所以经常就.NET和C#混为一谈(实质上它们是相辅相成的两个概念)。
    而作为一个开发者平台,它不仅仅是包含开发环境、技术框架、社区论坛、服务支持等,它还强调了平台的跨语言、跨平台编程的两个特性。

    跨语言和跨平台是什么

    跨语言:即只要是面向.NET平台的编程语言((C#、Visual Basic、C++/CLI、Eiffel、F#、IronPython、IronRuby、PowerBuilder、Visual COBOL 以及 Windows PowerShell)),用其中一种语言编写的类型可以无缝地用在另一种语言编写的应用程序中的互操作性。
    跨平台:一次编译,不需要任何代码修改,应用程序就可以运行在任意有.NET框架实现的平台上,即代码不依赖于操作系统,也不依赖硬件环境。

    什么是跨语言互操作,什么是CLS

    每门语言在最初被设计时都有其在功能和语法上的定位,让不同的人使用擅长的语言去干合适的事,这在团队协作时尤为重要。
    .NET平台上的跨语言是通过CLS这个概念来实现的,接下来我就以C#和VB来演示 什么是.NET中的跨语言互操作性。

    通俗来说,虽然c#和vb是两个不同的语言,但此处c#写的类可以在vb中当做自家写的类一样正常使用。

    比如我在vb中写了一个针对String的首字母大写的扩展方法,将其编译后的dll引用至C#项目中。

    在C#项目中,可以像自身代码一样正常使用来自vb这个dll的扩展方法。

    现在有那么多面向对象语言,但不是所有编程语言都能这样直接互操作使用,而.NET平台支持的C#和VB之所以能这样无缝衔接,先读而后知,后文将会介绍缘由。不过虽然.NET平台提供了这样一个互操作的特性,但终究语言是不一样的,每个语言有其特色和差异处,在相互操作的时候就会难免遇到一些例外情况。

    比如我在C#中定义了一个基类,类里面包含一个公开的指针类型的成员,我想在vb中继承这个类,并访问这个公开的成员。

    但是vb语言因为其定位不需要指针,所以并没有C#中如int*这样的指针类型,所以在vb中访问一个该语言不支持的类型会报错的,会提示:字段的类型不受支持。

    再比如,C#语言中,对类名是区分大小写的,我在C#中定义了两个类,一个叫BaseBusiness,另一个叫baseBusiness。我在vb中去继承这个BaseBusiness类。

    如图,在vb中访问这个类会报错的,报:"BaseBusiness"不明确,这是因为在vb中对类名是不区分大小写的。在vb中,它认为它同时访问了两个一模一样的类,所以按照vb的规则这是不合理的。那么为了在vb调用c#的程序集中避免这些因语言的差异性而导致的错误,在编写c#代码的时候 就应该提前知道vb中的这些规则,来应付式的开发。 

    但是,如果我想不仅仅局限于C#和VB,我还想我编写的代码在.Net平台上通用的话,那么我还必须得知道.NET平台支持的每一种语言和我编写代码所使用的语言的差异,从而在编写代码中避免这些。

    这几年编程语言层出不穷,在将来.NET可能还会支持更多的语言,如果说对一个开发者而言掌握所有语言的差异处这是不现实的,所以.NET专门为此参考每种语言并找出了语言间的共性,然后定义了一组规则,开发者都遵守这个规则来编码,那么代码就能被任意.NET平台支持的语言所通用。
    而与其说是规则,不如说它是一组语言互操作的标准规范,它就是公共语言规范 - Common Language Specification ,简称CLS

     CLS从类型、命名、事件、属性、数组等方面对语言进行了共性的定义及规范。这些东西被提交给欧洲计算机制造联合会ECMA,称为:共同语言基础设施。

    就以类型而言,CLS定义了在C#语言中符合规范的类型和不符合的有:

    当然,就编码角度而言,我们不是必须要看那些详略的文档。为了方便开发者开发,.NET提供了一个特性,名叫:CLSCompliantAttribute,代码被CLSCompliantAttribute标记后,如果你写的代码不符合CLS规范的话,编译器就会给你一条警告。

     

    值得一提的是,CLS规则只是面向那些公开可被其它程序集访问的成员,如public、继承的protected,对于该程序集的内部成员如Private、internal则不会执行该检测规则。也就是说,所适应的CLS遵从性规则,仅是那些公开的成员,而非私有实现。

    那么有没有那种特殊情况,比如我通过反射技术来访问该程序集中,当前语言并不拥有的类型时会发生什么情况呢?

    答案是可以尝试的,如用vb反射访问c#中的char*指针类型,即使vb中没有char*这种等价的指针类型,但mscorlib提供了针对指针类型的 Pointer 包装类供其访问,可以从运行时类携带的类型名称看到其原本的类型名。

    可以看到,该类中的元素是不符合CLS规范的。

    CLS异常

    提到特殊情况,还要说的一点就是异常处理。.NET框架组成中定义了异常类型系统,在编译器角度,所有catch捕获的异常都必须继承自System.Exception,如果你要调用一个 由不遵循此规范的语言 抛出其它类型的异常对象(C++允许抛出任何类型的异常,如C#调用C++代码,C++抛出一个string类型的异常),在C#2.0之前Catch(Exception)是捕捉不了的,但之后的版本可以。
    在后续版本中,微软提供了System.Runtime.CompilerServices.RuntimeWrappedException异常类,将那些不符合CLS的包含Exception的对象封装起来。并且可以通过RuntimeCompatibilityAttribute特性来过滤这些异常。
    RuntimeWrappedException :https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.compilerservices.runtimewrappedexception?view=netframework-4.7.2

    那么,这个段落总结一下,什么是CLS呢?

    在面向.NET开发中,编写跨语言组件时所遵循的那些共性,那些规范就叫做 Common Langrage Specification简称 CLS,公共语言规范 
    官方CLS介绍:https://docs.microsoft.com/zh-cn/dotnet/standard/language-independence-and-language-independent-components

    什么是CTS?

    如果理解了什么是CLS的话,那么你将很轻松理解什么是CTS。
    假设你已经围绕着封装 继承 多态 这3个特性设计出了多款面向对象的语言,你发现大家都是面向对象,都能很好的将现实中的对象模型表达出来。除了语法和功能擅长不同,语言的定义和设计结构其实都差不多一回事。

    比如,现实中你看到了一辆小汽车,这辆车里坐着两个人,那么如何用这门语言来表达这样的一个概念和场面?
    首先要为这门语言横向定义一个“类型”的概念。接下来在程序中就可以这样表示:有一个汽车类型,有一个人类型,在一个汽车类型的对象内包含着两个人类型的对象,因为要表达出这个模型,你又引入了“对象”的概念 。而现在,你又看到,汽车里面的人做出了开车的这样一个动作,由此你又引入了“动作指令”这样一个概念。
    接着,你又恍然大悟总结出一个定理,无论是什么样的“类型”,都只会存在这样一个特征,即活着的 带生命特征的(如人) 和 死的 没有生命特征的(如汽车) 这两者中的一个。最后,随着思想模型的成熟,你发现,这个“类型”就相当于一个富有主体特征的一组指令的集合。
    好,然后你开始照葫芦画瓢。你参考其它程序语言,你发现大家都是用class来表示类的含义,用struct表示结构的含义,用new来表示 新建一个对象的含义,于是,你对这部分功能的语法也使用class和new关键字来表示。然后你又发现,他们还用很多关键字来更丰富的表示这些现实模型,比如override、virtual等。于是,在不断的思想升级和借鉴后,你对这个设计语言过程中思想的变化仔细分析,对这套语言体系给抽象归纳,最终总结出一套体系。

    于是你对其它人这样说,我总结出了一门语言很多必要的东西如两种主要类别:值类别和引用类别,五个主要类型:类、接口、委托、结构、枚举,我还规定了,一个类型可以包含字段、属性、方法、事件等成员,我还指定了每种类型的可见性规则和类型成员的访问规则,等等等等,只要按照我这个体系来设计语言,设计出来的语言它能够拥有很多不错的特性,比如跨语言,跨平台等,C#和VB.net之所以能够这样就是因为这两门语言的设计符合我这个体系。

    那么,什么是CTS呢?

    当你需要设计面向.Net的语言时所需要遵循一个体系(.Net平台下的语言都支持的一个体系)这个体系就是CTS(Common Type System 公共类型系统),它包括但不限于:

    • 建立用于跨语言执行的框架。
    • 提供面向对象的模型,支持在 .NET 实现上实现各种语言。
    • 定义处理类型时所有语言都必须遵守的一组规则(CLS)。
    • 提供包含应用程序开发中使用的基本基元数据类型(如 Boolean、Byte、Char 等)的库。

    上文的CLS是CTS(Common Type System 公共类型系统)这个体系中的子集。
    一个编程语言,如果它能够支持CTS,那么我们就称它为面向.NET平台的语言。
    官方CTS介绍: https://docs.microsoft.com/zh-cn/dotnet/standard/common-type-system 

    微软已经将CTS和.NET的一些其它组件,提交给ECMA以成为公开的标准,最后形成的标准称为CLI(Common Language Infrastructure)公共语言基础结构。
    所以有的时候你见到的书籍或文章有的只提起CTS,有的只提起CLI,请不要奇怪,你可以宽泛的把他们理解成一个意思,CLI是微软将CTS等内容提交给国际组织计算机制造联合会ECMA的一个工业标准。

    什么是类库?

    在CTS中有一条就是要求基元数据类型的类库。我们先搞清什么是类库?类库就是类的逻辑集合,你开发工作中你用过或自己编写过很多工具类,比如搞Web的经常要用到的 JsonHelper、XmlHelper、HttpHelper等等,这些类通常都会在命名为Tool、Utility等这样的项目中。 像这些类的集合我们可以在逻辑上称之为 "类库",比如这些Helper我们统称为工具类库。

    什么是基础类库BCL?

    当你通过VS创建一个项目后,你这个项目就已经引用好了通过.NET下的语言编写好的一些类库。比如控制台中你直接就可以用ConSole类来输出信息,或者using System.IO 即可通过File类对文件进行读取或写入操作,这些类都是微软帮你写好的,不用你自己去编写,它帮你编写了一个面向.NET的开发语言中使用的基本的功能,这部分类,我们称之为BCL(Base Class Library), 基础类库,它们大多都包含在System命名空间下。

    基础类库BCL包含:基本数据类型,文件操作,集合,自定义属性,格式设置,安全属性,I/O流,字符串操作,事件日志等的类型

    什么是框架类库FCL?

    有关BCL的就不在此一一类举。.NET之大,发展至今,由微软帮助开发人员编写的类库越来越多,这让我们开发人员开发更加容易。由微软开发的类库统称为:FCL,Framework Class Library ,.NET框架类库,我上述所表达的BCL就是FCL中的一个基础部分,FCL中大部分类都是通过C#来编写的。

    在FCL中,除了最基础的那部分BCL之外,还包含我们常见的 如 : 用于网站开发技术的 ASP.NET类库,该子类包含webform/webpage/mvc,用于桌面开发的 WPF类库、WinForm类库,用于通信交互的WCF、asp.net web api、Web Service类库等等

    什么是基元类型?

    像上文在CTS中提到了 基本基元数据类型,大家知道,每门语言都会定义一些基础的类型,比如C#通过 int 来定义整型,用 string 来定义 字符串 ,用 object 来定义 根类。当我们来描述这样一个类型的对象时可以有这两种写法,如图:

    我们可以看到,上边用首字母小写的蓝色体string、object能描述,用首字母大写的浅蓝色String、Object也能描述,这两种表述方式有何不同?

    要知道,在vs默认的颜色方案中,蓝色体 代表关键字,浅蓝色体 代表类型。
    那么这样也就意味着,由微软提供的FCL类库里面 包含了 一些用于描述数据类型的 基础类型,无论我们使用的是什么语言,只要引用了FCL,我们都可以通过new一个类的方式来表达数据类型。
    如图:

    用new来创建这些类型的对象,但这样就太繁琐,所以C#就用 int关键字来表示System.Int32,用 string关键字来表示 System.String等,所以我们才能这样去写。

    像这样被表述于编译器直接支持的类型叫做基元类型,它被直接映射于BCL中具体的类。

    下面是部分面向.NET的语言的基元类型与对应的BCL的类别图 :

    System.Object的意义

    说起类型,这里要说CTS定义的一个非常重要的规则,就是类与类之间只能单继承,System.Object类是所有类型的根,任何类都是显式或隐式的继承于System.Object。

        System.Object定义了类型的最基本的行为:用于实例比较的Equals系列方法、用于Hash表中Hash码的GetHashCode、用于Clr运行时获取的类型信息GetType、用于表示当前对象字符串的ToString、用于执行实例的浅复制MemberwiseClone、用于GC回收前操作的析构方法Finalize 这6类方法。

    所以 Object不仅是C#语言的类型根、还是VB等所有面向.NET的语言的类型根,它是整个FCL的类型根。

       当然,CTS定义了单继承,很多编程语言都满足这个规则,但也有语言是例外,如C++就不做继承限制,可以继承多个,C++/CLI作为C++在对.NET的CLI实现,如果在非托管编码中多继承那也可以,如果试图在托管代码中多继承,那就会报错。我前面已经举过这样特殊情况的例子,这也在另一方面反映出,各语言对CTS的支持并不是都如C#那样全面的,我们只需明记一点:对于符合CTS的那部分自然就按照CTS定义的规则来。 任何可遵循CTS的类型规范,同时又有.NET运行时的实现的编程语言就可以成为.NET中的一员。

    计算机是如何运行程序的?

    接下来我要说什么是.NET的跨平台,并解释为什么能够跨语言。不过要想知道什么是跨平台,首先你得知道一个程序是如何在本机上运行的。

    什么是CPU

    CPU,全称Central Processing Unit,叫做中央处理器,它是一块超大规模的集成电路,是计算机组成上必不可少的组成硬件,没了它,计算机就是个壳。
    无论你编程水平怎样,你都应该先知道,CPU是一台计算机的运算核心和控制核心,CPU从存储器或高速缓冲存储器中取出指令,放入指令寄存器,并对指令译码,执行指令。
    我们运行一个程序,CPU就会不断的读取程序中的指令并执行,直到关闭程序。事实上,从电脑开机开始,CPU就一直在不断的执行指令直到电脑关机。

    什么是高级编程语言

    在计算机角度,每一种CPU类型都有自己可以识别的一套指令集,计算机不管你这个程序是用什么语言来编写的,其最终只认其CPU能够识别的二进制指令集。
    在早期计算机刚发展的时代,人们都是直接输入01010101这样的没有语义的二进制指令来让计算机工作的,可读性几乎没有,没人愿意直接编写那些没有可读性、繁琐、费时,易出差错的二进制01代码,所以后来才出现了编程语言。

    编程语言的诞生,使得人们编写的代码有了可读性,有了语义,与直接用01相比,更有利于记忆。
    而前面说了,计算机最终只识别二进制的指令,那么,我们用编程语言编写出来的代码就必须要转换成供机器识别的指令。
    就像这样:

    复制代码
    复制代码
    code: 1+2 
    function 翻译方法(参数:code) 
    { 
        ... 
        "1"=>"001"; 
        "2"=>"002";
        "+"=>"000"; 
        return 能让机器识别的二进制代码; 
    } 
    call 翻译方法("1+2") => "001 000 002"
    复制代码
    复制代码

    所以从一门编程语言所编写的代码文件转换成能让本机识别的指令,这中间是需要一个翻译的过程。
    而我们现在计算机上是运载着操作系统的,光翻译成机器指令也不行,还得让代码文件转化成可供操作系统执行的程序才行。
    那么这些步骤,就是编程语言所对应的编译环节的工程了。这个翻译过程是需要工具来完成,我们把它叫做 编译器。

    不同厂商的CPU有着不同的指令集,为了克服面向CPU的指令集的难读、难编、难记和易出错的缺点,后来就出现了面向特定CPU的特定汇编语言, 比如我打上这样的x86汇编指令 mov ax,bx ,然后用上用机器码做的汇编器,它将会被翻译成 1000100111011000 这样的二进制01格式的机器指令.

    不同CPU架构上的汇编语言指令不同,而为了统一一套写法,同时又不失汇编的表达能力,C语言就诞生了。
    用C语言写的代码文件,会被C编译器先转换成对应平台的汇编指令,再转成机器码,最后将这些过程中产生的中间模块链接成一个可以被操作系统执行的程序。

    那么汇编语言和C语言比较,我们就不需要去阅读特定CPU的汇编码,我只需要写通用的C源码就可以实现程序的编写,我们用将更偏机器实现的汇编语言称为低级语言,与汇编相比,C语言就称之为高级语言。

    在看看我们C#,我们在编码的时候都不需要过于偏向特定平台的实现,翻译过程也基本遵循这个过程。它的编译模型和C语言类似,都是属于这种间接转换的中间步骤,故而能够跨平台。
    所以就类似于C/C#等这样的高级语言来说是不区分平台的,而在于其背后支持的这个 翻译原理 是否能支持其它平台。

    什么是托管代码,托管语言,托管模块?

    作为一门年轻的语言,C#借鉴了许多语言的长处,与C比较,C#则更为高级。
    往往一段简小的C#代码,其功能却相当于C的一大段代码,并且用C#语言你几乎不需要指针的使用,这也就意味着你几乎不需要进行人为的内存管控与安全考虑因素,也不需要多懂一些操作系统的知识,这让编写程序变得更加轻松和快捷。

    如果说C#一段代码可以完成其它低级语言一大段任务,那么我们可以说它特性丰富或者类库丰富。而用C#编程不需要人为内存管控是怎么做到的呢?
        .NET提供了一个垃圾回收器(GC)来完成这部分工作,当你创建类型的时候,它会自动给你分配所需要的这部分内存空间。就相当于,有一个专门的软件或进程,它会读取你的代码,然后当你执行这行代码的时候,它帮你做了内存分配工作。 这部分本该你做的工作,它帮你做了,这就是“托管”的概念。比如现实中 托管店铺、托管教育等这样的别人替你完成的概念。

    因此,C#被称之为托管语言。C#编写的代码也就称之为托管代码,C#生成的模块称之为托管模块等。(对于托管的资源,是不需要也无法我们人工去干预的,但我们可以了解它的一些机制原理,在后文我会简单介绍。)

    只要有比较,就会产生概念。那么在C#角度,那些脱离了.NET提供的诸如垃圾回收器这样的环境管制,就是对应的 非托管了。

    非托管的异常

    我们编写的程序有的模块是由托管代码编写,有的模块则调用了非托管代码。在.NET Framework中也有一套基于此操作系统SEH的异常机制,理想的机制设定下我们可以直接通过catch(e)或catch来捕获指定的异常和框架设计人员允许我们捕获的异常。

    而异常类型的级别也有大有小,有小到可以直接框架本身或用代码处理的,有大到需要操作系统的异常机制来处理。.NET会对那些能让程序崩溃的异常类型给进行标记,对于这部分异常,在.NET Framework 4.0之前允许开发人员在代码中自己去处理,但4.0版本之后有所变更,这些被标记的异常默认不会在托管环境中抛出(即无法catch到),而是由操作系统的SEH机制去处理。 
    不过如果你仍然想在代码中捕获处理这样的异常也是可以的,你可以对需要捕获的方法上标记[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptionsAttribute]特性,就可以在该方法内通过catch捕获到该类型的异常。你也可以通过在配置文件中添加运行时节点来对全局进行这样的一个配置:

    <runtime>
         <legacyCorruptedStateExceptionsPolicy enabled="true" />
    </runtime>

    HandleProcessCorruptedStateExceptions特性:https://msdn.microsoft.com/zh-cn/library/azure/system.runtime.exceptionservices.handleprocesscorruptedstateexceptionsattribute.aspx 
    SEHException类:https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.sehexception(v=vs.100).aspx
    处理损坏状态异常博客专栏: https://msdn.microsoft.com/zh-cn/magazine/dd419661.aspx

    什么是CLR,.NET虚拟机?

    实际上,.NET不仅提供了自动内存管理的支持,他还提供了一些列的如类型安全、应用程序域、异常机制等支持,这些 都被统称为CLR公共语言运行库。

    CLR是.NET类型系统的基础,所有的.NET技术都是建立在此之上,熟悉它可以帮助我们更好的理解框架组件的核心、原理。
    在我们执行托管代码之前,总会先运行这些运行库代码,通过运行库的代码调用,从而构成了一个用来支持托管程序的运行环境,进而完成诸如不需要开发人员手动管理内存,一套代码即可在各大平台跑的这样的操作。

    这套环境及体系之完善,以至于就像一个小型的系统一样,所以通常形象的称CLR为".NET虚拟机"。那么,如果以进程为最低端,进程的上面就是.NET虚拟机(CLR),而虚拟机的上面才是我们的托管代码。换句话说,托管程序实际上是寄宿于.NET虚拟机中。

    什么是CLR宿主进程,运行时主机?

    那么相对应的,容纳.NET虚拟机的进程就是CLR宿主进程了,该程序称之为运行时主机。

    这些运行库的代码,全是由C/C++编写,具体表现为以mscoree.dll为代表的核心dll文件,该dll提供了N多函数用来构建一个CLR环境 ,最后当运行时环境构建完毕(一些函数执行完毕)后,调用_CorDllMain或_CorExeMain来查找并执行托管程序的入口方法(如控制台就是Main方法)。

    如果你足够熟悉CLR,那么你完全可以在一个非托管程序中通过调用运行库函数来定制CLR并执行托管代码。
    像SqlServer就集成了CLR,可以使用任何 .NET Framework 语言编写存储过程、触发器、用户定义类型、用户定义函数(标量函数和表值函数)以及用户定义的聚合函数。

    有关CLR大纲介绍: https://msdn.microsoft.com/zh-cn/library/9x0wh2z3(v=vs.85).aspx 
    CLR集成: https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008/ms131052(v%3dsql.100) 
    构造CLR的接口:https://msdn.microsoft.com/zh-cn/library/ms231039(v=vs.85).aspx 
    适用于 .NET Framework 2.0 的宿主接口:https://msdn.microsoft.com/zh-cn/library/ms164336(v=vs.85).aspx
    选择CLR版本: https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/startup/supportedruntime-element

    所以C#编写的程序如果想运行就必须要依靠.NET提供的CLR环境来支持。 而CLR是.NET技术框架中的一部分,故只要在Windows系统中安装.NET Framework即可。

    Windows系统自带.NET Framework

    Windows系统默认安装的有.NET Framework,并且可以安装多个.NET Framework版本,你也不需要因此卸载,因为你使用的应用程序可能依赖于特定版本,如果你移除该版本,则应用程序可能会中断。

    Microsoft .NET Framework百度百科下有windows系统默认安装的.NET版本 

    图出自 https://baike.baidu.com/item/Microsoft%20.NET%20Framework/9926417?fr=aladdin

    .NET Framework 4.0.30319

    在%SystemRoot%\Microsoft.NET下的Framework和Framework64文件夹中分别可以看到32位和64位的.NET Framework安装的版本。
    我们点进去可以看到以.NET版本号为命名的文件夹,有2.0,3.0,3.5,4.0这几个文件夹。

     

    .NET Framework4.X覆盖更新

    要知道.NET Framework版本目前已经迭代到4.7系列,电脑上明明安装了比4.0更高版本的.NET Framework,然而从文件夹上来看,最高不过4.0,这是为何?
        原来自.NET Framework 4以来的所有.NET Framework版本都是直接在v4.0.30319文件夹上覆盖更新,并且无法安装以前的4.x系列的老版本,所以v4.0.30319这个目录中其实放的是你最后一次更新的NET Framework版本。
    .NET Framework覆盖更新:https://docs.microsoft.com/en-us/dotnet/framework/install/guide-for-developers

    如何确认本机安装了哪些.NET Framework和对应CLR的版本?

    我们可以通过注册表等其它方式来查看安装的最新版本:https://docs.microsoft.com/zh-cn/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed 。
    不过如果不想那么复杂的话,还有种最直接简单的:
    那就是进入该目录文件夹,随便找到几个文件对其右键,然后点击详细信息即可查看到对应的文件版本,可以依据文件版本估摸出.NET Framework版本,比如csc.exe文件。

     

    什么是程序集

    上文我介绍了编译器,即将源代码文件给翻译成一个计算机可识别的二进制程序。而在.NET Framework目录文件夹中就附带的有 用于C#语言的命令行形式的编译器csc.exe 和 用于VB语言的命令行形式的编译器vbc.exe。

    我们通过编译器可以将后缀为.cs(C#)和.vb(VB)类型的文件编译成程序集。
    程序集是一个抽象的概念,不同的编译选项会产生不同形式的程序集。以文件个数来区分的话,那么就分 单文件程序集(即一个文件)和多文件程序集(多个文件)。
    而不论是单文件程序集还是多文件程序集,其总有一个核心文件,就是表现为后缀为.dll或.exe格式的文件。它们都是标准的PE格式的文件,主要由4部分构成:

    • 1.PE头,即Windows系统上的可移植可执行文件的标准格式
    • 2.CLR头,它是托管模块特有的,它主要包括
      • 1)程序入口方法
      • 2)CLR版本号等一些标志
      • 3)一个可选的强名称数字签名
      • 4)元数据表,主要用来记录了在源代码中定义和引用的所有的类型成员(如方法、字段、属性、参数、事件...)的位置和其标志Flag(各种修饰符) 
              正是因为元数据表的存在,VS才能智能提示,反射才能获取MemberInfo,CLR扫描元数据表即可获得该程序集的相关重要信息,所以元数据表使得程序集拥有了自我描述的这一特性。clr2中,元数据表大概40多个,其核心按照用途分为3类:
        • 1.即用于记录在源代码中所定义的类型的定义表:ModuleDef、TypeDef、MethodDef、ParamDef、FieldDef、PropertyDef、EventDef,
        • 2.引用了其它程序集中的类型成员的引用表:MemberRef、AssemblyRef、ModuleRef、TypeRef
        • 3. 用于描述一些杂项(如版本、发布者、语言文化、多文件程序集中的一些资源文件等)的清单表:AssemblyDef、FileDef、ManifestResourceDef、ExportedTypeDef
    • 3.IL代码(也称MSIL,后来被改名为CIL:Common Intermediate Language通用中间语言),是介于源代码和本机机器指令中间的代码,将通过CLR在不同的平台产生不同的二进制机器码。
    • 4.一些资源文件
    • 多文件程序集的诞生场景有:比如我想为.exe绑定资源文件(如Icon图标),或者我想按照功能以增量的方式来按需编译成.dll文件。 通常很少情况下才会将源代码编译成多文件程序集,并且在VS IDE中总是将源代码给编译成单文件的程序集(要么是.dll或.exe),所以接下来我就以单文件程序集为例来讲解。

      用csc.exe进行编译

      现在,我将演示一段文本是如何被csc.exe编译成一个可执行的控制台程序的。
      我们新建个记事本,然后将下面代码复制上去。

        View Code

      然后关闭记事本,将之.txt的后缀改为.cs的后缀(后缀是用来标示这个文件是什么类型的文件,并不影响文件的内容)。

      上述代码相当于Web中的http.sys伪实现,是建立了通信的socket服务端,并通过while循环来不断的监视获取包的数据实现最基本的监听功能,最终我们将通过csc.exe将该文本文件编译成一个控制台程序。

      我已经在前面讲过BCL,基础类库。在这部分代码中,为了完成我想要的功能,我用到了微软已经帮我们实现好了的String数据类型系列类(.NET下的一些数据类型)、Environment类(提供有关当前环境和平台的信息以及操作它们的方法)、Console类(用于控制台输入输出等)、Socket系列类(对tcp协议抽象的接口)、File文件系列类(对文件目录等操作系统资源的一些操作)、Encoding类(字符流的编码)等
      这些类,都属于BCL中的一部分,它们存在但不限于mscorlib.dll、System.dll、System.core.dll、System.Data.dll等这些程序集中。
      附:不要纠结BCL到底存在于哪些dll中,总之,它是个物理分散,逻辑上的类库总称。

      mscorlib.dll和System.dll的区别:https://stackoverflow.com/questions/402582/mscorlib-dll-system-dll

      因为我用了这些类,那么按照编程规则我必须在代码中using这些类的命名空间,并通过csc.exe中的 /r:dll路径 命令来为生成的程序集注册元数据表(即以AssemblyRef为代表的程序集引用表)。
      而这些代码引用了4个命名空间,但实际上它们只被包含在mscorlib.dll和System.dll中,那么我只需要在编译的时候注册这两个dll的信息就行了。

      好,接下来我将通过cmd运行csc.exe编译器,再输入编译命令: csc /out:D:\demo.exe D:\dic\demo.cs /r:D:\dic\System.dll

      /r:是将引用dll中的类型数据注册到程序集中的元数据表中 。
      /out:是输出文件的意思,如果没有该命令则默认输出{name}.exe。 
      使用csc.exe编译生成: https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/compiler-options/command-line-building-with-csc-exe 
      csc编译命令行介绍:https://www.cnblogs.com/shuang121/archive/2012/12/24/2830874.html

      总之,你除了要掌握基本的编译指令外,当你打上这行命令并按回车后,必须满足几个条件,1.是.cs后缀的c#格式文件,2.是 代码语法等检测分析必须正确,3.是 使用的类库必须有出处(引用的dll),当然 因为我是编译为控制台程序,所以还必须得有个静态Main方法入口,以上缺一不可。

      可以看出,这段命令我是将 位于D:\dic\的demo.cs文件给编译成 位于D:\名为demo.exe的控制台文件,并且因为在代码中使用到了System.dll,所以还需要通过/r注册该元数据表。
      这里得注意为什么没有/r:mscorlib.dll,因为mscorlib.dll地位的特殊,所以csc总是对每个程序集进行mscorlib.dll的注册(自包含引用该dll),因此我们可以不用/r:mscorlib.dll这个引用命令,但为了演示效果我还是决定通过/nostdlib命令来禁止csc默认导入mscorlib.dll文件。

      所以,最终命令是这样的: csc D:\dic\demo.cs /r:D:\dic\mscorlib.dll /r:D:\dic\System.dll /nostdlib

      因为没有指定输出文件/out选项, 所以会默认输出在与csc同一目录下名为demo.exe的文件。事实上,在csc的命令中,如果你没有指定路径,那么就默认采用在csc.exe的所在目录的相对路径。

      而我们可以看到,在该目录下有许多程序集,其中就包含我们需要的System.dll和mscorlib.dll,所以我们完全可以直接/r:mscorlib.dll /r:System.dll

      而类似于System.dll、System.Data.dll这样使用非常频繁的程序集,我们其实不用每次编译的时候都去手动/r一下,对于需要重复劳动的编译指令,我们可以将其放在后缀为.rsp的指令文件中,然后在编译时直接调用文件即可执行里面的命令 @ {name}.rsp。

      csc.exe默认包含csc.rsp文件,我们可以用/noconfig来禁止默认包含,而csc.rsp里面已经写好了我们会经常用到的指令。
      所以,最终我可以这样写 csc D:\dic\demo.cs 直接生成控制台应用程序。

      .NET程序执行原理

      好的,现在我们已经有了一个demo.exe的可执行程序,它是如何被我们运行的?。

      C#源码被编译成程序集,程序集内主要是由一些元数据表和IL代码构成,我们双击执行该exe,Windows加载器将该exe(PE格式文件)给映射到虚拟内存中,程序集的相关信息都会被加载至内存中,并查看PE文件的入口点(EntryPoint)并跳转至指定的mscoree.dll中的_CorExeMain函数,该函数会执行一系列相关dll来构造CLR环境,当CLR预热后调用该程序集的入口方法Main(),接下来由CLR来执行托管代码(IL代码)。

      JIT编译

      前面说了,计算机最终只识别二进制的机器码,在CLR下有一个用来将IL代码转换成机器码的引擎,称为Just In Time Compiler,简称JIT,CLR总是先将IL代码按需通过该引擎编译成机器指令再让CPU执行,在这期间CLR会验证代码和元数据是否类型安全(在对象上只调用正确定义的操作、标识与声称的要求一致、对类型的引用严格符合所引用的类型),被编译过的代码无需JIT再次编译,而被编译好的机器指令是被存在内存当中,当程序关闭后再打开仍要重新JIT编译。

      AOT编译

      CLR的内嵌编译器是即时性的,这样的一个很明显的好处就是可以根据当时本机情况生成更有利于本机的优化代码,但同样的,每次在对代码编译时都需要一个预热的操作,它需要一个运行时环境来支持,这之间还是有消耗的。

      而与即时编译所对应的,就是提前编译了,英文为Ahead of Time Compilation,简称AOT,也称之为静态编译。
      在.NET中,使用Ngen.exe或者开源的.NET Native可以提前将代码编译成本机指令。

      Ngen是将IL代码提前给全部编译成本机代码并安装在本机的本机映像缓存中,故而可以减少程序因JIT预热的时间,但同样的也会有很多注意事项,比如因JIT的丧失而带来的一些特性就没有了,如类型验证。Ngen仅是尽可能代码提前编译,程序的运行仍需要完整的CLR来支持。

      .NET Native在将IL转换为本机代码的时候,会尝试消除所有元数据将依靠反射和元数据的代码替换为静态本机代码,并且将完整的CLR替换为主要包含垃圾回收器的重构运行时mrt100_app.dll。

      .NET Native: https://docs.microsoft.com/zh-cn/dotnet/framework/net-native/ 
      Ngen.exe:https://docs.microsoft.com/zh-cn/dotnet/framework/tools/ngen-exe-native-image-generator 
      Ngen与.NET Native比较:https://www.zhihu.com/question/27997478/answer/38978762

      ---------------------------------------------------

      现在,我们可以通过ILDASM工具(一款查看程序集IL代码的软件,在Microsoft SDKs目录中的子目录中)来查看该程序集的元数据表和Main方法中间码。

      c#源码第一行代码:string rootDirectory = Environment.CurrentDirectory;被翻译成IL代码: call string [mscorlib/*23000001*/]System.Environment/*01000004*/::get_CurrentDirectory() /* 0A000003 */ 

      这句话意思是调用 System.Environment类的get_CurrentDirectory()方法(属性会被编译为一个私有字段+对应get/set方法)。

      点击视图=>元信息=>显示,即可查看该程序集的元数据。
      我们可以看到System.Environment标记值为01000004,在TypeRef类型引用表中找到该项:

      注意图,TypeRefName下面有该类型中被引用的成员,其标记值为0A000003,也就是get_CurrentDirectory了。
      而从其ResolutionScope指向位于0x23000001而得之,该类型存在于mscorlib程序集。

      于是我们打开mscorlib.dll的元数据清单,可以在类型定义表(TypeDef)找到System.Environment,可以从元数据得知该类型的一些标志(Flags,常见的public、sealed、class、abstract),也得知继承(Extends)于System.Object。在该类型定义下还有类型的相关信息,我们可以在其中找到get_CurrentDirectory方法。 我们可以得到该方法的相关信息,这其中表明了该方法位于0x0002b784这个相对虚地址(RVA),接着JIT在新地址处理CIL,周而复始。

      元数据在运行时的作用: https://docs.microsoft.com/zh-cn/dotnet/standard/metadata-and-self-describing-components#run-time-use-of-metadata

      程序集的规则

      上文我通过ILDASM来描述CLR执行代码的方式,但还不够具体,还需要补充的是对于程序集的搜索方式。

      对于System.Environment类型,它存在于mscorlib.dll程序集中,demo.exe是个独立的个体,它通过csc编译的时候只是注册了引用mscorlib.dll中的类型的引用信息,并没有记录mscorlib.dll在磁盘上的位置,那么,CLR怎么知道get_CurrentDirectory的代码?它是从何处读取mscorlib.dll的? 
      对于这个问题,.NET有个专门的概念定义,我们称为 程序集的加载方式。

      程序集的加载方式

      对于自身程序集内定义的类型,我们可以直接从自身程序集中的元数据中获取,对于在其它程序集中定义的类型,CLR会通过一组规则来在磁盘中找到该程序集并加载在内存。

      CLR在查找引用的程序集的位置时候,第一个判断条件是 判断该程序集是否被签名。
      什么是签名?

      强名称程序集

      就比如大家都叫张三,姓名都一样,喊一声张三不知道到底在叫谁。这时候我们就必须扩展一下这个名字以让它具有唯一性。

      我们可以通过sn.exe或VS对项目右键属性在签名选项卡中采取RSA算法对程序集进行数字签名(加密:公钥加密,私钥解密。签名:私钥签名,公钥验证签名),会将构成程序集的所有文件通过哈希算法生成哈希值,然后通过非对称加密算法用私钥签名,最后公布公钥生成一串token,最终将生成一个由程序集名称、版本号、语言文化、公钥组成的唯一标识,它相当于一个强化的名称,即强名称程序集。 
      mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

      我们日常在VS中的项目默认都没有被签名,所以就是弱名称程序集。强名称程序集是具有唯一标识性的程序集,并且可以通过对比哈希值来比较程序集是否被篡改,不过仍然有很多手段和软件可以去掉程序集的签名。

      需要值得注意的一点是:当你试图在已生成好的强名称程序集中引用弱名称程序集,那么你必须对弱名称程序集进行签名并在强名称程序集中重新注册。 
      之所以这样是因为一个程序集是否被篡改还要考虑到该程序集所引用的那些程序集,根据CLR搜索程序集的规则(下文会介绍),没有被签名的程序集可以被随意替换,所以考虑到安全性,强名称程序集必须引用强名称程序集,否则就会报错:需要强名称程序集。

      .NET Framework 4.5中对强签名的更改:https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/enhanced-strong-naming

      程序集搜索规则

      事实上,按照存储位置来说,程序集分为共享(全局)程序集和私有程序集。

      CLR查找程序集的时候,会先判断该程序集是否被强签名,如果强签名了那么就会去共享程序集的存储位置(后文的GAC)去找,如果没找到或者该程序集没有被强签名,那么就从该程序集的同一目录下去寻找。

      强名称程序集是先找到与程序集名称(VS中对项目右键属性应用程序->程序集名称)相等的文件名称,然后 按照唯一标识再来确认,确认后CLR加载程序集,同时会通过公钥效验该签名来验证程序集是否被篡改(如果想跳过验证可查阅https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/how-to-disable-the-strong-name-bypass-feature),如果强名称程序集被篡改则报错。

      而弱名称程序集则直接按照与程序集名称相等的文件名称来找,如果还是没有找到就以该程序集名称为目录的文件夹下去找。总之,如果最终结果就是没找到那就会报System.IO.FileNotFoundException异常,即尝试访问磁盘上不存在的文件失败时引发的异常。

      注意:此处文件名称和程序集名称是两个概念,不要模棱两可,文件CLR头内嵌程序集名称。

      举个例子:
      我有一个控制台程序,其路径为D:\Demo\Debug\demo.exe,通过该程序的元数据得知,其引用了一个程序集名称为aa的普通程序集,引用了一个名为bb的强名称程序集,该bb.dll的强名称标识为:xx001。
      现在CLR开始搜索程序集aa,首先它会从demo.exe控制台的同一目录(也就是D:\Demo\Debug\)中查找程序集aa,搜索文件名为aa.dll的文件,如果没找到就在该目录下以程序集名称为目录的目录中查找,也就是会查 D:\Demo\Debug\aa\aa.dll,这也找不到那就报错。
      然后CLR开始搜索程序集bb,CLR从demo.exe的元数据中发现bb是强名称程序集,其标识为:xx001。于是CLR会先从一个被定义为GAC的目录中去通过标识找,没找到的话剩下的寻找步骤就和寻找aa一样完全一致了。

      当然,你也可以通过配置文件config中(配置文件存在于应用程序的同一目录中)人为增加程序集搜索规则:
      1.在运行时runtime节点中,添加privatePath属性来添加搜索目录,不过只能填写相对路径: 

      <runtime>
                  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
                      <probing privatePath="relative1;relative2;" /> //程序集当前目录下的相对路径目录,用;号分割
                  </assemblyBinding>
      </runtime>

      2.如果程序集是强签名后的,那么可以通过codeBase来指定网络路径或本地绝对路径。

      复制代码
      复制代码
      <runtime>
                  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
                      <dependentAssembly>
                          <assemblyIdentity name="myAssembly"
                                            publicKeyToken="32ab4ba45e0a69a1"
                                            culture="neutral" />
                          <codeBase version="2.0.0.0"
                                    href="http://www.litwareinc.com/myAssembly.dll" />
                      </dependentAssembly>
                  </assemblyBinding>
      </runtime>
      复制代码
      复制代码

      当然,我们还可以在代码中通过AppDomain类中的几个成员来改变搜索规则,如AssemblyResolve事件、AppDomainSetup类等。

      有关运行时节点的描述:https://docs.microsoft.com/zh-cn/dotnet/framework/configure-apps/file-schema/runtime/runtime-element

      项目的依赖顺序

      如果没有通过config或者在代码中来设定CLR搜索程序集的规则,那么CLR就按照默认的也就是我上述所说的模式来寻找。
      所以如果我们通过csc.exe来编译项目,引用了其它程序集的话,通常需要将那些程序集复制到同一目录下。故而每当我们通过VS编译器对项目右键重新生成项目(重新编译)时,VS都会将引用的程序集给复制一份到项目bin\输出目录Debug文件夹下,我们可以通过VS中对引用的程序集右键属性-复制本地 True/Flase 来决定这一默认行为。

      值得一提的是,项目间的生成是有序生成的,它取决于项目间的依赖顺序。
      比如Web项目引用BLL项目,BLL项目引用了DAL项目。那么当我生成Web项目的时候,因为我要注册Bll程序集,所以我要先生成Bll程序集,而BLL程序集又引用了Dal,所以又要先生成Dal程序集,所以程序集生成顺序就是Dal=>BLL=>Web,项目越多编译的时间就越久。

      程序集之间的依赖顺序决定了编译顺序,所以在设计项目间的分层划分时不仅要体现出层级职责,还要考虑到依赖顺序。代码存放在哪个项目要有讲究,不允许出现互相引用的情况,比如A项目中的代码引用B,B项目中的代码又引用A。

      为什么Newtonsoft.Json版本不一致?

      而除了注意编译顺序外,我们还要注意程序集间的版本问题,版本间的错乱会导致程序的异常。

      举个经典的例子:Newtonsoft.Json的版本警告,大多数人都知道通过版本重定向来解决这个问题,但很少有人会琢磨为什么会出现这个问题,找了一圈文章,没找到一个解释的。

      比如:
      A程序集引用了 C盘:\Newtonsoft.Json 6.0程序集
      B程序集引用了 从Nuget下载下来的Newtonsoft.Json 10.0程序集
      此时A引用B,就会报:发现同一依赖程序集的不同版本间存在无法解决的冲突 这一警告。

      复制代码
      复制代码
       A:引用Newtonsoft.Json 6.0
              Func()
              {
                  var obj= Newtonsoft.Json.Obj;
                  B.JsonObj();
              }
      

      B: 引用Newtonsoft.Json 10.0
      JsonObj()
      {
      return Newtonsoft.Json.Obj;
      }

      复制代码
      复制代码

      A程序集中的Func方法调用了B程序集中的JsonObj方法,JsonObj方法又调用了Newtonsoft.Json 10.0程序集中的对象,那么当执行Func方法时程序就会异常,报System.IO.FileNotFoundException: 未能加载文件或程序集Newtonsoft.Json 10.0的错误。

      这是为什么?
      1.这是因为依赖顺序引起的。A引用了B,首先会先生成B,而B引用了 Newtonsoft.Json 10.0,那么VS就会将源引用文件(Newtonsoft.Json 10.0)复制到B程序集同一目录(bin/Debug)下,名为Newtonsoft.Json.dll文件,其内嵌程序集版本为10.0。
      2.然后A引用了B,所以会将B程序集和B程序集的依赖项(Newtonsoft.Json.dll)给复制到A的程序集目录下,而A又引用了C盘的Newtonsoft.Json 6.0程序集文件,所以又将C:\Newtonsoft.Json.dll文件给复制到自己程序集目录下。因为两个Newtonsoft.Json.dll重名,所以直接覆盖了前者,那么只保留了Newtonsoft.Json 6.0。
      3.当我们调用Func方法中的B.Convert()时候,CLR会搜索B程序集,找到后再调用 return Newtonsoft.Json.Obj 这行代码,而这行代码又用到了Newtonsoft.Json程序集,接下来CLR搜索Newtonsoft.Json.dll,文件名称满足,接下来CLR判断其标识,发现版本号是6.0,与B程序集清单里注册的10.0版本不符,故而才会报出异常:未能加载文件或程序集Newtonsoft.Json 10.0。

      以上就是为何Newtonsoft.Json版本不一致会导致错误的原因,其也诠释了CLR搜索程序集的一个过程。
      那么,如果我执意如此,有什么好的解决方法能让程序顺利执行呢?有,有2个方法。

      第一种:通过bindingRedirect节点重定向,即当找到10.0的版本时,给定向到6.0版本

        View Code

      如何在编译时加载两个相同的程序集?

      注意:我看过有的文章里写的一个AppDomain只能加载一个相同的程序集,很多人都以为不能同时加载2个不同版本的程序集,实际上CLR是可以同时加载Newtonsoft.Json 6.0和Newtonsoft.Json 10.0的。

      第二种:对每个版本指定codeBase路径,然后分别放上不同版本的程序集,这样就可以加载两个相同的程序集。

        View Code

      如何同时调用两个两个相同命名空间和类型的程序集?

      除了程序集版本不同外,还有一种情况就是,我一个项目同时引用了程序集A和程序集B,但程序集A和程序集B中的命名空间和类型名称完全一模一样,这个时候我调用任意一个类型都无法区分它是来自于哪个程序集的,那么这种情况我们可以使用extern alias外部别名。
      我们需要在所有代码前定义别名,extern alias a;extern alias b;,然后在VS中对引用的程序集右键属性-别名,分别将其更改为a和b(或在csc中通过/r:{别名}={程序集}.dll)。
      在代码中通过 {别名}::{命名空间}.{类型}的方式来使用。
      extern-alias介绍: https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/extern-alias

      共享程序集GAC

      我上面说了这么多有关CLR加载程序集的细节和规则,事实上,类似于mscorlib.dll、System.dll这样的FCL类库被引用的如此频繁,它已经是我们.NET编程中必不可少的一部分,几尽每个项目都会引用,为了不再每次使用的时候都复制一份,所以计算机上有一个位置专门存储这些我们都会用到的程序集,叫做全局程序集缓存(Global Assembly Cache,GAC),这个位置一般位于C:\Windows\Microsoft.NET\assembly和3.5之前版本的C:\Windows\assembly。
      既然是共享存放的位置,那不可避免的会遇到文件名重复的情况,那么为了杜绝该类情况,规定在GAC中只能存在强名称程序集,每当CLR要加载强名称程序集时,会先通过标识去GAC中查找,而考虑到程序集文件名称一致但版本文化等复杂的情况,所以GAC有自己的一套目录结构。我们如果想将自己的程序集放入GAC中,那么就必须先签名,然后通过如gacutil.exe工具(其存在于命令行工具中 https://docs.microsoft.com/zh-cn/dotnet/framework/tools/developer-command-prompt-for-vs中)来注册至GAC中,值得一提的是在将强名称程序集安装在GAC中,会效验签名。

      GAC工具: https://docs.microsoft.com/en-us/dotnet/framework/tools/gacutil-exe-gac-tool

      延伸

      CLR是按需加载程序集的,没有执行代码也就没有调用相应的指令,没有相应的指令,CLR也不会对其进行相应的操作。 当我们执行Environment.CurrentDirectory这段代码的时候,CLR首先要获取Environment类型信息,通过自身元数据得知其存在mscorlib.dll程序集中,所以CLR要加载该程序集,而mscorlib.dll又由于其地位特殊,早在CLR初始化的时候就已经被类型加载器自动加载至内存中,所以这行代码可以直接在内存中读取到类型的方法信息。
      在这个章节,我虽然描述了CLR搜索程序集的规则,但事实上,加载程序集读取类型信息远远没有这么简单,这涉及到了属于.NET Framework独有的"应用程序域"概念和内存信息的查找。

      简单延伸两个问题,mscorlib.dll被加载在哪里?内存堆中又是什么样的一个情况?

      应用程序域

      传统非托管程序是直接承载在Windows进程中,托管程序是承载在.NET虚拟机CLR上的,而在CLR中管控的这部分资源中,被分成了一个个逻辑上的分区,这个逻辑分区被称为应用程序域,是.NET Framework中定义的一个概念。
      因为堆内存的构建和删除都通过GC去托管,降低了人为出错的几率,在此特性基础上.NET强调在一个进程中通过CLR强大的管理建立起对资源逻辑上的隔离区域,每个区域的应用程序互不影响,从而让托管代码程序的安全性和健壮性得到了提升。

      熟悉程序集加载规则和AppDomain是在.NET技术下进行插件编程的前提。AppDomain这部分概念并不复杂。
      当启动一个托管程序时,最先启动的是CLR,在这过程中会通过代码初始化三个逻辑区域,最先是SystemDomain系统程序域,然后是SharedDoamin共享域,最后是{程序集名称}Domain默认域。

      系统程序域里维持着一些系统构建项,我们可以通过这些项来监控并管理其它应用程序域等。共享域存放着其它域都会访问到的一些信息,当共享域初始化完毕后,会自动加载mscorlib.dll程序集至该共享域。而默认域则用储存自身程序集的信息,我们的主程序集就会被加载至这个默认域中,执行程序入口方法,在没有特殊动作外所产生的一切耗费都发生在该域。

      我们可以在代码中创建和卸载应用程序域,域与域之间有隔离性,挂掉A域不会影响到B域,并且对于每一个加载的程序集都要指定域的,没有在代码中指定域的话,默认都是加载至默认域中。
      AppDomain可以想象成组的概念,AppDomain包含了我们加载的一组程序集。我们通过代码卸载AppDomain,即同时卸载了该AppDomain中所加载的所有程序集在内存中的相关区域。

      AppDomain的初衷是边缘隔离,它可以让程序不重新启动而长时间运行,围绕着该概念建立的体系从而让我们能够使用.NET技术进行插件编程。

      当我们想让程序在不关闭不重新部署的情况下添加一个新的功能或者改变某一块功能,我们可以这样做:将程序的主模块仍默认加载至默认域,再创建一个新的应用程序域,然后将需要更改或替换的模块的程序集加载至该域,每当更改和替换的时候直接卸载该域即可。 而因为域的隔离性,我在A域和B域加载同一个程序集,那么A域和B域就会各存在内存地址不同但数据相同的程序集数据。

      跨边界访问

      事实上,在开发中我们还应该注意跨域访问对象的操作(即在A域中的程序集代码直接调用B域中的对象)是与平常编程中有所不同的,一个域中的应用程序不能直接访问另一个域中的代码和数据,对于这样的在进程内跨域访问操作分两类。

      一是按引用封送,需要继承System.MarshalByRefObject,传递的是该对象的代理引用,与源域有相同的生命周期。
      二是按值封送,需要被[Serializable]标记,是通过序列化传递的副本,副本与源域的对象无关。
      无论哪种方式都涉及到两个域直接的封送、解封,所以跨域访问调用不适用于过高频率。
      (比如,原来你是这样调用对象: var user=new User(); 现在你要这样:var user=(User){应用程序域对象实例}.CreateInstanceFromAndUnwrap("Model.dll","Model.User"); )

      值得注意的是,应用程序域是对程序集的组的划分,它与进程中的线程是两个一横一竖,方向不一样的概念,不应该将这2个概念放在一起比较。我们可以通过Thread.GetDomain来查看执行线程所在的域。
      应用程序域在类库中是System.AppDomain类,部分重要的成员有:

      复制代码
      复制代码
              获取当前 System.Threading.Thread 的当前应用程序域
              public static AppDomain CurrentDomain { get; }
              使用指定的名称新建应用程序域
              public static AppDomain CreateDomain(string friendlyName);
              卸载指定的应用程序域。
              public static void Unload(AppDomain domain);
              指示是否对当前进程启用应用程序域的 CPU 和内存监视,开启后可以根据相关属性进行监控
              public static bool MonitoringIsEnabled { get; set; }
              当前域托管代码抛出异常时最先发生的一个事件,框架设计中可以用到
              public event EventHandler<FirstChanceExceptionEventArgs> FirstChanceException;
              当某个异常未被捕获时调用该事件,如代码里只catch了a异常,实际产生的是 b异常,那么b异常就没有捕捉到。
              public event UnhandledExceptionEventHandler UnhandledException;
              为指定的应用程序域属性分配指定值。该应用程序域的局部存储值,该存储不划分上下文和线程,均可通过GetData获取。
              public void SetData(string name, object data);
              如果想使用托管代码来覆盖CLR的默认行为https://msdn.microsoft.com/zh-cn/library/system.appdomainmanager(v=vs.85).aspx
              public AppDomainManager DomainManager { get; }
              返回域的配置信息,如在config中配置的节点信息
              public AppDomainSetup SetupInformation { get; }
      复制代码
      复制代码

      应用程序域: https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/application-domains

      AppDomain和AppPool

      注意:此处的AppDomain应用程序域 和 IIS中的AppPool应用程序池 是2个概念,AppPool是IIS独有的概念,它也相当于一个组的概念,对网站进行划组,然后对组进行一些如进程模型、CPU、内存、请求队列的高级配置。

      内存

      应用程序域把资源给隔离开,这个资源,主要指内存。那么什么是内存呢?

      要知道,程序运行的过程就是电脑不断通过CPU进行计算的过程,这个过程需要读取并产生运算的数据,为此我们需要一个拥有足够容量能够快速与CPU交互的存储容器,这就是内存了。对于内存大小,32位处理器,寻址空间最大为2的32次方byte,也就是4G内存,除去操作系统所占用的公有部分,进程大概能占用2G内存,而如果是64位处理器,则是8T。

      而在.NET中,内存区域分为堆栈和托管堆。

      堆栈和堆的区别

      堆和堆栈就内存而言只不过是地址范围的区别。不过堆栈的数据结构和其存储定义让其在时间和空间上都紧密的存储,这样能带来更高的内存密度,能在CPU缓存和分页系统表现的更好。故而访问堆栈的速度总体来说比访问堆要快点。

      线程堆栈

      操作系统会为每条线程分配一定的空间,Windwos为1M,这称之为线程堆栈。在CLR中的栈主要用来执行线程方法时,保存临时的局部变量和函数所需的参数及返回的值等,在栈上的成员不受GC管理器的控制,它们由操作系统负责分配,当线程走出方法后,该栈上成员采用后进先出的顺序由操作系统负责释放,执行效率高。
      而托管堆则没有固定容量限制,它取决于操作系统允许进程分配的内存大小和程序本身对内存的使用情况,托管堆主要用来存放对象实例,不需要我们人工去分配和释放,其由GC管理器托管。

      为什么值类型存储在栈上

      不同的类型拥有不同的编译时规则和运行时内存分配行为,我们应知道,C# 是一种强类型语言,每个变量和常量都有一个类型,在.NET中,每种类型又被定义为值类型或引用类型。

      使用 struct、enum 关键字直接派生于System.ValueType定义的类型是值类型,使用 class、interface、delagate 关键字派生于System.Object定义的类型是引用类型。
      对于在一个方法中产生的值类型成员,将其值分配在栈中。这样做的原因是因为值类型的值其占用固定内存的大小。

      C#中int关键字对应BCL中的Int32,short对应Int16。Int32为2的32位,如果把32个二进制数排列开来,我们要求既能表达正数也能表达负数,所以得需要其中1位来表达正负,首位是0则为+,首位是1则为-,那么我们能表示数据的数就只有31位了,而0是介于-1和1之间的整数,所以对应的Int32能表现的就是2的31次方到2的31次方-1,即2147483647和-2147483648这个整数段。

      1个字节=8位,32位就是4个字节,像这种以Int32为代表的值类型,本身就是固定的内存占用大小,所以将值类型放在内存连续分配的栈中。

      托管堆模型

      而引用类型相比值类型就有点特殊,newobj创建一个引用类型,因其类型内的引用对象可以指向任何类型,故而无法准确得知其固定大小,所以像对于引用类型这种无法预知的容易产生内存碎片的动态内存,我们把它放到托管堆中存储。

      托管堆由GC托管,其分配的核心在于堆中维护着一个nextObjPtr指针,我们每次实例(new)一个对象的时候,CLR将对象存入堆中,并在栈中存放该对象的起始地址,然后该指针都会根据该对象的大小来计算下一个对象的起始地址。不同于值类型直接在栈中存放值,引用类型则还需要在栈中存放一个代表(指向)堆中对象的值(地址)。

      而托管堆又可以因存储规则的不同将其分类,托管堆可以被分为3类:

      • 1.用于托管对象实例化的垃圾回收堆,又以存储对象大小分为小对象(<85000byte)的GC堆(SOH,Small Object Heap)和用于存储大对象实例的(>=85000byte)大对象堆(LOG,Larage Object Heap)。
      • 2.用于存储CLR组件和类型系统的加载(Loader)堆,其中又以使用频率分为经常访问的高频堆(里面包含有MethodTables方法表, MeghodDescs方法描述, FieldDescs方法描述和InterfaceMaps接口图),和较低的低频堆,和Stub堆(辅助代码,如JIT编译后修改机器代码指令地址环节)。
      • 3.用于存储JIT代码的堆及其它杂项的堆。

      加载程序集就是将程序集中的信息给映射在加载堆,对产生的实例对象存放至垃圾回收堆。前文说过应用程序域是指通过CLR管理而建立起的逻辑上的内存边界,那么每个域都有其自己的加载堆,只有卸载应用程序域的时候,才会回收该域对应的加载堆。

      而加载堆中的高频堆包含的有一个非常重要的数据结构表---方法表,每个类型都仅有一份方法表(MethodTables),它是对象的第一个实例创建前的类加载活动的结果,它主要包含了我们所关注的3部分信息:

      • 1包含指向EEClass的一个指针。EEClass是一个非常重要的数据结构,当类加载器加载到该类型时会从元数据中创建出EEClass,EEClass里主要存放着与类型相关的表达信息。
      • 2包含指向各自方法的方法描述器(MethodDesc)的指针逻辑组成的线性表信息:继承的虚函数, 新虚函数, 实例方法, 静态方法。
      • 3包含指向静态字段的指针。

      那么,实例一个对象,CLR是如何将该对象所对应的类型行为及信息的内存位置(加载堆)关联起来的呢?

      原来,在托管堆上的每个对象都有2个额外的供于CLR使用的成员,我们是访问不到的,其中一个就是类型对象指针,它指向位于加载堆中的方法表从而让类型的状态和行为关联了起来, 类型指针的这部分概念我们可以想象成obj.GetType()方法获得的运行时对象类型的实例。而另一个成员就是同步块索引,其主要用于2点:1.关联内置SyncBlock数组的项从而完成互斥锁等目的。 2.是对象Hash值计算的输入参数之一。

      上述gif是我简单画的一个图,可以看到对于方法中申明的值类型变量,其在栈中作为一块值表示,我们可以直接通过c#运算符sizeof来获得值类型所占byte大小。而方法中申明的引用类型变量,其在托管堆中存放着对象实例(对象实例至少会包含上述两个固定成员以及实例数据,可能),在栈中存放着指向该实例的地址。

      当我new一个引用对象的时候,会先分配同步块索引(也叫对象头字节),然后是类型指针,最后是类型实例数据(静态字段的指针存在于方法表中)。会先分配对象的字段成员,然后分配对象父类的字段成员,接着再执行父类的构造函数,最后才是本对象的构造函数。这个多态的过程,对于CLR来说就是一系列指令的集合,所以不能纠结new一个子类对象是否会也会new一个父类对象这样的问题。而也正是因为引用类型的这样一个特征,我们虽然可以估计一个实例大概占用多少内存,但对于具体占用的大小,我们需要专门的工具来测量。

      对于引用类型,u2=u1,我们在赋值的时候,实际上赋的是地址,那么我改动数据实际上是改动该地址指向的数据,这样一来,因为u2和u1都指向同一块区域,所以我对u1的改动会影响到u2,对u2的改动会影响到u1。如果我想互不影响,那么我可以继承IClone接口来实现内存克隆,已有的CLR实现是浅克隆方法,但也只能克隆值类型和String(string是个特殊的引用类型,对于string的更改,其会产生一个新实例对象),如果对包含其它引用类型的这部分,我们可以自己通过其它手段实现深克隆,如序列化、反射等方式来完成。而如果引用类型中包含有值类型字段,那么该字段仍然分配在堆上。

      对于值类型,a=b,我们在赋值的时候,实际上是新建了个值,那么我改动a的值那就只会改动a的值,改动b的值就只会改动b的值。而如果值类型(如struct)中包含的有引用类型,那么仍是同样的规则,引用类型的那部分实例在托管堆中,地址在栈上。

      我如果将值类型放到引用类型中(如:object a=3),会在栈中生成一个地址,在堆中生成该值类型的值对象,还会再生成这类型指针和同步块索引两个字段,这也就是常说装箱,反过来就是拆箱。每一次的这样的操作,都会涉及到内存的分布、拷贝,可见,装箱和拆箱是有性能损耗,因此应该减少值类型和引用类型之间转换的次数。 
      但对于引用类型间的子类父类的转换,仅是指令的执行消耗,几尽没有开销。

      选class还是struct

      那么我到底是该new一个class呢还是选择struct呢?

      通过上文知道对于class,用完之后对象仍然存在托管堆,占用内存。对于struct,用完之后直接由操作系统销毁。那么在实际开发中定义类型时,选择class还是struct就需要注意了,要综合应用场景来辨别。struct存在于栈上,栈和托管堆比较,最大的优势就是即用即毁。所以如果我们单纯的传递一个类型,那么选择struct比较合适。但须注意线程堆栈有容量限制,不可多存放超大量的值类型对象,并且因为是值类型直接传递副本,所以struct作为方法参数是线程安全的,但同样要避免装箱的操作。而相比较class,如果类型中还需要多一些封装继承多态的行为,那么class当然是更好的选择。

      GC管理器

      值得注意的是,当我new完一个对象不再使用的时候,这个对象在堆中所占用的内存如何处理?
      在非托管世界中,可以通过代码手动进行释放,但在.NET中,堆完全由CLR托管,也就是说GC堆是如何具体来释放的呢?

      当GC堆需要进行清理的时候,GC收集器就会通过一定的算法来清理堆中的对象,并且版本不同算法也不同。最主要的则为Mark-Compact标记-压缩算法。
      这个算法的大概含义就是,通过一个图的数据结构来收集对象的根,这个根就是引用地址,可以理解为指向托管堆的这根关系线。当触发这个算法时,会检查图中的每个根是否可达,如果可达就对其标记,然后在堆上找到剩余没有标记(也就是不可达)的对象进行删除,这样,那些不在使用的堆中对象就删除了。

      前面说了,因为nextObjPtr的缘故,在堆中分配的对象都是连续分配的,因为未被标记而被删除,那么经过删除后的堆就会显得支零破碎,那么为了避免空间碎片化,所以需要一个操作来让堆中的对象再变得紧凑、连续,而这样一个操作就叫做:Compact压缩。 
      而对堆中的分散的对象进行挪动后,还会修改这些被挪动对象的指向地址,从而得以正确的访问,最后重新更新一下nextObjPtr指针,周而复始。

      而为了优化内存结构,减少在图中搜索的成本,GC机制又为每个托管堆对象定义了一个属性,将每个对象分成了3个等级,这个属性就叫做:代,0代、1代、2代。

      每当new一个对象的时候,该对象都会被定义为第0代,当GC开始回收的时候,先从0代回收,在这一次回收动作之后,0代中没有被回收的对象则会被定义成第1代。当回收第1代的时候,第1代中没有被清理掉的对象就会被定义到第2代。 
      CLR初始化时会为0/1/2这三代选择一个预算的容量。0代通常以256 KB-4 MB之间的预算开始,1代的典型起始预算为512 KB-4 MB,2代不受限制,最大可扩展至操作系统进程的整个内存空间。

      比如第0代为256K,第1代为2MB。我们不停的new对象,直到这些对象达到256k的时候,GC会进行一次垃圾回收,假设这次回收中回收了156k的不可达对象,剩余100k的对象没有被回收,那么这100k的对象就被定义为第1代。现在就变成了第0代里面什么都没有,第1代里放的有100k的对象。这样周而复始,GC清除的永远都只有第0代对象,除非当第一代中的对象累积达到了定义的2MB的时候,才会连同清理第1代,然后第1代中活着的部分再升级成第二代...

      第二代的容量是没有限制,但是它有动态的阈值(因为等到整个内存空间已满以执行垃圾回收是没有意义的),当达到第二代的阈值后会触发一次0/1/2代完整的垃圾收集。

      也就是说,代数越长说明这个对象经历了回收的次数也就越多,那么也就意味着该对象是不容易被清除的。 
      这种分代的思想来将对象分割成新老对象,进而配对不同的清除条件,这种巧妙的思想避免了直接清理整个堆的尴尬。

      弱引用、弱事件

      GC收集器会在第0代饱和时开始回收托管堆对象,对于那些已经申明或绑定的不经访问的对象或事件,因为不经常访问而且还占内存(有点懒加载的意思),所以即时对象可达,但我想在GC回收的时候仍然对其回收,当需要用到的时候再创建,这种情况该怎么办?

      那么这其中就引入了两个概念:
      WeakReference弱引用、WeakEventManager弱事件
      对于这2两个不区分语言的共同概念,大家可自行扩展百度,此处就不再举例。

      GC堆回收

      那么除了通过new对象而达到代的阈(临界)值时,还有什么能够导致垃圾堆进行垃圾回收呢? 还可能windows报告内存不足、CLR卸载AppDomain、CLR关闭等其它特殊情况。

      或者,我们还可以自己通过代码调用。

      .NET有GC来帮助开发人员管理内存,并且版本也在不断迭代。GC帮我们托管内存,但仍然提供了System.GC类让开发人员能够轻微的协助管理。 这其中有一个可以清理内存的方法(并没有提供清理某个对象的方法):GC.Collect方法,可以对所有或指定代进行即时垃圾回收(如果想调试,需在release模式下才有效果)。这个方法尽量别用,因为它会扰乱代与代间的秩序,从而让低代的垃圾对象跑到生命周期长的高代中。

      GC还提供了,判断当前对象所处代数、判断指定代数经历了多少次垃圾回收、获取已在托管堆中分配的字节数这样的三个方法,我们可以从这3个方法简单的了解托管堆的情况。

      托管世界的内存不需要我们打理,我们无法从代码中得知具体的托管对象的大小,你如果想追求对内存最细微的控制,显然C#并不适合你,不过类似于有关内存把控的这部分功能模块,我们可以通过非托管语言来编写,然后通过.NET平台的P/Invoke或COM技术(微软为CLR定义了COM接口并在注册表中注册)来调用。

      像FCL中的源码,很多涉及到操作系统的诸如 文件句柄、网络连接等外部extren的底层方法都是非托管语言编写的,对于这些非托管模块所占用的资源,我们可以通过隐式调用析构函数(Finalize)或者显式调用的Dispose方法通过在方法内部写上非托管提供的释放方法来进行释放。

      像文中示例的socket就将释放资源的方法写入Dispose中,析构函数和Close方法均调用Dispose方法以此完成释放。事实上,在FCL中的使用了非托管资源的类大多都遵循IDispose模式。而如果你没有释放非托管资源直接退出程序,那么操作系统会帮你释放该程序所占的内存的。

      垃圾回收对性能的影响

      还有一点,垃圾回收是对性能有影响的。
      GC虽然有很多优化策略,但总之,只要当它开始回收垃圾的时候,为了防止线程在CLR检查期间对对象更改状态,所以CLR会暂停进程中的几乎所有线程(所以线程太多也会影响GC时间),而暂停的时间就是应用程序卡死的时间,为此,对于具体的处理细节,GC提供了2种配置模式让我们选择。

      第一种为:单CPU的工作站模式,专为单CPU处理器定做。这种模式会采用一系列策略来尽可能减少GC回收中的暂停时间。 
      而工作站模式又分为并发(或后台)与不并发两种,并发模式表现为响应时间快速,不并发模式表现为高吞吐量。

      第二种为:多CPU的服务器模式,它会为每个CPU都运行一个GC回收线程,通过并行算法来使线程能真正同时工作,从而获得性能的提升。

      我们可以通过在Config文件中更改配置来修改GC模式,如果没有进行配置,那么应用程序总是默认为单CPU的工作站的并发模式,并且如果机器为单CPU的话,那么配置服务器模式则无效。

      如果在工作站模式中想禁用并发模式,则应该在config中运行时节点添加 <gcConcurrent enabled="false" /> 
      如果想更改至服务器模式,则可以添加 <gcServer enabled="true" />。

      复制代码
      复制代码
       <configuration>
              <runtime>
                  <!--<gcConcurrent enabled="true|false"/>-->
                  <!--<gcServer enabled="true|false"/>-->
              </runtime>
      </configuration>
      复制代码
      复制代码

      gcConcurrent: https://docs.microsoft.com/zh-cn/dotnet/framework/configure-apps/file-schema/runtime/gcconcurrent-element 
      gcServer: https://docs.microsoft.com/zh-cn/dotnet/framework/configure-apps/file-schema/runtime/gcserver-element

      性能建议

      虽然我们可以选择适合的GC工作模式来改善垃圾回收时的表现,但在实际开发中我们更应该注意减少不必要的内存开销。

      几个建议是,减换需要创建大量的临时变量的模式、考虑对象池、大对象使用懒加载、对固定容量的集合指定长度、注意字符串操作、注意高频率的隐式装箱操作、延迟查询、对于不需要面向对象特性的类用static、需要高性能操作的算法改用外部组件实现(p/invoke、com)、减少throw次数、注意匿名函数捕获的外部对象将延长生命周期、可以阅读GC相关运行时配置在高并发场景注意变换GC模式...

      对于.NET中改善性能可延伸阅读 https://msdn.microsoft.com/zh-cn/library/ms973838.aspx 、 https://msdn.microsoft.com/library/ms973839.aspx

      .NET程序执行图

      至此,.NET Framework上的三个重要概念,程序集、应用程序域、内存在本文讲的差不多了,我画了一张图简单的概述.NET程序的一个执行流程:

      对于后文,我将单独的介绍一些其它杂项,首先是.NET平台的安全性。

      .NET的安全性

      .NET Framework中的安全机制分为 基于角色的安全机制 和 代码访问安全机制 。

      基于角色的安全性

      基于角色的安全机制作为传统的访问控制,其运用的非常广泛,如操作系统的安全策略、数据库的安全策略等等...它的概念就相当于我们经常做的那些RBAC权限管理系统一样,用户关联角色,角色关联权限,权限对应着操作。
      整个机制的安全逻辑就和我们平时编写代码判断是一样的,大致可以分为两个步骤.

      第一步就是创建一个主体,然后标识这个主体是什么身份(角色) ,第二步就是 身份验证,也就是if判断该身份是否可以这样操作。

      而在.NET Framework中,这主体可以是Windows账户,也可以是自定义的标识,通过生成如当前线程或应用程序域使用的主体相关的信息来支持授权。
      比如,构造一个代表当前登录账户的主体对象WindowsPrincipal,然后通过 AppDomain.CurrentDomain.SetThreadPrincipal(主体对象);或Thread.CurrentPrincipal的set方法来设置应用程序域或线程的主体对象, 最后使用System.Security.Permissions.PrincipalPermission特性来标记在方法上来进行授权验证。

      如图,我当前登录账号名称为DemoXiaoZeng,然后通过Thread.CurrentPrincipal设置当前主体,执行aa方法,顺利打印111。如果检测到PrincipalPermission类中的Name属性值不是当前登录账号,那么就报错:对主体权限请求失败。 

      在官方文档中有对.NET Framework基于角色的安全性的详细的介绍,感兴趣可以去了解 https://docs.microsoft.com/zh-cn/dotnet/standard/security/principal-and-identity-objects#principal-objects

      代码访问安全性

      在.NET Framework中还有一个安全策略,叫做 代码访问安全Code Access Security,也就是CAS了。

      代码访问安全性在.NET Framework中是用来帮助限制代码对受保护资源和操作的访问权限。
      举个例子,我通过创建一个FileIOPermission对象来限制对后续代码对D盘的文件和目录的访问,如果后续代码对D盘进行资源操作则报错。 

      FileIOPermission是代码控制访问文件和文件夹的能力。除了FileIOPermission外,还有如PrintingPermission代码控制访问打印机的权限、RegistryPermission代码控制操作注册表的权限、SocketPermission控制接受连接或启动Socket连接的权限。 

      对于这些通过代码来对受保护资源和操作的权限限制,也就是这些类名后缀为Permission的类,它们叫做 Permissions(权限),都继承自CodeAccessPermission,都有如Demand,Assert,Deny,PermitOnly,IsSubsetOf,Intersect和Union这些方法,在MSDN上有完整的权限列表:https://msdn.microsoft.com/en-us/library/h846e9b3(v=vs.100).aspx

      为了确定代码是否有权访问某一资源或执行某一操作,CLR的安全系统将审核调用堆栈,以将每个调用方获得的权限与要求的权限进行比较。 如果调用堆栈中的任何调用方不具备要求的权限,则会引发安全性异常并拒绝访问。

      图出自 https://docs.microsoft.com/zh-cn/dotnet/framework/misc/code-access-security

      而除了Permissions权限,代码访问安全性机制还有 权限集、证据、代码组、策略等概念。这些概念让CAS如此强大,但相应的,它们也让CAS变得复杂,必须为每个特定机器定义正确的PermissionSet和Code Groups才能设置成一个成功的CAS策略。

      考虑到这层原因,Microsoft .NET安全小组决定从头开始重建代码访问安全性。在.NET Framework4.0之后,就不再使用之前的那套CAS模型了,而是使用.NET Framework 2.0中引入的安全透明模型,然后稍加修改,修改后的安全透明模型成为保护资源的标准方法,被称之为:安全透明度级别2

      安全透明度2介绍:https://msdn.microsoft.com/en-us/library/dd233102(v=vs.100).aspx
      .NET Framework4.0的安全更改:https://msdn.microsoft.com/en-us/library/dd233103(v=vs.100).aspx 
      一个完整的CAS演示:https://www.codeproject.com/Articles/5724/Understanding-NET-Code-Access-Security

      对于安全透明度级别2我将不再介绍,感兴趣的可以看我推荐的这2篇文章,对Level2的安全透明度介绍的比较详细,包括实践、迁移。 
      https://www.red-gate.com/simple-talk/dotnet/.net-framework/whats-new-in-code-access-security-in-.net-framework-4.0---part-i/
      https://www.red-gate.com/simple-talk/dotnet/net-framework/whats-new-in-code-access-security-in-net-framework-4-0-part-2/

      ----------------------------------------------

      须注意:
      .NET平台上的安全机制,仅仅是.NET平台上的,因此它只限制于托管代码,我们可以直接调用非托管代码或进程通信间接调用非托管代码等多个手段来突破对托管代码 操作资源的限制。

      事实上,我们在平常项目中代码编写的安全机制(业务逻辑身份验证、项目框架验证)与这些平台级的安全机制没什么不同。我们可以理解为代码写的位置不一样,.NET安全机制是写在CLR组件中,而我们的安全机制是写在上层的代码中。这些平台级的标识更多的是和操作系统用户有关,而我们项目代码中的标识则是和在数据库中注册的用户有关, 大家都是通过if else来去判断,判断的主体和格局不一样,逻辑本质都是相同的。

      NET Core不支持代码访问安全性和安全性透明性。

      .NET是什么

      我在前文对.NET系统概述时,有的直接称.NET,有的称.NET Framework。那么准确来说什么是.NET?什么又是.NET Framework呢?

      .NET是一个微软搭造的开发者平台,它主要包括:

      • 1.支持(面向)该平台的编程语言(如C#、Visual Basic、C++/CLI、F#、IronPython、IronRuby...),
      • 2.用于该平台下开发人员的技术框架体系(.NET Framework、.NET Core、Mono、UWP等),
        • 1.定义了通用类型系统,庞大的CTS体系
        • 2.用于支撑.NET下的语言运行时的环境:CLR
        • 3..NET体系技术的框架库FCL
    • 3.用于支持开发人员开发的软件工具(即SDK,如VS2017、VS Code等)
    • .NET Framework是什么

      事实上,像我上面讲的那些诸如程序集、GC、AppDomain这样的为CLR的一些概念组成,实质上指的是.NET Framework CLR。

      .NET平台是微软为了占据开发市场而成立的,不是无利益驱动的纯技术平台的那种东西。基于该平台下的技术框架也因为 商业间的利益 从而和微软自身的Windows操作系统所绑定。所以虽然平台雄心和口号很大,但很多框架类库技术都是以Windows系统为蓝本,这样就导致,虽然.NET各方面都挺好,但是用.NET就必须用微软的东西,直接形成了技术-商业的绑定。

      .NET Framework就是.NET 技术框架组成在Windows系统下的具体的实现,和Windows系统高度耦合,上文介绍的.NET系统,就是指.NET Framework。

      部署.net Framework :https://docs.microsoft.com/zh-cn/dotnet/framework/deployment/deployment-guide-for-developers 
      .NET Framework高级开发:https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/29eafad8(v%3dvs.90) 
      .NET Framework源码在线浏览:https://referencesource.microsoft.com/

      如何在VS中调试.NET Framework源代码

      最为关键的是pdb符号文件,没得符号就调不了,对于符号我们从微软的符号服务器上下载(默认就已配置),还得有源代码来调试。

      点击工具-选项-调试-常规,如果你之前没有在该配置栏配置过,那么你就勾选 启用源服务器支持 、启用.net Framework源代码单步执行,然后将 要求源文件与原始版本完全匹配 给取消掉。

      然后就是下载pdb符号文件了,如果想直接下载那么可以在调试-符号这栏 将Microsoft符号服务器给勾上 。如果想按需下载,那么在调试的时候,可以点击调试-窗口 选择 模块/调用堆栈 来选择自己想加载的去加载。

      然后至 https://referencesource.microsoft.com/网站 点击右上角下载源代码。当你调试代码的时候,会提示你无可用源,这个时候你再将你下载下来的源码文件给浏览查找一下就可以了。

      如何配置VS来调试.NET Framework源码: https://referencesource.microsoft.com/#q=web 、https://technet.microsoft.com/zh-cn/cc667410.aspx

      还一种方法是,下载.NET Reflector插件,该插件可以帮助我们在VS中直接调试dll,这种方式操作非常简单,不过该插件收费,具体的可以查看我之前写过的文章(群里有该插件的注册版)

      .NET Core是什么

      有丑才有美,有低才有高,概念是比较中诞生的。.NET Core就是如此,它是其它操作系统的.NET Framework翻版实现。

      操作系统不止Windows,还有Mac和类Linux等系统, .NET的实现 如果按操作系统来横向分割的话,可以分为 Windows系统下的 .NET Framework 和 兼容多个操作系统的 .NET Core。

      我们知道,一个.NET程序运行核心在于.NET CLR,为了能让.NET程序在其它平台上运行,一些非官方社区和组织为此开发了在其它平台下的.NET实现(最为代表的是mono,其团队后来又被微软给合并了 ),但因为不是官方,所以在一些方面多少有些缺陷(如FCL),后来微软官方推出了.NET Core,其开源在Github中,并被收录在NET基金会(.NET Foundation,由微软公司成立与赞助的独立自由软件组织,其目前收录包括.NET编译器平台("Roslyn")以及ASP.NET项目系列,.NET Core,Xamarin Forms以及其它流行的.NET开源框架),旨在真正的 .NET跨平台。

      .NET Core是.NET 技术框架组成在Windows.macOS.Linux系统下的具体的实现。
      .NET Core是一个开源的项目,其由 Microsoft 和 GitHub 上的 .NET 社区共同维护,但 这份工作仍然是巨大的,因为在早期对.NET上的定义及最初的实现一直是以Windows系统为参照及载体,一些.NET机制实际上与Windows系统耦合度非常高,有些属于.NET自己体系内的概念,有些则属于Windows系统api的封装。 那么从Windows转到其它平台上,不仅要实现相应的CLR,还要舍弃或重写一部分BCL,因而,.NET Core在概念和在项目中的行为与我们平常有些不同。

      比如,NET Core不支持AppDomains、远程处理、代码访问安全性 (CAS) 和安全透明度,任何有关该概念的库代码都应该被替换。 
      这部分代码它不仅指你项目中的代码,还指你项目中using的那些程序集代码,所以你会在github上看到很多开源项目都在跟进对.NET Core的支持,并且很多开发者也尝试学习.NET Core,这也是一种趋势。

      .NET Core指南https://docs.microsoft.com/en-us/dotnet/core/
      .NET基金会:https://dotnetfoundation.org 
      .NET Core跨平台的行为变更:https://github.com/dotnet/corefx/wiki/ApiCompat 
      微软宣布.NET开发环境将开源 :https://news.cnblogs.com/n/508410/

      .NET Standard是什么

      值得一提的是微软还为BCL提出了一个标准,毕竟各式各样的平台,技术层出不穷,为了防止.NET在类库方面的碎片化,即提出了一套正式的 .NET API (.NET 的应用程序编程接口)规范,.NET Standard。

      正如上面CLS一样,.NET Standard就类似于这样的一个概念,无论是哪个托管框架,我们遵循这个标准,就能始终保持在BCL的统一性,即我不需要关心我是用的.NET Framework还是.NET Core,只要该类被定义于.NET Standard中,我就一定能在对应支持的.NET Standard的版本的托管框架中找到它。

      .NET Standard: https://docs.microsoft.com/zh-cn/dotnet/standard/net-standard#net-implementation-support

      .NET Standard开源代码:https://github.com/dotnet/standard

      .NET官方开源项目链接

      现在我将给出.NET相关的开源项目地址:
      参与.NET和.NET开源项目的起点:https://github.com/Microsoft/dotnet

      Visual Studio

      在文章最后,我还要简单的说下Visual Studio。

      通过上文得知,只需要一个txt记事本+csc.exe我们就可以开发出一个.NET程序,那么与之相比,.NET提供的开发工具VS有什么不同呢?

      我们用记事本+csc.exe来编写一个.NET程序只适合小打小闹,对于真正要开发一个项目而言,我们需要文件管理、版本管理、一个好的开发环境等。而vs ide则就是这样一个集成代码编辑、编译、调试、追踪、测试、部署、协作、插件扩展这样多个组件的集成开发环境,csc.exe的编译功能只是vs ide中的其中之一。使用vside开发可以节省大量的开发时间和成本。

      sln解决方案

      当你用VS来新建一个项目时,VS会先为你新建一个整体的解决方案。这个解决方案表现为.sln和.suo后缀格式的文件,它们均是文本文件,对解决方案右键属性可以进行相应的修改,也可以直接用记事本打开。

      在sln中,定义了解决方案的版本及环境,如包含的项目,方案启动项,生成或部署的一些项目配置等,你可以通过修改或重新定义sln来更改你的整个解决方案。 
      而suo则包含于解决方案建立关联的选项,相当于快照,储存了用户界面的自定义配置、调试器断点、观察窗口设置等这样的东西,它是隐藏文件,可删除但建议不要删除。

      我们可以通过对比各版本之间的sln来修改sln,也可以使用网上的一些转换工具,也可以直接点击VS的文件-新建-从现有代码创建项目来让项目在不同VS版本间切换。 
      Visual Studio 2010 - # Visual Studio 4.0
      Visual Studio 2012 - # Visual Studio 4.0
      Visual Studio 2013 - # Visual Studio 12.00
      Visual Studio 2015 - # Visual Studio 14
      Visual Studio 2017 - # Visual Studio 15

      项目模板

      VS使用项目模板来基于用户的选择而创建新的项目,也就是新建项目中的那些展示项(如mvc5项目/winform项目等等),具体表现为包含.vstemplate及一些定义的关联文件这样的母版文件。将这些文件压缩为一个 .zip 文件并放在正确的文件夹中时,就会在展示项中予以显示。

      用户可以创建或自定义项目模板,也可以选择现有的模板,比如我创建一个控制台项目就会生成一个在.vstemplate中定义好的Program.cs、AssemblyInfo.cs(程序集级别的特性)、App.config、ico、csproj文件

      csproj工程文件

      这里面,csproj是我们最常见的核心文件,CSharp Project,它是用于构建这个项目的工程文件。

      csproj是基于xml格式的MSBuild项目文件,其仍然是文本文件,可以打开并修改定义了的工程构造的属性,比如选择性的添加或删除或修改包含在项目中的文件或引用、修改项目版本、将其转换为其它类型项目等。

      MSBuild是微软定义的一个用于生成应用程序的平台(Microsoft Build Engine),在这里为VS提供了项目的构造系统,在微软官方文档上有着详细的说明:https://msdn.microsoft.com/zh-cn/library/dd393573.aspxhttps://docs.microsoft.com/zh-cn/visualstudio/msbuild/msbuild

      项目属性杂项

      现在,简单说明一下csproj文件的一些核心元素。我们用vs新建一个控制台项目,然后对项目右键属性打开项目属性,在应用程序页我们可以定义:程序集名称(生成出来的程序集以程序集名称作为文件名,相当于csc中的/out)、默认命名空间(每次新建类里面显示的命名空间)、目标框架、应用程序类型、程序集信息(AssemblyInfo中的信息)、启动对象(可同时存在多个Main方法,需指定其中一个为入口对象)、程序集资源(一些可选的图标及文件)

      1.在生成页有:

      • 条件编译符号(全局的预编译#define指令,不用在每个文件头部定义,相当于csc中的/define)
      • 定义DEBUG/TRACE常量(用于调试输出的定义变量,如智能追踪的时候可以输出该变量)
      • 目标平台(指定当前面向什么处理器生成的程序集,相当于csc中的/platform。选择x86则生成的程序集生成32位程序,能在32/64位Intel处理器中使用。选择x64则生成64位,只能在64位系统中运行。选择Any CPU则32位系统生成32位,64位系统则生成64位。注意:编译平台和目标调用平台必须保持一致,否则报错。生成的32位程序集不能调用64位程序集,64位也不能调用32位)、首选32位(如果目标平台是Any CPU并且项目是应用程序类型,则生成的是32位程序集)
      • 允许不安全代码(unsafe开关,在c#中进行指针编程,如调换a方法和b方法的地址)
      • 优化代码(相当于csc中的/optimize,优化IL代码让调试难以进行,优化JIT代码)
      • 输出路径(程序集输出目录,可选择填写相对路径目录或绝对路径目录)
      • XML文档文件(相当于csc中的/doc,为程序集生成文档注释文件,浏览对方程序集对象就可以看到相关注释,VS的智能提示技术就运用于此)
      • 为COM互操作注册(指示托管应用程序将公开一个 COM 对象,使COM对象可以与托管应用程序进行交互)

      2.在高级生成设置中有:语言版本(可以选择C#版本)、调试信息(相当于csc中的/debug。选择none则不生成任何调试信息,无法调试。选择full则允许将调试器附加到运行程序,生成pdb调试文件。选择pdb-only,自.NET2.0开始与full选项完全相同,生成相同的pdb调试文件。)、文件对齐(指定输出文件中节的大小)、DLL基址(起点地址)

      3.在生成事件选项中可以设置生成前和生产后执行的命令行,我们可以执行一些命令。

      4.在调试选项中有一栏叫:启用Visual Studio承载进程,通过在vshost.exe中加载运行项目程序集,这个选项可以增加程序的调试性能,启用后会自动在输出目录生成{程序集名称}.vshost.exe这样一个文件,只有当当前项目不是启动项目的时候才能删除该文件。

      IntelliTrace智能追溯

      还要介绍一点VS的是,其IntelliTrace智能追溯功能,该功能最早存在于VS2010旗舰版,是我用的最舒服的一个功能。

      简单介绍,该功能是用来辅助调试的,在调试时可以让开发人员了解并追溯代码所产生的一些事件,并且能够进行回溯以查看应用程序中发生的情形,它是一个非常强大的调试追踪器,它可以捕捉由你代码产生的事件,如异常事件、函数调用(从入口)、ADO.NET的命令(Sql查询语句...)、ASP.NET相关事件、代码发送的HTTP请求、程序集加载卸载事件、文件访问打开关闭事件、Winform/Webform/WPF动作事件、线程事件、环境变量、Console/Trace等输出...

      我们可以通过在调试状态下点击调试菜单-窗口-显示诊断工具,或者直接按Ctrl+Alt+F2来唤起该功能窗口。

      当然,VS还有其它强大的功能,我建议大家依次点完 菜单项中的 调试、体系结构、分析这三个大菜单里面的所有项,你会发现VS真是一个强大的IDE。比较实用且方便的功能举几个例子:

      比如 从代码生成的序列图,该功能在vs2015之前的版本可以找到(https://msdn.microsoft.com/en-us/library/dd409377.aspx 、https://www.zhihu.com/question/36413876)

      比如 模块关系的代码图,可以看到各模块间的关系 

      比如 对解决方案的代码度量分析结果 

      比如 调试状态下 函数调用的 代码图,我们可以看到MVC框架的函数管道模型

      以及并行堆栈情况、加载的模块、线程的实际情况

      还有如进程、内存、反汇编、寄存器等的功能,这里不再一一展示

      链接

      有关解决方案:https://msdn.microsoft.com/zh-cn/library/b142f8e7(v=vs.110).aspx
      有关项目模板: https://msdn.microsoft.com/zh-cn/library/ms247121(v=vs.110).aspx 
      有关项目元素的说明介绍:https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2010/16satcwx(v%3dvs.100) 
      有关调试更多内容:https://docs.microsoft.com/zh-cn/visualstudio/debugger/
      有关代码设计建议:https://docs.microsoft.com/zh-cn/visualstudio/code-quality/code-analysis-for-managed-code-warnings 
      有关IntelliTrace介绍:https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2010/dd264915(v%3dvs.100)

      建议

      我热爱编程。

      我知道大多数人对技术的积累都是来自于平常工作中,工作中用到的就去学,用不到就不学,学一年的知识,然后用个五六年。 
      我也能理解人的理想和追求不同,有的人可能就想平淡点生活。有的人可能是过了拼劲,习惯了安逸。有的人已经认命了。 
      而我现在也每天饱满工作没多少时间,但在下班之余我仍然坚持每天都看一看书。 
      想学没时间学,想拼不知道往哪拼。有埋汰自己脑袋笨的,有说自己不感兴趣的。有明明踌躇满志,但总三天捕鱼两天晒网的。我身边的朋友大多都这样。

      我想说,尽管我们每个人的境遇、思想、规划不同,但我肯定大家大部分是出于生计而工作。 
      而出于生计,那就是为了自己。而既然是为了自己,那就别每天浑浑噩噩过,即使你因各种原因而没有斗志。

      编程来不得虚的,如果你没走上管理,那么你的技术好就是好,不好就是不好,混不得,一分技术一分钱。自己不扎实,你运气就不可能太好。
      技术是相通的,操作系统、通信、数据结构、协议标准、技术规范、设计模式,语言只是门工具。要知其然也要知其所以然,只知道1个梨+1个梨=2个梨,不知道1个苹果+1个苹果等于啥就悲剧了。

      那怎样提升自己?肯定不能像之前那样被动的去学习了。
      光靠工作中的积累带来的提升是没有多少。你不能靠1年的技术重复3年的劳动,自己不想提升就不能怨天尤人。
      上班大家都一样,我认为成功与否取决于你的业余时间。你每天下班无论再苦都要花一个小时来学习,学什么都行,肯定能改变你的人生轨迹。
      比如你每天下班后都用一小时来学一个概念或技术点,那么300天就是300个概念或者技术点,这是何等的恐怖。

      当然,这里的学要有点小方法小技巧的。不能太一条道摸到黑的那种,虽然这样最终也能成功,并且印象还深刻,但是总归效率是有点低的。
      比如你从网上下载个项目源码,你项目结构不知道,该项目运用技术栈也不太了解,就一点一点的开始解读。这是个提升的好方法,但这样很累,可以成功,但是很慢。见的多懂的少,往往会因为一个概念上的缺失而在一个细小的问题上浪费很长时间。或者说一直漫无目的的看博客来了解技术,那样获取的知识也不系统。

      我的建议是读书,书分两类,一类是 讲底层概念的 一类是 讲上层技术实现的。
      可以先从上层技术实现的书读起(如何连接数据库、如何写网页、如何写窗体这些)。在有一定编程经验后就从底层概念的书开始读,操作系统的、通信的、数据库的、.NET相关组成的这些...
      读完之后再回过头读这些上层技术的书就会看的更明白更透彻,最后再琢磨git下来的项目就显得轻松了。

      就.NET CLR组成这一块中文书籍比较少,由浅到深推荐的书有 你必须知道的.NET(挺通俗),CLR C#(挺通俗,进阶必看),如果你想进一步了解CLR,可以看看园子里 包建强http://www.cnblogs.com/Jax/archive/2009/05/25/1488835.html 和中道学友http://www.cnblogs.com/awpatp/archive/2009/11/11/1601397.html翻译的书籍及文章,当然如果你英语合格的话也可以直接阅读他们翻译的来源书籍,我这里有Expert .NET 2.0 IL Assembler的机器翻译版,同时我也建议从调试的方面入手,如 NET高级调试(好多.NET文件调试、反编译的文章都是参考这本书和Apress.Expert.dot.NET.2.0.IL.Assembler(这本书我有机器翻译版)的内容)或者看看Java的JVM的文章。
      欢迎加群和我交流(书籍我都放在群文件里了)

       

      现在技术发展很快,我建议大家有基础的可以直接看官方文档,(详细链接我已经在各小节给出)以下是部分常用总链接:

      asp.net指南:https://docs.microsoft.com/zh-cn/aspnet/#pivot=core 
      Visual Studio IDE 指南:https://docs.microsoft.com/zh-cn/visualstudio/ide/ 
      C# 指南: https://docs.microsoft.com/zh-cn/dotnet/csharp/
      .NET指南:https://docs.microsoft.com/zh-cn/dotnet/standard/ 
      微软开发文档:https://docs.microsoft.com/zh-cn/

      最后送给大家我经常做的两句话:
      1.先问是不是,再问怎样做,最后我一定会问 为什么
      2.没人比谁差多少,相信自己,坚持不断努力,你也能成功

       

      我喜欢和我一样的人交朋友,不被环境影响,自己是自己的老师,欢迎加群 .Net web交流群, QQ群:166843154 欲望与挣扎

      作者:小曾
      出处:https://www.cnblogs.com/1996V/p/9037603.html 欢迎转载,但请保留以上完整文章,在显要地方显示署名以及原文链接。
      .Net交流群, QQ群:166843154 欲望与挣扎

       

      我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=367ug8kkmjk0s

    展开全文
  • 点击蓝色“程序猿DD”关注我回复“资源”获取独家整理的学习资料!作者 |潘锦来源 |phppan.com/2018/04/svr-stack说到后台技术栈,脑海中是不...

    点击蓝色“程序猿DD”关注我

    回复“资源”获取独家整理的学习资料!

    640?

    作者 | 潘锦

    来源 | phppan.com/2018/04/svr-stack

    说到后台技术栈,脑海中是不是浮现的是这样一幅图?


    640?wx_fmt=jpeg

    图 1


    有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如使用的框架,语言,数据库,服务,操作系统等等。


    整个后台技术栈我的理解包括 4 个层面的内容:


    • 语言:用了哪些开发语言,如:C++/Java/Go/PHP/Python/Ruby 等等;

    • 组件:用了哪些组件,如:MQ 组件,数据库组件等等;

    • 流程:怎样的流程和规范,如:开发流程,项目流程,发布流程,监控告警流程,代码规范等等;

    • 系统:系统化建设,上面的流程需要有系统来保证,如:规范发布流程的发布系统,代码管理系统等等;


    结合以上的的 4 个层面的内容,整个后台技术栈的结构如图 2 所示:


    640?wx_fmt=png

    图2 后台技术栈结构


    以上的这些内容都需要我们从零开始搭建,在创业公司,没有大公司那些完善的基础设施,需要我们从开源界,从云服务商甚至有些需要自己去组合,去拼装,去开发一个适合自己的组件或系统以达成我们的目标。咱们一个个系统和组件的做选型,最终形成我们的后台技术栈。

    各系统组件选型

    1、项目管理/Bug管理/问题管理

    项目管理软件是整个业务的需求,问题,流程等等的集中地,大家的跨部门沟通协同大多依赖于项目管理工具。有一些 SaaS 的项目管理服务可以使用,但是很多时间不满足需求,此时我们可以选择一些开源的项目,这些项目本身有一定的定制能力,有丰富的插件可以使用,一般的创业公司需求基本上都能得到满足,常用的项目如下:


    • Redmine:用 Ruby 开发的,有较多的插件可以使用,能自定义字段,集成了项目管理,Bug 问题跟踪,WIKI 等功能,不过好多插件 N 年没有更新了;

    • Phabricator:用 PHP 开发的,Facebook 之前的内部工具,开发这工具的哥们离职后自己搞了一个公司专门做这个软件,集成了代码托管, Code Review,任务管理,文档管理,问题跟踪等功能,强烈推荐较敏捷的团队使用;

    • Jira:用 Java 开发的,有用户故事,task 拆分,燃尽图等等,可以做项目管理,也可以应用于跨部门沟通场景,较强大;

    • 悟空 CRM :这个不是项目管理,这个是客户管理,之所以在这里提出来,是因为在 To B 的创业公司里面,往往是以客户为核心来做事情的,可以将项目管理和问题跟进的在悟空 CRM 上面来做,他的开源版本已经基本实现了 CR< 的核心 功能,还带有一个任务管理功能,用于问题跟进,不过用这个的话,还是需要另一个项目管理的软件协助,顺便说一嘴,这个系统的代码写得很难维护,只能适用于客户规模小(1万以内)时。

    2、DNS

    DNS 是一个很通用的服务,创业公司基本上选择一个合适的云厂商就行了,国内主要是两家:


    • 阿里万网:阿里 2014 年收购了万网,整合了其域名服务,最终形成了现在的阿里万网,其中就包含 DNS 这块的服务;

    • 腾讯 DNSPod:腾讯 2012 年以 4000 万收购 DNSPod 100% 股份,主要提供域名解析和一些防护功能;

    如果你的业务是在国内,主要就是这两家,选 一个就好,像今日头条这样的企业用的也是 DNSPod 的服务,除非一些特殊的原因才需要自建,比如一些 CDN 厂商,或者对区域有特殊限制的。要实惠一点用阿里最便宜的基础版就好了,要成功率高一些,还是用 DNSPod 的贵的那种。


    在国外还是选择亚马逊吧,阿里的 DNS 服务只有在日本和美国有节点,东南亚最近才开始部点, DNSPod 也只有美国和日本,像一些出海的企业,其选择的云服务基本都是亚马逊。


    如果是线上产品,DNS 强烈建议用付费版,阿里的那几十块钱的付费版基本可以满足需求。如果还需要一些按省份或按区域调试的逻辑,则需要加钱,一年也就几百块,省钱省力。


    如果是国外,优先选择亚马逊,如果需要国内外互通并且有自己的 APP 的话,建议还是自己实现一些容灾逻辑或者智能调度,因为没有一个现成的 DNS 服务能同时较好的满足国内外场景,或者用多个域名,不同的域名走不同的 DNS 。

    3、LB(负载均衡)

    LB(负载均衡)是一个通用服务,一般云厂商的 LB 服务基本都会如下功能:


    • 支持四层协议请求(包括 TCP、UDP 协议);

    • 支持七层协议请求(包括 HTTP、HTTPS 协议);

    • 集中化的证书管理系统支持 HTTPS 协议;

    • 健康检查;


    如果你线上的服务机器都是用的云服务,并且是在同一个云服务商的话,可以直接使用云服务商提供的 LB 服务,如阿里云的 SLB,腾讯云的 CLB,亚马逊的 ELB 等等。如果是自建机房基本都是 LVS + Nginx。

    4、CDN

    CDN 现在已经是一个很红很红的市场,基本上只能挣一些辛苦钱,都是贴着成本在卖。国内以网宿为龙头,他们家占据整个国内市场份额的 40% 以上,后面就是腾讯,阿里。网宿有很大一部分是因为直播的兴起而崛起。


    国外,Amazon 和 Akamai 合起来占比大概在 50%,曾经的国际市场老大 Akamai 拥有全球超一半的份额,在 Amazon CDN入局后,份额跌去了将近 20%,众多中小企业都转向后者,Akamai 也是无能为力。


    国内出海的 CDN 厂商,更多的是为国内的出海企业服务,三家大一点的 CDN 服务商里面也就网宿的节点多一些,但是也多不了多少。阿里和腾讯还处于前期阶段,仅少部分国家有节点。


    就创业公司来说,CDN 用腾讯云或阿里云即可,其相关系统较完善,能轻松接入,网宿在系统支持层面相对较弱一些,而且还贵一些。并且,当流量上来后,CDN 不能只用一家,需要用多家,不同的 CDN 在全国的节点覆盖不一样,而且针对不同的客户云厂商内部有些区分客户集群,并不是全节点覆盖(但有些云厂商说自己是全网节点),除了节点覆盖的问题,多 CDN 也在一定程度上起到容灾的作用。

    5、RPC 框架

    维基百科对 RPC 的定义是:远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。


    通俗来讲,一个完整的 RPC 调用过程,就是 Server 端实现了一个函数,客户端使用 RPC 框架提供的接口,调用这个函数的实现,并获取返回值的过程。


    业界 RPC 框架大致分为两大流派,一种侧重跨语言调用,另一种是偏重服务治理。


    跨语言调用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等。这类 RPC 框架侧重于服务的跨语言调用,能够支持大部分的语言进行语言无关的调用,非常适合多语言调用场景。但这类框架没有服务发现相关机制,实际使用时需要代理层进行请求转发和负载均衡策略控制。


    其中,gRPC 是 Google 开发的高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现框架的功能需要进一步的开发。


    Hprose(High Performance Remote Object Service Engine)是一个 MIT 开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。


    服务治理型的 RPC 框架的特点是功能丰富,提供高性能的远程调用、服务发现及服务治理能力,适用于大型服务的服务解耦及服务治理,对于特定语言(Java)的项目可以实现透明化接入。缺点是语言耦合度较高,跨语言支持难度较大。国内常见的冶理型 RPC 框架如下:


    • Dubbo:Dubbo 是阿里巴巴公司开源的一个 Java 高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。当年在淘宝内部,Dubbo 由于跟淘宝另一个类似的框架 HSF 有竞争关系,导致 Dubbo 团队解散,最近又活过来了,有专职同学投入。

    • DubboX:DubboX 是由当当在基于 Dubbo 框架扩展的一个 RPC 框架,支持 REST 风格的远程调用、Kryo/FST 序列化,增加了一些新的feature。Motan:Motan 是新浪微博开源的一个 Java 框架。它诞生的比较晚,起于 2013 年,2016 年 5 月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。

    • rpcx:rpcx 是一个类似阿里巴巴 Dubbo 和微博 Motan 的分布式的 RPC 服务框架,基于 Golang net/rpc 实现。但是 rpcx 基本只有一个人在维护,没有完善的社区,使用前要慎重,之前做 Golang 的 RPC 选型时也有考虑这个,最终还是放弃了,选择了 gRPC,如果想自己自研一个 RPC 框架,可以参考学习一下。

    6、名字发现/服务发现

    名字发现和服务发现分为两种模式,一个是客户端发现模式,一种是服务端发现模式。


    框架中常用的服务发现是客户端发现模式。


    所谓服务端发现模式是指客户端通过一个负载均衡器向服务发送请求,负载均衡器查询服务注册表并把请求路由到一台可用的服务实例上。现在常用的负载均衡器都是此类模式,常用于微服务中。


    所有的名字发现和服务发现都要依赖于一个可用性非常高的服务注册表,业界常用的服务注册表有如下三个:


    • etcd,一个高可用、分布式、一致性、key-value 方式的存储,被用在分享配置和服务发现中。两个著名的项目使用了它:Kubernetes 和 Cloud Foundry。

    • Consul,一个发现和配置服务的工具,为客户端注册和发现服务提供了API,Consul还可以通过执行健康检查决定服务的可用性。

    • Apache ZooKeeper,是一个广泛使用、高性能的针对分布式应用的协调服务。Apache ZooKeeper 本来是 Hadoop 的子工程,现在已经是顶级工程了。

    除此之外也可以自己实现服务实现,或者用 Redis 也行,只是需要自己实现高可用性。

    7、关系数据库

    关系数据库分为两种,一种是传统关系数据,如 Oracle,MySQL,Maria,DB2,PostgreSQL 等等,另一种是 NewSQL,即至少要满足以下五点的新型关系数据库:


    1. 完整地支持 SQL,支持 JOIN / GROUP BY /子查询等复杂 SQL 查询。

    2. 支持传统数据标配的 ACID 事务,支持强隔离级别。

    3. 具有弹性伸缩的能力,扩容缩容对于业务层完全透明。

    4. 真正的高可用,异地多活、故障恢复的过程不需要人为的接入,系统能够自动地容灾和进行强一致的数据恢复。

    5. 具备一定的大数据分析能力。


    传统关系数据库用得最多的是 MySQL,成熟,稳定,一些基本的需求都能满足,在一定数据量级之前基本单机传统数据库都可以搞定,而且现在较多的开源系统都是基于 MySQL,开箱即用,再加上主从同步和前端缓存,百万 pv 的应用都可以搞定了。不过 CentOS 7 已经放弃了 MySQL,而改使用 MariaDB。MariaDB 数据库管理系统是 MySQ L的一个分支,主要由开源社区在维护,采用 GPL 授权许可。开发这个分支的原因之一是:甲骨文公司收购了 MySQL 后,有将 MySQL 闭源的潜在风险,因此社区采用分支的方式来避开这个风险。


    在 Google 发布了 F1: A Distributed SQL Database That Scales 和 Spanner: Google’s Globally-Distributed Databasa 之后,业界开始流行起 NewSQL。于是有了 CockroachDB,于是有了奇叔公司的 TiDB。国内已经有比较多的公司使用 TiDB,之前在创业公司时在大数据分析时已经开始应用 TiDB,当时应用的主要原因是 MySQL 要使用分库分表,逻辑开发比较复杂,扩展性不够。

    8、NoSQL

    NoSQL 顾名思义就是 Not-Only SQL,也有人说是 No – SQL,个人偏向于 Not-Only SQL,它并不是用来替代关系库,而是作为关系型数据库的补充而存在。


    常见 NoSQL 有4个类型:


    • 键值,适用于内容缓存,适合混合工作负载并发高扩展要求大的数据集,其优点是简单,查询速度快,缺点是缺少结构化数据,常见的有 Redis,Memcache,BerkeleyDB 和 Voldemort 等等;

    • 列式,以列簇式存储,将同一列数据存在一起,常见于分布式的文件系统,其中以 Hbase,Cassandra 为代表。Cassandra 多用于写多读少的场景,国内用得比较多的有 360,大概 1500 台机器的集群,国外大规模使用的公司比较多,如 eBay,Instagram,Apple 和沃尔玛等等;

    • 文档,数据存储方案非常适用承载大量不相关且结构差别很大的复杂信息。性能介于 kv 和关系数据库之间,它的灵感来于 lotus notes,常见的有 MongoDB,CouchDB 等等;

    • 图形,图形数据库擅长处理任何涉及关系的状况。社交网络,推荐系统等。专注于构建关系图谱,需要对整个图做计算才能得出结果,不容易做分布式的集群方案,常见的有 Neo4J,InfoGrid 等。


    除了以上4种类型,还有一些特种的数据库,如对象数据库,XML 数据库,这些都有针对性对某些存储类型做了优化的数据库。


    在实际应用场景中,何时使用关系数据库,何时使用 NoSQL,使用哪种类型的数据库,这是我们在做架构选型时一个非常重要的考量,甚至会影响整个架构的方案。

    9、消息中间件

    消息中间件在后台系统中是必不可少的一个组件,一般我们会在以下场景中使用消息中间件:


    • 异步处理:异步处理是使用消息中间件的一个主要原因,在工作中最常见的异步场景有用户注册成功后需要发送注册成功邮件、缓存过期时先返回老的数据,然后异步更新缓存、异步写日志等等;通过异步处理,可以减少主流程的等待响应时间,让非主流程或者非重要业务通过消息中间件做集中的异步处理。

    • 系统解耦:比如在电商系统中,当用户成功支付完成订单后,需要将支付结果给通知ERP系统、发票系统、WMS、推荐系统、搜索系统、风控系统等进行业务处理;这些业务处理不需要实时处理、不需要强一致,只需要最终一致性即可,因此可以通过消息中间件进行系统解耦。通过这种系统解耦还可以应对未来不明确的系统需求。

    • 削峰填谷:当系统遇到大流量时,监控图上会看到一个一个的山峰样的流量图,通过使用消息中间件将大流量的请求放入队列,通过消费者程序将队列中的处理请求慢慢消化,达到消峰填谷的效果。最典型的场景是秒杀系统,在电商的秒杀系统中下单服务往往会是系统的瓶颈,因为下单需要对库存等做数据库操作,需要保证强一致性,此时使用消息中间件进行下单排队和流控,让下单服务慢慢把队列中的单处理完,保护下单服务,以达到削峰填谷的作用。

    业界消息中间件是一个非常通用的东西,大家在做选型时有使用开源的,也有自己造轮子的,甚至有直接用 MySQL 或 Redis 做队列的,关键看是否满足你的需求,如果是使用开源的项目,以下的表格在选型时可以参考:


    640?wx_fmt=png

    图 3


    以上图的纬度为:名字、成熟度、所属社区/公司、文档、授权方式、开发语言、支持的协议、客户端支持的语言、性能、持久化、事务、集群、负载均衡、管理界面、部署方式、评价。

    10、代码管理

    代码是互联网创业公司的命脉之一,代码管理很重要,常见的考量点包括两块:


    • 安全和权限管理,将代码放到内网并且对于关系公司命脉的核心代码做严格的代码控制和机器的物理隔离;

    • 代码管理工具,Git 作为代码管理的不二之选,你值得拥有。GitLab 是当今最火的开源 Git 托管服务端,没有之一,虽然有企业版,但是其社区版基本能满足我们大部分需求,结合 Gerrit 做 Code review,基本就完美了。当然 GitLab 也有代码对比,但没 Gerrit 直观。Gerrit 比 GitLab 提供了更好的代码检查界面与主线管理体验,更适合在对代码质量有高要求的文化下使用。

    11、持续集成

    持续集成简,称 CI(continuous integration),是一种软件开发实践,即团队开发成员经常集成他们的工作,每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。持续集成为研发流程提供了代码分支管理/比对、编译、检查、发布物输出等基础工作,为测试的覆盖率版本编译、生成等提供统一支持。


    业界免费的持续集成工具中系统我们有如下一些选择:


    • Jenkins:Java 写的有强大的插件机制,MIT 协议开源 (免费,定制化程度高,它可以在多台机器上进行分布式地构建和负载测试)。Jenkins 可以算是无所不能,基本没有 Jenkins 做不了的,无论从小型团队到大型团队 Jenkins 都可以搞定。不过如果要大规模使用,还是需要有人力来学习和维护。

    • TeamCity:TeamCity 与 Jenkins 相比使用更加友好,也是一个高度可定制化的平台。但是用的人多了,TeamCity就要收费了。

    • Strider:Strider 是一个开源的持续集成和部署平台,使用 Node.js 实现,存储使用的是 MongoDB,BSD 许可证,概念上类似 Travis 和Jenkins。

    • GitLab CI:从GitLab 8.0开始,GitLab CI 就已经集成在 GitLab,我们只要在项目中添加一个 .gitlab-ci.yml 文件,然后添加一个 Runner,即可进行持续集成。并且 GitLab 与 Docker 有着非常好的相互协作的能力。免费版与付费版本不同可以参见这里:https://about.gitlab.com/products/feature-comparison/。

    • Travis:Travis 和 GitHub 强关联;闭源代码使用 SaaS 还需考虑安全问题;不可定制;开源项目免费,其它收费。

    • Go:Go 是 ThoughtWorks 公司最新的 Cruise Control 的化身。除了 ThoughtWorks 提供的商业支持,Go 是免费的。它适用于 Windows,Mac 和各种 Linux 发行版。

    12、日志系统

    日志系统一般包括打日志,采集,中转,收集,存储,分析,呈现,搜索还有分发等。一些特殊的如染色,全链条跟踪或者监控都可能需要依赖于日志系统实现。日志系统的建设不仅仅是工具的建设,还有规范和组件的建设,最好一些基本的日志在框架和组件层面加就行了,比如全链接跟踪之类的。


    对于常规日志系统ELK能满足大部分的需求,ELK 包括如下组件:


    • ElasticSearch 是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,RESTful 风格接口,多数据源,自动搜索负载等。

    • Logstash 是一个完全开源的工具,它可以对你的日志进行收集、分析,并将其存储供以后使用。

    • Kibana 是一个开源和免费的工具,它可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

    • Filebeat 已经完全替代了 Logstash-Forwarder 成为新一代的日志采集器,同时鉴于它轻量、安全等特点,越来越多人开始使用它。


    因为免费的 ELK 没有任何安全机制,所以这里使用了 Nginx 作反向代理,避免用户直接访问 Kibana 服务器。加上配置 Nginx 实现简单的用户认证,一定程度上提高安全性。另外,Nginx 本身具有负载均衡的作用,能够提高系统访问性能。ELK 架构如图4所示:


    640?wx_fmt=png

    图 4,ELK 流程图


    对于有实时计算的需求,可以使用 Flume + Kafka + Storm + MySQL 方案,一 般架构如图 5 所示:


    640?wx_fmt=png

    图 5,实时分析系统架构图


    其中:


    • Flume 是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的日志收集系统,支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume 提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。

    • Kafka 是由 Apache 软件基金会开发的一个开源流处理平台,由 Scala 和 Java 编写。其本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”,它以可水平扩展和高吞吐率而被广泛使用。

    Kafka 追求的是高吞吐量、高负载,Flume 追求的是数据的多样性,二者结合起来简直完美。

    13、监控系统

    监控系统只包含与后台相关的,这里主要是两块,一个是操作系统层的监控,比如机器负载,IO,网络流量,CPU,内存等操作系统指标的监控。另一个是服务质量和业务质量的监控,比如服务的可用性,成功率,失败率,容量,QPS 等等。常见业务的监控系统先有操作系统层面的监控(这部分较成熟),然后扩展出其它监控,如 Zabbix,小米的 Open-Falcon,也有一出来就是两者都支持的,如 Prometheus。如果对业务监控要求比较高一些,在创业选型中建议可以优先考虑 Prometheus。这里有一个有趣的分布,如图6所示。


    640?wx_fmt=png

    图 6,监控系统分布


    亚洲区域使用 Zabbix 较多,而美洲和欧洲,以及澳大利亚使用 Prometheus 居多,换句话说,英文国家地区(发达国家?)使用 Prometheus 较多。


    Prometheus 是由 SoundCloud 开发的开源监控报警系统和时序列数据库(TSDB)。Prometheus 使用 Go 语言开发,是 Google BorgMon 监控系统的开源版本。相对于其它监控系统使用的 push 数据的方式,Prometheus 使用的是 pull 的方式,其架构如图 7 所示:


    640?wx_fmt=png

    图 7,Prometheus 架构图


    如上图所示,Prometheus 包含的主要组件如下:


    • Prometheus Server 主要负责数据采集和存储,提供 PromQL 查询语言的支持。Server 通过配置文件、文本文件、ZooKeeper、Consul、DNS SRV Lookup 等方式指定抓取目标。根据这些目标会,Server 定时去抓取 metrics 数据,每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。

    • 客户端 SDK:官方提供的客户端类库有 Go、Java、Scala、Python、Ruby,其他还有很多第三方开发的类库,支持 Nodejs、PHP、Erlang 等。

    • Push Gateway 支持临时性 Job 主动推送指标的中间网关。

    • Exporter Exporter 是 Prometheus 的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为 Prometheus 支持的格式。与传统的数据采集组件不同的是,它并不向中央服务器发送数据,而是等待中央服务器主动前来抓取。Prometheus 提供多种类型的 Exporter 用于采集各种不同服务的运行状态。目前支持的有数据库、硬件、消息中间件、存储系统、HTTP 服务器、JMX 等。

    • Alertmanager:是一个单独的服务,可以支持 Prometheus 的查询语句,提供十分灵活的报警方式。

    • Prometheus HTTP API 的查询方式,自定义所需要的输出。

    • Grafana 是一套开源的分析监视平台,支持 Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch 等数据源,其 UI 非常漂亮且高度定制化。

    创业公司选择 Prometheus + Grafana 的方案,再加上统一的服务框架(如 gRPC),可以满足大部分中小团队的监控需求。

    14、配置系统

    随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、降级开关,灰度开关,参数的配置、服务器的地址、数据库配置等等,除此之外,对后台程序配置的要求也越来越高:配置修改后实时生效,灰度发布,分环境、分用户,分集群管理配置,完善的权限、审核机制等等,在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求,业界有如下两种方案:


    • 基于 zk 和 etcd,支持界面和 api ,用数据库来保存版本历史,预案,走审核流程,最后下发到 zk 或 etcd 这种有推送能力的存储里(服务注册本身也是用 zk 或 etcd,选型就一块了)。客户端都直接和 zk 或 etcd 打交道。至于灰度发布,各家不同,有一种实现是同时发布一个需要灰度的 IP 列表,客户端监听到配置节点变化时,对比一下自己是否属于该列表。PHP 这种无状态的语言和其他 zk/etcd 不支持的语言,只好自己在客户端的机器上起一个 Agent 来监听变化,再写到配置文件或共享内存,如 360 的 Qconf。

    • 基于运维自动化的配置文件的推送,审核流程,配置数据管理和方案一类似,下发时生成配置文件,基于运维自动化工具如 Puppet,Ansible 推送到每个客户端,而应用则定时重新读取这个外部的配置文件,灰度发布在下发配置时指定IP列表。

    创业公司前期不需要这种复杂,直接上 zk,弄一个界面管理 zk 的内容,记录一下所有人的操作日志,程序直连 zk,或者或者用 Qconf 等基于 zk 优化后的方案。

    15、发布系统/部署系统

    从软件生产的层面看,代码到最终服务的典型流程如图 8 所示:


    640?wx_fmt=png

    图 8,流程图


    从上图中可以看出,从开发人员写下代码到服务最终用户是一个漫长过程,整体可以分成三个阶段:


    • 从代码(Code)到成品库(Artifact)这个阶段主要对开发人员的代码做持续构建并把构建产生的制品集中管理,是为部署系统准备输入内容的阶段。

    • 从制品到可运行服务 这个阶段主要完成制品部署到指定环境,是部署系统的最基本工作内容。

    • 从开发环境到最终生产环境 这个阶段主要完成一次变更在不同环境的迁移,是部署系统上线最终服务的核心能力。

    发布系统集成了制品管理,发布流程,权限控制,线上环境版本变更,灰度发布,线上服务回滚等几方面的内容,是开发人员工作结晶最终呈现的重要通道。开源的项目中没有完全满足的项目,如果只是 Web 类项目,Walle、Piplin 都是可用的,但是功能不太满足,创业初期可以集成 Jenkins + Gitlab + Walle(可以考虑两天时间完善一下),以上方案基本包括制品管理,发布流程,权限控制,线上环境版本变更,灰度发布(需要自己实现),线上服务回滚等功能。

    16、跳板机

    跳板机面对的是需求是要有一种能满足角色管理与授权审批、信息资源访问控制、操作记录和审计、系统变更和维护控制要求,并生成一些统计报表配合管理规范来不断提升IT内控的合规性,能对运维人员操作行为的进行控制和审计,对误操作、违规操作导致的操作事故,快速定位原因和责任人。其功能模块一般包括:帐户管理、认证管理、授权管理、审计管理等等。


    开源项目中,Jumpserver 能够实现跳板机常见需求,如授权、用户管理、服务器基本信息记录等,同时又可批量执行脚本等功能;其中录像回放、命令搜索、实时监控等特点,又能帮助运维人员回溯操作历史,方便查找操作痕迹,便于管理其他人员对服务器的操作控制。

    17、机器管理

    机器管理的工具选择的考量可以包含以下三个方面:


    1. 是否简单,是否需要每台机器部署 Agent(客户端)

    2. 语言的选择(Puppet/Chef vs Ansible/SaltStack )开源技术,不看官网不足以熟练,不懂源码不足以精通;Puppet、Chef 基于 Ruby 开发,Ansible、SaltStack 基于 Python 开发的

    3. 速度的选择(Ansible vs SaltStack)Ansible 基于 SSH 协议传输数据,SaltStack 使用消息队列 zeroMQ 传输数据;大规模并发的能力对于几十台-200 台规模的兄弟来讲,Ansible的性能也可接受,如果一次操作上千台,用 salt 好一些。


    如图9所示:


    640?wx_fmt=png

    图 9,机器管理软件对比


    一般创业公司选择 Ansible 能解决大部问题,其简单,不需要安装额外的客户端,可以从命令行来运行,不需要使用配置文件。至于比较复杂的任务,Ansible 配置通过名为 Playbook 的配置文件中的 YAML 语法来加以处理。Playbook 还可以使用模板来扩展其功能。

    创业公司的选择

    1、选择合适的语言

    • 选择团队熟悉的/能掌控的,创业公司人少事多,无太多冗余让研发团队熟悉新的语言,能快速上手,能快速出活,出了问题能快速解决的问题的语言才是好的选择。

    • 选择更现代一些的,这里的现代是指语言本身已经完成一些之前需要特殊处理的特性,比如内存管理,线程等等。

    • 选择开源轮子多的或者社区活跃度高的,这个原则是为了保证在开发过程中减少投入,有稳定可靠的轮子可以使用,遇到问题可以在网上快速搜索到答案。

    • 选择好招人的 一门合适的语言会让创业团队减少招聘的成本,快速招到合适的人。

    • 选择能让人有兴趣的 与上面一点相关,让人感兴趣,在后面留人时有用。

    2、选择合适的组件和云服务商

    • 选择靠谱的云服务商;

    • 选择云服务商的组件;

    • 选择成熟的开源组件,而不是最新出的组件;

    • 选择采用在一线互联网公司落地并且开源的,且在社区内形成良好口碑的产品;

    • 开源社区活跃度;

    选择靠谱的云服务商,其实这是一个伪命题,因为哪个服务商都不靠谱,他们所承诺的那些可用性问题基本上都会在你的身上发生,这里我们还是需要自己做一些工作,比如多服务商备份,如用 CDN,你一定不要只选一家,至少选两家,一个是灾备,保持后台切换的能力,另一个是多点覆盖,不同的服务商在 CDN 节点上的资源是不一样的。


    选择了云服务商以后,就会有很多的产品你可以选择了,比较存储,队列这些都会有现成的产品,这个时候就纠结了,是用呢?还是自己在云主机上搭呢?在这里我的建议是前期先用云服务商的,大了后再自己搞,这样会少掉很多运维的事情,但是这里要多了解一下云服务商的组件特性以及一些坑,比如他们内网会经常断开,他们升级也会闪断,所以在业务侧要做好容错和规避。


    关于开源组件,尽可能选择成熟的,成熟的组件经历了时间的考验,基本不会出大的问题,并且有成套的配套工具,出了问题在网上也可以很快的找到答案,你所遇到的坑基本上都有人踩过了。

    3、制定流程和规范

    • 制定开发的规范,代码及代码分支管理规范,关键性代码仅少数人有权限;

    • 制定发布流程规范,从发布系统落地;

    • 制定运维规范;

    • 制定数据库操作规范,收拢数据库操作权限;

    • 制定告警处理流程,做到告警有人看有人处理;

    • 制定汇报机制,晨会/周报;

    4、自研和选型合适的辅助系统

    所有的流程和规范都需要用系统来固化,否则就是空中楼阁,如何选择这些系统呢?参照上个章节咱们那些开源的,对比一下选择的语言,组件之类的,选择一个最合适的即可。


    比如项目管理的,看下自己是什么类型的公司,开发的节奏是怎样的,瀑布,敏捷的 按项目划分,还是按客户划分等等,平时是按项目组织还是按任务组织等等。


    比如日志系统,之前是打的文本,那么上一个 ELK,规范化一些日志组件,基本上很长一段时间内不用考虑日志系统的问题,最多拆分一下或者扩容一下。等到组织大了,自己搞一个日志系统。


    比如代码管理,项目管理系统这些都放内网,安全,在互联网公司来说,属于命脉了,命脉的东西还是放在别人拿不到或很难拿到的地方会比较靠谱一些。

    5、选择过程中需要思考的问题

    技术栈的选择有点像做出了某种承诺,在一定的时间内这种承诺没法改变,于是我们需要在选择的时候有一些思考。


    看前面内容,有一个词出现了三次,合适,选择是合适的,不是最好,也不是最新,是最合适,适合是针对当下,这种选择是最合适的吗?比如用 Go 这条线的东西,技术比较新,业界组件储备够吗?组织内的人员储备够吗?学习成本多少?写出来的东西能满足业务性能要求吗?能满足时间要求吗?


    向未来看一眼,在一年到三年内,我们需要做出改变吗?技术栈要做根本性的改变吗?如果组织发展很快,在 200 人,500 人时,现有的技术栈是否需要大动?


    创业过程中需要考虑成本,这里的成本不仅仅是花费多少钱,付出多少工资,有时更重要的是时间成本,很多业务在创业时大家拼的就是时间,就是一个时间窗,过了就没你什么事儿了。

    基于云的创业公司后台技术架构

    结合上面内容的考量,在对一个个系统和组件的做选型之后,以云服务为基础,一个创业公司的后台技术架构如图10所示:


    640?wx_fmt=png

    图 10,后台技术架构

    参考资料

    [1] http://database.51cto.com/art/201109/291781.htm

    [2] https://zh.wikipedia.org/wiki/Kafka

    [3] https://prometheus.io/docs/introduction/overview/

    [4] http://deadline.top/2016/11/23/配置中心那点事/

    [5] http://blog.fit2cloud.com/2016/01/26/deployment-system.html

    本文通过OpenWrite的免费Markdown转换工具发布

    -END-

    留言交流不过瘾

    关注我,回复“加群”加入各种主题讨论群

    640?wx_fmt=gif

    640?wx_fmt=gif

    朕已阅 640?

    展开全文
  • 一、前言作为一个码农这么多年,一直在想怎么提高我们的编码效率,关于如何提高编码效率,我自己的几点体会1、清晰的项目结构,要编写代码的地方集中2、实现相同功能的代码量少并且清晰易懂3、重复或有规律的代码...
  • C#微信开发

    千次阅读 2017-06-05 20:50:30
    C#开发微信门户及应用教程   作者:伍华聪   C#开发微信门户及应用(1)--开始使用微信接口 6 1、微信账号 6 2、微信菜单定义 7 3、接入微信的链接处理 8 4、使用开发方式创建菜单 14 5、我创建的菜单案例 17 C#...
  • C#+Access+asp.net教学计划管理系统目录博主介绍1、设计的可行性1.1、MIS系统的产生和发展情况1.2、教学计划管理系统的产生和可行性分析相关技术介绍2.1、ASP.Net2.0技术的介绍 目录 博主介绍 ???? 个人主页:苏州...
  • 整个代码生成过程,无需编写代码即可完成一张表的CRUD,当然需要根据具体业务中进行相应的修改,本次示例中字段比较少,但是当一张表的字段很多时,那么此功能能够将开发效率提高几个档次。 管理员登录 默认...
  • C#笔试面试题(90道题,含答案)

    千次阅读 2019-09-03 00:13:45
    1. 您正在编写自定义字典。该自定义字典类名为MyDictionary。 您需要确保该字典是类型安全的字典。 您应该使用哪个代码段? A. class MyDictionary :Dictionary<string, string> B. class MyDictionary ...
  • C#网站后台时的记录 文章正文 正文显示使用DATALIST和数据绑定技术实现,需要接收从列表页传来的数据并根据数据来寻找相应的数据库记录。 代码如下 string id =Request.QueryString["id"];  ...
  • 目录 1 系统概述 5 1.1 背景/现状 5 1.2 目的 5 1.3进度安排 6 1.4 系统环境 6 2 系统分析 7 2.1 系统需求分析 7 2.2系统实体联系(E-R)图 8 ...3.2.4 后台管理员模块 24 3.2.5 首页公告资料模块 25.
  • 同时,实训公布本次实训内容,要求每个学生认真做好实训的准备,查阅相关资料,精心编写代码。各组学生独立完成实训项目,独立撰写实训报告,认真及时进行实训总结。指导教师通过登陆教师后台管理系统对学生的实...
  • C#.NET C/S架构快速开发框架旗舰版V5.0(Ultimate Edition) 适用开发:制造、零售、服务、商贸、物流行业的ERP、MRP、MES、CRM、MIS、HIS、POS等数据管理应用系统运行平台:Windows + .NET Framework 4.5 ...
  • 但是,如果我想不仅仅局限于C#和VB,我还想我编写代码在.Net平台上通用的话,那么我还必须得知道.NET平台支持的每一种语言和我编写代码所使用的语言的差异,从而在编写代码中避免这些。 这几年编程语言层出不穷...
  • 代码获取 关注公众号【程序猿声】后台回复【WRB】即可获取。 1、目标与应用场景 同学们在进行各类活动时,通常需要一定的场地配合。如果是室外场地,例如操场等无需进行借用预约便可使用。但是大部分活动都需要在...
  • 基于.NET Core 3.1 网站开发和部署

    万次阅读 2020-01-04 15:45:44
    2.vscode安装C#插件 在vscode插件市场中搜索安装即可 新手还可以去这里了解vscode的强大之处 3.安装数据库 这里使用Centos7,因为.NET Core 3.1只支持7及以上版本 配置网络 nmcli conn nmcli conn add ifname ens34...
  • 代码轮子

    千次阅读 2019-03-06 14:36:14
    完整App@ PPRows for Mac- 在Mac上优雅的计算你写了多少行代码。...NewsBlur作者独自一个人 Samuel Clay 做出来的一款名为 NewsBlur 的新闻阅读器, 很多人都称其为 Google Reader 的替代品, 这是它的源码 NewsBl...
  • 功能架构部分详解: 快速开发:此功能为框架的核心,通过选择数据库中的表,就能自动生成对应的实体层、业务逻辑层、控制器、前端页面Vue文件,无需编写代码即可生成基本的CRUD操作。 接口密钥管理:管理接口的密钥...
  • 一个HTML页面只能显示HTML代码信息,不能与数据库进行数据的交互。asp.net方案提供了网页与数据库交互的方法,这里举出两种:①aspx文件 ②ashx文件+ajax技术 一、创建数据库  这里以SQL Server数据库为例,...
  • C#】GridView用法

    千次阅读 2011-10-27 12:32:37
    C#精髓 GridView72大绝技(前台代码+后台代码实现) http://www.jb51.net/article/20442.htm GridView使用集锦 http://www.cnblogs.com/spark_wu/category/182353.html Gridview控件的用法 2007-03-27 19:35 ...
  • 130道C#面试题

    2016-09-02 21:04:21
    C#/.Net/数据库笔试资料 C#资料(一) 1.静态成员和非静态成员的区别? 答: 静态变量使用 static 修饰符进行声明,在类被实例化时创建,通过类进行访问不带有 static 修饰符声明的变量称做非静态变量,在对象被...
  • 您可以编写C#并以64k或64gig(从手腕到云)运行它,这真是太棒了。 Upcoming Conference: If you're in or around Chicago in July 2013, consider joining Chris Walker from SecretLabs and I at the MonkeySpace ...
  • 基于asp.net企业门户网站设计

    千次阅读 2019-04-16 23:44:34
    本文讲解了一个公司的网站的建设,它基于数据关联的公司个性化页面及动态数据生成案例,在网页方面,综合考虑了页面色彩、页面的构架,充分的利用了图片、文字、图层、表格等元素。网站内容明确,层次清楚,达到了...
  • 企业管理系统一般包含后台管理UI、组织机构管理、权限管理、日志、数据访问、表单、工作流等常用必备功能。下面收集的几款优秀开源的管理系统,值得大家入门学习。如有新的优秀项目,我会不断补充。 开源项目是...
  • 对于网民来说,域名就是访问网站的一个地址。但对于企业来说,这就是门面,其地位等同于商标。 域名怎么选? 首先,回到设置域名的初衷,建网站的诉求。一定是要为企业(个人)的搜索、线上交易、营销服务的,所以能...
  • 例如,如果为网站配置了身份验证,但用户无法提供正确的身份验证信息,则Web服务器应以状态代码401(未经授权)进行响应。客户端经过身份验证后,代码可以执行自己的检查来验证客户端是否能够访问所请求的资源。如果...
  • asp.net知识库

    2015-06-18 08:45:45
    发布Oracle存储过程包c#代码生成工具(CodeRobot) New Folder XCodeFactory3.0完全攻略--序 XCodeFactory3.0完全攻略--基本思想 XCodeFactory3.0完全攻略--简单示例 XCodeFactory3.0完全攻略--IDBAccesser ...
  •  但是,如果我想不仅仅局限于C#和VB,我还想我编写代码在.Net平台上通用的话,那么我还必须得知道.NET平台支持的每一种语言和我编写代码所使用的语言的差异,从而在编写代码中避免这些。 这几年编程语言层出不穷...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 596
精华内容 238
关键字:

新闻网站c#审核代码前后台编写

c# 订阅