摘要内容……

阅读全文 »
l

一个函数秒杀 2Sum 3Sum 4Sum 问题

经常刷 LeetCode 的读者肯定知道鼎鼎有名的 twoSum 问题,我们的旧文 Two Sum 问题的核心思想twoSum 的几个变种做了解析。

但是除了 twoSum 问题,LeetCode 上面还有 3Sum4Sum 问题,我估计以后出个 5Sum6Sum 也不是不可能。

那么,对于这种问题有没有什么好办法用套路解决呢?本文就由浅入深,层层推进,用一个函数来解决所有 nSum 类型的问题。

一、twoSum 问题

[leetcode1](1. 两数之和 - 力扣(LeetCode) (leetcode-cn.com))

力扣上的 twoSum 问题,题目要求返回的是索引,这里我来编一道 twoSum 题目,不要返回索引,返回元素的值:

如果假设输入一个数组 nums 和一个目标和 target请你返回 nums 中能够凑出 target 的两个元素的值,比如输入 nums = [5,3,1,6], target = 9,那么算法返回两个元素 [3,6]。可以假设只有且仅有一对儿元素可以凑出 target

我们可以先对 nums 排序,然后利用前文「双指针技巧汇总」写过的左右双指针技巧,从两端相向而行就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<int> twoSum(vector<int>& nums, int target) {
// 先对数组排序
sort(nums.begin(), nums.end());
// 左右指针
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// 根据 sum 和 target 的比较,移动左右指针
if (sum < target) {
lo++;
} else if (sum > target) {
hi--;
} else if (sum == target) {
return {nums[lo], nums[hi]};
}
}
return {};
}

这样就可以解决这个问题,不过我们要继续魔改题目,把这个题目变得更泛化,更困难一点:

nums 中可能有多对儿元素之和都等于 target,请你的算法返回所有和为 target 的元素对儿,其中不能出现重复

函数签名如下:

1
vector<vector<int>> twoSumTarget(vector<int>& nums, int target);

比如说输入为 nums = [1,3,1,2,2,3], target = 4,那么算法返回的结果就是:[[1,3],[2,2]]

对于修改后的问题,关键难点是现在可能有多个和为 target 的数对儿,还不能重复,比如上述例子中 [1,3][3,1] 就算重复,只能算一次。

首先,基本思路肯定还是排序加双指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<vector<int>> twoSumTarget(vector<int>& nums, int target {
// 先对数组排序
sort(nums.begin(), nums.end());
vector<vector<int>> res;
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// 根据 sum 和 target 的比较,移动左右指针
if (sum < target) lo++;
else if (sum > target) hi--;
else {
res.push_back({lo, hi});
lo++; hi--;
}
}
return res;
}

但是,这样实现会造成重复的结果,比如说 nums = [1,1,1,2,2,3,3], target = 4,得到的结果中 [1,3] 肯定会重复。

出问题的地方在于 sum == target 条件的 if 分支,当给 res 加入一次结果后,lohi 不应该改变 1 的同时,还应该跳过所有重复的元素:

图片

1
2
3
4
5
6
7
8
9
10
11
12
13
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// 记录索引 lo 和 hi 最初对应的值
int left = nums[lo], right = nums[hi];
if (sum < target) lo++;
else if (sum > target) hi--;
else {
res.push_back({left, right});
// 跳过所有重复的元素
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}

这样就可以保证一个答案只被添加一次,重复的结果都会被跳过,可以得到正确的答案。不过,受这个思路的启发,其实前两个 if 分支也是可以做一点效率优化,跳过相同的元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vector<vector<int>> twoSumTarget(vector<int>& nums, int target) {
// nums 数组必须有序
sort(nums.begin(), nums.end());
int lo = 0, hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target) {
while (lo < hi && nums[lo] == left) lo++;
} else if (sum > target) {
while (lo < hi && nums[hi] == right) hi--;
} else {
res.push_back({left, right});
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}
return res;
}

这样,一个通用化的 twoSum 函数就写出来了,请确保你理解了该算法的逻辑,我们后面解决 3Sum4Sum 的时候会复用这个函数。

这个函数的时间复杂度非常容易看出来,双指针操作的部分虽然有那么多 while 循环,但是时间复杂度还是 O(N),而排序的时间复杂度是 O(NlogN),所以这个函数的时间复杂度是 O(NlogN)

二、3Sum 问题

Leetcode15

这是力扣第 15 题「三数之和」:

图片

题目就是让我们找 nums 中和为 0 的三个元素,返回所有可能的三元组(triple),函数签名如下:

1
vector<vector<int>> threeSum(vector<int>& nums);

这样,我们再泛化一下题目,不要光和为 0 的三元组了,计算和为 target 的三元组吧,同上面的 twoSum 一样,也不允许重复的结果:

1
2
3
4
5
6
7
8
vector<vector<int>> threeSum(vector<int>& nums) {
// 求和为 0 的三元组
return threeSumTarget(nums, 0);
}

vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
// 输入数组 nums,返回所有和为 target 的三元组
}

这个问题怎么解决呢?很简单,穷举呗。现在我们想找和为 target 的三个数字,那么对于第一个数字,可能是什么?nums 中的每一个元素 nums[i] 都有可能!

那么,确定了第一个数字之后,剩下的两个数字可以是什么呢?其实就是和为 target - nums[i] 的两个数字呗,那不就是 twoSum 函数解决的问题么🤔

可以直接写代码了,需要把 twoSum 函数稍作修改即可复用:

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
30
31
32
33
34
/* 从 nums[start] 开始,计算有序数组
* nums 中所有和为 target 的二元组 */
vector<vector<int>> twoSumTarget(
vector<int>& nums, int start, int target) {
// 左指针改为从 start 开始,其他不变
int lo = start, hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi) {
...
}
return res;
}

/* 计算数组 nums 中所有和为 target 的三元组 */
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
// 数组得排个序
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
// 穷举 threeSum 的第一个数
for (int i = 0; i < n; i++) {
// 对 target - nums[i] 计算 twoSum
vector<vector<int>>
tuples = twoSumTarget(nums, i + 1, target - nums[i]);
// 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
for (vector<int>& tuple : tuples) {
tuple.push_back(nums[i]);
res.push_back(tuple);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}

需要注意的是,类似 twoSum3Sum 的结果也可能重复,比如输入是 nums = [1,1,1,2,3], target = 6,结果就会重复。

关键点在于,不能让第一个数重复,至于后面的两个数,我们复用的 twoSum 函数会保证它们不重复。所以代码中必须用一个 while 循环来保证 3Sum 中第一个元素不重复。

至此,3Sum 问题就解决了,时间复杂度不难算,排序的复杂度为 O(NlogN)twoSumTarget 函数中的双指针操作为 O(N)threeSumTarget 函数在 for 循环中调用 twoSumTarget 所以总的时间复杂度就是 O(NlogN + N^2) = O(N^2)

三、4Sum 问题

Leetcode18

这是力扣第 18 题「四数之和」:

图片

函数签名如下:

1
vector<vector<int>> fourSum(vector<int>& nums, int target);

都到这份上了,4Sum 完全就可以用相同的思路:穷举第一个数字,然后调用 3Sum 函数计算剩下三个数,最后组合出和为 target 的四元组。

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
30
31
32
vector<vector<int>> fourSum(vector<int>& nums, int target) {
// 数组需要排序
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
// 穷举 fourSum 的第一个数
for (int i = 0; i < n; i++) {
// 对 target - nums[i] 计算 threeSum
vector<vector<int>>
triples = threeSumTarget(nums, i + 1, target - nums[i]);
// 如果存在满足条件的三元组,再加上 nums[i] 就是结果四元组
for (vector<int>& triple : triples) {
triple.push_back(nums[i]);
res.push_back(triple);
}
// fourSum 的第一个数不能重复
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}

/* 从 nums[start] 开始,计算有序数组
* nums 中所有和为 target 的三元组 */
vector<vector<int>>
threeSumTarget(vector<int>& nums, int start, int target) {
int n = nums.size();
vector<vector<int>> res;
// i 从 start 开始穷举,其他都不变
for (int i = start; i < n; i++) {
...
}
return res;

这样,按照相同的套路,4Sum 问题就解决了,时间复杂度的分析和之前类似,for 循环中调用了 threeSumTarget 函数,所以总的时间复杂度就是 O(N^3)

四、100Sum 问题?

在 LeetCode 上,4Sum 就到头了,但是回想刚才写 3Sum4Sum 的过程,实际上是遵循相同的模式的。我相信你只要稍微修改一下 4Sum 的函数就可以复用并解决 5Sum 问题,然后解决 6Sum 问题……

那么,如果我让你求 100Sum 问题,怎么办呢?其实我们可以观察上面这些解法,统一出一个 nSum 函数:

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
30
31
32
33
34
35
36
37
38
39
40
/* 注意:调用这个函数之前一定要先给 nums 排序 */
vector<vector<int>> nSumTarget(
vector<int>& nums, int n, int start, int target) {

int sz = nums.size();
vector<vector<int>> res;
// 至少是 2Sum,且数组大小不应该小于 n
if (n < 2 || sz < n) return res;
// 2Sum 是 base case
if (n == 2) {
// 双指针那一套操作
int lo = start, hi = sz - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target) {
while (lo < hi && nums[lo] == left) lo++;
} else if (sum > target) {
while (lo < hi && nums[hi] == right) hi--;
} else {
res.push_back({left, right});
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}
} else {
// n > 2 时,递归计算 (n-1)Sum 的结果
for (int i = start; i < sz; i++) {
vector<vector<int>>
sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
for (vector<int>& arr : sub) {
// (n-1)Sum 加上 nums[i] 就是 nSum
arr.push_back(nums[i]);
res.push_back(arr);
}
while (i < sz - 1 && nums[i] == nums[i + 1]) i++;
}
}
return res;
}

嗯,看起来很长,实际上就是把之前的题目解法合并起来了,n == 2 时是 twoSum 的双指针解法,n > 2 时就是穷举第一个数字,然后递归调用计算 (n-1)Sum,组装答案。

需要注意的是,调用这个 nSum 函数之前一定要先给 nums 数组排序,因为 nSum 是一个递归函数,如果在 nSum 函数里调用排序函数,那么每次递归都会进行没有必要的排序,效率会非常低。

比如说现在我们写 LeetCode 上的 4Sum 问题:

1
2
3
4
5
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
// n 为 4,从 nums[0] 开始计算和为 target 的四元组
return nSumTarget(nums, 4, 0, target);
}

再比如 LeetCode 的 3Sum 问题,找 target == 0 的三元组:

1
2
3
4
5
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
// n 为 3,从 nums[0] 开始计算和为 0 的三元组
return nSumTarget(nums, 3, 0, 0);
}

那么,如果让你计算 100Sum 问题,直接调用这个函数就完事儿了。

l

互联网是如何运作的:图解步骤详解

当你阅读这篇文章时,你正在见证历史。过去五十年间,科技和互联网的功能发生了翻天覆地的变化,如今已成为我们日常生活中便捷的系统。

但正如你可能已经猜到的那样,互联网并非一直都是现在这个样子,也并非一直如此流行。事实上,在2000年,只有52%的美国成年人表示他们使用互联网;但到了2018年,这个数字跃升至82%[1]。

