一些开发笔记

在 VSCode 里调试 JAVA

  1. 安装 JDK
  2. 把 JDK 添加进环境变量
  3. 安装 VSCode 的 JAVA extension pack 拓展
  4. 使用 VSCode 打开文件夹
  5. 新增一个 debug 的类型 JAVA ,点击右下角的添加配置,选择 JAVA Lanuch Program
  6. 写一份测试的代码,并在 mian 函数加个断点
  7. 然后在终端里运行编译命令和运行命令,如果编译命令不复杂,可以配合 code runner 拓展,直接点击右上角的运行图标,或设置一个任务

在 VSCode 里调试 Python

  1. 安装 Python
  2. 把 Python 的安装目录和安装目录下的 script 添加进环境变量里
  3. 在 VSCode 安装 Python 拓展( microsoft 出品的)
  4. 新增一个 debug 的类型 Python ,点击右下角的添加配置,选择 Python: Terminal (integrated)
  5. 写一份测试的代码,并加个断点
  6. 然后在终端里运行,可以配合 code runner 插件,直接点击右上角的运行图标,或设置一个任务

在 Windows10 里修改 administrator 帐号的帐号名的三种方式

  1. 通过管理里的系统工具修改
选中桌面中的计算机图标,右键,点击管理
系统工具
    本地用户和组
        用户
            选中Administrator,然后重命名
重启电脑
  1. 通过策略租修改
win+r 打开运行
输入 gpedit.msc 打开策略组
计算机配置
    windows设置
        安全设置
            本地策略
                安全选项
                    拉到最下面
                    双击这个选选项 重命名系统管理员帐户
重启电脑
  1. 通过 netplwiz (Network Places Wizard) 网上邻居向导 修改
win+r 打开运行
输入 netplwiz
选中Administrator,然后双击,然后修改
重启电脑

Windows10 无密码远程连接

  1. 允许任何远程着桌面的连接
  2. 修改 Windows 的安全策略,允许远程桌面连接使用空密码
win+r 打开运行
输入 gpedit.msc 打开策略组
计算机配置
    windows 设置
        安全设置
            本地策略
                安全选项
                    拉到最下面
                    禁用 使用空密码的本地账户只允许控制台登录

ss 的使用

需要一个在 wall 以外的服务器

ss 的各个版本 https://github.com/shadowsocks/shadowsocks/wiki/Feature-Comparison-across-Different-Versions

python

  1. 下载 python
  2. 安装 python
  3. 把 python 添加到环境变量
  4. 刷新环境变量
  5. 下载 ss
    python -m pip install shadowsocks
    # 如果上面那句下载失败,可以尝试用这句安装
    python -m pip install https://github.com/shadowsocks/shadowsocks/archive/master.zip -U
    # 如果上面那句还是下载失败,可以尝试先把 master.zip 文件下载到本地,再安装
    
  6. 在服务器的策略组放行相应的端口
  7. 在防火墙放行相应的端口
  8. 运行以下命令
    ssserver -p 61813 -k windows@163.qq -m aes-256-cfb
        -p 是端口号
        -k 是密码
        -m 是加密方式
    上面那条命令需要在 服务器的策略组 和 防火墙 放行 61813 端口
    
  9. 下载客户端
  10. 正确填写 ip 端口 密码
  11. 一些加密的算法会依赖这个库 libsodium ,最好把这个库也装上
    https://download.libsodium.org/libsodium/releases/
    
  12. 更新 ss
    python -m pip install --upgrade shadowsocks
    # 如果上面那句更新失败,可以尝试这样更新,先卸载再重新安装,卸载前记得先备份当前的版本
    python -m pip show shadowsocks
    python -m pip uninstall shadowsocks
    python -m pip install https://github.com/shadowsocks/shadowsocks/archive/master.zip -U
    
  13. 失效时,可以尝试,更改端口,更改密码,更改加密方式,换一个ip
  14. 参考
    https://github.com/shadowsocks/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E
    

libev

VS 和 VC 库 的对应关系

Visual Studio 6.0 VC6
Visual Studio .NET 2002 VC7.0
Visual Studio .NET 2003 VC7.1
Visual Studio 2005 VC8
Visual Studio 2008 VC9
Visual Studio 2010 VC10
Visual Studio 2012 VC11
Visual Studio 2013 VC12
Visual Studio 2015 VC14
Visual Studio 2017 VC15
Visual Studio 2019 VS16
Visual Studio 2022 VS17

php 版本和 VC 库的对应关系

5.2 vc6
5.3 vc9
5.4 vc9
5.5 vc11
5.6 vc11
7.0 vc14
7.1 vc14
7.2 vc15
7.3 vc15
7.4 vc15
8.0 vs16
8.1 vs16

Windows 安全地删除U盘

U盘 弹出失败的原因是进程占用了 U盘 ,只要占用 U盘 的进程都不在占用 U盘 或 都结束了, U盘 就可以安全地弹出了。

所以,让 U盘 安全地弹出的关键是找到占用 U盘 的进程。

当占用 U盘 的进程结束后,有时立即弹出 U盘 还是会失败的,这时再等待五六秒,再试一次弹出 U盘 就可以了。

