wenmo8 发布的文章

推荐一个C#开源库,用于单元测试中的断言,它提供了一系列的扩展方法,使得单元测试的断言看起来更加自然流畅。

01 项目简介

FluentAssertions 是一个基于 .NET 的断言库,它提供了一种链式调用的方式来编写断言语句。支持框架.NET Framework 4.7、.NET Core 2.1、.NET Core 3.0、.NET 6、.NET Standard 2.0/2.1,支持单元测试框架MSTest2、NUnit3、XUnit2、MSpec和NSpec3。

支持扩展方法分类:

图片

02 使用方法

1、更接近于自然语言

传统写法

Assert.AreEqual(5, result);

使用 FluentAssertions:

result.Should().Be(5);

2、多个断言条件,一个语句中连续进行多个断言

string actual = "ABCDEFGHI";
actual.Should().StartWith("AB").And.EndWith("HI").And.Contain("EF").And.HaveLength(9);

3、除了基本的等值断言外,FluentAssertions 还提供了丰富的断言方法
,用于验证各种不同类型的数据和场景。例如,你可以使用 BeGreaterThan 方法来验证一个数值是否大于另一个数值:

int largerNumber = 10;  
int smallerNumber = 5;  
largerNumber.Should().BeGreaterThan(smallerNumber);

03 项目地址

https://github.com/fluentassertions/fluentassertions

- End -

更多开源项目:
https://github.com/bianchenglequ/NetCodeTop

推荐阅读

ImageSharp:高性能跨平台.NET开源图形库

Garnet:微软官方基于.Net 8开源缓存系统,可无需任何改动直接替代Redis,而且还更高性能!

一套UI精美、控件丰富、多主题的WinForm开源界面库

CodeMaid:Visual Studio代码自动整理插件!

websocket-sharp:.NET平台上的WebSocket客户端与服务器开源库

在多线程编程中,死锁是一种非常常见的问题,稍不留神可能就会产生死锁,今天就和大家分享死锁产生的原因,如何排查,以及解决办法。

线程死锁通常是因为两个或两个以上线程在资源争夺中,形成循环等待,导致它们都无法继续执行各自后续操作的现象。

我们结合下图简单举个例子,线程1拥有资源A同时使用锁A进行锁定,并等待获取资源B;与此同时线程2拥有资源B同时使用锁B进行锁定,并等待获取资源A。此时便形成了线程1和线程2相互等待对方先释放锁的现象,形成了死循环,最终导致死锁。

01
、产生死锁的必要条件

根据死锁产生的原因,可以总结出以下四个死锁产生的必要条件。

1、互斥条件

互斥即非此即彼,一个资源要不是我拥有,要不是你拥有,就是不能我们俩同时拥有。也就是互斥条件是指至少有一个资源处于非共享状态,一次只能有一个线程可以访问该资源。

2、占有并等待条件

该条件是指一个线程在拥有至少一个资源的同时还在等待获取其他线程拥有的资源。

3、不可剥夺条件

该条件是指一个线程一旦获取了某个资源,则不可被强行剥夺对该资源的所有权,只能等待该线程自己主动释放。

4、循环等待条件

循环等待是指线程等待资源形成的循环链,比如线程A等待资源B,线程B等待资源C,线程C等待资源A,但是资源A被线程A拥有,资源B被线程B拥有,资源C被线程C拥有,如此形成了依赖死循环,都在等待其他线程释放资源。

02
、代码示例

下面我们实现一个简单的死锁代码示例,代码如下:

//锁1
private static readonly object lock1 = new();
//锁2
private static readonly object lock2 = new();
//模拟两个线程死锁
public static void ThreadDeadLock()
{
    //线程1
    var thread1 = new Thread(Thread1);
    //线程2
    var thread2 = new Thread(Thread2);
    //线程1 启动
    thread1.Start();
    //线程2 启动
    thread2.Start();
    //等待 线程1 执行完毕
    thread1.Join();
    //等待 线程2 执行完毕
    thread2.Join();
}
//线程1
public static void Thread1()
{
    //线程1 首先获取 锁1
    lock (lock1)
    {
        Console.WriteLine("线程1: 已获取 锁1");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程1: 等待获取 锁2");
        //线程1 等待 锁2
        lock (lock2)
        {
            Console.WriteLine("线程1: 已获取 锁2");
        }
    }
}
//线程2
public static void Thread2()
{
    //线程2 首先获取 锁2
    lock (lock2)
    {
        Console.WriteLine("线程2: 已获取 锁2");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程2: 等待获取 锁1");
        //线程2 等待 锁1
        lock (lock1)
        {
            Console.WriteLine("线程2: 已获取 锁1");
        }
    }
}

在上面的代码中,thread1 先拥有lock1,然后尝试获取lock2;thread2 先拥有锁住 lock2,然后尝试获取lock1;由于线程间相互等待对方释放资源,所以导致死锁。

下面我们看看上面代码执行效果:

可以发现线程1和线程2都在等待彼此所拥有的锁。

