以 Arduino IDE v2.3 为例,需要修改两个地方。 1、使 IDE 在编辑时 Tab 键缩进 4 个空格: 修改 C:\Users\<你的用户名>\.arduinoIDE\settings.json,增加 "editor.tabSize": 4 2、使自动格式化(Ctrl + T)缩进 4 个空格: 在 C:\Users\<你的用户名>\.arduinoIDE\ 创建文件 .clang-format,添加内容:IndentWidth:...
阡陌 发布的文章
阡陌修改 pip 源为国内镜像源
由于网络原因,直接使用 pip 默认源会很慢得可怕,这里提供一些国内的镜像,你可以选择使用: 阿里云:https://mirrors.aliyun.com/pypi/simple/ 中国科技大学:https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣(douban):http://pypi.douban.com/simple/ 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/ 中国科学技术大学:https://pypi.mirrors.ustc.edu.cn/simple/ 你可以在使用 pip 时通过 -i 参数指定使用上述源: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package 如果想要永久修改,可以在 pip 配置文件中设置。配置文件位置和名称取决于操作系统: Linux/Unix: ~/.pip/pip.conf Windows: %APPDATA%\pip\pip.ini(%APPDATA% 指 C:\Users\用户名\AppData\Roaming) 若文件不存在,则需手动创建。 配置文件内容如下: [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple 对于使用 http 的源,可以选择性地加上 trusted-host 以信任它,避免出现警告信息。例如: [global] index-url = http://pypi.douban.com/simple trusted-host = pypi.douban.com 这样配置后,你每次使用 pip 安装包时都会默认使用这个...
Ventoy:新一代多系统启动 U 盘解决方案
以前可能会用 Rufus、UltraISO 等工具将操作系统 ISO 镜像文件写入 U 盘,制作成可启动的 USB 启动盘;也可能会选择微 PE(WePE)、优启通(EasyU)、FirPE 等 WinPE 制作工具将 U 盘制作成启动盘。 Ventoy 也是一个制作可启动U盘的工具,与众不同的是它只需将 U 盘格式化一次,即可将操作系统 ISO、各种 WinPE,甚至是虚拟磁盘文件(VHD/VHDX)都给一锅给烩了。而且无需换个系统就折腾一次 U 盘,直接把他们拷贝到 U 盘存储区就行。 与传统 USB 启动盘制作相比: 无需重复制作启动盘:传统的启动盘每次只能装载一个操作系统镜像,如果需要更换操作系统,就需要重新制作启动盘。而 Ventoy 则打破了这一限制,它允许你在同一个 U 盘上存放多个操作系统镜像,无需每次都重新制作启动盘。 操作简便:传统启动盘的制作通常需要使用特定的软件,并且制作过程相对复杂。而 Ventoy 则非常简单,只需将 ISO 文件复制到 U 盘即可,无需任何额外的操作。 强大的兼容性:Ventoy 不仅支持 Windows 系列操作系统,还广泛支持各种 Linux 发行版。这意味着无论你使用的是什么操作系统,Ventoy 都能满足你的需求。 智能识别启动方式:无论你的电脑主板使用的是 Legacy BIOS 还是 UEFI 模式,Ventoy 都能自动检测并识别,确保启动的顺利进行。 数据安全可靠:在升级 Ventoy 版本时,用户的数据不会丢失,这意味着你可以放心地进行升级操作,而无需担心数据丢失的风险。 访问 Ventoy 官网(https://www.ventoy.net/)进行下载。 将官网下载好的文件压缩包解压后,点击Ventoy2Disk.exe,选择对应U盘安装即可。 制作好后会出现一个默认以 Ventoy 命名的分区,将下载的系统镜像文件(.iso)或其他 WinPE 工具生成的 ISO 镜像文件拷贝到这个分区就可以用了。 启动界面: 注意:列表中显示什么取决于你往 U 盘里放了什么镜像。 如何做一些额外配置? 通常无需任何配置即可使用,如果想做一些定制化工作,比如修改主题、自动安装系统等。运行 VentoyPlugson.exe,选中刚才制作的 U 盘点“启动”。 会自动打开一个网页: 包含了各种高级配置功能。 有两个配置值得一提:已经默认选择了绕过Windows 11硬件检查(可以绕过 Windows 11 的 TPM 限制)和绕过Windows 11 在线账户需求(可以绕过 Windows 11 有网络时才可以安装的限制...
Ventoy 启动盘提示 (0x1A) Security Violation 的解决方法
如果你的主板开启了安全启动的话,通过 Ventoy 启动电脑时,你不会看到想要的 Ventoy 选择系统镜像的界面,而是会看到屏幕显示如下提示: ERROR Verification failed: (0x1A) Security Violation 这时还需要一步操作,而这一步就非常麻烦。 这里有三种方法,操作时就看哪一种可用就用哪一种,建议用第2种。 方法1:临时关闭安全启动模式 进入 BIOS,找到安全启动模式,关闭它即可。 等维护完系统再打开它。 方法2:Enroll key from disk 我们先按 ok,然后任意键进入 MOK management: 1、光标选择第二项 enroll key from disk 2、找到 UTOYEFI 这个选项回车 3、找到 ENROLL_THIS_KEY_IN_MOKMANAGER.cer回车 4、选择 continue 回车 5、选择 yes 回车 6、选择 reboot 回车,重启 此时我们可以重新启动电脑就能进入正常的镜像列表了。 方法3:Eeroll hash from disk 我们先按 ok,然后任意键进入 MOK management: 1、光标选择第三项 enroll hash from disk 2、找到 UTOYEFI 这个选项回车 第 1 步先找到第 3 个菜单,也就是最后一个菜单进入,之后再使用方法一的第二步的方法,找到 UTOYEFI 选项回车进入,然后我们再次拍摄屏幕,找到 EFI/ 确定他的位置后回车,然后我们再次拍摄并识别屏幕找到 BOOT/ 一般在菜单的第 2 项也就是最后一项下光标一下就能找到,回车进入之后,我们再次拍摄并识别屏幕找到 grubx64.efi 建议从菜单的最后一项倒着往上数来定位,因为第 1 项的字比较少,可能无法识别,找到这个选项,回车进入后,夏光标找到 continue,回车在下光标找到。Yes,回车,然后重新启动也能正常进入了,每个电脑上仅需操作一次,以后就不需要再操作了。 3、找到 UEFI/ 回车 4、选择 boot/ 回车 5、找到 grubx64.efi 回车 6、选择 continue 回车 7、选择 yes 回车 8、选择 reboot 回车 此时我们可以重新启动电脑就能进入正常的镜像列表...
What is semihosting
Semihosting is a mechanism that enables code running on an ARM target to communicate and use the Input/Output facilities on a host computer that is running a debugger. Examples of these facilities include keyboard input, screen output, and disk I/O. For example, you can use this mechanism to enable functions in the C library, such as printf() and scanf(), to use the screen and keyboard of the host instead of having a screen and keyboard on the target system. This is useful because development hardware often does not have all the input and output facilities of the final system. Semihosting enables the host computer to provide these facilities. Semihosting is implemented by a set of defined software instructions, for example, SVCs, that generate exceptions from program control. The application invokes the appropriate semihosting call and the debug agent then handles the exception. The debug agent provides the required communication with the host. The semihosting interface is common across all debug agents provided by ARM. Semihosted operations work when you are debugging applications on your development platform, as shown in the following figure: In many cases, semihosting is invoked by code within library functions. The application can also invoke the semihosting operation directly. Note ARM processors prior to ARMv7 use the SVC instructions, formerly known as SWI instructions, to make semihosting calls. However, if you are compiling for an ARMv6-M or ARMv7-M, for example a Cortex-M1 or Cortex-M3 processor, semihosting is implemented using the BKPT instruction. Concepts The semihosting interface Can I change the semihosting operation numbers?. Using ARM C and C++ Libraries and Floating Point Support: The ARM C and C++ libraries. Reference Semihosting operations Debug agent interaction SVC...
FTP 协议讲解
FTP 概述 文件传输协议(FTP)作为网络共享文件的传输协议,在网络应用软件中具有广泛的应用。FTP的目标是提高文件的共享性和可靠高效地传送数据。 在传输文件时,FTP 客户端程序先与服务器建立连接,然后向服务器发送命令。服务器收到命令后给予响应,并执行命令。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,就可以相互传输数据。本文主要基于 LINUX 平台,对 FTP 客户端的实现原理进行详尽的解释并阐述如何使用 C 语言编写一个简单的 FTP 客户端。 FTP 协议 相比其他协议,如 HTTP 协议,FTP 协议要复杂一些。与一般的 C/S 应用不同点在于一般的C/S 应用程序一般只会建立一个 Socket 连接,这个连接同时处理服务器端和客户端的连接命令和数据传输。而FTP协议中将命令与数据分开传送的方法提高了效率。 FTP 使用 2 个端口,一个数据端口和一个命令端口(也叫做控制端口)。这两个端口一般是21 (命令端口)和 20 (数据端口)。控制 Socket 用来传送命令,数据 Socket 是用于传送数据。每一个 FTP 命令发送之后,FTP 服务器都会返回一个字符串,其中包括一个响应代码和一些说明信息。其中的返回码主要是用于判断命令是否被成功执行了。 命令端口 一般来说,客户端有一个 Socket 用来连接 FTP 服务器的相关端口,它负责 FTP 命令的发送和接收返回的响应信息。一些操作如“登录”、“改变目录”、“删除文件”,依靠这个连接发送命令就可完成。 数据端口 对于有数据传输的操作,主要是显示目录列表,上传、下载文件,我们需要依靠另一个 Socket来完成。 如果使用被动模式,通常服务器端会返回一个端口号。客户端需要用另开一个 Socket 来连接这个端口,然后我们可根据操作来发送命令,数据会通过新开的一个端口传输。 如果使用主动模式,通常客户端会发送一个端口号给服务器端,并在这个端口监听。服务器需要连接到客户端开启的这个数据端口,并进行数据的传输。 下面对 FTP 的主动模式和被动模式做一个简单的介绍。 主动模式 (PORT) 主动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P,即 21 端口,发起连接,同时开放N +1 端口监听,并向服务器发出 “port N+1” 命令,由服务器从它自己的数据端口 (20) 主动连接到客户端指定的数据端口 (N+1)。 FTP 的客户端只是告诉服务器自己的端口号,让服务器来连接客户端指定的端口。对于客户端的防火墙来说,这是从外部到内部的连接,可能会被阻塞。 被动模式 (PASV) 为了解决服务器发起到客户的连接问题,有了另一种 FTP 连接方式,即被动方式。命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。 被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。 第一个端口连接服务器的 21 端口,提交 PASV 命令。然后,服务器会开启一个任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。 它返回了 227 开头的信息,在括号中有以逗号隔开的六个数字,前四个指服务器的地址,最后两个,将倒数第二个乘 256 再加上最后一个数字,这就是 FTP 服务器开放的用来进行数据传输的端口。如得到 227 entering passive mode (h1,h2,h3,h4,p1,p2),那么端口号是 p1*256+p2,ip 地址为h1.h2.h3.h4。这意味着在服务器上有一个端口被开放。客户端收到命令取得端口号之后, 会通过 N+1 号端口连接服务器的端口 P,然后在两个端口之间进行数据传输。 主要用到的 FTP 命令 FTP 每个命令都有 3 到 4 个字母组成,命令后面跟参数,用空格分开。每个命令都以 "\r\n"结束。 要下载或上传一个文件,首先要登入 FTP 服务器,然后发送命令,最后退出。这个过程中,主要用到的命令有 USER、PASS、SIZE、REST、CWD、RETR、PASV、PORT、QUIT。 USER: 指定用户名。通常是控制连接后第一个发出的命令。“USER gaoleyi\r\n”: 用户名为gaoleyi 登录。 PASS: 指定用户密码。该命令紧跟 USER 命令后。“PASS gaoleyi\r\n”:密码为 gaoleyi。 SIZE: 从服务器上返回指定文件的大小。“SIZE file.txt\r\n”:如果 file.txt 文件存在,则返回该文件的大小。 CWD: 改变工作目录。如:“CWD dirname\r\n”。 PASV: 让服务器在数据端口监听,进入被动模式。如:“PASV\r\n”。 PORT: 告诉 FTP 服务器客户端监听的端口号,让 FTP 服务器采用主动模式连接客户端。如:“PORT h1,h2,h3,h4,p1,p2”。 RETR: 下载文件。“RETR file.txt \r\n”:下载文件 file.txt。 STOR: 上传文件。“STOR file.txt\r\n”:上传文件 file.txt。 REST: 该命令并不传送文件,而是略过指定点后的数据。此命令后应该跟其它要求文件传输的 FTP 命令。“REST 100\r\n”:重新指定文件传送的偏移量为 100 字节。 QUIT: 关闭与服务器的连接。 FTP 响应码 客户端发送 FTP 命令后,服务器返回响应码。 响应码用三位数字编码表示: 第一个数字给出了命令状态的一般性指示,比如响应成功、失败或不完整。 第二个数字是响应类型的分类,如 2 代表跟连接有关的响应,3 代表用户认证。 第三个数字提供了更加详细的信息。 第一个数字的含义如下: 1 表示服务器正确接收信息,还未处理。 2 表示服务器已经正确处理信息。 3 表示服务器正确接收信息,正在处理。 4 表示信息暂时错误。 5 表示信息永久错误。 第二个数字的含义如下: 0 表示语法。 1 表示系统状态和信息。 2 表示连接状态。 3 表示与用户认证有关的信息。 4 表示未定义。 5 表示与文件系统有关的信息。 Socket 编程的几个重要步骤 Socket 客户端编程主要步骤如下: socket() 创建一个 Socket connect() 与服务器连接 write() 和 read() 进行会话 close() 关闭 Socket Socket 服务器端编程主要步骤如下: socket() 创建一个 Socket bind() listen() 监听 accept() 接收连接的请求 write() 和 read() 进行会话 close() 关闭 Socket 实现 FTP 客户端上传下载功能 下面让我们通过一个例子来对 FTP 客户端有一个深入的了解。本文实现的 FTP 客户端有下列功能: 客户端和 FTP 服务器建立 Socket 连接。 向服务器发送 USER、PASS 命令登录 FTP 服务器。 使用 PASV 命令得到服务器监听的端口号,建立数据连接。 使用 RETR/STOR 命令下载/上传文件。 在下载完毕后断开数据连接并发送 QUIT 命令退出。 本例中使用的 FTP 服务器为 filezilla。在整个交互的过程中,控制连接始终处于连接的状态,数据连接在每传输一个文件时先打开,后关闭。 客户端和 FTP 服务器建立 Socket 连接 当客户端与服务器建立连接后,服务器会返回 220 的响应码和一些欢迎信息。 图 1. 客户端连接到服务器端 清单 1. 客户端连接到 FTP 服务器,接收欢迎信息 SOCKET control_sock; struct hostent *hp; struct sockaddr_in server; memset(&server, 0, sizeof(struct sockaddr_in)); /* 初始化socket */ control_sock = socket(AF_INET, SOCK_STREAM, 0); hp = gethostbyname(server_name); memcpy(&server.sin_addr, hp->h_addr, hp->h_length); server.sin_family = AF_INET; server.sin_port = htons(port); /* 连接到服务器端 */ connect(control_sock,(struct sockaddr *)&server, sizeof(server)); /* 客户端接收服务器端的一些欢迎信息 */ read(control_sock, read_buf, read_len); 客户端登录 FTP 服务器 当客户端发送用户名和密码,服务器验证通过后,会返回 230 的响应码。然后客户端就可以向服务器端发送命令了。 图 2. 客户端登录 FTP 服务器 清单 2. 客户端发送用户名和密码,登入 FTP 服务器 /* 命令 ”USER username\r\n” */ sprintf(send_buf,"USER %s\r\n",username); /*客户端发送用户名到服务器端 */ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息,正常为 ”331 User name okay, need password.” */ read(control_sock, read_buf, read_len); /* 命令 ”PASS password\r\n” */ sprintf(send_buf,"PASS %s\r\n",password); /* 客户端发送密码到服务器端 */ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息,正常为 ”230 User logged in, proceed.” */ read(control_sock, read_buf, read_len); 客户端让 FTP 服务器进入被动模式 当客户端在下载/上传文件前,要先发送命令让服务器进入被动模式。服务器会打开数据端口并监听。并返回响应码 227 和数据连接的端口号。 图 3. 客户端让服务器进入被动模式 清单 3. 让服务器进入被动模式,在数据端口监听 /* 命令 ”PASV\r\n” */ sprintf(send_buf,"PASV\r\n"); /* 客户端告诉服务器用被动模式 */ write(control_sock, send_buf, strlen(send_buf)); /*客户端接收服务器的响应码和新开的端口号, * 正常为 ”227 Entering passive mode (<h1,h2,h3,h4,p1,p2>)” */ read(control_sock, read_buf, read_len); 客户端通过被动模式下载文件 当客户端发送命令下载文件。服务器会返回响应码 150,并向数据连接发送文件内容。 图 4. 客户端从FTP服务器端下载文件 清单 4. 客户端连接到 FTP 服务器的数据端口并下载文件 /* 连接服务器新开的数据端口 */ connect(data_sock,(struct sockaddr *)&server, sizeof(server)); /* 命令 ”CWD dirname\r\n” */ sprintf(send_buf,"CWD %s\r\n", dirname); /* 客户端发送命令改变工作目录 */ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息,正常为 ”250 Command okay.” */ read(control_sock, read_buf, read_len); /* 命令 ”SIZE filename\r\n” */ sprintf(send_buf,"SIZE %s\r\n",filename); /* 客户端发送命令从服务器端得到下载文件的大小 */ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息,正常为 ”213 <size>” */ read(control_sock, read_buf, read_len); /* 命令 ”RETR filename\r\n” */ sprintf(send_buf,"RETR %s\r\n",filename); /* 客户端发送命令从服务器端下载文件 */ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息,正常为 ”150 Opening data connection.” */ read(control_sock, read_buf, read_len); /* 客户端创建文件 */ file_handle = open(disk_name, CRFLAGS, RWXALL); for( ; ; ) { ... ... /* 客户端通过数据连接 从服务器接收文件内容 */ read(data_sock, read_buf, read_len); /* 客户端写文件 */ write(file_handle, read_buf, read_len); ... ... } /* 客户端关闭文件 */ rc = close(file_handle); 客户端退出服务器 当客户端下载完毕后,发送命令退出服务器,并关闭连接。服务器会返回响应码 200。 图 5. 客户端从 FTP 服务器退出 清单 5. 客户端关闭数据连接,退出 FTP 服务器并关闭控制连接 /* 客户端关闭数据连接 */ close(data_sock); /* 客户端接收服务器的响应码和信息,正常为 ”226 Transfer complete.” */ read(control_sock, read_buf, read_len); /* 命令 ”QUIT\r\n” */ sprintf(send_buf,"QUIT\r\n"); /* 客户端将断开与服务器端的连接 */ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码,正常为 ”200 Closes connection.” */ read(control_sock, read_buf, read_len); /* 客户端关闭控制连接 */ close(control_sock); 至此,下载文件已经完成。需要注意的是发送 FTP 命令的时候,在命令后要紧跟 “\r\n”,否则服务器不会返回信息。回车换行符号 “\r\n” 是 FTP 命令的结尾符号,当服务器接收到这个符号时,认为客户端发送的命令已经结束,开始处理。否则会继续等待。 让我们来看一下 FTP 服务器这一端的响应情况: 清单 6. 客户端下载文件时,FTP 服务器的响应输出 (not logged in) (127.0.0.1)> Connected, sending welcome message... (not logged in) (127.0.0.1)> 220-FileZilla Server version 0.9.36 beta (not logged in) (127.0.0.1)> 220 hello gaoleyi (not logged in) (127.0.0.1)> USER gaoleyi (not logged in) (127.0.0.1)> 331 Password required for gaoleyi (not logged in) (127.0.0.1)> PASS ********* gaoleyi (127.0.0.1)> 230 Logged on gaoleyi (127.0.0.1)> PWD gaoleyi (127.0.0.1)> 257 "/" is current directory. gaoleyi (127.0.0.1)> SIZE file.txt gaoleyi (127.0.0.1)> 213 4096 gaoleyi (127.0.0.1)> PASV gaoleyi (127.0.0.1)> 227 Entering Passive Mode (127,0,0,1,13,67) gaoleyi (127.0.0.1)> RETR file.txt gaoleyi (127.0.0.1)> 150 Connection accepted gaoleyi (127.0.0.1)> 226 Transfer OK gaoleyi (127.0.0.1)> QUIT gaoleyi (127.0.0.1)> 221 Goodbye 首先,服务器准备就绪后返回 220。客户端接收到服务器端返回的响应码后,相继发送“USER username” 和 “PASS password” 命令登录。随后,服务器返回的响应码为 230 开头,说明客户端已经登入了。这时,客户端发送 PASV 命令让服务器进入被动模式。服务器返回如 “227 Entering Passive Mode (127,0,0,1,13,67)”,客户端从中得到端口号,然后连接到服务器的数据端口。接下来,客户端发送下载命令,服务器会返回响应码 150,并从数据端口发送数据。最后,服务器返回 “226 transfer complete”,表明数据传输完成。 需要注意的是,客户端不要一次发送多条命令,例如我们要打开一个目录并且显示这个目录,我们得发送 CWD dirname,PASV,LIST。在发送完 CWD dirname 之后等待响应代码,然后再发送后面一条。当 PASV 返回之后,我们打开另一个 Socket 连接到相关端口上。然后发送 LIST,返回 125 之后在开始接收数据,最后返回 226 表明完成。 在传输多个文件的过程中,需要注意的是每次新的传输都必须重新使用 PASV 获取新的端口号,接收完数据后应该关闭该数据连接,这样服务器才会返回一个 2XX 成功的响应。然后客户端可以继续下一个文件的传输。 上传文件与下载文件相比,登入验证和切换被动模式都如出一辙,只需要改变发送到服务器端的命令,并通过数据连接发送文件内容。 客户端通过被动模式向服务器上传文件 当客户端发送命令上传文件,服务器会从数据连接接收文件。 图 6. 客户端连接到 FTP 服务器的数据端口并上传文件 客户端通过主动模式向服务器上传文件 到目前为止,本文介绍的都是客户端用被动模式进行文件的上传和下载。下面将介绍客户端用主动模式下载文件。 图 7. 用主动模式从 FTP 服务器下载文件 清单 7. 用主动模式从 FTP 服务器下载文件的示例 C 程序 ... ... SOCKET data_sock; data_sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in name; name.sin_family = AF_INET; name.sin_addr.s_addr = htons(INADDR_ANY); server_port = p1*256+p2; length = sizeof(name); name.sin_port = htons(server_port); bind(server_sock, (struct sockaddr *)&name, length); struct sockaddr_in client_name; length = sizeof(client_name); /* 客户端开始监听端口p1*256+p2 */ listen(server_sock, 64); /* 命令 ”PORT \r\n” */ sprintf(send_buf,"PORT 1287,0,0,1,%d,%d\r\n", p1, p2); write(control_sock, send_buf,strlen(send_buf)); /* 客户端接收服务器的响应码和信息,正常为 ”200 Port command successful” */ read(control_sock, read_buf, read_len); sprintf(send_buf,"RETR filename.txt\r\n"); write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息,正常为 ”150 Opening data channel for file transfer.” */ read(control_sock, read_buf, read_len); /* ftp客户端接受服务器端的连接请求 */ data_sock = accept(server_sock,(struct sockaddr *)&client_name, &length); ... ... file_handle = open(disk_name, ROFLAGS, RWXALL); for( ; ; ) { ... ... read(data_sock, read_buf, read_len); write(file_handle, read_buf, read_len); ... ... } close(file_handle); 客户端通过 PORT 命令告诉服务器连接自己的 p1*256+p2 端口。随后在这个端口进行监听,等待 FTP 服务器连接上来, 再通过这个数据端口来传输文件。PORT 方式在传送数据时,FTP 客户端其实就相当于一个服务器端,由 FTP 服务器主动连接自己。 断点续传 由于网络不稳定,在传输文件的过程中,可能会发生连接断开的情况,这时候需要客户端支持断点续传的功能,下次能够从上次终止的地方开始接着传送。需要使用命令 REST。如果在断开连接前,一个文件已经传输了 512 个字节。则断点续传开始的位置为 512,服务器会跳过传输文件的前 512 字节。 清单 8. 从 FTP 服务器断点续传下载文件 ... ... /* 命令 ”REST offset\r\n” */ sprintf(send_buf,"REST %ld\r\n", offset); /* 客户端发送命令指定下载文件的偏移量 */ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息, *正常为 ”350 Restarting at <position>. Send STORE or RETRIEVE to initiate transfer.” */ read(control_sock, read_buf, read_len); ... ... /* 命令 ”RETR filename\r\n” */ sprintf(send_buf,"RETR %s\r\n",filename); /* 客户端发送命令从服务器端下载文件, 并且跳过该文件的前offset字节*/ write(control_sock, send_buf, strlen(send_buf)); /* 客户端接收服务器的响应码和信息,* *正常为 ”150 Connection accepted, restarting at offset <position>” */ read(control_sock, read_buf, read_len); ... ... file_handle = open(disk_name, CRFLAGS, RWXALL); /* 指向文件写入的初始位置 */ lseek(file_handle, offset, SEEK_SET); ... ... 结束语 本文从应用实现的角度,介绍了 FTP 协议。并用详尽的例子分析了如何用主动模式和被动模式实现 FTP 客户端上传下载文件,如何进行断点续传。通过本文可以让读者对 FTP 客户端的原理有一个深入的了...
LwIP 处理链路状态改变
文/告别年代 Email:byeyear@hotmail.com 基本流程 A. link up -> link down: 关闭 MAC 和 DMA; 调用 netif_set_link_down B. link down -> link up: 打开 MAC 和 DMA; 调用 netif_set_link_up C. 注意:当使用 RAW API 时,所有使用 RAW API 的代码(包括处理链路状态的代码)必须运行于同一线程环境。 代码分析 有四个函数和两个标志位和链路状态改变有关: A. netif_set_up 该函数设置 NETIF_FLAG_UP 标记,并在链路已 up 的情况下发送 arp 探测。 如果你的网络使用静态 IP,那么在 lwip 初始化时调用该函数; 如果你的网络使用 DHCP,那么 DHCP 成功后会帮你调用 netif_set_up。 B. netif_set_down 除非你需要关闭网络,否则一般不需要主动调用该函数。 C. netif_set_link_up 该函数设置 NETIF_FLAG_LINK_UP 标记,启动 DHCP 和 AutoIP,并在 NETIF_FLAG_UP 标记有效的情况下发送 arp 探测。 当链路从 down 变化为 up 时调用该函数。 注意:初始化时如果链路有效,low_level_init 将直接设置 NETIF_FLAG_LINK_UP,而不调用 netif_set_link_up 函数,避免在 lwip 没有完全初始化好时启动 DHCP。 D. netif_set_link_down 在链路从 up 变为 down 时调用该函数。 E. NETIF_FLAG_UP和NETIF_FLAG_LINK_UP 前者表示 lwip 协议栈已经就绪(已得到合法 IP 地址,且协议栈已准备好收发数据包);后者表示链路层有效。 或者说,一个是软件(协议栈)就绪标志,一个是硬件(链路层)就绪标志。 F. 参考 lwip 代码中的 netif.h 文件对这两个标记的详细说明。 网上很多 port 代码不管是否使用 DHCP 都在 lwip 初始化时设置 NETIF_FLAG_UP,这是不正确的。 NETIF_FLAG_UP 必须在获得有效 IP 地址后才能置位,以保证顺利发送 ARP 探测。 情景分析 A. 静态 IP,初始化时链路有效 这是最简情况。驱动层会看到 Link 有效,并直接设置 NETIF_FLAG_LINK_UP。 随后 lwip 初始化过程中调用 netif_set_up,该函数设置 NETIF_FLAG_UP,并发送 arp 探测。 B. 静态 IP,初始化时链路无效 驱动层不会设置 NETIF_FLAG_LINK_UP。 lwip 初始化过程会调用 netif_set_up,该函数看到没有 link,除了设置标记外不会做任何事。 当链路变为有效后,驱动层调用 netif_set_link_up,发送 arp 探测。 C. 动态 IP,初始化时链路有效 驱动层直接设置 NETIF_FLAG_LINK_UP。 lwip 初始化过程中调用 dhcp_start,启动 DHCP 过程。 但 lwip 初始化过程不会调用 netif_set_up,因为还没有获得有效IP地址。 获得有效 IP 后,lwip 会帮我们调用 netif_set_up,发送 arp 探测。 D. 动态 IP,初始化时链路无效 驱动层不会设置 NETIF_FLAG_LINK_UP。 lwip 初始化过程中仍然会调用 dhcp_start。对 dhcp_start 的调用是必须的,因为 dhcp 过程需要的资源将在该函数中分配。 等到链路有效后驱动层调用 netif_set_link_up,该函数会启动 DHCP 过程。 获得有效 IP 后,lwip 会帮我们调用 netif_set_up,发送 arp 探测。 E. 静态 IP,链路断开后重建 链路断开时 netif_set_link_down,重建后 netif_set_link_up 发送 arp 探测。 NETIF_FLAG_UP 在设置后就一直有效。 F. 动态 IP,链路断开后重建 链路重建后 netif_set_link_up 重新启动 DHCP 过程,该过程会清除 NETIF_FLAG_UP 标记。 DHCP 完成后 lwip 自动调用 netif_set_up 重新置位该标记并发起 arp 探测。 总结 使用静态 IP low_level_init 根据当前链路状态设置或不设置 NETIF_FLAG_LINK_UP; lwip 初始化时调用 netif_set_up; 链路状态改变时调用 netif_set_link_up/netif_set_link_down。 使用动态 IP low_level_init 根据当前链路状态设置或不设置 NETIF_FLAG_LINK_UP; lwip 初始化时调用 dhcp_start; 链路状态改变时调用 netif_set_link_up/netif_set_link_down; 不要在使用动态IP时直接调用 netif_set_up,而是由lwip协议栈代码在成功获得 IP 后为你调用这个函...
MAC 与 PHY 组成原理的简单分析
1. general 下图是网口结构简图。网口由 CPU、MAC 和 PHY 三部分组成。DMA 控制器通常属于 CPU 的一部分,用虚线放在这里是为了表示 DMA 控制器可能会参与到网口数据传输中。 对于上述的三部分,并不一定都是独立的芯片,根据组合形式,可分为下列几种类型: 方案一:CPU 集成 MAC 与 PHY; 方案二:CPU 集成 MAC,PHY 采用独立芯片; 方案三:CPU 不集成 MAC 与 PHY,MAC 与 PHY 采用集成芯片; 本例中选用方案二做进一步说明,因为 CPU 总线接口很常见,通常都会做成可以像访问内存一样去访问,没必要拿出来说,而 MAC 与 PHY 之间的 MII 接口则需要多做些说明。 下图是采用方案二的网口结构图。虚框表示 CPU,MAC 集成在 CPU 中。PHY 芯片通过 MII 接口与 CPU 上的 MAC 连接。 在软件上对网口的操作通常分为下面几步: 1)为数据收发分配内存; 2)初始化 MAC 寄存器; 3)初始化 PHY 寄存器(通过 MIIM); 4)启动收发; 2. MII MII 接口是 MAC 与 PHY 连接的标准接口。因为各厂家采用了同样的接口,用户可以根据所需的性能、价格,采用不同型号,甚至不同公司的 PHY 芯片。 需要发送的数据通过 MII 接口中的收发两组总线实现。而对 PHY 芯片寄存器的配置信息,则通过 MII 总的一组串口总线实现,即 MIIM(MII Management)。 下表列出了 MII 总线中主要的一些引脚 PIN Name Direction Description TXD[0:3] MAC to PHY Transmit Data TXEN MAC to PHY Transmit Enable TXCLK MAC to PHY Transmit Clock RXD[0:3] PHY to MAC Receive Data RXEN PHY to MAC Receive Enable RXCLK PHY to MAC Receive Clock MDC MAC to PHY Management Data Clock MDIO Bidirection Management Data I/O MIIM 只有两个线,时钟信号 MDC 与数据线 MDIO。读写命令均由 MAC 发起,PHY 不能通过 MIIM 主动向 MAC 发送信息。由于 MIIM 只能有 MAC 发起,我们可以操作的也就只有 MAC 上的寄存器。 3. DMA 收发数据总是间费时费力的事,尤其对于网络设备来说更是如此。CPU 做这些事情显然不合适。既然是数据搬移,最简单的办法当然是让 DMA 来做。毕竟专业的才是最好的。 这样 CPU 要做的事情就简单了。只需要告诉 DMA 起始地址与长度,剩下的事情就会自动完成。 通常在 MAC 中会有一组寄存器专门用户记录数据地址,tbase 与 rbase,CPU 按 MAC 要的格式把数据放好后,启动 MAC 的数据发送就可以了。启动过程常会用到寄存器 tstate。 4. MAC CPU 上有两组寄存器用与 MAC。一组用户数据的收发,对应上面的 DMA;一组用户 MIIM,用户对 PHY 进行配置。 两组寄存器由于都在 CPU 上,配置方式与其他 CPU 上寄存器一样,直接读写即可。 数据的转发通过 DMA 完成。 5. PHY 该芯片是一个 10M/100M Ethernet 网口芯片 PHY 芯片有一组寄存器用户保存配置,并更新状态。CPU 不能直接访问这组寄存器,只能通过 MAC 上的 MIIM 寄存器组实现间接访问。 同时 PHY 芯片负责完成 MII 总线的数据与 Media Interface 上数据的转发。该转发根据寄存器配置自动完成,不需要外接干预。 作者:fireaxe_hq@hotmail.c...
Qt 源码编译 configure 参数列表
configure meta -help,-h ............显示此帮助屏幕 -verbose,-v .........在配置期间输出详细消息 -continue............尽管有错误仍然继续配置 -redo ................用以前使用的选项重新配置。其他选项可能会通过,但不会保存以供-redo稍后使用。 -recheck .............放弃缓存的负配置测试结果。安装缺失的依赖关系后使用它。 -recheck-all .........放弃所有缓存的配置测试结果。 -feature- <特征> ...启用<特征> -no-feature- <feature>禁用<feature> [none] -list-features .......列出可用功能。请注意一些功能也有专用的命令行选项。 -list-libraries ......列出可能的外部依赖关系。 Build options -opensource ..........构建Qt的开源版本 -commercial ..........构建Qt的商业版 -confirm-license .....自动确认许可证 -release.............关闭调试版本的Qt [yes] -debug ...............打开调试生成Qt [no] -debug-and-release ...构建两个版本的Qt,包含和不包含打开调试[是](仅适用于Apple和Windows) -optimize-debug ......在调试版本中启用调试友好的优化[自动](MSVC不支持) -optimize-size .......优化发布版本的大小而不是速度[no] -optimized-tools .....甚至在调试版本中构建优化的主机工具[no] -force-debug-info ....为发布版本创建符号文件[no] -separate-debug-info。分离调试信息以分离文件[no] -strip ...............释放不需要的符号的二进制文件[是] -force-asserts .......即使在发布版本中启用Q_ASSERT [no] -developer-build .....编译并链接Qt以开发Qt本身(用于自动测试的出口,额外检查等)[no] -shared..............建立共享的Qt库[是](不适用于UIKit) -static ..............构建静态Qt库[no](对于UIKit是) -framework ...........构建Qt框架包[是](仅限Apple) -platform <target> ...选择主机mkspec [检测到] -xplatform <target> ..交叉编译时选择target mkspec [PLATFORM] -device <name> .......交叉编译设备<name> -device-option <key = value> ...为设备mkspec添加选项 -appstore-compliant ..禁用平台应用商店中不允许使用的代码。默认情况下,默认情况下,默认情况下,平台需要通过默认应用商店进行分发,特别是Android,iOS,tvOS,watchOS和Universal Windows Platform。 [汽车] -qtnamespace <name> ..将所有Qt库代码封装在'namespace <name> {...}'中。 -qtlibinfix <infix>将所有libQt5 * .so重命名为libQt5 * <infix> .so。 -testcocoon ..........带有TestCocoon代码覆盖工具的仪器[no] -gcov ................具有GCov代码覆盖工具的仪器[no] -sanitize {address | thread | memory | undefined}仪器与指定的编译器消毒剂。 -c ++ std <edition> ....选择C ++标准<edition> [c ++ 1z / c ++ 14 / c ++ 11](不支持MSVC) -sse2 ................使用SSE2指令[自动] -sse3 / -ssse3 / -sse4.1 / -sse4.2 / -avx / -avx2 / -avx512启用特定的x86指令[auto]启用的仍然受到运行时检测。 -mips_dsp / -mips_dspr2使用MIPS DSP / rev2指令[auto] -qreal <type> ........ typedef qreal到指定的类型。 [双]注意:这会影响二进制兼容性。 -R <string> ..........为Qt添加一个显式的运行时库路径库。支持相对于LIBDIR的路径。 -rpath ...............使用库链接Qt库和可执行文件将路径安装为运行时库路径。如同-R LIBDIR。在苹果平台上,禁用这意味着使用绝对安装名称(基于 LIBDIR)动态库和框架。 [汽车]减少输出......减少输出符号的数量[自动] -reuce-relocations ..减少重定位量[auto](仅适用于Unix) -plugin-manifests ....将清单嵌入插件[no](仅限Windows) -static-runtime ......使用-static,使用静态运行时[no](仅限Windows) -pch .................使用预编译头文件[auto] -ltcg ................使用链接时间码生成[no] -use-gold-linker .....使用GNU gold链接器[auto] -incredibuild-xge ....使用IncrediBuild XGE [no](仅限Windows) -ccache ..............使用ccache编译器缓存[no](仅适用于Unix) -make-tool <tool> ....使用<tool>构建qmake [nmake](仅适用于Windows) -mp ..................使用多个处理器进行编译(仅限MSVC) -warnings-are-errors。将警告视为错误[no; yes如果-developer-build] -silent ..............减少构建输出以便发出警告和错误可以更容易地看到 Build environment -sysroot <dir> ....... 将<dir>设置为目标sysroot -gcc-sysroot ......... 使用-sysroot,将编译器通过--sysroot [yes] -pkg-config ..........使用pkg-config [auto](仅适用于Unix) -D <string> ..........传递附加的预处理器定义 -I <string> ..........传递额外的包含路径 -L <string> ..........传递额外的库路径 -F <string> ..........传递额外的框架路径(仅适用于Apple) -sdk <sdk> ...........使用Apple提供的SDK <sdk>构建Qt。争论应该是以下列出的可用SDK之一'xcodebuild -showsdks'。请注意,该参数仅适用于Qt库和使用目标mkspec构建的应用程序 - 不是主机工具,如qmake,moc,rcc等。 -android-sdk path ....设置Android SDK根路径[$ ANDROID_SDK_ROOT] -android-ndk路径....设置Android NDK根路径[$ ANDROID_NDK_ROOT] -android-ndk-platform设置Android平台 -android-ndk-host ....设置Android NDK主机(linux-x86,linux-x86_64等)[$ ANDROID_NDK_HOST] -android-arch ........设置Android体系结构(armeabi,armeabi-v7a,arm64-v8a,x86,x86_64,mips,mips64) -android-toolchain-version ...设置Android工具链版本 -android-style-assets自动从设备中提取样式资产运行。此选项使Android样式表现良好正确的,但也使得Android平台插件与LGPL2.1不兼容。 [是] Component selection -skip <repo> .........从构建中排除整个存储库。 -make <part> .........将<part>添加到要构建的零件列表中。指定此选项将首先清除默认列表。[库和例子,如果不是交叉构建也是工具,还测试是否 - 开发人员构建] -nomake <part> .......从要构建的零件列表中排除<part>。 -compile-examples ....未设置时,只安装示例的源代码[是] -gui .................构建Qt GUI模块和依赖[yes] -widgets .............编译Qt Widgets模块和依赖[yes] -no-dbus .............不要构建Qt D-Bus模块[Android和Windows默认] -dbus-linked .........构建Qt D-Bus并链接到libdbus-1 [auto] -dbus-runtime ........构建Qt D-Bus并动态加载libdbus-1 [no] -accessibility.......启用可访问性支持[是]注意:不建议禁用可访问性。 -qml-debug ...........启用QML调试支持[yes] Qt附带一些第三方库的捆绑副本。这些被使用默认情况下,如果自动检测相应的系统库失败。 Core options -doubleconversion ....选择使用的双转换库[system / qt / no]没有暗示使用sscanf_l和snprintf_l(不精确)。 -glib ................启用Glib支持[no;在Unix上自动] -eventfd .............启用eventfd支持 -inotify .............启用inotify支持 -iconv ...............启用iconv(3)支持[posix / sun / gnu / no](仅适用于Unix) -icu .................启用ICU支持[自动] -pcre ................选择使用的libpcre2 [system / qt] -pps .................启用PPS支持[自动](仅限QNX) -zlib ................选择用过的zlib [system / qt] Logging backends -Journald ..........启用日志支持[no](仅限Unix) -syslog ............启用syslog支持[no](仅适用于Unix) -slog2 .............启用slog2支持[自动](仅限QNX) Network options -ssl .................启用SSL支持方法[自动] -no-openssl ..........不要使用OpenSSL [Apple和WinRT上的默认] -openssl-linked ......使用OpenSSL并链接到libssl [no] -openssl-runtime .....使用OpenSSL并动态加载libssl [auto] -securetransport .....使用SecureTransport [auto](仅限Apple) -sctp ................启用SCTP支持[no] -libproxy ............启用libproxy的使用[no] -system-proxies ......默认使用系统网络代理[yes] GUI, printing, widget options -cups ................启用CUPS支持[自动](仅适用于Unix) -fontconfig ..........启用Fontconfig支持[auto](仅适用于Unix) -freetype ............选择使用的FreeType [system / qt / no] -harfbuzz ............选择用过的HarfBuzz-NG [系统/ qt / no](不在Apple和Windows上自动检测) -gtk .................启用GTK平台主题支持[auto] -lgmon ...............启用lgmon支持[自动](仅限QNX) -no-opengl ...........禁用OpenGL支持 -opengl <api> ........启用OpenGL支持。支持的API:es2(在Windows上默认),桌面(在Unix上默认),动态(仅限Windows) -opengles3 ...........启用OpenGL ES 3.x支持而不是ES 2.x [自动] -angle ...............使用捆绑的ANGLE支持OpenGL ES 2.0 [自动](仅限Windows) -combined-angle-lib ..将LibEGL和LibGLESv2合并到LibANGLE(仅限Windows) -qpa <name> ..........选择默认的QPA后端(例如,xcb,cocoa,windows) -xcb-xlib .............启用Xcb-Xlib支持[auto] Platform backends -direct2d ..........启用Direct2D支持[自动](仅限Windows) -directfb ..........启用DirectFB支持[no](仅适用于Unix) -eglfs .............启用EGLFS支持[auto;没有在Android和Windows上] -gbm ...............为GBM [auto]启用后端(仅限Linux) -kms ...............启用KMS [auto]的后端(仅适用于Linux) -linuxfb ...........启用Linux Framebuffer支持[auto](仅限Linux) -mirclient .........启用Mir客户端支持[no](仅Linux) -xcb ...............选择使用的xcb- *库[system / qt / no](-qt-xcb仍然使用libxcb本身的系统版本) Input backends -evdev .............启用evdev支持[auto] -imf ...............启用IMF支持[自动](仅限QNX) -libinput ..........启用libinput支持[auto] -mtdev .............启用mtdev支持[auto] -tslib .............启用tslib支持[自动] -xinput2 ...........启用XInput2支持[自动] -xkbcommon-x11 .....选择与xcb结合使用的xkbcommon[系统/ QT / NO] -xkb-config-root <dir> ...使用-qt-xkbcommon-x11,设置默认的XKB配置根目录<dir> [检测] -xkbcommon-evdev ...启用X-less xkbcommon与libinput结合使用[汽车] Image formats -gif ...............启用对GIF的读取支持[自动] -ico ...............启用对ICO的支持[是] -libpng ............选择用过的libpng [system / qt / no] -libjpeg ...........选择使用的libjpeg [system / qt / no] Database options -sql- <driver> ........启用SQL <驱动程序>插件。支持的驱动db2 ibase mysql oci odbc psql sqlite2 sqlite tds[全自动] -sqlite ..............选择用过的sqlite3 [系统/ qt] Qt3D options -assimp ..............选择使用的assimp库[system / qt / no] -qt3d-profile-jobs ...启用作业分析[no] -qt3d-profile-gl .....启用OpenGL分析[no] -qt3d-simd ...........选择SIMD支持级别[no / sse2 / avx2] -qt3d-render .........启用Qt3D渲染方面[是] -qt3d-input ..........启用Qt3D输入方面[是] -qt3d-logic ..........启用Qt3D逻辑方面[是] -qt3d-extras .........启用Qt3D Extras方面[yes] -qt3d-animation .......启用Qt3D动画方面[是] Multimedia options -pulseaudio ..........启用PulseAudio支持[自动](仅适用于Unix) -alsa ................启用ALSA支持[自动](仅适用于Unix) -no-gstreamer ........禁用对GStreamer的支持 -gstreamer [版本]。启用GStreamer支持[自动]在没有参数的情况下,首先尝试1.0,然后再尝试0.10。 -mediaplayer-backend <名称> ...选择媒体播放器后端(仅限Windows)支持的后端:directshow(默认),wmf Webengine options -webengine-alsa ................启用ALSA支持[自动](仅限Linux) -webengine-pulseaudio ..........启用PulseAudio支持[自动](仅限Linux) -webengine-embedded-build ......启用Linux嵌入式构建[auto](仅限Linux) -webengine-icu .................使用系统ICU库[system / qt](仅限Linux) -webengine -ffmpeg ..............使用系统FFmpeg库[system / qt](仅限Linux) -webengine-opus ................使用系统Opus库[system / qt](仅限Linux) -webengine-webp ................使用系统WebP库[system / qt](仅限Linux) -webengine-pepper-plugins ......启用Pepper Flash和Widevine插件[自动] -webengine-printing-and-pdf ....启用打印和输出到PDF[汽车] -webengine-proprietary-codecs ..启用对专有编解码器的支持[no] -webengine-spellchecker ........启用对拼写检查程序的支持[是] -webengine-native-spellchecker。启用对原生拼写检查器的支持[否](仅限macOS) -webengine-webrtc ..............启用对WebRTC的支持[自动...
使用 OpenSSL 生成和签发 SSL 证书
SSL 证书是数字证书的一种,也称为服务器 SSL 证书。它遵守 SSL 协议,由受信任的数字证书颁发机构 CA 在验证服务器身份后颁发。SSL 证书具有服务器身份验证和数据传输加密功能,可以确保在客户端和服务器之间的信息传输过程中,数据不被改变,保证数据的完整性,并防止信息泄露。 为什么需要证书 设想用户 A 要欺骗用户 B,A 可以向 B 发送一份伪造是 C 发送的报文,A 用自己的私钥进行数字签名,并附上 A 自己的公钥,谎称这公钥是 C 的。B 如何知道这个公钥不是 C 的呢? 那假如说你(客户端)拿到的公钥并不是服务端给的呢,而是黑客塞给你的呢?而你却把这个假公钥当成真的,那么当你使用这个假公钥加密一些敏感信息时,黑客就可以截取你的这段信息,由于这信息是用黑客自己的公钥加密的,这样一来,黑客拿自己的私钥就能解密得到你的敏感信息。这就是问题所在。 要解决这个问题,其实只要保证“公钥”是可信的。只有服务端发给你的公钥你才能拿,而坏人给你的公钥,你要懂得识别并丢弃它。 显然,这需要有一个值得信赖的机构来将公钥与其对应的实体(人或机器)进行绑定 (binding)。这样的机构就叫做认证中心 CA (Certification Authority)。 那么数字证书究竟是什么东西?其实它就是一个文件,包含如下信息: 公钥的提供方信息 公钥 数字证书中的公钥,能够与服务端的私钥配对使用,实现数据传输过程中的加密和解密。 CA机构的签名 证书有效时间 证书采用的加密算法 证书生成过程 在自己的服务器上生成一对公钥和私钥。然后将域名、申请者、公钥(注意不是私钥,私钥是无论如何也不能泄露的)等其他信息整合在一起,生成 .csr 文件。将这个 .csr 文件发给 CA 机构,CA 机构收到申请后,会通过各种手段验证申请者的组织信息和个人信息,如无异常(组织存在,企业合法,确实是域名的拥有者),CA 就会使用散列算法对 .csr 里的明文信息先做一个 HASH,得到一个信息摘要,再用 CA 自己的私钥对这个信息摘要进行加密,生成一串密文,密文即是所说的签名。签名 + .csr 明文信息,即是证书。CA 把这个证书返回给申请人。 SSL 证书根据验证级别可以分为域名型 SSL 证书、企业型 SSL 证书、增强型 SSL 证书。按照域名数量,又可以分为单域名 SSL 证书、多域名版 SSL 证书、通配符 SSL 证书。 按照用途分为顶级证书、服务器证书和客户端证书。 顶级证书是权威证书颁发机构所持有的证书,权威机构使用顶级证书给服务器证书或客户端证书进行签名。 服务器证书是我们最常见的,访问多有 https 的网站,都会收到一个服务器证书,如果证书是有有名的权威机构颁发,则浏览器就会帮我们处理证书。如果证书是由不知名机构颁发,浏览器会跳出窗口,问我们是否信任此证书。 客户端证书常见于网银。像招商银行的客户端,可以使用电子证书。之所以存在客户端证书,原因是服务器想验证客户的合法性。这时候,信任是双向的。 一般互联网上,只需要服务器证书,信任是单向的,服务器并不对客户做验证。 自签名证书 当用于在私人网络或测试环境中时,与公共信任机构无关,我们可以自签名。OpenSSL 自签名证书是由 OpenSSL 库生成的一种数字证书,它用于加密与认证,由证书持有人本身进行签名。这样的证书一般不被第三方信任。 生成私钥 openssl genrsa -out private.pem 2048 genrsa:用于生成 RSA 密钥对的 OpenSSL 命令 -des3:使用 3-DES 对称加密算法加密密钥对,该参数需要用户在密钥生成过程中输入一个口令用于加密。今后使用该密钥时,需要输入相应的口令(服务启动时要输入)。如果不加该选项,则不对密钥进行加密。 -out:密钥保存到的文件 2048:RSA 模数位数,在一定程度上表征了密钥强度。 可以查看密钥: openssl rsa -noout -text -in private.pem 生成公钥 在生成证书过程中其实使用不到公钥文件的。 openssl rsa -in private.pem -pubout > public.pem 数据签名认证测试 这不是证书自签名过程的环节,仅用于测试。 1、私钥签名: echo -n A message for signing > data.txt openssl dgst -sha1 -sign private.pem -out sha1.sign data.txt 2、公钥验签: openssl dgst -sha1 -verify public.pem -signature sha1.sign data.txt 输出 Verified OK 说明签名验证正确。内容没有被篡改过。 自签名 虽然也可以自己扮演 CA 的角色来给证书签名,完成标准的证书申请流程。还有更简单的办法,那就是自签名。 以前生成证书的时候不需要备用名称字段(subjectAltName),现在的浏览器校验证书都需要这个字段。如果没有这个字段,即使证书的 CN(Common Name)与你的域名或 IP 是一致的,浏览器还是会提示证书无效: NET::ERR_CERT_COMMON_NAME_INVALID 所以我们需要配置扩展信息,可以与常规信息放在一起存到一个配置文件: [req] default_bits = 2048 prompt = no default_md = sha256 req_extensions = req_ext distinguished_name = dn [dn] C=CN ST=Guangdong L=Shenzhen O=MCULoop OU=R&D CN=192.168.1.100 #IP或域名 emailAddress=webmaster@localhost [req_ext] #不一定是这个名字,但要与其他地方匹配 basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [alt_names] #不一定是这个名字,但要与其他地方匹配 IP.0=192.168.1.100 IP.1=如果还有别的就这添加 DNS.0=www.mculoop.com DNS.1=如果有别的域名也可以这样添加 保存成一个文件名(任意),比如:req.conf 如果按照标准的流程,这里应该先生成申请文件: # openssl req -new -key private.pem -out cert.csr -config req.conf 不过我们自签名,就不用申请文件了,直接签名即可生成证书: # openssl req -x509 -key private.pem -days 3650 -out cert.crt -config req.conf -extensions req_ext -days 3650:从生成之时算起,证书时效为 3650 天。 -key:指定证书所使用的密钥文件 -out:生成证书文件 至此就完成自签名证书的生成了。 查看证书: openssl x509 -noout -text -in cert.crt 自此证书就可以使用了。不过由于证书不是被浏览器信任的 CA 机构签的,所以用浏览器打开网站时仍会提示: 您的连接不是私密连接 NET::ERR_CERT_AUTHORITY_INVALID 这时把证书安装到“受信任的根证书颁发机构”存储区就可以了。 自己扮演 CA 完成签名 上面的自签名证书已经可以用了,若要体验一下完整的签名过程可以再扮演 CA 的角色来给申请文件签名。 生成 CA 根证书 其实也是 CA 自签名的过程,先按上面的过程搞出 CA 根证书。 openssl req -x509 -newkey rsa:1024 -nodes -keyout ca.key -out ca.crt -days 3650 ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:GD Locality Name (eg, city) []:SZ Organization Name (eg, company) [Internet Widgits Pty Ltd]:MCULoop Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:MCULoop CA Email Address []: 命令已经包含了私钥文件的生成过程。 用 CA 签发证书 openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in cert.csr -out cert.crt -days 3650 -extfile req.conf -extensions req_ext 如果要使用签发的证书,需要把 CA 证书添加进信任机构,签发好的证书就不需要加...