可以安全退出 U盘 时也不要马上拔 U盘 ,最好等个五六秒再拔 U盘 。

这是各种方法的总结

  1. 最简单的,在系统托盘右键,弹出,或在此电脑的界面,选中 U盘,右键,弹出
  2. 关掉所有文件夹再试一次弹出 U盘
  3. explorer.exe 重启,再试一次弹出 U盘
  4. 注销当前的登录,再次登录,再试一次弹出 U盘
  5. 通过任务管理器找到占用 U盘 的进程
    1. 打开任务管理器
    2. 切换到性能页面
    3. 打开资源监视器
    4. 切换到 cpu 页面
    5. 在 关联的句柄 栏目的搜索框内输入你的 U盘 盘符(如G:\)
    6. 即可看到当前占用 U盘 的进程
  6. 关闭 U盘 的写入缓存
    1. 控制面板
    2. 管理工具
    3. 计算机管理
    4. 设备管理
    5. 磁盘驱动
    6. 选择当前的 U盘
    7. 右键 属性 策略
    8. 关闭写缓存
    9. 关闭写缓存可能会造成 U盘 数据的丢失,关闭了写缓存后,要确保 U盘 里的文件已经保存好再弹出 U盘
  7. 在磁盘管理里使 U盘 脱机
    1. 控制面板
    2. 管理工具
    3. 磁盘管理
    4. 选择当前的 U盘
    5. 右键 脱机
    6. U盘 脱机后下次插入要重新挂载,脱机之后可以马上联机,再安全弹出,这样 U盘 下次插入时就不用联机了
    7. 脱机会直接关掉所有的文件句柄,要确保 U盘 里的文件已经保存好再脱机
    8. 脱机选项不可选,可能是有虚拟内存分配到 U盘 ,这时取消在 U盘 上的虚拟内存即可
      • 进入电脑属性->高级系统设置->高级->性能设置->高级->更改,关闭自动管理分页文件大小,取消在该磁盘上设置的虚拟内存
    9. 有时脱机还会失败,这时就继续参考下面的步骤
  8. 通过 日志 查找占用 U盘 的进程
    1. 打开事件查看器
    2. windows日志
    3. 系统
    4. 找到最近的一条 来源 Kernel-PnP 的记录
    5. 记录的常规里有占用 U盘 的 进程id 的
    6. 再通过任务管理器的 pid 找到对应的进程
    7. 有时,即使找到占用 U盘 的进程,但却无法结束进程,或 进程是系统的核心进程
      • 这时可以尝试以这样的关键词(进程名 或 进程id + Kernel-PnP )在网上搜索解决的方法
  9. 如果是 Windows Defender 占用 U盘 可以试试这样操作
    1. 打开 Windows Defender 安全中心
    2. 病毒和威胁防护
    3. 病毒和威胁防护 设置
    4. 将在使用的 U盘 盘符添加为排除项
  10. 最后的方法,关机,关机后就肯定可以安全地拔 U盘

笔者通常在直接弹出 U盘 失败后,会多试几次,然后通过 日志 找到对应的进程,然后通过 任务管理器 结束对应的进程,如果结束进程失败,还是会再次在网上搜索解决方法。 脱机 和 关闭写缓存都有一点副作用; 关闭文件夹,重启 explorer.exe ,注销,关机,这类都有点麻烦。

windows 下的 linux 环境

其实就三种套路

wsl1 和 wsl1 之前的 sfu/sua 是子系统, wsl2 其实算是虚拟机,其它的都是运行在 win32 上的兼容层。 nt 内核其实是有三个子系统 win32 ,os/2 和 posix ,只是除了 win32 其它两个都没有什么存在感。

所谓的 linux 环境,除了需要 bash 之外,还需要各种 linux 的工具,还需要处理管道,还需要处理文件名和路径的问题。 还有各种 linux api 的问题(但不写需要编译的代码,只使用环境中的工具,其实这个问题可以忽略的)。

下载 AcFun 视频

0. 需求

  1. 一部安卓手机
  2. AcFun 安卓版客户端
  3. ffmpeg

1. 使用手机客户端缓存视频

2. 在手机的文件管理器里找到缓存文件

  1. 用文件管理器打开手机的根目录
  2. 找到 acfun 文件夹
  3. 点进这个目录里 acfun→core→local
  4. 然后点进一个以数字名命的文件夹
  5. 这个文件夹里就是缓存的视频,文件名都没有后缀,可以通过创建时间大致判断出哪个是最新缓存的视频

3. 在电脑里下载 ffmpeg

  1. 打开 ffmepg 的下载网址,并选择系统对应的版本下载
http://www.ffmpeg.org/download.html
  1. 把下载后的文件解压
  2. 解压后的文件里会有个一个名为 ffmpeg 的文件,这个就是 ffmpeg 的主程序,可以用来转换视频格式

4. 把缓存的视频文件复制进电脑,并使用 ffmpeg 转换为 MP4 格式

  1. 把缓存文件从手机复制进 ffempeg 的解压目录
  2. cd 进 ffempeg 的解压目录
  3. 运行以下命令,转换格式后的文件名必须带有 MP4 的后缀
ffmpeg -i "缓存视频文件的文件名" "转换格式后的文件名"

