Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

因为买了两个NX30Pro,主要是奔着可以刷Openwrt,然后把两个都刷成了ImmortalWrt,结果发现要作为有线中继的话设置还有点麻烦,所以记录下
首先要设置个与主路由相同网段的静态地址,方便管理
在网络-LAN-基本设置-协议设置成“静态地址”
然后IPv4地址就是刚才说的静态地址,可以设个好记的
子网掩码就默认的,255.255.255.0,
IPv4网关就是主路由地址,DNS也设置成主路由地址
基础设置改成忽略此接口-不提供DHCP服务,由主路由提供
然后在高级设置中将IPv6的 路由通告服务和DHCPv6服务跟NDP代理禁用掉,
物理设置中勾选桥接接口
目前我是这样设置可以作为有线中继连通网络了,后续有变化继续更新

在学习向量数据库的时候,发现检索的算法 HNSW 是个比较重要且核心的概念,直接学习(面向小白)可能有一定的门槛,所以我们先通过常见算法的跳表来做个引入
跳表的前置基础就是最常见的序列结构之一的链表,链表的特点是添加插入元素效率非常高,查询检索则需要线性复杂度,比如Java中的hashmap,在jdk1.8以后就把
单纯链表的结构改成了链表和红黑树结合的方式,因为当链表比较长的时候,线性时间复杂度也是个比较慢的方法相比对数时间复杂度,而对于链表,也有直接在它基础
优化而来的跳表结构,跳表是在链表的基础上引入了分层的概念,通过投硬币概率来决定是否生成新层,先用比较通俗的话来讲一下我的理解
因为链表查找只能从前往后一个一个找(单向链表),那么有没有办法可以跳过一些,加速我的查找,那么引入了分层的结构,在更上层以更稀疏的数据链表来索引数据,
举个例子,我原来是一个

1
1 --> 2 --> 3 --> 4 --> 5 --> 6 --> 7 --> 8 --> 9 

这样的链表,我要查找8个元素是否存在,那我需要查找八次才能找到
如果我把结构改成

1
2
3
1 --------------> 4 --------------> 7 ---------> null 
| | |
1 --> 2 --> 3 --> 4 --> 5 --> 6 --> 7 --> 8 --> 9 --> null

变成这样的结构,我现在第一层找最后一个比8小的元素,就是7,然后再到下一层查找,这样我的查找路径就是 1 --> 4 --> 7 --> 8 速度加快了一倍
直观来说这样是能够提升很多查询效率,但是具体怎么实现我们也来看一下
首先定义个简单的Node

1
2
3
4
5
6
7
8
9
public class Node {
public int data = -1;

public Node[] forwards;

public Node(int level) {
forwards = new Node[level];
}
}

接下去是SkipList的主体结构

