0%

本地配置同网段,优先从低IP传输数据

比如,对端是192.168.12.77/16

本地配置192.168.1.7/16与192.168.12.177/16两个IP,无论是否配置到同一网卡,在通信时,本地只会从192.168.1.7/16发包

在不做任何特殊网络转发配置前提下,必然ping不通对端

https://docs.ceph.com/en/latest/rbd/rbd-openstack/

集成

集成到内核模块

rbd已被引入到Linux内核,在内核视角看,RBD镜像是由远程分布式存储提供的一块线性块设备,实现细节都被内核模块 rbd.ko 隐藏

RBD 镜像的数据最终就是其所在池里的一批对象

内核挂载过程:

  1. 获取rbd镜像列表

    1
    rbd list
  2. 使用 rbd 将镜像名称映射到内核模块。

    指定镜像名称、池名称和用户名。如果 RBD 内核模块尚未加载,rbd 将代您加载。

    1
    sudo rbd device map {pool-name}/{image-name} --id {user-name}

    如果您使用 cephx 身份验证,则还必须指定一个密钥。密钥可能来自密钥环或包含密钥的文件。

    1
    2
    sudo rbd device map rbd/myimage --id admin --keyring /path/to/keyring
    sudo rbd device map rbd/myimage --id admin --keyfile /path/to/file
    • 查看使用 rbd 映射到内核模块的块设备映像

      1
      rbd device list
    • 解除映射

      1
      sudo rbd device unmap /dev/rbd/{poolname}/{imagename}
  3. 内核模块 rbd.ko 通过librados连接到集群,把指定的 pool/image 打开

  4. 模块为该image申请一个 rbd_device 结构体的示例,并注册一个块设备

    • 对上层来说,向本地的硬盘一样,提供 make_request_fn 入口
    • 对下层来说,每个BIO都被切分成对象粒度的RADOS请求,写入到Ceph集群

内核只缓存页数据,元数据有用户态的rbd或librbd维护,内核只在需要时通过 rbd_header 对象读取最小必要信息

QEMU

Ceph 块设备最常见的用例是向虚拟机提供块设备映像。例如,用户可以创建具有理想配置的操作系统和任何相关软件的“golden”映像。然后,用户创建该镜像的快照(snapshot)。最后,用户克隆该快照(可能多次)。制作快照的写时复制(copy-on-write)能力意味着 Ceph 可以快速地向虚拟机提供块设备映像,因为客户端不必在每次启动新的虚拟机时下载整个映像

Ceph 块设备连接到 QEMU 虚拟机,如下图所示

img

安装

有关 QEMU 文档,请参阅 QEMU Manual.。有关安装详细信息,请参阅 Installation

使用

QEMU 命令行要求您指定 Ceph 池和镜像名称。您还可以指定快照。

QEMU 将假定 Ceph 配置位于默认位置(例如,/etc/ceph/$cluster.conf),并且您以默认 client.admin 用户身份执行命令

1
qemu-img {command} [options] rbd:{pool-name}/{image-name}[@snapshot-name][:option1=value1][:option2=value2...]

可以指定另一个 Ceph 配置文件路径或其他用户。例如,指定 id 和 conf 选项可能如下所示:

1
qemu-img {command} [options] rbd:glance-pool/maipo:id=glance:conf=/etc/ceph/ceph.conf
  • 指定用户时,请勿在用户 ID 前添加客户端类型(例如 client.),否则您将收到身份验证错误。
  • 您应该将管理员用户的密钥或使用 :id={user} 选项指定的另一个用户的密钥放在密钥环文件中,该文件存储在默认路径中(即 /etc/ceph 或具有适当文件所有权和权限的本地目录)。

  • 详情请参阅User Management - User

使用QEMU创建镜像
1
qemu-img create -f raw rbd:{pool-name}/{image-name} {size}

raw 数据格式实际上是 RBD 中 format 选项唯一合理的选项。从技术上讲,您可以使用其他 QEMU 支持的格式(例如 qcow2 或 vmdk)。但这样做会增加额外的开销,并且在启用缓存(见下文)时也会导致卷对于虚拟机实时迁移不安全。

使用 QEMU 调整镜像大小
1
qemu-img resize rbd:{pool-name}/{image-name} {size}
使用 QEMU 检索image信息
1
qemu-img info rbd:{pool-name}/{image-name}
使用 RBD 运行 QEMU

QEMU 可以将块设备从主机传递到客户机,但从 QEMU 0.15 开始,无需将images 映射为主机上的块设备。相反,QEMU 会直接通过 librbd 将镜像附加为虚拟块设备。

您可以使用 qemu-img 将现有的虚拟机镜像转换为 Ceph 块设备镜像。

1
2
# 将现有的qcow2镜像转换为ceph块镜像
qemu-img convert -f qcow2 -O raw debian_squeeze.qcow2 rbd:data/squeeze

要运行从该image启动的虚拟机,您可以运行:

1
qemu -m 1024 -drive format=raw,file=rbd:data/squeeze