在Windows下配置Tomcat服务器

0. 目标

1. 下载 JAVA

https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

选择,Windows 64 位

2. 安装 JAVA

打开下载的 exe 文件,一路点 next 直到安装完毕

3. 配置 JAVA 环境变量

  1. 新建一个环境变量 JAVA_HOME ,值为 JDK 的安装目录,例子
C:\Program Files\Java\jdk1.8.0_161
  1. 把以下值加入到环境变量 Path
%JAVA_HOME%\bin
%JAVA_HOME%\jre\bin
%JAVA_HOME%\lib

3. 下载 Tomcat

https://tomcat.apache.org/download-90.cgi

选择,core,Windows 64 位

4. 配置 Tomcat

  1. 解压下载的文件
  2. 把解压后的文件夹复制到 C 盘的根目录下(这里可以是任意目录)
  3. 把 Tomcat 目录下的 bin 文件夹的路劲加入的环境变量
  4. 把网站程序复制进 Tomcat 的 webapps 文件夹
  5. 启动 Tomcat ,启动 Tomcat 的脚本在 bin\startup.bat

DOS 的启动过程

安装 PHP7 的 GUI 扩展

1 下载拓展

2 安装拓展

  1. 把下载下来的压缩包解压
  2. 把 php_ui.dll 复制到 PHP 的 ext 目录下
  3. 把 libui.dll 和 pthreadVC2.dll 放到 PHP 的根目录下

3 运行 demo

注意

如果出现这种错误

无法启动此程序,因为计算机中丢失 libui.dll,尝试重新安装该程序以解决此问题。

这个提示出现说明你没有放入 libui 和 pthreadVC2 文件到 php 的根目录下

linux 实现后台不间断运行

gcc 编译流程

编译的四个步骤

  1. 预处理 由 .c 文件到 .i 文件。
    • 对各种预处理命令进行处理,包括头文件包含、宏定义的扩展、条件编译的选择等
  2. 编译 由 .i 文件到 .s 文件。
    • 将预处理得到的源代码文件,进行“翻译转换”,产生出机器语言的目标程序,得到机器语言的汇编文件
  3. 汇编 由 .s 文件到 .o 文件。
    • 将汇编代码翻译成了机器码,但是还不可以运行
  4. 链接 由 .o 文件到可执行文件。
    • 处理可重定位文件,把各种符号引用和符号定义转换成为可执行文件中的合适信息,通常是虚拟地址

编译的四个步骤对应的 gcc 命令

gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o test

实际上 gcc 这个命令只是这些后台程序的包装,它会根据不同的参数要求去调用预处理器cpp、预编译程序cc1、汇编器as、链接器ld

cpp test.c -o test.i
cc1 test.i -o test.s
as test.s -o test.o
ld test.o -o test

GNU 的命令

GNU 工具链

其它

参考

感觉 GNU 真的除了内核之外,什么都有了。

让网站支持 ipv6

  1. 拥有一个 ipv6 地址
  2. 域名解释 aaaa 记录指向 ipv6 的地址
  3. 让网站程序支持 ipv6 地址
    • 通过网关翻译
      • 代码里用 ipv4 地址,域名解释 aaaa 记录到 网关,网关把 ipv6 的流量翻译成 ipv4 的流量再传递给网站
      • 代码里用 ipv6 地址,域名解释 a 记录到 网关,网关把 ipv4 的流量翻译成 ipv6 的流量再传递给网站
    • 让 web 服务器同时监听两个地址,代码里和 ip 相关的部分,都改成能兼容 ipv4 和 ipv6

用来测试网站对 ipv6 的支持,也可以用来查看 DNSSEC 的支持 https://ipv6.ustc.edu.cn/onlinechecklog.php

在命令行里格式化 json

  1. python 的 json.tool ,好像 python2 和 3 都可以这样用

    echo '{ "name": "xiaohong", "age": 18 }' | python -m json.tool
    
    • 会转义中文,如果想不转义中文
    • 需要修改标准库里的文件,这样不是很好,在 python 的标准库 json 文件夹下有个 tool.py 文件,更改其中调用的 json.dump 函数,传一个ensure_ascii = False 参数即可
  2. json_pp , windwos 的 git bash 和 大多数 linux 发行版都有这个工具

    echo '{ "name": "xiaohong", "age": 18 }' | json_pp
    
  3. jq , 虽然大多数 linux 发行版都没有这个工具,但中文互联网环境下有好多文章都推荐这个

  4. php 的命令行,标准输入中一定要有数据,不然会一直等待;同样地 python 或 node 也可以实现类似的

    echo '{ "name": "xiaohong", "age": 18, "chinese character":"汉字" }' | \
    php -r 'print(json_encode(json_decode(file_get_contents("php://stdin")),JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));';
    
  5. PowerShell 的 ConvertFrom-Json 和 ConvertTo-Json ,这两个要组合来使用

    echo '{"type":"image","offset":0,"count":20}' | ConvertFrom-Json | ConvertTo-Json
    

配合 curl 使用

curl -s -k https://localhost/dev.json | json_pp
curl -s -k https://localhost/dev.json | python -m json.tool

配合 PowerShell 的 Invoke-WebRequest 使用

