1

Tue 23 September 2025

禁用root使用ssh密码登陆

一般情况下,主机是允许root用户远程登陆的。而root用户的名字一般就是root。如果攻击者知道你VPS的ip地址、用户名(默认有一个root可用)和ssh端口号(默认22),那么保护你电脑的就只有你的用户密码了。如果你的root密码被破解了(比如你用了一些123456的弱密码),那你的VPS就任人鱼肉了! 如果你只是非root用户密码被破解,那么root用户可以暂时保护一下你。

怎么禁用root用户远程ssh登陆。

首先,我们编辑ssh程序的设置文件:

sudo vim /etc/ssh/sshd_config

在界面中应该在类似的内容:

#Port 22
#AddressFamily any
AddressFamily inet
#ListenAddress 0.0.0.0
#ListenAddress ::

#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key

# Ciphers and keying
#RekeyLimit default none

# Logging
#SyslogFacility AUTH
#LogLevel INFO

# Authentication:

#LoginGraceTime 2m
PermitRootLogin no  (就是这里!!)
# PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

找到PermitRootLogin参数,它的值yes改成no。要注意去掉前面的#号。:wq退出保存。

重启ssh服务后生效:

sudo service sshd restart

切记不要关闭旧的Shell。新开一个Shell,用你刚刚的非root用户登陆Shell。成功后再关闭旧的Shell窗口。


更改默认的ssh端口

默认的ssh端口号是22。SSH 的端口一般建议把它改成一个大于1024小于65535的整数。比如,我想改成1234。

在你的VPS后台防火墙里先打开1234端口。有些VPS后台可能没有防火墙,就可以不用管。

下面,简单地用ufw将新的ssh端口1234打开:

sudo ufw allow 1234/tcp comment 'SSH' # 1234记得换上你自己的端口啦

然后像修改root访问ssh的权限一样打开ssh的设置文件:

sudo vim /etc/ssh/sshd_config

在开头不远处就一个#Port 22的字样。#号在多数情况下是注释号,表明后面的内容不生效。

将#号去除,然后22改为1234:

Port 1234

重启ssh服务生效

sudo service sshd restart

此时不要关闭终端,新开一个窗口进行用新端口号、非root用户进行ssh登陆测试。如果成功,就可以关闭旧的Shell了。OK,安全指数+1,哈哈!新端口成功后,旧的22端口已经没有用。可以关掉了。