从最初让你来到这里的问题:“互联网是如何运作的?”到网上购物、与家人朋友交流,互联网彻底改变了我们的生活、协作和学习方式。但这一切是如何开始的?互联网又是如何演变成我们今天所熟知的无处不在的系统呢?

互联网在我们的日常生活中扮演着重要的角色。

  • 2000 年,只有 52% 的美国成年人使用互联网 [1]
  • 2018年,这一数字跃升至89%。
  • 2013年,美国成年人中,只有8%的人在家不使用宽带互联网,但拥有智能手机。
  • 2018年,这一数字上升至20%。

互联网如何运作信息图

要充分了解互联网的运作方式以及我们是如何走到今天的,我们需要从头开始。

互联网简史

1969 年 10 月 29 日,一个名为 ARPANET(高级研究计划署)的组织启动了互联网的第一个版本(也称为 ARPANET),连接了犹他大学、加州大学圣巴巴拉分校、加州大学洛杉矶分校和斯坦福研究所的四台主要计算机 [2]。

当这个计算机网络连接起来后,大学就可以从一个组织到另一个组织以及在内部访问文件和传输信息。

随着研究人员不断完善系统,他们持续连接其他大学的计算机,包括麻省理工学院、哈佛大学和卡内基梅隆大学。最终,ARPANET 更名为“互联网”。

在这个阶段,谁使用了互联网?

互联网早期只有计算机专家、科学家、工程师和图书管理员使用,他们必须学习复杂的系统才能使用它,但随着技术的进步和消费者的适应,它成为了世界各地人们必不可少的工具。

互联网的功能是如何以及何时发生变化的?

20世纪70年代是互联网发展的重要转型时期。电子邮件于1972年问世,全国各地的图书馆实现了互联互通,最重要的是,由于传输控制协议和互联网协议(TCP/IP)架构的出现,信息交换变得更加顺畅。

这些协议的发明有助于规范通过网络发送和接收信息的方式,使信息传输更加一致,无论你在哪里或以何种方式访问互联网。

互联网何时变得用户友好?

1986年,美国国家科学基金会通过资助NSFNET(一个遍布全国的超级计算机网络),将互联网的发展推向了新的高度。

这些超级计算机为个人计算奠定了基础,弥合了仅用于学术目的的计算机和用于执行日常任务的计算机之间的差距。

1991年,明尼苏达大学开发了第一个用户友好的互联网界面,使用户能够更便捷地访问校园文件和信息。内华达大学里诺分校在此基础上继续完善这一易用界面,并引入了搜索功能和索引功能。

消费者是什么时候开始使用互联网的?

随着互联网的发展不断演变和重点转移,美国国家科学基金会于 1995 年 5 月停止了对互联网骨干网 (NSFNET) 的赞助。

这一变革取消了互联网商业用途的所有限制,并最终促成了互联网的多元化发展和快速增长。此后不久,AOL、CompuServe 和 Prodigy 也加入了 Delphi 的行列,开始向消费者提供商业互联网服务。

上世纪九十年代末, WiFi和 Windows 98的问世标志着科技行业致力于发展互联网的商业化。这一举措使微软等公司得以接触到新的受众群体——消费者(比如您)。

如今互联网的使用情况如何?

快进到今天。据估计,现在有 30 亿人使用互联网,其中许多人每天都使用互联网来帮助他们从 A 点到达 B 点、与亲人联系、在工作中协作,或者了解更多重要问题,例如互联网是如何运作的?[3]

随着科技的进步和互联网渗透到我们生活的方方面面,预计会有更多人使用互联网。研究人员预测,到 2030 年,互联网用户将达到 75 亿,连接到互联网的设备将达到 5000 亿台 [4]。

互联网是如何运作的?

现在你已经对互联网的发展历程有了一些了解,让我们来探讨一下当前的问题:“互联网是如何运作的?”

互联网是一个全球性的计算机网络,它通过互连的设备传输各种数据和媒体。它的工作原理是使用数据包路由网络,该网络遵循互联网协议 (IP) 和传输控制协议 (TCP) [5]。

TCP 和 IP 协同工作,确保无论你使用什么设备或在哪里使用,互联网上的数据传输都是一致且可靠的。

数据通过互联网传输时,是以消息和数据包的形式进行的。通过互联网发送的数据称为消息,但在发送消息之前,消息会被分解成更小的部分,称为数据包。

这些消息和数据包使用互联网协议 (IP) 和传输控制协议 (TCP) 从一台计算机传输到另一台计算机。IP 是一套规则系统,用于管理信息如何通过互联网连接从一台计算机发送到另一台计算机。

通过数字地址(IP 地址),IP 系统接收有关如何传输数据的进一步指令。

传输控制协议 (TCP) 与 IP 协同工作,确保数据传输的可靠性和稳定性。这有助于防止数据包丢失,确保数据包按正确顺序重组,并避免因延迟而对数据质量产生负面影响。

想知道从浏览器启动到搜索结果,互联网是如何运作的吗?让我们一步一步地了解这个过程[7][8][9]。

当你在浏览器中输入网址时……

步骤 1:您的电脑或设备通过调制解调器或路由器连接到互联网。这些设备共同使您能够连接到全球其他网络 [6]。

您的路由器使多台计算机能够加入同一网络,而调制解调器则连接到您的 ISP(互联网服务提供商),ISP 为您提供有线或 DSL 互联网服务。

步骤二:输入网址,也称为URL(统一资源定位符)。每个网站都有其唯一的URL,它会告诉你的网络服务提供商(ISP)你想访问哪个网站。

步骤 3:您的查询被推送至您的 ISP,ISP 连接到多个服务器,这些服务器存储和发送数据,例如 NAP 服务器(网络访问保护)和 DNS(域名服务器)。

接下来,你的浏览器会通过 DNS 查找你在搜索引擎中输入的域名对应的 IP 地址。DNS 会将你在浏览器中输入的文本域名转换成数字 IP 地址。

  • 例如:Google.com 变为 64.233.191.255

步骤 4:您的浏览器向目标服务器发送超文本传输协议 (HTTP) 请求,以使用 TCP/IP 将网站的副本发送到客户端。

第五步:服务器批准请求后,会向您的计算机发送“200 OK”消息。然后,服务器以数据包的形式将网站文件发送到浏览器。

步骤 6:当浏览器重新组装数据包时,网站加载完毕,您可以学习、购物、浏览和互动。

第七步:尽情享受搜索结果吧!

互联网的未来

无论您是在查找有关互联网如何运作的信息、在线观看您最喜欢的电影,还是在网上浏览旅游优惠,不可否认的是,互联网带我们去了很多地方,而且它还将继续这样做!

虽然现在互联网似乎没有发生什么变化,但很有可能,当我们回顾过去,看到我们走了多远,我们使用这项技术的方式发生了多大的变化,最终,我们会发现,我们自己也是互联网历史的一部分。

[1] 皮尤互联网研究中心;互联网/宽带概况介绍

[2] 沃尔特·豪;《互联网简史》

[3] 资金;以下是互联网用户数量

[4] 思科;物联网

[5] Lifewire;TCP(传输控制协议)详解

[6] Mozilla;网络如何运作

[7] 事物是如何运作的;互联网是如何运作的?

[8] 如何成为极客;互联网是如何运作的?

[9] Medium,《互联网是如何运作的》

互联网简史

几个世纪以来,哲学家和作家们一直在构想一个共享的世界知识库。我们今天所知的互联网是如何形成的?

重大突破[2]

  • 1969 年 10 月 29 日: ARPANET(后来更名为互联网)成功建立了加州大学洛杉矶分校和斯坦福研究所之间的连接。
  • 20 世纪 60 年代末:图书馆实现了目录的自动化和联网,不再依赖于 ARPANET。
  • 20世纪70年代:传输控制协议和互联网协议(TCP/IP)的建立,推动了互联网技术的成熟。这些协议的发明有助于规范信息在网络上的发送和接收方式。
  • 1986年:美国国家科学基金会资助了NSFNET,它是互联网56Kbps的主干网。当时由于联邦资金用于运营和维护,因此存在一些商业限制。
  • 1991年:创建了用户友好的互联网界面。
  • 1992 年 7 月: Delphi 成为第一家提供互联网接入的全国性商业在线服务商。
  • 1995年5月:互联网上所有商业用途的限制都被取消。这使得互联网得以多元化发展并迅速壮大。
  • 1997年: WiFi发明。
  • 1998年: Windows 98上市。
  • 2007年:智能手机广泛普及。
  • 2009年: 4G网络推出。
  • 如今:全球有30亿人使用互联网。[3]
  • 到 2030 年:预计互联网用户将达到 75 亿,连接到互联网的设备将达到 5000 亿台。[4]

互联网是如何运作的?

互联网是一个全球性的计算机网络,它通过互联设备传输各种数据和媒体。它的工作原理是利用数据包路由网络,该网络遵循互联网协议(IP)和传输控制协议(TCP)。[5]

消息 + 数据包

  • 通过互联网发送的数据称为消息。
  • 信息在发送之前会被分割成称为数据包的小部分。

互联网协议(IP)

  • 管理信息如何通过互联网连接从一台计算机发送到另一台计算机的规则
  • 规定了计算机应如何通过发送附带数字地址(IP 地址)的数据来向其他计算机发送信息。
    • 公共 IP 地址:可通过互联网访问
    • 私有IP地址:分配给封闭网络(例如家庭网络或企业网络)中的设备,该网络无法通过互联网访问。

传输控制协议(TCP)

  • 与 IP 协同工作,确保数据传输的可靠性和稳定性。
  • 无丢包,无延迟影响数据质量,数据包按正确顺序重新组装。

上网时会发生什么……

步骤 1:您的电脑或设备通过调制解调器或路由器连接到互联网,从而可以连接到全球其他网络。[6]

路由器允许多台计算机加入同一网络,而调制解调器连接到您的互联网服务提供商 (ISP),ISP 提供有线或 DSL 互联网。

你的个人电脑被称为客户端,而不是服务器。

  • 客户端计算机通过互联网服务提供商 (ISP) 连接到互联网。
    • 例如:您的手机连接到移动网络或您的笔记本电脑连接到 WiFi。
  • 服务器是直接连接到互联网的计算机。
    • 例如:存储网页、网站或应用程序的计算机。

步骤二:输入网址,也称为URL。URL是统一资源定位符的缩写。

步骤 3:您的查询将被处理并推送至您的 ISP。您的 ISP 拥有多个服务器,用于存储和发送数据,例如 NAP 服务器(网络访问保护)和 DNS(域名服务器)。

您的浏览器会通过 DNS 查找您在浏览器中输入的域名对应的 IP 地址。

  • DNS 将您在浏览器中输入的基于文本的域名转换为基于数字的 IP 地址。
    • 例如: Google.com 变为 64.233.191.255

步骤 4:浏览器向目标服务器发送超文本传输协议 (HTTP) 请求,以使用 TCP/IP 将网站的副本发送给客户端。

  • HTTP:用于互联网通信的语言。
  • HTTPS: HTTP 的安全版本,您的浏览器和网站之间的所有通信都经过加密。

步骤五:服务器批准请求并向客户端计算机发送“200 OK”消息。然后,服务器以数据包的形式将网页文件发送到浏览器。