Invoke-WebRequest https://localhost/dev.json -UseBasicParsing -SkipCertificateCheck | Select -ExpandProperty Content | ConvertFrom-Json | ConvertTo-Json
Invoke-WebRequest https://localhost/dev.json -UseBasicParsing -SkipCertificateCheck | Select -ExpandProperty Content | python -m json.tool

如果 Invoke-WebRequest 出现了这种错误。

因为 Internet Explorer 引擎不可用,或者 Internet Explorer 的首次启动配置不完整

可以尝试以下步骤来解决

  1. 打开 IE 的 internet 选项
  2. 点击安全选项卡,选中本地 intranet ,并点击站点按钮
  3. 新的窗口中点击高级
  4. 添加 about:security_powershell.exe 到输入框,点击添加
  5. 把所有 IE 窗口一个个关闭就好了,再次在 powershell 下运行 Invoke-WebRequest

Windows 里的 Java 环境配置

  1. 下载并安装 JDK
  2. 把 JDK 加入环境变量
    1. 新建一个新的环境变量 JAVA_HOME ,值是 JDK 的根目录
    2. 在 PATH 里加入 %JAVA_HOME%\bin 和 %JAVA_HOME%\lib 和 %JAVA_HOME%\jre\bin (如果没有这个目录就忽略)
    3. 如果是 JDK 1.5 及之前的版本还需要一个 CLASSPATH 的环境变量
      • CLASSPATH 的值是 .;%Java_Home%\bin;%JAVA_HOME%\lib;%Java_Home%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
  3. 下载并安装 maven
  4. 把 maven 加入环境变量
    1. 新建一个新的环境变量 MAVEN_HOME ,值是 maven 的根目录
    2. 在 PATH 里加入 %MAVEN_HOME%\bin

windows10 之前的系统在修改 path 时要注意分号 ;

在 JDK1.5 以后,classpath 并不是必须配置了,在 JDK1.5 之前,是没有办法在当前目录下加载类的(找不到 JDK 目录下 lib 文件夹中的 .jar 文件),所以我们需要通过配置 classpath ,但 JDK1.5 之后, JRE 能自动搜索目录下类文件,并且加载 dt.jar 和 tool.jar 的类。 dt.jar 是关于运行环境的类库,主要是用于 swing 的包,如果不使用可以不配置。 tools.jar 是工具类库。

编译和运行时可以通过参数 -classpath 指定 classpath 的路径,例如这样

javac -encoding UTF-8 -classpath .;./junit4.jar;./org.hamcrest.core_1.3.0.jar AaaTest.java JunitRunner.java
java -Dfile.encoding=UTF-8 -classpath .;./junit4.jar;./org.hamcrest.core_1.3.0.jar JunitRunner

无法执行 powershell 脚本

通常是执行策略的原因导致的。

设置脚本执行策略,通常把 策略 设为 RemoteSigned 就可以了

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

查看 powershell 脚本执行策略

Get-ExecutionPolicy
Get-ExecutionPolicy -List

https://docs.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.2

python 的 http 服务和 cgi

python 一行命令启动 http 服务

# 最简单的 http 服务
python -m http.server
# 有 cgi 的 http 服务
python -m http.server --cgi
# 有 cgi 的和指定端口号的 http 服务
python -m http.server --cgi 8081
# 有 cgi 的,指定端口号的和指定ip地址的 http 服务
python -m http.server --cgi 8081 --bind 127.0.0.1
# 有 cgi 的,指定端口号的,指定ip地址的和指定站点根目录的 http 服务
python -m http.server --cgi 8081 --bind 127.0.0.1 --directory _book

请求 cgi 脚本的例子

curl 127.0.0.1:8000/cgi-bin/test.py

cgi 脚本的例子

#!/usr/bin/python3

print ("Content-type:text/html")
print ()                             # 空行,告诉服务器结束头部
print ('<html>')
print ('<head>')
print ('<meta charset="utf-8">')
print ('<title>Hello Word</title>')
print ('</head>')
print ('<body>')
print ('<h2>Hello Word!</h2>')
print ('</body>')
print ('</html>')
#!/usr/bin/python3

print ("Content-type: application/json")
print ()                             # 空行,告诉服务器结束头部
print ('{"result": "this is a test"}')

这个例子大致相当于这个命令 python -m http.server 8888

from http.server import HTTPServer, SimpleHTTPRequestHandler

if __name__ == '__main__':
    host = ('0.0.0.0', 8888)
    server = HTTPServer(host, SimpleHTTPRequestHandler)
    print('Serving HTTP on {host} port {port} (http://{host}:{port}/) ...'.format(host=host[0], port=host[1]))
    server.serve_forever()

这个例子大致相当于这个命令 python -m http.server --cgi 8888

from http.server import HTTPServer, CGIHTTPRequestHandler

if __name__ == '__main__':
    host = ('0.0.0.0', 8888)
    server = HTTPServer(host, CGIHTTPRequestHandler)
    print('Serving HTTP on {host} port {port} (http://{host}:{port}/) ...'.format(host=host[0], port=host[1]))
    server.serve_forever()

不使用命令行的 http 服务例子,这是单线程的