1
2
3
4
5
6
7
8
public class SkipList {
private static final int MAX_LEVEL = 16;

private int currentLevel = 0;

private Node head = new Node(MAX_LEVEL);

private Random random = new Random();

包含最大层数,当前层数,头结点,跟随机值
生成随机层数参考了redis的zset实现方法

1
2
3
4
5
6
7
private int generateRandomLevel() {
// 参考redis sorted set 实现
int level = 1;
while ((random.nextInt() & 0xFFFF) < (0.25 * 0xFFFF))
level += 1;
return Math.min(level, MAX_LEVEL);
}

接下去先看下

插入元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void insert(int value) {
int level = generateRandomLevel();
if (level >= currentLevel) {
currentLevel = level;
}

Node newNode = new Node(level);
newNode.setData(value);
Node[] update = new Node[level];

Arrays.fill(update, head);

Node p = head;

for (int i = level - 1; i >= 0; --i) {
// 找到应该处在的位置,把前一元素记录为待更新
while (p.forwards[i] != null && p.forwards[i].data < value) {
p = p.forwards[i];
}
// 对应这一层,更新新的位置p
update[i] = p;
}

// 每一层都处理前后链接
for (int i = 0; i < level; i++) {
newNode.forwards[i] = update[i].forwards[i];
update[i].forwards[i] = newNode;
}
}

看下图片演示,类似于链表的插入,但是需要处理每一层的前后链接

查找元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Node search(int value) {
Node p = head;
// 逐层找到比目标元素小的最后一个元素
// 然后再往下一层找
for (int i = currentLevel - 1; i >= 0 ; i--) {
while (p.forwards[i] != null && p.forwards[i].data < value) {
p = p.forwards[i];
}
}
// 此时如果前一元素就是目标元素就返回该元素
if (p.forwards[0] != null && p.forwards[0].data == value) {
return p.forwards[0];
} else {
return null;
}
}

也可以看下图里的演示,按红色的线寻找元素

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void delete(int value) {
Node[] update = new Node[currentLevel];
Node p = head;
// 找到待删除元素的前一元素
for (int i = currentLevel - 1; i >= 0; i--) {
while (p.forwards[i] != null && p.forwards[i].data < value) {
p = p.forwards[i];
}
update[i] = p;
}
// 判断如果该层有这个元素就更新链接
if (p.forwards[0] != null && p.forwards[0].data == value) {
for (int i = currentLevel - 1; i >= 0; i--) {
if (update[i].forwards[i] != null && update[i].forwards[i].data == value) {
update[i].forwards[i] = update[i].forwards[i].forwards[i];
}
}
}
}

删除操作就类似于插入操作。了解了跳表原理以后其实HNSW就是个在空间层面的跳表

将一个小点,在局域网中包括类似于homelab的或者仅仅只是搭一个用来当实验环境的,在局域网内机器比较少的时候大部分DHCP会给分配相同的ip,但是这不是一定的,当机器比较多,并且上下线比较频繁时就会出现ip地址变动,这对于一些使用场景比较讨厌,所以我想把ip固定下来,先简单引用下DHCP的原理,他分为下面几个阶段

DHCP发现(DISCOVER)

client在物理子网上发送广播来寻找可用的服务器。网络管理员可以配置一个本地路由来转发DHCP包给另一个子网上的DHCP服务器。该client实现生成一个目的地址为255.255.255.255或者一个子网广播地址的UDP包。

客户也可以申请它使用的最后一个IP地址(在下面的例子里为192.168.1.100)。如果该客户所在的网络中此IP仍然可用,服务器就可以准许该申请。否则,就要看该服务器是授权的还是非授权的。授权服务器会拒绝请求,使得客户立刻申请一个新的IP。非授权服务器仅仅忽略掉请求,导致一个客户端请求的超时,于是客户端就会放弃此请求而去申请一个新的IP地址。

DHCP提供(OFFER)

当DHCP服务器收到一个来自客户的IP租约请求时,它会提供一个IP租约。DHCP为客户保留一个IP地址,然后通过网络单播一个DHCPOFFER消息给客户。该消息包含客户的MAC地址、服务器提供的IP地址、子网掩码、租期以及提供IP的DHCP服务器的IP。

服务器基于在CHADDR字段指定的客户硬件地址来检查配置。这里的服务器,192.168.1.1,将IP地址指定于YIADDR字段。

DHCP请求(REQUEST)

当客户PC收到一个IP租约提供时,它必须告诉所有其他的DHCP服务器它已经接受了一个租约提供。因此,该客户会发送一个DHCPREQUEST消息,其中包含提供租约的服务器的IP。当其他DHCP服务器收到了该消息后,它们会收回所有可能已提供给该客户的租约。然后它们把曾经给该客户保留的那个地址重新放回到可用地址池中,这样,它们就可以为其他计算机分配这个地址。任意数量的DHCP服务器都可以响应同一个IP租约请求,但是每一个客户网卡只能接受一个租约提供。

DHCP确认(Acknowledge,ACK)

当DHCP服务器收到来自客户的REQUEST消息后,它就开始了配置过程的最后阶段。这个响应阶段包括发送一个DHCPACK包给客户。这个包包含租期和客户可能请求的其他所有配置信息。这时候,TCP/IP配置过程就完成了。

该服务器响应请求并发送响应给客户。整个系统期望客户来根据选项来配置其网卡。

DHCP释放(RELEASE)

客户端向DHCP服务器发送一个请求以释放DHCP资源,并注销其IP地址。鉴于客户端更多的时候并不清楚何时用户会将其从网络中移除,此协议不会托管“DHCP释放的发送”。

DHCP NAK

服务器回复客户,客户要求的IP不能被分配。

Ubuntu

而对于Ubuntu,现在较新版本的设置方式也有点变化,比如我用的是22.04

1
cd /etc/netplan/

先去这个目录下查看配置文件
我们要找的是这个 00-installer-config.yaml 文件

原本的是长这样

默认是通过dhcp分配ip
我们要改成静态ip

1
2
3
4
5
6
7
8
9
10
11
12
network:
renderer: networkd
ethernets:
ens33:
addresses:
- 192.168.1.xx/24
nameservers:
addresses: [114.114.114.114, 233.5.5.5]
routes:
- to: default
via: 192.168.1.1
version: 2

解释一下 ethernets中的ens33表示我们的网卡接口的标识
可以通过 ip addr 查看

第一个是本地回环,第二个可以看下是否跟内网ip对应上,如果是的话就这个ens33
然后就是addresses填的地址,就是我们想要固定的ip地址,记得得是自己局域网网段的
上面只是个示例,然后是nameservers里的是dns服务器,也是按需填写
这里的114.114.114.114跟233.5.5.5分别是电信跟阿里云的,仅供参考
然后是routes就是网关地址,按自己的环境填写,网络没有任何特殊处理的一般就是路由器
接下去就是应用这个配置

1
sudo netplan apply

这样ip配置就生效了
可以通过
ip addr show ens33查看

之前自定义部署的derper在Mac端和Windows使用时没啥问题,但是在我想作为网关的Armbian小机器上一直会报 “x509: certificate signed by unknown authority”
错误,原因在于之前在通过acme生成证书以后我是直接把cer证书重命名成crt就映射到derper的docker目录里,对于Mac和Windows客户端应该是有兼容逻辑,而实际使用需要完整的证书去识别认证机构,否则就会被认为是自定义证书,在一些常规的证书认证逻辑里就会报上面的问题
这边需要先把生成的 fullchain.cer 完整证书转成crt格式的

1
openssl x509 -inform PEM -in fullchain.cer -out derper.demo.com.crt

这边是PEM格式的证书,如果是DER的话就改一下

1
openssl x509 -inform DER -in certificate.cer -out certificate.crt

转换完成后就重新启动下docker
这里的原因主要是fullchain.cer是包含了完整的证书链,而ca.cer只是包含用户证书,所以需要替换一下,这算是个小问题

上次分享了rustdesk如何私有化部署,但是在实际使用中存在一个小问题,就是如果被连接端是mac的话,在mac进入超时自动锁定或者被主动锁定之后,连接就会进入一个“已连接,等待画面传输”的状态,但实际后续并不会传输画面,而是继续卡在这个无法实际连接的状态

这个问题目前最新版的rustdesk还没有很好的解决,不过可以通过一个小技巧来解决,只是会有一个限制
就是需要能够使用vnc连接这个被连接端的mac或者ssh连接
第一种是如果能用vnc连接的话,就先连接,然后稍微动下鼠标,然后rustdesk就能获取画面进行下一步操作
第二种是通过ssh连接到mac以后,通过一个mac下的小命令 caffeinate 这个命令是mac自带的,可以防止mac
进入睡眠状态,具体命令如下

1
caffeinate -u -t 2

-u: 表示用户处于活动状态,此参数将打开屏幕并禁止屏幕进入空闲休眠。

-d:禁止屏幕休眠。

-i:禁止系统空闲休眠。

-m:禁止硬盘进入休眠。

-s:禁止系统进入睡眠状态,此参数仅在插上电源的时候才有效。

-t:指定命令有效的超时值,以秒为单位。
这样我们就能从rustdesk获得画面进行操作了,希望后面rustdesk能解决掉这个问题,因为这两种方式也不是很完善,如果只有纯远程的话就没办法了

0%