步骤 6:浏览器重新组装数据包,网页加载。

第七步:尽情上网吧![7][8][9]

l

在本节中,我们将通过一个完整的网络流量示例,逐步演示计算机在通过计算机网络进行通信时将经历哪些步骤。本示例将整合其他章节中的大量信息,是一个全面而完整的示例。

大多数人都会惊讶地发现,计算机发送一个简单的数据包竟然需要这么多步骤,而且其中有很多步骤对于普通用户来说是完全隐藏的,除非你事先知道,否则你永远不会知道。

如果您已经浏览过本网站的大部分内容,那么您可能已经对本教程可能包含的步骤数量有所了解。

下面的示例展示了一个小型家庭网络,其中一台计算机刚刚启动。该计算机已手动配置了 IP 地址,但尚未与网络通信。用户坐在计算机旁,打开 Web 浏览器并尝试访问 www.iis.se。

首先,我们来看一下整体情况。这张图展示了示例中的网络拓扑结构。在接下来的很多步骤中,我们会放大查看网络中最相关的部分,以避免每次都绘制完整的网络拓扑图。

网络拓扑示例

现在让我们从交通示例讲解开始!

步骤 1:计算机想要发送流量

一台连接到家庭网络的计算机刚刚启动。该计算机已手动配置了 IP 地址、子网掩码、DNS 服务器和默认网关。DNS 服务器和默认网关地址都指向家庭路由器的局域网 IP 地址。

计算机用户打开网页浏览器并访问www.iis.se

首先,Web浏览器会指示计算机上的操作系统建立计算机与www.iis.se之间的通信。

网页浏览器指示操作系统建立会话

步骤 2:DNS

这部分又细分为许多子步骤。

步骤 2a:DNS 缓存

计算机操作系统会检查其 DNS 缓存,以确定是否已知道www.iis.se的 IP 地址。由于计算机刚刚启动,之前从未访问过www.iis.se,因此 DNS 缓存为空。

计算机检查其 DNS 缓存

现在计算机必须向其 DNS 服务器查询www.iis.se的 IP 地址。

步骤 2b:组合 DNS 查询

计算机将构建一个 DNS 查询,并将其发送到它配置使用的 DNS 服务器 192.168.1.1。

DNS 查询的目标地址是 192.168.1.1,源 IP 地址是计算机自身的 IP 地址,即 192.168.1.5。

DNS 使用 UDP 作为传输协议。DNS 查询的目标端口是 53/UDP。当 DNS 查询到达 DNS 服务器时,服务器可以通过查看目标端口 53/UDP 来判断该消息是发往 DNS 服务器程序的,并将消息转发给正在运行的 DNS 程序。

计算机操作系统还必须随机化一个源端口,该端口也会写入消息中。

计算机构建 DNS 查询

但是,当计算机将 DNS 查询组合起来时,它注意到必须检查应该将数据包发送到哪个目标 MAC 地址。

所以目前,操作系统会将数据包放入内存中的一个队列中,然后开始确定要使用的目标 MAC 地址。

步骤 2c:检查 ARP 表中是否存在有效的 MAC 地址

计算机现在将检查其 ARP 表,看看它是否知道与路由器 IP 地址 192.168.1.1 关联的 MAC 地址。

但由于计算机刚刚启动,尚未学习任何 ARP 条目,因此其 ARP 表完全为空。

计算机检查其ARP表

步骤 2d:向网络发送 ARP 请求

现在,计算机必须向网络中的其他设备构建一个 ARP 请求。该请求将被发送到目标 MAC 地址 FF:FF:FF:FF:FF:FF,即广播地址。结果是,局域网上的所有其他计算机和设备都将收到该请求并读取其内容。

计算机发送 ARP 查询

家用路由器收到ARP请求并读取消息,因为该请求发送到了广播MAC地址FF:FF:FF:FF:FF:FF。

家用路由器可以从消息中看到计算机正在请求 IP 地址为 192.168.1.1 的设备。由于路由器配置为使用该 IP 地址,因此家用路由器将通过构建 ARP 应答并将其发送回计算机来响应此消息。

步骤 2e:来自路由器的 ARP 回复

当计算机收到 ARP 回复时,操作系统会读取该回复。它会将回复添加到 ARP 表中,以便在几分钟内记住与 192.168.1.1 关联的 MAC 地址。

路由器对计算机的 ARP 回复

现在计算机终于收集到了发送 DNS 消息所需的所有信息。

步骤 2f:发送 DNS 查询

现在,计算机将向 DNS 服务器发送 DNS 查询,该服务器作为一项服务运行在家庭路由器 192.168.1.1 上。

家用路由器收到查询后,发现这是一个针对路由器自身 IP 地址和 MAC 地址的 DNS 查询,并意识到它必须处理这个 DNS 查询并发送一个答案。

PC 发送 DNS 查询

步骤 2g:家用路由器检查其 DNS 缓存

家用路由器本身就是一个DNS服务器,但它也依赖于互联网上的其他DNS服务器。家用路由器不可能知道互联网上每一个DNS地址。因此,它会根据需要向负责不同域名(例如example.com)的DNS服务器发出请求。

家用路由器也像电脑一样配备了DNS缓存。每次家用路由器处理来自电脑的DNS查询时,它都会将DNS回复保存在自身的DNS缓存中一段时间。这样做是为了避免重复处理相同的DNS查询,从而加快响应速度。

家用路由器会检查其 DNS 缓存

在这种情况下,家用路由器很久没有收到关于www.iis.se 的查询,因此路由器的 DNS 缓存中不存在该域名。所以,路由器必须向其配置的互联网 DNS 服务器请求解析此 DNS 查询。

步骤 2h:家用路由器准备并发送其 DNS 查询

现在,路由器准备向其 DNS 服务器发送 DNS 查询。路由器在首次启动并从互联网服务提供商 (ISP) 获取公网 IP 地址时,通过 DHCP 从 ISP 获知了可用的 DNS 服务器。

因此,家用路由器会准备一个DNS查询,方法是将查询信息封装在一个UDP消息中,目标端口为53/UDP,源端口为随机UDP端口。然后,它会将该消息封装到一个IP数据包中。该IP数据包会从家用路由器的公网IP地址发送到DNS服务器地址。

家用路由器发送 DNS 查询

当家用路由器准备好数据包并准备发送时,它会查看路由表以确定发送路径。路由表显示,到达内部局域网 192.168.1.0 的最佳路径是通过 LAN 端口,但此数据包需要发送到互联网上的另一个 IP 网络。因此,家用路由器选择 WAN 端口作为最佳目标地址。

在此阶段,家用路由器可能需要执行 ARP 请求来查找下一跳路由器 115.20.97.113 的 MAC 地址,但我们假设家用路由器已经在其 ARP 缓存中获得了此信息。

步骤 2i:DNS 查询通过互联网进行路由

这里的一些步骤已经过简化和缩短。

互联网上每个接收到 DNS 请求的路由器都会执行以下操作:

  • 收到包裹
  • 查看目标 IP 地址,以确定数据包的去向。
  • 它会查看路由表,以确定数据包的最佳路径。
  • 从数据包中移除旧的MAC 地址并添加新的 MAC 地址。它会使用自身出接口上的 MAC 地址作为流量的源 MAC 地址,并将下一跳路由器的 MAC 地址作为目标 MAC 地址。
  • 将数据包发送到下一跳路由器

来自家庭路由器的DNS查询通过互联网进行路由。

步骤 2j:DNS 服务器响应

最终,数据包到达 DNS 服务器,DNS 服务器将处理该数据包并准备响应。

就像普通电脑一样,服务器也有IP地址、子网掩码和默认网关。因此,它的工作方式与普通电脑非常相似。

DNS服务器向家庭路由器做出响应

步骤 2k:家用路由器可以向计算机发送 DNS 应答。

现在,在收到来自 DNS 服务器的 DNS 回复后,家用路由器终于可以创建 DNS 回复并将其发送到计算机,以便让计算机知道www.iis.se的 IP 地址。

家用路由器向计算机发送 DNS 应答

步骤 3:计算机建立与 www.iis.se 的会话

在这个阶段,很多事情同时发生。

计算机主要会启动一个名为“TCP 三次握手”的过程,这是 TCP 通信的建立阶段,包含计算机和服务器之间的三条消息。在使用 TCP 时(例如网页浏览),TCP 会尽力确保一切运行正常,包括通过握手建立会话。这样做是为了让服务器做好接收会话的准备,并确定通信应使用的端口。

TCP 三次握手由三条消息组成:

  • 第一个消息由计算机发送,名为“SYN”,代表同步(Synchronise)。它告知对方我们希望同步 TCP 会话的设置。该消息还包含计算机随机选择的 TCP 源端口。
  • 第二条消息是服务器的回复,称为“SYN-ACK”,即同步确认(Synchronise Acknowledgement)的缩写。这表示服务器确认已收到该消息,并已准备好建立通信会话。
  • 第三条消息由计算机发送,通过发送“ACK”(确认)来结束会话。这意味着一切准备就绪。

计算机的ARP缓存中已经存储了家庭路由器IP地址的正确ARP信息。因此,计算机可以通过家庭路由器向互联网发送任何数据包,而无需事先进行ARP查找。

但这也是本例中计算机首次尝试直接与路由器之外的物体通信。TCP 三次握手将在计算机和互联网上的 Web 服务器之间直接进行。

在之前的DNS查找过程中,计算机只是与家用路由器通信。家用路由器再与互联网上的DNS服务器通信。但计算机与互联网上的任何IP地址之间并没有直接通信。

区别在于,现在计算机想要直接与互联网上的某些内容通信,那么家用路由器就必须对流量执行地址转换。

步骤 3a:计算机发送 TCP SYN 消息

接下来,我们将仔细研究计算机通过初始化 TCP 三次握手来建立 TCP 会话时发生的情况。

为此,计算机操作系统会随机生成一个用于通信的 TCP 源端口。然后,它会组装 TCP SYN 消息并将其发送到 Web 服务器。该 TCP 消息不包含任何其他数据,它只是一个空的 TCP 消息。

当 TCP SYN 消息通过路由器时,路由器会对该消息执行 NAT 转换。路由器还会将执行的 NAT 转换信息保存在其 NAT 表中,以便跟踪会话并对任何回复执行反向 NAT 转换。

计算机向Web服务器发送TCP SYN消息

图片显示的是从计算机到Web服务器的TCP SYN数据包。

  • “我想与您同步一个 TCP 会话”

步骤 3b:Web 服务器回复 TCP SYN-ACK 包

这里可以看到 Web 服务器返回给计算机的 TCP SYN-ACK 响应:

  • “好的,我可以安排会话了,我确认已收到您的消息。”

计算机向 Web 服务器发送 TCP SYN-ACK 消息

该数据包与路由器上的 NAT 表条目匹配,以便路由器能够看到应该将数据包转发到哪个 LAN 计算机以及如何对数据包执行 NAT。

步骤 3c:计算机发送 TCP 确认信号

最后,计算机向Web服务器发送TCP ACK确认包。

  • “那么我确认,从现在开始,我们正式成立一个会议!”

计算机向Web服务器发送TCP ACK

