2024年1月

我一直认为程序员是一个技术和艺术并存的职业。我们的代码不应该是一些单纯的程序文本,而应该更像是一副富有艺术气息的作品,就如同画家作是画家的心血一样,我们的每一行代码都应该被善待和珍视。
最近,随着一些大厂的陆续暴雷,以及一些裁员风波,我在不少自媒体平台都刷到了关于新式“防御性编程”的段子,初看感觉只是个文娱的文案,但之后回忆过往,再结合自己的工作经历,发现现在这种现象确实是越来越普遍了。

对一些不太了解的同学,下面我说一说以前和现在“防御性编程”的区别。
以前的“防御性编程”
现在的“防御性编程”
定义
一种谨慎的编程方法,默认外部代码是不安全的,对外部调用不信任,需要自己完成检查和校验,最大限度的保护自己。是一种有效提高代码健壮性的手段。
通过人为的保留 bug,弱化代码性能、健壮性,通过不写注释,不写文档,提高复杂度等方式,人为降低代码的可维护性,变相提高自己的不可替代性,以达到裁员避雷的目的。
实现方式
严谨的编码规范

定义安全边界

边界值检查

权限检查

异常处理
保留 bug

弱化代码性能,为优化保留空间

不写有效的注释、文档以及提交说明

引入多余的架构,多层嵌套,超类,超长方法,海量局部变量,复杂化程序结构,提高代码的复杂度

类型、变量、方法的命名不具描述性
目的
得到健壮的代码
造就“屎山”,让作者不可替代
针对
外部代码
公司和领导

刚看到那些短视频,感觉有些看不惯,感觉短视频的作者肯定不是开发。但过后想想,又觉得这是一件多么无奈的事情呀!哎~~~

转眼间十几年过去了,我已经是两个孩子的父亲不再是那个刚出大学的少年了。我还记得,我当时的毕业设计是《基于 RFID 技术的仓库管理系统》,那时还不懂分布式,只是一个简单的 C/S 系统,当时是很用心的在做。在那个时代,我们称程序员和医生是两个不会老的职业,因为,都需要不断的学习实践丰富经历。十年磨一剑,方成架构师。
随着 Hadoop 的到来,Win Phone 与 Andorid 对局的完败,国内各种去 .NET 化风潮,作为 .NET 开发者,我们迎来了至暗时刻。在那个时候,很多人都转了 Java 或别的语言,或者改做管理、猎头。但即便是在那个年代,也没有出现现在的“防御性编程”。开发者更多是通过对新锐技术的实践,来实现自身的不可能替代性。我们所面临的最大危机,不是35,而是大数据和移动应用的大方向下,语言和技术环境的厮杀。当时,催生了无数的大数据和移动开发培训班。大数据移动开发的高薪低学历门槛,吸引了大量的人员,涌入培训学校,为今天 Java 程序员的惨烈竞争埋下伏笔。(因为大数据培训班比较赚钱,所以 .NET 课程全停了,这也是为什么 .NET 开发会有断层,导致部分老系统,年轻程序员无法维护的原因,.NET体系自身变化很快,新增的多,淘汰的也多,产品线也广,当年的培训断层导致了今天的 .NET 不好招人。)
从什么时候35岁开始有罪了呢?细想下来,也记不清了。大约是当自己的同事,因为年龄被裁员的时候,我觉得,我也快了,该想想自己今后的出路了。程序员吗,按我的印象,很多人都不善交际,围不住大领导,能进公司管理层的恐怕也是凤毛麟角。年纪大了,有了家庭了就多了份担当。大家都不得不面对35岁危机,很现实,也很无奈。
想想过往,我接手的那些穿透几十层的代码调用,不知所云的缩写命名,没人敢动的“屎山”代码。这些真的是开发者能力问题吗?还是有意而为之,只为自保?如果是后者我觉得可能他们自保失败了。
存在即是合理,有人为了有份工作连年龄都谎报,就别说有开发者在写代码时留个后手了。对于这种现象,我只能说”我理解,但我不认同”。市场能够调节供求关系,但这种人为的对35岁开发者的歧视,市场无能为力,恐怕长期都无法改变。目前这种避雷用的“防御性编程”,如果还能走通并大行其道,只能说贵公司项目管理就是垃圾。一个基本的代码检查都做不好的也就自认倒霉吧。对于一个管理过程严谨,有优秀人才把关的企业,这种方式往往行不通。
从开发者本身讲,虽然大部分人是做开发只是份工作为了养家糊口,但如果长此以往形成习惯,恐怕今后就写不出高质量的代码了。以 .NET 的多线程开发为例,一套东西需要学习的文档在700页以上,从学会到吃透,可能需要1年时间甚至更久。但如果一味的留一手,工作中不用多线程,那最多半年就会忘记的差不多了。所以,从自身职业规划角度讲,我还是呼吁大家能重视自己的代码,不要养成坏习惯,毁了这个职业。对于大部分普通开发者,只有实打实的技术实力,才是我们真正的铁饭碗。