from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class myHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps({'result': 'this is a test'}).encode())

if __name__ == '__main__':
    host = ('0.0.0.0', 8888)
    server = HTTPServer(host, myHandler)
    print('Serving HTTP on {host} port {port} (http://{host}:{port}/) ...'.format(host=host[0], port=host[1]))
    server.serve_forever()

不使用命令行的 http 服务例子,这是多线程的

from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import json

class ThreadingHttpServer(ThreadingMixIn, HTTPServer):
    pass

class myHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps({'result': 'this is a test'}).encode())

if __name__ == '__main__':
    host = ('0.0.0.0', 8888)
    server = ThreadingHttpServer(host, myHandler)
    print('Serving HTTP on {host} port {port} (http://{host}:{port}/) ...'.format(host=host[0], port=host[1]))
    server.serve_forever()

不使用命令行的 http 服务例子,这是多线程的,再加上 cgi 的支持

from http.server import HTTPServer, CGIHTTPRequestHandler
from socketserver import ThreadingMixIn

class ThreadingHttpServer(ThreadingMixIn, HTTPServer):
    pass

if __name__ == '__main__':
    host = ('0.0.0.0', 8888)
    server = ThreadingHttpServer(host, CGIHTTPRequestHandler)
    print('Serving HTTP on {host} port {port} (http://{host}:{port}/) ...'.format(host=host[0], port=host[1]))
    server.serve_forever()

HTTPServer 和 BaseHTTPRequestHandler 是两个关键的类,一个用于接收 http 请求,一个用于处理请求,其它类基本是派生自这两个类

BaseHTTPRequestHandler -> SimpleHTTPRequestHandler -> CGIHTTPRequestHandler
HTTPServer -> ThreadingHttpServer

参考文档

debian 一句命令安装 docker

因为要经常部署和重装系统,所以就整理了这样一句命令,可能会因为 docker 的更新而失效,要注意修命令里的版本号

具体环境

这是文档 https://docs.docker.com/engine/install/debian/

sudo apt-get remove -y docker docker-engine docker.io containerd runc ; \
sudo apt-get update && \
sudo apt-get install -y \
    ca-certificates \
    curl \
    gnupg \
    lsb-release && \
if [ -e /usr/share/keyrings/docker-archive-keyring.gpg ] ; \
  then rm /usr/share/keyrings/docker-archive-keyring.gpg; \
  else curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg ;fi && \
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt-get update && \
sudo apt-get install -y docker-ce docker-ce-cli containerd.io && \
sudo docker run --rm hello-world && \
sudo curl -L --retry 100 --retry-delay 2 "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && \
sudo chmod +x /usr/local/bin/docker-compose && \
docker-compose --version

如果 docker-compose 总是下载失败,可以尝试使用这样的脚本下载

COMMAND="curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose"
for ((i=0;i<100;i++))
do
    $COMMAND
    if [ $? -eq 0 ]; then
        exit 0;
    fi
done
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version

生成用于测试的容器的命令

具体环境

docker pull debian:11

docker run \
    -it \
    --rm \
    -p 443:443 \
    debian:11 /bin/bash

cp /etc/apt/sources.list /etc/apt/sources.list_bak && \
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
apt update && \
apt install -y vim && \
apt install -y curl && \
apt install -y net-tools && \
apt install -y netcat && \
apt install -y procps

apt install -y bsdmainutils
apt install -y python3
apt install -y python3-pip

curl 断点续传

例子

curl -C - -o php-7.3.11.tar.gz https://www.php.net/distributions/php-7.3.11.tar.gz

解释

-L 允许重定向
-C 开启断点续传
 - 这个表示开始和结束位置,一般就这样就可以了
--retry 100 超时重试 100 次,也可以是断点续传时的重试
--retry-delay 5 每次超时后等待 5 秒再重试
--connect-timeout 5 连接超过 5 秒算超时
    一次连接超时时间。如果出错, 提示形如:curl: (28) connect() timed out!
--max-time 10 单次请求的最大时间
    一次连接过程最大的允许时间。出错提示如:curl: (28) Operation timed out after 2000 milliseconds with 0 bytes received
--max-time 要大于 --connect-timeouts
--retry-max-time 30 整体请求的最大时间
-o 参数将服务器的回应保存成文件
-O 参数将服务器回应保存成文件,并将 URL 的最后部分当作文件名。
如果服务器主动返回 失败 例如 reset 这类的,就会直接退出的了,不论有没有设置 --retry

github 好像不支持断点下载

在 debian 新建命令的别名

用于 git pull 和 git push 的脚本

因为 github 的 pull 和 push 总是超时,所以写了两段脚本用于失败后的自动重试

#!/bin/bash

# ./git_help.sh pull
# ./git_help.sh push

GIT_COMMAND="git "
case $1 in
    "push"|"pull")
        GIT_COMMAND=$GIT_COMMAND$1
    ;;
    *)
        echo "only input push or pull";
        exit 1;
    ;;
esac

for ((i=0;i<100;i++))
do
    $GIT_COMMAND
    if [ $? -eq 0 ]; then
        exit 0;
    fi
done
# ./git_help.ps1 pull
# ./git_help.ps1 push