只要计算机和服务器持续通信,它们就会一直使用同一个会话进行通信。这包括使用相同的TCP端口,这样沿途的所有设备都能跟踪会话、地址转换等信息。

会话可能只持续足够长的时间以下载网页,或者网络服务器和计算机可以选择保持会话更长时间,以防用户想要继续浏览网页。

步骤 4:网页浏览器与 Web 服务器通信

一旦操作系统建立了 TCP 会话,操作系统就会通知 Web 浏览器现在可以开始与 Web 服务器通信了。

网络浏览器会使用HTTP协议来实现这一点,HTTP协议是互联网上传输网页的标准协议。

计算机和Web服务器之间建立HTTP会话

这也意味着我们已经完成了示例,其中包括建立通信的大部分步骤。从现在开始,计算机和服务器可以相互通信以传输网页,直到传输完成。然后,它们可以选择发送所谓的 TCP RESET 消息来结束会话,该消息会通知所有设备会话已结束。

l

一文搞懂网络知识,IP、子网掩码、网关、DNS、端口号

网络的基本概念

  • 客户端:应用 C/S(客户端/服务器) B/S(浏览器/服务器)
  • 服务器:为客户端提供服务、数据、资源的机器
  • 请求:客户端向服务器索取数据
  • 响应:服务器对客户端请求作出反应,一般是返回给客户端数据

img

URL

  • Uniform Resource Locator(统一资源定位符)
  • 网络中每一个资源都对应唯一的地址——URL

IP 、子网掩码路由器DNS

img

IP地址

IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址(每个机器都有一个编码,如MAC上就有一个叫MAC地址的东西)的差异。是32位二进制数据,通常以十进制表示,并以“.”分隔。IP地址是一种逻辑地地址,用来标识网络中一个个主机,在本地局域网上是惟一的。

img

IP

IP(网络之间互连的协议)它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统,只要遵守IP协议就可以与因特网互连互通。IP地址有唯一性,即每台机器的IP地址在全世界是唯一的。这里指的是网络上的真实IP它是通过本机IP地址和子网掩码的”与”运算然后再通过各种处理算出来的(要遵守TCP协议还要加报文及端口什么的,我没有细追究,现在还用不上,反正暂时知道被处理过的就行了),顺便教大家查自己真实IP的方法:

img

img

子网掩码

要想理解什么是子网掩码,就不能不了解IP地址的构成。互联网是由许多小型网络构成的,每个网络上都有许多主机,这样便构成了一个有层次的结构。IP地址在设计时就考虑到地址分配的层次特点,将每个IP地址都分割成网络号和主机号两部分,以便于IP地址的寻址操作。
IP地址的网络号和主机号各是多少位呢?如果不指定,就不知道哪些位是网络号、哪些是主机号,这就需要通过子网掩码来实现。什么是子网掩码子网掩码不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分子网掩码的设定必须遵循一定的规则。与IP地址相同,子网掩码的长度也是32位,左边是网络位,用二进制数字“1”表示;右边是主机位,用二进制数字“0”表示。假设IP地址为“192.168.1.1”子网掩码为“255.255.255.0”。其中,“1”有24个,代表与此相对应的IP地址左边24位是网络号;“0”有8个,代表与此相对应的IP地址右边8位是主机号。这样,子网掩码就确定了一个IP地址的32位二进制数字中哪些是网络号、哪些是主机号。这对于采用TCP/IP协议的网络来说非常重要,只有通过子网掩码,才能表明一台主机所在的子网与其他子网的关系,使网络正常工作。

常用的子网掩码有数百种,这里只介绍最常用的两种子网掩码。

  • 子网掩码是“255.255.255.0”的网络:
    最后面一个数字可以在0~255范围内任意变化,因此可以提供256个IP地址。但是实际可用的IP地址数量是256-2,即254个,因为主机号不能全是“0”或全是“1”。

  • 子网掩码是“255.255.0.0”的网络:
    后面两个数字可以在0~255范围内任意变化,可以提供255²个IP地址。但是实际可用的IP地址数量是255²-2,即65023个。

IP地址的子网掩码设置不是任意的。如果将子网掩码设置过大,也就是说子网范围扩大,那么,根据子网寻径规则,很可能发往和本地主机不在同一子网内的目标主机的数据,会因为错误的判断而认为目标主机是在同一子网内,那么,数据包将在本子网内循环,直到超时并抛弃,使数据不能正确到达目标主机,导致网络传输错误;如果将子网掩码设置得过小,那么就会将本来属于同一子网内的机器之间的通信当做是跨子网传输,数据包都交给缺省网关处理,这样势必增加缺省网关(文章下方有解释)的负担,造成网络效率下降。因此,子网掩码应该根据网络的规模进行设置。如果一个网络的规模不超过254台电脑,采用“255.255.255.0”作为子网掩码就可以了,现在大多数局域网都不会超过这个数字,因此“255.255.255.0”是最常用的IP地址子网掩码;假如在一所大学具有1500多台电脑,这种规模的局域网可以使用“255.255.0.0”。

网关

网关实质上是一个网络通向其他网络的IP地址。比如有网络A和网络B,网络A的IP地址范围为“192.168.1.1192.168.1.254”,子网掩码为255.255.255.0;网络B的IP地址范围为“192.168.2.1192.168.2.254”,子网掩码为255.255.255.0。在没有路由器的情况下,两个网络之间是不能进行TCP/IP通信的,即使是两个网络连接在同一台交换机(或集线器)上,TCP/IP协议也会根据子网掩码(255.255.255.0)判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信,则必须通过网关。如果网络A中的主机发现数据包的目标主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络B的网关,网络B的网关再转发给网络B的某个主机。网络B向网络A转发数据包的过程也是如此 所以说,只有设置好网关的IP地址,TCP/IP协议才能实现不同网络之间的相互通信。那么这个IP地址是哪台机器的IP地址呢?网关的IP地址是具有路由功能的设备的IP地址,具有路由功能的设备有路由器、启用了路由协议的服务器(实质上相当于一台路由器)、代理服务器(也相当于一台路由器)。

路由器(Windows下叫默认网关,网关就是路由,路由就是网关不要蒙)

如果搞清了什么是网关,默认网关也就好理解了。就好像一个房间可以有多扇门一样,一台主机可以有多个网关。默认网关的意思是一台主机如果找不到可用的网关,就把数据包发给默认指定的网关,由这个网关来处理数据包。现在主机使用的网关,一般指的是默认网关。
下方是百度百科给出的解释

img

如何设置默认网关 一台电脑的默认网关是不可以随随便便指定的,必须正确地指定,否则一台电脑就会将数据包发给不是网关的电脑,从而无法与其他网络的电脑通信。默认网关的设定有手动设置和自动设置两种方式。

  • 手动设置:手动设置适用于电脑数量比较少、TCP/IP参数基本不变的情况,比如只有几台到十几台电脑。因为这种方法需要在联入网络的每台电脑上设置“默认网关”,非常费劲,一旦因为迁移等原因导致必须修改默认网关的IP地址,就会给网管带来很大的麻烦,所以不推荐使用。需要特别注意的是:默认网关必须是电脑自己所在的网段中的IP地址,而不能填写其他网段中的IP地址。
  • 自动设置:自动设置就是利用DHCP服务器来自动给网络中的电脑分配IP地址、子网掩码和默认网关。这样做的好处是一旦网络的默认网关发生了变化时,只要更改了DHCP服务器中默认网关的设置,那么网络中所有的电脑均获得了新的默认网关的IP地址。这种方法适用于网络规模较大、TCP/IP参数有可能变动的网络。另外一种自动获得网关的办法是通过安装代理服务器软件(如MS Proxy)的客户端程序来自动获得,其原理和方法和DHCP有相似之处。由于篇幅所限,就不再详述了。

缺省网关

缺省网关(Default Gateway)是计算机网络中一个如何将数据包转发到其他网络中的节点。在一个典型的TCP / IP网络,节点(如服务器、工作站和网络设备)都有一个定义的默认路由设置(指向默认网关)。可以在没有特定路由的情况下,明确出发送数据包的下一跳IP地址。
下方是百度百科给出的解释:

img

可以看出缺省网关就是默认网关,那么有人会说既然有一样为什么又凭空多出来一个缺省网关,我的理解是这样的,应该说默认网关是缺省网关的一个子集。缺省网关有一个定义的默认路由设置(指向默认网关),缺省网关就相当于一个代理服务器暂时管理发送的数据包,当发送到目标主机时先由目标主机的缺省网关接收再找到对应的默认网关,就相当于缺省网关是父类,默认网关是子类~~

DNS服务器

域名服务器(Domain Name Server)。在Internet上域名与IP地址之间是一一对应的,域名虽然便于人们记忆,但机器之间只能互相认识IP地址,它们之间的转换工作称为域名解析,域名解析需要由专门的域名解析服务器来完成,DNS就是进行域名解析的服务器 。

DHCP服务器

DHCP指的是由服务器控制一段IP地址范围,客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码。提升地址的使用率。

MAC地址

MAC地址就如同我们身份证上的身份证号码,具有全球唯一性。(知道这个就行了,不用往下看了)
MAC(Media Access Control,介质访问控制)地址

  • 前24位叫做组织唯一标志符(Organizationally Unique Identifier,即OUI),是由IEEE的注册管理机构给不同厂家分配的代码,区分了不同的厂家。
  • 后24位是由厂家自己分配的,称为扩展标识符。同一个厂家生产的网卡中MAC地址后24位是不同的。
    网卡的物理地址通常是由网卡生产厂家烧入网卡的EPROM(一种闪存芯片,通常可以通过程序擦写),它存储的是传输数据时真正赖以标识发出数据的电脑和接收数据的主机的地址。

也就是说,在网络底层的物理传输过程中,是通过物理地址来识别主机的,它一定是全球唯一的。比如,著名的以太网卡,其物理地址是48bit(比特位)的整数,如:44-45-53-54-00-00,以机器可读的方式存入主机接口中。以太网地址管理机构(除了管这个外还管别的)(IEEE)(IEEE:电气和电子工程师协会)将以太网地址,也就是48比特的不同组合,分为若干独立的连续地址组,生产以太网网卡的厂家就购买其中一组,具体生产时,逐个将唯一地址赋予以太网卡。
在一个稳定的网络中,IP地址和MAC地址是成对出现的。如果一台计算机要和网络中另一外计算机通信,那么要配置这两台计算机的IP地址,MAC地址是网卡出厂时设定的,这样配置的IP地址就和MAC地址形成了一种对应关系。在数据通信时,IP地址负责表示计算机的网络层地址,网络层设备(如路由器)根据IP地址来进行操作;MAC地址负责表示计算机的数据链路层地址,数据链路层设备(如交换机)根据MAC地址来进行操作。IP和MAC地址这种映射关系由ARP(Address Resolution Protocol,地址解析协议)协议完成。

服务器

  1. 服务器的分类
    按照软件开发阶段来分,服务器可以大致分为2种
    (1)远程服务器
    别名:外网服务器、正式服务器
    使用阶段:应用上线后使用的服务器
    使用人群:供全体用户使用
    速度:服务器的性能、用户的网速
    (2)本地服务器
    别名:内网服务器、测试服务器
    使用阶段:应用处于开发、测试阶段使用的服务器
    使用人群:仅供公司内部的开发人员、测试人员使用
    速度:由于是局域网,所以速度飞快,有助于提高开发测试效率
  2. 本地服务器的选择
    远程服务器就是本地内网服务器开放外网访问而已
    如果处于学习、开发阶段,自己搭建一个本地服务器即可