前言:

在经过1年多的深思,十几年的框架编写技术沉淀下,花了近一个月的时间,终于又为 .Net 及 .Net Core 的微服务系列框架贡献当中的一个重要组件。

1、开源地址:

https://github.com/cyq1162/Taurus.DTC

2、Nuget 包引用ID:

由于
CYQ.Data
Orm 组件本身支持10多种数据库,因此提供的包,只根据消息队列的需要分拆提供。

默认Taurus.DTC 支持同时使用 RabbitMQ 和 Kafka 两种消息队列。

如果一个项目中只用RabbitMQ,则引入 Tarurus.DTC.RabbitMQ,减少对 Kafka 的依赖包。

3、Taurus.DTC 微服务分布式事务框架基础说明:


基础说明:

1、框架分为 Client(客户端,即调用端)和 Server(服务端,即服务提供方)。

2、项目需要选择数据存储类型(数据库或分布式缓存)和数据传输类型(消息队列)。

3、框架的名称空间:Taurus.Plugin.DistributedTransaction。

4、框架在设计上采用异步最终一致性的设计理念,并实现了无需入侵业务数据库的机制。


数据存储:

可选择数据库(MSSQL、MySql、Oracle、PostgreSql 等 CYQ.Data 所支持的10多种数据库之一)

MSSQL配置示例如下:

{"ConnectionStrings": {"DTC.Server.Conn": "server=.;database=MSLog;uid=sa;pwd=123456"}
}

也可选择分布式缓存存储,配置示例如下(二者选其一即可):

{"AppSettings": {"Redis.Servers":"127.0.0.1:6379 ,192.168.1.666666:6379-withpassword","MemCache.Servers":"127.0.0.1:11211"}
}


消息队列:

目前消息队列支持 RabbitMQ 或者 Kafka(配置其中一方即可):

{"AppSettings": {"DTC.Server.Rabbit":"127.0.0.1;guest;guest;/",//ip;username;password;virtualpath;
  "DTC.Server.Kafka":"127.0.0.1:9092"}
}

以上配置为Server端,客户端更改 Server 为 Client 即可。

名称空间下提供类库使用:

DTC:

Client 端:提供三个方法,事务提交、事务回滚、任务发布。
DTC.Client.CommitAsync(...)
//事务提交 DTC.Client.RollBacktAsync(...) //事务回滚 DTC.Client.PublishTaskAsync(...) //发布任务

Server 端:提供一个方法,事务订阅
DTC.Server.Subscribe(...)
//订阅事务回调。

DTCConfig:

左侧为代码,右侧为配置 AppSettings 的 key//是否启用,默认 true
DTCConfig.Client.IsEnable,对应配置项:DTC.Client.IsEnable//是否打印追踪日志,日志写入App_Data\log 目录。
DTCConfig.Client.IsPrintTraceLog,对应配置项:DTC.Client.IsPrintTraceLog//配置数据库存储链接,链接 key 是在 ConnectionStrings 中。
DTCConfig.Client.Conn,对应配置项:DTC.Client.Conn//配置数据库对应生成的表名:默认DTC_Client
DTCConfig.Client.TableName,对应配置项:DTC.Client.TableName//配置使用的RabbitMQ消息队列,配置格式为:ip;username;password;virtualpath;
DTCConfig.Client.Rabbit,对应配置项:DTC.Client.Rabbit//配置使用的Kafka消息队列,配置格式为:ip:port
DTCConfig.Client.Kafka,对应配置项:DTC.Client.Kafka//以下为定时线程任务配置://扫描的间隔时间
DTCConfig.Client.Worker.ScanDBSecond,对应配置项:DTC.Client.ScanDBSecond//事务或任务确认完成状态时,清除模式是删除还是转移到历史表,对应:DTC_Client_History
DTCConfig.Client.Worker.ConfirmClearMode,对应配置项:DTC.Client.ConfirmClearMode//事务或任务未完成状态时,保留秒数。
DTCConfig.Client.Worker.TimeoutKeepSecond,对应配置项:DTC.Client.TimeoutKeepSecond//事务或任务未完成状态时,清除模式是删除还是转移到历史表,对应:DTC_Client_History
DTCConfig.Client.Worker.TimeoutClearMode,对应配置项:DTC.Client.TimeoutClearMode//事务或任务未完成状态时,最大可重试次数。
DTCConfig.Client.Worker.MaxRetries,对应配置项:DTC.Client.MaxRetries

Server 和 Client 的配置属性一致,仅 Client 换成 Server。

DTC 特性标准和方法回调参数:

Server 端:
特性标注:DTCServerSubscribe
回调参数:DTCServerSubscribePara

Client 端:
特性标注:DTCClientCallBack
回调参数:DTCClientCallBackPara


4、Server 端 使用 .Net Core 示例:

1、Nuget 搜索 Taurus.DTC 引入工程项目中。

2、Program 或 Startup 添加服务使用引入:

  services.AddTaurusMvc(); //服务添加
  app.UseTaurusDtc(StartType.Server); //服务使用,启用服务端

3、appsettings.json 配置基本属性:

{"ConnectionStrings": {"DTC.Server.Conn": "host=localhost;port=3306;database=cyqdata;uid=root;pwd=123456;Convert Zero Datetime=True;"},"AppSettings": {"DTC.Server.Rabbit": "127.0.0.1;guest;guest;/" //IP;UserName;Password;VirtualPaath
}

4、选择数据库对应的依赖组件,如MySql,可以:

Nuget 上可以搜索 MySql.Data 、或者 CYQ.Data.MySql (会自动引入MySql.Data)  都可, 引入项目即可。

5、代码编写,可以参考源码中提供的示例代码,如下:

    public classServerController : Taurus.Mvc.Controller
{
/// <summary> ///provide a Create api , and it provide a transation , callhttps://localhost:5001/server/create/// </summary> [HttpPost]
[Require(
"name")]public void Create(stringname)
{
//do something insert int createID = 123456;//here will receive a header:X-Request-ID if (DTC.Server.Subscribe(createID.ToString(), "OnCreate")) //事务相关信息保存,以便后续回调处理提交或回滚 {
Console.WriteLine(
"call : DTC.Server.Subscribe call.");
}
Write(createID,
true);
}


[DTCServerSubscribe(
"OnCreate")] //订阅回调,处理提交或回滚 private static boolAnyMethodNameForOnCreateCallBack(DTCServerSubscribePara para)
{
para.CallBackContent
= "what message you need?";
Console.WriteLine(
"call back :" + para.ExeType + ", content :" +para.Content);if (para.ExeType == ExeType.Commit) { return true; }if (para.ExeType ==ExeType.RollBack)
{
string createID =para.Content;//return DeleteByID(createID); return true;
}
return false;
}

[DTCServerSubscribe(
"ToDoTask")] //订阅任务 private static boolDoSomeTask(DTCServerSubscribePara para)
{
Console.WriteLine(
"call :" + para.ExeType + ", content :" +para.Content);
para.CallBackContent
= "I do ok.";return true;
}

}


5、Client 端 使用 .Net Core 示例:

1、Nuget 搜索 Taurus.DTC 引入工程项目中。

2、Program 或 Startup 添加服务使用引入:

  services.AddTaurusMvc(); //服务添加
  app.UseTaurusDtc(StartType.Client); //服务使用,启用服务端

3、appsettings.json 配置基本属性:

{"ConnectionStrings": {"DTC.Client.Conn": "host=localhost;port=3306;database=cyqdata;uid=root;pwd=123456;Convert Zero Datetime=True;"},"AppSettings": {"DTC.Client.Rabbit": "127.0.0.1;guest;guest;/" //IP;UserName;Password;VirtualPaath
}

4、选择数据库对应的依赖组件,如MySql,可以:

Nuget 上可以搜索 MySql.Data 、或者 CYQ.Data.MySql (会自动引入MySql.Data)  都可, 引入项目即可。

5、代码编写,可以参考源码中提供的示例代码,如下:

   public classClientController : Taurus.Mvc.Controller
{
[HttpGet]
public voidTransation()
{
//do something RpcTask task = Rpc.StartPostAsync("https://localhost:5001/server/create", Encoding.UTF8.GetBytes("name=hello world"));if(task.Result.IsSuccess)
{
if(JsonHelper.IsSuccess(task.Result.ResultText))
{
if (DTC.Client.CommitAsync(1, "OnOK"))
{
Console.WriteLine(
"call : DTC.Client.CommitAsync.");
}
Write(
"Commit OK.", true);return;
}
}
if (DTC.Client.RollBackAsync(1, "OnFail"))
{
Console.WriteLine(
"call : DTC.Client.RollBackAsync call.");
}
Write(
"RollBack ing....", false);
}
[DTCClientCallBack(
"OnFail")]
[DTCClientCallBack(
"OnOK")]
[DTCClientCallBack(
"OnDoOK")]private voidOnCallBack(DTCClientCallBackPara para)
{
Console.WriteLine(
"call back :" + para.ExeType + "-" + para.CallBackKey + "-" +para.CallBackContent);
}
/// <summary> ///to publish a new task , starthttps://localhost:5000/client/publishtask/// </summary> [HttpGet]public voidPublishTask()
{
if (DTC.Client.PublishTaskAsync("I give you some info.", "ToDoTask", "OnDoOK"))
{
Console.WriteLine(
"call : DTC.Client.PublishTaskAsync.");
}
Write(
"Publish Task OK.", true);
}

}


6、其它:CYQ.Data 支持的数据库链接语句示例

###--------------------------------------------------------###

   Txt::  Txt Path=E:\
   Xml::  Xml Path=E:\
Access::  Provider=Microsoft.Jet.OLEDB.4.0; Data Source=E:\cyqdata.mdb
Sqlite::  Data Source=E:\cyqdata.db;failifmissing=false;
 MySql::  host=localhost;port=3306;database=cyqdata;uid=root;pwd=123456;Convert Zero Datetime=True;
 Mssql::  server=.;database=cyqdata;uid=sa;pwd=123456;provider=mssql; 
Sybase::  data source=127.0.0.1;port=5000;database=cyqdata;uid=sa;pwd=123456;provider=sybase; 
Postgre:  server=localhost;uid=sa;pwd=123456;database=cyqdata;provider=pg; 
    DB2:  Database=SAMPLE;User ID=administrator;Server=127.0.0.1;password=1234560;provider=db2; 
FireBird  user id=SYSDBA;password=123456;database=d:\\test.dbf;server type=Default;data source=127.0.0.1;port number=3050;provider=firebird;
Dameng::  user id=SYSDBA;password=123456789;data source=127.0.0.1;schema=test;provider=dameng;
KingBaseES server=127.0.0.1;User Id=system;Password=123456;Database=test;Port=54321;schema=public;provider=kingbasees;
Oracle ODP.NET::
Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT = 1521)))(CONNECT_DATA =(SID = orcl)));User ID=sa;password=123456