03
、排查死锁

上一节中我们编写了一个简单的死锁代码示例,但是实际研发过程中代码不可能这么简单直观,一眼就能看出来问题所在。因此如何排查发生死锁呢?

其实我们的开发工具Visual Studio就可以查看。可以通过调试菜单中窗口下的线程、调用堆栈、并行堆栈等调试窗口查看。

上面代码正常运行后,编辑器为如下状态,也没有报错,啥也看不出来。

在默认状态下是无法看出东西,此时我们只需要点击全部中断按钮,则死锁的相关信息都会展示出来,如下图。

可以看到已经提示检测到死锁了,同时在调用堆栈窗口中还可以通过双击切换具体发生死锁的代码。

我们再切换至并行堆栈调试窗口,和调用堆栈相比,并行堆栈窗口更偏向图形化,并且发生死锁的两个线程方法都有体现出来,同样可以通过双击切换到具体代码,如下图:

下面我们再来看看线程调试窗口,如下图,可以发现前面有两个箭头,其中黄色箭头表示当前选中的发生死锁的代码,图中绿色选中代码,灰色箭头表示第一个发生死锁的代码。可以通过双击当前窗口中行进行发生死锁代码的切换,如下图:

当然还可以通过其他方式排查死锁,比如分析dump文件,这里就不深入了,后面有机会再单独讲解。

04
、解决办法

下面介绍几种避免死锁的指导思想。

1、顺序加锁

顺序加锁就是为了避免产生循环等待,如果大家都是先锁定lock1,再锁定lock2,则就不会产生循环等待。

看看如下代码:

//线程1
public static void Thread1New()
{
    //线程1 首先获取 锁1
    lock (lock1)
    {
        Console.WriteLine("线程1: 已获取 锁1");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程1: 等待获取 锁2");
        //线程1 等待 锁2
        lock (lock2)
        {
            Console.WriteLine("线程1: 已获取 锁2");
        }
    }
}
//线程2
public static void Thread2New()
{
    //线程2 首先获取 锁2
    lock (lock1)
    {
        Console.WriteLine("线程2: 已获取 锁2");
        //模拟一些操作
        Thread.Sleep(1000);
        Console.WriteLine("线程2: 等待获取 锁1");
        //线程2 等待 锁1
        lock (lock2)
        {
            Console.WriteLine("线程2: 已获取 锁1");
        }
    }
}

我们看看代码执行结果。

2、使用尝试锁

我们可以使用一些其他锁机制,比如使用Monitor.TryEnter方法尝试获取锁,如果在指定时间内没有获取到锁,则释放当前所拥有的锁,以此来避免死锁。

3、使用超时机制

我们可以通过Thead结合CancellationToken实现超时机制,避免线程无限等待。当然可以直接使用Task,因为Task本身就支持CancellationToken,提供了内置的取消支持使用起来更方便。

4、避免嵌套使用锁

一个线程在拥有一个锁的同时尽量避免再去申请另一个锁,这样可以避免循环等待。

上面是使用Thread实现的示例,现在大家直接使用Thread可能比较少,大多数都是使用Task,最后给大家一个Task死锁示例,代码如下:

//锁1
private static readonly object lock1 = new();
//锁2
private static readonly object lock2 = new();
//模拟两个任务死锁
public static async Task TaskDeadLock()
{
    //启动 任务1
    var task1 = Task.Run(() => Task1());
    //启动 任务2
    var task2 = Task.Run(() => Task2());
    //等待两个任务完成
    await Task.WhenAll(task1, task2);
}
//任务1
public static async Task Task1()
{
    //任务1 首先获取 锁1
    lock (lock1)
    {
        Console.WriteLine("任务1: 已获取 锁1");
        //模拟一些操作
        Task.Delay(1000).Wait();
        //任务1 等待 锁2
        Console.WriteLine("任务1: 等待获取 锁2");
        lock (lock2)
        {
            Console.WriteLine("任务1: 已获取 锁2");
        }
    }
}
//任务2
public static async Task Task2()
{
    //线程2 首先获取 锁2
    lock (lock2)
    {
        Console.WriteLine("任务2: 已获取 锁2");
        //模拟一些操作
        Task.Delay(100).Wait();
        // 任务2 等待 锁1
        Console.WriteLine("任务2: 等待获取 锁1");
        lock (lock1)
        {
            Console.WriteLine("任务2: 获取 锁1");
        }
    }
}


:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。
https://gitee.com/hugogoos/Planner

Scoop是什么?

Scoop 是一个基于 Windows 的包管理器,能够帮助开发者高效管理开发环境和应用程序。

它推荐通过命令行进行包的安装、更新和卸载,同时提供了简单易用的包组织方式,透明化了安装和管理的过程。

与传统的 Windows 应用安装比较,Scoop 允许用户使用命令行将应用安装到用户的个人目录中,消除了系统管理员权限的需求。

来看看我本地安装的应用。

安装Scoop