RBD 缓存可以显著提升性能。

  • 自 QEMU 1.2 起,QEMU 的缓存选项控制 librbd 缓存:

    1
    qemu -m 1024 -drive format=rbd,file=rbd:data/squeeze,cache=writeback
  • 如果您有旧版本的 QEMU,则可以将 librbd 缓存配置(与任何 Ceph 配置选项一样)设置为“文件”参数的一部分:

    1
    qemu -m 1024 -drive format=raw,file=rbd:data/squeeze:rbd_cache=true,cache=writeback

    如果设置了 rbd_cache=true,则必须设置 cache=writeback,否则可能会丢失数据。因为,不设置writeback,QEMU 将不会向 librbd 发送刷新请求。如果 QEMU 在此配置下不干净退出,则 rbd 上的文件系统可能会损坏。

启用丢弃/修剪(discard/trim)

从 Ceph 0.46 版和 QEMU 1.1 版开始,Ceph 块设备支持discard操作。即客户机可以发送 TRIM 请求,让 Ceph 块设备回收未使用的空间。

  • 此功能可以通过在客户机中挂载 ext4 或 XFS 文件系统并使用discard选项来启用。

为了使客户机可以使用此功能,必须为块设备显式启用此功能。为此,必须指定与驱动器关联的 discard_granularity:

1
2
qemu -m 1024 -drive format=raw,file=rbd:data/squeeze,id=drive1,if=none \
-device driver=ide-hd,drive=drive1,discard_granularity=512
  • 这使用了 IDE 驱动程序。从 Linux 内核版本 5.0 开始,virtio 驱动程序支持丢弃。

如果使用 libvirt,请使用 virsh edit 编辑 libvirt 域的配置文件,以添加 xmlns:qemu 值。然后,添加 qemu:commandline 块作为该域的子块。以下示例显示如何将两个 qemu id= 的设备设置为不同的 discard_granularity 值。

1
2
3
4
5
6
7
8
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<qemu:commandline>
<qemu:arg value='-set'/>
<qemu:arg value='block.scsi0-0-0.discard_granularity=4096'/>
<qemu:arg value='-set'/>
<qemu:arg value='block.scsi0-0-1.discard_granularity=65536'/>
</qemu:commandline>
</domain>
QEMU缓存选项

QEMU 的缓存选项对应于以下 Ceph RBD 缓存设置。

  • Writeback:

    1
    rbd_cache = true
  • Writethrough:

    1
    2
    rbd_cache = true
    rbd_cache_max_dirty = 0
  • None:

    1
    rbd_cache = false

QEMU 的缓存设置覆盖 Ceph 的缓存设置(包括在 Ceph 配置文件中明确设置的设置)。

Libvirt

libvrit 库在虚拟机管理程序接口和调用这些API的应用层软件之间,创建了一个抽象层

借助libvirt,开发者和系统管理员可以专注于通用管理框架、API和通用Shell接口,可用于多种虚拟机管理程序:

  • QEMU/KVM
  • XEN
  • LXC
  • VirtualBox
  • etc.

可将 Ceph 块设备与可与 libvirt 交互的软件配合使用。下面的堆栈图说明了 libvirt 和 QEMU 如何通过 librbd 使用 Ceph 块设备。

img

最常见的 libvirt 用例涉及向 OpenStack、OpenNebula 或 CloudStack 等云解决方案提供 Ceph 块设备。还可以将 Ceph 块设备与 libvirt、virsh 和 libvirt API 结合使用。

要创建使用 Ceph 块设备的虚拟机,请使用以下部分中的步骤。

  • 在示例性实施例中,我们使用 libvirt-pool 作为池名称、client.libvirt 作为用户名、new-libvirt-image 作为image名称。

与其他

块设备与Openstack

使用Ceph块设备

SSH

SSH客户端 可连接运行了 SSH服务器 的远程主机

SSH协议:专为远程登录会话和其他网络服务提供安全性的协议。建立在应用层和传输层基础上的安全协议

连接

  • 通过SSH协议防止远程登录过程的信息泄露

    | 命令 | 对应英文 | 作用 |
    | :————————————: | :—————: | :—————- |
    | ssh 用户名@ip | secure shell | 远程bash |
    | scp 用户名@ip:文件名或录用 | secure copy | 远程复制文件 |

    1
    2
    3
    4
    5
    6
    7
    8
    ssh [-p port] user@remote Ip
    # user:远程机器上的用户名,不指定的话默认当前用户
    # remote:远程机器ip,可以是 ip/域名,或者别名
    # port:SSH 服务器进程监听的端口,不指定,默认是22

    ssh -p 22 Amos@172.16.140.133

    exit #退出当前用户

    Windows若想ssh连接到Linux,则需要安装相应的客户端

    • Linux默认采用的编码格式是UTF-8,Windows默认采用的编码格式是ANSI(GB2312、GBK),所以需要设置一下相应编码:

      img

  • 对所有传输的数据进行加密,防止DNS与IP欺骗

    | 选项 | 含义 |
    | :—: | :—————————————————————————————- |
    | -r | 给出的源文件是目录文件,将递归赋值该目下的所有子目录和文件,目标文件必须为一目录名 |
    | -P | 若远程SSH服务器的端口不是22,需要用大写字母 P 来指定端口 |

    1
    2
    3
    4
    5
    6
    7
    8
    # 将本地当前目录下的 01.py 复制到远程家目录下的 Desktop/01.py
    scp -P port 01.py user@remote:Desktop/01.py

    # 把远程家目录下的 Desktop/01.py 文件复制到本地当前目录下的 01.py
    scp -P port user@remote:Desktop/01.py 01.py

    # 加上-r 传输文件夹
    scp -r demo user@remote:Desktop

    1583664333575