param($a)
$GIT_COMMAND = "git "
if ($a -eq "push") {
  $GIT_COMMAND = $GIT_COMMAND + $a
} elseif ($a -eq "pull") {
  $GIT_COMMAND = $GIT_COMMAND + $a
} else {
  echo "only input push or pull"
  exit 1
}

for ($i = 1; $i -lt 100; $i++) {
  echo $i" "$GIT_COMMAND
  PowerShell -command $GIT_COMMAND
  # Invoke-Expression $GIT_COMMAND
  # Invoke-Command -ScriptBlock {Write-Host $GIT_COMMAND}
  # Invoke-Command -ScriptBlock {$GIT_COMMAND}
  if ($? -eq $true) {
    break
  }
}

href 和 src

href 是 Hypertext Reference (超文本引用) 的缩写。 href 目的是为了建立联系,让当前标签能够链接到目标地址。

src 是 source (来源) 的缩写。 指向的内容将会应用到文档中当前标签所在位置。 例如 img 和 script 的 src 属性。

href 和 src 的值通常是 url 但也可以不是 url 。 其中一个例子就是 img 标签的 src 可以直接放 base64 的值。

可以简单但不严谨地理解为 href 不会加载, src 会加载。 这样解释好象很有道理。 但 link 标签是用 href 的。 link 标签能加载 css 和 页面图标 还有 manifest 还有 预加载的 js 。

URI URL URN

它们的全称

URL,URN,URC 都属于 URI 。 语法都是

URI = scheme ":" scheme-specific-part
URI = scheme ":" ["//" authority] path ["?" query] ["#" fragment]
authority = [userinfo "@"] host [":" port]

一些例子

          userinfo       host      port
          ┌──┴───┐ ┌──────┴──────┐ ┌┴┐
  https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top
  └─┬─┘   └───────────┬──────────────┘└───────┬───────┘ └───────────┬─────────────┘ └┬┘
  scheme          authority                  path                 query           fragment

  ldap://[2001:db8::7]/c=GB?objectClass?one
  └┬─┘   └─────┬─────┘└─┬─┘ └──────┬──────┘
  scheme   authority   path      query

  mailto:John.Doe@example.com
  └─┬──┘ └────┬─────────────┘
  scheme     path

  news:comp.infosystems.www.servers.unix
  └┬─┘ └─────────────┬─────────────────┘
  scheme            path

  tel:+1-816-555-1212
  └┬┘ └──────┬──────┘
  scheme    path

  telnet://192.0.2.16:80/
  └─┬──┘   └─────┬─────┘│
  scheme     authority  path

  urn:oasis:names:specification:docbook:dtd:xml:4.1.2
  └┬┘ └──────────────────────┬──────────────────────┘
  scheme                    path

URN

URN 的作用是描述资源的身份,例如 一个人的名字。 URN 的其中一个应用例子是 图书的 ISBN 号码 。

URN 的语法

urn:NID:NSS

NID 是 namespace identifier (命名空间标识符) 的缩写。

NSS 是 namespace specific string (命名空间特定字符串) 的缩写。

URN 的 NID 和 NSS 部分相当于 URI 里的 path 部分。

URL

URL 的作用是描述资源的访问路径,例如 一个人的住址。

URL 的语法

                    hierarchical part
        ┌───────────────────┴─────────────────────┐
                    authority               path
        ┌───────────────┴───────────────┐┌───┴────┐
  abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
  └┬┘   └───────┬───────┘ └────┬────┘ └┬┘           └─────────┬─────────┘ └──┬──┘
scheme  user information     host     port                  query         fragment

其它

URC (统一资源特征),在九十年代的时候, URL URI URC 被期望能组成一个互联网信息架构。 但 URC 一直停留在理论阶段,随之更晚出现的其他技术(例如 资源描述框架)取代了它们。

URI 的 scheme 和 URN 的 NID 都需要在 IANA 注册。

完整的 url 或 urn 会被称为绝对 uri , 只有一部分的 url 或 urn 会被称为相对 uri 。

还有一个 Data URI 或者叫做 Data URL 。 这是具体的语法,也属于 URI 。

dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype  := [ type "/" subtype ] *( ";" parameter )
data       := *urlchar
parameter  := attribute "=" value

比较好区分的一个特征是,以 urn 开头的 uri 就一定是 urn , 以 data 开头的 uri 就一定是 data url 。 除此之外的都是 url 。

url 两个斜杆 // 其实是没什么作用的

为什么文件 URL 以3斜杠开头?

参考

linux 系统的启动流程

  1. 通电
  2. 加电自检 (POST, Power-On Self-Test)
  3. 硬件初始化
    • legacy BIOS
      • MBR
    • UEFI
      • GPT
  4. 执行引导程序 (bootloader)
    • LILO (LInux LOader)
    • SYSLINUX
    • GRUB (GNU Grand Unified Bootloader)
  5. 加载和启动 linux 内核镜像
    • initrd
    • initramfs
  6. 执行 init
    • init (SystemV init)
    • UpStart
    • systemd (system deamon)
  7. 执行守护进程

现在主流的启动过程是

MBR -> GRUB -> initramfs -> systemd