透过以下命令安装

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

Scoop的几个核心概念

Bucket (桶)

Bucket 是 Scoop 中用于存储应用包配置文件 (如 JSON 文件) 的地方。举一个例,
main
是 Scoop 默认的主 Bucket,还有丰富的社区共享 Bucket,如
extras
,包含了更多的应用。 添加新的 Bucket:

scoop bucket add extras
App (应用)

App 是 Scoop 的核心,它指的是安装和管理的应用程序,比如 Python,.NET,Node.js等。

程序根目录

Scoop 通常将应用安装在
~/scoop/apps/
目录中,便于用户进行独立管理。可以通过以下命令查看安装路径:

scoop prefix <app-name>
Version (版本管理)

Scoop 支持同一应用的多版本管理,通过切换功能可以随时切换用户需要的版本。

多版本管理的原理

Scoop 的多版本管理通过应用根目录中的不同子目录实现,每个版本都保存在独立的目录中:

~/scoop/apps/<app-name>/<version>

通过设置连接 (如
current
连接),Scoop 可以日常指导到指定版本:

~/scoop/apps/<app-name>/current -> <version>

这样,用户可以随时切换版本,而不需要重新安装。

怎么安装包:以 .NET 和 Python 举例

安装 version

透过 Scoop 安装 versions,versions用来管理旧版本的应用:

scoop bucket add version
安装 .NET

透过 Scoop 安装 .NET:

scoop install dotnet-sdk

验证安装:

dotnet --version
安装两个版本的 .NET

安装特定版本:

scoop install dotnet6-sdk
scoop install dotnet7-sdk

切换版本:

scoop reset dotnet6-sdk
scoop reset dotnet7-sdk
安装 Python

透过 Scoop 安装 Python:

scoop install python

验证安装:

python --version
安装两个版本的 Python

安装特定版本:

scoop install python27
scoop install python310

切换版本:

scoop reset python27
scoop reset python310

Scoop 的更新操作

更新 Scoop 自身

使用以下命令更新 Scoop:

scoop update
更新所有已安装的包

更新所有已安装包到最新版本:

scoop update *
更新特定的包

如果只需要更新某个特定包,例如 Python:

scoop update python

常见配置和问题解决

设置全局安装路径

如果需要为所有用户配置全局安装路径,可以修改 Scoop 的配置:

scoop config global_path true
环境变量冲突

切换不同版本的应用时,可能会遇到环境变量冲突。通过以下命令重置环境变量:

scoop reset <app-name>
清理过时版本

清理应用的旧版本以节省磁盘空间:

scoop cleanup <app-name>

通过 Scoop 快速搭建开发环境

利用 Scoop,可以快速搭建一个开发环境。例如:

安装 VS Code
scoop install vscode
安装 Node.js
scoop install nodejs
安装 Git
scoop install git

通过这些简单的命令,您可以快速构建一个功能齐全的开发环境。

兴趣是最好的老师,
HelloGitHub
让你对编程感兴趣!

简介

HelloGitHub
分享 GitHub 上有趣、入门级的开源项目。

github.com/521xueweihan/HelloGitHub

这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、Java、Go、C/C++、Swift...让你在短时间内感受到开源的魅力,对编程产生兴趣!


以下为本期内容|每个月
28
号更新

C 项目

1、
fluent-bit
:高效轻量的日志收集与处理工具。这是一个轻量级、高效的日志收集、处理和转发工具,支持实时收集和处理来自不同来源的日志数据,具有低内存和 CPU 占用的特性。

2、
write-a-hash-table
:从零用 C 语言实现哈希表。该项目是讲解如何用 C 语言实现一个开放寻址和双重哈希的哈希表,代码约为 200 行,预计学习时间为 1 到 2 小时。

C# 项目

3、
ArchiSteamFarm
:开源的 Steam 挂卡工具。这是一个用 C# 开发的工具,可以管理多个 Steam 账号并自动收集卡牌,无需安装或运行 Steam 客户端,支持 VAC 免疫、交易等功能,适用于 Windows、Linux 和 macOS 系统。

4、
qttabbar
:为 Windows 资源管理器增加标签页功能的工具。这是一个用 C# 开发的小工具,允许用户在 Windows 资源管理器(Explorer)中使用标签页功能,支持以标签页的形式同时打开和管理多个文件夹。

C++ 项目

5、
love
:完全免费的 2D 游戏引擎。这是一款免费、开源、可商用的 2D 游戏开发框架,支持使用 Lua 语言开发跨平台的 2D 游戏,兼容 Windows、macOS、Linux、Android 和 iOS 平台。

6、
WLED
:无线控制 LED 灯带。该项目是一个运行在 ESP32 和 ESP8266 上的 Web 服务器,可实现通过 WiFi 控制 LED 灯带的颜色和效果,支持 100 种特效、50 种调色板、250 个用户自定义预设等功能。

CSS 项目