免密登录

在本地 .ssh 文件夹下登录远程服务器,保存SSH相关的配置信息

生成本地当前用户公钥

  • 执行 ssh-keygen 即可生成SSH公钥,全部回车
  • id_rsa.pub :即本机公钥
  • id_rsa :即本机私钥

将公钥上传到服务器

  • 执行 ssh-copy-id -p port user@remote ,让远程服务器记住公钥

1583670524766

工作原理:非对称加密算法

使用 公钥 加密的数据,需要私钥解密
使用 私钥 加密的数据,需要公钥解密

  • 本地发送的数据,用私钥加密
  • 服务器若保存有公钥,会对数据解密
  • 数据处理完后用公钥加密,回传给本地

ssh配置别名

输入 ssh username@ipaddress 很繁琐
~/.ssh/config 里追加以下内容

1
2
3
4
Host newName
HostName ip_address
User username
Port port

保存之后,就可使用别名登录

1583672523059

Webserver

http.tar.gz 是用C语言编程的一个简单版webserver

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
# 将http.tar.gz拷贝到服务端,并解压
root@tzj-virtual-machine:/opt# tar -xzvf http.tar.gz
http/
http/success.html
http/images/
http/images/MC_icon_white.png
http/images/tit.jpg
http/images/logo.jpg
http/images/11.jpg
http/images/mail.jpg
http/images/MC_icon.png
http/images/pop_close.png
http/images/conterpic3.jpg
http/images/bdlogo.gif
http/images/QR1.jpg
http/images/QR2.jpg
http/default.html
http/data.txt
http/makefile
http/jquery-1.11.1.min.js
http/myhttp
http/css/
http/css/style.css
http/src/
http/src/useradd.c
http/src/work.h
http/src/pub.c
http/src/s.c
http/src/server.c
http/src/work.c
http/src/pub.h
http/src/pass.c
http/index.html
http/favicon.ico
http/error.html
http/templet.zhujy
http/postfile.html

# 进入解压目录,编译源码
root@tzj-virtual-machine:/opt# cd http/
root@tzj-virtual-machine:/opt/http# ls
css default.html favicon.ico index.html makefile postfile.html success.html
data.txt error.html images jquery-1.11.1.min.js myhttp src templet.zhujy
root@tzj-virtual-machine:/opt/http# make

# 关闭防火墙
root@tzj-virtual-machine:/opt/http# systemctl status ufw
● ufw.service - Uncomplicated firewall
Loaded: loaded (/lib/systemd/system/ufw.service; enabled; vendor preset: enabled)
Active: active (exited) since Sun 2025-09-28 18:24:44 CST; 1 week 4 days ago
Docs: man:ufw(8)
Main PID: 676 (code=exited, status=0/SUCCESS)
CPU: 72ms

9月 28 18:24:42 tzj-virtual-machine systemd[1]: Starting Uncomplicated firewall...
9月 28 18:24:44 tzj-virtual-machine systemd[1]: Finished Uncomplicated firewall.
root@tzj-virtual-machine:/opt/http# systemctl stop ufw
root@tzj-virtual-machine:/opt/http# systemctl disable ufw
Synchronizing state of ufw.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable ufw
Removed /etc/systemd/system/multi-user.target.wants/ufw.service.

# root用户启动或关闭web服务
root@tzj-virtual-machine:/opt/http# ./myhttp stop
signal SIGTERM
myhttp end

root@tzj-virtual-machine:/opt/http# ./myhttp start
listen 80 success
myhttp begin