简单但又不严谨地理解, init 是第一个运行在用户态的进程。 这里描述的 init 可能是 SystemV init 也可能是 UpStart 也可能是 systemd 。

笔者认为作为一个写上层应用的程序员,了解到 加载和启动 linux 内核镜像 这一层就已经足够深入的了。

获取本机公网 IP 的几个方法

# 获取本机公网 IP 
curl ifconfig.me
curl icanhazip.com
curl ipinfo.io/ip
curl ipecho.net/plain
curl www.trackip.net/i
curl ip.sb
curl whatismyip.akamai.com
curl ifconfig.co
curl ident.me
curl inet-ip.info
curl 'https://api.ipify.org?format=json' #ipv4
curl 'https://api64.ipify.org?format=json' #ipv6
# 获取本机公网 IP ,这几个有返回位置信息
curl myip.ipip.net
curl ip.cn
curl cip.cc
curl ip-api.com
curl ip-api.com/json
# 查询某个ip的信息
curl http://ip-api.com/json/58.62.220.66

在命令行中直接运行代码

大概就两种套路,从标准输入读取代码,从命令行参数读取代码,从命令行参数读取代码时要留意转义字符

php

代码开头都不需要 <? 或 <?php

phpcode=$(cat <<- 'EOF'
echo '123';
echo "asd";
$a="qwe";
echo $a;
echo "\"\$a\"\\";
var_dump($argv);
EOF
);

echo $phpcode | php -a -- a=1;

php -r "$phpcode" -- a=1;

php -a <<- 'EOF'
echo '123';
echo "asd";
$a="qwe";
echo $a;
echo "\"\$a\"\\";
var_dump($argv);
EOF

node

nodecode=$(cat <<- 'EOF'
console.log('hello');
EOF
);

echo $nodecode | node

node -e "$nodecode"

node <<- 'EOF'
console.log('hello');
EOF

python

执行标准输入里的代码,和普通文件里的代码一样,必须要有换行符

pythoncode=$(cat <<- 'EOF'
print('hi1')
print('hi2')
EOF
);

echo $pythoncode | python

python << 'EOF'
print('hi1')
print('hi2')
EOF

在一行里,换行用分号;替代
python -c "import os;print(os.environ['PATH'])"

分页公式

相关变量

计算总页数

伪代码

totalPage = ceil((totalRecord  +  pageSize  - 1) / pageSize);

php

$totalPage = ceil(($totalRecord  +  $pageSize  - 1) / $pageSize);

sql

SELECT @totalRecord := 123, @pageSize := 6;
SELECT ceil((@totalRecord + @pageSize + 1) / @pageSize);

ceil 是 向上取整 的函数

sql

关于分页的语法

SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
三种写法
SELECT * FROM table LIMIT rows
SELECT * FROM table LIMIT offset, rows
SELECT * FROM table LIMIT rows OFFSET offset

具体的例子

-- 每页 10 行,第 1 页
SELECT * FROM table LIMIT 10
SELECT * FROM table LIMIT 0, 10
SELECT * FROM table LIMIT 10 OFFSET 0
-- 每页 10 行,第 2 页
SELECT * FROM table LIMIT 10, 10
SELECT * FROM table LIMIT 10 OFFSET 10
-- 每页 10 行,第 3 页
SELECT * FROM table LIMIT 20, 10
SELECT * FROM table LIMIT 10 OFFSET 20
-- 每页 10 行,第 4 页
SELECT * FROM table LIMIT 30, 10
SELECT * FROM table LIMIT 10 OFFSET 30

这是获取总行数的 sql

SELECT count(*) FROM table LIMIT 1;

这是获取总页数的 sql

SELECT @pageSize := 6;
SELECT ceil((count(*) + @pageSize + 1) / @pageSize) FROM table LIMIT 1;
SELECT ceil((count(*) + 7) / 6) from table LIMIT 1;

这是获取某一页的 sql

-- 每页 6 行,第 2 页
SELECT @pageSize := 6;
SELECT @pageNo := 2;

SELECT @rows := @pageSize;
SELECT @offset := (@pageNo-1)*@pageSize;

SELECT * FROM table LIMIT @offset, @rows;
SELECT * FROM table LIMIT @rows offset @offset;

计算当前页的大小

计算当前页的行数,这是伪代码
function getCurrentPageSize(pageSize, pageNo, totalRecord, totalPage)
{
    if (pageNo < totalPage) {
        return pageSize;
    } else if (pageNo == totalPage) {
        return pageSize + (totalRecord - totalPage*pageSize);
    } else {
        // Throw an exception
    }
}

安装 busybox

apt install -y busybox
docker run -it --rm --name my-busybox busybox:1.36-glibc sh
type busybox
busybox --help
https://busybox.net/live_bbox/live_bbox.html
https://frippery.org/busybox/
https://github.com/rmyorston/busybox-w32

建议下载 busybox64u.exe 这个版本,64位且支持 unicode ,虽然这个版本只支持 win10和win11

编译安装

直接用 apt 安装的 busybox 版本太旧了, busybox 常常要用到宿主机的文件,容器里的 busybox 用起来不怎么方便。

安装前置的依赖

apt install -y bzip2 && \
apt install -y make && \
apt install -y gcc