7、
latex-css
:LaTex 文档风格的 CSS 库。这是一个能够让你的网站看起来像 LaTex 文档的 CSS 样式库,支持定理、定义、引理和证明的标签,以及作者、副标题、摘要等可选的类名。

Go 项目

8、
gofakeit
:快速生成假数据的 Go 库。该项目是用于生成各种假数据的 Go 语言库,支持随机生成名字、地址、电话和日期等格式的数据。它默认不支持生成中文假数据,但可以通过自定义方式扩展或调整生成逻辑实现。来自
@DeShuiYu
的分享

gofakeit.Name()             // Markus Moen
gofakeit.Email()            // alaynawuckert@kozey.biz
gofakeit.Phone()            // (570)245-7485
gofakeit.BS()               // front-end
gofakeit.BeerName()         // Duvel
gofakeit.Color()            // MediumOrchid
gofakeit.Company()          // Moen, Pagac and Wuckert
gofakeit.CreditCardNumber() // 4287271570245748
gofakeit.HackerPhrase()     // Connecting the array won't do anything, we need to generate the haptic COM driver!
gofakeit.JobTitle()         // Director
gofakeit.CurrencyShort()    // USD

9、
hertz
:字节跳动开源的 Go 微服务 HTTP 框架。该项目是由 CloudWeGo 团队开发的高性能、易扩展的 Go 语言 Web 框架,可用于构建 RESTful API、微服务以及需要高吞吐量的 Web 应用。在字节跳动内部,Hertz 已被广泛应用,表现稳定可靠。来自
@DeShuiYu
的分享

10、
lima
:在 macOS 上轻松运行 Linux 虚拟机。这是一款在 macOS 上创建和管理 Linux 虚拟机的工具,它通过 QEMU 和 macOS 原生虚拟化技术,提供一个开箱即用、轻量级的 Linux 虚拟机环境,支持运行各种 Linux 应用和工具,以及 Docker、Podman、K8s 等容器。

# 安装
brew install lima
# 启动
limactl start
# 运行 Linux 命令
lima sudo apt-get install -y neofetch
lima neofetch
# 运行容器
lima nerdctl run --rm hello-world
kubectl apply -f ...

11、
primitive
:用几何图形生成图像。该项目是用 Go 语言开发的命令行工具,它通过图像处理算法,可将任意图像转换为仅由单一几何图形组成的图像。

12、
zap
:快速、结构化的 Go 日志库。该项目是 Uber 开发的一款高性能日志库,专为那些需要快速、结构化日志记录的场景而设计。与其他 Go 语言的日志库相比,zap 的性能更为优越,尤其是在需要频繁记录日志的高并发环境中。来自
@两双筷子sqldc
的分享

logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
  // Structured context as loosely typed key-value pairs.
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

Java 项目

13、
async-profiler
:Java 性能分析工具。这是一个低开销的 Java 采样分析器,可用于对 Java 应用进行性能分析。它能够帮助开发者追踪 CPU 消耗的热点代码路径、定位内存泄漏,以及分析线程锁争用和线程间的同步等问题。来自
@塔咖
的分享

14、
javaparser
:强大的 Java 代码解析库。这是一个纯 Java 实现的解析库,专为解析、分析和生成 Java 代码而设计。它能够将 Java 代码解析为抽象语法树(AST),支持从 Java 1.0 到 Java 21 版本。可用于开发代码分析工具、重构工具、编译器等。

15、
kestra
:事件驱动的声明式编排平台。这是一个基于 Java 开发的事件驱动编排平台,旨在简化计划性和事件驱动工作流。它提供直观的 Web 界面,内置代码编辑器和数百个插件,用户可以通过 YAML 代码轻松构建可靠的工作流,支持编排微服务、批处理任务、临时脚本、SQL 查询、数据同步等多种任务。来自
@啊gBAfV
的分享

JavaScript 项目

16、
Dexie.js
:轻量级的 IndexedDB 库。这一个轻量级的 JavaScript 库,封装了浏览器原生的 IndexedDB API,使得在客户端浏览器中进行数据存储和查询变得更加容易,支持存储大量数据、事务和索引等功能。

const db = new Dexie('MyDatabase');

// Find some old friends
const oldFriends = await db.friends
    .where('age').above(75)
    .toArray();

// or make a new one
await db.friends.add({
    name: 'HelloGitHub',
    age: 25,
    street: 'East 13:th Street',
    picture: await getBlob('HelloGitHub.png')
});

17、
gitstars
:你的 GitHub Star 管家。这是一个用于管理 GitHub 上 Star 项目的工具,支持根据标签和编程语言对项目进行分类。来自
@cfour
的分享

18、
jitsi-meet
:开源的视频会议平台。这是一个基于 WebRTC 构建的视频会议平台,提供高清音视频质量,支持私聊、举手、投票和虚拟背景等多种功能,兼容所有主流浏览器和移动平台。

19、
mermaid
:用类似 Markdown 的语法绘制图表。这是一个开源的图表绘制工具,支持通过类似 Markdown 的语法生成多种类型的图表,如流程图、序列图和甘特图等。