root@tzj-virtual-machine:/opt/http# ./myhttp start
listen 80 success
myhttp begin
root@tzj-virtual-machine:/opt/http# ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.16.140.199 netmask 255.255.255.0 broadcast 172.16.140.255
ether 00:0c:29:e5:2a:4c txqueuelen 1000 (Ethernet)
RX packets 86878 bytes 82336775 (82.3 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 42514 bytes 11662099 (11.6 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 62266 bytes 14774523 (14.7 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 62266 bytes 14774523 (14.7 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# 访问,启动成功

image-20251009204945796

修改输出内容

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
root@tzj-virtual-machine:/opt/http# vim hello.c

#include <stdio.h>

int main()
{
printf("hello world\n");

return 0;
}

# 编译
root@tzj-virtual-machine:/opt/http# gcc hello.c -o hello.cgi

# 启动http
root@tzj-virtual-machine:/opt/http# ./myhttp start
listen 80 success
myhttp begin

## 此时,访问http://172.16.140.199/hello.cgi
root@tzj-virtual-machine:/opt/http# ./myhttp start
listen 80 success
myhttp begin
root@tzj-virtual-machine:/opt/http# accept by 172.16.140.1
thread is begin
recv:
GET /hello.cgi HTTP/1.1
Host: 172.16.140.199
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

accept by 172.16.140.1
thread is begin
headbuf:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length:12

thread_is end
recv:
GET /favicon.ico HTTP/1.1
Host: 172.16.140.199
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://172.16.140.199/hello.cgi
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

headbuf:
HTTP/1.1 200 OK
Content-Type: image/x-icon
Content-Length:2550

thread_is end

image-20251009205450529

中文乱码 :Linux默认采用的编码格式是UTF-8,浏览器显示默认采用的编码格式是GBK

image-20251009205857951

直接修改浏览器编码

在输头部信息中指定编码格式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@tzj-virtual-machine:/opt/http# vim ceshi.c

#include <stdio.h>

int main()
{
printf("<head>");
printf("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");
printf("</head>");
printf("<html>");
printf("测试\n");
printf("</html>");

return 0;
}

root@tzj-virtual-machine:/opt/http# gcc ceshi.c -o ceshi.cgi

image-20251009205638155

ADB

安卓调试工具(Android Debug Bridge,ADB),PC端与安卓手机的通道,管理手机设备或模拟器的状态

Windows配置ADB环境

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
# 将abd.exe添加到环境变量
D:\deps\platform-tools

# 测试abd命令
C:\Users\tian_zj>adb
Android Debug Bridge version 1.0.31

-a - directs adb to listen on all interfaces for a connection
-d - directs command to the only connected USB device
returns an error if more than one USB device is present.
-e - directs command to the only running emulator.
returns an error if more than one emulator is running.
-s <specific device> - directs command to the device or emulator with the given
serial number or qualifier. Overrides ANDROID_SERIAL
environment variable.
-p <product name or path> - simple product name like 'sooner', or
a relative/absolute path to a product
out directory like 'out/target/product/sooner'.
If -p is not specified, the ANDROID_PRODUCT_OUT
environment variable is used, which must
be an absolute path.
-H - Name of adb server host (default: localhost)
-P - Port of adb server (default: 5037)
devices [-l] - list all connected devices
('-l' will also list device qualifiers)
connect <host>[:<port>] - connect to a device via TCP/IP
Port 5555 is used by default if no port number is specified.
disconnect [<host>[:<port>]] - disconnect from a TCP/IP device.
Port 5555 is used by default if no port number is specified.
Using this command with no additional arguments
will disconnect from all connected TCP/IP devices.

将Android设备通过USB连接到PC

1
2
3
# 将Android设备通过USB连接到PC,安装相应驱动

# Android设备启动USB调试功能

2016-05-31_205333

1
2
3
4
C:\Users\tian_zj>adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
List of devices attached

targetcli块设备

服务端配置

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
[root@node205]# yum install -y targetcli

[root@node205]# systemctl enable --now target

[root@node205 yum.repos.d]# targetcli
Warning: Could not load preferences file /root/.targetcli/prefs.bin.
targetcli shell version 2.1.54
Copyright 2011-2013 by Datera, Inc and others.
For help on commands, type 'help'.

/> ls
o- / ......................................................................................................................... [...]
o- backstores .............................................................................................................. [...]
| o- block .................................................................................................. [Storage Objects: 0]
| o- fileio ................................................................................................. [Storage Objects: 0]
| o- pscsi .................................................................................................. [Storage Objects: 0]
| o- ramdisk ................................................................................................ [Storage Objects: 0]
o- iscsi ............................................................................................................ [Targets: 0]
o- loopback ......................................................................................................... [Targets: 0]
o- vhost ............................................................................................................ [Targets: 0]
o- xen-pvscsi ....................................................................................................... [Targets: 0]
/> cd backstores/
/> backstores/block create
/> cd backstores/ramdisk
/backstores/ramdisk> create name=rd1 size=10G
Created ramdisk rd1 with size 10G.
/backstores/ramdisk> cd /
/> cd iscsi
/iscsi> create iqn.2025-09.com.example:ram.target
Created target iqn.2025-09.com.example:ram.target.
Created TPG 1.
Global pref auto_add_default_portal=true
Created default portal listening on all IPs (0.0.0.0), port 3260.
/iscsi> ls
o- iscsi .............................................................................................................. [Targets: 1]
o- iqn.2025-09.com.example:ram.target .................................................................................. [TPGs: 1]
o- tpg1 ................................................................................................. [no-gen-acls, no-auth]
o- acls ............................................................................................................ [ACLs: 0]
o- luns ............................................................................................................ [LUNs: 0]
o- portals ...................................................................................................... [Portals: 1]
o- 0.0.0.0:3260 ....................................................................................................... [OK]
/iscsi> cd /
/> ls
o- / ......................................................................................................................... [...]
o- backstores .............................................................................................................. [...]
| o- block .................................................................................................. [Storage Objects: 0]
| o- fileio ................................................................................................. [Storage Objects: 0]
| o- pscsi .................................................................................................. [Storage Objects: 0]
| o- ramdisk ................................................................................................ [Storage Objects: 1]
| o- rd1 ............................................................................................... [(10.0GiB) deactivated]
| o- alua ................................................................................................... [ALUA Groups: 1]
| o- default_tg_pt_gp ....................................................................... [ALUA state: Active/optimized]
o- iscsi ............................................................................................................ [Targets: 1]
| o- iqn.2025-09.com.example:ram.target ................................................................................ [TPGs: 1]
| o- tpg1 ............................................................................................... [no-gen-acls, no-auth]
| o- acls .......................................................................................................... [ACLs: 0]
| o- luns .......................................................................................................... [LUNs: 0]
| o- portals .................................................................................................... [Portals: 1]
| o- 0.0.0.0:3260 ..................................................................................................... [OK]
o- loopback ......................................................................................................... [Targets: 0]
o- vhost ............................................................................................................ [Targets: 0]
o- xen-pvscsi ....................................................................................................... [Targets: 0]
/> cd iscsi/iqn.2025-09.com.example:ram.target/tpg1/
iscsi/iqn.2025-09.com.example:ram.target/tpg1/acls/ iscsi/iqn.2025-09.com.example:ram.target/tpg1/luns/
iscsi/iqn.2025-09.com.example:ram.target/tpg1/portals/
/> cd iscsi/iqn.2025-09.com.example:ram.target/tpg1/luns
/iscsi/iqn.20...get/tpg1/luns> create /backstores/ramdisk/rd1
Created LUN 0.
/iscsi/iqn.20...get/tpg1/luns> cd /iscsi/iqn.2025-09.com.example:ram.target/tpg1/acls
/iscsi/iqn.20...get/tpg1/acls> create iqn.2025-09.com.example:initiator01
Created Node ACL for iqn.2025-09.com.example:initiator01
Created mapped LUN 0.
/iscsi/iqn.20...get/tpg1/acls> cd /iscsi/iqn.2025-09.com.example:ram.target/tpg1/portals/
/iscsi/iqn.20.../tpg1/portals> delete
0.0.0.0 ip_address= ip_port=
/iscsi/iqn.20.../tpg1/portals> delete 0.0.0.0 3260
Deleted network portal 0.0.0.0:3260
/iscsi/iqn.20.../tpg1/portals> create 10.152.1.205 3260
Using default IP port 3260
Created network portal 10.152.1.205:3260.
/iscsi/iqn.20.../tpg1/portals> cd /
/> saveconfig
Configuration saved to /etc/target/saveconfig.json
/> exit
Global pref auto_save_on_exit=true
Last 10 configs saved in /etc/target/backup/.
Configuration saved to /etc/target/saveconfig.json

scst

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
rpm -i scst-4.19.90-52.23.v2207.gfb08.ky10.aarch64-3.7.0-1.ky10.src.rpm

cd ~/rpmbuild/SOURCES/

tar -xvf scst-3.7.0.tar.bz2 -C /root/dl

cd ~/dl/scst-3.7.0/

make clean
make scst -j
make iscsi -j
make scstadm -j

make scst_install
make iscsi_install
make scstadm_install


cd /root/dl/scst-3.7.0/scst/src/dev_handlers

vim Kbuild
obj-m := scst_cdrom.o scst_changer.o scst_disk.o scst_modisk.o scst_tape.o \
scst_raid.o scst_processor.o scst_user.o scst_vdisk.o

make

cp scst_vdisk.ko /lib/modules/4.19.90-52.23.v2207.gfb01.ky10.aarch64/

depmod

# 改为本机IP,仅第一次需要操作
vim /roor/dl/scst_config



modprobe brd rb_nr=1 rd_size=16777216 max_part=0

modprobe scst_vdisk
modprobe iscsi-scst
iscsi-scstd
scstadmin -config /root/dl/scst_config




chmod +x /etc/rc.d/rc.local

vim /etc/rc.local
1
2
3
iscsiadm -m node -T iqn.2025-09.local:ramdisk.target -p 10.152.1.205 --logout


fio离线安装

1
2
3
4
5
6
7
tar -xzvf fio-fio-3.41.tar.gz

cd fio-fio-3.41

./configure

make && make install

系统安装与配置

网络配置

关闭防火墙

关闭SELinux

关闭NetworkManager

1
2
systemctl stop NetworkManager
systemctl disable NetworkManager

ssh配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
```









## 安装所需的服务

```shell
yum install gcc gcc-c++ make autoconf readline readline-devel vim-enhanced openssh-clients

yum install lrzsz net-tools -y

安装NFS服务器

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
yum install -y nfs-utils rpcbind

# 设置共享目录,设置为完全共享目录
[root@node ~]# mkdir /home/nfs
[root@node ~]# chmod -R u=rwx,g=rwx,o=rwx /home/nfs
[root@node home]# chown nfsuser.nfsuser /home/nfs

# 新增共享用户
所有客户机用户如果未指定,在访问NFS服务端时,都会被映射为nobody用户。如果要让客户端的用户在读写的共享目录上有写的权利,必须设置一个用户,将所有客户端用户映射为该用户,以获取对服务端的操作权限
[root@node ~]# useradd -u 1100 -s /sbin/nologin -M nfsuser
[root@node ~]# id nfsuser
[root@node ~]# passwd nfsuser
更改用户 usershare 的密码 。
新的 密码:135qetadg
重新输入新的 密码:135qetadg
[root@node ~]# id nfsuser
uid=1100(nfsuser) gid=1100(nfsuser) 组=1100(nfsuser)


# 编辑exports文件
[root@node home]# vim /etc/exports
/home/nfs 192.168.100.191/16(rw,sync,no_all_squash,insecure,anonuid=1100,anongid=1100)

# 启动NFS
[root@node ~]# systemctl start rpcbind
[root@node ~]# systemctl status rpcbind
● rpcbind.service - RPC Bind
Loaded: loaded (/usr/lib/systemd/system/rpcbind.service; enabled; >
Active: active (running) since Tue 2025-09-30 22:51:51 CST; 6min a>
Docs: man:rpcbind(8)
Main PID: 1088 (rpcbind)
Tasks: 1 (limit: 98884)
Memory: 1.9M
CGroup: /system.slice/rpcbind.service
└─1088 /usr/bin/rpcbind -w -f

9月 30 22:51:51 node systemd[1]: Starting RPC Bind...
9月 30 22:51:51 node systemd[1]: Started RPC Bind.
[root@node ~]# systemctl enable rpcbind
[root@node ~]# systemctl start nfs-server.service
[root@node ~]# systemctl enable nfs-server.service
Created symlink /etc/systemd/system/multi-user.target.wants/nfs-server.service → /usr/lib/systemd/system/nfs-server.service.

# 配置校验
root@node home]# showmount -e
Export list for node:
/home/nfs 192.168.100.191/16

客户端挂载

1
mount -t nfs 192.168.100.191:/home/nfs /mnt -o proto=tcp -o nolock

https://luyuhuang.tech/2020/12/09/kcp.html

https://zhuanlan.zhihu.com/p/10153049382

KCP整体流程

  1. 调用 ikcp_send 发送数据,创建报文段实例,加入 snd_queue

  2. ikcp_update 会在合适的时刻调用 ikcp_flush

    ikcp_flush 中:

    • 发送ACK列表中所有的ACK
    • 检查是否需要发送窗口探测和通知报文,若需要,则发送相应的报文
    • 根据发送窗口大小,将适量报文段从 发送队列snd_queue 移入 发送缓冲snd_buf
    • 将 发送缓冲区 snd_buf 中满足条件的报文段发送出去:
      • 新加入 snd_buf 中,从未被发送过的报文
      • 发送过,但在RTO内未收到ACK 的报文,进行重传
      • 发送过得,但是ACK时序若干次的报文,执行快速重传
    • 根据丢包情况,计算 sshresh 和 cwnd
  3. 对端收到报文后,icp_input 会被调用,解析收到的数据

    所有的报文都有 una 字段,根据una将相应的报文标为送达

    • 如果是 ACK 报文, 就将相应的报文标记为已送达

    • 如果是数据报文, 就将它放入 rcv_buf, 然后将 rcv_buf 中顺序正确的报文移入 rcv_queue

      接着将相关信息插入 ACK 列表, 在稍后的 ikcp_flush 调用中会发送相应的 ACK;

    • 如果是窗口探测报文, 就标记 “需要发送窗口通知”. 在稍后的 ikcp_flush 调用中会发送窗口通知报文;

    包括窗口通知报文在内的所有报文都有 wnd 字段, 据此更新 rmt_wnd;(对端窗口大小)

    根据 ACK 失序情况决定快速重传

    计算cwnd

  4. 调用 ikcp_recv 接收数据,从 rcv_queue 中读取数据

  5. 对端发送ACK报文给发送方

  6. 发送方在 ikcp_input 中解析到ACK,将相应报文标为已送达

队列中的报文都是有序的(发送队列与接收队列都是)

receive buffer

缓冲区

缓冲区与滑动窗口相关

发送

snd_buf 是一个链表,链表中的报文段编号始终递增

  • 发送队列中的报文段插入到链表尾
  • 收到ACK的报文从链表中删除

snd_buf

两个指针

  • snd_una 指针指向第一个已经发出但未收到ACK的报文段

    小于该una的报文,均已收到ACK,

    在发送缓冲区 snd_buf 中,已经收到ACK的报文段会被删除

  • snd_nxt 指向缓冲区的下一个插入位置

发送窗口 cwnd 的起始位置是 snd_una 指针指向的位置,大小为 cwnd

flush时,会从 snd_queue 中取出报文,并从 snd_nxt 指向的位置开始插入 snd_buf ,然后 snd_nxt 向右移动

snd_nxt - snd_una >= cwnd 时,不允许新的报文加入 snd_buf

发送窗口中未确认到达的报文何时重传,取决于这个报文的RTO,报文在一个RTO时间内仍未被确认,则重传

  • RTO初始值被赋为 rx_rto
  • 每次超时重传,RTO都会以某种方式增长(取决于重传机制,可以翻倍或翻0.5倍,或快速重传)

接收

在 KCP 的实现中, rcv_buf 是一个报文段链表,链表中报文段编号始终递增。当收到新的报文时,会根据编号插入到链表中相应的位置中。顺序正确的报文会从链表头部弹出, 移动到 rcv_queue 中。

  • nrcv_que 为接收队列的长度,rcv_buf 中在 rcv_nxt 之前的报文段都将移入 rcv_queue
  • 接收方及时读取前 nrcv_que 个报文,读取后删除该报文,接收窗口向右滑动

rcv_buf

指针 rcv_nxt :指向编号最小的未收到报文应插入的位置,即已收到最小编号的报文之前

  • 每收到一个数据报文,都会根据报文编号,将其插入 rcv_buf 对应的位置。然后,检查 rcv_nxt 是否可向右移动,仅当报文顺序正确且连续才能移动

KCP会通知发送方,剩余的接收窗口大小 rcv_wnd-nrcv_queue

  • 发送方根据 这个值调整发送窗口的大小

如果收到的数据报文编号大于 rcv_nxt+rcv_wnd ,远超接收窗口,这个报文会被丢弃

报文中,会携带报文发送者的接收缓冲区 rcv_buf 中还未收到的最小报文编号——rcv_nxt

  • 因此,KCP发送的每个报文都会将una字段赋值为 rcv_nxt

具有ACK+UNA的双重确认机制

实例

flow

t1:发送una=1的报文,该报文被加入发送缓冲区

  • snd_una 指向1,snd_nxt 指向2

t2-t3:依次发送2至3号报文,snd_nxt 依次后移

t4:对端收到3号报文,放入 rcv_buf 中,发送3号报文的ACK报文

t5:对端收到2号报文,放入rcv_buf 中,发送2号报文的ACK报文

t7:收到2号报文的ACK报文,将2号报文标为已送达,送 snd_buf 中移除

  • ACK报文的una为1,标识rcv_nxt指向1,对端还未收到una=1的报文,因此发送方知道需要重发

    snd_una 仍为1

t8:重发1号报文,

t9:1号报文到达对端,对端发送1号报文的ACK

  • 由于已收到3号报文,所以该ACK报文的una=4

t10:发送方收到1号报文的ACK报文,将1号报文标为发送成功,同时una=4,所以也将3号报文标为已收到,snd_una 移动到4

重传机制

每个报文的RTO会随着重传次数的增加而增加

初始值保存在 rx_rto 中,

KCP计算RTO初始值的方法是TCP的标准方法,在RFC 6298中

  • 标准方式通过RTT计算RTO:报文从发出到收到ACK经过的时间为一个RTT,如果某个报文在一个RTT内未收到ACK,说明可能丢包了
  • RTO与RTT正相关,且应高于RTT,以容忍一定程度的抖动

计算所依赖的两个中间量,$srtt$ (对一段时间内RTT均值的估计)与 $rttvar$ (对 RTT 平均偏差的估计)

  • 每收到一个ACK都能得到一个RTT,然后更新中间变量,得出RTO

  • 其中,$g=\frac{1}{8}$ 与 $h=\frac{1}{4}$ 为常数,$interval$ 为计时器粒度(调用 ikcp_flush 的时间间隔)

  • 指数加权移动平均
  • RTO至少为一个时钟间隔

  • 初始值,测得第一个RTT时对srtt和rttvar初始化

拥塞控制

  • 网络带宽有限
  • 接收方接收队列、接收缓冲有限

发送方不能无限制地发送数据,否则必然会导致网络与缓冲区无法容纳发送的数据,导致大量丢包

拥塞控制,实际上是动态计算发送窗口大小的过程

  • 慢启动(Slow start)
  • 拥塞避免(Congestion Avoidance)
  • 快速恢复(Fast Recovery)

拥塞控制策略

congestion control

慢启动:理想情况下, 发送数据的速率正好等于网路的容量, 然而我们并不知道网路的实际容量. 我们的做法是先将 cwnd 设为 1, 随后平均每经过一个 RTT 时间 cwnd 都翻一倍, 以探测到何时会出现丢包. 这种方法便是慢启动.

在慢启动中 cwnd 会指数增长, 这个过程显然不能一直持续下去. 当 cwnd 超过一个阈值之后, 便会进入拥塞避免阶段. 这个阈值我们称为慢启动阈值(Slow Start Threshold), 通常记作 ssthresh

在拥塞避免阶段, cwnd 会近似于线性增长.

随着 cwnd 的增长, 网路容量会被逐步填满

网络容量填满之后, 就会不可避免地出现丢包. 这就意味着我们的发送速率过大, cwnd 应该减小了. KCP 称之为丢包退让。此时有两种策略:

  • 一种是将 ssthresh 置为当前 cwnd 的一半, 然后 cwnd 置为 1, 重新执行慢启动

  • 将 ssthresh 置为当前 cwnd 的一半, 但 cwnd 置为比 ssthresh 稍高的值, 然后进入快速恢复阶段

    快速恢复阶段中, cwnd 会以与拥塞避免相同的方式线性增长

KCP 采用的策略是, 如果发生超时重传, 就进入慢启动; 如果发生快速重传, 就进入快速恢复.

  • 接收方会不断地向对端汇报接收窗口的剩余大小, 发送方会限制 cwnd 不超过 rmt_wnd.

慢启动

慢启动时, 平均每经过一个 RTT 时间 cwnd 都翻一倍

  • 每收到一个 ACK cwnd 都加一

slow start

拥塞避免和快速恢复

在拥塞避免阶段, 差不多要等当前发送窗口发的报文都确认到达之后, cwnd 才增加 1.

congestion avoidance

在 KCP 的实现中, 拥塞避免阶段的 cwnd 仍然会在每收到一个 ACK 的时候增长, 只不过不是增加 1, 而是增加零点几.

KCP 的做法是维护一个中间变量 incr,计算方式为诶

当 incr 累计增加的值超过一个 mss 时, cwnd 增加 1

image-20250918112104044

实际上相当于每收到一个 ACK, cwnd 都自增 $\frac{1}{cwnd}$ ,即要等当前发送窗口发的报文都确认到达之后, cwnd 才增加 1.

去掉 $\frac{mss}{16}$ 即为TCP拥塞避免阶段cwnd的增长方式

特性

  • 快速重传:KCP 可以根据接收方返回的确认信息快速判断哪些数据包已经丢失,并迅速进行重传。
  • 选择性确认:允许接收端告知发送端哪些包已收到,仅重传未被确认接收的包
  • 无连接操作:基于UDP,不需要像TCP进行三次握手建立连接,减少初始的延迟
  • 拥塞控制:类似TCP的拥塞控制
  • 流量控制:可以调整发送和接收窗口的大小,使发送方根据对端处理能力和网络条件调整数据发送速率
  • 可配置的传输策略:允许用户根据应用需求调整内部参数,
  • 前向错误修正(Forward Error Correction,FEC):结合FEC技术,通过发送额外的冗余数据来恢复丢失的包
特性类别协议描述
拥塞控制机制TCP固定算法(慢启动、拥塞避免等),保守的调整策略(指数和线性增长)
KCP灵活算法,动态调整策略,快速调整窗口大小
重传机制的延迟TCP固定重传间隔(RTO),多次确认触发重传,需要主动开启选择性重传(SACK)
KCP快速重传,选择性重传,减少重传延迟
流量控制TCP固定流量控制(依赖接收窗口和发送窗口),通用性设计
KCP自适应流量控制,应用层反馈调整发送窗口和重传策略
应用场景TCP广泛应用于各种网络环境,标准化要求高
KCP优化特定场景(如高丢包率和高延迟网络),灵活实现

TCP 作为一种成熟且广泛使用的传输协议,在设计上注重可靠性和通用性,因此在拥塞控制和流量控制方面相对保守,以确保在各种网络条件下都能稳定运行。

拥塞控制机制

  • TCP
    • 固定算法:TCP的拥塞算法(慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快速重传(Fast Retransmit)和快速恢复(Fast Recovery)),考虑了兼容性与可靠性,但调整机制固定,相应速度慢
    • 保守的调整策略:采用指数增长和线性增长,在高丢包率或高延迟的网络中,会导致拥塞窗口增长速度慢,影响传输效率
  • KCP
    • 灵活算法:拥塞机制根据网络实况进行快速调整
    • 动态的调整策略

重传机制的延迟

  • TCP
    • 固定重传间隔:使用固定的重传超时(RTO),随着每次重传逐渐增加(指数回避)。在高延迟与高丢包率的网络环境中重传延迟长
    • 多次确认触发重传:TCP的快速重传需要等待三个重复ACK才会触发,延迟高
  • KCP
    • 快速重传:KCP在检测到丢包后,立即重传,不需要等待多个重复的ACK
    • 选择性重传:KCP只重传丢失的数据包,不是所有未确认的数据包

应用场景的差异

  • TCP

    • 广泛应用:TCP 设计用于广泛的网络环境,包括稳定的有线网络和不稳定的无线网络,因此其机制必须足够通用和保守,保证在各种情况下的可靠性。

    • 标准化要求:作为互联网的基础协议,TCP 的各项机制经过严格标准化,任何修改都需要广泛测试和验证,以确保不会影响现有网络的稳定性。

  • KCP

    • 特定优化:KCP 设计初衷是优化特定场景下的传输性能,特别是高丢包率和高延迟网络,因此在设计上更加灵活,能够根据实时网络状况进行调整。

    • 灵活实现:KCP 可以根据具体应用需求进行优化,例如在实时通信和在线游戏等场景中,灵活的流量控制和快速重传机制显著提升了传输效率。

KCP check

可以利用这个函数构建高效的事件驱动网络程序

  1. 避免轮询:不需要定期遍历所有连接调用irudp_update)
  2. 精确调度:每个连接都知道自己何时需要被更新
  3. 资源节约:只在必要时处理连接,减少CPU使用
  4. 可扩展性:即使连接数增加,调度算法依然高效
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
// 示例:管理多个RUDP连接的调度器
typedef struct {
irudpcb **connections; // RUDP连接数组
int count; // 连接数量
} rudp_scheduler_t;

void efficient_rudp_loop(rudp_scheduler_t *scheduler) {
while (running) {
IUINT32 current_time = get_current_millis();
IUINT32 next_update_time = 0xffffffff;

// 找到所有连接中最早需要更新的时间
for (int i = 0; i < scheduler->count; i++) {
IUINT32 check_time = irudp_check(scheduler->connections[i], current_time);
if (check_time < next_update_time) {
next_update_time = check_time;
}
}

// 等待到最近的更新时间点或直到有网络事件
int timeout = (next_update_time > current_time) ?
(next_update_time - current_time) : 0;

// 使用epoll/select等待网络事件,超时时间为timeout
int events = epoll_wait(epoll_fd, events_buffer, max_events, timeout);

// 处理网络事件
for (int i = 0; i < events; i++) {
irudpcb *rudp = get_rudp_from_event(events_buffer[i]);
// 处理输入数据
irudp_input(rudp, received_data, data_len);
}

// 更新到期的连接
current_time = get_current_millis();
for (int i = 0; i < scheduler->count; i++) {
if (irudp_check(scheduler->connections[i], current_time) <= current_time) {
irudp_update(scheduler->connections[i], current_time);
}
}
}
}