Clash 源码详解系列集锦
众所周知,Clash for Windows 衫裤跑路了,让我默哀一分钟,For Freedom!!!
所以一个奇怪的想法出现了,我能否扛起大旗走下去!...我瞎说的,第一步就先读懂源码吧。
众所周知,Clash for Windows 衫裤跑路了,让我默哀一分钟,For Freedom!!!
所以一个奇怪的想法出现了,我能否扛起大旗走下去!...我瞎说的,第一步就先读懂源码吧。
程序初始化会读取用户目录下的 ~/.config/clash/*
配置文件
执行 tree ~/.config/clash/*
可以查看 clash 配置文件结构如下:
├── cache.db
├── config.ini
├── config.yaml
├── Country.mmdb
├── logs
│ ├── 2024-02-28-144228.log
│ ├── 2024-03-04-175206.log
│ ├── 2024-03-04-175247.log
│ └── 2024-03-06-121729.log
└── profiles
├── 1703153138868.yml
├── 1709629664192.yml
└── list.yml
2 directories, 11 files
constant/config.go
包含了初始化代码:
HomeDir
;这是一段很标准的获取用户 HOME
目录的代码,我们可以灵活的应用在其他项目中。
currentUser, err := user.Current()
if err != nil {
dir := os.Getenv("HOME")
if dir == "" {
log.Fatalf("Can't get current user: %s", err.Error())
}
HomeDir = dir
} else {
HomeDir = currentUser.HomeDir
}
~/.config
文件;dirPath := path.Join(HomeDir, ".config", Name)
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
if err := os.MkdirAll(dirPath, 0777); err != nil {
log.Fatalf("Can't create config directory %s: %s", dirPath, err.Error())
}
}
~/.config/config.ini
文件;ConfigPath = path.Join(dirPath, "config.ini")
if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
log.Info("Can't find config, create a empty file")
os.OpenFile(ConfigPath, os.O_CREATE|os.O_WRONLY, 0644)
}
有趣的是,作者没有使用 func Create(name string) (*File, error)
方法, 定义如下:
// Create creates or truncates the named file. If the file already exists,
// it is truncated. If the file does not exist, it is created with mode 0666
// (before umask). If successful, methods on the returned File can
// be used for I/O; the associated file descriptor has mode O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
作者使用了 O_WRONLY
和 0644
权限更加合适。
这些数字和权限的映射关系如下:
4:读权限(Read) 2:写权限(Write) 1:执行权限(eXecute) 数字的意义是各个权限的相加。例如,0666 表示 4 (读) + 2 (写) + 2 (写) + 1 (执行) + 1 (执行) + 1 (执行)。
Country.mmdb
;MMDBPath = path.Join(dirPath, "Country.mmdb")
if _, err := os.Stat(MMDBPath); os.IsNotExist(err) {
log.Info("Can't find MMDB, start download")
err := downloadMMDB(MMDBPath)
if err != nil {
log.Fatalf("Can't download MMDB: %s", err.Error())
}
}
这个真是太重要了,属于是 Clash 本地存储方案的核心。
在项目方案设计是,我们可以借鉴这个思想。
不过为什么没有用 sqllite
我们可以探讨一下:
"MMDB" 和 "SQLite" 是两种完全不同的数据库系统,用于不同的用途,并有一些关键的区别。
SQLite: 是一种嵌入式数据库引擎,它是一种关系型数据库管理系统(RDBMS)。SQLite是一款轻量级、自包含的数据库,适用于嵌入式系统和移动应用等场景。
用途:
SQLite: 可以用于各种通用的数据库需求,包括嵌入式系统、移动应用、桌面应用和小型服务等。它支持 SQL 查询语言,可以存储和检索多种类型的数据。
数据模型:
SQLite: 遵循关系型数据库模型,支持表、行和列的概念,以及 SQL 查询语言。
性能和用途限制:
总的来说,MMDB 和 SQLite 面向不同的应用场景。MMDB 更适合处理与地理位置信息相关的数据,而 SQLite 是一种通用的关系型数据库引擎,适用于各种数据存储和检索需求。
func downloadMMDB(path string) (err error) {
resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz")
if err != nil {
return
}
defer resp.Body.Close()
gr, err := gzip.NewReader(resp.Body)
if err != nil {
return
}
defer gr.Close()
tr := tar.NewReader(gr)
for {
h, err := tr.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") {
continue
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, tr)
if err != nil {
return err
}
}
return nil
}
var (
HomeDir string
ConfigPath string
MMDBPath string
)
func init() {
currentUser, err := user.Current()
if err != nil {
dir := os.Getenv("HOME")
if dir == "" {
log.Fatalf("Can't get current user: %s", err.Error())
}
HomeDir = dir
} else {
HomeDir = currentUser.HomeDir
}
dirPath := path.Join(HomeDir, ".config", Name)
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
if err := os.MkdirAll(dirPath, 0777); err != nil {
log.Fatalf("Can't create config directory %s: %s", dirPath, err.Error())
}
}
ConfigPath = path.Join(dirPath, "config.ini")
if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
log.Info("Can't find config, create a empty file")
os.OpenFile(ConfigPath, os.O_CREATE|os.O_WRONLY, 0644)
}
MMDBPath = path.Join(dirPath, "Country.mmdb")
if _, err := os.Stat(MMDBPath); os.IsNotExist(err) {
log.Info("Can't find MMDB, start download")
err := downloadMMDB(MMDBPath)
if err != nil {
log.Fatalf("Can't download MMDB: %s", err.Error())
}
}
}
func downloadMMDB(path string) (err error) {
resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz")
if err != nil {
return
}
defer resp.Body.Close()
gr, err := gzip.NewReader(resp.Body)
if err != nil {
return
}
defer gr.Close()
tr := tar.NewReader(gr)
for {
h, err := tr.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") {
continue
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, tr)
if err != nil {
return err
}
}
return nil
}