20、
semantic-release
:自动化的版本管理和发布工具。这是一个基于 Node.js 开发的自动化版本管理和发布工具,旨在通过语义化版本控制(Semantic Versioning)简化项目发布流程。它通过分析 Git 提交信息,自动推导版本号,并生成相应的变更日志。

Kotlin 项目

21、
ChinesePinyin-CodeCompletionHelper
:支持拼音输入法自动补全的 JetBrains 插件。这是一个专为 JetBrains 系列 IDE 设计的插件,支持拼音和五笔输入法的代码补全功能。

Python 项目

22、
grip
:GitHub README 文件本地预览工具。这是一款用于本地预览 GFM(GitHub Flavored Markdown)文件的工具。它通过 Flask 和 GitHub Markdown API,能在本地环境中渲染并显示与 GitHub 完全一致的 Markdown 文件效果。

$ cd myrepo
$ grip
 * Running on http://localhost:6419/

23、
ha_xiaomi_home
:小米开源的 HA 插件。该项目是由小米开源的 Home Assistant 集成插件,可实现小米 IoT 智能设备与 Home Assistant 的无缝集成,支持设备发现、状态同步、远程控制等功能。来自
@魔羽Magic
的分享

24、
pex
:相见恨晚的 Python 项目打包工具。这是一个开源的 Python 项目打包工具,专为跨环境部署和无法访问公网的部署场景设计。它能够将 Python 项目及其所有依赖,甚至是 Python 解释器(可选),打包成单个可执行文件(.pex),让开发者无需安装运行环境,即可直接运行 Python 程序,支持 Linux 和 macOS 系统。

$ pex "uvicorn[standard]" -c uvicorn --inject-args 'example:app --port 8888' -ohellogithub-app.pex
$ ./hellogithub-app.pex
INFO:     Started server process [2014]
INFO:     Waiting for application startup.
INFO:     ASGI 'lifespan' protocol appears unsupported.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8888 (Press CTRL+C to quit)
^CINFO:     Shutting down
INFO:     Finished server process [2014]

25、
runlike
:快速获取 Docker 容器启动命令的工具。这是一个用于解析运行中容器的工具,可自动生成对应的 docker run 启动命令。它能够提取容器的配置信息,包括包括端口绑定、映射卷、环境变量、网络设置等,适用于复制、调试或迁移容器的场景。来自
@猎隼丶止戈reNo7
的分享

$ runlike -p redis

docker run \
    --name=redis \
    -e "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
    -e "REDIS_VERSION=2.8.9" \
    -e "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-2.8.9.tar.gz" \
    -e "REDIS_DOWNLOAD_SHA1=003ccdc175816e0a751919cf508f1318e54aac1e" \
    -p 0.0.0.0:6379:6379/tcp \
    --detach=true \
    myrepo/redis:7860c450dbee9878d5215595b390b9be8fa94c89 \
    redis-server --slaveof 172.31.17.84 6379

26、
viztracer
:开箱即用的 Python 性能分析工具。这是一款高效、易用的 Python 程序性能分析工具,帮助开发者调试代码和分析性能瓶颈。它针对性能追踪做了优化,尽可能减少对被分析程序的性能影响,支持时间线追踪、多进程分析和模块化追踪等功能,并生成直观、交互式的分析报告。来自
@孤胆枪手
的分享

# 安装
pip install viztracer
# 生成分析报告
viztracer my_script.py arg1 arg2
# 可视化查看报告
vizviewer result.json 

Rust 项目

27、
himalaya
:管理邮件的命令行工具。这是一个用 Rust 开发的命令行电子邮件客户端,支持多账户、邮件收发、多种邮件协议(如 IMAP 和 SMTP)和 PGP 加密等功能。

28、
zoxide
:更智能的 cd 命令。这是一个用 Rust 编写的高效 cd 命令工具,灵感来自 z 和 autojump。它能够根据用户访问频率自动排序目录,快速定位最可能的目标目录,并支持模糊匹配和交互选择。来自
@啊gBAfV
的分享

Swift 项目

29、
ConfettiSwiftUI
:五彩纸屑动画效果的 Swift 库。这是一个用于添加五彩纸屑动画效果的 Swift 库,支持自定义纸屑形状、爆炸半径和角度。

30、
MarkEdit
:专为 macOS 设计的极简 Markdown 编辑器。这是一款适用于 macOS 的免费开源 Markdown 编辑器,体积小巧(仅 3 MB)。它提供干净清爽的界面和专注于写作的编辑环境。作者认为 Markdown 语法非常直观,因此未提供 Markdown 预览功能。

人工智能

31、
garak
:开源的 LLMs 漏洞扫描工具。该项目是 NVIDIA 开源的用于扫描 LLMs 漏洞的工具,支持检测 LLM 可能存在的安全问题和不良行为,包括幻觉、数据泄漏、提示注入、错误信息、有毒内容生成和越狱等。