端口号

端口包括物理端口和逻辑端口。物理端口是用于连接物理设备之间的接口,逻辑端口是逻辑上用于区分服务的端口。TCP/IP协议中的端口就是逻辑端口,通过不同的逻辑端口来区分不同的服务。
端口有什么用呢?我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区 分不同的服务的。

  • 公认端口(Well-Known Ports)
    这类端口也常称之为”常用端口”。这类端口的端口号从0到1023,它们紧密绑定于一些特定的服务。通常这些端口的通信明确表明了某种服务的协议,这种端口是不可再重新定义它的作用对象。80端口实际上总是HTTP通信所使用的,而23号端口则是Telnet服务专用的。
  • 注册端口(Registered Ports)
    端口号从1025到49151。分配给用户进程或应用程序。这些进程主要是用户选择安装的一些应用程序,而不是分配好的公认端口的常用程序。
  • 动态和/或私有端口(Dynamic and/or Private Ports)
    之所以称为动态端口,因为它一般不固定分配某种服务,而是动态分配。
l

VIM多光标插件——vim-visual-multi

插件简介 mg979/vim-visual-multi

vim宏操作和多光标插件

1
VIM多光标插件,提供vim/nvim快速操作多光标编辑的能力

顺便在这里附上我自己的nvim配置仓库地址 yaocccc/nvim

安装

1
2
推荐使用vim-plug安装
Plug 'mg979/vim-visual-multi', {'branch': 'master'}

配置

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
" 贴上本人的配置
let g:VM_theme = 'ocean'
let g:VM_highlight_matches = 'underline'
let g:VM_maps = {}
let g:VM_maps['Find Under'] = '<C-n>'
let g:VM_maps['Find Subword Under'] = '<C-n>'
let g:VM_maps['Select All'] = '<C-d>'
let g:VM_maps['Select h'] = '<C-Left>'
let g:VM_maps['Select l'] = '<C-Right>'
let g:VM_maps['Add Cursor Up'] = '<C-Up>'
let g:VM_maps['Add Cursor Down'] = '<C-Down>'
let g:VM_maps['Add Cursor At Pos'] = '<C-m>'
let g:VM_maps['Add Cursor At Word'] = '<C-w>'
let g:VM_maps['Remove Region'] = 'q'

" 从上至下的意义:
" ['Find Under'] -> 选中光标下的词(ctrl+n继续向下选中相同文本)
" ['Find Subword Under'] -> 选中光标下的词(ctrl+n继续向下选中相同文本)
" ['Select All'] -> 选中文件中所有光标下的词
" ['Select h'] -> 从光标往左选中文本(ctrl+n继续向下选中相同文本)
" ['Select l'] -> 从光标往右选中文本(ctrl+n继续向下选中相同文本)
" ['Add Cursor Up'] -> 向上添加一个光标(原光标+上光标 继续使用则继续添加)
" ['Add Cursor Down'] -> 向下添加一个光标(原光标+下光标 继续使用则继续添加)
" ['Add Cursor At Pos'] -> 将当前光标添加入多光标列表中
" ['Add Cursor At Word'] -> 将当前光标所在词的词首加上多光标列表中
" ['Remove Region'] -> 移除当前光标

VIMRC

基本概念

!!!重要!!!
!!!重要!!!
!!!重要!!!

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
30
31
32
简单来说该插件提供三种模式:
多光标 normal 模式
多光标 visual 模式
多光标 insert 模式

可以对应vim的三模式
如何切换:
normal <-> visual 模式互相转换: Tab
normal --> insert 进入插入模式: i、a、c等 和 vim原生的按键是一样的
insert --> normal 返回普通模式: Esc

normal模式 可以使用 hjkl w e b f t $ ^ 0 等等按键来移动 多光标中的全部光标(不可以使用上下左右 PS: 上下左右可以用来移动原光标)
visual模式 可以使用 hjkl w e b f t $ ^ 0 等等按键来选中 多光标中的全部光标(不可以使用上下左右 PS: 上下左右可以用来移动原光标)
insert模式 可以使用 上下左右 等等按键来移动 多光标中的全部光标

如何从正常VIM模式中进入多光标模式
1: VIM处于normal模式
2: 用以下快捷键即可
['Find Under'] -> 选中光标下的词(ctrl+n继续向下选中相同文本)
['Find Subword Under'] -> 选中光标下的词(ctrl+n继续向下选中相同文本)
['Select All'] -> 选中文件中所有光标下的词
['Select h'] -> 从光标往左选中文本(ctrl+n继续向下选中相同文本)
['Select l'] -> 从光标往右选中文本(ctrl+n继续向下选中相同文本)
['Add Cursor Up'] -> 向上添加一个光标(原光标+上光标 继续使用则继续添加)
['Add Cursor Down'] -> 向下添加一个光标(原光标+下光标 继续使用则继续添加)
['Add Cursor At Pos'] -> 将当前光标添加入多光标列表中
['Add Cursor At Word'] -> 将当前光标所在词的词首加上多光标列表中

如何从多光标模式中退出
一股脑Esc就对了


使用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
当前有以下文本,试完成以下案例 原始光标初始位置在第1行的l位置

let aa = 1;
let bbb = 2;
let cccc = 3;
let ddddd = 4;


案例1: 将 全部let 改成 const
案例重点: 如何添加多光标

方案1: ctrl+d c const
1: ctrl+d 选中文件中所有光标下的词
2: c 从visual模式进入插入模式,并变更当前选中的文本
方案2: ctrl+n ctrl+n ctrl+n ctrl+n c const
1: ctrl+n 选中光标下的词 * 4次
2: c 从visual模式进入插入模式,并变更当前选中的文本
方案3: ctrl+下 ctrl+下 ctrl+下 xxx i const
1: ctrl+下 * 3次 将共计4个l位置的光标添加进多光标列表
2: xxx删除 i进入插入模式
...


案例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
案例2: 将 124行的 let 改成 const
案例重点: 如何取消/跳过某光标

方案1: ctrl+d 下下q c const
1: ctrl+d 选中文件中所有光标下的词
2: 下下 移动原始光标到第三行 q 取消当前光标
3: c 从visual模式进入插入模式,并变更当前选中的文本
方案2: ctrl+n ctrl+n 下下 ctrl+n c const
1: ctrl+n 选中光标下的词 * 2次
2: 下下 移动原始光标到第四行
3: ctrl+n 选中光标下的词
4: c 从visual模式进入插入模式,并变更当前选中的文本
方案3: ctrl+下 下下 ctrl+x xxx i const
1: ctrl+下 将共计2个l位置的光标添加进多光标列表
2: 下下 移动原始光标到第四行
3: ctrl+x 添加当前位置光标进多光标列表
4: xxx删除 i进入插入模式
...


案例2

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
案例3: 将文本内容改成
案例重点: 多光标下normal和visual的切换 光标的跳转等
let aa = 1 * aa;
let bbb = 2 * bbb;
let cccc = 3 * cccc;
let ddddd = 4 * ddddd;

方案1: ctrl+d tab w tab e y tab ww a * 空格 esc p
1: ctrl+d 选中文件中所有光标下的词
2: tab 从visual模式转换到normal模式
3: w 跳到 下个词首(aa、bbb等所在词)
4: tab 从normal模式转换到visual模式
5: e选中到词尾 y复制选中部分
6: tab 从visual模式转换到normal模式
7: ww a ... 光标跳转并插入 * 空格
8: esc 从 insert模式回归到normal
9: p粘贴刚刚选中的多个词
方案2: ctrl+下 ctrl+下 ctrl+下 w tab e y tab ww a * 空格 esc p
1: ctrl+下 * 3次 将共计4个l位置的光标添加进多光标列表
2: w 跳到 下个词首(aa、bbb等所在词)
3: tab 从normal模式转换到visual模式
4: e选中到词尾 y复制选中部分
5: tab 从visual模式转换到normal模式
6: ww a ... 光标跳转并插入 * 空格
7: esc 从 insert模式回归到normal
8: p粘贴刚刚选中的多个词

案例3

PS

1
2
3
4
5
6
7
8
9
建议将列出的按键配置都自己试一遍
该插件还有不少其他用法,例如:
自定义搜索文本跳转
多行对齐
...

不过我不会 有需要进阶的同好可以看原仓库说明


mg979/vim-visual-multi

l

GT系列蓝牙双模键盘使用手册

GT系列当前包括BLE60 、 Omega45 、 Farad69、Omega50四款键盘,四款键盘均采用的LotKB开源固件进行驱动,功能按键等设定基本一致:

功能按键说明

休眠按键:P – 同时按下Lshift+Rshift时按此键可以手动进入休眠模式,按任意键可以唤醒。(此功能按键修改配列不会改变)

关机按钮:ESC或~ – 同时按下Lshift+Rshift时按此键可以手动进入关机模式,关机后需要按背部的Bootloader按钮才可重新开机。长期不用或携带外出建议关机。

切换蓝牙设备:Q/W/E – 同时按下Lshift+Rshift时按Q/W/E可以在已绑定的蓝牙设备之间进行切换。

重新开启蓝牙广播:R – 同时按下Lshift+Rshift时按R可以重新开启蓝牙广播,用于切换设备后进行绑定。

显示蓝牙键盘状态灯:L 或 ? – 在电池供电情况下,状态灯会自动熄灭,如要显示当前状态灯,同时按下Lshift+Rshift时按L或?,将显示状态灯5秒。

锁定WIN键:G – 同时按下Lshift+Rshift时按此键,将锁定WIN键,再次同时按下Lshift+Rshift时按此键,将恢复WIN键,重启将自动恢复WIN键。

切换连接模式按键:M – 在通过USB和蓝牙同时连接一台设备(也可通过USB连接一台设备、蓝牙连接另一台设备)的情况下,同时按下Lshift+Rshift时按此键可以切换连接模式。如未同时使用USB模式和蓝牙模式,此按键无效。

进入DFU刷机模式:B – 同时按下Lshift+Rshift时按此键可以重启到DFU刷机模式。也可在唤醒的同时按Space+B进入DFU刷机模式。

清空当前设备蓝牙绑定信息按键:O – 同时按下Lshift+Rshift时按此键可以清空当前蓝牙绑定信息。仅清空当前设备,其余绑定设备不会清空。

重置键盘:I – 同时按下Lshift+Rshift时按此键可以重置键盘。重置键盘会清空所有的蓝牙绑定信息、自定义的配列、存储的键盘配置信息。

RGB灯效控制:调整颜色: A/S/D/F/C/V – 同时按下Lshift+Rshift时按A/S/D/F/C/V,分别是增加饱和度、降低饱和度、增加亮度、降低亮度、增加色调、降低色调。

RGB灯效控制:调整模式:Z – 同时按下Lshift+Rshift时按Z,循环灯效。

RGB灯效控制:开关RGB:X – 同时按下Lshift+Rshift时按X,切换RGB灯的开关。