由于各种数据库链接语句基本一致,除了特定写法外,可以通过链接补充:provider=mssql、provider=mysql、provider=db2、provider=postgre等来区分。
###--------------------------------------------------------###

7、总结:

原本的设计是作为
Taurus.Mvc
微服务系列中的一个插件,单独内部集成使用,后来想了想,还是在设计中独立了出来,支持在任意 Mvc 框架中使用。

默认情况下,Spring Boot 中的 Bean 是非线程安全的。这是因为,默认情况下 Bean 的作用域是单例模式,那么此时,所有的请求都会共享同一个 Bean 实例,这意味着这个 Bean 实例,在多线程下可能被同时修改,那么此时它就会出现线程安全问题。

Bean 的作用域(Scope)指的是确定在应用程序中创建和管理 Bean 实例的范围。也就是在 Spring 中,可以通过指定不同的作用域来控制 Bean 实例的生命周期和可见性。例如,单例模式就是所有线程可见并共享的,而原型模式则是每次请求都创建一个新的原型对象。

1.单例Bean一定不安全吗?

并不是,单例 Bean 分为以下两种类型:

  1. 无状态 Bean(线程安全)
    :Bean 没有成员变量,或多线程只会对 Bean 成员变量进行查询操作,不会修改操作。
  2. 有状态 Bean(非线程安全)
    :Bean 有成员变量,并且并发线程会对成员变量进行修改操作。

所以说:
有状态的单例 Bean 是非线程安全的,而无状态的 Bean 是线程安全的

但在程序中,只要有一种情况会出现线程安全问题,那么它的整体就是非线程安全的,所以总的来说,单例 Bean 还是非线程安全的。

① 无状态的Bean

无状态的 Bean 指的是不存在成员变量,或只有查询操作,没有修改操作,它的实现示例代码如下:

import org.springframework.stereotype.Service;

@Service
public class StatelessService {
    public void doSomeTask() {
        // 执行任务
    }
}

② 有状态的Bean

有成员变量,并且存在对成员变量的修改操作,如下代码所示:

import org.springframework.stereotype.Service;

@Service
public class UserService {
    private int count = 0;
    public void incrementCount() {
        count++; // 非原子操作,并发存在线程安全问题
    }
    public int getCount() {
        return count;
    }
}

2.如何保证线程安全?

想要保证有状态 Bean 的线程安全,可以从以下几个方面来实现:

  1. 使用 ThreadLocal(线程本地变量)
    :每个线程修改自己的变量,就没有线程安全问题了。
  2. 使用锁机制
    :例如 synchronized 或 ReentrantLock 加锁修改操作,保证线程安全。
  3. 设置 Bean 为原型作用域(Prototype)
    :将 Bean 的作用域设置为原型,这意味着每次请求该 Bean 时都会创建一个新的实例,这样可以防止不同线程之间的数据冲突,不过这种方法增加了内存消耗。
  4. 使用线程安全容器
    :例如使用 Atomic 家族下的类(如 AtomicInteger)来保证线程安全,此实现方式的本质还是通过锁机制来保证线程安全的,Atomic 家族底层是通过乐观锁 CAS(Compare And Swap,比较并替换)来保证线程安全的。