32、
letta
:为 LLM 应用注入记忆能力的开发框架。该项目是用于构建具有记忆功能的 LLM 应用的 Python 框架,支持创建拥有长期记忆和持久状态的智能体(Agent),并能够集成多种 LLM API 服务。

33、
MagicMirror
:零门槛 AI 换脸应用。这是一款轻量级、开箱即用的 AI 换脸工具,无需 GPU、可离线使用,一键轻松尝试新形象(发型、穿搭等)。来自
@Del
的分享

34、
vllm
:更高效的 LLMs 推理和服务引擎。这是一个高效易用的大型语言模型推理引擎,专为解决推理速度慢、资源利用率低等问题而设计。它基于 PyTorch 和 CUDA,并结合内存优化算法(PagedAttention)、计算图优化和模型并行技术,大幅降低 GPU 内存占用,并充分利用多 GPU 资源提升推理性能。同时,vLLM 与 HF 模型无缝兼容。支持在 GPU、CPU、TPU 等多种硬件平台上高效运行,适用于实时问答、文本生成和推荐系统等场景。来自
@Caleb
的分享

from vllm import LLM

prompts = ["Hello, my name is", "The capital of France is"]  # Sample prompts.
llm = LLM(model="lmsys/vicuna-7b-v1.3")  # Create an LLM.
outputs = llm.generate(prompts)  # Generate texts from the prompts.

35、
zerox
:使用视觉模型将 PDF 转换为 Markdown。该项目基于视觉模型 API 服务,提供了将 PDF 文档转化为 Markdown 的功能。其原理是先将原文件(如 pdf、docx)转换为图片,然后把图片发给视觉模型处理,最后汇总所有结果生成完整的 Markdown 文件。

import path from "path";
import { zerox } from "zerox";

const result = await zerox({
  filePath: path.resolve(__dirname, "./hellogithub.pdf"),
  openaiAPIKey: process.env.OPENAI_API_KEY,
});

其它

36、
awesome-low-level-design
:系统详细设计的学习资源合集。这是一份帮助开发者学习和掌握 LLD(Low-Level Design)的资源集合,包含各种系统的实现细节、示例代码和接口定义等,非常适合准备面试和提升技术水平。

37、
bocker
:仅用百行 shell 实现的 Docker。该项目通过大约 100 行 Bash 代码实现了 Docker 的基本功能,适合用于学习如何使用简单的脚本语言实现容器的核心功能。

38、
docker-xiaoya
:一键部署 Alist+Emby+Jellyfin 服务。该项目提供了一键部署 Alist、Emby 和 Jellyfin 服务的解决方案,帮你轻松构建完整的家庭多媒体中心,支持 Linux、macOS、Windows 等平台。

39、
domain-driven-hexagon
:学习领域驱动设计与六边形架构的最佳实践。这是一个结合领域驱动设计(DDD)与六边形架构(Hexagonal Architecture)的示例项目,旨在帮助开发者理解如何设计高质量、可扩展、可维护的应用程序。

40、
Racer
:自制开源小赛车。该项目是基于 ESP32-H2 构建的小型赛车,仅有五六厘米大小。它能够识别颜色并作出反应(神经网络),同时支持键盘控制。来自
@孤胆枪手
的分享

最后

感谢参与分享开源项目的小伙伴们,欢迎更多的开源爱好者来 HelloGitHub 自荐/推荐开源项目。如果你发现了 GitHub 上有趣的项目,就
点击这里
分享给大家伙吧!

本期有你感兴趣的开源项目吗?如果有的话就留言告诉我吧~如果还没看过瘾,可以
点击阅读
往期内容。

感谢您的阅读,如果觉得本期内容还不错的话
求赞、求分享
❤️

iptables 是一个常看常忘的命令,本文试图从应用的角度理解它

iptables 是运行在用户空间的应用软件,通过控制 Linux 内核 netfilter 模块,来管理网络数据包的处理和转发

一些常用的场景

1. 禁止 ip 访问后端 IP


192.168.64.6
上增加规则:

# -A INPUT: 将规则添加到 INPUT 链,表示处理进入的流量
# -s 192.168.64.7: 指定源 IP 地址,即要阻止的 IP
# -d 192.168.64.6: 指定目标 IP 地址,即后端 IP
# -j DROP: 表示丢弃匹配的流量
iptables -A INPUT -s 192.168.64.7 -d 192.168.64.6 -j DROP

# -j REJECT: 丢弃流量的同时向源 IP 返回一个拒绝消息。请求方直接提示:Connection refused
iptables -A INPUT -s 192.168.64.7 -d 192.168.64.6 -j REJECT

# -p 指定协议类型为 TCP
# --dport 指定目标端口
iptables -A INPUT -s 192.168.64.7 -d 192.168.64.6 -p tcp --dport 80 -j REJECT