PCB背部多功能按钮 – 背部按钮属于多功能按钮。键盘正常模式下,按下一秒后松手键盘关机;按下三秒后松手键盘进入DFU刷机模式;按下10秒后松手,键盘将重置。在断电状态,按下背部按钮不放,同时按下键盘第一行首个按键或最后一个按键,然后通电,将强制进入DFU模式。

如果要绑定并切换多设备,按以下步骤操作:

一、初次绑定

1、键盘上按下双Shift+Q,然后再按下双shift+R,在需要绑定键盘的设备上搜索蓝牙键盘,在搜索到相应蓝牙键盘名称后,将第一个设备绑定到键盘Q键上;

2、键盘上按下双Shift+W,然后再按下双shift+R,然后绑定第二个设备到键盘W键;;

3、键盘上按下双Shift+E,然后再按下双shift+R,然后绑定第三个设备到键盘E键;

4、通过双shift+Q/W/E进行输入设备的切换

二、更换绑定

其他说明

状态提示灯说明

GT系列蓝牙键盘状态灯一般为3颗,以Omega45为例:

img

如要更换某个设备的绑定,如要新绑定一个设备到W键上。
1、将W键绑定设备上的蓝牙键盘进行删除(如windows系统进入系统设备删除蓝牙键盘;Android系统进入设置-蓝牙后删除蓝牙键盘);

2、键盘上按下双Shift+W,切换到W设备;

3、然后键盘上再按下双shift+O,删除W上绑定的设备,绑定新的设备到键盘W键上;

相关参数的设定 – 考虑到耗电问题,正常键盘扫描按键输入为10ms一次,回报率为100Mhz;如果15秒不按键转入慢速扫描,100ms一次,当有按键按下,又自动转入正常扫描速度10ms一次;如果20分钟无按键行为将自动转入休眠模式,此时要重新启用键盘,只需要按任意键就可唤醒,唤醒动作后约1-2s可以正常输入。

在线编译

通过在线编辑,可以可视化配置按键配列、生成固件。

通过此网站 :lotkb.glab.online 或 访问导航中“在线编译” 。可以可视化修改按键,更改按键更加方便;可以更改一些参数设定,如自动休眠时间、RGB灯数量等。

LotKB采用的低功耗蓝牙:Bluetooth Low Energy,并且是5.0版本,可向下兼容蓝牙4.0,但不支持蓝牙3.0及以下。由于采用的较新的协议,需要较新的系统才能支持。如:
iOS 6以上;Android 4.3以上;Windows 8.1以上;Windows Phone 8.1;较新的MacOS。
除了操作系统以外,也需要硬件设备支持,至少要硬件设备支持蓝牙4.0以上才行。从时间上来说,可能要2011年以后的设备才能支持蓝牙4.0,也才可能使用此键盘的蓝牙功能

键盘使用说明

本页面最后修改时间2025-06-09

  • 本页面操作说明全系列键盘通用,所有内容基于最新版固件,如与当前键盘实际不符,建议升级固件
  • 更多键盘专有说明可以访问键盘产品信息页面

相关概念

开机

GT系列三模键盘PCB没有硬件电源开关,只需要接入电池或插入USB通电后,键盘就会自动开机。

软关机后,可以插入USB线 / 按击一下背部多功能按钮 / 按下旋钮中键 实现开机。

关机

按下背部多功能按钮1秒以上后(不超过3秒)松手 或 系统功能按键Lshift+Rshift+Backspace或~进入关机状态。

插入 USB 时,无法关机(关机后马上会被唤醒)。

自动睡眠

在一定时间不敲击按键后,键盘将自动进入睡眠模式以便省电。进入自动睡眠的时间可以自由设定。

自动睡眠后,按任意键可唤醒键盘重新工作。

插入 USB 后,由 USB 供电,自动睡眠功能不会启用,如处于睡眠状态也将自动唤醒。

手动睡眠

同时按下Lshift+Rshift+P可以手动进入睡眠模式

手动睡眠后,按任意键可唤醒键盘重新工作。

如果启用了唤醒按键功能,手动睡眠后唤醒时需要按Space+U才能唤醒键盘。

插入 USB 后,将直接唤醒键盘重新工作,并且无法手动睡眠。

蓝牙连接设备

如需通过 蓝牙连接 进行输入,确保键盘处于蓝牙连接模式,并完成 配对蓝牙设备 的过程。

2.4G连接设备

如需通过 2.4G无线 进行输入,确保键盘处于2.4G无线连接模式,将 2.4G接收器 插入设备的USB口,并完成2.4G接收器配对

USB 连接设备

直接将 USB 线缆插入到键盘的 USB 接口,键盘即自动切换至 USB 模式,使用 USB 输入。

2.4G接收器模式

将 USB 线缆插入到键盘的 USB 接口,将键盘2.4G无线模式切换至 2.4G接收模式,此时当前键盘可作为其他2.4G键盘的接收器使用。

有线 / 无线状态切换

同时按下Lshift+Rshift+M可以在USB / 无线 状态之间切换,如未插入USB线,此切换功能无效。

USB连接状态下,可通过Lshift+Rshift+Q/W/E直接跳转到对应蓝牙或2.4G通道。

省电模式

在没有接入 USB 电源的情况下,为提升续航,键盘处于省电模式。

在省电模式下,所有的指示灯仅在变化后亮起 5 秒,然后熄灭。键盘在无操作 15 秒后转入慢速扫描模式,无操作 600 秒后自动睡眠。(前述三个时间均支持通过配置工具自定义)

慢速扫描模式下,按键检测将可能略有延迟。在慢速扫描模式下按任意按键即可恢复到快速扫描模式。

自动睡眠后,按下键盘的任意按键可唤醒键盘。

电量显示

电量百分比仅供参考

由于测量数据是根据电压估算,显示电量和实际可能存在误差,损耗较大的电池电量百分比可能无法达到 100%。

在新版本的 iOS、安卓和 Windows 上,应该能够正常的显示蓝牙设备的电量百分比。

对于安卓手机,如果没有正常的显示电量,可以尝试下载 BatON 软件来获取蓝牙设备电量。

如USB与2.4G无线连接状态,或其他无法显示电量的情况,可以通过Lshift+Rshift+H输出电量

通过Lshift+Rshift+H输出电量时,N代表未能检测到数值,F代表电池电量满,数值代表电量百分比。

新版固件支持通过键盘控制面板显示电量

全键无冲(NKRO)

此键盘支持 NKRO(全键无冲)模式。USB、蓝牙、2.4G模式均支持全键无冲模式。

唤醒按键

启用唤醒按键功能后,手动睡眠后唤醒需按Space+U唤醒键盘。
自动睡眠时,不需要按唤醒按键,可任意键唤醒。

默认不启用唤醒按键功能,可通过Lshift+Rshift+I启用或禁用。

DFU 模式

说明

采用nRF52810/nRF52811芯片的键盘,由于空间不足不支持DFU模式,请采用线刷方式更新固件

DFU模式为键盘内置的蓝牙刷机模式,在通过手机nRF Connect程序升级时,必须确保在蓝牙通讯模式,并进入DFU模式。

同时按下Lshift+Rshift+B可以重启到DFU刷机模式。也可长按PCB背部多功能按钮4秒以上后(不超过9秒)松手重启到DFU刷机模式。

USB ISP 模式

USB ISP 模式为USB芯片的烧录模式,在更新USB固件时需要先进入USB ISP模式。

短接PCB背部K1位置两个焊盘,并插入USB线可手动进入USP ISP模式;

用烧录工具烧录USB固件时,烧录工具会自动跳转USB ISP模式,无需手动短接K1。

键盘控制面板

键盘控制面板是一个网页应用程序,可通过网页程序控制键盘的各种功能。

包括获取键盘各种信息、控制键盘功能、更改灯光和层级、更改输出模式等。

键盘需2024年12月7日后固件支持接入键盘控制面板,接收器需2025年1月4日后固件支持接入键盘控制面板。

键盘控制面板支持作为网页程序进行安装,安装后支持离线使用。

轴灯版PCB

轴灯版PCB采用键盘主控直接驱动RGB灯,所以无复杂灯效,仅有单色常亮、单色呼吸和彩虹循环,可手动调色,可视为单色轴灯的增强版本。

轴灯分为轴灯模式和指示灯模式,出厂默认为指示灯模式,可通过Lshift+Rshift+L 或 通过配置工具设置一颗状态灯开关按键 在指示灯模式和轴灯模式之间切换。

指示灯模式时,不可控制,以不同颜色指示不同状态,详细参考 状态提示灯说明

要控制轴灯,请先切换到轴灯模式。轴灯模式时,采用Lshift+Rshift+Z X C V等RGB控制功能调整轴灯。

RGB轴灯版PCB

RGB轴灯版PCB采用WS2812 RGB灯,支持各种丰富绚丽的灯效(同QMK灯效),支持键盘状态指示灯、USB与蓝牙状态指示灯。

可采用Lshift+Rshift+Z X C V等RGB控制功能调整RGB轴灯。

或 接入配置工具,找到 灯光 功能,将RGB阵列相关按键设定到你指定的按键上控制RGB灯光。

RGB轴灯同时兼容指示灯,指示灯可通过配置工具设置一颗状态灯开关按键进行开关

指示灯支持独立运行,建议在使用电池时,关闭RGB轴灯,开启指示灯,指示灯将可自动关闭节能

系统功能按键说明

特别提示
  • 默认采用Lshift+Rshift做为功能键触发按键
  • 可以通过自行编译固件使用Lctrl+Rctrl  Win+ESC等做为触发按键。
  • 更改后下表所列功能按键需做相应调整,如触发按键更改为Win+ESC,则按下Win+ESC+P为睡眠。
  • 系统功能按键为内置功能,不因修改了按键而消失,只要您确保LShift/RShift等功能触发按键存在即可使用
  • PAD数字键盘等较少按键的键盘,没有Shift等按键,下列系统功能可以通过配置工具-层级/功能-键盘功能进行设定。
  • 新版固件也支持通过键盘控制面板控制各种系统功能