下载源码

curl -O "https://busybox.net/downloads/busybox-1.36.1.tar.bz2"

解压源码

tar -xjf ./busybox-1.36.1.tar.bz2

切换进源码目录并开始安装

cd busybox-1.36.1
make defconfig
make install

编译后的 可执行二进制文件 可能在当前编译的目录,可能在源码的目录,反正要找一下

find / -name busybox

找到后就复制或剪切到 /bin 目录

cp /root/busybox-1.36.1/busybox /bin/busybox

一句命令完成下载和安装,但需要是 root 用户,其它用户需要修改对应的目录

apt install -y bzip2 && \
apt install -y make && \
apt install -y gcc && \
cd && \
curl -O "https://busybox.net/downloads/busybox-1.36.1.tar.bz2" && \
tar -xjf ./busybox-1.36.1.tar.bz2 && \
cd busybox-1.36.1 && \
make defconfig && \
make install && \
cp /root/busybox-1.36.1/busybox /bin/busybox && \
cd

如何自建一个 DNS 服务

使用这个仓库的代码来搭建 DNS , 使用这个仓库是因为笔者平时主要使用php做开发, https://github.com/yswery/PHP-DNS-SERVER

使用的是 v1.4.1 版本

git checkout v1.4.1
  1. 下载源码
https://github.com/yswery/PHP-DNS-SERVER
  1. 安装依赖,加上 --ignore-platform-reqs 参数,是因为这个仓库声明依赖的php版本比较旧,没这个参数可能无法下载composer里的依赖
composer install --no-suggest --no-dev --ignore-platform-reqs
  1. 参考 example/example.php 文件自己新建一个启动用的php文件 startup.php
require_once __DIR__.'/vendor/autoload.php';

$json = <<< EOF
{
    "test.localhost.com": {
      "A": "127.0.0.1"
    }
}
EOF;

class JsonTextResolver extends \yswery\DNS\Resolver\JsonResolver
{
    public function addZoneText(string $zone)
    {
        $zone = json_decode($zone, true);
        $this->addZoneArr($zone);
    }

    public function addZoneArr(array $zone)
    {
        $resourceRecords = $this->isLegacyFormat($zone) ? $this->processLegacyZone($zone) : $this->processZone($zone);
        $this->addZone($resourceRecords);
    }
}

// JsonResolver created and provided with json dns records
$jsonResolver = new JsonTextResolver([]);
$jsonResolver->addZoneText($json);

// ipconfig /flushdns
// php localhost2.php

// System resolver acting as a fallback to the JsonResolver
$systemResolver = new yswery\DNS\Resolver\SystemResolver();

// StackableResolver will try each resolver in order and return the first match
$stackableResolver = new yswery\DNS\Resolver\StackableResolver([$jsonResolver, $systemResolver]);

// Create the eventDispatcher and add the event subscribers
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$eventDispatcher->addSubscriber(new \yswery\DNS\Event\Subscriber\EchoLogger());
$eventDispatcher->addSubscriber(new \yswery\DNS\Event\Subscriber\ServerTerminator());

// Create a new instance of Server class
$config = null;
$storageDirectory = null;
$useFilesystem = false;
$ip = '127.0.0.1';
$port = 53;
$server = new yswery\DNS\Server($stackableResolver, $eventDispatcher, $config, $storageDirectory, $useFilesystem, $ip, $port);

// Start DNS server
$server->start();
  1. 这是启动的命令,可以把这个命令保存在一个用于启动的脚本文件
php startup.php
  1. 修改网卡的 dns 配置

  2. 可以用这样的命令来测试是否生效,如果没有生效可以尝试刷洗 dns 缓存

nslookup -debug -querytype=A -port=53 test.localhost.com 127.0.0.1
  1. 如何写配置文件可以参考仓库里的文档或参考 example 里的文件

  2. 如果不把设为服务,则每次开机后都要手工启动一次

在本地使用 unbound 搭建一个 DNS 服务

笔者原本是想用php的库来建一个本地的dns服务,但找了很久都没找到合适的,最后还是选择用 unbound , 和 bind 相比, unbound 提供了免编译免安装的版本。

使用的是 v1.19.0 版本

  1. unbound 的仓库
https://github.com/NLnetLabs/unbound
  1. unbound 的文档
https://unbound.docs.nlnetlabs.nl/en/latest/index.html
  1. 下载 unbound
https://nlnetlabs.nl/projects/unbound/download/
  1. 下载完后,解压,然后复制一份配置文件
cp example.conf unbound.conf
  1. 修改配置文件,整个配置文件非常大,在特定位置改好这几项就好了

interface: 127.0.0.1

local-data: "test.localhost.com A 127.0.0.1"

forward-zone:
    name: "."
    forward-addr: 系统原本的主dns地址
    forward-addr: 系统原本的备用dns地址
  1. 检测配置文件
unbound-checkconf unbound.conf
./unbound-checkconf.exe unbound.conf
  1. 运行
# 在前台运行
unbound -vv -c unbound.conf
./unbound.exe -vv -c unbound.conf

# 在后台运行
unbound -d -vv -c unbound.conf
./unbound.exe -d -vv -c unbound.conf

那些能作为工具的网站