# 看当前的 iptables 规则
# -L "list",列出当前的规则
# -n "numeric",即使用数字 IP 地址和端口号而不是主机名和服务名
# -v "verbose",显示详细信息
iptables -L -n -v
# 列出带编号的规则
iptables -L --line-numbers
# 删除 INPUT 链中的第 1 条规则
# 注意!删除成功后序号会改变,需要重新查询序号
iptables -D INPUT 1
# 清除 INPUT 链所有规则
iptables -F INPUT  

# 清除当前活跃的表(未指定默认是 filter 表)的所有 iptables 规则
# 等同于 iptables -F -t filter
iptables -F

2. 端口转发

默认情况下,Linux 系统不会转发目的 IP 地址不是本地网络的 IPv4 数据包。这是出于安全考虑,防止系统意外成为恶意流量的转发表。要启用 IPv4 数据包转发功能,需要修改内核参数
net.ipv4.ip_forward

需要注意,上面的命令仅临时启用 IPv4 数据包。需要永久启用转发,需要修改
/etc/sysctl.conf
文件。 在该文件中添加或修改
net.ipv4.ip_forward=1
一行。 然后运行
sudo sysctl -p
应用更改

cat /proc/sys/net/ipv4/ip_forward
sudo sysctl -w net.ipv4.ip_forward=1

将本机的 8080 端口转发到 80 端口

iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80

# curl 127.0.0.1:8080
# curl 192.168.64.6:8080
# curl: (7) Failed to connect to 127.0.0.1 port 8080 after 1 ms: Couldn't connect to server

# 非本机访问 ok
# curl 192.168.64.6:8080

PREROUTING 链修改的是从外部连接过来时的转发,所以上面的方式本机
curl 127.0.0.1:8080
会提示:Couldn't connect to server

如果本机连接到本机的转发,需要修改为 OUTPUT 链:

# 清除已有 nat 规则
# iptables -F -t nat
iptables -t nat -A OUTPUT -p tcp --dport 8080 -j REDIRECT --to-port 80

# 非本机访问失败:
# curl 192.168.64.6:8080
# curl: (7) Failed to connect to 192.168.64.6 port 8080 after 1 ms: Connection refused

# 本机访问 ok
# curl 192.168.64.6:8080
# curl 127.0.0.1:8080

转发内网 IP


192.168.64.6
上增加规则:

# 清除已有 nat 规则
# iptables -F -t nat

iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.64.7:80
iptables -t nat -A POSTROUTING -p tcp -d 192.168.64.7 --dport 80 -j SNAT --to-source 192.168.64.6

转发公网的 IP 和端口

# 清除已有 nat 规则
# iptables -F -t nat

iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 106.55.113.110:80
# --to-source 使用公网地址会无法访问
iptables -t nat -A POSTROUTING -p tcp -d 106.55.113.110 --dport 80 -j SNAT --to-source 192.168.64.6

需要注意的是
SNAT

--to-source
需要设置为连接公网的网卡对应的内网 IP(通过 ip ad 查询),如果设置为公网 IP,数据包可能被丢弃

3. 内部 IP 共享上网

NAT 目的是为了解决 IPv4 公网 IP 不足的问题:

  • 当私有网络中的设备发送数据包到公共网络时,NAT 设备会将数据包的 源IP地址(内网 IP)从私有地址转换为 公共IP地址,并维护一个转换表,记录所有的地址转换关系
  • NAT 设备接收数据包时,会根据转换表将数据包的目标 IP 地址转换为内部设备的内网 IP,并将其发送到内部网络

包含的操作:

  • SNAT (Source Network Address Translation):修改数据包的源 IP 地址
  • DNAT (Destination Network Address Translation):修改数据包的目的 IP 地址
  • MASQUERADE:和 SNAT 类似,但是对每个包都会动态获取指定输出接口(网卡)的 IP,因此如果接口的 IP 地址发送了变化,MASQUERADE 规则不受影响

举个 NAT 的例子:村民张三需要写信给河南的李四,写完后他在信封上写上,寄件人地址:
勤劳村 8 号
,收件人地址:
河南省孟津县陈倪路 20 号
。然后就把这封信投递到村里的邮局。邮递员拿到信件一看,这信要是寄出去,收件人通过
勤劳村 8 号
这个回信肯定没办法寄回村里,于是就将信封上寄件人地址修改为:
四川省兴文县勤劳村邮局
,再将信件发出,同时在本子上记录发往河南的这封信对应的是
勤劳村 8 号
。 李四收到信件就按照信件上的信息编写信封,寄件人地址:
河南省孟津县陈倪路 20 号
,收件人地址:
四川省兴文县勤劳村邮局
,这样这封回信就寄到了村里的邮局,邮递员一看到这封信是来自河南,对着笔记本就知道这封信是送往
勤劳村 8 号
,于是将收件人地址修改为了
勤劳村 8 号
,这样邮递员派件的时候就可以把回信送到张三家

详细 NAT 原理可以参考这篇文章:
[译] NAT 穿透是如何工作的:技术原理及企业级实践

实践