功能 按键 功能说明
睡眠 Lshift+Rshift+P 手动进入睡眠模式,按任意键可以唤醒。
关机 Lshift+Rshift+~或Backspace 手动进入关机模式。新版固件将ESC更换成了Backspace。 关机后需要插入USB线 或 短按背部多功能按钮开机。 长期不用或携带外出建议关机。
重启 Lshift+Rshift+N 手动重启键盘,用于遇到部分故障后重启修正。
有线/无线状态切换 Lshift+Rshift+M 在USB有线和无线同时工作时,可以切换有线/无线连接模式。 如未同时使用USB有线和无线,按键无效。
无线模式切换 Lshift+Rshift+U 在键盘支持BLE5.0与2.4G无线两种无线模式时,在两种模式之间切换。
无线收发模式切换 Lshift+Rshift+J 最新固件中,当处于2.4G无线模式时,可在2.4G接收 [接收器] 和2.4G发送 [键盘] 两种2.4G无线模式之间切换。
切换蓝牙设备/无线接收器 Lshift+Rshift+Q/W/E 可以在已配对的蓝牙设备/无线接收器之间进行切换,Q/W/E代表不同蓝牙连接通道/无线接收器
重启蓝牙广播 2.4G无线配对 Lshift+Rshift+R 蓝牙模式:重新开启蓝牙广播,用于手动进行连接或切换设备后配对。 2.4G无线模式:发起配对通讯,与2.4G接收器进行配对。
进入DFU Lshift+Rshift+B 重启到DFU刷机模式。 也可长按PCB背部多功能按钮4秒以上后松手重启到DFU刷机模式。
清空当前配对 Lshift+Rshift+O 蓝牙模式:清空当前蓝牙设备配对信息。仅清空当前设备,其余配对设备不会清空。 2.4G无线模式:解除与当前2.4G接收器的配对
输出电量 Lshift+Rshift+H 通过键盘输出当前键盘剩余电量。输出N为检测未稳定,F为满电,数字为电量百分比。
状态灯开关 Lshift+Rshift+L 无轴灯PCB/RGB轴灯PCB:开启或关闭状态指示灯显示(注:不包括键盘大小写等)。 轴灯版PCB:使轴灯在轴灯模式和指示灯模式之间切换
多功能按钮 PCB背部按钮属于多功能按钮。 键盘正常模式下,按下1秒以上后(不超过3秒)松手键盘关机; 按下4秒以上后(不超过9秒)松手键盘进入DFU刷机模式; 按下10秒以上后松手,键盘将重置。 关机状态短按一下开机。
唤醒按键 Space+U 启用唤醒按键功能后,手动睡眠后唤醒需按Space+U唤醒键盘。 自动睡眠时,不需要按唤醒按键,可任意键唤醒。
切换唤醒按键 Lshift+Rshift+I 启用或禁用唤醒按键功能。 启用唤醒按键功能后,手动睡眠后唤醒需按Space+U唤醒键盘
切换默认层 Lshift+Rshift+数字键 Lshift+Rshift+1切换默认层到第2层。 Lshift+Rshift+0切换默认层到第1层。 睡眠或关机后唤醒自动恢复第1层为默认层
RGB调整颜色 A/S/D/F/C/V 轴灯版(轴灯模式下)为控制轴灯;非轴灯版为控制底灯 同时按下Lshift+Rshift时按A/S/D/F/C/V, 分别是增加饱和度、降低饱和度、增加亮度、降低亮度、增加色调、降低色调。
RGB灯效循环 Lshift+Rshift+Z RGB轴灯版为控制RGB轴灯在灯效之间循环 轴灯版(轴灯模式下)为控制轴灯在常亮、呼吸、多彩变换之间循环 非轴灯版为控制底灯在32种灯效之间循环
RGB灯开关 Lshift+Rshift+X RGB轴灯版为RGB轴灯开关 轴灯版(轴灯模式下)为轴灯开关 非轴灯版为RGB底灯开关

配对蓝牙设备

初次配对

  1. 键盘上按下Lshift+Rshift+Q,切换到蓝牙通道1️⃣,然后再按下Lshift+Rshift+R开启广播;
  2. 在需要连接键盘的设备上搜索蓝牙键盘,搜索到相应蓝牙键盘名称后,将第一个设备配对到键盘蓝牙通道1️⃣(Q键);
    • 各个操作系统 配对蓝牙键盘 的方式请自行搜索相关文档
    • 如果仅有一个蓝牙设备,可以配对到任意蓝牙通道,但是建议配对到蓝牙1️⃣
    • 如不需要多设备切换,可不再配对设备到蓝牙通道2️⃣和蓝牙通道3️⃣
  3. 键盘上按下Lshift+Rshift+W,切换到蓝牙通道2️⃣,然后再按下Lshift+Rshift+R开启广播,再配对第二个设备到键盘蓝牙通道2️⃣(W键 );
  4. 键盘上按下Lshift+Rshift+E,切换到蓝牙通道3️⃣,然后再按下Lshift+Rshift+R开启广播,再配对第三个设备到键盘蓝牙通道3️⃣(E键 );
  5. 通过Lshift+Rshift+Q/W/E切换到不同的蓝牙通道,可在多个配对设备之间切换。

更换配对

如需更换某个设备的配对,例:要重新配对一个设备到蓝牙通道2️⃣(W键 )。

  1. 将W键配对的设备上的蓝牙键盘进行删除(如windows系统进入系统设备删除蓝牙键盘;Android系统进入设置-蓝牙后删除蓝牙键盘);
  2. 键盘上按下Lshift+Rshift+W,切换到蓝牙通道2️⃣;
  3. 键盘上再按下Lshift+Rshift+O,删除蓝牙通道2️⃣配对的设备;
  4. 如有必要,按下Lshift+Rshift+R开启广播;
  5. 在需要配对键盘的设备上搜索蓝牙键盘,搜索到相应蓝牙键盘名称后,将新的设备配对到键盘蓝牙通道2️⃣(W键 );

Tip

上述切换蓝牙通道及删除配对设备等,也可通过BT 1 / BT 2 / BT 3 / 无线 解除配对 等按键进行操作

采用BT 1 / BT 2 / BT 3按键切换蓝牙通道后,需要按下无线 广播配对手动开启蓝牙广播

2.4G接收器配对

  1. 将接收器插入电脑USB口通电
  2. 按下Lshift+Rshift+U 将键盘切换到2.4G无线模式
  3. 键盘未配对:可多次按下Lshift+Rshift+R启动配对通讯,直到正确连接输出按键
  4. 键盘已配对:按下Lshift+Rshift+O删除配对信息,可多次按下Lshift+Rshift+R启动配对通讯,直到正确连接输出按键
  5. 配对多个接收器:按下Lshift+Rshift+Q/W/E切换不同的无线通道,然后重复执行2-4三个步骤完成与不同接收器的配对
  6. 通过Lshift+Rshift+Q/W/E切换到不同的无线通道,可在多个配对的无线接收器之间切换。
  7. 如PAD等小键盘,无Lshift、Rshift,可通过配置工具配置切换 无线模式/无线 广播配对/无线 解除配对/BT 1 / BT 2 / BT 3按键,或通过键盘控制面板,启动配对通讯、删除配对信息、切换无线接收器

Tip

配对前请确保键盘固件更新到支持三模固件的版本,并确保接收器和键盘的固件版本对应。

状态提示灯说明

GT系列无线键盘无灯版指示灯一般为3颗,每个键盘指示灯的位置稍有不同,但是指示逻辑基本一致:

  • 蓝色灯-蓝牙连接成功、蓝牙输出
  • 绿色灯-USB输出
  • 红色灯-2.4G无线输出
  • 蓝色指示灯闪烁-蓝牙通道1️⃣广播中
  • 绿色指示灯闪烁-蓝牙通道2️⃣广播中
  • 红色指示灯闪烁-蓝牙通道3️⃣广播中
  • USB连接状态下,状态灯常亮
  • 蓝牙连接状态下,指示灯5秒后自动熄灭(可自定义常亮时长)
  • 蓝牙广播时闪烁30秒后未连接自动熄灭。

GT系列无线键盘单色RGB轴灯版默认采用轴灯作为指示,不同颜色代表不同状态:

  • 蓝色-蓝牙连接成功、蓝牙输出
  • 绿色-USB输出
  • 青色-2.4G无线输出
  • 粉色-蓝牙通道1️⃣广播中
  • 黄色-蓝牙通道2️⃣广播中
  • 红色-蓝牙通道3️⃣广播中
  • USB连接状态下,状态灯常亮
  • 蓝牙连接状态下,指示灯5秒后自动熄灭(可自定义常亮时长)
  • 蓝牙广播30秒后未连接自动熄灭。

GT系列无线键盘炫彩RGB轴灯版默认采用某颗轴灯作为指示,不同颜色代表不同状态:

  • 绿色-USB输出
  • 蓝色-蓝牙通道1️⃣输出
  • 红色-蓝牙通道2️⃣输出
  • 橙色-蓝牙通道3️⃣输出
  • 青色-2.4G无线1️⃣输出
  • 紫色-2.4G无线2️⃣输出
  • 粉色-2.4G无线3️⃣输出
  • 黄色-2.4G无线接收器接收模式启用
  • 指示灯可通过配置工具设置一颗状态灯开关按键进行开关
  • 指示灯支持独立运行,建议在使用电池时,关闭RGB轴灯,开启指示灯,指示灯将可自动关闭节能

关于充电

  • PCB自带锂电池充电功能,可通过USB连线进行充电。
  • 可插入电脑USB口充电,也可插入5V电源适配器充电。
  • 最常见的5V电源适配器就是手机充电头,但是不建议采用快充头进行充电,因为快充头支持9V/12V等较高电压输出,可能导致烧毁PCB。
  • 锂电池充电电流最大支持400mah左右(老版200mah),1000mah电池大概需要3小时左右能充满。

关于续航

  • 无轴灯版本,1000mah电池可使用约90天(每日8小时)
  • 新版带编码器与RGB矩阵灯的版本,1000mah电池可使用约80天(每日8小时)1
  • RGB轴灯耗电量非常大,开启轴灯后1000mah电池可使用仅约5天。2
  • 具体续航时间因硬件及使用环境等差异而不同,如电池实际容量(排除虚标)大小及损耗情况,无线信号强度等均对续航有影响

硬件运作方式

硬件上采用nRF52系列芯片+CH55x芯片协同实现的USB、蓝牙、2.4G无线功能

  • nRF52系列芯片当前支持nRF52810/nRF52811/nRF52832三款芯片
  • CH55x芯片主要使用CH552T或CH554T,也支持同系列的CH552G等芯片
  • nRF52主要负责 运行键盘主控程序、蓝牙通讯、2.4G通讯、灯光控制,是主功能芯片。
  • CH55x主要负责 USB通讯,包括转发nRF52数据到USB主机,转发配置数据到nRF52,烧录nRF52芯片固件

本页面最后修改时间2025-06-09

本页面操作说明全系列键盘通用,所有内容基于最新版固件,如与当前键盘实际不符,建议升级固件 更多键盘专有说明可以访问键盘产品信息页面

l

vim快捷键

行跳转快捷键

  • 文件行跳转
    • gg: 跳转到文件第一行。
    • G(Shift+g) : 跳转到文件最后一行。
    • nG(如10G) : 跳转到第n行。
    • :n(如:10+ 回车) : 跳转到第𝑛行。
    • H/ M/L : 跳转到当前屏幕的最高行(High)、中间行(Middle)、最低行(Low)。
  • 行内跳转
    • 0(数字零) : 跳转到当前行行首。
    • ^(Shift+6) : 跳转到当前行第一个非空字符。
    • $(Shift+4) : 跳转到当前行行尾。
  • 其他
    • Ctrl+o: 返回到跳转前的位置。
    • %: 跳转到与之匹配的括号行

vim-multiple-cursors插件快捷键

vim-multiple-cursors插件在Vim 中实现了类似Sublime Text/VS Code 的多光标编辑功能。核心在于使用Ctrl-n选择下一个匹配项,Ctrl-p跳回上一个,Ctrl-x跳过当前项。进入多光标模式后,可使用c(修改)、I(行首插入)、A(行尾插入) 等命令批量编辑。

核心快捷键(Normal/Visual 模式)

  • <C-n>(Ctrl+n) : 启动多光标模式并选择当前光标下的单词,或选择下一个匹配项。
  • <C-p>(Ctrl+p) : 移除上一个光标,撤销上一次的Ctrl-n操作。
  • <C-x>(Ctrl+x) : 跳过当前匹配的单词。
  • <C-g>(Ctrl+g) : 退出多光标模式,返回到单个光标。