更多ssh使用的技巧可参考我的文章《Linux基础 实用Linux命令一览表》 (ฅ´ω`ฅ) :


禁ping

仅Ubuntu/ufw有效。其它Linux发行版可自行找类似教程

就算你隐藏了公网ip,别人通过穷举法还是有可能发现你的ip。所以还是设置自己的主机禁ping,伪装成是一台不能使用的机器吧。这是一种非常强的保护特征,因为扫IP应用一般优先攻击可以ping通的ip。

很简单,只要

sudo vim /etc/ufw/before.rules

外面到本地的访问是由INPUT控制的,其中echo-request就是ping所采用的类型。因此,只要找到ok icmp codes for INPUT下面的记录:

# ok icmp codes for INPUT
-A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT
-A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT
-A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT
# -A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT
-A ufw-before-input -p icmp --icmp-type echo-request -j DROP

如上面的代码框所示,找到含有echo-request,把ACCEPT改成DROP。这样外面的机器就无法ping通你的VPS了!

可重新加载一下ufw以使改动生效(不知道是否是必需):

sudo ufw reload

你可以用windows个人电脑或者Linux的主机ping一下你的VPS;或者直接使用Chinaz来ping:

msedge_CFIrR2UEWs

成功设置时会出现“满江红”现象,提示Ping不可用。


Fail2ban防扫描和暴力破解

Fail2ban是防止后台暴力扫描的流行保护软件之一。推荐安装:

安装Fail2ban:

sudo apt update && sudo apt install fail2ban -y 

查看一下状态:

sudo fail2ban-client status

有时候,会出现类似报错:

[5583]: ERROR   Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?

这提示fail2ban并未启动。你可以用systemctl status fail2ban命令进一步核实。如果确实未启动,只要:

sudo systemctl start fail2ban

即可启动fail2ban。

值得一提的是,fail2ban默认ssh是被监控的:

Status
|- Number of jail:      1
`- Jail list:   sshd

你可以在/etc/fail2ban/filter.d里查看各种应用的默认配置。我看sshd.conf的设置是挺强的,大家用默认的配置文件问题不大:

MobaXterm1_CHS_hSgLGAvM6n

我还建议大家设置一下/etc/fail2ban/jail.local文件(有些系统是/etc/fail2ban/jail.conf)中的sshd选项。比如,我的选项是:

MobaXterm1_CHS_p6WxCplfcN

该配置是一种比较流行的方案。它将启用SSH监视,并配置 fail2ban 以在5次无效登录尝试之后封禁IP地址,封禁时间为1小时(3600s)。虽然不是永久封禁,但已经是一种很强的防护了,恶意IP不太可能持续地进行攻击。

查看工作状态:

# 查看fail2ban的工作状态
sudo fail2ban-client status 

# 查看sshd监视的状态
sudo fail2ban-client status sshd

# 检查当前封禁的IP地址列表
sudo fail2ban-client status sshd | grep "Banned IP list"

/var/log/fail2ban.log保存了拦截日志。查看全部记录

less /var/log/fail2ban.log

如果有攻击记录,内容类似:

查看实时拦截:

tail -f -n 30 /var/log/fail2ban.log

这里的内容类似《NAS系列 PVE基本设置 》的“Fail2ban防扫描和暴力破解”小节。更多高级用法以后再研究。


清理日志

Ubuntu将var目录挂载到新硬盘

如果你的VPS运行时间很久的话,日志管理对于节省空间是很重要的。这里有个文章可以简单参考一下。Docker的日志管理可见:《Docker系列 配置Docker全局环境》。

# 删除目前所有的系统日志
sudo rm -rf /var/log/journal/*

# 查看日志管理软件是否存在
which journalctl # /bin/journalctl

# 日志保存的最长时间是1周
sudo journalctl --vacuum-time=1w

# 日志保存的最大容量是300M
sudo journalctl --vacuum-size=300M

清理日志的自动化脚本

可将Linux系统日志所占空间控制到一个较低的水平Debian/Ubuntu均适用(日志保存在/var/log里的系统原则上均适用),CentOS未测试

先检查一下依赖:

# 检查依赖,没有的话要自己用apt-get安装
which xargs # /usr/bin/xargs
which tee # /usr/bin/tee

新建一个文件,比如clean_system_log.sh:

vim ~/clean_system_log.sh

添加以下内容(按需注释某些内容即可):

#! /bin/bash

# 清除遗留在/var/cache中的已取回的包文件的本地仓库。它清除的目录是 /var/cache/apt/archives/ 和 /var/cache/apt/archives/partial/
apt-get clean

# clean journals
rm -rf /var/log/journal/*

# clean .gz logs
# ls -hlS /var/log/ | grep -E '.gz'
ls /var/log/ | grep -E '.gz' | xargs -i rm -r /var/log/{}

# Echo nothing to logs
# 这里并未将所有的log去除,只是将那些体积比较大的log去除。强迫症患者可以自己添加一些项目进去,比如tallylog/faillog之类的。另外,auth可以查看登陆记录,在意的话可以不删除。
ls /var/log/ | grep -E 'syslog|messages|user|daemon|btmp|auth|mail' | xargs -i tee /var/log/{}

# Mail 
ls /var/mail | xargs -i tee /var/mail/{}

# Report
echo 'Clean all system logs!'

进行权限和软链接映射:

sudo chmod +x ~/clean_system_log.sh && sudo ln -s `echo ~`/clean_system_log.sh /usr/bin/clean_system_log

最后检查一下是否成功添加为环境变量:

which clean_system_log  # /usr/bin/clean_system_log

输出/usr/bin/clean_system_log的字样说明已经成功。

使用时,只要运行sudo clean_system_log即可。你也可以添加到root用户的cron任务里自动地定期清理系统日志,比如:

# clean system logs every 10-day
* * */10 * * root /usr/bin/clean_system_log >/dev/null 2>&1

北京时间

中国用户一般是使用北京时间,所以我们最好修改一下时间以适应自己的习惯。

查看时间:

# 查看时间
date

如果要修改时区,可运行下面命令:

# 为当前用户
tzselect 

按提示操作即可。如果为当前用户设置选定时区,可用:

sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

如果要为所有用户设置选定时区,则编辑下面文件:

# 为所有用户
sudo vim /etc/profile

添加以下记录:

TZ='Asia/Shanghai'; export TZ
source /etc/profile && source ~/.bashrc

中英文界面切换

如果想修改默认显示语言,可以修改下面文件:

# 修改文件
sudo vim /etc/default/locale

英文一般填写以下内容:

# 英文
LANG="en_US.UTF-8"
LANGUAGE="en_US:en"

中文一般填写以下内容:

# 中文
LANG="zh_CN.UTF-8"
LANGUAGE="zh_CN:zh"

重启系统生效。

一般还是建议英文吧,慢慢就会习惯了。查bug也方便一些不是? 文件权限

非常重要!非常重要!非常重要!

我们之前也有了解过一些了,就是类似这种-rw-r--r--。

强烈推荐学习参考资料Linux chmod命令!

你也会知道1,2,4和x,w,r的关系以及他们的排列组合/和在改变文件权限上的灵活运用。自己创建一个文件来试玩吧!这个必须要掌握!

这里给一些常用的用户名(组):

www-data:www-data的id是33:33
root:root的id是0:0

参考资料:

Linux chmod命令

find:搜索文件

find . -name *.gcno -o -name *.gcda # | xargs rm -f

tree:文件列表

更多细节请参考Linux tree命令 | 菜鸟教程 (runoob.com)或运行tree --help查看。

tree是非常好用的文件层级结构查看命令。除了tree -h,比较实用的形式有:

形式1:tree -h -L 2
├── [   4]  private
│   ├── [   3]  backup
│   └── [   5]  lucky-huangwb8
└── [   8]  share
    ├── [   8]  docker
    ├── [   5]  dump
    ├── [   2]  images
    ├── [   2]  private
    ├── [   2]  snippets
    └── [   4]  template
形式2:tree -augfCD -L 2
.
├── [bensz    bensz    Feb 14 13:56]  ./private
│   ├── [bensz    users    Feb 14 13:56]  ./private/backup
│   └── [bensz    users    Feb 17 23:39]  ./private/lucky-huangwb8
└── [bensz    bensz    Feb 17 19:56]  ./share
    ├── [bensz    bensz    Feb 17 23:14]  ./share/docker
    ├── [bensz    bensz    Feb 18 07:50]  ./share/dump
    ├── [bensz    bensz    Feb 14 12:24]  ./share/images
    ├── [bensz    bensz    Feb 14 12:24]  ./share/private
    ├── [bensz    bensz    Feb 14 12:24]  ./share/snippets
    ├── [bensz    bensz    Feb 17 23:13]  ./share/softwares
    └── [bensz    bensz    Feb 14 12:24]  ./share/template

SSH之防自动断开

SSH登录Linux长时间不操作就会自动断开问题 – 腾讯云开发者社区-腾讯云

因为RackNerd的机器线路不好,开这个可以大大降低日常使用时的断线概率,推荐使用!

有时候,连接ssh时如果没有活动(比如打代码或开htop监测),ssh连接很快就断开,这种现象经常会给我们带来困扰。它在《Linux基础 个人VPS安全》的禁Ping操作后更加常见。因此,我建议将ssh的默认连接时间延长,以防连接中的Shell频繁掉线。具体作法如下:

在该VPS的Shell里输入:

vi /etc/ssh/sshd_config

找到下面2个参数:

#ClientAliveInterval 0
#ClientAliveCountMax 3

去掉前面的注释,并修改值为:

ClientAliveInterval 15
ClientAliveCountMax 6

ClientAliveInterval指定了服务器端向客户端请求响应的时间间隔, 默认是0, 不发送请求;改为15秒,则15秒发送一次请求,客户端自动响应,这样就保持长连接不会自动断开了。你也可以改一个比15更小的正整数。

ClientAliveCountMax:指定了服务器发出请求后客户端没有响应的次数达到一定值, 就会自动断开。默认值为3,这里使用6次,以阻碍ssh过早地断开。

重启sshd服务,使配置生效:

service sshd restart

SSH之访问其它主机

通过ssh访问另一台主机。不过我们平时都有MobaXterm、FinalShell之类的管理软件,感觉这个功能不是很需要。

ssh访问另一台主机

ssh [email protected]

ssh根据特定的端口访问,比如1234

ssh [email protected] -p 1234

参考资料:

Linux 下你所不知道的 7 个 SSH 命令用法

SSH之VPS相互信任

ssh公私钥是一个非常实用的功能,特别是你的VPS有很多用户时。建议基于bitwarden来管理VPS和公私钥(即附件功能)。

基本的原理,就是把你的本地主机用户的ssh公匙文件复制到远程主机用户的~/.ssh/authorized_keys文件中。

比如,对于某用户,在本地VPS运行此命令:

ssh-keygen -t rsa

会有三提示输入,第一次是生成的文件名字,第二次和第三次是输入密码,可以直接回车三次则不设置密码。我觉得家用级别可以不用专门设置密码,直接空白即可。最后,用户目录~/.ssh/会产生两个文件:id_rsa(私钥)和id_rsa.pub(公钥)。

当然,一种更成熟的做法是:

ssh-keygen -t rsa -C "VPS1.Pubkey-to-VPS2"

这样产生的公钥和私钥就会有名字。这种做法建议在多人使用VPS的情况下使用,这样你就知道该VPS信任的公钥对应哪一个用户。

之后,你可以通过这个命令将本地公钥添加到远程服务器:

ssh-copy-id -p <远程服务器ssh端口> <远程服务器用户名>@<远程服务器IP或域名>

ssh-copy-id -p 22 [email protected]

按提示输入密码即可。之后,你也可以连接到远程服务器里看看公钥是否存在:

ssh -p <远程服务器用户名>@<远程服务器IP或域名>

ssh -p 22 [email protected] cat ~/.ssh/authorized_keys # 内容会包括本地服务器的公钥

当然,你也可以直接登陆远程主机,然后在远程主机的root用户主目录下的.ssh目录内新建(比如通过Vim)一个名为authorized_keys的文件。将本地主机上的id_rsa.pub的内容拷贝到该文件内。这样在本地主机上使用scp命令复制文件到远程主机上将不提示输入密码了,可以直接复制;该本地主机登陆该远程主机时也不需要密码。

你也可以为VPS配置多个ssh公钥。很简单,直接在该VPS的.ssh目录中的authorized_keys文件中添加你想要信任的主机的id_rsa.pub的内容即可!

我觉得ssh-keygen可以好好的利用,比如为特定主机设定一定期限的公钥。自己ssh-keygen --help摸索吧!

这个功能在VSCode作为终端工具的时候很常用,可以看一下我的博文《VSCode作为终端工具替代FinalShell和MobaXterm》。

参考资料:

配置多个SSH公钥流程
ubuntu 添加多个ssh公钥和私钥
使用公钥+私钥:MobaXterm设置无密码登录:使用MobaXterm的小伙伴可以按此教程设置,实测可行。

SSH之登陆黑/白名单

参考《使用hosts.allow和hosts.deny实现简单的防火墙》。

如果你用登陆ssh的ip比较固定(比如仅在某局域网内),可以设置一个关于登陆IP的白名单,减少安全问题。

对于Ubuntu/Debian系统,可以这样查看ssh登陆信息:

检查最近失败登陆次数

tail -f -n 30 /var/log/auth.log | grep -E 'sshd' | grep -E 'preauth|error|invalid'

如果你的服务器正在被攻击,结果类似这样:

MobaXterm1_CHS_RDoEZNZZhz

你也可以查看历史攻击记录:

查看异常状态

cat /var/log/auth.log | grep -E 'sshd' | grep -E 'preauth|error|invalid' | less

那么应该如何应对这种ssh暴力破解攻击呢?以下是几点建议:

可以根据《Linux基础 基于密钥对的SSH远程登陆》教程设置禁用root密码登陆。
对于普通用户,也可以禁用密码验证,或者根据《Docker系列 搭建密码管理应用bitwarden》教程进行强密码设置和记录。
基于hosts.deny封禁特定IP。用root权限编辑一个文件:

vim /etc/hosts.deny

添加某些规则,基本格式是:daemon, daemon, ...: client, client, ...: option。比如:

允许192.168.1.18通过SSH登录

sshd:192.168.1.18:allow # 禁止可用deny

允许192.168.1.*通过SSH登录

sshd:192.168.1.:allow

ALL表示除了上面设置的IP。其他全部拒绝SSH登录

sshd:ALL

这是我遇到的一些SSH暴力破解黑名单有:

非常恶意,存在用户名试探和暴力破解

sshd:46.101.190.103:deny sshd:128.199.135.7:deny sshd:143.42.58.165:deny sshd:139.144.183.199:deny sshd:133.242.173.204:deny sshd:75.119.139.181:deny

最后,用root权限重启ssh服务:

service sshd restart

注意,这时不要关闭旧的Shell。重新登陆,确定自己现在的设备可以成功登陆后,再关掉旧的Shell。 SSH之登陆失败次数限制

Beta版本,暂未实现。

确定文件/etc/pam.d/sshd是否存在

ls /etc/pam.d/sshd # /etc/pam.d/sshd

确定pam_tally2.so是否存在

find /lib* -iname "pam_tally2.so"

/lib/x86_64-linux-gnu/security/pam_tally2.so

编辑系统vim /etc/pam.d/common-auth (有些系统是system-auth)文件,在 auth 字段所在的那一部分策 略下面添加如下策略参数:

普通帐户和root的帐户登录连续5次失败,就统一锁定300秒, 300秒后可以解锁。如果不想限制root 帐户,可以把 even_deny_root、root_unlock_time这两个参数去掉

auth required pam_tally2.so onerr=fail deny=5 unlock_time=300 even_deny_root root_unlock_time=300

故意输错密码,tail -f /var/log/auth.log(有些系统是/var/log/secure)会有以下信息:

Oct 31 07:30:01 openmediavault sshd[1573140]: error: maximum authentication attempts exceeded for XXX from 192.168.1.1 port 51655 ssh2 [preauth] Oct 31 07:30:01 openmediavault sshd[1573140]: Disconnecting authenticating user XXX 192.168.1.1 port 51655: Too many authentication failures [preauth] Oct 31 07:30:01 openmediavault sshd[1573140]: PAM 4 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.1.1 user=XXX Oct 31 07:30:01 openmediavault sshd[1573140]: PAM service(sshd) ignoring max retries; 5 > 3

上面只是限制了从终端登陆,如果想限制ssh远程的话, 要改的是vim /etc/pam.d/sshd这个文件,添加的内容跟上面一样!

检查最近失败登陆次数

tail -f -n 30 /var/log/auth.log | grep -E 'sshd' | grep -E 'preauth|error|invalid'

查看异常状态

cat /var/log/auth.log | grep -E 'sshd' | grep -E 'preauth|error|invalid' | less

tail -f /var/log/auth.log | grep -E 'sshd' | grep -E 'preauth|error'

显示已登录的是连接到服务器的最大数量的IP的列表

netstat -anp |grep -E 'tcp|udp' |awk '{print $20}' |cut -d: -f1 |sort uniq -c |sort -n

检查当前你的服务器活跃的连接信息

netstat -n | grep :80 | wc -l

参考:

Linux登录失败处理功能

[设置Linux用户连续N次输入错误密码进行登陆时,自动锁定X分钟](

SSH之跨主机复制 rsync

速度比较快、稳定。推荐! 建议先完成“SSH之VPS相互信任”的相关设置。

我个人推荐通过rsync将本地文件单向复制到远程主机。需要两台主机都带有rsync。一般都是默认有的,可以用rsync –version检查两台主机。 这里是一些rsync的用法,大家可以尝试一下:

方法1

nohup rsync -av -P -e --partial 'ssh -p <远程主机的ssh端口号>' <本地文件> <远程主机的用户名>@<远程主机ip>:<远程主机目录> > ~/rsync.log 2>&1 &

方法2

nohup rsync -av -P -e --partial 'ssh -p <远程主机的ssh端口号>' <本地文件> <远程主机的用户名>@<远程主机ip>:<远程主机目录>

Ctrl+z 悬挂

bg # 放入后台 disown %1# 忽略第1个作业的HUP信号

方法3:基于screen

screen -dmS rsync_task rsync -av -P -e --partial 'ssh -p <远程主机的ssh端口号>' <本地文件> <远程主机的用户名>@<远程主机ip>:<远程主机目录> > ~/rsync.log

参数说明如下:

-a:这是 --archive 的简写形式,用于执行归档模式的复制。归档模式会保留文件的所有属性,包括所有权、权限、时间戳等。它还会递归地复制目录,并保持目录结构的完整性。
-v:这是 --verbose 的简写形式,用于显示详细的输出信息。当使用 -v 选项时,rsync 会在复制过程中输出更多的信息,以便你可以看到正在进行的操作。
-P:这是 --progress 的简写形式,用于显示复制进度。当使用 -P 选项时,rsync 会在复制过程中显示进度条和估计剩余时间。
--partial:保证断点续传。

当我们将某目录从服务器A转移至B时,某些应用(比如wordpress)文件可能会很多,一直开着窗口转移是比较危险的;最好是让备份命令在后台运行,即便关闭当前窗口也不影响进程。

文件夹的格式要多加注意。比如,我的chevereto的docker根目录是/docker/chevereto。我建议,根目录和目标目录都要写/docker/chevereto/,最后的/不要忘记。这样可以实现精确的拷贝。

我之前曾多次使用方法2,挺稳的;其它方法大家感兴趣可以探索一下。由于认证问题,nohup或screen可能会失败。因此,最好先做好“SSH之VPS相互信任”的准备工作。小伙伴们有什么好的经验,也欢迎在评论区留言喔! scp

可行但不推荐

scp这个命令用于在两台VPS间通过ssh协议传输文件(夹)。比如你要备份本地docker目录到新主机,就很管用了!苯苯最近就操作过。如果你的远程主机通过公钥信任了本地主机,你传输的时候还不需要密码!

远程复制文件夹

scp -r -p -P <远程主机的ssh端口号> <本地目录> <远程主机的用户名>@<远程主机ip>:<远程主机目录>

远程复制文件。不用-r

scp -p -P <远程主机的ssh端口号> <本地文件> <远程主机的用户名>@<远程主机ip>:<远程主机目录>

远程主机用的22端口,还不用指定-P

scp -p <本地文件> <远程主机的用户名>@<远程主机ip>:<远程主机目录>

参考资料:

scp在Linux主机之间复制文件/目录不用输入密码

https://blog.csdn.net/m0_37886429/article/details/79641551

trash-cli

通过apt-get就可以安装了

sudo apt-get install trash-cli

自己试试看:

删除文件和目录(仅放入回收站中),也可以trash

trash-put

列出被删除了的文件和目录

trash-list

从回收站中恢复文件或目录 trash.

trash-restore

删除回收站中的文件

trash-rm

清空回收站

trash-empty

还蛮好用的。不过我平时还是直接rm的。习惯了(~ ̄▽ ̄)~

参考资料:

Linux下设置回收站Trash

硬链接型复制

比如,将A通过硬链接的复制到B,这样可以节省磁盘空间。具体方法是:

cp -lR A/. B/

这个命令行选项的含义是:

-l:表示创建硬链接而不是复制文件。
-R:表示递归地复制目录及其内容。

实时查看状态

如果你想看某个命令的持续变化状态,可以这样

watch -n2 <你的命令>

这个操作在NAS管理里就特别常用,VPS中用得不多。比如,我想每2秒钟查看zpool status -v nas这个命令的输出变化,可以这样:

watch -n2 zpool status -v nas

输出结果如下(01:56:08 to go的部分是有变化的):

MobaXterm1_CHS_U9oNkzee4Q

watch对于那种想实时了解运行状态的强迫症患者来说是简直就是个神器,自己体会吧(~ ̄▽ ̄)~ 进程操作

基本命令

ps # 观察当前用户所有进程 ps ux ps aux # a-包括其它用户的进程;u-进程的详细内容;x-不是通过终端控制的进程 top # 动态观察CPU和内存占用比较高的进程。通过q退出 kill PID # 通过进程的PID关闭。不要随便结束以root身份启动的进程 kill -9 PID # 用kill无法进行的时候,强行终止

这里有个我觉得比较好用的ps:

ps -ef | grep -E 'multiqc|stats'

使用时,将multiqc|stats换成你要找的程序的名字。

这里有个我觉得比较好用的kill:

kill -9 ps -ef | grep -E 'java' | awk '{print $2}'

使用时,将这个java改成你要结束的进程名字(某段特征也行) 查看本地网络

ifconfig|less

大多数情况下,docker0和echXXX这两个网络的信息对我们比较重要。 查看进程网速占用

查看自己的网卡

ifconfig|grep -E 'eth|enp' # 很多人都是eth0;

安装nethogs

sudo apt-get install nethogs

通过nethogs可以实时查看流量消耗:

查看网速

sudo nethogs eth0 # sudo nethogs enp0s25

它是一个动态页面:

image-20220421084114639 查看硬件信息

之前我们学习的neofetch也可以看。这里介绍其它的一些命令:

查看基本信息

lshw -short|less

查看详细信息

lshw|less

查看系统信息

uname -a

sudo dmidecode | grep "System Information" -A9 | egrep "Manufacturer|Product|Serial" # 查看服务器型号、序列号

查看BIOS信息

sudo dmidecode -t bios

# 查看内存槽及内存条

sudo dmidecode -t memory | head -45 | tail -23

查看网卡信息

sudo dmesg | grep -i Ethernet

查看pci信息,即主板所有硬件槽信息

lspci | head -10

查看显卡

sudo lspci | grep -i vga

这里还可以推荐一个工具,叫做hardinfo。你可以用apt-get来安装它:

sudo apt-get install hardinfo

然后使用方法也蛮简单的,就是:

sudo hardinfo | less

之所以用less,是因为它会输出一大堆东西,用less命令观察会比较方便。f向前翻页,b向后翻页,q退出。

扩展阅读:

Linux 基础-查看 cpu、内存和环境等信息

查看公网ip

curl ifconfig.me

crontab执行定期任务

编辑文件内容:

另一种比较流行的命令是: crontab -e

vim /etc/crontab

具体用法我不多说了。自己看参考资料吧!口诀:分时日月周。

一种比较快捷的方法是直接在Shell里输入类似命令:

echo -e "\n# 关于这条cron记录的说明\n0 */1 * * * root /usr/bin/bash /root/disk_warning.sh" >> /etc/crontab

结果类似于:

NVIDIA_Share_1ZmNKIOLy3

cron还可以用于开机自启。比如,如果开机想要执行cd /var/www/node/ && npm run start,可以这样:

开机启动

@reboot cd /var/www/node/ && npm run start

延迟10秒启动

@reboot sleep 10 && cd /var/www/node/ && npm run start

参考资料:

Linux crontab 命令
第七星尘的独立博客 | linux利用cron定时服务来实现开机启动某些应用

压缩与解压

压缩为tar.gz

tar zvfc test.tar.gz ~/test/

解压为tar.gz

tar zvfx test.tar.gz

解压zip

unzip test.zip

直接查看文本类的tar.gz

zcat text.tar.gz|less

一般这几个比较常用。还有一些gzip啥,你用到的时候自己Google一下。 Conda

如果你不知道什么是conda或者anaconda,你百度一下Ubuntu安装conda。

它是一个软件版本控制的平台。利用conda,你可以在同一台电脑中配置无数个不同的环境(多个软件不同版本的搭配)。你可能不知道我在说什么。总之,你知道它是一个生产力工具就行了。

你在玩Docker的时候并不需要conda。你只要知道它,以后有需要的话就使用它吧!

如果你是生物信息学、人工智能或者是Python的学习者,这个东西我不说你也知道。如果你了解过python2和python3那些事,相信你也会不由自主地喜欢上conda的。

言尽于此,自己体会! FTP

仅限Ubuntu。暂未完成测试。

sudo apt-get install vsftpd

lsof -i | grep ftp

service pure-ftpd stop # 停止旧的ftp服务器

systemctl restart vsftpd

systemctl status vsftpd

小结

这一回写完,我们的Linux基础也觉得差不多了。以后要精进,还是要靠多查书、多检索、多试错。苯苯也在慢慢地学习中呢!

而且有了这些基础,相信你已经迫不及待地想要了解docker怎么使用了!我也是呀!

在本博客中,我会介绍那些最经典的Docker应用。毫不夸张地说,这些Docker应用彻底地改变了我的生活。相信你以后也会有同感的!


个人建议

iPhone 18相比iPhone 17的提升应该不会很大,想换机的小伙伴入手iPhone 17是一个比较安全的选择。
iPhone 18 Pro Max在续航上是牙膏挤爆的一代,会与iPhone 17 Pro Max拉开明显代差,这是由多种因素决定的:N2工艺A20 Pro;更先进的LTPO+面板;C2基带对能耗的优化。如果嫌弃iPhone 17 Pro Max续航不大并且手握15/16系列的小伙伴可以观望;更老机型的小伙伴入手问题不大。
今年有换机需求的安卓小伙伴,原本在安卓生态里的直接升级原厂设备即可;入苹果生态要慎重。

iPhone用户换iPhone 17系列 Pro版本

以Pro Max为例

我目前使用的iPhone 15 Pro Max有这些缺点:

容易发热:据说与A17 Pro芯片的设计缺陷有关。就是看看B站都容易发热,这一点和我的Oppo K12s 5G形成鲜明对比。拍照/拍视频的时间稍微久一点容易降频警告。
容易杀后台:升级IOS 26之后这个问题很明显。老实说,我感觉IOS 26对内存的消耗比CPU性能的消耗更加明显。
续航焦虑:上班时经常用magsafe,晚上回家的电量可以保持60-70%。如果没有magsafe,就是浏览网页刷一下信息流,晚上回家很容易到25%左右。

这里我罗列了iPhone 17 pro max与前2代相比的情况如下表所示。iPhone 17 pro max的升级点很好地解决了这些痛点;对我而言,只要以后Apple AI的运行不要太过占据内存,这台机器可以钉个5年以上没啥问题。由于我还是希望在未来玩一下iPhone的影像,再加上对续航的迷之执着、对手感的不敏感,Pro Max机型仍然是我的首选。 参数 15 Pro Max 16 Pro Max 17 Pro Max 值得升级17 Pro Max的理由 屏幕尺寸 6.7英寸 6.9英寸 6.9英寸 🔥 比15大0.2英寸,视觉体验升级 刷新率 120Hz ProMotion 120Hz ProMotion 120Hz ProMotion 全系保持顶级 屏幕亮度 2000尼特(户外) 2000尼特(户外) 3000尼特(户外) 🔥 户外亮度提升50%,强光下清晰可见 处理器 A17 Pro(第一代3nm) A18 Pro(第二代3nm) A19 Pro芯片 🔥 最新一代处理器,性能领先两代 内存 8GB 8GB 12GB 🔥 内存提升50%,AI任务和多任务更流畅 散热系统 普通散热 普通散热 VC均热板散热 🔥 首次采用均热板,游戏性能更稳定 后置相机系统 48MP主摄+12MP超广角+12MP长焦(5倍) 48MP主摄+48MP超广角+12MP长焦(5倍) 48MP主摄+48MP超广角+48MP长焦(8倍) 🔥 全48MP三摄+8倍变焦,拍摄能力全面提升 前置相机 12MP TrueDepth 12MP TrueDepth 18MP Center Stage 🔥 像素提升50%,自拍效果显著改善 续航时间 29小时视频播放 33小时视频播放 37小时视频播放 🔥 比15 Pro Max多8小时,史上最长续航 快充功率 27W有线充电 27W有线充电 40W有线充电 🔥 充电速度提升48%,20分钟充50% 无线充电 15W MagSafe 15W MagSafe 15W MagSafe 标准MagSafe充电 存储规格 256GB/512GB/1TB 256GB/512GB/1TB 256GB/512GB/1TB/2TB 🔥 首次支持2TB,专业用户首选 机身重量 221g 227g 约230g 略有增加但功能大幅提升 设计升级 钛合金边框 5级钛合金边框 锻造铝金属一体成型 🔥 全新材质+横向摄像头设计 AI功能 Apple Intelligence(基础) Apple Intelligence(完整) Apple Intelligence(增强) 🔥 最完整的AI体验,12GB内存加持 起售价格 9999元(已降价7000+) 9999元 9999元 🔥 加量不加价,性价比突出 标准版

这代标准版可谓是牙膏挤爆了: 参数 15 标准版 16 标准版 17 标准版 值得升级17标准版的理由 屏幕尺寸 6.1英寸 6.1英寸 6.3英寸 🔥 屏幕更大,显示内容更多 刷新率 60Hz 固定刷新率 60Hz 固定刷新率 1-120Hz ProMotion 🔥 历史性升级!首次支持120Hz高刷+常显 处理器 A16 仿生芯片(4nm) A18 芯片(3nm N3E) A19 芯片(3nm N3P) 🔥 最新一代处理器,性能领先两代 内存 6GB 8GB 8GB 🔥 内存提升33%(相比15),AI功能完整支持 后置相机系统 48MP主摄+12MP超广角 48MP主摄+12MP超广角 48MP主摄+48MP超广角(增强夜景) 夜景拍摄能力显著提升 前置相机 12MP TrueDepth 12MP TrueDepth 18MP Center Stage 🔥 像素提升50%,支持横屏自拍不损失像素 续航时间 20小时视频播放 22小时视频播放 30小时视频播放 🔥 比15多10小时,比16多8小时,史上最长续航 快充功率 20W有线充电 20W有线充电 增强快充(20分钟50%) 🔥 充电效率大幅提升 无线通信 Wi-Fi 6 + 蓝牙5.3 Wi-Fi 6 + 蓝牙5.3 Wi-Fi 7 + 蓝牙6 + Thread 🔥 自研N1芯片,网络速度和稳定性提升 起步存储 128GB 128GB 256GB 🔥 存储翻倍!彻底解决存储焦虑 Always-On显示 不支持 不支持 支持常亮显示 🔥 首次支持,无需唤醒查看信息 相机控制按键 无 支持相机控制按键 支持相机控制按键 物理按键快捷拍摄 AI功能 基础Apple Intelligence 完整Apple Intelligence 完整Apple Intelligence + Visual Intelligence 🔥 AI能力最强,加入视觉智能功能 材质工艺 铝金属边框+玻璃背板 铝金属边框+玻璃背板 优化铝金属一体化设计 工艺质感进一步提升 起售价格 5999元(128GB) 5999元(128GB) 5999元(256GB) 🔥 加量不加价!同价格获得2倍存储

之前用着标准版或者非高刷的更老版本的iPhone都很推荐购买iPhone 17标准版,建议等双十一的时候上车。 Air版

还没有上市,而且是个测试用的小众机型,预计不会有很多国人购买。 安卓用户换iPhone 17系列?

由于今年的iPhone 17基础版升级比较显著,那么某些有换机需要的安卓用户可能会问:是否有必要趁此机会转到IOS阵营呢?我以自身经历为例给大家大致讲一下。

我只使用过2台iPhone:iPhone 7 Plus和iPhone 15 pro max,在这2两台手机之间我没有使用过其它手机。当时使用iPhone 7 Plus的时候,我同时还使用了ipad mini 4。由于接触infuse、shadowrocket、Reeder等软件之后,再加上App store的优秀管理,我还是染上了使用付费软件的习惯。所以当时在换iPhone 15 pro max的时候,基本上没有考虑过安卓手机,因为一旦离开苹果生态,之前买的软件都用不上了(而且它们是真的好用),安卓里面也没有很好的平替。后面我换了iPhone 15 pro max,因为工作需要又二手淘了一个Macbook M1 Pro。因为我有NAS,为了增加利用率,我想要给家人搞一个本地影音平台,找了一圈发现Apple TV居然是一个不错的盒子,所以又买了个Apple TV;目前很满意,这玩意没广告,老人家也可以掌握简单的使用,大大节省了之前在安卓电视/盒子里使用Kodi的麻烦。后面苹果推出的Mac min M4的性价比很高,想到MacBook上对MacOS的良好印象,直接用教育优惠搞了一下Mac mini和目前的Windows桌机搭配着使用。后面发现苹果有个apple watch系列,最后又低价收了一下apple watch ultra 2来用着,整体感觉不错。最惊喜的是Airpods Pro 2,如果有多个apple的设备你会懂的,那种无缝切换的感觉真的很爽;降噪效果也是不错的。

到这里,相信大家基本理解了我的意思:买iPhone基本上是为了苹果生态和苹果服务买单的,而不单单是iPhone这一部手机。升级手机只是因为新的手机更加好用,也不见得它的配置会比同期的安卓旗舰要强多少。其实类似我这样的人是不在少数的,就是图在生态里的设备联动和体验方便。而且苹果生态的体验和其它厂商相比并不逊色,在大部分时期都是行业的领导者。就算不是冲首发或冲最新型号,用二手设备也可以获得不错的体验。二手市场里苹果设备的流通性也是不错的,因此老设备出手也比较简单;就算卖不出去,以旧换新的流程也很成熟,多少能抵一点。随着时间更替,更新设备的时间到了,那就直接换新的设备就行,就体验、迁移、习惯等方面不需要很大的变化。人年纪大了还是喜欢稳定!

另外,就iPhone本身而言,一般来说做工也是行业里的佼佼者,设备质量、售后服务是细节满满,值得依赖。苹果的新系统对旧设备的支持政策往往也是比较友好的。比如,我用的AirPod Pro 2,之前也是通过OTA更新就获得了摇头拒绝的新功能。新IOS的对旧iPhone的支持力度往往是比较大的(三星应该是仅某些机型的支持比较久),特别是与国内厂商相比,这让我感觉苹果公司对系统的优化和掌握能力还是比较强的: 排名 厂商 支持年限 老设备支持力度 特色 🥇 1 三星 7年(S24系列) 优秀 承诺最长,覆盖全产品线 🥈 2 苹果 6-7年(实际) 优秀 历史最佳,无明确承诺 🥉 3 华为 4-5年 良好 百机升级计划,覆盖广 4 小米 3-4年 中等 HyperOS转型,流畅度提升 5 OPPO/一加 3-4年 中等 系统稳定性较好 6 vivo 3年 中等 流畅度优秀,AI优化

综上,其实选择iPhone的原因很单纯:单个设备能力尚可;生态互联好;稳定而持续优化的体验。假设我只使用iPhone这一个苹果设备,iPhone本身与其它安卓手机相比并没有太大的吸引力。以我双持的安卓手机Oppo K12s 5G为例(这台手机主要是工作中用来接发消息、打电话用的),它的主要配置是这样的:

搭载第四代骁龙6处理器
配备7000毫安时大电池和80瓦超级闪充,预装ColorOS 15系统
屏幕采用6.67英寸120Hz高刷新率OELD直屏,分辨率2400×1080,支持1200nit全局激发最高亮度
拍照方面配备5000万像素主摄(豪威OV50D40传感器)和200万像素黑白副摄,支持IP65防水防尘等级,湿手触控和手套模式
散热采用5700mm²超级大VC均热板,历经超50类严苛品质测试,主打五年长寿电池概念。

听上去对于非游戏发烧友、非影像发烧友来说绝对是一个不错的配置。但是这台手机只需要930元左右!也就是说,大家经常吐槽的iPhone“容易发热、续航差、标准版没高刷”等方面,似乎显得iPhone很差;但事实证明这些部分并不值钱。iPhone值钱的地方,其实是在芯片、屏幕、陀螺仪、外放、生态、服务这种“无法随意增加”的项目里。容易发热对吧,搞个外挂的散热怎么说?续航差是吧,咱外挂一个充电宝怎么说?没高刷是吧,反正自适应了也看不出来。但是,芯片、屏幕、陀螺仪、外放、服务这些东西,没有就是没有,没办法加上去(或者说没有办法轻易地增加上去),这些就是它值钱的基础。长期这样的坚持形成了口碑,自然就有品牌溢价了。

反过来,对于安卓用户,上述逻辑也是适用的。对于在某个生态里的安卓用户,除非不怕折腾的,不然升级新设备的时候也没多少有选择的空间,一般还是用原厂设备。对于只有1台手机的小伙伴,同样品牌的其它硬件不多,说实话对品牌的忠诚度确实是比较低的,那个好就去那个,这种小伙伴确实有可能被今年的iPhone 17系列吸引。但这里还是给个小小的警告,苹果生态水还是比较深的,进去了确实不容易出来,介意的小伙伴要慎入。 iPhone 18系列的预测

没错!每次换新iPhone前,关于下一代iPhone的预测和分析也是必不可少的;而且也是可以有理有据的,因此总有一些可信的信息来源。这是我在2024-10-12左右就了解到的iPhone 17系列的相关信息:

  • 全系120Hz Promotion自适应刷新,这意味着其日常使用体感更加流畅。 目前iPhone只有Pro版有只有120Hz。

  • 完全版Apple Intelegence,可以使用强大的AI功能。当然,最重要的是更加智能的Siri。

  • 全新设计的iPhone 17 Air(Slim)机型是一款针对女性用户的时尚轻薄机型。取消Plus生产线(此前数据提示Plus机型仅占10%的销量)。它可能会使用苹果自研的不支持毫米波的5G芯片(今年预计上市的iPhone SE 4也可能会使用该芯片)

  • Pro机型的3颗镜头均升级至4800w像素,影像能力大大提升

  • 3nm A19芯片,更强性能 + 更低能耗,续航能力大大增强

  • Pro机型支持屏下FaceID(也有人说是18Pro),灵动岛更小

  • 内存加大至12G,成为实质上的第1款AI iPhone。日常运行应用更加流畅。

  • USB-C 3.0接口,这个是自15代就有了,充电、传数据较14及以前机型大为提升

  • Pro机型采用石墨烯 + VC均热板,有望告别“火龙果”

  • Wifi 7芯片,蓝牙、wifi能力大幅增强

  • 全新设计的音量键,有新的交互(不太重要)

  • 防刮抗反射显示屏,告别贴膜(应该不太可能)

从今天来看,除了AI Siri、全新设计音量键、新设备命名、屏下FaceID、灵动岛更小等,大部分爆料还是比较准的。假设一年前换iPhone 16标准版的小伙伴得知iPhone 17标准版的升级幅度,想必不会轻易购入iPhone 16标准版吧(当然,我不换机的理由是刚刚购入15PM)!

截止2025-09-20,关于iPhone 18的关键爆料有:

iPhone 18 Pro 系列“外观变化不大”,基本延续 iPhone 17 Pro 的设计语言。
iPhone 18 系列中的 A20 芯片将采用台积电的 2nm 工艺进行制造。
摄像模组:三摄模组仍在,但有细微调整的可能。至少一颗后置相机可能采用三星研发的“三层堆栈”(threelayer / stacked)图像传感器,且 Pro 机型的主摄 48MP Fusion 镜头被传将加入可变光圈(variable aperture)。
使用新一代的 LTPO+ 面板,更优化的自适应刷新率
灵动岛 / 屏下 Face ID:苹果计划推进屏下 Face ID,但可能推迟到更后代(因此 iPhone 18 系列可能仍保留灵动岛)。同时也有消息暗示屏幕形态或有“惊喜”或小改动(例如曲面或边框微调)。
更先进的基带(传闻中的 Apple C2 二代调制解调器)
多家媒体与爆料称折叠屏 iPhone iPhone 18 Fold)已进入供应链验证阶段,并被列为后续重要机型之一。
多篇海外媒体与爆料汇总提到 Pro 版将采用下一代 A20 芯片(名称为传闻),性能与能效预期提升。
相机控制按钮优化
发布节奏/分批发布的可能:苹果可能采取“高端机先发、标准版延后”的策略(即 2026 年秋季发布会可能只发布高端产品),暗示 iPhone 18 全系列同步上市存在变数。即:iPhone 18 AirProPro Max和折叠版将在2026年9月推出,而iPhone 18iPhone 18e将在2027年推出
其它:iPhone 18可能进一步提升至50W或更高 USB-C 4.0支持

外观

这个比较容易猜到,因为iPhone 17是外观大改的系列,而苹果一般会保留iPhone基本外观不变达数个版本,因此基本上可以推断iPhone 18系列的外观与iPhone 17系列基本一致。不过,由于iPhone 17 Pro的配色比较丑,预计iPhone 17 Pro的配色会有比较大的发挥空间。这一块直接过。 CPU/GPU/内存

iPhone 18 Pro系列和苹果折叠屏机型将搭载A20芯片,这款芯片将首发台积电2nm制程工艺。A20芯片其晶体管密度再度提升,预计对比A19芯片性能提升15%,能效比提升30%。

这个基本上是板上钉钉的消息。此外,A20芯片还将引入台积电的新一代晶圆级多芯片封装技术(WMCM),带来三大革新:

内存架构革新:RAM将直接与CPU/GPU/神经网络引擎集成于同一晶圆,取代现有的分离式设计
性能有所提升,并且散热效率提高了20%,电池续航延长10-15%
芯片封装面积缩减15%,为iPhone内部其他组件腾出更多空间

台积电大概在2025年3月左右已在新竹宝山工厂启动了 2nm 工艺的试产工作。按照计划,该工艺将于 2025 年下半年正式进入批量生产阶段。台积电今年底 2nm 产能将看向 4 万片,明年能达到 10 万片,而 WMCM 封装主要是针对现有的 InFO 封装产线进行升级,其产能在 2026 年底也有望看向 7-8 万片。内存方面,17 pro max刚刚升到12G,应该不太可能下一年就升级内存。而且,半导体工艺与封装(例如 2nm、新的芯片封装)提升了 SoC 的性能与内存集成能力,技术上才能支持更大容量、更高带宽的 LPDDR 类型内存。明年作为新推出N2的时代,内存容量的增加应该不会很激进。 因此,我预计内存增加至少也得19或20系列的时候才会考虑。

苹果近年来定位“高端”的底气是其优秀的芯片设计能力,但是去年A18的时候直接都被别人追着尾巴咬了,GPU更是遥遥落后。今年A19 Pro芯片的GPU升级很大,明显是感受到高通和联发科的竞争压力,之前以为的很差的GPU能力立刻就追了上来,估计是之前的研究成果的压着没搞,底蕴还是有的。相信明年的移动芯片领域会迎来更强的竞争,说不定又是牙膏挤爆的一代。

综上,iPhone 18 Pro Max的芯片会使用全新N2工艺,预测性能、散热、续航会有进一步提升,但其性能改善有多少就不好预测了;因为当年iPhone 15的时候提升就不太明显。 面板

iPhone 18系列将会采用全新的LTPO+面板,这是一项尚未量产商用的全新技术,苹果会率先将其应用到iPhone 18 Pro和iPhone 18 Pro Max上。鉴于三星和LG是苹果目前的OLED面板供应商,因此,这两家韩国企业很有可能会负责研发LTPO+。

这是一个可能比较高的升级。我总结了一下近几代Pro Max机型的屏幕素质变化: 参数 📱 13 Pro Max 📱 14 Pro Max 📱 15 Pro Max 📱 16 Pro Max 📱 17 Pro Max 🖥️ 屏幕尺寸 6.7英寸 6.7英寸 6.7英寸 6.9英寸 6.9英寸 📺 屏幕类型 Super Retina XDR OLED Super Retina XDR OLED Super Retina XDR OLED Super Retina XDR OLED Super Retina XDR OLED 🔍 分辨率 2778×1284 2796×1290 2796×1290 2868×1320 2868×1320 ✨ 像素密度(PPI) 458 ppi 460 ppi 460 ppi 460 ppi 460 ppi ⚡ 刷新率 10-120Hz ProMotion 1-120Hz ProMotion 1-120Hz ProMotion 1-120Hz ProMotion 1-120Hz ProMotion ☀️ 典型亮度 1000 nits 1000 nits 1000 nits 1000 nits 1000 nits 🌟 HDR峰值亮度 1200 nits 1600 nits 1600 nits 1600 nits 1600 nits 🏖️ 户外峰值亮度 – 2000 nits 2000 nits 2000 nits 3000 nits 🏝️ 动态岛 刘海 ✓ ✓ ✓ ✓ 💤 Always-On显示 ✗ ✓ ✓ ✓ ✓

可以看到16到17这一代已经没有很大的进步了。而且,17代的数字版和pro版用的是同一种屏幕,这一般也意味着下一代旗舰机型的屏幕素质会大有变化。从技术发展路径来看,LTPO+方案将驱动TFT改用氧化物(如IGZO),显著降低漏电流,支持超低刷新率(最低1Hz)下的稳定电压维持。另一大优点是其功耗大幅降低,氧化物驱动TFT的漏电流远低于LTPS,在静态画面或低刷新率场景下(如常亮显示),功耗降低30%以上。 摄像模组

    三星正在研发一种三层/堆栈式 CMOS 图像传感器,目标用于 iPhone 18 的某些镜头。该设计可提升响应速度、降低噪点、改善动态范围和读出速度。堆栈式传感器通常能实现更快像素读出(减少卷帘快门效应)、更好低光表现,并为更高级的计算摄影功能提供硬件基础。之前索尼好像就开发过堆栈式 CMOS 图像传感器(详见“索尼半导体解决方案推出用于移动设备的 CMOS 图像传感器,可在低照度和高对比度环境下实现卓越的图像质量”)。
    iPhone 18 Pro 的 48MP 主摄可能配备可变光圈,让系统在光圈开合间切换以控制进光量与景深效果。可变光圈在极亮或极暗场景下提供更好的曝光与景深控制,配合苹果的图像信号处理可带来更自然的人像虚化和强光下的细节保存。
    更长焦距的潜望式长焦镜头;更大的主感光元件(接近或达到 1″ 级别)与更好的夜景/动态范围; 全局快门/更低滚动快门的堆栈式传感器或更高帧率的视频(例如 8K/60)

整体上,摄像模组这一块的爆料不是很多。我回顾了一下最近几代摄像模组的改变: 参数 13 Pro Max 14 Pro Max 15 Pro Max 16 Pro Max 17 Pro Max 主摄 12MP f/1.5 48MP f/1.78 48MP f/1.78 48MP f/1.78 48MP f/1.6 超广角 12MP f/2.4 12MP f/2.2 12MP f/2.2 48MP f/2.2 48MP f/2.2 长焦 12MP f/2.8 3x变焦 12MP f/2.8 3x变焦 12MP f/2.8 5x变焦 12MP f/2.8 5x变焦 48MP f/2.8 8x变焦 前置 12MP f/2.2 12MP f/1.9 自动对焦 12MP f/1.9 自动对焦 12MP f/1.9 自动对焦 18MP f/1.9 Center Stage 视频 4K@30fps ProRes 4K@30fps Action Mode 4K@60fps Action Mode 4K@120fps 风噪消除 4K@120fps ProRes RAW 变焦范围 0.5x-3x 0.5x-3x (+2x) 0.5x-5x 0.5x-5x 0.5x-8x

15 pro max这一代是第1次CPU使用3纳米工艺,还第1次转了Type-C口,可谓史诗级更新。我听说iPhone 18 Pro Max的A20系列会用上第一代2nm工艺;而17 pro max的摄像能力较16已经有了比较大的提升,前置也是史诗级更新。如果苹果公司会适当控制牙膏的总量,我推测: iPhone 18 pro max将继续保持三颗 48MP Fusion 后摄,但是会有一些特性改变,从而向“更专业相机”逐渐靠拢;前摄为了照顾灵动岛的大小,在有新的技术突破前不会轻易升级规格。 灵动岛

iPhone 18系列的灵动岛将会缩小。iPhone 18 Pro系列机型预计将在显示屏顶部为前置摄像头预留一个”单孔”,而其他对光学性能要求不高的组件将隐藏于前面板下方,类似于Android智能手机的设计。

经常有所谓的“确证消息”爆料新一代iPhone会使用屏下 Face ID组件,但根据苹果面对新技术时的保守策略,直接在pro max直接引入屏下Face ID组件基本不太可能;变小的概率老实说也不是很大。一般来说,灵动岛里藏着完整的 TrueDepth 人脸识别系统(点阵投射器、红外相机/感应器、泛光感应器等)和前置摄像头。这些光学与电学元件需要物理空间来保持对齐、散热与光学通道,不能随意压缩到很小而不影响性能/识别率。缩小开孔或把传感器塞得更紧,要么牺牲 Face ID 的识别速度与低光表现,要么需要采用更复杂/昂贵的方案(比如更高密度元件或更复杂的光学设计),这会增加成本或降低稳定性。“屏下摄像头/屏下 Face ID”往往在成像质量、透光率、低光表现、和指纹/面部识别精度上不如裸露方案,尤其是用于人脸识别的点阵投射器和红外组件,屏下实现更复杂,需要时间成熟。就算前置摄像头可以变小,但整体上灵动岛应该不至于有明显的缩小。

另外,我觉得IOS目前有一个很重要的原则:苹果把这个开孔当成“可用的UI元素”来设计——它不仅显示时间/电量/信号,还能扩展显示通话、音乐、计时器、Live Activities 等。要保证这些信息在动画、文字和触控上都能可用,岛的尺寸需要留出一定空间,不然会变得拥挤或触控误操作多。我个人觉得,灵动岛的动效设计让我有一种它是“活”的感觉,实际使用的时候并不会觉得它特别的突兀,很容易自适应。再加上灵动岛已经出现很长时间了,用户已经基本习惯它的交互,苹果公司应该不会在没有明显技术优势下对它大有更动。

综上,我觉得iPhone 18 pro max的灵动岛与17相比基本保持不变,或者仅有略微的体积缩小。 基带

苹果的下一代C2调制解调器预计将在2026年推出,首次在iPhone 18 Pro机型中亮相,并且苹果的第二代5G调制解调器将在2026年的iPhone 18系列中首次登场。C2调制解调器将支持mmWave,理论下载速度可能达到6 Gbps,超越C1的4 Gbps上限,与高通最新产品相媲美。

这是我最期待的、也是阻碍我换iPhone 17的主要原因。从目前已发布的信息来看,iPhone 16e在5G视频流媒体播放方面续航时间为7小时53分钟,比iPhone 16多出53分钟;最新的iPhone Air中使用的C1X基带性能更加出色,它比iPhone 16 Pro中的高通调制解调器速度更快,同时功耗降低30%。我总结了一下历代iPhone的续航表现: 参数 13 Pro Max 14 Pro Max 15 Pro Max 16 Pro Max 17 Pro Max 电池容量 4352mAh 4323mAh 4422mAh 4685mAh 5088mAh 视频播放时长 28小时 29小时 29小时 33小时 39小时 流媒体视频 25小时 25小时 25小时 29小时 35小时 音频播放 95小时 95小时 95小时 105小时 130小时 实际续航测试 约25-27小时 约26-28小时 约27-28小时 28小时14分钟 ?

如果C2调制解调器更加给力,再加上稍微加大一点的电池(或者能量密度更高的电池)、更加高效的2nm CPU,预计iPhone 18 pro max的续航会大幅加强,说不定能比iPhone 17 Pro max还能再强1.5-2h(根据极客湾手机续航3.5版,17 pro max的新机续航大致是9小时27分),这个升级不可谓不强呀,基本上抹平安卓旗舰狂加电池增加的续航。这方面的消息得等iphone 17e明年继续推出才会比较明朗。

不过也有非技术性的影响因素,特别是苹果公司与高通的合作协议,没到期的情况下肯定还是继续使用高通基带的(目前最新的消息是延长到2027年3月)。 时间 协议内容 覆盖产品 备注 2023年9月 苹果与高通签署基带芯片供应协议 2024-2026年iPhone 为期三年的初始协议 2024年2月 协议延长至2027年3月 延续至2027年产品 苹果自研基带进展不顺

综上,我认为:如果iPhone 17e使用C2,基本上iPhone 18 pro max系列用上C2是板上钉钉的事;但不排除继续使用高通的基带至合作结束。 折叠屏 iPhone

苹果首款折叠屏iPhone预计将在2025年第三季度末或第四季度初启动生产,采用类似Galaxy Z Fold系列的翻盖式设计双屏方案。这款设备计划于2026年秋季正式发布并上市销售。首款折叠iPhone或被命名为iPhone Ultra。内屏尺寸为7.76英寸,外屏为5.49英寸,内屏采用屏下摄像头技术。展开时厚度为4.6毫米,折叠时厚度为9.2毫米。预计定价较高,可能在2000至2500美元之间(约合人民币1.4万-1.8万元)。苹果还可能重启Touch ID,取代目前主流的Face ID,提升折叠屏设备的适配性。

虽然每年折叠iPhone的消息比较响,但我认为明年推出折叠屏的概率不大。首先,安卓折叠屏手机的上市时间已经比较长了,相关的供应链相对而言还是比较成熟的,苹果公司却一直不更新折叠屏手机肯定有一些考量。一种比较主流的说法是苹果公司向来以对产品质量极为苛刻著称,作为高端品牌,苹果不愿意在尚未完美的情况下推出产品;市场上现有的折叠屏手机普遍存在屏幕褶皱问题,这严重影响了用户体验。

苹果公司明年不会推出折叠屏的原因如下:

折叠屏是一个相对小众的市场,尽管花了大钱去搞,也不一定能抢占多少市场
屏幕褶皱问题暂时没有合适的行业解决方案
苹果对于非畅销机型的定位更多是测试机型,而不是比Pro Max更加高端的机型。目前已经有e/air两种机型用于持续迭代和评估新技术,基本上已经将产品的更新周期缩小至半年,这种状态应该会持续一段时间。

苹果公司明年会推出折叠屏的原因如下:

今年“iPhone Air”主打轻薄机身,也许苹果已经在做折叠手机的路上做最后一步冲刺
苹果对申请关于三折叠和四折叠手机的新专利比较积极
据称iPhone 18和iPhone 18e将在2027年推出。如果2026年秋季只推出3款机型会有点“弱”,但最后一个机型是Fold的话就会比之前更强,而且这个话题度直接拉满。这个推断比较合理,因为确实有消息称苹果公司准备像很多安卓厂商那样调整iPhone的发布周期
IOS 26的“液态玻璃”似乎很适合在软屏上运行,说不定苹果又想像灵动岛那样通过软件交互处理硬件缺陷问题

综上, iPhone 18 系列是否推折叠屏不太确定;如果推出,苹果公司应该会相应缩短iPhone产品的发布周期。 相机控制按钮

苹果正在简化相机控制按钮的设计以节省成本。目前的相机控制按钮具有电容和压力感应功能,但电容组件将被移除。苹果通过转向纯压力感应按钮来节省成本和复杂性。

目前iPhone 16机型上的相机控制按钮在蓝宝石水晶表面下使用电容和压力传感器。电容层检测触摸手势,而力传感器识别不同的压力水平以实现点击、按压和滑动。和大多数小伙伴的看法一样,这是苹果为了迎合AI而特意加的一个硬件,但Apple智能还没有全面来到,导致这个按钮的存在有点尴尬;对于国行用户来说更是一个鸡肋。这个按钮在实际使用时没有任何优势,但缺点很多:

致命问题:用户单手握持手机时容易误触到相机控制按键位置的玻璃并导致触屏失灵现象
成本很高:制造、维修和保养成本很高(按键是采用激光焊接到框架上,如果按键出现了故障问题,就需要更换整个框架)
操作复杂:用户需要学习新的交互方式,包括轻触、按压、滑动等多种手势,与屏幕操作相比有明显劣势

那么相机控制按钮会不会像Touch Bar那样直接被取消呢?明年不太可能,原因是:

iPhone 16系列才刚引入这个功能,苹果不太可能立即承认失败
相机控制按钮是Apple智能的重要入口,用户可以通过单击该按键,利用手机的”视觉智能”功能来识别物体、获取相关信息等。取消这个按钮可能会加强用户关于“Apple智能开发失败”这种信念,与苹果大力追赶AI的决策相背
嘴硬一波:苹果公司现任CEO库克在接受采访时,表达了对这一决策的坚定支持,并表示这一改变对于提升用户体验至关重要

综上,我个人觉得iPhone 18系列的相机控制按钮暂应该只会被优化,而不是取消。未来相机控制按钮也有可能会像Home键、3.5mm耳机孔、3D Touch那样被取消,不过很大程度上取决于用户与Apple智能(目前还是一个饼)的交互方式。 小结

一家之言仅供参考。时间有限,内容或有错漏,请各位小伙伴批评指正哈!


解题报告

带tag的可持久化平衡树,Pushdown需要在新建的节点上进行,并且Pushdown需要新建两个儿子,以避免影响之前的信息。

好像看到了很多Pushdown在原节点上进行的代码(但不是即时交换儿子),由于个人习惯是传tag必须保证节点已更新,所以没有采用这种方法。 示例程序

include

include

include

define fr first

define sc second

define mp make_pair

using namespace std; typedef long long LL; const int maxn=200000,maxt=3e7;

int te;LL lstans; struct NODE {NODE son[2];int si,val,fix;LL sum;bool flip;} PL[maxt+5]; typedef NODE ptr;typedef pair ppp; ptr pl=PL,ro[maxn+5],lim; mt19937 mrand(19260817);

define EOLN(x) ((x)==10 || (x)==13 || (x)==EOF)

inline char readc(){ static char buf[1<<16],l=buf,r=buf; return l==r && (r=(l=buf)+fread(buf,1,1<<16,stdin),l==r)?EOF:*l++; } template int readi(T &x){ T tot=0;char ch=readc(),lst='+'; while (!isdigit(ch)) {if (ch==EOF) return EOF;lst=ch;ch=readc();} while (isdigit(ch)) tot=(tot<<3)+(tot<<1)+(ch^48),ch=readc(); lst=='-'?x=-tot:x=tot;return EOLN(ch); } struct fastO{ int si;char buf[1<<16]; fastO() {si=0;} void putc(char ch){ if (si==(1<<16)) fwrite(buf,1,si,stdout),si=0; buf[si++]=ch; } ~fastO() {fwrite(buf,1,si,stdout);} }fo;

define putc fo.putc

template void writei(T x,char ch='\n'){ static int len=0,buf[100]; if (x<0) putc('-'),x=-x; do buf[len++]=x%10,x/=10; while (x); while (len) putc(buf[--len]+48); if (ch) putc(ch); } inline ptr newnode(int x){ pl++;pl->son[0]=pl->son[1]=PL; pl->si=1;pl->val=pl->sum=x;pl->fix=mrand(); pl->flip=0; return pl; } void Pushup(ptr p){ p->si=p->son[0]->si+1+p->son[1]->si; p->sum=p->son[0]->sum+p->val+p->son[1]->sum; } ptr Addflip(ptr p){ if (p==PL) return PL; ptr now=++pl;now=p; swap(now->son[0],now->son[1]); now->flip^=1; return now; } void Pushdown(ptr p){ if (p->flip){ p->son[0]=Addflip(p->son[0]); p->son[1]=Addflip(p->son[1]); p->flip^=1; } } ppp Split(ptr p,int k){ if (p==PL) return mp(PL,PL); ptr now=++pl;now=p; Pushdown(now); ppp res; if (k<=now->son[0]->si){ res=Split(now->son[0],k); now->son[0]=res.sc; res.sc=now; } else { res=Split(now->son[1],k-now->son[0]->si-1); now->son[1]=res.fr; res.fr=now; } Pushup(now); return res; } ptr Merge(ptr x,ptr y){ if (x==PL || y==PL) return x==PL?y:x; ptr now; if (x->fixfix){ if (x<=lim) now=++pl,now=x; else now=x; Pushdown(now); now->son[1]=Merge(now->son[1],y); } else { if (y<=lim) now=++pl,now=y; else now=y; Pushdown(now); now->son[0]=Merge(x,now->son[0]); } Pushup(now); return now; } ptr Flip(ptr p,int L,int R){ lim=pl; ppp A=Split(p,L-1),B=Split(A.sc,R-L+1); return Merge(A.fr,Merge(Addflip(B.fr),B.sc)); } ptr Insert(ptr p,int rk,int k){ lim=pl; ppp res=Split(p,rk); return Merge(Merge(res.fr,newnode(k)),res.sc); } ptr Delete(ptr p,int k){ lim=pl; ppp A=Split(p,k),B=Split(A.fr,k-1); return Merge(B.fr,A.sc); } LL getsum(ptr p,int L,int R){ lim=pl; ppp A=Split(p,L-1),B=Split(A.sc,R-L+1); pl=lim; return B.fr->sum; } int main(){ PL->son[0]=PL->son[1]=PL; ro[0]=PL; readi(te); for (int t=1,fa,tp,x,y;t<=te;t++){ readi(fa);readi(tp);readi(x);x^=lstans; if (tp!=2) readi(y),y^=lstans; ro[t]=ro[fa]; if (tp==1) ro[t]=Insert(ro[fa],x,y); else if (tp==2) ro[t]=Delete(ro[fa],x); else if (tp==3) ro[t]=Flip(ro[fa],x,y); else if (tp==4) writei(lstans=getsum(ro[t],x,y)); } return 0; }


菜单
主要内容

书签 设置 文档

iVampireSP 的物语
22 十二月 2022

发布于 5 四月 2020
作者 iVampireSP
ivampiresp.com
阅读约 2 分钟

标签

书签栏/box-import-to

高亮

在文章中选择文本以创建新的高亮。 链接

使用 nginx 的 rtmp 模块搭建一个流式媒体(直播)服务器 - iVampireSP 的物语

自建一个流式媒体服务器

近期在家里闲着没事干,然后和群员聊着聊着,突然想起了之前想要搭建的流式媒体服务器,但是没有搭建成功。然而在这个超长的寒假中,我成功实现了(后来发现很简单) 结构

在搭建后不久,我做了很多修改,大概是这个样子

前端:完全静态,因为是自己的流式媒体服务器,自己播着玩玩也用不到动态。

流式媒体服务器:Nginx 的rtmp模块。

后端:Nodejs,做实时评论用,关于为啥不用php等。。我觉得nodejs来的更快。当然代码都是Ctrl+C,Ctrl+V来的 前端

这里我推荐使用hls.js,因为hls流的话很方便,就是有点延时。。。代码可以自由发挥了。 搭建

我这里建议使用Debian系的Linux系统,当然Windows也不是不可以,我都会在后面放出。

为什么建议使用Debian系的Linux系统呢?因为apt一下很方便。其他可以可以自行搜索了,编译安装。

请额外放行端口:1935,3000(可能),80,443

PS:树莓派也可以 安装必要的软件包

首先准备一个纯净的系统(不是纯净的自己看着改代码)

更新系统

sudu apt update

安装nginx和rtmp模块及wget(万一没有)

sudo apt install nginx-full libnginx-mod-rtmp wget -y

设置nginx.conf

sudo vim /etc/nginx/nginx.conf

你需要在第一个server 前加一段

rtmp { server { listen 1935; application live { live on; } application hls { live on; hls on; hls_path /var/www/html/hls; hls_fragment 2s; } } }

他看上去应该像这样:

hls_path你可以修改,但是建议放在和前端文件同级的一个目录下。

然后重启nginx

sudo systemctl restart nginx

此时rtmp服务器你就搭建完成了

就这么简单

现在你可以推个流试试了,串流密钥随便写。

使用OBS推流,服务器地址为:rtmp://IP:1935/live/,密钥随便

拉流:rtmp://IP:1935/live/你的密钥

HLS流:使用OBS推流,服务器地址为:rtmp://IP:1935/hls/,密钥随便

拉流:rtmp://IP:1935/hls/你的密钥

或者用浏览器播放hls的m3u8

http(s)://IP/hls/密钥.m3u8 实时评论

这里就直接用nodejs实现了

参考:https://www.jianshu.com/p/5539ccd8d9c4

首先安装nodejs和npm以及创建目录,安装依赖

sudo apt install nodejs npm -y cd ~ mkdir livechat cd livechat npm install express socket.io

apt里的nodejs的版本是足够了的

创建一个目录,然后开始安装软件,代码在上面的URL里。

全部弄完后启动就行,然后试试能不能访问。能访问的话就可以配置nginx的反向代理了。 配置反向代理

为何要反向代理?因为支持https并且利用nginx的rtmp模块。

那为什么不直接用nodejs搭建rtmp服务器呢?因为我没尝试过,既然nginx都跑起来了,那顺便用nginx搭建其他网站也不是问题。

接下来你需要在现有的http{}中添加一个sever,你可以新建配置文件,或者直接在nginx.conf中写,如果你还要架设多个网站的话,我还是推荐新建配置文件,这里我就直接在nginx.conf中写了。

vim /etc/nginx/nginx.conf

server { listen 8081; server_name live.nwl.im; location / { proxy_pass http://127.0.0.1:3000/; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

        if ($request_method = 'OPTIONS') {
            return 204;
        }
    }       
    # 为什么需要下面这段呢因为我的教程里是设置的反代如果不加这段是直接从nodejs那里读取但是nodejs那里是没有ts和m3u8文件的
    location /hls {  
        #server hls fragments  
        types{  
            application/vnd.apple.mpegurl m3u8;  
            video/mp2t ts;  
        }  
        # 把下面的alias后面的目录改成和hls_path相同的目录
        alias temp/hls;  
        expires -1;  
        # 设置CORS如果不需要将下面那一段删除
        add_header 'Access-Control-Allow-Origin' '*'; 
    }
}

他看起来应该像这样(可能会和我的不一样,我的配置文件也许和你的环境不符合,请按照你的实际环境修改),当然我这里是加了ssl的。如果你更改了nodejs的端口,你也需要在上方配置文件中修改一下proxy_pass部分。

然后检查nginx的配置文件是否有误

sudo nginx -t

如果返回类似如下消息,你的流式媒体服务器搭建完成了,接下来就是前端。

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful

重启nginx

sudo systemctl nginx restart

前端

这里我推荐前端使用nodejs,因为。。需要依靠socket.io

打开你按照上方URL中创建的index.html,你可以在里面加入如下视频标签和hls.js

    <script src="https://cdn.bootcss.com/hls.js/0.13.2/hls.min.js"></script>
    <script>
        var video = document.getElementById('video');
        if (Hls.isSupported()) {
            var hls = new Hls();
            hls.loadSource('http(s)://你的IP或者域名/密钥.m3u8');
            hls.attachMedia(video);
            hls.on(Hls.Events.MANIFEST_PARSED, function() {
                video.play();
            });
        } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
            video.src = 'http(s)://你的IP或者域名/hls/密钥.m3u8';
            video.addEventListener('loadedmetadata', function() {
                video.play();
            });
        }
    </script>