具体实现如下。

① 使用ThreadLocal保证线程安全

实现代码如下:

import org.springframework.stereotype.Service;

@Service
public class UserService {
    private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);

    public void incrementCount() {
        count.set(count.get() + 1);
    }

    public int getCount() {
        return count.get();
    }
}

使用 ThreadLocal 需要注意一个问题,在用完之后记得调用 ThreadLocal 的 remove 方法,不然会发生内存泄漏问题。

② 使用锁机制

锁机制中最简单的是使用 synchronized 修饰方法,让多线程执行此方法时排队执行,这样就不会有线程安全问题了,如下代码所示:

import org.springframework.stereotype.Service;

@Service
public class UserService {
    private int count = 0;
    public synchronized void incrementCount() {
        count++; // 非原子操作,并发存在线程安全问题
    }
    public int getCount() {
        return count;
    }
}

③ 设置为原型作用域

原型作用域通过 @Scope("prototype") 来设置,表示每次请求时都会生成一个新对象(也就没有线程安全问题了),如下代码所示:

import org.springframework.stereotype.Service;

@Service
@Scope("prototype")
public class UserService {
    private int count = 0;
    public void incrementCount() {
        count++; // 非原子操作,并发存在线程安全问题
    }
    public int getCount() {
        return count;
    }
}

④ 使用线程安全容器

我们可以使用线程安全的容器,例如 AtomicInteger 来替代 int,从而保证线程安全,如下代码所示:

import org.springframework.stereotype.Service;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class UserService {

    private AtomicInteger count = new AtomicInteger(0);

    public void incrementCount() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

实际工作中如何保证线程安全?

实际工作中,通常会根据具体的业务场景来选择合适的线程安全方案,但是以上解决线程安全的方案中,ThreadLocal 和原型作用域会使用更多的资源,占用更多的空间来保证线程安全,所以在使用时通常不会作为最佳考虑方案。

而锁机制和线程安全的容器通常会优先考虑,但需要注意的是 AtomicInteger 底层是乐观锁 CAS 实现的,因此它存在乐观锁的典型问题 ABA 问题(如果有状态的 Bean 中既有 ++ 操作,又有 -- 操作时,可能会出现 ABA 问题),此时就要使用锁机制,或 AtomicStampedReference 来解决 ABA 问题了。

小结

单例模式的 Bean 并不一定都是非线程安全的,其中有状态的 Bean 是存在线程安全问题的。实际工作中通常会使用锁机制(synchronized 或 ReentrantLock)或线程安全的容器来解决 Bean 的线程安全问题,但具体使用哪种方案,还要结合具体业务场景来定。

本文已收录到我的面试小站
www.javacn.site
,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

网络安全的重要性

网络安全(Cyber Security)是一项重要的技术领域,旨在保护网络系统的硬件、软件及其系统中的数据免受任何偶然或恶意的破坏、更改或泄露。其目标是确保网络系统能够持续、可靠且正常地运行,同时保证网络服务的连续性。简而言之,网络安全就像是为网络提供了一支坚实的保镖,保护它免受各种潜在威胁的侵害,除了人为破坏因素外,几乎所有其他潜在威胁都会受到有效的防护。

image

如果你认为只要安装了杀毒软件就可以无忧无虑,那你就大错特错了。你可能会问为什么没有人攻击你呢?原因很简单,因为你对黑客来说没有足够的价值。下面我提供两个链接给你,一个是威胁地图,展示了所有攻击请求的情况,另一个是已经被黑客攻击的摄像头监控。这只是为了让你明白威胁无处不在。

实时威胁地图:
https://cybermap.kaspersky.com/cn

image

实时摄像机监控:
http://www.insecam.org/

image

真实案例

网络漏洞

image

当你在《唐人街探案》这部电影中看到黑客能够利用网络攻击打开监狱的大门,你可能觉得这种情节离谱。然而,我们不要忘记电影都是源于生活的。为了增加影视效果和个人感情色彩,电影会对情节进行改编。但实际上,在现实生活中也存在类似的真实案例。

image

信息外泄

回顾2011年12月21日的一次具有深远意义的网络安全事故,黑客公开曝光了知名程序员网站CSDN的用户数据库。这次事件导致超过600万个明文的注册邮箱账号和密码遭到泄露和外泄,成为中国互联网历史上的一次重大事件。这次安全事故的影响非常广泛,不仅仅对CSDN的用户造成了巨大的损失,也引起了整个互联网界的关注和警惕。

image

在现实生活中,类似的网络安全案件肯定还有很多,只是我们并不经常关注到它们。这是因为有一些人默默地承担着巨大的负重,致力于保护我们的网络安全。然而,如果你现在还觉得自己的个人信息在网络上是安全的,那么现在就是时候改变这种看法了。因为一旦你把信息放到了网络上,你就很难保留任何隐私。这并不是危言耸听,而是事实。因此,网络安全一直以来都是非常重要的问题。

常见网络攻击概述

网络攻击是指针对计算机信息系统、基础设施、计算机网络或个人计算机设备等进行的各种类型的恶意进攻行为。这些网络攻击动机多种多样,包括利益驱使、政治诉求、商业竞争和炫耀等。

网络攻击动机:

  • 利益驱使:一些攻击者会通过控制大量被感染的计算机(也称为"肉鸡")来从事挖矿、发送垃圾邮件等行为,以获取经济利益。还有一些黑产分子会通过拖库、出售个人信息等手段来谋取非法利益。此外,一些勒索软件如Wannacry等也是以敲诈勒索为目的,通过加密用户数据来要挟用户支付赎金。
  • 政治诉求:一些攻击者可能会篡改网页、操纵信息传播等,以达到政治宣传或煽动的目的。
  • 商业竞争:一些攻击者可能会试图获取竞争对手的机密信息,以获取商业优势。
  • 炫耀:一些攻击者可能会进行恶作剧或破坏行为,以显示自己的技术能力或满足自己的虚荣心。

信息搜集

目标信息收集是每一次攻击的关键第一步。通过尽可能全面地收集攻击目标的信息和漏洞,攻击者可以大大提高攻击的成功率。在信息收集方面,有许多方法可供攻击者选择,其中一个你可能已经听说过的就是社会工程学。

社会工程学是黑客米特尼克悔改后在《欺骗的艺术》一书中提出的一种危害手段。它利用受害者的心理弱点、本能反应、好奇心、信任和贪婪等因素,通过设置各种陷阱来欺骗和伤害目标。这种手段可以包括伪装成可信的人或机构、诱导受害者分享个人信息或敏感数据、通过社交工具获取目标的访问权限等。

image

口令突破

信息搜集完毕后,可能会有人想知道如何利用这些信息或者如何进入系统内部。在此我想简要介绍一下口令突破的概念。口令突破是指非法获取他人密码等敏感信息的行为。其中,弱口令是指仅包含简单数字和字母的口令,比如"123"、“abc"等。由于这样的口令很容易被他人破解,使用弱口令会使用户的计算机面临风险,因此不推荐用户使用弱口令。

在设置密码时,大多数系统都会有强弱判断功能,这是防止他人攻击和破解的有效手段。因此,建议大家设置一个复杂度高的密码,这样更难以被他人猜测和破解。然而,也要记住,设置复杂密码后一定要牢记,以免忘记密码而无法正常使用系统或者服务。

口令猜解技巧:经验、运气以及你对Ta的了解程度 (社工字典)

image

木马程序

特洛伊木马程序是一种恶意软件,可以直接入侵用户的电脑并造成破坏。它通常伪装成工具程序或者游戏等诱使用户打开带有特洛伊木马的邮件附件或直接从网上下载。一旦用户打开这些附件或者执行这些程序,它们就像古代特洛伊人在敌人城外留下的木马一样悄悄留在自己的电脑中,并隐藏一个在Windows启动时执行的程序。当您连接到互联网时,这个程序会通知黑客,向其报告您的IP地址和预设的端口。黑客收到这些信息后,利用这个隐藏的程序,可以任意修改您的计算机参数、复制文件,甚至窥视您硬盘中的全部内容,从而实现对您计算机的控制。

image

电子邮件攻击

首先,电子邮件轰炸和电子邮件“滚雪球”是指利用伪造的IP地址和电子邮件地址,向同一邮箱发送数以千计、万计甚至无穷多次的内容相同的垃圾邮件,从而导致受害人邮箱被“炸”,严重的情况下甚至可能对电子邮件服务器操作系统造成危险或瘫痪。

其次,电子邮件欺骗是指攻击者冒充系统管理员(邮件地址与系统管理员完全相同),给用户发送邮件要求其修改口令(口令可能为指定字符串),或在看似正常的附件中加载病毒或其他木马程序。一些单位的网络管理员会有定期免费发送防火墙升级程序的义务,这为黑客成功利用该方法提供了可乘之机。然而,只要用户提高警惕,一般情况下这种欺骗的危害性不会太大。

在前面,简要介绍了一些基本概念和常见攻击类型,然而总体而言,它们都可以归结为一个通用的流程。

image

安全意识

关键基础设施已成为主要攻击目标,其威力不亚于传统战争。我认为这主要是因为网络攻击的成本低、效果好。无论是海上、陆上还是空中的基础设施建设都需要大量的资金和时间,而网络攻击则不同,一旦成功,网络瘫痪将导致所有设备基本上都无法正常运作。就拿人们常用的二维码技术来说,一旦被攻破,现代社会几乎所有人都没有现金的储备,这将导致整个金融流通系统瘫痪。虽然短期修复还相对容易,但如果长期瘫痪,后果不堪设想。

image

总结

所有的计算机程序都是由人类编写的,然而人类是整个过程中最容易出错的环节。据统计,平均每千行代码就可能存在6个安全漏洞。如今,网络诈骗和欺诈行为层出不穷,人们对此已经有了一定的安全意识,这主要得益于网络报道和短视频的普及。然而,黑客们却并没有停下他们的步伐。一旦犯下错误,往往后悔莫及。因此,网络安全变得至关重要。网络战争几乎随时随地都在发生,因此在和平时期,我们必须提前做好准备,跟随我一起渐渐的了解网络安全。

使用STM32CubeMX软件配置STM32F407开发板上串口USART1进行DMA传输数据,然后实现与实验“
STM32CubeMX教程9 USART/UART 异步通信
”相同的目标

1、准备材料

开发板(
正点原子stm32f407探索者开发板V2.4

ST-LINK/V2驱动
STM32CubeMX软件(
Version 6.10.0

keil µVision5 IDE(
MDK-Arm

CH340G Windows系统驱动程序(
CH341SER.EXE

XCOM V2.6串口助手

2、实验目标

使用STM32CubeMX软件配置STM32F407开发板上
串口USART1进行DMA传输数据,然后实现与实验“
STM32CubeMX教程9 USART/UART 异步通信
”相同的目标

3、实验流程

3.0、前提知识

直接存储器访问(DMA)是实现存储器与外设、存储器与存储器之间高效传输数据的一种方法,其拥有
①从外设到存储器、②从存储器到外设和③从存储器到存储器三种传输模式
,STM32F407有DMA1和DMA2两个DMA控制器,其中DMA2可以实现上述三种传输模式,DMA1仅可以实现前两种传输模式

每个DMA均有8个流(stream),每个流又有8个通道,但是每个流只能同时使用8个通道中的一个
,支持DMA的外设一般可以选择8个流共64个通道中的某2个通道发起DMA请求,当多个流共同发起DMA请求时,由DMA仲裁器来决定谁先发送(软件可以配置DMA流的优先级),如下图所示为STM32F407的DMA1/2具体的外设请求映射表
(注释1)

举个例子:当使用USART1_TX发起DMA请求时,USART1_TX可以选择的DMA通道只有DMA2 Stream7 CH4,因此DMA请求从DMA2 Stream7 CH4发起,然后经过仲裁器到存储器端口处取要发送的数据,然后经过FIFO(可以配置不使用),最后将数据送到外设端口USART1,上述描述如下图中黄色标注所示
(注释1)

在使用DMA传输数据时一般需要设置①DMA请求和②DMA流两个主要参数,其中DMA请求就是存储器或者外设发起的传输需求,而DMA流就是进行DMA传输的数据链路(上图黄色链路)

使用某个外设的DMA传输时,一般流程为“添加外设具体的DMA请求 -> 选择该DMA请求的流Stream ->设置DMA优先级 -> 设置DMA请求模式 -> 设置地址递增(一般为外设地址不变,内存地址递增) -> 配置FIFO -> 配置传输数据的数据宽度 -> 启动DMA流全局中断 -> 在程序中以DMA方式启动外设”

先入先出(FIFO)可以想象成一个缓存区,启用FIFO之后,可以将输出暂存在该缓存区中,然后当数据量一旦达到了FIFO设置的阈值才会从缓存区中取出来发送出去,
当DMA传输的源和目标的数据宽度不同时,FIFO将变得非常有用(可以在FIFO中改变传输数据的宽度)

3.1、CubeMX相关配置

请先阅读“
STM32CubeMX教程1 工程建立
”实验3.4.1小节配置RCC和SYS

3.1.1、时钟树配置

系统时钟树配置均设置为STM32F407总线能达到的最高时钟频率,无需使用LSE,具体如下图所示

3.1.2、外设参数配置

具体配置如下图所示,下面对每个序号配置做详细说明

  1. 在Pinout & Configuration页面左边功能分类栏目Connectivity中单击其中USART1,在页面中间USART1 Mode and Configuration中将串口模式设置为异步通信工作模式,无硬件流控制,然后在Configuration页面中设置USART1的相关参数,具体的USART1基本参数配置与实验“STM32CubeMX USART/UART异步通信”相同,在页面下方的参数配置栏上面会有很多标签,当前处于的位置为参数设置
  2. 单击DMA Settings进入USART1的DMA配置页面,支持DMA功能的外设在这里都会有DMA Settings页面
  3. 单击ADD按键来增加外设的DMA,如果有可以使用的DMA流则会以列表形式供用户选择,这里可以选择发送TX和接收RX两个串口的DMA请求
  4. 选择可用的DMA流,DMA的方向会根据用户选择的DMA请求自动推断,一般只有一个选项,接着选择DMA流的优先级(注意与DMA中断优先级区分)
  5. 设置USART_RX的DMA模式为循环模式,并设置地址递增,由于外设USART1的地址固定,因此不需要地址递增,而接收的数据存储到内存里需要地址递增的存储
  6. 下方左侧为配置是否使用FIFO,如果使用则需要配置阈值,右边配置外设和内存的数据宽度,由于串口传输数据是以字节方式传输,存储也是,因此这里均选择Byte,下方为突发传输设置,当不使用FIFO时只能为单次传输,而当使用FIFO时,突发传输可以配置为4、8或16增量突发传输

3.1.3、外设中断配置

在Pinout & Configuration页面左边System Core/NVIC中勾选USART1和DMA2 stream2/7全局中断,然后选择合适的中断优先级即可,步骤如下图所示

3.2、生成代码

请先阅读“
STM32CubeMX教程1 工程建立
”实验3.4.3小节配置Project Manager

单击页面右上角GENERATE CODE生成工程

3.2.1、外设初始化调用流程

在生成的工程主函数main()中调用MX_DMA_Init()函数对USART1 RX/TX用到的DMA时钟及其流的中断进行了配置

在MX_USART1_UART_Init()函数中相比较于“
STM32CubeMX教程9 USART/UART 异步通信
”实验增加了USART1 RX/TX DMA相关的参数配置及初始化

3.2.2、外设中断调用流程

勾选DMA2 stream2/7全局中断后,会在生成的工程文件stm32f4xx_it.c中新增全局中断函数DMA2_Stream2_IRQHandler()和DMA2_Stream7_IRQHandler()

这两个中断服务函数均调用了HAL库的DMA中断统一管理函数HAL_DMA_IRQHandler(),该函数中根据各种标志判断DMA传输完成/失败/一半完成等事件,然后根据不同的事件调用不同的回调函数,这里DMA传输完成之后调用了hdma->->XferCpltCal1back(),这是个函数指针

上述过程如下图所示

这个函数指针在以DMA方式启动USART1发送或者接收数据时被指向DMA传输完成回调UART_DMATransmitCpl()函数

在该DMA传输完成回调UART_DMATransmitCplt()函数中最终调用了USART1_TX传输完成回调HAL_UART_TxCpltCallback()函数,该函数在串口实验中我们重新实现过,也就是说DMA最终的传输完成中断回调函数使用了外设的中断回调函数

其他的中断事件回调流程类似,上述过程如下图所示

3.2.3、添加其他必要代码

本实验与“
STM32CubeMX教程9 USART/UART 异步通信
”实验代码一致,除以下三个方面需要修改

①将主函数开始的以中断方式启动的串口接收函数修改为以DMA方式启动的HAL_UART_Receive_DMA函数

②主循环中将以中断方式启动的串口发送函数修改为以DMA方式启动的HAL_UART_Transmit_DMA函数

③删除空闲回调函数中的再次启动串口接收函数HAL_UART_Receive_IT(huart, rxBuffer, RX_CMD_LEN)

其他所有代码无需修改,具体修改后的代码如下所示

为什么删除再次启动串口接收中断的函数?

在串口章节的实验中,我们提到当以中断方式启动串口接收之后,串口接收完毕一次就不能自动接收第二次了,用户必须手动再次调用中断接收函数才可以重新接收,因此我们才在串口空闲回调函数里重新启用,以此来实现无限次的自动接收

但是在本实验中,我们已经配置了串口USART1的DMA接收模式为循环模式,在该模式下其接收完毕一次之后能够继续接收,因此不需要我们在空闲回调函数中重新启用

如果将串口USART1的DMA发送模式配置为循环模式,当按下按键触发时,会出现一直发送的情况,读者可以尝试尝试,这显然不符合我们的预想,因此串口USART1的DMA发送模式配置为了普通模式

4、常用函数

/*以DMA方式启动USART接收*/
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
 
/*以DMA方式启动USART传输*/
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)

5、烧录验证

5.1、具体步骤

“使用‘STM32CubeMX STM32F4 HAL库 USART/UART异步通信’实验的STM32CubeMX工程 -> 增加配置USART1的DMA相关设置 -> NVIC中启动DMA的全局中断并选择合适的中断优先级 -> 在生成的代码上进行3处修改(修改内容参看3.2小节)”

5.2、实验现象

实验现象与“
STM32CubeMX教程9 USART/UART 异步通信
”实验现象一致

6、注释详解

注释1
:图片来自STM32F4xx中文参考手册 RM0090

参考资料

STM32Cube高效开发教程(基础篇)

更多内容请浏览
OSnotes的CSDN博客