使用流程

  1. 移动光标到想要修改的单词上。
  2. 按下<C-n>选中该单词。
  3. 继续按下<C-n>选中接下来的匹配项。
  4. 按下c(修改), i(插入), a(追加) 等模式键进入编辑模式。
  5. 输入新内容(所有光标处会同步修改)。
  6. 按下<Esc>退出多光标编辑模式。

可视模式(Visual Mode)使用

在可视模式下选中一段文本,然后按<C-n>,该插件会在选中的每一行行尾添加光标,方便批量操作。

常见问题与技巧

  • 退出: 如果选错了,使用<C-g>取消。
  • 兼容性: 建议将默认的<C-n>映射为其他键,以防与插件快捷键冲突。
  • 适用场景: 批量修改变量名、在多行开头添加注释符、快速格式化数据。

在 Vim 中使用 NERDTree 时,切换焦点的常用方法有:

1. 标准切换方法

  • Ctrl + w + w:在所有窗口间循环切换
  • Ctrl + w + h/j/k/l:向左/下/上/右切换窗口

2. 从 NERDTree 直接返回原文件

  • t:在新标签页打开文件(NERDTree 中)
  • T:在后台标签页打开文件
  • i:在水平分割窗口中打开
  • s:在垂直分割窗口中打开

3. 快速切换回原窗口

如果 NERDTree 在左侧,当前编辑文件在右侧:

  • Ctrl + w + l:从 NERDTree 切换到右侧窗口

4. NERDTree 特定命令

  • q:关闭 NERDTree 窗口
  • 重新打开::NERDTreeToggle:NERDTreeFocus

5. 我的常用配置(在 .vimrc 中)

vimrc

1
2
3
4
" 使用 Ctrl+n 切换 NERDTree
map <C-n> :NERDTreeToggle<CR>
" 自动切换焦点到文件
let NERDTreeQuitOnOpen = 1 " 打开文件后自动关闭 NERDTree

最简单的方式:直接按 Ctrl + w + w 即可在 NERDTree 和编辑窗口间切换。

l

Navidrome、Audiobookshelf 客户端推荐【自测】

https://makifx.com/boutique-software)

本人自建 音乐服(音海拾贝)| 有声书服(悦耳声阅)

群组链接:https://t.me/yinhai_chat

通知频道:https://t.me/yinhai_notify

【悦耳声阅】有声书分享频道:https://t.me/YueErFM

# Navidrome(音乐)

## IOS

DS Cloud
https://apps.apple.com/app/id6476057278

维克音乐

https://apps.apple.com/us/app/offline-music-player-wake/id6544783918?platform=iphone

LMP

https://apps.apple.com/us/app/lmp-a-better-music-player/id6451009326

音流
https://github.com/gitbobobo/StreamMusic

箭头音乐
https://cn.amcfy.com

Cinetry

https://github.com/gstory0404/Cinetry

LMP 音乐{TF}

https://testflight.apple.com/join/H0qG853j

## mac

DS Cloud
https://apps.apple.com/app/id6476057278

维克音乐

https://apps.apple.com/us/app/offline-music-player-wake/id6544783918?platform=iphone

FeiShin

https://github.com/jeffvli/feishin

Pure Music (棉花音乐)

https://github.com/pure-music/PureMusic/releases

Cinetry

https://github.com/gstory0404/Cinetry

## APTV

DS Cloud
https://apps.apple.com/app/id6476057278

维克音乐

https://apps.apple.com/us/app/offline-music-player-wake/id6544783918?platform=iphone

LMP

https://apps.apple.com/us/app/lmp-a-better-music-player/id6451009326

## Windows

FeiShin

https://github.com/jeffvli/feishin

音流
https://github.com/gitbobobo/StreamMusic

Pure Music (棉花音乐)

https://github.com/pure-music/PureMusic/releases

Cinetry

https://github.com/gstory0404/Cinetry

## Android

DS One(DS Cloud)

https://play.google.com/store/apps/details?id=com.cz.player.dsone

音流
https://github.com/gitbobobo/StreamMusic

tempus

https://github.com/eddyizm/tempus

tempo

https://github.com/CappielloAntonio/tempo/releases

箭头音乐
https://cn.amcfy.com

Cinetry

https://github.com/gstory0404/Cinetry

Symfonium

https://symfonium.app/

Pure Music (棉花音乐)

https://github.com/pure-music/PureMusic/releases

Musicfree(需配合插件使用)
https://github.com/CTZZG/MusicFree/releases 插件地址:

1
2
3
4
5
https://gitee.com/Rrance/WP/raw/master/音海拾贝.js




## Android TV

音流
https://github.com/gitbobobo/StreamMusic

Symfonium

https://symfonium.app/

Pure Music (棉花音乐)

https://github.com/pure-music/PureMusic/releases

## Linux

FeiShin

https://github.com/jeffvli/feishin

Pure Music (棉花音乐)

https://github.com/pure-music/PureMusic/releases

Cinetry

https://github.com/gstory0404/Cinetry

# Audiobookshelf(有声书)

##IOS 端

Audiobookshelf:
https://testflight.apple.com/join/wiic7QIW

Tonspur:

https://testflight.apple.com/join/E92V6bRM

Prologue:

https://testflight.apple.com/join/zTWS6ahB

Cinetry

https://github.com/gstory0404/Cinetry

希声:
https://apps.apple.com/cn/app/%E5%B8%8C%E5%A3%B0-%E4%B8%BA-audiobookshelf-%E6%89%93%E9%80%A0/id6754208326

AudioBooth:

https://testflight.apple.com/join/cAG6dVeN

dscloud:
https://apps.apple.com/app/id6476057278

SoundLeaf:
https://github.com/SoundLeaf/SoundLeafApp

ShelfPlayer:
https://apps.apple.com/us/app/shelfplayer/id6475221163

## 安卓

audiobookshelf:
https://play.google.com/store/apps/details?id=com.audiobookshelf.app

DS One(DS Cloud)

https://play.google.com/store/apps/details?id=com.cz.player.dsone

Cinetry

https://github.com/gstory0404/Cinetry

Lissen:
https://github.com/GrakovNe/lissen-android

LitLyric:
https://github.com/shane9b3/LitLyric---Beta

Kitzi:
https://github.com/bennybar/kitzi_abs_player

Buchable:
https://github.com/Vito0912/abs_flutter

## Windows

musicfree(需配合插件使用):

https://github.com/CTZZG/MusicFreeDesktop-1/releases

插件地址:

1
2
3
4
5
https://gitee.com/Rrance/WP/raw/master/audiobookshelf.js




Cinetry

https://github.com/gstory0404/Cinetry

Buchable:
https://github.com/Vito0912/abs_flutter

## MAC

Cinetry

https://github.com/gstory0404/Cinetry

希声:
https://apps.apple.com/cn/app/%E5%B8%8C%E5%A3%B0-%E4%B8%BA-audiobookshelf-%E6%89%93%E9%80%A0/id6754208326

l

WindowsPE和MacOS安装盘二合一制作方法

一、准备U盘

准备一个U盘,安装包一般大小都在12G以上,所以建议至少16G,或者直接使用移动硬盘。

插入U盘,打开Mac的磁盘工具

选择U盘-抹掉

  • 名称:随意

  • 格式:格式Mac OS扩展(日志式)

  • 方案:GUID分区图

notion image

抹掉完成之后,再对U盘进行分区用于制作Mac OS安装盘,点击+号,增加新的分区

分区信息:

  • 名称:UOS

  • 格式:Mac OS扩展(日志式)

  • 大小:必须大于你的安装包,我这里直接分了16G

新增分区之后,就可以准备制作Mac OS安装盘了。

二、制作Mac OS安装盘

下载好的Mac OS安装包默认放在应用程序中按照一下方式进行安装盘制作:

  1. 敲“sudo”(无引号)
  2. 空格
  3. 拖拽文件“createinstallmedia”到终端(文件位置在“安装macOS Ventura.app”-右键-显示包文件-Contents-Resources里)
  4. 敲“--volume”(无引号)
  5. 空格
  6. 从桌面把你之前插入的U盘的图标拖进终端(我这里U盘名字为UOS)
  7. 敲“--nointeraction”(无引号)
  8. 回车
  9. 输入密码(密码不显示)
  10. 回车,静待安装盘建立完成。

以上步骤综合为这么一串代码,请根据自己系统版本和U盘名字对应修改:

1
sudo /Applications/Install\ macOS\ Ventura.app/Contents/Resources/createinstallmedia --volume /Volumes/UOS --nointeraction

整个制作过程如下,到最后显示出这个就表示安装盘制作完成。

1
2
3
4
5
6
7
8
sudo /Applications/Install\ macOS\ Ventura.app/Contents/Resources/createinstallmedia --volume /Volumes/Install\ macOS\ Ventura --nointeraction
Password:
Erasing disk: 0%... 10%... 20%... 30%... 100%
Copying essential files...
Copying the macOS RecoveryOS...
Making disk bootable...
Copying to disk: 0%... 10%... 20%... 30%... 40%... 50%... 60%... 70%... 80%... 90%... 100%
Install media now available at "/Volumes/Install macOS Ventura"

到这里,如果你的引导没有屏蔽多余的启动项,在启动中已经可以看到这个安装盘了,选择既可以使用。

三、加入黑苹果EFI

安装盘制作完成之后,为了可以以后将U盘作为启动盘从BIOS的启动界面启动安装盘,就需要将自己配置好的引导文件EFI文件放到EFI分区中。

使用工具挂载EFI分区,比如用OpenCore Configurator 或者Hackintool 或者 OCAuxiliaryTools都可以,挂载U盘的EFI分区之后,将自己的配置好的EFI文件放进去,就成为了自己黑苹果的专用安装盘了。

那如果需要用于不同的机器来安装黑苹果,或者调试的时候修改EFI文件,总是需要挂载EFI分区,就比较麻烦了。

我们可以在引导分区之外,新建一个公开的引导分区。

打开磁盘工具,选择U盘,点分区,选择之前制作Mac OS安装盘剩下没有使用的部分,点+号,增加新的分区。

分区信息:

  • 名称:MAC_EFI(随意,不能有中文)

  • 格式:MS-DOS (FAT32)

  • 大小:1G(引导文件很小,一般大于200M即可)

notion image

这个新建的引导分区同样也可以放黑苹果的EFI引导文件,而且是无需挂载的,适合频繁修改调试引导配置和更换文件。

四、制作Windows PE

按照上面的方式同样新增一个分区

分区信息:

  • 名称:WINPE(随意,同样不能有中文)

  • 格式:MS-DOS (FAT32)

  • 大小:大于你的PE文件

新增分区完成之后,这里就需要一个支持UEFI启动的WinPE,一般PE主流格式都是exe格式,不符合我们的需要,所以需要在Window系统下先将PE制作成iso文件。

这里推荐老毛桃PE,功能比较多,可以联网,适合大多数PE需求。

然后在Mac下,打开iso文件,将里面的文件复制到新增的WINPE分区中。

结束

这样,一个Mac OS的安装盘和PE盘二合一的U盘就制作好了,剩下还有多余空间,可以当做正常存储U盘使用。

l