然后做些适当的修改,就完成了,接下来你可以把评论区整合进去。

我这放一下我的index.html,如果你喜欢的话,可以拿去用。

iVampireSPのLive
near_me
菜单
keyboard_arrow_down

实时评论区


    本直播页面是使用Nginx RTMP模块实现的,并且使用了hls.js和socket.io做到的接收流和实时评论,缺点是不能查看以前的评论。后端:nodejs。搭建教程:点我查看

    接下来就是Windows端,Windows端nodejs部分和上面是一样的,nginx.conf少做修改也可以。

    带rtmp的nginx Windows端:https://github.com/illuspas/nginx-rtmp-win32

    接下来你可以随心所欲的修改了,比如添加一些功能。

    希望你使用愉快~


    菜单
    主要内容
    

    书签 设置 文档

    iVampireSP 的物语
    22 十二月 2022
    
    发布于 6 八月 2020
    作者 iVampireSP
    ivampiresp.com
    

    标签

    书签栏/box-import-to
    

    高亮

    在文章中选择文本以创建新的高亮。 WordPress 个性化的登录界面 - iVampireSP 的物语

    一个自定义WordPress登录界面代码

    先上图

    这是我最近突发奇想肝出来的一个WordPress登录界面,布局虽然不是很好,但是还是有那么0.01的花里胡哨的

    (至少加载速度比原版慢了很多,资源占用也不小

    (并且布局时我使用的是px,所以并没有对手机端进行适配。(容我稍后写一下媒体查询)

    特点:使用了css3动画,大部分动画都使用纯css3完成。加入了Hitokoto

    动画:背景缩放/背景模糊/动态登录框/顶部hitokoto自动刷新

    不足:移动端UI不适配,但是可以正常登录。

    代码(添加到主题的functions.php即可)

    function custom_login() {
    echo ''; } function custom_login_footer() { echo ''; } add_action('login_head', 'custom_login');
    add_action('login_footer', 'custom_login_footer');

    |´・ω・)ノ

    ヾ(≧∇≦*)ゝ

    (☆ω☆)

    (╯‵□′)╯︵┴─┴

     ̄﹃ ̄

    (/ω\)

    ∠( ᐛ 」∠)_

    (๑•̀ㅁ•́ฅ)

    →_→

    ୧(๑•̀⌄•́๑)૭

    ٩(ˊᗜˋ*)و

    (ノ°ο°)ノ

    (´இ皿இ`)

    ⌇●﹏●⌇

    (ฅ´ω`ฅ)

    (╯°A°)╯︵○○○

    φ( ̄∇ ̄o)

    ヾ(´・ ・`。)ノ"

    ( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃

    (ó﹏ò。)

    Σ(っ °Д °;)っ

    ( ,,´・ω・)ノ"(´っω・`。)

    ╮(╯▽╰)╭

    o(////▽////)q

    >﹏<

    ( ๑´•ω•) "(ㆆᴗㆆ)

    😂

    😀

    😅

    😊

    🙂

    🙃

    😌

    😍

    😘

    😜

    😝

    😏

    😒

    🙄

    😳

    😡

    😔

    😫

    😱

    😭

    💩

    👻

    🙌

    🖕

    👍

    👫

    👬

    👭

    🌚

    🌝

    🙈

    💊

    😶

    🙏

    🍦

    🍉

    😣

    Source: github.com/k4yt3x/flowerhd


    设定文本

    你可以预先设定待会要被播放的文本。 使用浏览器合成声音

    这里一个除了IE以外的主流浏览器可以使用的功能

    var sound = window.speechSynthesis; // 定义局部变量 var read_text = new SpeechSynthesisUtterance(text); // 实例化 sound.speak(read_text); // 朗读

    其中将text改成你所需要朗读的文本,或者取变量里面的值。 使用JQuery 做个示例

    var sound = window.speechSynthesis; var read_text = new SpeechSynthesisUtterance($("#textarea").val()); sound.speak(read_text);

    但是这样你会发现一个问题:播放时浏览器并不会提示,甚至可能无法方便的暂停。 但是也不是没有好处的,因为这不需要白嫖API接口,就是兼容性有些差。 使用百度TTS播放自定义文本语音

    所以我们可以白嫖百度TTS接口啊(滑稽)

    audio = document.createElement('audio'); // 声明audio全局变量并创建元素 source = document.createElement('source'); // 声明source全局变量并创建元素 source.type = "audio/mpeg"; // source类型 source.src = 'https://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&spd=8&text=' + $('#textarea').text(); // 拼接URL并读取要朗读的内容,设定src地址 source.autoplay = "autoplay"; // 自动播放 source.controls = "controls"; // 显示控件(其实没必要) audio.appendChild(source); // 作为source的父元素 audio.play(); // 播放

    使用jQuery做个示例

    audio = document.createElement('audio'); source = document.createElement('source'); source.type = "audio/mpeg"; source.src = 'https://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&spd=8&text=' + $('#textarea').val(); source.autoplay = "autoplay"; audio.appendChild(source); audio.play();

    优点:使用audio标签,可控音频播放。 缺点:需要联网。


    菜单
    主要内容
    

    书签 设置 文档

    晓雨杂记
    22 十二月 2022
    
    lihaoyu.cn
    

    标签

    书签栏/box-import-to
    

    高亮

    在文章中选择文本以创建新的高亮。 链接

    优化 Windows Search 搜索,加快搜索速度! | 大蛋糕的烘焙坊

    于风雨中 承载所有的回忆 前言 ​

    Windows Search 这个东西,一言难尽…

    你说它没用吧,它还挺有用的;你说它有用吧,它搜索速度还贼慢,还搜不到想要的东西。

    这篇文章,我会告诉各位我的 Windows 搜索为什么那么好用,以及该怎么进行配置。 索引 ​

    等等,什么是索引?

    Windows 10 中的搜索索引:常见问题解答
    

    我们在这里做个比喻。电脑像一本书,而索引就像书里面的书签。

    我想在这本书里找到一句话,如果那句话正好有一个书签,我就能很快地找到这句话。

    反之,如果没有书签,我就可能需要几个小时来找这句话。

    搜索索引也是一个道理。它通过为每个文件建立独一无二的标记并将其存储,从而加快搜索速度,改善搜索体验。 步骤 ​ 设置索引模式 ​

    首先,让我们打开 Windows 的设置,导航到“隐私和安全性”->“搜索 Windows”。

    然后我们就能够看到索引状态、索引模式、排除项等等。 索引设置

    如果你用的是 Windows 10,则直接在“设置”的第一级页面中进入“搜索”分类即可。
    

    索引模式大多数人默认都是“经典”即只为桌面、音乐、图片、文档文件夹编制索引。

    让我们把它改成“增强”吧 排除不想索引的文件夹 ​

    Windows Search 提供了排除项功能,可以根据自己的需要对部分文件夹进行排除。

    比如,有时候你会发现搜索结果里出现了 DLL 文件或者 QQ 等东西缓存到电脑上的图片之类的东西。

    这时候,我们就需要设置排除的文件夹了。

    排除项一次只能设置一个文件夹,所以你可能需要一段时间来进行排除。 重建索引 ​

    万事大吉以后,我们可以重建索引。这样将会删除之前 Windows 在你电脑上编制的索引,然后重新编制。

    将页面滚动到最底部,选择“高级索引选项”在打开的窗口中选择“高级”; 高级索引选项

    在另一个弹出的窗口中选择“删除和重建索引”; 删除和重建索引

    选择后,Windows 会弹出一个提示框询问你是否继续操作,我们点击“确定”; 重建索引

    等待几秒,Windows 会自动开始编制索引。

    稍微等上几分钟,然后你应该就可以通过任务栏的搜索按钮 / 搜索框体验到完美的 Windows Search 了。 Windows Search 注意事项 ​

    需要注意的是,设置为“增强”模式后,Windows 将会对所有文件(除了排除文件夹)进行索引,索引量通常上万。

    这可能导致 Windows 更新失败,提示 0x800707e7 错误。

    因此,每次更新前为了保险起见,先重建索引,当 Windows 开始编制索引时立刻重启电脑。这样的话更新就不会出现什么大问题。

    如果你有意见,欢迎在评论区里指正!


    SEO(Search Engine Optimization):汉译为搜索引擎优化。是一种方式:利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。目的是让其在行业内占据领先地位,获得品牌收益。很大程度上是网站经营者的一种商业行为,将自己或自己公司的排名前移。–百度百科
    

    很多新的小博主已经拥有了自己的网站,但是在主流搜索引擎平台却无法搜索到自己网站的内容,这很可能是因为没有做好SEO(搜索引擎优化),本篇文章教大家如何给自己的网站做好SEO 网站速度优化

    很多搜索引擎爬虫都非常注重网站的速度,因为他直接影响到了用户的体验,如果网站的打开速度非常慢,那么可能导致搜索引擎无法抓取或者用户认为网站不存在,导致访问量低 速度优化的最好方式就是使用CDN(内容分发网络)类型产品,可以非常明显的提高速度,如果网站在国外或许可以考虑下Cloudflare,但是Cloudflare的CDN产品在国内访问效果并不十分良好 顺便为大家推荐阿里云和腾讯云的CDN产品,本站使用的就是腾讯云CDN(看啥看,赶紧打钱啊) 值得一提的是,网站内的脚本或其他元素过多可能会显著影响客户体验,以前笔者的朋友的网站打开后直接吃满CPU,打开调试发现引用了一百多个JS 还有,图片的加载也影响着网站速度,推荐使用网上的工具对你的图片进行压缩,如果图片是透明的,则应该使用PNG压缩,如果图片不需要透明,则强力推荐Webp压缩,Webp压缩的特点是低损/无损压缩、压缩效果好,类似于HEIC格式图片,HEIC是无损,并且提供更好的压缩效果,但是兼容性差,部分手机内置相机自动HEIC格式保存,博客文章的封面图建议压缩到100KB以内,下面是一张Webp有损压缩后的图片,可以看到这张1080p图片只有100KB不到

    Sitemap

    如果你的网站资源多,搜索引擎可能抓取会“走弯路”,抓取到一些不推荐去抓取的内容,例如图片详细信息或者登录界面,一般来说,这些特殊页面会被robots.txt屏蔽,但是为了让搜索引擎快速抓取,推荐制作一个Sitemap,现在的网站大多数动态网站,如果有能力,最好自己根据网站的实际情况来制作Sitemap抓取程序 链接优化

    网站的链接要尽可能的短,尤其对于博客论坛一类的站点,下面是一些例子: 长链接: blog.mbrjun.cn/index.php?archives/2020/02/03/xxxx.html 短链接: blog.mbrjun.cn/archives/233 如果你的网站是动态站点,请务必考虑重写(Rewrite/伪静态) SEO插件

    有的论坛/博客源码可以自己安装插件和主题,所以简单的介绍下大多数SEO插件的功能:可以自动更新站点地图,为爬虫进行特有的抓取优化 网站持续运行

    如果想要被搜索引擎抓取,则应该保证网站持续运行,如果网站服务器经常无法连接,通常是访问量过大,导致服务器负载状态爆满,一般来说只有两种解决方法,就是站点优化和提高服务器配置 关键词和标签

    网站想要在搜索引擎精准展现需要有适合的关键词(keyword)和标签,但是由于近年来网站不断出现关键词滥用的问题,使得关键词在搜索引擎的权重不断变低,所以如果你要对blog一类的站点进行优化,要注意去正确使用标签功能,建议在10个左右即可 内容质量

    如果想要让网站更多的展现在搜索引擎上,应该提高网站里面的内容质量,这一点需要站长自行把控 友情链接

    想要提高网站浏览量,可以选择性的互换友情链接,另外说明一下,换友情链接最好找到和你网站差不多的同类站点,另外,你需要经常检查友情链接的质量,如果对方网站经常跑路或者打不开那就不用换了( 域名

    据说老域名和备案的网站搜索引擎会更喜欢


    菜单
    主要内容
    

    书签 设置 文档

    MBRjun-Blog
    22 十二月 2022
    
    发布于 6 九月 2020
    作者 MBRjun
    mbrjun.cn
    阅读约 14 分钟
    

    将在几秒钟内移除此书签。 标签

    书签栏/box-import-to
    

    高亮

    在文章中选择文本以创建新的高亮。 Windowsの快捷键 - MBR's Blog

    100+个常用快捷键

    Ctrl + X 剪切选定项

    Ctrl + C(或 Ctrl + Insert) 复制选定项

    Ctrl + V(或 Shift + Insert) 粘贴选定项

    Ctrl + Z 撤消操作

    Alt + Tab 在打开的应用之间切换

    Alt + F4 关闭活动项,或者退出活动应用

    Windows 徽标键 + L 锁定电脑

    Windows 徽标键 + D 显示和隐藏桌面

    F2 重命名选定项

    F3 在文件资源管理器中搜索文件或文件夹

    F4 在文件资源管理器中显示地址栏列表

    F5 刷新活动窗口

    F6 在窗口中或桌面上循环浏览屏幕元素

    F10 激活活动应用中的菜单栏

    Alt + F8 在登录屏幕上显示密码

    Alt + Esc 以项目打开的顺序循环切换项目

    Alt + 带下划线的字母 执行该字母对应的命令

    Alt + Enter 显示选定项的属性

    Alt + 空格键 为活动窗口打开快捷菜单

    Alt + 向左键 后退

    Alt + 向右键 前进

    Alt + Page Up 向上移动一个屏幕

    Alt + Page Down 向下移动一个屏幕

    Ctrl + F4 关闭活动文档(在全屏模式和允许你同时打开多个文档的应用中)

    Ctrl + A 选择文档或窗口中的所有项目

    Ctrl + D(或 Delete) 删除选定项并将其移动到“回收站”

    Ctrl + R(或 F5) 刷新活动窗口

    Ctrl + Y 重做操作

    Ctrl + 向右键 将光标移动到下一个字词的起始处

    Ctrl + 向左键 将光标移动到上一个字词的起始处

    Ctrl + 向下键 将光标移动到下一个段落的起始处

    Ctrl + 向上键 将光标移动到上一个段落的起始处

    Ctrl + Alt + Tab 使用箭头键在所有打开的应用之间切换

    Ctrl + Alt + Shift + 箭头键 当分组或磁贴在“开始”菜单中获得焦点时,请将其按指定方向移动

    Ctrl + 箭头键(用来移动到某个项目)+ 空格键 在窗口中或桌面上选择多个单独的项目

    Ctrl + Shift 加某个箭头键 选择文本块

    Ctrl + Esc 打开“开始”屏幕

    Ctrl + Shift + Esc 打开任务管理器

    Ctrl + Shift 在提供了多个键盘布局时切换键盘布局

    Ctrl + 空格键 打开或关闭中文输入法编辑器 (IME)

    Shift + F10 显示选定项的快捷菜单

    Shift 加任意箭头键 在窗口中或桌面上选择多个项目,或者在文档中选择文本

    Shift + Delete 无需先将选定项移动到“回收站”,直接将其删除

    向右键 打开右侧的下一个菜单,或者打开子菜单

    向左键 打开左侧的下一个菜单,或者关闭子菜单

    Esc 停止或退出当前任务

    Windows 徽标键键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 打开或关闭“开始”菜单

    Windows 徽标键 + A 打开“操作中心”

    Windows 徽标键 + B 将焦点设置到通知区域

    Windows 徽标键 + Shift + C 在侦听模式下打开 Cortana

    注意: Cortana 仅在某些国家/地区提供,并且某些 Cortana 功能可能无法随时随地使用。如果 Cortana 不可用或已关闭,你仍然可以使用搜索。

    Windows 徽标键 + D 显示和隐藏桌面

    Windows 徽标键 + Alt + D 显示和隐藏桌面上的日期和时间

    Windows 徽标键 + E 打开“文件资源管理器”

    Windows 徽标键 + F 打开“反馈中心”

    Windows 徽标键 + G 打开游戏栏(当游戏处于打开状态时)

    Windows 徽标键 + H 打开“共享”超级按钮

    Windows 徽标键 + I 打开“设置”

    Windows 徽标键 + J 将焦点设置到 Windows 提示(如果可用)。

    出现 Windows 提示时,将焦点移到该提示。 再次按这些键盘快捷方式,将焦点移到定位 Windows 提示的屏幕上的元素。

    Windows 徽标键 + K 打开“连接”快速操作

    Windows 徽标键 + L 锁定电脑或切换帐户

    Windows 徽标键 + M 最小化所有窗口

    Windows 徽标键 + O 锁定设备方向

    Windows 徽标键 + P 选择演示显示模式

    Windows 徽标键 + R 打开“运行”对话框

    Windows 徽标键 + S 打开搜素

    Windows 徽标键 + T 在任务栏上循环切换应用

    Windows 徽标键 + U 打开“轻松使用设置中心”

    Windows 徽标键 + V 循环切换通知

    Windows 徽标键 + Shift + V 以相反的顺序循环切换通知

    Windows 徽标键 + X 打开“快速链接”菜单

    Windows 徽标键 + Z 显示在以全屏模式呈现的应用中可用的命令

    Windows 徽标键 + 逗号 (,) 临时快速查看桌面

    Windows 徽标键 + Pause 显示“系统属性”对话框

    Windows 徽标键 + Ctrl + F 搜索电脑(如果你位于网络中)

    Windows 徽标键 + Shift + M 将最小化的窗口还原到桌面

    Windows 徽标键 + 数字 打开桌面,并启动固定到任务栏的位于该数字所表示位置的应用。如果该应用已在运行,则切换到该应用。

    Windows 徽标键 + Shift + 数字 打开桌面,并启动固定到任务栏的位于该数字所表示位置的应用的新实例

    Windows 徽标键 + Ctrl + 数字 打开桌面,并切换到固定到任务栏的位于该数字所表示位置的应用的最后一个活动窗口

    Windows 徽标键 + Alt + 数字 打开桌面,并打开固定到任务栏的位于该数字所表示位置的应用的跳转列表

    Windows 徽标键 + Ctrl + Shift + 数字 打开桌面,并以管理员身份打开位于任务栏上给定位置的应用的新实例

    Windows 徽标键 + Tab 打开“任务视图”

    Windows 徽标键 + 向上键 最大化窗口

    Windows 徽标键 + 向下键 从屏幕中删除当前应用或最小化桌面窗口

    Windows 徽标键 + 向左键 将应用或桌面窗口最大化到屏幕左侧

    Windows 徽标键 + 向右键 将应用或桌面窗口最大化到屏幕右侧

    Windows 徽标键 + Home 最小化除活动桌面窗口以外的所有窗口(在第二道笔划时还原所有窗口)

    Windows 徽标键 + Shift + 向上键 将桌面窗口拉伸到屏幕的顶部和底部

    Windows 徽标键 + Shift + 向下键 在垂直方向上还原/最小化活动桌面窗口,同时保持宽度不变

    Windows 徽标键 + Shift + 向左键或向右键 将桌面中的应用或窗口从一个监视器移动到另一个监视器

    Windows 徽标键 + 空格键 切换输入语言和键盘布局

    Windows 徽标键 + Ctrl + 空格键 更改为以前选择的输入

    Windows 徽标键 + Enter 打开“讲述人”

    Windows 徽标键 + 正斜杠 (/) 启动 IME 重新转换

    Windows 徽标键 + 加号 (+) 或减号 (-) 使用“放大镜”放大或缩小

    Windows 徽标键 + Esc 退出“放大镜”

    命令提示符键盘快捷方式

    按此键 执行此操作

    Ctrl + C(或 Ctrl + Insert) 复制选定文本

    Ctrl + V(或 Shift + Insert) 粘贴选定文本

    Ctrl + M 进入标记模式

    Alt + 选择键 在阻止模式中开始选择

    箭头键 按指定方向移动光标

    Page Up 将光标向上移动一页

    Page Down 将光标向下移动一页

    Ctrl + Home(标记模式) 将光标移动到缓冲区的起始处

    Ctrl + End(标记模式) 将光标移动到缓冲区的末尾

    Ctrl + 向上键 在输出历史记录中向上移动一行

    Ctrl + 向下键 在输出历史记录中向下移动一行

    Ctrl + Home(历史记录导航) 如果命令行为空,则将视口移动到缓冲区顶部。否则,删除命令行中光标左侧的所有字符。

    Ctrl + End(历史记录导航) 如果命令行为空,则将视口移动到命令行。否则,删除命令行中光标右侧的所有字符。

    对话框键盘快捷方式

    按此键 执行此操作

    F4 显示活动列表中的项目

    Ctrl + Tab 在选项卡上向前移动

    Ctrl + Shift + Tab 在选项卡上向后移动

    Ctrl + 数字(数字 1 到 9) 移动到第 n 个选项卡

    Tab 在选项上向前移动

    Shift + Tab 在选项上向后移动

    Alt + 带下划线的字母 执行与该字母一起使用的命令(或选择相应的选项)

    空格键 如果活动选项是复选框,则选中或清除该复选框

    Backspace 如果在“另存为”或“打开”对话框中选中某个文件夹,则打开上一级文件夹

    箭头键 如果活动选项是一组选项按钮,则选择某个按钮

    文件资源管理器键盘快捷方式

    按此键 执行此操作

    Alt + D 选择地址栏

    Ctrl + E 选择搜索框

    Ctrl + F 选择搜索框

    Ctrl + N 打开新窗口

    Ctrl + W 关闭活动窗口

    Ctrl + 鼠标滚轮 更改文件和文件夹图标的大小及外观

    Ctrl + Shift + E 显示选定文件夹上的所有文件夹

    Ctrl + Shift + N 创建一个新文件夹

    Num Lock + 星号 (*) 显示选定文件夹下的所有子文件夹

    Num Lock + 加号 (+) 显示选定文件夹的内容

    Num Lock + 减号 (-) 折叠选定文件夹

    Alt + P 显示预览面板

    Alt + Enter 打开选定项的“属性”对话框

    Alt + 向右键 查看下一个文件夹

    Alt + 向上键 查看上一级文件夹

    Alt + 向左键 查看上一个文件夹

    Backspace 查看上一个文件夹

    向右键 显示当前选项(如果它处于折叠状态),或者选择第一个子文件夹

    向左键 折叠当前选项(如果它处于展开状态),或者选择其父文件夹

    End 显示活动窗口的底端

    Home 显示活动窗口的顶端

    F11 最大化或最小化活动窗口

    虚拟桌面键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + Tab 打开任务视图

    Windows 徽标键 + Ctrl + D 添加虚拟桌面

    Windows 徽标键 + Ctrl + 向右键 在你于右侧创建的虚拟桌面之间进行切换

    Windows 徽标键 + Ctrl + 向左键 在你于左侧创建的虚拟桌面之间进行切换

    Windows 徽标键 + Ctrl + F4 关闭你正在使用的虚拟桌面

    任务栏键盘快捷方式

    按此键 执行此操作

    Shift + 单击某个任务栏按钮 打开某个应用,或快速打开应用的另一个实例

    Ctrl + Shift + 单击某个任务栏按钮 以管理员身份打开应用

    Shift + 右键单击某个任务栏按钮 显示该应用的窗口菜单

    Shift + 右键单击某个已分组的任务栏按钮 显示该组的窗口菜单

    Ctrl + 单击某个已分组的任务栏按钮 循环切换该组的窗口

    设置键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + I 打开设置

    Backspace 回退到“设置”主页

    在带有搜索框的任何页面上键入 搜索设置

    Windows 10 应用中的键盘快捷方式

    在许多应用(如照片、Groove 和地图)中,当你将鼠标指针悬停在某个按钮上时,工具提示中就会显示快捷方式。在其他应用(如画图、写字板和 Office)中,按 Alt 键或 F10 即可显示标记了键盘快捷方式的命令。如果菜单中某个字母有下划线,请同时按下 Alt 键和带有下划线的键,而不是选择该菜单项。例如,按 Ctrl + N 可在“画图”中创建新图片。

    温馨提示:

    借助触摸键盘,按下 Ctrl 键即可看到一些快捷方式。

    以下是 Windows 10 中内置的某些 Microsoft 应用中常见的键盘快捷方式。这包括 Microsoft Edge、计算器、游戏栏、Groove、地图、画图、照片、音乐和电视以及写字板。

    Microsoft Edge 键盘快捷方式

    按此键 执行此操作

    Ctrl + D 将当前站点添加到收藏夹或阅读列表

    Ctrl + I 打开“收藏夹”窗格

    Ctrl + J 打开“下载”窗格

    Ctrl + H 打开“历史记录”窗格

    Ctrl + P 打印当前页面

    Ctrl + F 在页面上查找

    Alt + C 打开 Cortana

    注意

    Cortana 仅在特定的国家/地区中提供,并且某些 Cortana 功能可能不支持随时随地使用。如果 Cortana 不可用或已关闭,你仍然可以使用搜索。

    Ctrl + Shift + R 进入阅读视图

    Ctrl + T 打开一个新选项卡

    Ctrl + Shift + T 重新打开最近关闭的选项卡

    Ctrl + W 或 Ctrl + F4 关闭活动选项卡

    Ctrl + K 复制选项卡

    Ctrl + N 打开新窗口

    Ctrl + Shift + P 打开新的 InPrivate 浏览窗口

    Ctrl + Tab 切换到下一个选项卡

    Ctrl + Shift + Tab 切换到上一个选项卡

    Ctrl + 1、2、3,依此类推 切换到特定选项卡号

    Ctrl + 9 切换到最后一个选项卡

    Ctrl + 加号 (+) 放大 (25%)

    Ctrl + 减号 (-) 缩小 (25%)

    Ctrl + 0 重置缩放级别

    Backspace 或 Alt + 向左键 返回

    Alt + 向右键 前进

    F5 或 Ctrl + R 刷新页面

    Esc 停止加载页面

    Ctrl + L 或 F4 或 Alt + D 选中地址栏

    Ctrl + Shift + L 在新选项卡中打开地址栏查询

    Ctrl + E 在地址栏中打开搜索查询

    Ctrl + Enter 在地址栏中将“www.”添加到所键入文本的开头,将“.com”添加到所键入文本的末尾

    Ctrl + 单击 在新选项卡中打开链接

    Ctrl + Shift + 单击 在新选项卡中打开链接并切换到该选项卡

    Alt + Shift + 单击 在新窗口中打开链接

    Ctrl + Shift + M 开始创建 Web 笔记

    Ctrl + Alt + M 将所选内容复制到 Web 笔记中

    F12 打开 F12 开发人员工具

    Ctrl + U 查看源

    F6 在网页内容和地址栏之间切换焦点

    F7 为活动选项卡打开“插入光标浏览”

    Ctrl + Shift + Delete 显示用于清除浏览数据的控件

    “计算器”键盘快捷方式

    按此键 执行此操作

    Alt + 1 切换到“标准型”模式

    Alt + 2 切换到“科学型”模式

    Alt + 3 切换到“程序员”模式

    Ctrl + M 存储在内存中

    Ctrl + P 添加到内存

    Ctrl + Q 从内存中减去

    Ctrl + R 从内存中重新调用

    Ctrl + L 清除内存

    F9 选择 ±

    R 选择 1/x

    @ 求平方根

    Delete 选择 CE

    Ctrl + H 打开或关闭计算历史记录

    向上键 在“历史记录”列表中上移

    向下键 在“历史记录”列表中下移

    Ctrl + Shift + D 清除历史记录

    F3 在“科学型”模式下选择 DEG

    F4 在“科学型”模式下选择 RAD

    F5 在“科学型”模式下选择 GRAD

    Ctrl + G 在“科学型”模式下选择 10x

    Ctrl + O 在“科学型”模式下选择 cosh

    Ctrl + S 在“科学型”模式下选择 sinh

    Ctrl + T 在“科学型”模式下选择 tanh

    Shift + S 在“科学型”模式下选择 sin-1

    Shift + O 在“科学型”模式下选择 cos-1

    Shift + T 在“科学型”模式下选择“tan-1”

    Ctrl + Y 在“科学型”模式下选择 y√x

    D 在“科学型”模式下选择 Mod

    L 在“科学型”模式下选择 log

    M 在“科学型”模式下选择 dms

    N 在“科学型”模式下选择 ln

    Ctrl + N 在“科学型”模式下选择 ex

    O 在“科学型”模式下选择 cos

    P 在“科学型”模式下选择 Pi

    Q 在“科学型”模式下选择 x2

    S 在“科学型”模式下选择 sin

    T 在“科学型”模式下选择 tan

    V 在“科学型”模式下选择 F-E

    X 在“科学型”模式下选择 Exp

    Y, ^ 在“科学型”模式下选择 xy 在“科学型”模式下选择 x3

    ; 在“科学型”模式下选择 Int

    ! 在“科学型”模式下选择 n!

    F2 在“程序员”模式下选择 DWORD

    F3 在“程序员”模式下选择 WORD

    F4 在“程序员”模式下选择 BYTE

    F5 在“程序员”模式下选择 HEX

    F6 在“程序员”模式下选择 DEC

    F7 在“程序员”模式下选择 OCT

    F8 在“程序员”模式下选择 BIN

    F12 在“程序员”模式下选择 QWORD

    A-F 在“程序员”模式下选择 A-F

    J 在“程序员”模式下选择 RoL

    K 在“程序员”模式下选择 RoR

    < 在“程序员”模式下选择 Lsh

    在“程序员”模式下选择 Rsh

    % 在“程序员”模式下选择 Mod

    | 在“程序员”模式下选择 Or

    ^ 在“程序员”模式下选择 Xor

    ~ 在“程序员”模式下选择 Not

    & 在“程序员”模式下选择 And

    空格键 在“程序员”模式下切换位值

    游戏栏键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + G 打开游戏栏(当游戏处于打开状态时)

    Windows 徽标键 + Alt + G 录制最后 30 秒

    Windows 徽标键 + Alt + R 开始或停止录制

    Windows 徽标键 + Alt + Print Screen 获取你的游戏的屏幕截图

    Windows 徽标键 + Alt + T 显示/隐藏录制计时器

    Groove 键盘快捷方式

    按此键 执行此操作

    Ctrl + P 播放或暂停

    Ctrl + F 跳到下一曲

    Ctrl + B 重新播放当前歌曲/跳到上一曲

    F9 调高音量

    F8 调低音量

    F7 静音

    Ctrl + Enter 选择项目并进入选择模式

    Ctrl + A 全选

    Delete 删除选定项

    Ctrl + Shift + P 播放选定项

    Ctrl + T 打开或关闭“重复播放”

    Ctrl + H 打开或关闭“无序播放”

    Ctrl + Q 搜索

    “地图”键盘快捷方式

    按此键 执行此操作

    箭头键 按任意方向平移地图

    Ctrl + 加号或减号键(+ 或 -) 放大或缩小

    Ctrl + 向左键或向右键 旋转

    Ctrl + 向上键或向下键 倾斜

    或 – 键 在 3D 城市视图中放大或缩小

    Page Up 或 Page Down 在 3D 城市视图中推远或拉近

    Ctrl + Y 在鸟瞰图和道路视图之间切换地图视图

    Ctrl + Home 在你的当前位置上居中放置地图

    Ctrl + D 获取路线

    Ctrl + F 搜索

    Ctrl + M 最小化活动选项卡

    Ctrl + P 打印

    Ctrl + T 显示或隐藏路况

    Backspace 返回

    Ctrl + H 共享

    Ctrl + L 将焦点移动到地图

    Ctrl + W 关闭活动选项卡

    Ctrl + Tab 转到下一个选项卡

    Ctrl + Shift + Tab 转到上一个选项卡

    Ctrl + S 显示或隐藏街道

    Ctrl + C 复制到剪贴板

    “电影和电视”键盘快捷方式

    按此键 执行此操作

    Alt + Enter 全屏播放

    Esc 退出全屏

    Enter 选择焦点中的内容

    空格键

    Ctrl + P 播放或暂停(当视频处于焦点中时)

    Alt + 向左键

    Windows 徽标键 + Backspace 返回

    Ctrl + T 打开或关闭“重复播放”

    F7 静音

    F8 调低音量

    F9 调高音量

    “画图”键盘快捷方式

    按此键 执行此操作

    F11 以全屏模式查看图片

    F12 将此图片另存为新文件

    Ctrl + A 选择整个图片

    Ctrl + B 将所选文本改为粗体

    Ctrl + C 将选择内容复制到剪贴板

    Ctrl + E 打开“属性”对话框

    Ctrl + G 显示或隐藏网格线

    Ctrl + I 将所选文本改为斜体

    Ctrl + N 创建新图片

    Ctrl + O 打开现有图片

    Ctrl + P 打印图片

    Ctrl + R 显示或隐藏标尺

    Ctrl + S 将更改保存到图片

    Ctrl + U 为所选文本添加下划线

    Ctrl + V 从剪贴板粘贴选择内容

    Ctrl + W 打开“调整大小和扭曲”对话框

    Ctrl + X 剪切选择内容

    Ctrl + Y 恢复更改

    Ctrl + Z 撤销更改

    Ctrl + 加号 (+) 将画笔、直线或形状轮廓的宽度增加一个像素

    Ctrl + 减号 (-) 将画笔、直线或形状轮廓的宽度减少一个像素

    Ctrl + Page Up 放大

    Ctrl + Page Down 缩小

    Alt + F4 关闭图片及其“画图”窗口

    向右键 将选择内容或活动形状向右移动一个像素

    向左键 将选择内容或活动形状向左移动一个像素

    向下键 将选择内容或活动形状向下移动一个像素

    向上键 将选择内容或活动形状向上移动一个像素

    Shift + F10 显示上下文菜单

    “照片”键盘快捷方式

    按此键 执行此操作

    空格键(在“集锦”中) 选择项目并进入选择模式

    Enter(从选择模式) 在处于选择模式下时选择项目

    空格键(查看照片) 显示或隐藏命令

    空格键(查看视频) 播放或暂停视频

    箭头键(在集锦中) 向上、向下、向左或向右滚动

    左箭头或右箭头键(位于单个项目或幻灯片放映上) 显示下一个或上一个项目

    箭头键(位于缩放的照片上) 在照片内移动

    Ctrl + 加号或减号(+ 或 -) 放大或缩小(查看照片时)

    Ctrl + 0 重置照片的缩放

    Esc 返回到上一个屏幕

    Ctrl + S 保存

    Ctrl + P 打印

    Ctrl + C 复制

    Ctrl + R(查看或编辑) 旋转照片

    E(查看照片) 增强照片效果

    Ctrl + Z(编辑) 撤销更改

    Ctrl + Y(编辑) 恢复更改

    Ctrl + /(编辑) 查看原件

    Shift + 箭头键 调整裁剪或选择性对焦区域的大小

    Ctrl + 箭头键 移动裁剪或选择性对焦区域

    F5(查看项目) 开始幻灯片放映

    Alt + Enter 查看文件信息

    Ctrl + L 设置为锁屏界面

    Ctrl + N(在“相册”视图中) 创建新相册

    Ctrl + R(在“相册”视图中) 删除相册

    Ctrl + D 将选定项添加到相册

    Ctrl + U 从相册中删除选定项

    “写字板”键盘快捷方式

    按此键 执行此操作

    F3 在“查找”对话框中搜索文本的下一个实例

    F12 将此文档另存为新文件

    Ctrl + 1 设置单倍行距

    Ctrl + 2 设置双倍行距

    Ctrl + 5 将行距设置为 1.5

    Ctrl + A 全选

    Ctrl + B 将所选文本改为粗体

    Ctrl + C 将选择内容复制到剪贴板

    Ctrl + D 插入 Microsoft 绘图

    Ctrl + E 向中心对齐文本

    Ctrl + F 在文档中搜索文本

    Ctrl + H 在文档中替换文本

    Ctrl + I 将所选文本改为斜体

    Ctrl + J 两端对齐文本

    Ctrl + L 向左对齐文本

    Ctrl + N 创建新文档

    Ctrl + O 打开现有文档

    Ctrl + P 打印文档

    Ctrl + R 向右对齐文本

    Ctrl + S 将更改保存到文档

    Ctrl + U 为所选文本添加下划线

    Ctrl + V 从剪贴板粘贴选择内容

    Ctrl + X 剪切选择内容

    Ctrl + Y 恢复更改

    Ctrl + Z 撤销更改

    Ctrl + 等于号 (=) 使所选文本成为下标

    Ctrl + Shift + 等于号 (=) 使所选文本成为上标

    Ctrl + Shift + 大于号 (>) 增加字体大小

    Ctrl + Shift + 小于号 (<) 减小字体大小

    Ctrl + Shift + A 将字符更改为全部使用大写字母

    Ctrl + Shift + L 更改项目符号样式

    Ctrl + 向左键 将光标向左移动一个字

    Ctrl + 向右键 将光标向右移动一个字

    Ctrl + 向上键 将光标移动到上一行

    Ctrl + 向下键 将光标移动到下一行

    Ctrl + Home 移动到文档的开头

    Ctrl + End 移动到文档的末尾

    Ctrl + Page Up 向上移动一个页面

    Ctrl + Page Down 向下移动一个页面

    Ctrl + Delete 删除下一个字

    Alt + F4 关闭“写字板”

    Shift + F10 显示上下文菜单

    Windows 10 用于辅助功能的 Windows 键盘快捷方式

    辅助功能快捷方式可帮助你将电脑与键盘或辅助设备结合使用。下面是 Windows 10 中辅助技术的键盘快捷方式列表,包括“放大镜”、“高对比度”等。

    “放大镜”键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + 加号 (+) 或减号 (-) 放大或缩小

    Ctrl + Alt + 空格键 以全屏模式预览桌面

    Ctrl + ALT + D 切换到停靠模式

    Ctrl + ALT + F 切换到全屏模式

    Ctrl + ALT + I 反色

    Ctrl + ALT + L 切换到镜头模式

    Ctrl + ALT + R 调整镜头大小

    Ctrl + Alt + 箭头键 按箭头键的方向平移

    Windows 徽标键 + Esc 退出“放大镜”

    其他辅助功能键盘快捷方式

    按此键 执行此操作

    按右 Shift 八秒钟 打开和关闭筛选键

    左 Alt + 左 Shift + Print Screen 打开或关闭高对比度

    左 Alt + 左 Shift + Num Lock 打开或关闭鼠标键

    按 Shift 五次 打开或关闭粘滞键

    按 Num Lock 五秒钟 打开或关闭切换键

    Windows 徽标键 + U 打开“轻松使用设置中心”

    查看原文 title: Windowsの快捷键 date: 2020-09-06 21:45:32 updated: 2020-09-06 21:48:15 categories: Windows 10 tags: - win10 - windows,快捷键,PC,电脑

    Ctrl + X 剪切选定项

    Ctrl + C(或 Ctrl + Insert) 复制选定项

    Ctrl + V(或 Shift + Insert) 粘贴选定项

    Ctrl + Z 撤消操作

    Alt + Tab 在打开的应用之间切换

    Alt + F4 关闭活动项,或者退出活动应用

    Windows 徽标键 + L 锁定电脑

    Windows 徽标键 + D 显示和隐藏桌面

    F2 重命名选定项

    F3 在文件资源管理器中搜索文件或文件夹

    F4 在文件资源管理器中显示地址栏列表

    F5 刷新活动窗口

    F6 在窗口中或桌面上循环浏览屏幕元素

    F10 激活活动应用中的菜单栏

    Alt + F8 在登录屏幕上显示密码

    Alt + Esc 以项目打开的顺序循环切换项目

    Alt + 带下划线的字母 执行该字母对应的命令

    Alt + Enter 显示选定项的属性

    Alt + 空格键 为活动窗口打开快捷菜单

    Alt + 向左键 后退

    Alt + 向右键 前进

    Alt + Page Up 向上移动一个屏幕

    Alt + Page Down 向下移动一个屏幕

    Ctrl + F4 关闭活动文档(在全屏模式和允许你同时打开多个文档的应用中)

    Ctrl + A 选择文档或窗口中的所有项目

    Ctrl + D(或 Delete) 删除选定项并将其移动到“回收站”

    Ctrl + R(或 F5) 刷新活动窗口

    Ctrl + Y 重做操作

    Ctrl + 向右键 将光标移动到下一个字词的起始处

    Ctrl + 向左键 将光标移动到上一个字词的起始处

    Ctrl + 向下键 将光标移动到下一个段落的起始处

    Ctrl + 向上键 将光标移动到上一个段落的起始处

    Ctrl + Alt + Tab 使用箭头键在所有打开的应用之间切换

    Ctrl + Alt + Shift + 箭头键 当分组或磁贴在“开始”菜单中获得焦点时,请将其按指定方向移动

    Ctrl + 箭头键(用来移动到某个项目)+ 空格键 在窗口中或桌面上选择多个单独的项目

    Ctrl + Shift 加某个箭头键 选择文本块

    Ctrl + Esc 打开“开始”屏幕

    Ctrl + Shift + Esc 打开任务管理器

    Ctrl + Shift 在提供了多个键盘布局时切换键盘布局

    Ctrl + 空格键 打开或关闭中文输入法编辑器 (IME)

    Shift + F10 显示选定项的快捷菜单

    Shift 加任意箭头键 在窗口中或桌面上选择多个项目,或者在文档中选择文本

    Shift + Delete 无需先将选定项移动到“回收站”,直接将其删除

    向右键 打开右侧的下一个菜单,或者打开子菜单

    向左键 打开左侧的下一个菜单,或者关闭子菜单

    Esc 停止或退出当前任务

    Windows 徽标键键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 打开或关闭“开始”菜单

    Windows 徽标键 + A 打开“操作中心”

    Windows 徽标键 + B 将焦点设置到通知区域

    Windows 徽标键 + Shift + C 在侦听模式下打开 Cortana

    注意: Cortana 仅在某些国家/地区提供,并且某些 Cortana 功能可能无法随时随地使用。如果 Cortana 不可用或已关闭,你仍然可以使用搜索。

    Windows 徽标键 + D 显示和隐藏桌面

    Windows 徽标键 + Alt + D 显示和隐藏桌面上的日期和时间

    Windows 徽标键 + E 打开“文件资源管理器”

    Windows 徽标键 + F 打开“反馈中心”

    Windows 徽标键 + G 打开游戏栏(当游戏处于打开状态时)

    Windows 徽标键 + H 打开“共享”超级按钮

    Windows 徽标键 + I 打开“设置”

    Windows 徽标键 + J 将焦点设置到 Windows 提示(如果可用)。

    出现 Windows 提示时,将焦点移到该提示。 再次按这些键盘快捷方式,将焦点移到定位 Windows 提示的屏幕上的元素。

    Windows 徽标键 + K 打开“连接”快速操作

    Windows 徽标键 + L 锁定电脑或切换帐户

    Windows 徽标键 + M 最小化所有窗口

    Windows 徽标键 + O 锁定设备方向

    Windows 徽标键 + P 选择演示显示模式

    Windows 徽标键 + R 打开“运行”对话框

    Windows 徽标键 + S 打开搜素

    Windows 徽标键 + T 在任务栏上循环切换应用

    Windows 徽标键 + U 打开“轻松使用设置中心”

    Windows 徽标键 + V 循环切换通知

    Windows 徽标键 + Shift + V 以相反的顺序循环切换通知

    Windows 徽标键 + X 打开“快速链接”菜单

    Windows 徽标键 + Z 显示在以全屏模式呈现的应用中可用的命令

    Windows 徽标键 + 逗号 (,) 临时快速查看桌面

    Windows 徽标键 + Pause 显示“系统属性”对话框

    Windows 徽标键 + Ctrl + F 搜索电脑(如果你位于网络中)

    Windows 徽标键 + Shift + M 将最小化的窗口还原到桌面

    Windows 徽标键 + 数字 打开桌面,并启动固定到任务栏的位于该数字所表示位置的应用。如果该应用已在运行,则切换到该应用。

    Windows 徽标键 + Shift + 数字 打开桌面,并启动固定到任务栏的位于该数字所表示位置的应用的新实例

    Windows 徽标键 + Ctrl + 数字 打开桌面,并切换到固定到任务栏的位于该数字所表示位置的应用的最后一个活动窗口

    Windows 徽标键 + Alt + 数字 打开桌面,并打开固定到任务栏的位于该数字所表示位置的应用的跳转列表

    Windows 徽标键 + Ctrl + Shift + 数字 打开桌面,并以管理员身份打开位于任务栏上给定位置的应用的新实例

    Windows 徽标键 + Tab 打开“任务视图”

    Windows 徽标键 + 向上键 最大化窗口

    Windows 徽标键 + 向下键 从屏幕中删除当前应用或最小化桌面窗口

    Windows 徽标键 + 向左键 将应用或桌面窗口最大化到屏幕左侧

    Windows 徽标键 + 向右键 将应用或桌面窗口最大化到屏幕右侧

    Windows 徽标键 + Home 最小化除活动桌面窗口以外的所有窗口(在第二道笔划时还原所有窗口)

    Windows 徽标键 + Shift + 向上键 将桌面窗口拉伸到屏幕的顶部和底部

    Windows 徽标键 + Shift + 向下键 在垂直方向上还原/最小化活动桌面窗口,同时保持宽度不变

    Windows 徽标键 + Shift + 向左键或向右键 将桌面中的应用或窗口从一个监视器移动到另一个监视器

    Windows 徽标键 + 空格键 切换输入语言和键盘布局

    Windows 徽标键 + Ctrl + 空格键 更改为以前选择的输入

    Windows 徽标键 + Enter 打开“讲述人”

    Windows 徽标键 + 正斜杠 (/) 启动 IME 重新转换

    Windows 徽标键 + 加号 (+) 或减号 (-) 使用“放大镜”放大或缩小

    Windows 徽标键 + Esc 退出“放大镜”

    命令提示符键盘快捷方式

    按此键 执行此操作

    Ctrl + C(或 Ctrl + Insert) 复制选定文本

    Ctrl + V(或 Shift + Insert) 粘贴选定文本

    Ctrl + M 进入标记模式

    Alt + 选择键 在阻止模式中开始选择

    箭头键 按指定方向移动光标

    Page Up 将光标向上移动一页

    Page Down 将光标向下移动一页

    Ctrl + Home(标记模式) 将光标移动到缓冲区的起始处

    Ctrl + End(标记模式) 将光标移动到缓冲区的末尾

    Ctrl + 向上键 在输出历史记录中向上移动一行

    Ctrl + 向下键 在输出历史记录中向下移动一行

    Ctrl + Home(历史记录导航) 如果命令行为空,则将视口移动到缓冲区顶部。否则,删除命令行中光标左侧的所有字符。

    Ctrl + End(历史记录导航) 如果命令行为空,则将视口移动到命令行。否则,删除命令行中光标右侧的所有字符。

    对话框键盘快捷方式

    按此键 执行此操作

    F4 显示活动列表中的项目

    Ctrl + Tab 在选项卡上向前移动

    Ctrl + Shift + Tab 在选项卡上向后移动

    Ctrl + 数字(数字 1 到 9) 移动到第 n 个选项卡

    Tab 在选项上向前移动

    Shift + Tab 在选项上向后移动

    Alt + 带下划线的字母 执行与该字母一起使用的命令(或选择相应的选项)

    空格键 如果活动选项是复选框,则选中或清除该复选框

    Backspace 如果在“另存为”或“打开”对话框中选中某个文件夹,则打开上一级文件夹

    箭头键 如果活动选项是一组选项按钮,则选择某个按钮

    文件资源管理器键盘快捷方式

    按此键 执行此操作

    Alt + D 选择地址栏

    Ctrl + E 选择搜索框

    Ctrl + F 选择搜索框

    Ctrl + N 打开新窗口

    Ctrl + W 关闭活动窗口

    Ctrl + 鼠标滚轮 更改文件和文件夹图标的大小及外观

    Ctrl + Shift + E 显示选定文件夹上的所有文件夹

    Ctrl + Shift + N 创建一个新文件夹

    Num Lock + 星号 (*) 显示选定文件夹下的所有子文件夹

    Num Lock + 加号 (+) 显示选定文件夹的内容

    Num Lock + 减号 (-) 折叠选定文件夹

    Alt + P 显示预览面板

    Alt + Enter 打开选定项的“属性”对话框

    Alt + 向右键 查看下一个文件夹

    Alt + 向上键 查看上一级文件夹

    Alt + 向左键 查看上一个文件夹

    Backspace 查看上一个文件夹

    向右键 显示当前选项(如果它处于折叠状态),或者选择第一个子文件夹

    向左键 折叠当前选项(如果它处于展开状态),或者选择其父文件夹

    End 显示活动窗口的底端

    Home 显示活动窗口的顶端

    F11 最大化或最小化活动窗口

    虚拟桌面键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + Tab 打开任务视图

    Windows 徽标键 + Ctrl + D 添加虚拟桌面

    Windows 徽标键 + Ctrl + 向右键 在你于右侧创建的虚拟桌面之间进行切换

    Windows 徽标键 + Ctrl + 向左键 在你于左侧创建的虚拟桌面之间进行切换

    Windows 徽标键 + Ctrl + F4 关闭你正在使用的虚拟桌面

    任务栏键盘快捷方式

    按此键 执行此操作

    Shift + 单击某个任务栏按钮 打开某个应用,或快速打开应用的另一个实例

    Ctrl + Shift + 单击某个任务栏按钮 以管理员身份打开应用

    Shift + 右键单击某个任务栏按钮 显示该应用的窗口菜单

    Shift + 右键单击某个已分组的任务栏按钮 显示该组的窗口菜单

    Ctrl + 单击某个已分组的任务栏按钮 循环切换该组的窗口

    设置键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + I 打开设置

    Backspace 回退到“设置”主页

    在带有搜索框的任何页面上键入 搜索设置

    Windows 10 应用中的键盘快捷方式

    在许多应用(如照片、Groove 和地图)中,当你将鼠标指针悬停在某个按钮上时,工具提示中就会显示快捷方式。在其他应用(如画图、写字板和 Office)中,按 Alt 键或 F10 即可显示标记了键盘快捷方式的命令。如果菜单中某个字母有下划线,请同时按下 Alt 键和带有下划线的键,而不是选择该菜单项。例如,按 Ctrl + N 可在“画图”中创建新图片。

    温馨提示:

    借助触摸键盘,按下 Ctrl 键即可看到一些快捷方式。

    以下是 Windows 10 中内置的某些 Microsoft 应用中常见的键盘快捷方式。这包括 Microsoft Edge、计算器、游戏栏、Groove、地图、画图、照片、音乐和电视以及写字板。

    Microsoft Edge 键盘快捷方式

    按此键 执行此操作

    Ctrl + D 将当前站点添加到收藏夹或阅读列表

    Ctrl + I 打开“收藏夹”窗格

    Ctrl + J 打开“下载”窗格

    Ctrl + H 打开“历史记录”窗格

    Ctrl + P 打印当前页面

    Ctrl + F 在页面上查找

    Alt + C 打开 Cortana

    注意

    Cortana 仅在特定的国家/地区中提供,并且某些 Cortana 功能可能不支持随时随地使用。如果 Cortana 不可用或已关闭,你仍然可以使用搜索。

    Ctrl + Shift + R 进入阅读视图

    Ctrl + T 打开一个新选项卡

    Ctrl + Shift + T 重新打开最近关闭的选项卡

    Ctrl + W 或 Ctrl + F4 关闭活动选项卡

    Ctrl + K 复制选项卡

    Ctrl + N 打开新窗口

    Ctrl + Shift + P 打开新的 InPrivate 浏览窗口

    Ctrl + Tab 切换到下一个选项卡

    Ctrl + Shift + Tab 切换到上一个选项卡

    Ctrl + 1、2、3,依此类推 切换到特定选项卡号

    Ctrl + 9 切换到最后一个选项卡

    Ctrl + 加号 (+) 放大 (25%)

    Ctrl + 减号 (-) 缩小 (25%)

    Ctrl + 0 重置缩放级别

    Backspace 或 Alt + 向左键 返回

    Alt + 向右键 前进

    F5 或 Ctrl + R 刷新页面

    Esc 停止加载页面

    Ctrl + L 或 F4 或 Alt + D 选中地址栏

    Ctrl + Shift + L 在新选项卡中打开地址栏查询

    Ctrl + E 在地址栏中打开搜索查询

    Ctrl + Enter 在地址栏中将“www.”添加到所键入文本的开头,将“.com”添加到所键入文本的末尾

    Ctrl + 单击 在新选项卡中打开链接

    Ctrl + Shift + 单击 在新选项卡中打开链接并切换到该选项卡

    Alt + Shift + 单击 在新窗口中打开链接

    Ctrl + Shift + M 开始创建 Web 笔记

    Ctrl + Alt + M 将所选内容复制到 Web 笔记中

    F12 打开 F12 开发人员工具

    Ctrl + U 查看源

    F6 在网页内容和地址栏之间切换焦点

    F7 为活动选项卡打开“插入光标浏览”

    Ctrl + Shift + Delete 显示用于清除浏览数据的控件

    “计算器”键盘快捷方式

    按此键 执行此操作

    Alt + 1 切换到“标准型”模式

    Alt + 2 切换到“科学型”模式

    Alt + 3 切换到“程序员”模式

    Ctrl + M 存储在内存中

    Ctrl + P 添加到内存

    Ctrl + Q 从内存中减去

    Ctrl + R 从内存中重新调用

    Ctrl + L 清除内存

    F9 选择 ±

    R 选择 1/x

    @ 求平方根

    Delete 选择 CE

    Ctrl + H 打开或关闭计算历史记录

    向上键 在“历史记录”列表中上移

    向下键 在“历史记录”列表中下移

    Ctrl + Shift + D 清除历史记录

    F3 在“科学型”模式下选择 DEG

    F4 在“科学型”模式下选择 RAD

    F5 在“科学型”模式下选择 GRAD

    Ctrl + G 在“科学型”模式下选择 10x

    Ctrl + O 在“科学型”模式下选择 cosh

    Ctrl + S 在“科学型”模式下选择 sinh

    Ctrl + T 在“科学型”模式下选择 tanh

    Shift + S 在“科学型”模式下选择 sin-1

    Shift + O 在“科学型”模式下选择 cos-1

    Shift + T 在“科学型”模式下选择“tan-1”

    Ctrl + Y 在“科学型”模式下选择 y√x

    D 在“科学型”模式下选择 Mod

    L 在“科学型”模式下选择 log

    M 在“科学型”模式下选择 dms

    N 在“科学型”模式下选择 ln

    Ctrl + N 在“科学型”模式下选择 ex

    O 在“科学型”模式下选择 cos

    P 在“科学型”模式下选择 Pi

    Q 在“科学型”模式下选择 x2

    S 在“科学型”模式下选择 sin

    T 在“科学型”模式下选择 tan

    V 在“科学型”模式下选择 F-E

    X 在“科学型”模式下选择 Exp

    Y, ^ 在“科学型”模式下选择 xy 在“科学型”模式下选择 x3

    ; 在“科学型”模式下选择 Int

    ! 在“科学型”模式下选择 n!

    F2 在“程序员”模式下选择 DWORD

    F3 在“程序员”模式下选择 WORD

    F4 在“程序员”模式下选择 BYTE

    F5 在“程序员”模式下选择 HEX

    F6 在“程序员”模式下选择 DEC

    F7 在“程序员”模式下选择 OCT

    F8 在“程序员”模式下选择 BIN

    F12 在“程序员”模式下选择 QWORD

    A-F 在“程序员”模式下选择 A-F

    J 在“程序员”模式下选择 RoL

    K 在“程序员”模式下选择 RoR

    < 在“程序员”模式下选择 Lsh

    在“程序员”模式下选择 Rsh

    % 在“程序员”模式下选择 Mod

    | 在“程序员”模式下选择 Or

    ^ 在“程序员”模式下选择 Xor

    ~ 在“程序员”模式下选择 Not

    & 在“程序员”模式下选择 And

    空格键 在“程序员”模式下切换位值

    游戏栏键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + G 打开游戏栏(当游戏处于打开状态时)

    Windows 徽标键 + Alt + G 录制最后 30 秒

    Windows 徽标键 + Alt + R 开始或停止录制

    Windows 徽标键 + Alt + Print Screen 获取你的游戏的屏幕截图

    Windows 徽标键 + Alt + T 显示/隐藏录制计时器

    Groove 键盘快捷方式

    按此键 执行此操作

    Ctrl + P 播放或暂停

    Ctrl + F 跳到下一曲

    Ctrl + B 重新播放当前歌曲/跳到上一曲

    F9 调高音量

    F8 调低音量

    F7 静音

    Ctrl + Enter 选择项目并进入选择模式

    Ctrl + A 全选

    Delete 删除选定项

    Ctrl + Shift + P 播放选定项

    Ctrl + T 打开或关闭“重复播放”

    Ctrl + H 打开或关闭“无序播放”

    Ctrl + Q 搜索

    “地图”键盘快捷方式

    按此键 执行此操作

    箭头键 按任意方向平移地图

    Ctrl + 加号或减号键(+ 或 -) 放大或缩小

    Ctrl + 向左键或向右键 旋转

    Ctrl + 向上键或向下键 倾斜

    或 – 键 在 3D 城市视图中放大或缩小

    Page Up 或 Page Down 在 3D 城市视图中推远或拉近

    Ctrl + Y 在鸟瞰图和道路视图之间切换地图视图

    Ctrl + Home 在你的当前位置上居中放置地图

    Ctrl + D 获取路线

    Ctrl + F 搜索

    Ctrl + M 最小化活动选项卡

    Ctrl + P 打印

    Ctrl + T 显示或隐藏路况

    Backspace 返回

    Ctrl + H 共享

    Ctrl + L 将焦点移动到地图

    Ctrl + W 关闭活动选项卡

    Ctrl + Tab 转到下一个选项卡

    Ctrl + Shift + Tab 转到上一个选项卡

    Ctrl + S 显示或隐藏街道

    Ctrl + C 复制到剪贴板

    “电影和电视”键盘快捷方式

    按此键 执行此操作

    Alt + Enter 全屏播放

    Esc 退出全屏

    Enter 选择焦点中的内容

    空格键

    Ctrl + P 播放或暂停(当视频处于焦点中时)

    Alt + 向左键

    Windows 徽标键 + Backspace 返回

    Ctrl + T 打开或关闭“重复播放”

    F7 静音

    F8 调低音量

    F9 调高音量

    “画图”键盘快捷方式

    按此键 执行此操作

    F11 以全屏模式查看图片

    F12 将此图片另存为新文件

    Ctrl + A 选择整个图片

    Ctrl + B 将所选文本改为粗体

    Ctrl + C 将选择内容复制到剪贴板

    Ctrl + E 打开“属性”对话框

    Ctrl + G 显示或隐藏网格线

    Ctrl + I 将所选文本改为斜体

    Ctrl + N 创建新图片

    Ctrl + O 打开现有图片

    Ctrl + P 打印图片

    Ctrl + R 显示或隐藏标尺

    Ctrl + S 将更改保存到图片

    Ctrl + U 为所选文本添加下划线

    Ctrl + V 从剪贴板粘贴选择内容

    Ctrl + W 打开“调整大小和扭曲”对话框

    Ctrl + X 剪切选择内容

    Ctrl + Y 恢复更改

    Ctrl + Z 撤销更改

    Ctrl + 加号 (+) 将画笔、直线或形状轮廓的宽度增加一个像素

    Ctrl + 减号 (-) 将画笔、直线或形状轮廓的宽度减少一个像素

    Ctrl + Page Up 放大

    Ctrl + Page Down 缩小

    Alt + F4 关闭图片及其“画图”窗口

    向右键 将选择内容或活动形状向右移动一个像素

    向左键 将选择内容或活动形状向左移动一个像素

    向下键 将选择内容或活动形状向下移动一个像素

    向上键 将选择内容或活动形状向上移动一个像素

    Shift + F10 显示上下文菜单

    “照片”键盘快捷方式

    按此键 执行此操作

    空格键(在“集锦”中) 选择项目并进入选择模式

    Enter(从选择模式) 在处于选择模式下时选择项目

    空格键(查看照片) 显示或隐藏命令

    空格键(查看视频) 播放或暂停视频

    箭头键(在集锦中) 向上、向下、向左或向右滚动

    左箭头或右箭头键(位于单个项目或幻灯片放映上) 显示下一个或上一个项目

    箭头键(位于缩放的照片上) 在照片内移动

    Ctrl + 加号或减号(+ 或 -) 放大或缩小(查看照片时)

    Ctrl + 0 重置照片的缩放

    Esc 返回到上一个屏幕

    Ctrl + S 保存

    Ctrl + P 打印

    Ctrl + C 复制

    Ctrl + R(查看或编辑) 旋转照片

    E(查看照片) 增强照片效果

    Ctrl + Z(编辑) 撤销更改

    Ctrl + Y(编辑) 恢复更改

    Ctrl + /(编辑) 查看原件

    Shift + 箭头键 调整裁剪或选择性对焦区域的大小

    Ctrl + 箭头键 移动裁剪或选择性对焦区域

    F5(查看项目) 开始幻灯片放映

    Alt + Enter 查看文件信息

    Ctrl + L 设置为锁屏界面

    Ctrl + N(在“相册”视图中) 创建新相册

    Ctrl + R(在“相册”视图中) 删除相册

    Ctrl + D 将选定项添加到相册

    Ctrl + U 从相册中删除选定项

    “写字板”键盘快捷方式

    按此键 执行此操作

    F3 在“查找”对话框中搜索文本的下一个实例

    F12 将此文档另存为新文件

    Ctrl + 1 设置单倍行距

    Ctrl + 2 设置双倍行距

    Ctrl + 5 将行距设置为 1.5

    Ctrl + A 全选

    Ctrl + B 将所选文本改为粗体

    Ctrl + C 将选择内容复制到剪贴板

    Ctrl + D 插入 Microsoft 绘图

    Ctrl + E 向中心对齐文本

    Ctrl + F 在文档中搜索文本

    Ctrl + H 在文档中替换文本

    Ctrl + I 将所选文本改为斜体

    Ctrl + J 两端对齐文本

    Ctrl + L 向左对齐文本

    Ctrl + N 创建新文档

    Ctrl + O 打开现有文档

    Ctrl + P 打印文档

    Ctrl + R 向右对齐文本

    Ctrl + S 将更改保存到文档

    Ctrl + U 为所选文本添加下划线

    Ctrl + V 从剪贴板粘贴选择内容

    Ctrl + X 剪切选择内容

    Ctrl + Y 恢复更改

    Ctrl + Z 撤销更改

    Ctrl + 等于号 (=) 使所选文本成为下标

    Ctrl + Shift + 等于号 (=) 使所选文本成为上标

    Ctrl + Shift + 大于号 (>) 增加字体大小

    Ctrl + Shift + 小于号 (<) 减小字体大小

    Ctrl + Shift + A 将字符更改为全部使用大写字母

    Ctrl + Shift + L 更改项目符号样式

    Ctrl + 向左键 将光标向左移动一个字

    Ctrl + 向右键 将光标向右移动一个字

    Ctrl + 向上键 将光标移动到上一行

    Ctrl + 向下键 将光标移动到下一行

    Ctrl + Home 移动到文档的开头

    Ctrl + End 移动到文档的末尾

    Ctrl + Page Up 向上移动一个页面

    Ctrl + Page Down 向下移动一个页面

    Ctrl + Delete 删除下一个字

    Alt + F4 关闭“写字板”

    Shift + F10 显示上下文菜单

    Windows 10 用于辅助功能的 Windows 键盘快捷方式

    辅助功能快捷方式可帮助你将电脑与键盘或辅助设备结合使用。下面是 Windows 10 中辅助技术的键盘快捷方式列表,包括“放大镜”、“高对比度”等。

    “放大镜”键盘快捷方式

    按此键 执行此操作

    Windows 徽标键 + 加号 (+) 或减号 (-) 放大或缩小

    Ctrl + Alt + 空格键 以全屏模式预览桌面

    Ctrl + ALT + D 切换到停靠模式

    Ctrl + ALT + F 切换到全屏模式

    Ctrl + ALT + I 反色

    Ctrl + ALT + L 切换到镜头模式

    Ctrl + ALT + R 调整镜头大小

    Ctrl + Alt + 箭头键 按箭头键的方向平移

    Windows 徽标键 + Esc 退出“放大镜”

    其他辅助功能键盘快捷方式

    按此键 执行此操作

    按右 Shift 八秒钟 打开和关闭筛选键

    左 Alt + 左 Shift + Print Screen 打开或关闭高对比度

    左 Alt + 左 Shift + Num Lock 打开或关闭鼠标键

    按 Shift 五次 打开或关闭粘滞键

    按 Num Lock 五秒钟 打开或关闭切换键

    Windows 徽标键 + U 打开“轻松使用设置中心”

    查看原文


    菜单
    主要内容
    

    书签 设置 文档

    MBRjun-Blog
    22 十二月 2022
    
    发布于 9 九月 2020
    作者 MBRjun
    mbrjun.cn
    

    标签

    书签栏/box-import-to
    

    高亮

    在文章中选择文本以创建新的高亮。 链接

    普通索引与唯一索引的选择问题 - MBR's Blog

    说到这个问题之前,首先我们需要先了解一下change buffer

    当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InooDB 会将这些更新操作缓存在 change buffer 中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer 中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

    需要说明的是,虽然名字叫作 change buffer,实际上它是可以持久化的数据。也就是说,change buffer 在内存中有拷贝,也会被写入到磁盘上。

    将 change buffer 中的操作应用到原数据页,得到最新结果的过程称为 merge。除了访问这个数据页会触发 merge 外,系统有后台线程会定期 merge。在数据库正常关闭(shutdown)的过程中,也会执行 merge 操作。

    显然,如果能够将更新操作先记录在 change buffer,减少读磁盘,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够避免占用内存,提高内存利用率。

    那么,什么条件下可以使用 change buffer 呢?

    对于唯一索引来说,所有的更新操作都要先判断这个操作是否违反唯一性约束。比如,要插入 (4,400) 这个记录,就要先判断现在表中是否已经存在 k=4 的记录,而这必须要将数据页读入内存才能判断。如果都已经读入到内存了,那直接更新内存会更快,就没必要使用 change buffer 了。

    因此,唯一索引的更新就不能使用 change buffer,实际上也只有普通索引可以使用。

    change buffer 用的是 buffer pool 里的内存,因此不能无限增大。change buffer 的大小,可以通过参数 innodb_change_buffer_max_size 来动态设置。这个参数设置为 50 的时候,表示 change buffer 的大小最多只能占用 buffer pool 的 50%。

    因为 merge 的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做 merge 之前,change buffer 记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。

    因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。

    回到我们开头的问题,普通索引和唯一索引应该怎么选择。其实,这两类索引在查询能力上是没差别的,主要考虑的是对更新性能的影响。所以,我建议你尽量选择普通索引。

    查看原文


    ECDSA 证书,即 ECC 证书,具有很多优势,例如效率更高,满足同等安全性的情况下密钥更短,下面详细列出了一部分优势:

    ECC 密钥更短,意味着 ECC 将占用更少的资源却有更高的性能;
    ECC 更易扩展,随着 RSA 密钥更长只会让 SSL/TLS 面临更多的麻烦;
    ECC 并不太容易受到量子计算机的安全威胁;
    256 位 ECC 安全性等同于 3072 位 RSA
    

    TCP BBR(Bottleneck Bandwidth and Round-trip propagation time)是由Google设计,于2016年发布的拥塞算法。

    该算法认为随着网络接口控制器逐渐进入千兆速度时,与缓冲膨胀相关的延迟相比丢包更应该被认为是识别拥塞的主要决定因素,所以基于延迟模型的拥塞控制算法(如BBR)会有更高的吞吐量和更低的延迟。 在大多数 Linux 发行版中,cubic 通常是默认设置,但我们发现 htcp 通常效果更好。如果 BBR 在您的系统上可用,您可能还想尝试它。 检查内核兼容

    首先检测内核是否支持 TCP BBR

    1

    sysctl net.ipv4.tcp_available_congestion_control | grep bbr

    如果支持,会有类似这样的返回

    1

    net.ipv4.tcp_available_congestion_control = reno cubic bbr

    如果没有任何返回,说明内核不支持,目前已知不支持 TCP BBR 的发行版有 PVE 开启 BBR

    1

    sysctl -w net.ipv4.tcp_congestion_control=bbr


    菜单
    主要内容
    

    书签 设置 文档

    MBRjun-Blog
    22 十二月 2022
    
    发布于 16 二月 2022
    作者 MBRjun
    mbrjun.cn
    

    标签

    25-03-02-chromium
    

    高亮

    在文章中选择文本以创建新的高亮。 链接

    Linux systemd 可预测的网络接口名 - MBR's Blog “可预测的网络接口名”英文“Redictable Network Interface Names”,简称“可预测接口名”英文“Redictable Interface Names” 从systemd v197 版本开始,systemd/udev 为所有的网络接口自动分配可预测,稳定的网络接口名(如:enp4s0d1v7)。 为什么要开发可预测接口名

    传统的接口命名方式是内核driver在探测到所有的接口之后,简单的按照数字编号分配作为eth的后缀的方式命名(如:eth0)。如果你的机器具有多个接口,每次重启系统,网卡的接口名都有可能变化。现在有一个物理网卡的接口名是 eth0,重启主机后,接口名就有可能变成 eth1

    在某些时候,网卡接口名不固定会出大问题,比如你正在配置链路聚合,但是你的多个网卡接口名每次重启都会变化,每次启动系统后,你都可能需要重新配置你的网络设置。又例如防火墙规则对不可预测的接口名非常敏感,接口名称改变会带来很大的安全隐患。

    为了解决接口名变化的问题,udev 曾支持通过 MAC 地址来绑定接口名,但是仍然存在一些问题。例如虚拟化环境的 MAC 地址就不是固定的。最大的问题是用户空间和内核空间在分配名字上存在竞争。所以这种支持很快就被去掉了。 后来,“可预测的网络接口名”的出现彻底解决了这个问题。 命名规则

    目前, systemd 有以下五种命名策略:

    传统的接口名 ethX 、wifiX……
    固件、BIOS提供的不可插拔的板载设备,接口名 enoX
    固件、BIOS提供的 PCIe 可热插拔设备,接口名 ensX
    根据硬件物理/地理地址,接口名 enpXsYdZvM
    根据硬件物理地址(MAC),接口名 enxFFFF0000AAAA
    

    最后一个策略不会被系统采用,除非用户手动选择,如果用户有自定义的 udev 规则,那么优先按照 udev 来执行。 通过阅读相关文档和源码,我们可以了解具体的命名方式,我以的我一张双口洋垃圾网卡 NVIDIA Mellanox 342A 端口 2 的接口名来举例

    1 2 3 4 5 6 7

    enp4s0d1v7 en - 前两个字母"en"说明这个接口是以太网接口 p - PCI Bus 号,"p4"意味着这是我机器上的第四张网卡 s - PCI Slot 号,大多数设备所有网卡都为 "s0" d - 设备端口号,"d0"意味着这是我这张网卡的第二个口 第一个口呢?第一个口不存在该参数 v - SR-IOV 虚拟功能(VF)编号,"v7"意味着这是该网口的第八个 VF

    优点

    接口名不会因为系统重启、硬件更改、系统更新而改变,在生产环境中这很棒! 缺点

    如果你的设备只有一个网口,你在对网口进行操作之前,你必须先查看网口的接口名,而不是使用 eth0 如何启用/禁用 启用

    确保/etc/systemd/network/99-default.link没有指向/dev/null
    确保没有使用/etc/systemd/network/中自定义的命名规则文件(扩展名为.link的文件)。
    在/etc/default/grup配置文件中,参数GRUB_CMDLINE_LINUX_DEFAULT=""中移除net.ifnames=0
    update-grub
    重启设备
    

    禁用

    ln -s /dev/null /etc/systemd/network/99-default.link
    重启设备
    

    菜单
    主要内容
    

    书签 设置 文档

    MBRjun-Blog
    22 十二月 2022
    
    发布于 8 四月 2022
    作者 MBRjun
    mbrjun.cn
    阅读约 10 分钟
    

    标签

    书签栏/box-import-to
    

    高亮

    在文章中选择文本以创建新的高亮。 链接

    TLS 1.3 和 0-RTT - MBR's Blog

    写一篇关于 TLS 1.3 的文章

    首先需要明确的是,同等情况下,TLS1.3 比 1.2 少一个 RTT 时间。

    客户端完成 TCP 握手需要一个 RTT 时间, TLS1.2 完成TLS 密钥协商需要两个 RTT 时间, TLS1.3 只需要一个 RTT 时间。 因此对于 https, 收到第一个 http 响应包,TLS1.2 需要 4 个 RTT 时间, TLS1.3 需要 3 个 RTT 时间。

    考虑 session 重用,根据数据表明,大部分的 TLS 的请求都在重用, TLS1.2 session 重用需要一个 RTT 时间, TLS1.3 则因为在第一个包中携带数据,只需要0个 RTT,有点类似 TLS 层的TCP Fast Open。 因此对于 https, 收到第一个 http 响应包,比非重用减少一个 RTT, TLS1.2 需要3个 RTT 时间, TLS1.3 需要2个 RTT 时间。

    另外如果开启 TCP 的 TFO,收到第一个 https 响应包的时间,则再减少一个 RTT,在 session 重用的时候就是 TLS1.2 需要2个 RTT,TLS1.3 只需要1个 RTT 时间。 How

    为什么 TLS 1.3 能少一个 RTT 时间?

    考虑 TLS 1.2, 下面握手流程来自 RFC5246 ,在第一个 RTT 需要协商算法版本等信息, 在第二个 RTT 才能完成对称密钥的协商。

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

    Client Server ClientHello --------> ServerHello Certificate ServerKeyExchange CertificateRequest <-------- ServerHelloDone Certificate ClientKeyExchange CertificateVerify* [ChangeCipherSpec] Finished --------> [ChangeCipherSpec] <-------- Finished Application Data <-------> Application Data

    Figure 1.  Message flow for a full handshake
    

    如果要减少 RTT,就是解决如何在第一个 RTT 内就能完成密钥的协商? 因为 TLS 1.3 只支持 PFS 的算法,已经取消了 RSA 作为密钥协商算法, 因此以下讨论仅用 ECDHE 来说明。

    使用 ECDHE 需要解决以下问题:

    双方把自己的公钥发送给对方
    确认发送的公钥信息没有被中间人篡改
    

    很自然的,我们会考虑能不能把 ECDHE 的公钥在 client hello 中发送? 服务端收到 client hello 后,随机生成本地的 ECDHE 私钥后,就能直接能计算出 pre-master secret,进而计算出所有密钥。 同时服务端发送 finish 消息,通过 hmac 验证,client hello 中的 ECDHE 公钥没有被篡改。

    在第二个 RTT 开始,收到 server hello 后,client 也能通过服务端 ECDHE 的公钥计算出 pre-master secret, 发送自己的 Finish 消息,并和应用数据一起发送。 服务端验证 Finish 成功后才接收数据。

    上面分析,完全是拍脑袋的结果,事实上 TLS 1.3 是这样吗? TLS 1.3

    对比TLS 1.2主要的修改如下:

    使用更严格的算法,只使用 PFS 的算法,如禁用了 RSA 密钥协商, 只使用 AEAD 算法
    使用 HKDF 密钥导出算法替代 PRF 算法
    server hello 之后的握手包也开始加密, 并去掉了changeCipherSpec 消息
    更改了 session 重用机制, 使用 PSK 的机制,同时 session ticket 中添加了过期时间。 过去 TLS 1.2 中的 ticket 不包含过期时间,只能通过 ticket key 的更新让之前所有发送的 ticket 都失效
    版本协商作为 client hello 的扩展,提供版本列表
    支持 0-RTT 发送
    

    后文所有的代码来源于下面两个库, server 端源码来自 Cloudflare 的 tls-tris,Client 端参考了 bifurcation/mint clientHello

    如前文所述, TLS 1.3 为了减少一个 RTT 时间,必须在 client hello 中发送本地的 ECDHE 的公钥,因为可能支持多个曲线,所以需要发送每个曲线的 ECDHE 公钥。 每个公钥和对应的曲线, 称为 keyShare。 keyshare 列表作为 clientHello 的扩展被发送

    1 2 3 4 5

    // 每个 keyShare 的条目,包含曲线 ID 和公钥 struct { NamedGroup group; opaque key_exchange<1..2^16-1>; } KeyShareEntry;

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

    func (h ClientHandshake) CreateClientHello(opts ConnectionOptions, caps Capabilities) (HandshakeMessage, error) { // key_shares h.OfferedDH = map[NamedGroup][]byte{} ks := KeyShareExtension{ HandshakeType: HandshakeTypeClientHello, Shares: make([]KeyShareEntry, len(caps.Groups)), } for i, group := range caps.Groups { pub, priv, err := newKeyShare(group) //为每个支持的曲线,生成公私钥,作为keyshare if err != nil { return nil, err } ks.Shares[i].Group = group ks.Shares[i].KeyExchange = pub h.OfferedDH[group] = priv } ... }

    serverHello and HelloRetryRequest, EncryptedExtensions, CertificateRequest, Certifacate, CertificateVerify, Finished

    服务端收到 client 后,协商曲线,如果有支持的曲线则使用该 keyshare, 否则发送 HelloRetryRequest 消息通知client。
    服务端生成 ECDHE 公私钥后, 通过客户端的 keyShare 协商出密钥 ECDHESecret(TLS 1.2 中的 premaster secret)。然后通过 serverHello 发送服务端的 keyShare。
    需要注意的是 keyShare 没有使用私钥签名, 整个过程的不可抵赖和防篡改是通过 certificateVerify 证明持有私钥,以及 finished 消息使用 hmac 验证历史消息来确定的。
    serverHello 之后的握手消息需要加密,导出加密密钥。
    通过 early secret 和 ECDHE secre t导出server_handshake_traffic_secret。
    再从 server_handshake_traffic_secret 中导出 key 和 iv,使用该 key 和 iv 对 server hello 之后的握手消息加密。
    同样的计算 client_handshake_traffic_secret,使用对应的 key 和 iv 进行解密后续的握手消息
    

    1 2 3 4 5 6 7 8 9 10 11

    Derive-Secret(Secret, Label, Messages) = HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length) early secret=HKDF-Extract(0,0) Handshake Secret = HKDF-Extract(ecdhe secret, early secret) server_handshake_traffic_secret = Derive-Secret(Handshake Secret, "server handshake traffic secret", ClientHello...ServerHello) client_handshake_traffic_secret = Derive-Secret(Handshake Secret, "server handshake traffic secret", ClientHello...ServerHello) server_handshake_key := hkdfExpandLabel(hash, server_handshake_traffic_secret, nil, "key", hs.suite.keyLen) server_handshake_iv := hkdfExpandLabel(hash, server_handshake_traffic_secret, nil, "iv", 12) client_handshake_key := hkdfExpandLabel(hash, server_handshake_traffic_secret, nil, "key", hs.suite.keyLen) client_handshake_iv := hkdfExpandLabel(hash, server_handshake_traffic_secret, nil, "iv", 12)

    在 EncryptedExtensions 消息中发送扩展信息,比如 alpn 协议,服务端是否支持 earlyData
    如果服务端需要客户端证书,则发送 CertificateRequest , 在其扩展中指定支持的签名算法和CA
    发送 certificate 和 certificateVerify 消息
    在 certificate 可以指定 OCSP stapling和 sct。
    certificateVerify 跟以前 client 发送的类似, 使用私钥对历史握手消息的摘要进行签名, 并发送签名的算法。
    发送 finished 消息, 从 server_handshake_traffic_secret 中导出 serverFinishedKey, 使用 hmac 计算 finished 后发送。 TLS 1.2 是使用 PRF(master_secret, digest(handshake)) 导出的。
    导出最终的对称密钥。 先从 Handshake Secret 中导出 master secret,再从 master secret 导出两个方向的对称密钥 key 和 iv
    

    1 2 3

    masterSecret = hkdfExtract(hash, nil, Handshake Secret) client_application_traffic_secret_0 = Derive-Secret(masterSecret, "client application traffic secret", ClientHello...server Finished) server_application_traffic_secret_0 = Derive-Secret(masterSecret, "server application traffic secret", ClientHello...server Finished)

    Certifacate and CertificateVerify, Finished

    客户端收到 serverHello 后,通过 server 的 keyshare 计算出 ECDHE secret。 然后跟 server 端一样,通过一系列的 khdf 密钥导出, 两个方向的后续握手密钥,以及 master secret 和两个方向的 application traffic secret。 因为 client 和 server 端 early secret 和协商出来的 ecdhe secret 相同, 因此所有后续导出的对应的密钥都是相同的。

    1 2 3 4 5 6 7 8

    early secret=HKDF-Extract(0,0) Handshake Secret = HKDF-Extract(ecdhe secret, early secret) server_handshake_traffic_secret = Derive-Secret(Handshake Secret, "server handshake traffic secret", ClientHello...ServerHello) client_handshake_traffic_secret = Derive-Secret(Handshake Secret, "client handshake traffic secret", ClientHello...ServerHello) masterSecret = hkdfExtract(hash, nil, Handshake Secret) client_application_traffic_secret_0 = Derive-Secret(masterSecret, "client application traffic secret", ClientHello...server Finished) server_application_traffic_secret_0 = Derive-Secret(masterSecret, "server application traffic secret", ClientHello...server Finished) resumption_master_secret = Derive-Secret(masterSecret, "resumption master secret", hash(all handshake message))

    发送 finished 后, 就完成了整个握手信息, 通过 master secret 和整个握手的摘要,计算 resumption secret newSessionTicket

    收到客户端的 Certifacate 和 CertificateVerify,同样进行证书链的认证以及验证签名
    服务端收到客户端的 finished 消息后,验证完后,同样计算 resumption secret
    发送 new session ticket,包含整个 session 的信息。 newSessionTicket 使用server_application_traffic_secret 加密
    在加密的ticket中,相比 TLS1.2,包含了当前的创建时间,因此可以方便的配置和验证 ticket 的过期时间。
    

    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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

    func (hs *serverHandshakeState) sendSessionTicket13() error { c := hs.c if c.config.SessionTicketsDisabled { return nil } foundDHE := false for , mode := range hs.clientHello.pskKeyExchangeModes { if mode == pskDHEKeyExchange { foundDHE = true break } } if !foundDHE { return nil } //只支持dhe的方式计算psk hash := hashForSuite(hs.suite) handshakeCtx := hs.finishedHash13.Sum(nil) resumptionSecret := hkdfExpandLabel(hash, hs.masterSecret, handshakeCtx, "resumption master secret", hash.Size()) ageAddBuf := make([]byte, 4) sessionState := &sessionState13{ //需要加密的session信息,包含resumptionSecret vers: c.vers, suite: hs.suite.id, createdAt: uint64(time.Now().Unix()), resumptionSecret: resumptionSecret, alpnProtocol: c.clientProtocol, SNI: c.serverName, maxEarlyDataLen: c.config.Max0RTTDataSize, } for i := 0; i < numSessionTickets; i++ { if , err := io.ReadFull(c.config.rand(), ageAddBuf); err != nil { //随机生成ageAddBuf c.sendAlert(alertInternalError) return err } sessionState.ageAdd = uint32(ageAddBuf[0])<<24 | uint32(ageAddBuf[1])<<16 | //ageAdd使用随机值 uint32(ageAddBuf[2])<<8 | uint32(ageAddBuf[3]) ticket := sessionState.marshal() var err error if c.config.SessionTicketSealer != nil { cs := c.ConnectionState() ticket, err = c.config.SessionTicketSealer.Seal(&cs, ticket) } else { ticket, err = c.encryptTicket(ticket) //使用tiket key加密 } if err != nil { c.sendAlert(alertInternalError) return err } if ticket == nil { continue } ticketMsg := &newSessionTicketMsg13{ lifetime: 24 * 3600, // TODO(filippo) //24小时 maxEarlyDataLength: c.config.Max0RTTDataSize, withEarlyDataInfo: c.config.Max0RTTDataSize > 0, ageAdd: sessionState.ageAdd, //随机值 ticket: ticket, //session信息 } if _, err := c.writeRecord(recordTypeHandshake, ticketMsg.marshal()); err != nil { return err } } return nil }

    session 重用和 0-RTT

    client 收到 NewSessionTicket 消息后, 收到的 ticket 和客户端本地发送 finished 后计算的 resumptionSecret,两者一起组成了 PreSharedKey,即 PSK。 然后 client 把 PSK 保存到本地 cache 中, serverName 作为 cache 的 key。

    1 2 3 4 5 6 7 8 9 10 11 12 13

    func (h ClientHandshake) HandleNewSessionTicket(hm HandshakeMessage) (PreSharedKey, error) { var tkt NewSessionTicketBody _, err := tkt.Unmarshal(hm.body)

        psk := PreSharedKey{
        CipherSuite:  h.Context.suite,
        IsResumption: true,
        Identity:     tkt.Ticket,       // ticket 中也包含 resumptionSecret,但是被加密
        Key:          h.Context.resumptionSecret,       //客户端本地发送 finished 后计算的 resumptionSecret
    }
    return psk, nil
    

    } c.config.PSKs.Put(c.config.ServerName, psk) //这里使用 serverName 做为 key

    client

    在 client hello 中,会在本地 cache 中查找 servername 对应的 PSK, 找到后则在 client hello 的 psk 扩展中带上两部分

    Identity: 就是 NewSessionTicket 中加密的 ticket
    Binder: 从之前 client 发送 finished 计算的 resumption secret,导出 early secret,进而导出后续的 binderKey  binder_macKey 使用 binder_macKey 对不包含 PSK 部分的 clientHello HMAC
    

    1 2 3 4 5 6

    Early Secret = HKDF-Extract(0, resumption secret) binder_key = deriveSecret(Early Secret, "resumption psk binder key", "") binder_macKey = hkdfExpandLabel(ctx.params.hash, binder_key, "finished", []byte{}, ctx.params.hash.Size()) earlyTrafficSecret = ctx.deriveSecret(Early Secret, "client early traffic secret", clientHello) earlyExporterSecret = ctx.deriveSecret(Early Secret, "early exporter master secret", ClientHello) clientEarlyTrafficKey, clientEarlyTrafficIv= ctx.makeTrafficKeys(earlyTrafficSecret)

    通过 resumption secret 最终导出 earlyData 的加密密钥,以及 PSK 扩展中 binder 的 hmac 密钥。 发送 clientHello 后,使用 resumption secret 导出的 clientEarlyTrafficKey 和 IV,对 early data 加密后发送。

    需要注意的是 earlydata 在 ticket 有效期内,不能防止重放攻击。

    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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

    func (h ClientHandshake) CreateClientHello(opts ConnectionOptions, caps Capabilities) (HandshakeMessage, error) { // key_shares h.OfferedDH = map[NamedGroup][]byte{} ks := KeyShareExtension{ HandshakeType: HandshakeTypeClientHello, Shares: make([]KeyShareEntry, len(caps.Groups)), } for i, group := range caps.Groups { pub, priv, err := newKeyShare(group) //为每个支持的曲线,生成公私钥,作为keyshare if err != nil { return nil, err } ks.Shares[i].Group = group ks.Shares[i].KeyExchange = pub h.OfferedDH[group] = priv } ... if key, ok := caps.PSKs.Get(opts.ServerName); ok { //从cache中获取PSK,尝试session重用 h.OfferedPSK = key keyParams, ok := cipherSuiteMap[key.CipherSuite] compatibleSuites := []CipherSuite{} for _, suite := range ch.CipherSuites { if cipherSuiteMap[suite].hash == keyParams.hash { compatibleSuites = append(compatibleSuites, suite) } } ch.CipherSuites = compatibleSuites //更新psk能使用的算法 if opts.EarlyData != nil { //使用psk的话可以使用0-rtt发送early data ed = &EarlyDataExtension{} //开启early data ch.Extensions.Add(ed) }

                psk = &PreSharedKeyExtension{
            HandshakeType: HandshakeTypeClientHello,
            Identities: []PSKIdentity{
                {Identity: key.Identity},   //Identity就是加密的ticket
            },
            Binders: []PSKBinderEntry{
                // Note: Stub to get the length fields right
                {Binder: bytes.Repeat([]byte{0x00}, keyParams.hash.Size())},
            },
        }
        ch.Extensions.Add(psk)  //添加psk作为client hello的扩展
        h.Context.preInit(key)  //resumption secret导出early secret->binder key
        trunc, err := ch.Truncated()    //clientHello减去psk 扩展部分
        truncHash := h.Context.params.hash.New()
        truncHash.Write(trunc)
        binder := h.Context.computeFinishedData(h.Context.binderKey, truncHash.Sum(nil))    //binder_key导出macKey, 计算clientHello hmac
        // Replace the PSK extension
        psk.Binders[0].Binder = binder  //client hellohmac
        ch.Extensions.Add(psk) //替换psk扩展
        h.clientHello, err = HandshakeMessageFromBody(ch)   //重新构造client hello
    
                h.Context.earlyUpdateWithClientHello(h.clientHello) //导出client_early_traffic_secret及其keyiv,作为0-RTTearly data的密钥
    }
    ...
    

    } func (c *Conn) clientHandshake() error { logf(logTypeHandshake, "Starting clientHandshake") h := &ClientHandshake{} hIn := NewHandshakeLayer(c.in) hOut := NewHandshakeLayer(c.out) // Generate ClientHello caps := Capabilities{ CipherSuites: c.config.CipherSuites, Groups: c.config.Groups, SignatureSchemes: c.config.SignatureSchemes, PSKs: c.config.PSKs, PSKModes: c.config.PSKModes, Certificates: c.config.Certificates, } opts := ConnectionOptions{ ServerName: c.config.ServerName, NextProtos: c.config.NextProtos, EarlyData: c.earlyData, } chm, err := h.CreateClientHello(opts, caps) if err != nil { return err } // Write ClientHello err = hOut.WriteMessage(chm) if err != nil { return err } if opts.EarlyData != nil { //使用client_early_traffic_secret的key/iv加密early data, 支持0-rtt发送 // Rekey output to early data keys err = c.out.Rekey(h.Context.params.cipher, h.Context.clientEarlyTrafficKeys.key, h.Context.clientEarlyTrafficKeys.iv) // Send early application data logf(logTypeHandshake, "[client] Sending data...") _, err = c.Write(opts.EarlyData) if err != nil { return err } // Send end_of_earlyData logf(logTypeHandshake, "[client] Sending end_of_early_data...") err = c.sendAlert(AlertEndOfEarlyData) //发送end_of_early_data alert标记early data结束 if err != nil { return err } } ... }

    server

    和 TLS 1.2 之前不同, session 重用,使用的不是过去的 master secret。 TLS1.2 加密 ticket 后使用过去的 master secret,然后和两个随机数作为参数,一起 PRF 导出密钥。 而 TLS 1.3 只使用过去的 resumption secret 导出 early data 的密钥, 之后的密钥会和 ECDHE secret,一起导出。

    服务端收到 client hello 后,生成本地的 keyShare
    检查 client hello 的 PSK 扩展, 解密 ticket,查看该 ticket 是否过期,已经版本算法等协商结果是否可用,然后使用 ticket 中的 resumption secret 计算 client hello 的 hmac, 检查 binder 是否正确。
    验证完 ticket 和 binder 之后,在 serverHello 中表示使用 PSK,以及哪个 PSK。
    和 client 一样,从 resumtion secret 中导出 earlyData 使用的密钥
    和不使用 session 重用一样,导出后续的密钥,唯一不同的是 resumption secret 作为 early secret 的输入
    收到 endOfEarlyData alert 后,切换到 client 方向的应用程序密钥
    serverHello 发送后依然会发送 EncryptedExtensions 和 Finished 消息,但不会再发送 Certificate 和 CerficateVerify 消息。
    
    func (hs *serverHandshakeState) doTLS13Handshake() error {
    
            var ks keyShare
    CurvePreferenceLoop:
        for _, curveID := range config.curvePreferences() { // tls 1.3所有的曲线,都必须生成对应的keyshare keyshare中包含dh的公钥
            for _, keyShare := range hs.clientHello.keyShares {
                if curveID == keyShare.group {
                    ks = keyShare
                    break CurvePreferenceLoop
                }
            }
        }
    
            privateKey, serverKS, err := config.generateKeyShare(ks.group)  //ecdhe服务端生成公私钥 
        hs.hello13.keyShare = serverKS  //公钥作为keyshare
        earlySecret, pskAlert := hs.checkPSK()  // 检查psk,看能否session重用
        switch {
        ...
        case earlySecret != nil:
            c.didResume = true  //ticket和binder验证通过
        }
        hs.finishedHash13 = hash.New()
        hs.finishedHash13.Write(hs.clientHello.marshal())
        handshakeCtx := hs.finishedHash13.Sum(nil)  //client hello摘要
        earlyClientCipher, _ := hs.prepareCipher(handshakeCtx, earlySecret, "client early traffic secret") //client_early_traffic_secret=Derive-Secret(earlySecret, "client early traffic secret",hash(clientHello))
        ecdheSecret := deriveECDHESecret(ks, privateKey)    //客户端公钥和server私钥,根据曲线乘法,计算出对称密钥
    
            hs.finishedHash13.Write(hs.hello13.marshal())
        //发送server  hello, 包含协商信息,  需要注意的是,服务端的keyshare没有使用私钥签名
        if _, err := c.writeRecord(recordTypeHandshake, hs.hello13.marshal()); err != nil {
            return err
        }
        // 和不使用session重用一致,只是early secret使用resumption secret作为输入
        // 不再发送发送Certificate和CerficateVerify消息
        ...
        return nil
    }
    func (hs *serverHandshakeState) checkPSK() (earlySecret []byte, alert alert) {
        if hs.c.config.SessionTicketsDisabled {
            return nil, alertSuccess
        }
        foundDHE := false
        for _, mode := range hs.clientHello.pskKeyExchangeModes {
            if mode == pskDHEKeyExchange {  //只支持psk dhe模式
                foundDHE = true
                break
            }
        }
        if !foundDHE {
            return nil, alertSuccess
        }
        hash := hashForSuite(hs.suite)
        hashSize := hash.Size()
        for i := range hs.clientHello.psks {
            sessionTicket := append([]uint8{}, hs.clientHello.psks[i].identity...)
            if hs.c.config.SessionTicketSealer != nil {
                var ok bool
                sessionTicket, ok = hs.c.config.SessionTicketSealer.Unseal(hs.clientHelloInfo(), sessionTicket)
                if !ok {
                    continue
                }
            } else {
                sessionTicket, _ = hs.c.decryptTicket(sessionTicket)    //使用默认的session ticket key解密
                if sessionTicket == nil {
                    continue
                }
            }
            s := &sessionState13{}  //还原tls 1.3 session
            if s.unmarshal(sessionTicket) != alertSuccess {
                continue
            }
            if s.vers != hs.c.vers {
                continue
            }
            //client收到ticket后,通过lifetime,计算obfTicketAge,并加上随机值ageAdd,这里减回去,得到client的ticket有效时间
            clientAge := time.Duration(hs.clientHello.psks[i].obfTicketAge-s.ageAdd) * time.Millisecond //tls 1.3 ticket带时间了
            serverAge := time.Since(time.Unix(int64(s.createdAt), 0))       //距离本次ticket的创建时间
            if clientAge-serverAge > ticketAgeSkewAllowance || clientAge-serverAge < -ticketAgeSkewAllowance {
                // XXX: NSS is off spec and sends obfuscated_ticket_age as seconds
                clientAge = time.Duration(hs.clientHello.psks[i].obfTicketAge-s.ageAdd) * time.Second
                if clientAge-serverAge > ticketAgeSkewAllowance || clientAge-serverAge < -ticketAgeSkewAllowance {
                    continue
                }
            }
            // This enforces the stricter 0-RTT requirements on all ticket uses.
            // The benefit of using PSK+ECDHE without 0-RTT are small enough that
            // we can give them up in the edge case of changed suite or ALPN or SNI.
            if s.suite != hs.suite.id {
                continue
            }
            if s.alpnProtocol != hs.c.clientProtocol {
                continue
            }
            if s.SNI != hs.c.serverName {
                continue
            }
            earlySecret := hkdfExtract(hash, s.resumptionSecret, nil)   // earlySecret = hkdfExtract(psk, 0); psk=resumption_master_secret
            handshakeCtx := hash.New().Sum(nil)
            binderKey := hkdfExpandLabel(hash, earlySecret, handshakeCtx, "resumption psk binder key", hashSize) //binder_key=Derive-Secret(early secret, "resumption psk binder key", "")
            binderFinishedKey := hkdfExpandLabel(hash, binderKey, nil, "finished", hashSize) //finished_key=Derive-Secret(binder_key, "finished", "")
            chHash := hash.New()
            chHash.Write(hs.clientHello.rawTruncated)   //不包含psk扩展
            expectedBinder := hmacOfSum(hash, chHash, binderFinishedKey)    //通过finishKey计算clienthello的hmac
            if subtle.ConstantTimeCompare(expectedBinder, hs.clientHello.psks[i].binder) != 1 { //hmac验证
                return nil, alertDecryptError
            }
            if i == 0 && hs.clientHello.earlyData {
                // This is a ticket intended to be used for 0-RTT
                if s.maxEarlyDataLen == 0 {
                    // But we had not tagged it as such.
                    return nil, alertIllegalParameter
                }
                if hs.c.config.Accept0RTTData { //服务端支持0rtt 0rtt会引起重放攻击
                    hs.c.binder = expectedBinder
                    hs.c.ticketMaxEarlyData = int64(s.maxEarlyDataLen)
                    hs.hello13Enc.earlyData = true
                }
            }
            hs.hello13.psk = true
            hs.hello13.pskIdentity = uint16(i)
            return earlySecret, alertSuccess
        }
        return nil, alertSuccess
    }
    func (c *Conn) handleEndOfEarlyData() {
        if c.phase != readingEarlyData || c.vers < VersionTLS13 {
            c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
            return
        }
        c.phase = waitingClientFinished
        if c.hand.Len() > 0 {
            c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
            return
        }
        c.in.setCipher(c.vers, c.hs.hsClientCipher) //切换client的应用程序密钥
    }
    

    记录层

    通过clientHello中带有short headers扩展, 删除了记录层开头的几个字节 HKDF (HMAC-based key derivation function)

    HKDF是基于HMAC的密钥导出算法,用来替换TLS 1.3之前的PRF算法。

    HKDF follows the extract-then-expand paradigm, where the KDF
    logically consists of two modules. The first stage takes the input
    keying material and extracts from it a fixed-length pseudorandom
    key K. The second stage expands the key K into several additional
    pseudorandom keys (the output of the KDF).
    
    0
                                 |
                                 v
    PSK(resumption secret) ->  HKDF-Extract = Early Secret
                                 |
                                 +-----> Derive-Secret(.,
                                 |                     "external psk binder key" |
                                 |                     "resumption psk binder key",
                                 |                     "")
                                 |                     = binder_key
                                 |
                                 +-----> Derive-Secret(., "client early traffic secret",
                                 |                     ClientHello)
                                 |                     = client_early_traffic_secret
                                 |
                                 +-----> Derive-Secret(., "early exporter master secret",
                                 |                     ClientHello)
                                 |                     = early_exporter_master_secret
                                 v
                           Derive-Secret(., "derived secret", "")
                                 |
                                 v
                    (EC)DHE -> HKDF-Extract = Handshake Secret
                                 |
                                 +-----> Derive-Secret(., "client handshake traffic secret",
                                 |                     ClientHello...ServerHello)
                                 |                     = client_handshake_traffic_secret
                                 |
                                 +-----> Derive-Secret(., "server handshake traffic secret",
                                 |                     ClientHello...ServerHello)
                                 |                     = server_handshake_traffic_secret
                                 v
                           Derive-Secret(., "derived secret", "")
                                 |
                                 v
                      0 -> HKDF-Extract = Master Secret
                                 |
                                 +-----> Derive-Secret(., "client application traffic secret",
                                 |                     ClientHello...server Finished)
                                 |                     = client_application_traffic_secret_0
                                 |
                                 +-----> Derive-Secret(., "server application traffic secret",
                                 |                     ClientHello...server Finished)
                                 |                     = server_application_traffic_secret_0
                                 |
                                 +-----> Derive-Secret(., "exporter master secret",
                                 |                     ClientHello...server Finished)
                                 |                     = exporter_master_secret
                                 |
                                 +-----> Derive-Secret(., "resumption master secret",
                                                       ClientHello...client Finished)
                                                       = resumption_master_secret
    

    hdlist:一个列表硬盘的命令行小工具,仅90kb

    用C写的硬盘列表小工具命令行,大小仅90kb。 写装机软件和启动盘制作工具时,可以用来检测硬盘列表。 通过判断总线类型是否为”USB”来判断是否是U盘或者移动硬盘。 输出

    格式:硬盘号|硬盘名|总线类型|容量(字节)|分区表类型|盘符 获取

    下载:https://pan.hotpe.top/s/4PKu0 源码:https://github.com/VirtualHotBar/WinKits/blob/main/hdlist.c


    手贱,在图形化界面上使用 root 登录了一下,然后 GNOME 的登录界面就一直有 root 账户了。直接禁用有点烦的,还是换个办法吧。 在 /var/lib/AccountsService/users/ 把 root 这个文件删掉,然后重启 systemd accounts-daemon.service 或者重启 PC 即可。


    盗版是否值得?

    首先,这个问题从我较为了解方面的来看,大致可以分为三个方向:数字文件,书籍,周边产品。

    数字文件,我简单分为软件 / 音乐。软件服务,如微软 Windows 或 Adobe 系列软件,实际上都是【开放】盗版的。

    软件
    

    Windows 实际上压根不管个人用户用盗版,毕竟根本管不过来;更多的是 OEM 厂商自带的正版 Windows 里赚钱,以及起诉大公司。如果你说绿镖那种山寨厂,Windows 都是 KMS 激活,那我无话可说;但是如果某家互联网大厂被公开起诉要求支付软件使用费,直接传达给用户的就是:这家厂商不尊重版权意识。为了自己的声誉,100% 互联网大厂都会选择买正版。我也听过字节跳动给员工报销某小众软件正版许可证的故事,这才是正常的版权意识。Adobe 盗版如果不拿来商用,他才懒得管你呢。当然,PS 里的每张图片 1000% 有 Adobe 的自家隐藏水印,揪出来商用的盗版轻而易举。

    实际上软件盗版有时候更是一种宣传软件的方式。某软件激活码 123-321-12345678 就可以通过核验,某软件多年不修的激活漏洞,微软不告小公司 / 私人,实际上无形中就是在推广他,认可他的软件。10 年前后电脑城里一架子的雨林木风激活版,winrar 国家电网激活版,无形中直接让【Windows 电脑,winrar 解压】的概念直接深入人心,Windows 也毫无悬念拿下巨额市场。

    闲鱼上的软件售卖简直是我见到最暴利的行业,不止一次我看到他们投诉 / 关闭免费的资源,收集后高价售卖;也有很多次,我因为急需购买,下载后才发现是免费软件。二次盗版更简单,我把买到的软件 N 次倒卖即可,反正 CtrlCV 就可以复制无限个副本。不予评价,但互联网的本质是开放 - 共享 - 帮助。

    游戏盗版实际上是处在一个尴尬地位的。拿我来说,我真的会下盗版游戏;但我 100% 上车补票。大多数游戏我会玩 1-5 小时盗版,再决定是否入正;说实话,GTA5 的有些盗版比正版还稳定 hhhhhh。我反对购买盗版游戏,没钱的人也在大多数,但上车补票才是正经玩游戏的最好做法。

    音乐
    

    音乐盗版是目前较为挣扎的。刘明野的工具箱,musictools,lxmusic 相继的关停意味着版权资本的严打盗版,但 “音乐不应该只为贵族服务”。让每个人都享受美好才是音乐软件应该做出的担当。我当然支持创作者应得的利益,但这不是音乐平台各家垄断的理由。我支持高清晰度音乐收 2 倍价格,但所有的音乐应在一个软件内可以企及,最差的清晰度应该被免费提供。全中国有几亿人的收入水平在 3000 人民币 / 月 以下,音乐平台的固步自封就是在变相垄断,搞资本那一套。

    书籍
    

    书籍是我较为难受的一点。作为学生,部分海外书籍是真的买不起;这时候反而就想着 “pdd 盗版一下好不好”,实在是自愧。但出版社,经销商多少的书籍是亏钱的?先不谈京东等平台口口声声的所谓 “为消费者着想” 的强制低价扣出版社利润,光是中国童书近些年因部分人的无底线而毁灭就已经让不少作家几近失去生活来源。图书业在走下坡路。正版的价格无法覆盖出版社开支,库存压力也是压死骆驼的最后一根稻草。最大的原因在于:盗版实在太便宜了。

    今天我在商场的书摊,16.8 元买了一本《天气之子》。不出所料,盗版书,部分地方影印的不清不楚,同款拼多多 11.5 元。最可悲的是盗版的课本啊,打着正版的旗号卖影印版,刺鼻劣质的油墨纸张就这样寄给了全国的学生。盗版总是难免的;我的朋友们,花不少钱在网上买盗印明信片等等物品的也绝对不在少数。几分钱,几元钱的成本就这样翻倍,卖出几万几十万份,原作者是一分都拿不到的。鸟山明在多年前就能挣到天价稿费,和当地优良的出版业文化脱不了干系。当人们自发的支持正版书籍,创作者才能有足够的资金和支持做出更好的产品。盗版的成本过于低廉,利润太过可观,最终受害的只有出版社,书商和作者。像游戏一样,我赞成上车补票,但购买影印盗版绝对是最差的主意,这在促进图书盗版行业的发展。更妥当的做法:电子版(比如 Zlibrary)阅读后抉择是否值得入正版。

    周边产品
    

    明星的盗版签名 / 照片不必多说,ACG 周边产品才是盗版重灾区 -- 实际上这是我最不反对盗版的地方。成本 2 元的亚克力板,成本 3 元的金属徽章,在炒作 / 饭圈的推动下越来越瞩目,越来越贵;少则 30-50,多达 800-8000. 成本有吗?成本在哪里?不要说你的 IP 成本,所谓 IP 成本就是割韭菜成本。我不信,一块 2 元成本的亚克力板卖 10 元就无法覆盖所有绘画 / 运输 / 人力成本!所谓 “购买‘谷子’多少代表‘厨’力” 的奸商言论也是离谱之极,正常的 < 周边产品 > 被赋予了奇怪的新名称之后加入了饭圈,摇身一变成了各类饭圈人所谓 “入圈” 的证明,顺带炒高价格带坏风气,荒谬至极!!!我会因为对某个人物 / 形象的喜爱 / 认可而购买相关的产品,但我对某个人物 / 形象的喜爱 / 认可绝不基于我所购买的产品多少和价格!这是纯粹无语的饭圈行为,搞得现在没个抑郁症没四五十个周边产品就不配喜欢 xx 人 /xx 角色了,更有人以此为荣。我支持正版,本文头图(下面的配图)就是今日买的正版徽章;但我绝对不反对盗版。哪天资本不拿着那个吃相难看的脸割韭菜,我就放弃所有的盗版产品,立帖为证!在东西一样的前提下,我也选择上车补票;展示的,消耗品一律盗版,当然正式的收藏绝对必须正版产品。我买正版是展现我对某个人物 / 形象的喜爱 / 认可,买盗版是因为我有自己的生活和价值观,我也有正常的消耗品价值观。多年前我的包上就别着某人物形象的正版铁皮徽章,没想到现在的饭圈文化里,没个三四层保护和专门的包都不能带 “谷子” 出门了。荒谬的文化新制度,盗版反而是 “理所应当” 但又缺一不可的避难所了。


    图片丢失,待修复

    所有用到的软件,驱动在这里及下文其他的网盘链接下载。 众所周知,随身 wifi 这个行业就是坑蒙拐骗。商家以远低于成本价的价格寄给你设备,再靠虚假虚标的流量套餐反薅羊毛。目前 50% 的随身 wifi 终端(下文简称棒子)均为高通 410/210 芯片(多为二手回收手机拆下),这也给了我们很多的可玩性。

    我从三个商家处入手了 5 个棒子,分别是‘袆通’jz04,‘影腾’ufi003_mb_v02,‘影腾’ufi103s_v03。其中 jz04 为 210 芯片,带卡槽,可惜刷机包丢失,暂无法使用。其余 2 个型号均为 410 芯片,下文以’影腾‘ufi003_mb_v02 型号作介绍。技术有限,中兴微玩不来;210/410 均可刷 debian/openwrt,特别的:210 可以做短信转发。

    收到棒子,拧掉两颗螺丝开外壳,主板正面丝印 ufi003_mb_v02。贴纸无用。这里分两种: 1. 只想要切卡用自己卡 2. 要刷机 1. 切卡

    为了防止你插自己的卡让商家赚不到钱,聪明的商家让新版的板子都不带卡槽了。当然,不管有无卡槽,这一步是统一的:拿一根针,或任何坚固的物品,断电情况下干掉图中任意一个电容!这一步是为了让切卡不受管理后台的控制,大多数 003 型号的棒子输入了切卡密码也没办法直接切卡。当然,你也可以 9008 刷入已经切好的软件包,但是这样不如干掉电容更简单且来得实在。

    没有卡槽的解决方法也简单,拼多多 1 元左右买一个 6 脚卡槽,电烙铁焊上即可,记得买的是大卡卡槽不要买错,翻盖式或者直插式都可以。

    当然,高通 410 拆机芯片的稳定程度完全不及中兴微及其他芯片,更加建议购买原生即可换卡的随身 wifi。 2. 刷机

    ufi003 分两种,一种原生就有 ADB,一种原生没有开 ADB。针对原生开了 ADB 的棒子,就会很简单,进入下面的备份流程即可。这里部分引用了 Tony 博客的文章。

    不知道 ADB 怎么看?ADB 全称 Android Debug Bridge,安卓调试桥。电脑打上 vivo 驱动,插入棒子,右键此电脑,点击设备管理器,查看是否有 Android Devices 且此选项里是否有类似 Android Composite ADB Interface 字样。

    对于出厂没有开 ADB 的棒子,可以通过刷已经打开 ADB 的包来解决。刷解 ADB 包之前有一个小要求,你的设备得是出厂固件;所以,刷入之前,先用 Miko 备份出厂包以便救砖。这里我提供一个酷安大佬的全量包,亲测可以完美刷入。当然这里我建议刷入打开 ADB 全量包之后再次 Miko 备份,否则如果后续砖了或者出问题需要先刷回出厂包,再刷进解 ADB 包,非常浪费时间。

    Miko 备份过程也很简单,打开了 ADB 的设备只需要在 cmd 输入 adb shell 进入终端后输入 adb reboot edl 进入 9008 高通深刷模式(没有原生打开 adb 的设备,你需要用针 / 取卡针穿透外壳的重置孔 / 拆开外壳按住按钮 并在按住按钮的时候插入电脑,在设备管理器 - 端口选项卡里看到 Qualcomm HS-USB QDLoader 9008 或类似字样即可),此时 Miko 左下角会出现 MSM8916 字样或显示端口(例如 COM5)。

    第一种方法 - 备份 XML: 点击 Read-Backup Xml Fireware,点击左下角 Load Partition Structure 后在右侧看到 MSM8916 字样即为成功。全选所有文件,选择输出目录,点击下方 backup all 即可备份 xml 文件。

    第二种方法 - 备份 bin:Read-Partition Backup/Erase,点击左下角 Load Partition Structure 后在右侧看到 MSM8916 字样即为成功。全选所有文件,选择输出目录,点击下方 Read Full Image 再选择导出目录并命名即可备份为 bin 文件。 恢复备份包同下面的 9008 刷入过程

    刷入包也是同样的按住按钮 / 顶针进入 9008,已经打开 ADB 的设备也可以在 cmd 输入

    adb shell
    

    进入终端后输入

    adb reboot edl
    

    进 9008,如果刷机包是几个 Xml 文档就选择 flash-sml flasher,如果是 bin 文件就使用 emmc block0 flasher。

    XML:9008 下 Miko-Flash-xml flasher-Xml flasher-Rawprogram,选择刷机包里的 rawprogram0.xml 即可,其他选项一概不要动,点击 Flash 刷入即可。刷入成功后会自动重启设备。(如果不成功,请刷回出厂包再试一次刷机,以及检查是否有文件缺失,如果都不行就换一个包刷入。)

    BIN:9008 下 Miko-Flash-emmc block0 flasher,双击 Emmc Data,选择 bin 文件后点 Flash 刷入。

    刷入开启 ADB 的全量包:我提供的压缩包里的两个文件,01 是可以在任何状态下直接刷入的,02 是需要在出厂环境下刷入的。依据文件类型,用上文的 XML/BIN 方法刷入成功后重启 / 自动重启设备,进设备管理器查看有 Android Devices 且此选项里有类似 Android Composite ADB Interface 字样,即为 ADB 开启成功。

    ADB 开了,就可以舒服的刷上 Debian 了。OpenWRT 也类似,这里不提供具体路径,可自行酷安寻找。这里的固件是 “遥控车” 固件,这里我更推荐 KyonLi 的 Debian。KyonLi 的 Debian 下载 6.6 内核版本(release12.6-9 可用,12.6-8 的 6.10 内核版本被 tony 证实有掉线情况)。建议使用 boot-no-modem.img,删除了 modem 模块,省一些内存。下载 rootfs.img.xz,解压得到 rootfs.img。

    在这里的 Debian-20220608 文件下下载 UFI003.zip,解压后将 KyonLi 的 Github 下载的的 rootfs.img 和 boot-no-modem.img 替换原有的 rootfs.img 和 boot.img 文件(boot-no-modem.img 重命名为 boot.img),将棒子进入 fastboot 模式后点击 flash.bat 刷入,没有报错并显示 ALL DONE! 便为刷入 Debian 成功。

    (进入 fastboot 模式输入 adb reboot bootloader 即可,如想更加方便建议下载搞机助手或酷铵水遍的随身 wifi 工具箱。)KyonLi 的 Debian(Tree12.6-9)用户名 / 密码 root/1234,WiFi 名称 / 密码 openstick-failsafe/12345678,后续可能会有改变,请参照作者 GitHub。

    刷完后插入棒子,CMD 输入

    adb devices
    

    查看是否检测到设备。如果检测到,输入

    adb shell
    

    即可进入终端。通过 adb 连接时,使用 nmtui,htop 等需要显示图形的命令,可能会出现如下 TERM environment variable needs set. 错误,设置 TERM 环境变量即可解决。

    export TERM=linux
    

    设备已插入,但是 adb devices 就是检测不到 / 积极的拒绝连接 /error: device not found?这个问题困扰了我两天,现给出 3 个解决方案。

    1. 重装 OPPO 手机驱动。

    2. 查看 5037 端口占用。输入

      adb kill-server

    (如果被积极拒绝,看解决方案三)然后 adb start-server。输入

    netstat -ano | findstr “5037”
    

    找到占用端口的进程,记下 PID,cmd 里

    taskkill/f /pid xxxxx
    

    来 kill 掉这个进程。

    1. 设备 venderID 丢失 / 没有获取到。在设备管理器里未知设备 / 通用串行总线等地方找到 ADB Interface,双击 详细信息 设备描述 / 设备实例路径 / 硬件 id 里,记下 VID_后面的四位,例如 % USB\VID_18D1&PID_D00D.DeviceDescTest% 里就是 18D1。(双击 ADB Interface 后点击事件选项卡,已配置设备 / 已添加驱动服务里,也可以找到这个 ID。)进去 C:\Users\ 你的用户名 目录下的.android 文件夹,找到 adb_usb.ini,输入 0xXXXX(末尾 XXXX 是你刚刚获取的 VID 后面四位,例如 0x18D1),保存后重启电脑,解决。

    遥控车固件里同样有 OpenWRT 包,原作者链接在这里: https://www.kancloud.cn/a813630449/ufi_car/2795165。没有 OpenWRT 需求,暂且不谈。另致谢 Tony 及 HandsomeMod。


    Go 的错误处理性能测试(panic 和 recover,直接返回 err 和 errors.As)

    panic 和 recover 在 Go 官方的 JSON 包中也有使用。但是我一直这种直接 panic 并 recover 的方式性能如何,所以有了这次测试。

    先说结论:

    goos: darwin
    
    goarch: arm64
    
    pkg: go-recover-test
    
    cpu: Apple M1
    
    BenchmarkErrorReturn-8                  1000000000               0.3197 ns/op          0 B/op          0 allocs/op
    
    BenchmarkErrorReturnWithDefer-8         576616402                2.110 ns/op           0 B/op          0 allocs/op
    
    BenchmarkPanicRecover-8                 16081614                74.77 ns/op            0 B/op          0 allocs/op
    
    BenchmarkErrorsAs-8                     33246062                37.72 ns/op            0 B/op          0 allocs/op
    
    BenchmarkErrorsAsWrapped-8              23144929                49.71 ns/op            0 B/op          0 allocs/op
    
    BenchmarkTypeAssertion-8                1000000000               0.3166 ns/op          0 B/op          0 allocs/op
    
    BenchmarkReturnBusinessError-8          1000000000               0.4713 ns/op          0 B/op          0 allocs/op
    
    BenchmarkPanicRecoverBusinessError-8    16641386                96.30 ns/op            0 B/op          0 allocs/op
    
    PASS
    
    ok      go-recover-test 8.533s
    

    基础错误处理性能

    错误返回性能:
        普通 error 返回:~0.32 纳秒 / 操作
        带 defer 的错误返回:~2.07 纳秒 / 操作
        panic/recover:~74.10 纳秒 / 操作
    错误类型检查性能:
        普通类型断言:~0.32 纳秒 / 操作
        errors.As(直接错误):~36.32 纳秒 / 操作
        errors.As(包装错误):~48.62 纳秒 / 操作
    自定义错误处理性能:
        返回自定义业务错误:~0.48 纳秒 / 操作
        panic/recover 自定义业务错误:~73.39 纳秒 / 操作
    

    主要结论 错误返回方式的性能差异:

    直接返回 error 是最快的(亚纳秒级)
    defer 会带来约 6 倍的性能开销
    panic/recover 的开销最大,比直接返回慢约 230 倍
    

    错误类型检查的性能差异:

    直接类型断言最快(与普通错误返回相当)
    errors.As 有显著开销(约 36-49 纳秒)
    错误包装会进一步增加 errors.As 的开销(约 34% 的额外开销)
    

    自定义错误的影响:

    使用自定义错误类型的开销很小(比简单错误多约 0.15 纳秒)
    自定义错误的复杂度(字段数量)对性能影响很小
    panic/recover 的性能开销主要来自机制本身,与错误类型关系不大
    

    实践建议

    日常错误处理:
        优先使用返回 error 的方式
        不用担心自定义错误类型带来的性能影响
        需要时可以放心使用 defer,其开销在大多数场景下可以接受
    错误类型检查:
        如果知道具体错误类型,优先使用类型断言
        需要处理错误包装链时才使用 errors.As
        避免在热点代码路径中过度使用 errors.As
    panic/recover 使用:
        仅用于真正的异常情况
        不适合用作常规错误处理机制
        适用于程序初始化或不可恢复的错误场景
    

    性能优化建议

    在热点代码路径中避免使用 panic/recover
    如果需要频繁检查错误类型,优先使用类型断言
    在错误处理的性能要求不高的场景,可以优先考虑代码的清晰度和可维护性
    

    panic 和 recover 不能随便用,但是 errors.As 虽然也有性能开销,但是那一点也无所谓,因为这对于错误的传播非常有帮助。比如我就自定义了个 HTTPError,我可以直接返回这个错误,然后再进行统一的错误处理。比如直接返回一个 HTTPError,在错误处理时,这个错误肯定是可以被用户知道的。但是如果你返回了其他类型的错误,比如数据库错误,那么这个错误肯定是要被遮蔽的,需要通过日志进行输出,而不是告知用户。

    测试代码

    package main
    
    
    
    import (
    
        "errors"
    
        "fmt"
    
        "testing"
    
    )
    
    
    
    // 自定义错误类型
    
    type CustomError struct {
    
        msg string
    
    }
    
    
    
    func (e *CustomError) Error() string {
    
        return e.msg
    
    }
    
    
    
    // 另一个自定义错误类型用于测试性能
    
    type BusinessError struct {
    
        Code    int
    
        Message string
    
    }
    
    
    
    func (e *BusinessError) Error() string {
    
        return fmt.Sprintf("error code: %d, message: %s", e.Code, e.Message)
    
    }
    
    
    
    var (
    
        errTest     = errors.New("test error")
    
        customErr   = &CustomError{msg: "custom error"}
    
        wrappedErr  = fmt.Errorf("wrapped: %w", customErr)
    
        businessErr = &BusinessError{Code: 500, Message: "internal server error"}
    
    )
    
    
    
    // 使用传统错误返回的函数
    
    func returnError() error {
    
        return errTest
    
    }
    
    
    
    // 返回自定义错误
    
    func returnCustomError() error {
    
        return customErr
    
    }
    
    
    
    // 返回业务错误
    
    func returnBusinessError() error {
    
        return businessErr
    
    }
    
    
    
    // 返回包装的错误
    
    func returnWrappedError() error {
    
        return wrappedErr
    
    }
    
    
    
    // panic 业务错误
    
    func panicBusinessError() {
    
        panic(businessErr)
    
    }
    
    
    
    // 使用 defer 的传统错误返回函数
    
    func returnErrorWithDefer() error {
    
        var err error
    
        defer func() {
    
            // 空的 defer用于测试 defer 的开销
    
        }()
    
        err = errTest
    
        return err
    
    }
    
    
    
    // 不使用 defer 直接调用 recover错误示范
    
    func returnErrorWithoutDefer() error {
    
        // recover 在非 defer 函数中无效总是返回 nil
    
        if r := recover(); r != nil {
    
            return r.(error)
    
        }
    
        panic(errTest)
    
    }
    
    
    
    // 使用 panic 的函数
    
    func doPanic() {
    
        panic(errTest)
    
    }
    
    
    
    // 包装 panic/recover 的函数
    
    func returnErrorWithRecover() error {
    
        var err error
    
        func() {
    
            defer func() {
    
                if r := recover(); r != nil {
    
                    err = r.(error)
    
                }
    
            }()
    
            doPanic()
    
        }()
    
        return err
    
    }
    
    
    
    // 包装 panic/recover 业务错误的函数
    
    func returnBusinessErrorWithRecover() error {
    
        var err error
    
        func() {
    
            defer func() {
    
                if r := recover(); r != nil {
    
                    err = r.(error)
    
                }
    
            }()
    
            panicBusinessError()
    
        }()
    
        return err
    
    }
    
    
    
    // 测试传统错误返回的性能
    
    func BenchmarkErrorReturn(b *testing.B) {
    
        for i := 0; i < b.N; i++ {
    
            err := returnError()
    
            if err != nil {
    
                _ = err
    
            }
    
        }
    
    }
    
    
    
    // 测试带 defer 的错误返回性能
    
    func BenchmarkErrorReturnWithDefer(b *testing.B) {
    
        for i := 0; i < b.N; i++ {
    
            err := returnErrorWithDefer()
    
            if err != nil {
    
                _ = err
    
            }
    
        }
    
    }
    
    
    
    // 测试 panic/recover 的性能
    
    func BenchmarkPanicRecover(b *testing.B) {
    
        for i := 0; i < b.N; i++ {
    
            err := returnErrorWithRecover()
    
            if err != nil {
    
                _ = err
    
            }
    
        }
    
    }
    
    
    
    // 测试没有 defer  recover会导致 panic
    
    func BenchmarkRecoverWithoutDefer(b *testing.B) {
    
        // 这个测试会失败因为 recover 必须在 defer 
    
        b.Skip("这个测试会导致程序崩溃,因为 recover 必须在 defer 中使用")
    
        for i := 0; i < b.N; i++ {
    
            err := returnErrorWithoutDefer()
    
            if err != nil {
    
                _ = err
    
            }
    
        }
    
    }
    
    
    
    // 测试简单的 errors.As 性能
    
    func BenchmarkErrorsAs(b *testing.B) {
    
        var customErr *CustomError
    
        for i := 0; i < b.N; i++ {
    
            err := returnCustomError()
    
            if errors.As(err, &customErr) {
    
                _ = customErr
    
            }
    
        }
    
    }
    
    
    
    // 测试带包装的 errors.As 性能
    
    func BenchmarkErrorsAsWrapped(b *testing.B) {
    
        var customErr *CustomError
    
        for i := 0; i < b.N; i++ {
    
            err := returnWrappedError()
    
            if errors.As(err, &customErr) {
    
                _ = customErr
    
            }
    
        }
    
    }
    
    
    
    // 测试普通的类型断言性能作为对照
    
    func BenchmarkTypeAssertion(b *testing.B) {
    
        for i := 0; i < b.N; i++ {
    
            err := returnCustomError()
    
            if ce, ok := err.(*CustomError); ok {
    
                _ = ce
    
            }
    
        }
    
    }
    
    
    
    // 测试返回自定义业务错误的性能
    
    func BenchmarkReturnBusinessError(b *testing.B) {
    
        for i := 0; i < b.N; i++ {
    
            err := returnBusinessError()
    
            if err != nil {
    
                if be, ok := err.(*BusinessError); ok {
    
                    _ = be.Code
    
                    _ = be.Message
    
                }
    
            }
    
        }
    
    }
    
    
    
    // 测试 panic/recover 自定义业务错误的性能
    
    func BenchmarkPanicRecoverBusinessError(b *testing.B) {
    
        for i := 0; i < b.N; i++ {
    
            err := returnBusinessErrorWithRecover()
    
            if err != nil {
    
                if be, ok := err.(*BusinessError); ok {
    
                    _ = be.Code
    
                    _ = be.Message
    
                }
    
            }
    
        }
    
    }
    

    执行测试:

    go test -bench=. -benchmem
    

    Category: 待整理