我是按着
使用iptables将ubuntu配置为路由器
进行操作,最后的效果:客户端可以通过连接一台配置了 SNAT 或者 MASQUERADE 的机器访问公网

注意:给网关和客户端指定
vmnet15
后还需要手动配置一下
虚拟网络

网关 IP 配置:

network:
  version: 2
  ethernets:
    ens33: # WAN 接口
      dhcp4: true
    ens34: # LAN 接口
      dhcp4: no
      addresses: [10.1.2.1/24]

客户端配置:

network:
  version: 2
  ethernets:
    ens33:
      dhcp4: no
      addresses: [10.1.2.2/24]
      gateway4: 10.1.2.1 # 网关
      nameservers:
        addresses: [114.114.114.114]

在网关机器上进行 iptables 的配置:

sudo sysctl -w net.ipv4.ip_forward=1
# 将 10.1.2.0/24 网络中的所有主机伪装成 192.168.184.131 这个公网IP地址,以便它们可以访问外部网络
# -o ens33: 匹配通过 ens33 接口出站的数据包
iptables -t nat -A POSTROUTING -s 10.1.2.0/24 -o ens33 -j SNAT --to-source 192.168.184.131

这条命令允许内部网络的设备通过网关访问互联网。内部设备的 IP 地址在数据包离开网关时被替换为网关的公共 IP 地址,从而使外部网络只看到网关的 IP 地址,除了 SNAT 还可以使用 MASQUERADE,二者效果类似

iptables -t nat -A POSTROUTING -s 10.1.2.0/24 -j MASQUERADE

理解 iptables

表:

  1. filter 表: 这是默认表,用于过滤数据包,决定是否允许数据包通过
  2. nat 表: 用于网络地址转换 (Network Address Translation, NAT)。它主要用于修改数据包的源地址或目标地址,例如将私有 IP 地址转换为公有 IP 地址
  3. mangle 表: 用于修改数据包的头部信息,例如修改 TTL (Time To Live) 值、TOS (Type of Service) 值等
  4. raw 表: 用于在连接跟踪之前处理数据包,主要用于控制连接跟踪是否启用
  5. security 表: (较新版本) 用于安全策略的实施,例如 SELinux

链:

  1. PREROUTING
  2. INPUT
  3. FORWARD
  4. OUTPUT
  5. POSTROUTING

数据包的不同场景:

  • 收到的、目的是本机的包:PRETOUTING -> INPUT
  • 收到的、目的是其他主机的包:PRETOUTING -> FORWARD -> POSTROUTING
  • 本地产生的包:OUTPUT -> POSTROUTING


图片来自:
从零开始认识 iptables

表包含若干个链:

  1. filter 表包含三个链:INPUT, FORWARD, OUTPUT
  2. nat 表包含三个链:PREROUTING, POSTROUTING, OUTPUT
  3. mangle 表五个链都包含
  4. raw 表包含两个链:PREROUTING, OUTPUT

看到这些排列组合,可能已经凌乱了,可以看下面的这张图,
conntrack
理解为
raw
表,来自:
Netfilter Kernel (Packet) Traversal

Netfilter 内核数据包遍历就像保卫萝卜(塔防游戏)一样,数据包就像游戏中的怪物,会按照特定的路径移动,链就像在特定位置安置的炮塔,
当数据包经过某个链时,链就会对数据包进行一些操作
,链中包含若干条规则

既然已经有了链,可以在数据包的不同阶段执行特定操作,为什么还需要表呢?原因是不同规则的执行顺序可能会影响结果。比如,有两条规则:

  1. 对数据包执行 SNAT
  2. 对数据包的源 IP 进行过滤

如果先执行 SNAT,过滤操作会基于 SNAT 修改后的 IP 和端口进行匹配。但如果先执行过滤,数据包可能在 SNAT 应用之前就被过滤掉了。每个表的操作结果都会影响后续表的处理,所以
表的作用是将规则按照功能进行分类,避免执行顺序导致规则失效
。表的处理顺序:
raw -> mangle -> nat -> filter

注意:
相同表中相同链中如果多个规则匹配同一个数据包,则只有第一个匹配的规则会被执行

需要注意的是,在使用 iptables 命令时,如果没有指定表,默认表是 filter(最后处理的那个表)

# 手动指定 -t 为 nat 表
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80

持久化

iptables 规则存储在内存中,
系统重启后规则会丢失
,可以安装
iptables-persistent
来持久化规则,规则保存在
/etc/iptables/rules.v4

sudo apt update
sudo apt install iptables-persistent

配置好 iptables 规则后,需要手动运行以下命令持久化规则:

sudo netfilter-persistent save

参考资料

iptables - wiki
iptables的四表五链与NAT工作原理
iptables做TCP/UDP端口转发【转】
通过iptables实现端口转发和内网共享上网
iptables error: unknown option --dport
How iptables tables and chains are traversed
[译] NAT - 网络地址转换(2016)
[译] 深入理解 iptables 和 netfilter 架构
VMware实现iptables NAT及端口映射