Ubuntu Download下载站点及镜像站点整理

Ubuntu Download下载站点及镜像站点整理

关键词建

Ubuntu download 官方站点, Ubuntu 下载官网地址, Ubuntu 镜像站点大全, Ubuntu 官方下载页面, Ubuntu 下载站点整理, Ubuntu 镜像源推荐, Ubuntu 官方下载链接, Ubuntu 下载指南, Ubuntu 镜像站点列表, Ubuntu 官方下载中心

其他:Kali Linux Download (for Chinese)

Kali Linux Download (for English)

🌐 Ubuntu 官方下载站点

官方主站点

官方直接下载链接(当前 LTS:22.04.4)

版本归档

🌍 国内镜像站点 - ✅ 已验证

教育网镜像

清华大学镜像 ✅

中科大镜像 ✅

上海交通大学镜像 ✅

华中科技大学镜像 ✅

北京交通大学镜像 ✅

商业镜像

阿里云镜像 ✅

华为云镜像 ✅

腾讯云镜像 ✅

网易镜像 ✅

搜狐镜像 ✅

🌎 国际镜像站点 - ✅ 已验证

亚洲地区

日本镜像 ✅

韩国镜像 ✅

新加坡镜像 ✅

欧洲地区

德国镜像 ✅

法国镜像 ✅

英国镜像 ✅

荷兰镜像 ✅

北美地区

美国官方镜像 ✅

加拿大镜像 ✅

美国西海岸镜像 ✅

📦 Ubuntu 版本下载链接

Ubuntu 22.04.4 LTS (Jammy Jellyfish) ✅

Ubuntu 20.04.6 LTS (Focal Fossa) ✅

Ubuntu 23.10 (Mantic Minotaur) ✅

其他架构版本

⚡ 下载建议

按地区选择镜像

中国大陆: 清华大学、阿里云、中科大

亚太地区: 日本、新加坡、韩国镜像

欧洲: 德国、法国、荷兰镜像

北美: 美国官方镜像

其他地区: 选择地理位置最近的镜像

下载方式

1
2
3
4
5
6
7
8
9
10
11
# 使用 wget 下载(推荐)
wget https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 使用 aria2 多线程下载
aria2c -x 16 -s 16 https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 使用 curl 下载
curl -O https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 断点续传
wget -c https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

校验文件完整性

1
2
3
4
5
6
7
8
9
10
# 校验 SHA256 哈希值
sha256sum ubuntu-22.04.4-desktop-amd64.iso

# 使用官方校验文件验证
wget https://releases.ubuntu.com/22.04/SHA256SUMS
sha256sum -c SHA256SUMS 2>&1 | grep ubuntu-22.04.4-desktop-amd64.iso

# 验证 GPG 签名
wget https://releases.ubuntu.com/22.04/SHA256SUMS.gpg
gpg --verify SHA256SUMS.gpg SHA256SUMS

🔧 相关工具

镜像写入工具

Rufus (Windows) - https://rufus.ie/

Etcher (跨平台) - https://www.balena.io/etcher/

UNetbootin (跨平台) - https://unetbootin.github.io/

Ventoy (多镜像管理) - https://www.ventoy.net/

启动盘制作器 (Ubuntu 内置)

命令行工具(Linux)

1
2
3
4
5
6
7
8
# 使用 dd(Linux)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync

# 使用 dd(macOS)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/rdiskX bs=1m

# 使用 balenaEtcher CLI
sudo etcher-cli ubuntu-22.04.4-desktop-amd64.iso --drive /dev/sdX

🔄 软件源镜像配置

使用国内镜像加速软件包更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 备份原始源列表
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup

# 编辑源列表
sudo nano /etc/apt/sources.list

# 替换为清华镜像(Ubuntu 22.04 示例)
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse

快速切换镜像脚本

1
2
3
4
5
#!/bin/bash
# 切换到清华镜像
sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo apt update

⚠️ 重要提醒

安全与验证

下载后务必校验文件完整性

仅使用官方或可信镜像

检查 GPG 签名

验证文件大小是否匹配

系统要求

  • 桌面版: 最低 4GB 内存,25GB 磁盘空间

  • 服务器版: 最低 1GB 内存,5GB 磁盘空间

  • 推荐配置: 8GB+ 内存,50GB+ 磁盘空间

合法使用

  • Ubuntu 完全免费开源

  • 遵循 Ubuntu 版权政策

  • 商业使用完全允许

📊 镜像状态检测

快速测试命令

1
2
3
4
5
6
7
8
9
10
11
# 测试镜像连通性
curl -s -o /dev/null -w "%{http_code}" https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/

# 测试下载速度(小文件)
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/HEADER.html

# 比较多个镜像
for mirror in "releases.ubuntu.com" "mirrors.tuna.tsinghua.edu.cn" "mirrors.aliyun.com"; do
echo "测试 $mirror:"
time wget -O /dev/null "https://$mirror/ubuntu-releases/HEADER.html" 2>&1 | grep real
done

🆘 故障排除

常见问题

下载中断: 使用 wget -c 断点续传

校验失败: 从不同镜像重新下载

下载缓慢: 尝试不同地理位置镜像

连接超时: 检查防火墙/代理设置

替代下载方式

  • Torrent: 官方 Ubuntu 种子文件

  • BitTorrent: P2P 下载方式

  • CDN: 使用商业 CDN 镜像

  • 本地缓存: 大学/机构镜像

所有标记为 ✅ 的链接均已验证可用。此整理提供了从最快最可靠的源下载 Ubuntu 的全面选择,可根据您的地理位置选择最适合的镜像站点。

AWK 的主流版本

AWK 的主流版本是 GNU AWK(gawk),它是 AWK 语言的最主要实现,也是目前更新最活跃的版本。以下是其最新版本及主要升级内容:

AWK 的主流版本是 GNU AWK(gawk),它是 AWK 语言的最主要实现,也是目前更新最活跃的版本。以下是其最新版本及主要升级内容:

一、最新稳定版本

截至 2024 年,GNU AWK 的最新稳定版本是 gawk 5.3.0(2023 年 10 月发布)。

二、主要版本升级内容(5.x 系列)

1. gawk 5.3.0(2023 年 10 月)

  • 正则表达式增强:支持 Unicode 15.0 标准,改进了对多字节字符的匹配精度。

  • 性能优化:提升了大型数组遍历和字符串操作的效率,尤其在处理 GB 级文本时速度提升约 10%。

  • 新增函数:strtonum() 函数支持更多进制转换(如二进制 0b 前缀、十六进制 0x 前缀)。

  • 兼容性修复:解决了与最新 Linux 内核版本的兼容性问题,修复了部分边缘场景的内存泄漏。

2. gawk 5.2.0(2022 年 12 月)

  • 安全增强:新增 –sandbox 模式,限制脚本对文件系统的写入和命令执行,防止恶意脚本攻击。

  • 调试功能:改进了 –debug 选项,支持更详细的语法解析日志,便于复杂脚本的调试。

  • 数组功能:支持对数组使用 delete 命令时返回删除的元素数量(n = delete arr)。

  • 国际化:增强了对非英语 locales 的支持,strftime() 函数可正确处理多语言日期格式。

3. gawk 5.1.0(2021 年 5 月)

主要特性更新:

  • 引入 命名捕获组(正则表达式中支持 (?…) 语法,可通过 arr[“name”] 访问捕获内容)。

  • 新增 @load 指令,支持动态加载扩展模块(如 JSON 解析、数学函数扩展)。

  • 改进 FPAT 变量(字段模式),更灵活地定义非分隔符的字段格式(适合处理不规则 CSV)。

性能优化:优化了正则表达式引擎,复杂模式匹配速度提升约 15%。

三、5.x 系列与旧版本(4.x)的核心差异

  1. 正则功能:5.x 支持命名捕获组、Unicode 更高版本标准,正则表达式能力更接近现代编程语言。2. 安全性:新增沙箱模式,适合在多用户环境中运行不可信的 awk 脚本。3. 扩展性:支持动态加载模块,可扩展功能(如处理 JSON、XML 等格式)。4. 性能:数组操作和字符串处理效率显著提升,更适合大数据量场景。

四、升级方法

源码编译(推荐,获取最新版本):wget https://ftp.gnu.org/gnu/gawk/gawk-5.3.0.tar.gz tar -zxvf gawk-5.3.0.tar.gz cd gawk-5.3.0 ./configure && make && sudo make install

包管理器安装(适用于主流 Linux 发行版,版本可能滞后):

  • Ubuntu/Debian:sudo apt update && sudo apt install gawk

  • CentOS/RHEL:sudo yum install gawk

  • macOS(Homebrew):brew install gawk

验证版本:gawk –version # 输出 “GNU Awk 5.3.0” 即表示升级成功

五、使用建议

  • 对于日常文本处理(如日志分析、数据清洗),gawk 5.x 的新特性(如命名捕获组、沙箱模式)能显著提升效率和安全性。

  • 若脚本需要兼容旧系统(如 CentOS 7 自带 gawk 4.0.2),需注意避免使用 5.x 独有的特性(如命名捕获组)。

  • 官方文档(info gawk 或在线手册)是学习新特性的最佳资源,尤其推荐关注正则增强和扩展模块部分。

通过升级到最新版本,可充分利用 AWK 在文本处理中的现代特性,适应更复杂的场景需求。

截至 2024 年,GNU AWK 的最新稳定版本是 gawk 5.3.0(2023 年 10 月发布)。

二、主要版本升级内容(5.x 系列)

1. gawk 5.3.0(2023 年 10 月)

  • 正则表达式增强:支持 Unicode 15.0 标准,改进了对多字节字符的匹配精度。

  • 性能优化:提升了大型数组遍历和字符串操作的效率,尤其在处理 GB 级文本时速度提升约 10%。

  • 新增函数:strtonum() 函数支持更多进制转换(如二进制 0b 前缀、十六进制 0x 前缀)。

  • 兼容性修复:解决了与最新 Linux 内核版本的兼容性问题,修复了部分边缘场景的内存泄漏。

2. gawk 5.2.0(2022 年 12 月)

  • 安全增强:新增 –sandbox 模式,限制脚本对文件系统的写入和命令执行,防止恶意脚本攻击。

  • 调试功能:改进了 –debug 选项,支持更详细的语法解析日志,便于复杂脚本的调试。

  • 数组功能:支持对数组使用 delete 命令时返回删除的元素数量(n = delete arr)。

  • 国际化:增强了对非英语 locales 的支持,strftime() 函数可正确处理多语言日期格式。

3. gawk 5.1.0(2021 年 5 月)

主要特性更新:

  • 引入 命名捕获组(正则表达式中支持 (?…) 语法,可通过 arr[“name”] 访问捕获内容)。

  • 新增 @load 指令,支持动态加载扩展模块(如 JSON 解析、数学函数扩展)。

  • 改进 FPAT 变量(字段模式),更灵活地定义非分隔符的字段格式(适合处理不规则 CSV)。

性能优化:优化了正则表达式引擎,复杂模式匹配速度提升约 15%。

三、5.x 系列与旧版本(4.x)的核心差异

  1. 正则功能:5.x 支持命名捕获组、Unicode 更高版本标准,正则表达式能力更接近现代编程语言。2. 安全性:新增沙箱模式,适合在多用户环境中运行不可信的 awk 脚本。3. 扩展性:支持动态加载模块,可扩展功能(如处理 JSON、XML 等格式)。4. 性能:数组操作和字符串处理效率显著提升,更适合大数据量场景。

四、升级方法

源码编译(推荐,获取最新版本):wget https://ftp.gnu.org/gnu/gawk/gawk-5.3.0.tar.gz tar -zxvf gawk-5.3.0.tar.gz cd gawk-5.3.0 ./configure && make && sudo make install

包管理器安装(适用于主流 Linux 发行版,版本可能滞后):

  • Ubuntu/Debian:sudo apt update && sudo apt install gawk

  • CentOS/RHEL:sudo yum install gawk

  • macOS(Homebrew):brew install gawk

验证版本:gawk –version # 输出 “GNU Awk 5.3.0” 即表示升级成功

五、使用建议

  • 对于日常文本处理(如日志分析、数据清洗),gawk 5.x 的新特性(如命名捕获组、沙箱模式)能显著提升效率和安全性。

  • 若脚本需要兼容旧系统(如 CentOS 7 自带 gawk 4.0.2),需注意避免使用 5.x 独有的特性(如命名捕获组)。

  • 官方文档(info gawk 或在线手册)是学习新特性的最佳资源,尤其推荐关注正则增强和扩展模块部分。

通过升级到最新版本,可充分利用 AWK 在文本处理中的现代特性,适应更复杂的场景需求。

awk分析Nginx日志

awk分析Nginx日志

以下是一个基于 Nginx 访问日志的 AWK 逻辑组合实战示例,模拟实际工作中“多条件筛选异常请求并统计”的场景,包含基础逻辑组合、高级统计及输出格式化。

场景背景

假设我们有一份 Nginx 访问日志 access.log,格式如下(简化版,字段含义对应):

1
2
$1: 客户端IP  $4: 访问时间  $6: 请求方法  $7: 请求路径  $9: 状态码  $10: 响应大小(字节)  

示例日志内容:

1
2
3
4
5
6
192.168.1.1 - - [10/Oct/2023:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1500  
10.0.0.2 - - [10/Oct/2023:10:01:30 +0800] "POST /login HTTP/1.1" 500 0
192.168.1.3 - - [10/Oct/2023:10:02:15 +0800] "GET /api/data HTTP/1.1" 404 100
10.0.0.2 - - [10/Oct/2023:10:03:00 +0800] "POST /login HTTP/1.1" 500 0
192.168.1.1 - - [10/Oct/2023:10:05:00 +0800] "GET /largefile.zip HTTP/1.1" 200 10485760

需求目标

用 AWK 逻辑组合实现:

筛选两类“异常请求”:

  • 条件A:状态码为 500(服务器错误)且请求方法为 POST 的请求(可能是接口故障)。

  • 条件B:状态码为 200(正常)但响应大小 > 10MB(10485760 字节)的 GET 请求(可能是大文件滥用)。

分别统计两类异常的出现次数,并按 IP 分组统计。

输出格式化报告,包含总次数和 Top IP。

实现代码

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
#!/usr/bin/awk -f  
# 处理 Nginx 日志,筛选并统计异常请求

# 字段对应(根据实际日志格式调整)
# $1: IP $6: 请求方法(如"GET") $9: 状态码 $10: 响应大小

# 逻辑组合筛选异常请求,并统计
{
# 条件A:500错误 + POST请求(提取方法时去除引号,如"POST→POST)
if ($9 == 500 && substr($6, 2, length($6)-2) == "POST") {
typeA_total++ # 总次数+1
typeA_ip[$1]++ # 按IP统计
}
# 条件B:200正常 + GET请求 + 响应大小>10MB(10485760字节)
else if ($9 == 200 && substr($6, 2, length($6)-2) == "GET" && $10 > 10485760) {
typeB_total++ # 总次数+1
typeB_ip[$1]++ # 按IP统计
}
}

# 输出报告(END块汇总结果)
END {
print "===== 异常请求统计报告 ====="
# 输出类型A统计
print "\n1. 服务器错误(500+POST):"
print " 总次数:", typeA_total
print " 主要IP分布:"
for (ip in typeA_ip) {
printf " %-15s 出现 %d 次\n", ip, typeA_ip[ip]
}

# 输出类型B统计
print "\n2. 大文件请求(200+GET+>10MB):"
print " 总次数:", typeB_total
print " 主要IP分布:"
for (ip in typeB_ip) {
printf " %-15s 出现 %d 次\n", ip, typeB_ip[ip]
}
}

执行与输出

保存脚本为 log_analysis.awk,添加执行权限:chmod +x log_analysis.awk

执行:./log_analysis.awk access.log

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
===== 异常请求统计报告 =====  

1. 服务器错误(500+POST):
总次数: 2
主要IP分布:
10.0.0.2 出现 2 次

2. 大文件请求(200+GET+>10MB):
总次数: 1
主要IP分布:
192.168.1.1 出现 1 次

逻辑组合解析

多条件“与”组合:

  • 条件A用 $9 == 500 && 方法==POST,确保同时满足“状态码500”和“POST请求”。

  • 条件B用 $9 == 200 && 方法==GET && 大小>10MB,三重条件筛选大文件请求。

字符串处理与逻辑结合:

  • 用 substr($6, 2, length($6)-2) 提取请求方法(去除日志中方法外的引号,如 “POST→POST),避免因引号导致匹配失败。

分支逻辑(if-else):

  • 用 else if 区分两类异常,避免重复统计(同一行不会同时属于A和B)。

数组统计:

  • 用关联数组 typeA_ip[$1]++ 按IP分组,实现“多维度统计”(既算总数,又看IP分布)。

实际价值

此示例可直接用于生产环境的日志监控:

  • 快速定位接口故障(500+POST)的频发IP,排查是否为恶意请求;

  • 识别大文件滥用(200+GET+大尺寸)的IP,限制其带宽或访问权限;

  • 通过逻辑组合灵活扩展规则(如添加时间范围 $4 ~ /10:00:00/ 筛选特定时段异常)。

awk的逻辑组合

AWK的逻辑组合(多条件判断、分支处理等)凭借其轻量、高效的文本处理能力,在除日志分析外的多个领域都有广泛应用。以下是几个典型典型领域及具体场景,结合逻辑组合的用法说明:

一、数据清洗与格式转换

核心需求:从非结构化/半结构化数据中提取有效信息,按规则过滤、转换格式。逻辑组合应用:用多条件判断筛选符合要求的记录,或按不同格式规则分支处理。

示例:处理 CSV 格式的用户数据

假设有 users.csv(格式:姓名,年龄,性别,注册日期),需:

保留“年龄>18 且 注册日期在2023年后”的有效用户;

对“男性”用户添加标签 [M],对“女性”用户添加标签 [F]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 用逻辑组合筛选+分支处理
awk -F ',' '
NR == 1 {print "姓名,标签,年龄,注册日期"} # 保留表头
NR > 1 {
# 条件1:年龄>18 且 注册日期以2023/2024开头(多条件与)
if ($2 > 18 && ($4 ~ /^2023/ || $4 ~ /^2024/)) {
# 条件2:按性别分支处理(逻辑或+分支)
if ($3 == "男") tag = "[M]";
else if ($3 == "女") tag = "[F]";
else tag = "[未知]";
print $1 "," tag "," $2 "," $4; # 输出转换后的数据
}
}
' users.csv > cleaned_users.csv

二、系统配置与监控

核心需求:分析系统配置文件、监控系统状态,提取关键信息或判断异常。逻辑组合应用:结合正则匹配与条件判断,筛选特定配置项或异常状态。

示例:分析 /etc/passwd 识别特殊用户

需求:找出“可登录(shell 非 /sbin/nologin)且 家目录不在 /home 下”的特权用户。

1
2
3
4
5
6
7
8
# 多条件组合筛选系统用户
awk -F ':' '
# 条件:shell不是nologin,且家目录不以/home开头(逻辑与+非)
$7 != "/sbin/nologin" && !($6 ~ /^\/home/) {
print "特权用户:", $1, "家目录:", $6, "shell:", $7
}
' /etc/passwd

三、报表生成与统计分析

核心需求:从原始数据中按多维度统计(如分类计数、求和、平均值),生成结构化报表。逻辑组合应用:用条件分组统计,结合数组存储多维度结果。

示例:销售数据多维度统计

假设有 sales.txt(格式:地区,产品,销售额),需统计:

“华东地区”的“手机”和“电脑”销售额总和;

其他地区“电脑”销售额超 10000 的记录数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 多条件分组统计
awk -F ',' '
NR > 1 {
# 条件1:华东地区的手机或电脑(逻辑与+或)
if ($1 == "华东" && ($2 == "手机" || $2 == "电脑")) {
east_total += $3;
}
# 条件2:非华东地区且电脑销售额>10000(逻辑与+非)
else if ($1 != "华东" && $2 == "电脑" && $3 > 10000) {
other_high_count++;
}
}
END {
print "华东地区手机+电脑总销售额:", east_total;
print "其他地区电脑销售额超10000的记录数:", other_high_count;
}
' sales.txt

四、文本内容提取与过滤

核心需求:从文档、代码、配置文件中提取符合特定规则的内容(如提取URL、过滤敏感词)。逻辑组合应用:用正则+条件判断精准定位目标内容。

示例:从HTML中提取有效链接

需求:从网页源码中提取“以 https 开头且 域名包含 example”的链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 正则+逻辑组合提取链接
awk '
# 匹配<a>标签中的href,且链接符合https+example条件(正则+逻辑与)
/<a&#91;^>]+href="&#91;^"]+"/ {
# 提取href值(简化处理)
if (match($0, /href="(&#91;^"]+)"/, arr)) {
url = arr&#91;1];
# 条件:以https开头且包含example(逻辑与)
if (url ~ /^https/ && url ~ /example/) {
print url;
}
}
}
' page.html

五、数据库与数据交互辅助

核心需求:处理数据库导出的文本(如SQL结果、CSV备份),进行格式转换或校验。逻辑组合应用:用多条件校验数据合法性,或按数据库格式规则转换。

示例:校验MySQL导出的用户表数据

假设有 users.sql(导出格式:INSERT INTO … (id,name,email) VALUES (1,’a’,‘a@x.com‘);),需校验:

id 为数字且 >0;

email 包含 @ 且 域名不为 test.com。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 多条件校验数据合法性
awk '
/INSERT INTO.*users/ { # 匹配用户表插入语句
# 提取id、email(简化正则)
if (match($0, /VALUES \((&#91;0-9]+),&#91;^,]+,'\''(&#91;^'\'']+)'\'')/, arr)) {
id = arr&#91;1];
email = arr&#91;2];
# 条件1:id>0 且 email含@ 且 域名不是test.com(多条件与)
if (id > 0 && email ~ /@/ && !(email ~ /@test\.com$/)) {
valid++;
} else {
invalid++;
print "无效记录:", $0;
}
}
}
END {
print "有效记录数:", valid, "无效记录数:", invalid;
}
' users.sql

总结

AWK 逻辑组合的核心价值在于**“用简洁的语法实现多条件驱动的文本处理”**,其应用场景覆盖:

  • 数据处理(清洗、转换、统计);

  • 系统管理(配置分析、状态监控);

  • 内容提取(文档、代码、网页);

  • 数据校验(数据库、文件格式)。

只要涉及“按规则筛选/处理文本”的场景,AWK 的逻辑组合都能大幅提升效率,尤其适合中小规模数据的快速处理(无需编写复杂脚本或依赖数据库)。

AWK逻辑组合专题:从基础到实战

awk逻辑组合专题:从基础到实战

一、专题提纲

AWK 逻辑组合专题:从基础到实战

一、专题提纲

  1. 什么是 AWK 逻辑组合?2. 基础逻辑运算符与优先级3. 模式(Pattern)的逻辑组合方式4. 高级逻辑组合技巧5. 典型工作场景实战

二、核心知识点(基础篇)

1. 逻辑组合的本质

AWK 的逻辑组合是通过运算符将多个条件(模式)关联,实现“多条件筛选”或“分支处理”。核心目标:精准定位需要处理的行,并按规则执行对应动作。

2. 基础逻辑运算符

运算符含义示例(条件)说明&&逻辑与$2 > 100 && $3 == “ok”两个条件同时满足才成立``逻辑或!逻辑非!($1 == “admin”)对条件取反(不满足时成立)

3. 模式组合的 3 种基础方式

(1)单个模式块内的多条件组合(同一动作)

用逻辑运算符将多个条件写在同一个模式中,匹配后执行同一动作。

1
2
3
# 示例:筛选“年龄>20 且 性别为male”的行,打印姓名  
awk '$2 > 20 && $3 == "male" {print $1}' student.txt

(2)多个模式块(不同条件对应不同动作)

对不同条件设置独立的 模式 {动作} 块,各自匹配并执行对应操作(类似 if-else if)。

1
2
3
4
5
6
7
# 示例:按分数区间输出评价  
awk '
$2 >= 90 {print $1, "→ 优秀"} # 条件1:90分以上
$2 >= 60 && $2 < 90 {print $1, "→ 及格"} # 条件2:60-89分
$2 < 60 {print $1, "→ 不及格"} # 条件3:60分以下
' scores.txt

(3)范围模式(连续区间匹配)

用 , 分隔两个模式,表示“从第一个模式成立的行开始,到第二个模式成立的行结束”的所有行(包含边界)。

1
2
3
# 示例:提取日志中“&#91;START]”到“&#91;END]”之间的所有行  
awk '/\&#91;START\]/, /\&#91;END\]/ {print}' app.log

4. 优先级与括号的重要性

  • 优先级顺序:!(非) > &&(与) > ||(或)。

  • 复杂逻辑必须用 () 明确分组,避免歧义。# 错误写法(&& 优先级高于 ||,实际等价于 $1==”a” || ($2==”b” && $3>100)) awk ‘$1 == “a” || $2 == “b” && $3 > 100 {print}’ data.txt # 正确写法(用括号明确“a或b”的组合) awk ‘($1 == “a” || $2 == “b”) && $3 > 100 {print}’ data.txt

三、高级应用技巧

1. 逻辑与正则的混合组合

将逻辑运算符与正则表达式结合,实现更灵活的匹配。

1
2
3
# 示例:筛选“包含error且来自192.168网段,或包含warn且状态码为500”的日志行  
awk '(/error/ && $1 ~ /^192\.168\./) || (/warn/ && $9 == 500) {print}' access.log

(~ 表示“匹配正则”,$1 ~ /^192.168./ 即第1字段为192.168网段IP)

2. 条件嵌套(动作块内的逻辑组合)

在动作块({})中用 if-else 嵌套逻辑,实现“先筛选行,再细分处理”。

1
2
3
4
5
6
7
# 示例:先筛选分数>80的行,再按科目细分统计  
awk '$3 > 80 { # 外层:只处理分数>80的行
if ($2 == "math") math++; # 内层:按科目累加
else if ($2 == "english") english++;
}' scores.txt
END {print "数学优秀:", math; print "英语优秀:", english}

3. 短路求值与效率优化

AWK 支持逻辑运算的“短路特性”:

  • A && B:若 A 为假,直接跳过 B(无需计算)。

  • A || B:若 A 为真,直接跳过 B(无需计算)。可利用此特性优化复杂逻辑的执行效率。

1
2
3
# 示例:先判断行是否包含“POST”请求(快速筛选),再判断状态码(复杂判断)  
awk '/POST/ && $9 == 500 {print "POST请求500错误:", $7}' access.log

4. 结合数组的多条件统计

用逻辑组合定义数组的键,实现按“多维度条件”统计。

1
2
3
4
5
6
7
8
9
# 示例:统计“不同IP+不同状态码”的出现次数  
awk '{
# 键为“IP:状态码”,仅统计GET请求且状态码非200的记录
if ($6 == "\"GET" && $9 != 200) {
key = $1 ":" $9;
count&#91;key]++
}
} END {for (k in count) print k, count&#91;k]}' access.log

四、典型工作场景实战

1. 日志分析:错误分类与统计

场景:从 Nginx 日志中统计“404错误(来自移动端)”和“500错误(来自PC端)”的数量。

1
2
3
4
5
6
7
8
9
10
11
# Nginx日志格式(简化):$1=IP,$12=设备类型,$9=状态码  
awk '
# 条件1:404错误且设备为移动端
$9 == 404 && $12 ~ /Mobile/ {count_404_mobile++}
# 条件2:500错误且设备为PC端
$9 == 500 && $12 ~ /PC/ {count_500_pc++}
END {
print "移动端404次数:", count_404_mobile;
print "PC端500次数:", count_500_pc
}' access.log

2. 数据清洗:多条件过滤无效数据

场景:处理用户数据(user.txt,格式:姓名 年龄 注册时间),筛选“年龄>18、注册时间在2023年之后”的有效用户。

1
2
3
4
5
awk '  
# 年龄>18 且 注册时间($3)以2023/2024开头(假设格式YYYY-MM-DD)
$2 > 18 && ($3 ~ /^2023/ || $3 ~ /^2024/) {print}
' user.txt > valid_users.txt

3. 系统配置分析:用户权限筛选

场景:从 /etc/passwd 中筛选“可登录(shell非/sbin/nologin)且家目录在/home下”的用户。

1
2
3
4
5
awk -F ':' '  
# 分隔符为:,$7=shell,$6=家目录
$7 != "/sbin/nologin" && $6 ~ /^\/home/ {print $1, $6}
' /etc/passwd

4. 报表生成:多维度数据汇总

场景:从销售数据(sales.csv,格式:日期,产品,销售额)中,统计“产品A在2023年10月销售额>1000”的日期。

1
2
3
4
5
6
awk -F ',' '  
NR > 1 && $2 == "A" && $1 ~ /^2023-10/ && $3 > 1000 {
print $1, "销售额:", $3
}
' sales.csv > report.txt

五、总结

  • 基础核心:掌握 &&/||/! 的用法,明确优先级,善用括号。

  • 高级技巧:结合正则、数组、条件嵌套,利用短路特性优化效率。

  • 实战关键:根据场景选择“单模式多条件”或“多模式块”,优先用逻辑组合减少代码量。

通过多练习日志分析、数据清洗等场景,可快速掌握逻辑组合的精髓,提升 AWK 处理复杂文本的效率。

ls与du统计占用空间大小的区别

ls与du统计占用空间大小的区别

ls 命令显示的文件大小是通过读取文件系统的元数据(metadata)获取的,具体来说是读取文件的 inode 信息 中的大小字段。以下是详细解释:

一、获取文件大小的底层原理

文件系统存储元数据每个文件在 Linux 系统中都对应一个 inode(索引节点),其中记录了文件的关键属性:

  • 文件大小(字节数)

  • 创建/修改/访问时间

  • 权限、所有者

  • 数据块在磁盘上的存储位置等

ls 命令通过系统调用(如 stat() 或 lstat())读取这些元数据,从而获取文件大小。(https://www.calcguide.tech/2025/08/20/ls与du统计占用空间大小的区别/)

ls 命令的实现逻辑当执行 ls -l 或 ls -s 时,ls 会:

  • 遍历目标目录下的所有文件/目录

  • 对每个文件调用 stat() 系统调用,获取 inode 中的 st_size 字段(文件大小,单位字节)

  • 根据用户指定的格式(如 -h 显示人类可读单位,-k 显示 KB 等)转换并展示大小

二、不同 ls 选项的文件大小显示

ls 提供多种选项控制文件大小的显示方式,本质都是对 st_size 字段的转换:

选项含义示例(1024字节文件)默认(无)显示字节数(st_size 原始值)1024-h人类可读格式(自动转换为 K/M/G)1.0K-k以 KB(1024字节)为单位1-l长格式中显示字节数(同默认)1024-s显示磁盘占用块数(每个块默认512字节)2(1024/512=2块)

三、特殊情况说明

目录的大小ls -l 显示的目录大小通常是固定值(如 4096 字节),这是目录本身的 inode 元数据占用的磁盘块大小,而非目录内所有文件的总大小。若要查看目录内所有文件的总大小,需用 du -sh 目录名。

符号链接(软链接)ls -l 显示的是软链接本身的大小(通常是链接目标路径的字符串长度),而非目标文件的大小。若要查看目标文件大小,需用 ls -lL 链接名(-L 跟随链接)。

稀疏文件(Sparse File)稀疏文件的 ls 显示大小(st_size)可能远大于实际磁盘占用(如虚拟机镜像文件),因为其未写入数据的区域不占用磁盘空间。

四、验证:手动查看文件大小元数据

可以通过 stat 命令直接查看文件的元数据,对比 ls 的结果:

1
2
3
4
5
6
7
# 查看文件的元数据
stat example.txt

# 输出中与大小相关的字段:
# Size: 1024 (文件逻辑大小,同 ls 显示的字节数)
# Blocks: 8 (实际占用磁盘块数,同 ls -s 显示的值)

总结:ls 的文件大小来自文件系统的 inode 元数据,通过系统调用读取后转换为用户指定的格式,不同选项仅影响显示单位,不改变原始大小值。

sendmmsg系统调用及示例

sendmmsg 函数详解

  1. 函数介绍

sendmmsg 是Linux 2.6.39引入的高效批量发送消息系统调用。它是 sendmsg 的批量版本,允许应用程序在单次系统调用中发送多个消息,显著减少了系统调用开销,特别适用于高吞吐量的网络服务器和实时应用。

  1. 函数原型
1
2
3
4
5
#define _GNU_SOURCE
#include <sys/socket.h>
int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
int flags);

  1. 功能

sendmmsg 允许向套接字批量发送多个消息,每个消息可以包含数据和控制信息。它支持分散缓冲区发送、地址信息指定、控制数据发送等功能,是构建高性能网络应用的关键工具。

  1. 参数
  • int sockfd: 套接字文件描述符

  • *struct mmsghdr msgvec: 消息向量数组,描述多个发送消息

  • unsigned int vlen: 消息向量数组的长度(最大可发送的消息数)

  • int flags: 发送标志,与sendmsg相同

  1. 返回值
  • 成功: 返回实际发送的消息数量

  • 失败: 返回-1,并设置errno

  1. 相似函数,或关联函数
  • sendmsg: 单消息发送函数

  • send: 基本发送函数

  • recvmmsg: 对应的批量接收函数

  • writev: 分散缓冲区写入函数

  1. 示例代码

示例1:基础sendmmsg使用

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
* 演示sendmmsg的基础使用方法
*/
int demo_sendmmsg_basic() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
struct mmsghdr msgvec&#91;3];
struct iovec iov&#91;3]&#91;1];
char *messages&#91;3] = {
"First message from sendmmsg",
"Second message from sendmmsg",
"Third message from sendmmsg"
};
int messages_sent;

printf("=== sendmmsg 基础使用示例 ===\n");

// 创建TCP服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("创建服务器套接字失败");
return -1;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);

// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server_fd);
return -1;
}

// 监听连接
if (listen(server_fd, 1) == -1) {
perror("监听失败");
close(server_fd);
return -1;
}

printf("服务器监听在端口 8080\n");

// 启动客户端连接
if (fork() == 0) {
// 客户端代码
sleep(1); // 等待服务器启动
client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;

memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(client_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
printf("客户端连接成功\n");

// 接收服务器发送的消息
char buffer&#91;256];
for (int i = 0; i < 3; i++) {
ssize_t bytes = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (bytes > 0) {
buffer&#91;bytes] = '\0';
printf("客户端接收到消息 %d: %s\n", i + 1, buffer);
}
}
}

close(client_fd);
exit(0);
}

// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受连接失败");
close(server_fd);
return -1;
}

printf("客户端连接来自: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 准备批量发送消息结构
memset(msgvec, 0, sizeof(msgvec));

for (int i = 0; i < 3; i++) {
// 设置每个消息的缓冲区
iov&#91;i]&#91;0].iov_base = messages&#91;i];
iov&#91;i]&#91;0].iov_len = strlen(messages&#91;i]);

// 设置消息头
msgvec&#91;i].msg_hdr.msg_iov = iov&#91;i];
msgvec&#91;i].msg_hdr.msg_iovlen = 1;
msgvec&#91;i].msg_hdr.msg_name = NULL; // TCP不需要目标地址
msgvec&#91;i].msg_hdr.msg_namelen = 0;
}

printf("准备批量发送3个消息...\n");

// 批量发送消息
messages_sent = sendmmsg(client_fd, msgvec, 3, 0);

if (messages_sent == -1) {
perror("sendmmsg 失败");
close(client_fd);
close(server_fd);
return -1;
}

printf("成功发送 %d 个消息:\n", messages_sent);

// 显示发送结果
for (int i = 0; i < messages_sent; i++) {
printf(" 消息 %d: 发送了 %u 字节\n", i + 1, msgvec&#91;i].msg_len);
}

close(client_fd);
close(server_fd);

// 等待客户端结束
int status;
wait(&status);

return 0;
}

int main() {
return demo_sendmmsg_basic();
}

示例2:UDP批量发送

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

/**
* 演示UDP批量发送消息
*/
int demo_udp_batch_send() {
int client_fd;
struct sockaddr_in server_addr;
struct mmsghdr msgvec&#91;5];
struct iovec iov&#91;5]&#91;2]; // 每个消息使用2个缓冲区
char *message_parts&#91;5]&#91;2] = {
{"UDP ", "Message 1"},
{"UDP ", "Message 2"},
{"UDP ", "Message 3"},
{"UDP ", "Message 4"},
{"UDP ", "Message 5"}
};
int messages_sent;

printf("=== UDP批量发送示例 ===\n");

// 创建UDP客户端套接字
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (client_fd == -1) {
perror("创建UDP套接字失败");
return -1;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8081);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

printf("UDP客户端准备向 127.0.0.1:8081 发送消息\n");

// 准备批量发送结构(使用分散缓冲区)
memset(msgvec, 0, sizeof(msgvec));

for (int i = 0; i < 5; i++) {
// 设置分散缓冲区
iov&#91;i]&#91;0].iov_base = message_parts&#91;i]&#91;0];
iov&#91;i]&#91;0].iov_len = strlen(message_parts&#91;i]&#91;0]);
iov&#91;i]&#91;1].iov_base = message_parts&#91;i]&#91;1];
iov&#91;i]&#91;1].iov_len = strlen(message_parts&#91;i]&#91;1]);

// 设置消息头
msgvec&#91;i].msg_hdr.msg_iov = iov&#91;i];
msgvec&#91;i].msg_hdr.msg_iovlen = 2;
msgvec&#91;i].msg_hdr.msg_name = &server_addr;
msgvec&#91;i].msg_hdr.msg_namelen = sizeof(server_addr);
}

printf("准备发送5个UDP消息(每个消息使用分散缓冲区)...\n");

// 批量发送UDP消息
messages_sent = sendmmsg(client_fd, msgvec, 5, 0);

if (messages_sent == -1) {
perror("sendmmsg 失败");
close(client_fd);
return -1;
}

printf("成功发送 %d 个UDP消息:\n", messages_sent);

// 显示发送统计
size_t total_bytes = 0;
for (int i = 0; i < messages_sent; i++) {
printf(" 消息 %d: %u 字节\n", i + 1, msgvec&#91;i].msg_len);
total_bytes += msgvec&#91;i].msg_len;
}

printf("总发送字节数: %zu\n", total_bytes);

close(client_fd);

return 0;
}

/**
* UDP服务器用于接收批量消息
*/
int udp_server_receive() {
int server_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer&#91;256];
ssize_t bytes_received;
int message_count = 0;

server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd == -1) {
perror("创建UDP服务器套接字失败");
return -1;
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8081);

if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定UDP套接字失败");
close(server_fd);
return -1;
}

printf("UDP服务器监听在端口 8081\n");
printf("等待接收消息(10秒超时)...\n");

// 设置接收超时
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

// 接收消息
while (message_count < 10) {
bytes_received = recvfrom(server_fd, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("接收超时\n");
break;
}
perror("接收消息失败");
break;
}

buffer&#91;bytes_received] = '\0';
printf("接收到消息 %d: %s (来自 %s:%d)\n",
++message_count, buffer,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}

close(server_fd);
return 0;
}

int main() {
// 启动UDP服务器
if (fork() == 0) {
sleep(1); // 等待客户端准备
return udp_server_receive();
}

// 执行UDP批量发送
sleep(1);
int result = demo_udp_batch_send();

// 等待服务器结束
int status;
wait(&status);

return result;
}

示例3:高性能网络服务器

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <time.h>

/**
* 高性能服务器结构
*/
typedef struct {
int server_fd;
int port;
struct pollfd *clients;
int max_clients;
int client_count;
unsigned long messages_sent;
unsigned long bytes_sent;
} high_perf_server_t;

/**
* 初始化高性能服务器
*/
int server_init(high_perf_server_t *server, int port, int max_clients) {
struct sockaddr_in server_addr;

memset(server, 0, sizeof(high_perf_server_t));
server->port = port;
server->max_clients = max_clients;
server->client_count = 0;
server->messages_sent = 0;
server->bytes_sent = 0;

// 分配客户端数组
server->clients = calloc(max_clients + 1, sizeof(struct pollfd));
if (!server->clients) {
perror("分配客户端数组失败");
return -1;
}

// 创建服务器套接字
server->server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server->server_fd == -1) {
perror("创建服务器套接字失败");
free(server->clients);
return -1;
}

// 设置套接字选项
int opt = 1;
if (setsockopt(server->server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
perror("设置套接字选项失败");
close(server->server_fd);
free(server->clients);
return -1;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);

// 绑定套接字
if (bind(server->server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server->server_fd);
free(server->clients);
return -1;
}

// 监听连接
if (listen(server->server_fd, 10) == -1) {
perror("监听失败");
close(server->server_fd);
free(server->clients);
return -1;
}

// 设置服务器套接字为poll监听
server->clients&#91;0].fd = server->server_fd;
server->clients&#91;0].events = POLLIN;

printf("高性能服务器初始化完成,监听端口 %d\n", port);
return 0;
}

/**
* 接受新客户端连接
*/
int server_accept_client(high_perf_server_t *server) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd;

client_fd = accept(server->server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受连接失败");
return -1;
}

if (server->client_count >= server->max_clients) {
printf("客户端数量已达上限,拒绝连接\n");
close(client_fd);
return -1;
}

// 添加到客户端数组
int index = server->client_count + 1;
server->clients&#91;index].fd = client_fd;
server->clients&#91;index].events = POLLOUT; // 准备发送数据
server->client_count++;

printf("新客户端连接: %s:%d (fd=%d)\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);

return 0;
}

/**
* 使用sendmmsg向客户端批量发送消息
*/
int server_send_batch_messages(high_perf_server_t *server, int client_index) {
int client_fd = server->clients&#91;client_index].fd;
const int BATCH_SIZE = 8; // 每次批量发送8个消息
struct mmsghdr msgvec&#91;BATCH_SIZE];
struct iovec iov&#91;BATCH_SIZE]&#91;1];
char messages&#91;BATCH_SIZE]&#91;128];
int messages_to_send = 0;

// 准备批量发送消息
memset(msgvec, 0, sizeof(msgvec));

for (int i = 0; i < BATCH_SIZE; i++) {
// 构造消息内容
snprintf(messages&#91;i], sizeof(messages&#91;i]),
"Server Message %lu: Batch %d, Index %d",
server->messages_sent + i + 1,
(int)(server->messages_sent / BATCH_SIZE) + 1, i + 1);

// 设置缓冲区
iov&#91;i]&#91;0].iov_base = messages&#91;i];
iov&#91;i]&#91;0].iov_len = strlen(messages&#91;i]);

// 设置消息头
msgvec&#91;i].msg_hdr.msg_iov = iov&#91;i];
msgvec&#91;i].msg_hdr.msg_iovlen = 1;
msgvec&#91;i].msg_hdr.msg_name = NULL;
msgvec&#91;i].msg_hdr.msg_namelen = 0;

messages_to_send++;
}

// 批量发送消息
int messages_sent = sendmmsg(client_fd, msgvec, messages_to_send, MSG_NOSIGNAL);

if (messages_sent == -1) {
if (errno == EPIPE || errno == ECONNRESET) {
printf("客户端 %d 连接断开\n", client_fd);
return -1;
}
perror("sendmmsg 失败");
return -1;
}

// 更新统计信息
server->messages_sent += messages_sent;
for (int i = 0; i < messages_sent; i++) {
server->bytes_sent += msgvec&#91;i].msg_len;
}

printf("向客户端 %d 批量发送 %d 个消息\n", client_fd, messages_sent);

return 0;
}

/**
* 运行服务器主循环
*/
int server_run(high_perf_server_t *server) {
printf("服务器开始运行,等待客户端连接...\n");

time_t start_time = time(NULL);

while (difftime(time(NULL), start_time) < 30) { // 运行30秒
// 使用poll等待事件
int nfds = server->client_count + 1;
int activity = poll(server->clients, nfds, 1000); // 1秒超时

if (activity == -1) {
if (errno == EINTR) continue; // 被信号中断
perror("poll 失败");
break;
}

if (activity == 0) {
// 超时,继续循环
continue;
}

// 检查服务器套接字(新连接)
if (server->clients&#91;0].revents & POLLIN) {
server_accept_client(server);
activity--;
}

// 检查客户端套接字(准备发送数据)
for (int i = 1; i <= server->client_count && activity > 0; i++) {
if (server->clients&#91;i].revents & POLLOUT) {
if (server_send_batch_messages(server, i) == -1) {
// 客户端断开连接,移除客户端
close(server->clients&#91;i].fd);
// 将最后一个客户端移到当前位置
if (i < server->client_count) {
server->clients&#91;i] = server->clients&#91;server->client_count];
}
server->client_count--;
i--; // 重新检查当前位置
}
activity--;
}
}
}

return 0;
}

/**
* 清理服务器资源
*/
void server_cleanup(high_perf_server_t *server) {
// 关闭所有客户端连接
for (int i = 1; i <= server->client_count; i++) {
close(server->clients&#91;i].fd);
}

// 关闭服务器套接字
if (server->server_fd != -1) {
close(server->server_fd);
}

// 释放内存
if (server->clients) {
free(server->clients);
}

printf("服务器资源清理完成\n");
printf("统计信息:\n");
printf(" 总发送消息数: %lu\n", server->messages_sent);
printf(" 总发送字节数: %lu\n", server->bytes_sent);
if (server->messages_sent > 0) {
printf(" 平均消息大小: %.2f 字节\n",
(double)server->bytes_sent / server->messages_sent);
}
}

/**
* 演示高性能网络服务器
*/
int demo_high_performance_server() {
high_perf_server_t server;

printf("=== 高性能网络服务器示例 ===\n");

// 初始化服务器
if (server_init(&server, 8082, 10) != 0) {
return -1;
}

// 启动测试客户端
if (fork() == 0) {
sleep(2); // 等待服务器启动

// 创建多个并发客户端
for (int i = 0; i < 3; i++) {
if (fork() == 0) {
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;

memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8082);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
printf("客户端 %d 连接成功\n", i + 1);

// 接收服务器发送的消息
char buffer&#91;256];
for (int msg = 0; msg < 20; msg++) {
ssize_t bytes = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
if (bytes > 0) {
buffer&#91;bytes] = '\0';
printf("客户端 %d 接收消息: %s\n", i + 1, buffer);
} else if (bytes == 0) {
printf("客户端 %d 连接关闭\n", i + 1);
break;
}
}
}

close(client_sock);
exit(0);
}
}

// 等待所有客户端完成
for (int i = 0; i < 3; i++) {
int status;
wait(&status);
}

exit(0);
}

// 运行服务器
server_run(&server);

// 清理资源
server_cleanup(&server);

// 等待测试客户端结束
int status;
wait(&status);

return 0;
}

int main() {
return demo_high_performance_server();
}

示例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
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <signal.h>

/**
* 实时数据流结构
*/
typedef struct {
int sockfd;
struct sockaddr_in dest_addr;
unsigned long packets_sent;
unsigned long bytes_sent;
time_t start_time;
volatile int running;
} data_stream_t;

// 全局变量用于信号处理
static data_stream_t *g_stream = NULL;

/**
* 信号处理函数
*/
void signal_handler(int sig) {
if (g_stream) {
g_stream->running = 0;
printf("\n收到信号 %d,准备停止数据流...\n", sig);
}
}

/**
* 初始化数据流发送器
*/
int stream_init(data_stream_t *stream, const char *dest_ip, int dest_port) {
memset(stream, 0, sizeof(data_stream_t));
stream->start_time = time(NULL);
stream->running = 1;

// 设置全局指针用于信号处理
g_stream = stream;

// 注册信号处理
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);

// 创建UDP套接字
stream->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (stream->sockfd == -1) {
perror("创建UDP套接字失败");
return -1;
}

// 设置目标地址
memset(&stream->dest_addr, 0, sizeof(stream->dest_addr));
stream->dest_addr.sin_family = AF_INET;
stream->dest_addr.sin_port = htons(dest_port);
stream->dest_addr.sin_addr.s_addr = inet_addr(dest_ip);

printf("数据流发送器初始化完成\n");
printf("目标地址: %s:%d\n", dest_ip, dest_port);

return 0;
}

/**
* 发送实时数据包批次
*/
int stream_send_batch(data_stream_t *stream) {
const int BATCH_SIZE = 16; // 每次发送16个数据包
struct mmsghdr msgvec&#91;BATCH_SIZE];
struct iovec iov&#91;BATCH_SIZE]&#91;1];
char packets&#91;BATCH_SIZE]&#91;256];
struct timespec ts;

// 准备批量发送结构
memset(msgvec, 0, sizeof(msgvec));

clock_gettime(CLOCK_REALTIME, &ts);

for (int i = 0; i < BATCH_SIZE; i++) {
// 构造实时数据包
snprintf(packets&#91;i], sizeof(packets&#91;i]),
"REALTIME_DATA:%lu.%09ld:Packet_%lu:Value_%d",
ts.tv_sec, ts.tv_nsec,
stream->packets_sent + i + 1,
rand() % 1000);

// 设置缓冲区
iov&#91;i]&#91;0].iov_base = packets&#91;i];
iov&#91;i]&#91;0].iov_len = strlen(packets&#91;i]);

// 设置消息头
msgvec&#91;i].msg_hdr.msg_iov = iov&#91;i];
msgvec&#91;i].msg_hdr.msg_iovlen = 1;
msgvec&#91;i].msg_hdr.msg_name = &stream->dest_addr;
msgvec&#91;i].msg_hdr.msg_namelen = sizeof(stream->dest_addr);
}

// 批量发送数据包
int packets_sent = sendmmsg(stream->sockfd, msgvec, BATCH_SIZE, 0);

if (packets_sent == -1) {
perror("sendmmsg 发送数据包失败");
return -1;
}

// 更新统计信息
stream->packets_sent += packets_sent;
for (int i = 0; i < packets_sent; i++) {
stream->bytes_sent += msgvec&#91;i].msg_len;
}

return packets_sent;
}

/**
* 显示实时统计信息
*/
void stream_show_stats(data_stream_t *stream) {
time_t current_time = time(NULL);
double uptime = difftime(current_time, stream->start_time);

if (uptime > 0) {
double packets_per_sec = stream->packets_sent / uptime;
double bytes_per_sec = stream->bytes_sent / uptime;

printf("\r运行时间: %.0fs | 数据包: %lu | 字节: %lu | "
"速率: %.0f包/s %.2fMB/s",
uptime, stream->packets_sent, stream->bytes_sent,
packets_per_sec, bytes_per_sec / (1024 * 1024));
fflush(stdout);
}
}

/**
* 运行数据流发送器
*/
int stream_run(data_stream_t *stream) {
printf("数据流发送器开始运行,按 Ctrl+C 停止\n");

time_t last_stats_time = time(NULL);
int batch_count = 0;

while (stream->running) {
int result = stream_send_batch(stream);
if (result == -1) {
break;
}

batch_count++;

// 定期显示统计信息
time_t current_time = time(NULL);
if (current_time - last_stats_time >= 1) {
stream_show_stats(stream);
last_stats_time = current_time;
}

// 控制发送速率(每秒约1000个数据包)
if (batch_count % 64 == 0) {
usleep(10000); // 10ms延迟
}
}

printf("\n数据流发送器停止\n");
return 0;
}

/**
* 演示实时数据流发送
*/
int demo_real_time_data_stream() {
data_stream_t stream;

printf("=== 实时数据流发送示例 ===\n");

// 初始化数据流发送器
if (stream_init(&stream, "127.0.0.1", 8083) != 0) {
return -1;
}

// 启动数据接收器
if (fork() == 0) {
int server_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer&#91;512];
ssize_t bytes_received;
int packet_count = 0;

server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd == -1) {
perror("创建接收器套接字失败");
exit(1);
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8083);

if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定接收器套接字失败");
close(server_fd);
exit(1);
}

printf("数据接收器启动,监听端口 8083\n");

// 设置接收超时
struct timeval timeout;
timeout.tv_sec = 30;
timeout.tv_usec = 0;
setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

// 接收数据包
while (packet_count < 1000) {
bytes_received = recvfrom(server_fd, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("接收超时\n");
break;
}
perror("接收数据包失败");
break;
}

buffer&#91;bytes_received] = '\0';
packet_count++;

if (packet_count % 100 == 0) {
printf("接收器: 接收到 %d 个数据包\n", packet_count);
}
}

printf("接收器: 总共接收到 %d 个数据包\n", packet_count);
close(server_fd);
exit(0);
}

// 运行数据流发送器
srand(time(NULL));
stream_run(&stream);

// 显示最终统计
printf("\n\n最终统计:\n");
printf(" 总发送数据包: %lu\n", stream.packets_sent);
printf(" 总发送字节数: %lu\n", stream.bytes_sent);
printf(" 平均包大小: %.2f 字节\n",
stream.packets_sent > 0 ? (double)stream.bytes_sent / stream.packets_sent : 0);

// 清理资源
close(stream.sockfd);

// 等待接收器结束
int status;
wait(&status);

return 0;
}

int main() {
return demo_real_time_data_stream();
}

示例5:性能对比测试

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>

/**
* 性能测试结构
*/
typedef struct {
const char *name;
unsigned long messages_sent;
unsigned long bytes_sent;
struct timeval start_time;
struct timeval end_time;
} perf_test_t;

/**
* 使用传统sendmsg进行测试
*/
int test_sendmsg_performance(int sockfd, struct sockaddr_in *dest_addr,
int message_count, const char *message) {
struct msghdr msg;
struct iovec iov&#91;1];
int sent_count = 0;

// 准备发送结构
memset(&msg, 0, sizeof(msg));
iov&#91;0].iov_base = (void*)message;
iov&#91;0].iov_len = strlen(message);

msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = dest_addr;
msg.msg_namelen = sizeof(*dest_addr);

// 逐个发送消息
while (sent_count < message_count) {
if (sendmsg(sockfd, &msg, 0) == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("sendmsg 失败");
break;
}
} else {
sent_count++;
}
}

return sent_count;
}

/**
* 使用sendmmsg进行测试
*/
int test_sendmmsg_performance(int sockfd, struct sockaddr_in *dest_addr,
int message_count, const char *message) {
const int BATCH_SIZE = 32;
struct mmsghdr msgvec&#91;BATCH_SIZE];
struct iovec iov&#91;BATCH_SIZE]&#91;1];
char *messages&#91;BATCH_SIZE];
int total_sent = 0;
int messages_sent;

// 准备批量发送结构
memset(msgvec, 0, sizeof(msgvec));

for (int i = 0; i < BATCH_SIZE; i++) {
messages&#91;i] = strdup(message);
if (!messages&#91;i]) {
perror("分配消息缓冲区失败");
return total_sent;
}

iov&#91;i]&#91;0].iov_base = messages&#91;i];
iov&#91;i]&#91;0].iov_len = strlen(message);

msgvec&#91;i].msg_hdr.msg_iov = iov&#91;i];
msgvec&#91;i].msg_hdr.msg_iovlen = 1;
msgvec&#91;i].msg_hdr.msg_name = dest_addr;
msgvec&#91;i].msg_hdr.msg_namelen = sizeof(*dest_addr);
}

// 批量发送消息
while (total_sent < message_count) {
int to_send = (message_count - total_sent < BATCH_SIZE) ?
message_count - total_sent : BATCH_SIZE;

messages_sent = sendmmsg(sockfd, msgvec, to_send, 0);

if (messages_sent == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("sendmmsg 失败");
break;
}
} else {
total_sent += messages_sent;
}
}

// 释放内存
for (int i = 0; i < BATCH_SIZE; i++) {
free(messages&#91;i]);
}

return total_sent;
}

/**
* UDP性能测试服务器
*/
int perf_test_server(int port) {
int server_fd;
struct sockaddr_in server_addr;

server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd == -1) {
perror("创建UDP服务器套接字失败");
return -1;
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);

if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定服务器套接字失败");
close(server_fd);
return -1;
}

printf("性能测试服务器启动,监听端口 %d\n", port);
return server_fd;
}

/**
* 演示性能对比测试
*/
int demo_performance_comparison() {
int server_fd, client_fd;
struct sockaddr_in server_addr, dest_addr;
const int SERVER_PORT = 8084;
const int MESSAGE_COUNT = 10000;
const char *test_message = "Performance test message for sendmmsg vs sendmsg comparison";
perf_test_t tests&#91;2];

printf("=== sendmmsg vs sendmsg 性能对比测试 ===\n");

// 初始化测试结构
tests&#91;0].name = "sendmsg";
tests&#91;0].messages_sent = 0;
tests&#91;0].bytes_sent = 0;

tests&#91;1].name = "sendmmsg";
tests&#91;1].messages_sent = 0;
tests&#91;1].bytes_sent = 0;

// 启动服务器
server_fd = perf_test_server(SERVER_PORT);
if (server_fd == -1) {
return -1;
}

// 创建客户端套接字
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (client_fd == -1) {
perror("创建客户端套接字失败");
close(server_fd);
return -1;
}

// 设置目标地址
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(SERVER_PORT);
dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 启动服务器接收线程
if (fork() == 0) {
char buffer&#91;512];
int received_count = 0;

printf("服务器开始接收测试消息...\n");

// 设置接收超时
struct timeval timeout;
timeout.tv_sec = 30;
timeout.tv_usec = 0;
setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

while (received_count < MESSAGE_COUNT * 2) { // 两次测试
ssize_t bytes = recv(server_fd, buffer, sizeof(buffer) - 1, 0);
if (bytes > 0) {
received_count++;
if (received_count % 1000 == 0) {
printf("服务器已接收 %d 个消息\n", received_count);
}
} else if (bytes == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("服务器接收超时\n");
break;
}
}
}

printf("服务器接收完成,总共接收 %d 个消息\n", received_count);
close(server_fd);
exit(0);
}

// 等待服务器启动
sleep(1);

// 测试1: 使用sendmsg
printf("\n测试1: 使用传统sendmsg发送 %d 个消息...\n", MESSAGE_COUNT);
gettimeofday(&tests&#91;0].start_time, NULL);

tests&#91;0].messages_sent = test_sendmsg_performance(client_fd, &dest_addr,
MESSAGE_COUNT, test_message);
tests&#91;0].bytes_sent = tests&#91;0].messages_sent * strlen(test_message);

gettimeofday(&tests&#91;0].end_time, NULL);

printf("sendmsg测试完成: 发送 %lu 个消息\n", tests&#91;0].messages_sent);

// 短暂休息
sleep(1);

// 测试2: 使用sendmmsg
printf("\n测试2: 使用sendmmsg发送 %d 个消息...\n", MESSAGE_COUNT);
gettimeofday(&tests&#91;1].start_time, NULL);

tests&#91;1].messages_sent = test_sendmmsg_performance(client_fd, &dest_addr,
MESSAGE_COUNT, test_message);
tests&#91;1].bytes_sent = tests&#91;1].messages_sent * strlen(test_message);

gettimeofday(&tests&#91;1].end_time, NULL);

printf("sendmmsg测试完成: 发送 %lu 个消息\n", tests&#91;1].messages_sent);

// 计算并显示结果
printf("\n=== 性能测试结果 ===\n");

for (int i = 0; i < 2; i++) {
double elapsed_time = (tests&#91;i].end_time.tv_sec - tests&#91;i].start_time.tv_sec) +
(tests&#91;i].end_time.tv_usec - tests&#91;i].start_time.tv_usec) / 1000000.0;

double messages_per_sec = tests&#91;i].messages_sent / elapsed_time;
double bytes_per_sec = tests&#91;i].bytes_sent / elapsed_time;

printf("%s 测试:\n", tests&#91;i].name);
printf(" 发送消息数: %lu\n", tests&#91;i].messages_sent);
printf(" 发送字节数: %lu\n", tests&#91;i].bytes_sent);
printf(" 耗时: %.3f 秒\n", elapsed_time);
printf(" 吞吐量: %.0f 消息/秒 (%.2f MB/s)\n",
messages_per_sec, bytes_per_sec / (1024 * 1024));
printf(" 平均延迟: %.3f 微秒/消息\n",
(elapsed_time * 1000000) / tests&#91;i].messages_sent);
printf("\n");
}

// 计算性能提升
double sendmsg_time = (tests&#91;0].end_time.tv_sec - tests&#91;0].start_time.tv_sec) +
(tests&#91;0].end_time.tv_usec - tests&#91;0].start_time.tv_usec) / 1000000.0;
double sendmmsg_time = (tests&#91;1].end_time.tv_sec - tests&#91;1].start_time.tv_sec) +
(tests&#91;1].end_time.tv_usec - tests&#91;1].start_time.tv_usec) / 1000000.0;

if (sendmsg_time > 0 && sendmmsg_time > 0) {
double improvement = (sendmsg_time - sendmmsg_time) / sendmsg_time * 100;
printf("性能提升: %.1f%%\n", improvement);
}

close(client_fd);

// 等待服务器结束
int status;
wait(&status);

return 0;
}

int main() {
return demo_performance_comparison();
}

sendmmsg 标志参数详解

常用标志:

  • MSG_CONFIRM: 提供路径确认反馈

  • MSG_DONTROUTE: 不使用网关路由

  • MSG_DONTWAIT: 非阻塞操作

  • MSG_EOR: 发送记录结束标记

  • MSG_MORE: 还有更多数据要发送

  • MSG_NOSIGNAL: 发送时不产生SIGPIPE信号

  • MSG_OOB: 发送带外数据

高级标志:

  • MSG_PROXY: SOCKS代理相关

  • MSG_TRYHARD: 尽力发送(已废弃)

使用注意事项

系统要求:

内核版本: 需要Linux 2.6.39或更高版本

glibc版本: 需要支持sendmmsg的glibc版本

编译选项: 需要定义_GNU_SOURCE

性能优化:

批量大小: 根据应用需求选择合适的批量大小

缓冲区管理: 合理分配缓冲区避免内存浪费

错误处理: 妥善处理部分发送的情况

错误处理:

部分发送: 处理实际发送消息数少于请求的情况

连接状态: 检查连接是否正常

资源清理: 及时关闭套接字和释放内存

安全考虑:

缓冲区溢出: 确保消息缓冲区大小正确

输入验证: 验证发送的数据内容

权限检查: 确保有适当的网络访问权限

sendmmsg 优势

1. 性能优势:

  • 减少系统调用: 单次调用发送多个消息

  • 降低上下文切换: 减少用户态和内核态切换

  • 提高吞吐量: 特别适用于小消息高频发送场景

2. 功能优势:

  • 完整功能: 支持sendmsg的所有功能

  • 分散缓冲区: 每个消息可使用多个缓冲区

  • 控制信息: 支持发送辅助控制数据

3. 应用场景:

  • 高并发服务器: Web服务器、游戏服务器

  • 实时系统: 音视频流传输、传感器数据采集

  • 消息队列: 批量消息处理系统

  • 网络协议: 实现自定义网络协议

总结

sendmmsg 是构建高性能网络应用的重要工具,它提供了:

  • 批量消息发送能力,显著减少系统调用开销

  • 与sendmsg相同的完整功能集

  • 更好的性能和可扩展性

  • 适用于高吞吐量的实时应用

通过合理使用 sendmmsg,可以大幅提升网络应用的性能,特别是在需要发送大量小消息的场景中效果显著。在实际应用中,需要注意批量大小的选择、错误处理和系统兼容性等问题。

setfsgid系统调用及示例

我们来深入学习 setfsgid 和 setfsuid 这两个系统调用

1. 函数介绍

在 Linux 系统中,每个进程都有一套与之相关的用户 ID (UID) 和组 ID (GID),比如:

  • 真实用户 ID (Real UID): 登录系统的用户 ID。

  • 有效用户 ID (Effective UID): 决定进程当前拥有哪些权限,用于权限检查。

  • 保存的设置用户 ID (Saved Set-UID): 用于在有效 UID 和真实 UID 之间切换。

通常,文件访问权限检查是基于进程的 有效 UID 和 有效 GID 进行的。

但是,Linux 提供了两个特殊的系统调用:setfsuid 和 setfsgid。

  • setfsuid (Set File System UID): 用来设置或修改进程的 File System UID (文件系统用户 ID)。

  • setfsgid (Set File System GID): 用来设置或修改进程的 File System GID (文件系统组 ID)。

关键点:这两个 ID 只用于文件系统的权限检查!它们不影响其他任何权限检查(比如信号发送权限、进程调度优先级等)。当内核执行文件访问权限检查时,它会使用 文件系统 UID/GID 而不是 有效 UID/GID。

默认情况下:当一个进程启动时,它的 文件系统 UID/GID 会被自动设置为与 有效 UID/GID 相同。

为什么需要它们?(主要场景)一个经典的例子是 NFS (Network File System) 服务器:

NFS 服务器进程通常以 root 权限运行,因此它的 有效 UID 是 0 (root)。

但是,当它代表一个非 root 用户(比如 UID 1000)访问 NFS 文件系统上的文件时,它需要进行权限检查,仿佛是 UID 1000 在访问。

如果只使用 setuid/seteuid 来切换有效 UID,会影响服务器进程的其他权限(比如绑定到特权端口)。

所以,NFS 服务器可以调用 setfsuid(1000),这样在进行文件权限检查时,内核会看到 UID 是 1000,但进程的其他权限(如有效 UID 仍然是 root)保持不变。

简单来说:setfsuid 和 setfsgid 允许进程在进行文件访问权限检查时,“伪装”成另一个用户或组,而不会影响进程的其他特权。

2. 函数原型

1
2
3
4
5
6
7
8
#include <sys/fsuid.h> // 包含函数声明 (在某些系统上可能在 unistd.h 或其他地方)

// 设置文件系统用户 ID
int setfsuid(uid_t fsuid);

// 设置文件系统组 ID
int setfsgid(gid_t fsgid);

3. 功能

分别设置调用进程的文件系统用户 ID (FS-UID) 和文件系统组 ID (FS-GID),仅用于文件系统的访问权限检查。

4. 参数

fsuid:

  • uid_t 类型。

  • 指定要设置的新文件系统用户 ID。

fsgid:

  • gid_t 类型。

  • 指定要设置的新文件系统组 ID。

5. 返回值

这两个函数的返回值比较特殊:

  • 总是返回调用者之前的 文件系统 UID (setfsuid) 或 文件系统 GID (setfsgid)。

  • 无论调用成功与否!

这与其他大多数系统调用不同(它们通常成功返回 0 或文件描述符,失败返回 -1)。这种设计意味着你无法仅通过返回值判断调用是否成功。

那么如何判断是否成功呢?通常的做法是:

先调用 setfsuid/setfsgid。

然后立即再次调用 setfsuid/setfsgid,传入同一个 ID。

如果两次返回的旧 ID 相同,说明第一次调用成功了;如果不同,则说明第一次调用失败。

调用失败的情况:调用通常会成功,但在某些安全模型下或传入无效 ID 时可能会失败。不过,失败的具体条件比较复杂,实践中很少遇到。

6. 相似函数或关联函数

  • setuid / seteuid / setreuid / setresuid: 用于设置进程的真实、有效、保存的用户 ID。这些会影响所有权限检查,而不仅仅是文件系统权限。

  • setgid / setegid / setregid / setresgid: 用于设置进程的组 ID。

  • getuid / geteuid / getgid / getegid: 获取进程的真实和有效 UID/GID。

  • capset / capget: 用于设置和获取进程的能力 (capabilities),这是 Linux 中更细粒度的权限控制机制,可以作为 setuid/setfsuid 的现代替代方案。

  • 文件系统权限检查: 内核在 open, read, write, stat 等系统调用时执行的检查。

7. 示例代码

由于 setfsuid/setfsgid 的使用场景比较特殊(主要是像 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
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid 等
#include <stdio.h>
#include <unistd.h>
#include <sys/fsuid.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

int main() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
uid_t old_fsuid, new_fsuid;
gid_t old_fsgid, new_fsgid;

printf("--- Demonstrating setfsuid and setfsgid ---\n");

// 1. 获取当前进程的各种 ID
if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
perror("getresuid/getresgid");
exit(EXIT_FAILURE);
}

printf("Initial IDs:\n");
printf(" Real UID: %d\n", ruid);
printf(" Effective UID: %d\n", euid);
printf(" Saved UID: %d\n", suid);
printf(" Real GID: %d\n", rgid);
printf(" Effective GID: %d\n", egid);
printf(" Saved GID: %d\n", sgid);
printf(" (By default, FS-UID and FS-GID equal Effective UID/GID)\n");

// 2. 演示 setfsuid
printf("\n--- Testing setfsuid ---\n");
// 获取当前 FS-UID (通过调用 setfsuid 并传入当前 EUID)
old_fsuid = setfsuid(euid);
printf(" Current FS-UID (queried): %d\n", old_fsuid);

// 尝试将 FS-UID 设置为一个不同的值,例如 euid + 1 (如果存在)
// 注意:这只是演示,实际是否有效取决于系统中是否存在该 UID
// 并且权限检查仍然基于文件系统权限
new_fsuid = (euid == 0) ? 1 : euid - 1; // 简单地选择一个不同的 UID
printf(" Attempting to set FS-UID to: %d\n", new_fsuid);

uid_t result1 = setfsuid(new_fsuid);
printf(" setfsuid(%d) returned: %d (should be the old FS-UID: %d)\n", new_fsuid, result1, old_fsuid);

// 验证是否设置成功:再次调用 setfsuid 获取当前值
uid_t result2 = setfsuid(new_fsuid);
printf(" Verifying: setfsuid(%d) again returned: %d\n", new_fsuid, result2);
if (result1 == result2) {
printf(" -> FS-UID was successfully set to %d.\n", new_fsuid);
} else {
printf(" -> FS-UID setting might have failed.\n");
}

// 3. 演示 setfsgid (逻辑同上)
printf("\n--- Testing setfsgid ---\n");
old_fsgid = setfsgid(egid);
printf(" Current FS-GID (queried): %d\n", old_fsgid);

new_fsgid = (egid == 0) ? 1 : egid - 1; // 选择一个不同的 GID
printf(" Attempting to set FS-GID to: %d\n", new_fsgid);

gid_t result3 = setfsgid(new_fsgid);
printf(" setfsgid(%d) returned: %d (should be the old FS-GID: %d)\n", new_fsgid, result3, old_fsgid);

gid_t result4 = setfsgid(new_fsgid);
printf(" Verifying: setfsgid(%d) again returned: %d\n", new_fsgid, result4);
if (result3 == result4) {
printf(" -> FS-GID was successfully set to %d.\n", new_fsgid);
} else {
printf(" -> FS-GID setting might have failed.\n");
}

// 4. 尝试一个实际的文件操作来“感受” FS-UID/GID 的影响
// 注意:这个例子的权限检查结果可能不会明显变化,
// 因为我们没有真正切换到一个权限不同的用户上下文,
// 也没有访问一个严格基于 FS-UID/GID 权限的特殊文件系统。
printf("\n--- Attempting file operation to observe behavior ---\n");
const char *test_file = "test_fsuid_file.txt";
int fd;

// 创建一个测试文件
fd = open(test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "test", 4);
close(fd);
printf(" Created test file '%s'.\n", test_file);
} else {
perror(" Creating test file");
}

// 尝试读取文件 (应该成功,因为我们没有真正改变有效权限)
fd = open(test_file, O_RDONLY);
if (fd != -1) {
printf(" Successfully opened '%s' for reading (as expected).\n", test_file);
close(fd);
} else {
perror(" Opening test file for reading");
}

// 清理测试文件
if (unlink(test_file) == -1) {
perror(" Deleting test file");
} else {
printf(" Deleted test file '%s'.\n", test_file);
}

printf("\n--- Important Notes ---\n");
printf("1. setfsuid/setfsgid primarily affect filesystem permission checks.\n");
printf("2. They do not change effective UID/GID for other operations.\n");
printf("3. Their main use is in system daemons like NFS servers.\n");
printf("4. The return value is the OLD fsid, success is verified by calling again.\n");

return 0;
}

编译和运行:

1
2
3
4
5
6
7
8
9
# 假设代码保存在 fsuid_fsgid_example.c 中
gcc -o fsuid_fsgid_example fsuid_fsgid_example.c

# 运行程序 (普通用户权限即可)
./fsuid_fsgid_example

# 你也可以用 sudo 运行,看看 root 权限下的行为
# sudo ./fsuid_fsgid_example

预期输出 (作为普通用户运行):

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
--- Demonstrating setfsuid and setfsgid ---
Initial IDs:
Real UID: 1000
Effective UID: 1000
Saved UID: 1000
Real GID: 1000
Effective GID: 1000
Saved GID: 1000
(By default, FS-UID and FS-GID equal Effective UID/GID)

--- Testing setfsuid ---
Current FS-UID (queried): 1000
Attempting to set FS-UID to: 999
setfsuid(999) returned: 1000 (should be the old FS-UID: 1000)
Verifying: setfsuid(999) again returned: 999
-> FS-UID was successfully set to 999.

--- Testing setfsgid ---
Current FS-GID (queried): 1000
Attempting to set FS-GID to: 999
setfsgid(999) returned: 1000 (should be the old FS-GID: 1000)
Verifying: setfsgid(999) again returned: 999
-> FS-GID was successfully set to 999.

--- Attempting file operation to observe behavior ---
Created test file 'test_fsuid_file.txt'.
Successfully opened 'test_fsuid_file.txt' for reading (as expected).
Deleted test file 'test_fsuid_file.txt'.

--- Important Notes ---
1. setfsuid/setfsgid primarily affect filesystem permission checks.
2. They do not change effective UID/GID for other operations.
3. Their main use is in system daemons like NFS servers.
4. The return value is the OLD fsid, success is verified by calling again.

总结:对于 Linux 编程新手,setfsuid 和 setfsgid 是比较特殊的系统调用。它们不是日常编程中会频繁使用的。理解它们有助于理解 Linux 权限模型的细节,特别是文件系统权限检查是如何与进程的其他权限分离的。在编写需要代表不同用户执行文件操作的系统服务(如文件服务器)时,它们会很有用。在常规应用程序开发中,使用标准的 setuid/seteuid 或现代的 capabilities 通常更合适。

setfsuid系统调用及示例

我们来深入学习 setfsgid 和 setfsuid 这两个系统调用。(https://www.calcguide.tech/2025/08/19/setfsuid系统调用及示例/)

1. 函数介绍

在 Linux 系统中,每个进程都有一套与之相关的用户 ID (UID) 和组 ID (GID),比如:

  • 真实用户 ID (Real UID): 登录系统的用户 ID。

  • 有效用户 ID (Effective UID): 决定进程当前拥有哪些权限,用于权限检查。

  • 保存的设置用户 ID (Saved Set-UID): 用于在有效 UID 和真实 UID 之间切换。

通常,文件访问权限检查是基于进程的 有效 UID 和 有效 GID 进行的。

但是,Linux 提供了两个特殊的系统调用:setfsuid 和 setfsgid。

  • setfsuid (Set File System UID): 用来设置或修改进程的 File System UID (文件系统用户 ID)。

  • setfsgid (Set File System GID): 用来设置或修改进程的 File System GID (文件系统组 ID)。

关键点:这两个 ID 只用于文件系统的权限检查!它们不影响其他任何权限检查(比如信号发送权限、进程调度优先级等)。当内核执行文件访问权限检查时,它会使用 文件系统 UID/GID 而不是 有效 UID/GID。

默认情况下:当一个进程启动时,它的 文件系统 UID/GID 会被自动设置为与 有效 UID/GID 相同。

为什么需要它们?(主要场景)一个经典的例子是 NFS (Network File System) 服务器:1. NFS 服务器进程通常以 root 权限运行,因此它的 有效 UID 是 0 (root)。2. 但是,当它代表一个非 root 用户(比如 UID 1000)访问 NFS 文件系统上的文件时,它需要进行权限检查,仿佛是 UID 1000 在访问。3. 如果只使用 setuid/seteuid 来切换有效 UID,会影响服务器进程的其他权限(比如绑定到特权端口)。4. 所以,NFS 服务器可以调用 setfsuid(1000),这样在进行文件权限检查时,内核会看到 UID 是 1000,但进程的其他权限(如有效 UID 仍然是 root)保持不变。

简单来说:setfsuid 和 setfsgid 允许进程在进行文件访问权限检查时,“伪装”成另一个用户或组,而不会影响进程的其他特权。

2. 函数原型

1
2
3
4
5
6
7
8
#include <sys/fsuid.h> // 包含函数声明 (在某些系统上可能在 unistd.h 或其他地方)

// 设置文件系统用户 ID
int setfsuid(uid_t fsuid);

// 设置文件系统组 ID
int setfsgid(gid_t fsgid);

3. 功能

分别设置调用进程的文件系统用户 ID (FS-UID) 和文件系统组 ID (FS-GID),仅用于文件系统的访问权限检查。

4. 参数

fsuid:

  • uid_t 类型。

  • 指定要设置的新文件系统用户 ID。

fsgid:

  • gid_t 类型。

  • 指定要设置的新文件系统组 ID。

5. 返回值

这两个函数的返回值比较特殊:

  • 总是返回调用者之前的 文件系统 UID (setfsuid) 或 文件系统 GID (setfsgid)。

  • 无论调用成功与否!

这与其他大多数系统调用不同(它们通常成功返回 0 或文件描述符,失败返回 -1)。这种设计意味着你无法仅通过返回值判断调用是否成功。

那么如何判断是否成功呢?通常的做法是:1. 先调用 setfsuid/setfsgid。2. 然后立即再次调用 setfsuid/setfsgid,传入同一个 ID。3. 如果两次返回的旧 ID 相同,说明第一次调用成功了;如果不同,则说明第一次调用失败。

调用失败的情况:调用通常会成功,但在某些安全模型下或传入无效 ID 时可能会失败。不过,失败的具体条件比较复杂,实践中很少遇到。

6. 相似函数或关联函数

  • setuid / seteuid / setreuid / setresuid: 用于设置进程的真实、有效、保存的用户 ID。这些会影响所有权限检查,而不仅仅是文件系统权限。

  • setgid / setegid / setregid / setresgid: 用于设置进程的组 ID。

  • getuid / geteuid / getgid / getegid: 获取进程的真实和有效 UID/GID。

  • capset / capget: 用于设置和获取进程的能力 (capabilities),这是 Linux 中更细粒度的权限控制机制,可以作为 setuid/setfsuid 的现代替代方案。

  • 文件系统权限检查: 内核在 open, read, write, stat 等系统调用时执行的检查。

7. 示例代码

由于 setfsuid/setfsgid 的使用场景比较特殊(主要是像 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
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid 等
#include <stdio.h>
#include <unistd.h>
#include <sys/fsuid.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

int main() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
uid_t old_fsuid, new_fsuid;
gid_t old_fsgid, new_fsgid;

printf("--- Demonstrating setfsuid and setfsgid ---\n");

// 1. 获取当前进程的各种 ID
if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
perror("getresuid/getresgid");
exit(EXIT_FAILURE);
}

printf("Initial IDs:\n");
printf(" Real UID: %d\n", ruid);
printf(" Effective UID: %d\n", euid);
printf(" Saved UID: %d\n", suid);
printf(" Real GID: %d\n", rgid);
printf(" Effective GID: %d\n", egid);
printf(" Saved GID: %d\n", sgid);
printf(" (By default, FS-UID and FS-GID equal Effective UID/GID)\n");

// 2. 演示 setfsuid
printf("\n--- Testing setfsuid ---\n");
// 获取当前 FS-UID (通过调用 setfsuid 并传入当前 EUID)
old_fsuid = setfsuid(euid);
printf(" Current FS-UID (queried): %d\n", old_fsuid);

// 尝试将 FS-UID 设置为一个不同的值,例如 euid + 1 (如果存在)
// 注意:这只是演示,实际是否有效取决于系统中是否存在该 UID
// 并且权限检查仍然基于文件系统权限
new_fsuid = (euid == 0) ? 1 : euid - 1; // 简单地选择一个不同的 UID
printf(" Attempting to set FS-UID to: %d\n", new_fsuid);

uid_t result1 = setfsuid(new_fsuid);
printf(" setfsuid(%d) returned: %d (should be the old FS-UID: %d)\n", new_fsuid, result1, old_fsuid);

// 验证是否设置成功:再次调用 setfsuid 获取当前值
uid_t result2 = setfsuid(new_fsuid);
printf(" Verifying: setfsuid(%d) again returned: %d\n", new_fsuid, result2);
if (result1 == result2) {
printf(" -> FS-UID was successfully set to %d.\n", new_fsuid);
} else {
printf(" -> FS-UID setting might have failed.\n");
}

// 3. 演示 setfsgid (逻辑同上)
printf("\n--- Testing setfsgid ---\n");
old_fsgid = setfsgid(egid);
printf(" Current FS-GID (queried): %d\n", old_fsgid);

new_fsgid = (egid == 0) ? 1 : egid - 1; // 选择一个不同的 GID
printf(" Attempting to set FS-GID to: %d\n", new_fsgid);

gid_t result3 = setfsgid(new_fsgid);
printf(" setfsgid(%d) returned: %d (should be the old FS-GID: %d)\n", new_fsgid, result3, old_fsgid);

gid_t result4 = setfsgid(new_fsgid);
printf(" Verifying: setfsgid(%d) again returned: %d\n", new_fsgid, result4);
if (result3 == result4) {
printf(" -> FS-GID was successfully set to %d.\n", new_fsgid);
} else {
printf(" -> FS-GID setting might have failed.\n");
}

// 4. 尝试一个实际的文件操作来“感受” FS-UID/GID 的影响
// 注意:这个例子的权限检查结果可能不会明显变化,
// 因为我们没有真正切换到一个权限不同的用户上下文,
// 也没有访问一个严格基于 FS-UID/GID 权限的特殊文件系统。
printf("\n--- Attempting file operation to observe behavior ---\n");
const char *test_file = "test_fsuid_file.txt";
int fd;

// 创建一个测试文件
fd = open(test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "test", 4);
close(fd);
printf(" Created test file '%s'.\n", test_file);
} else {
perror(" Creating test file");
}

// 尝试读取文件 (应该成功,因为我们没有真正改变有效权限)
fd = open(test_file, O_RDONLY);
if (fd != -1) {
printf(" Successfully opened '%s' for reading (as expected).\n", test_file);
close(fd);
} else {
perror(" Opening test file for reading");
}

// 清理测试文件
if (unlink(test_file) == -1) {
perror(" Deleting test file");
} else {
printf(" Deleted test file '%s'.\n", test_file);
}

printf("\n--- Important Notes ---\n");
printf("1. setfsuid/setfsgid primarily affect filesystem permission checks.\n");
printf("2. They do not change effective UID/GID for other operations.\n");
printf("3. Their main use is in system daemons like NFS servers.\n");
printf("4. The return value is the OLD fsid, success is verified by calling again.\n");

return 0;
}

编译和运行:

1
2
3
4
5
6
7
8
9
# 假设代码保存在 fsuid_fsgid_example.c 中
gcc -o fsuid_fsgid_example fsuid_fsgid_example.c

# 运行程序 (普通用户权限即可)
./fsuid_fsgid_example

# 你也可以用 sudo 运行,看看 root 权限下的行为
# sudo ./fsuid_fsgid_example

预期输出 (作为普通用户运行):

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
--- Demonstrating setfsuid and setfsgid ---
Initial IDs:
Real UID: 1000
Effective UID: 1000
Saved UID: 1000
Real GID: 1000
Effective GID: 1000
Saved GID: 1000
(By default, FS-UID and FS-GID equal Effective UID/GID)

--- Testing setfsuid ---
Current FS-UID (queried): 1000
Attempting to set FS-UID to: 999
setfsuid(999) returned: 1000 (should be the old FS-UID: 1000)
Verifying: setfsuid(999) again returned: 999
-> FS-UID was successfully set to 999.

--- Testing setfsgid ---
Current FS-GID (queried): 1000
Attempting to set FS-GID to: 999
setfsgid(999) returned: 1000 (should be the old FS-GID: 1000)
Verifying: setfsgid(999) again returned: 999
-> FS-GID was successfully set to 999.

--- Attempting file operation to observe behavior ---
Created test file 'test_fsuid_file.txt'.
Successfully opened 'test_fsuid_file.txt' for reading (as expected).
Deleted test file 'test_fsuid_file.txt'.

--- Important Notes ---
1. setfsuid/setfsgid primarily affect filesystem permission checks.
2. They do not change effective UID/GID for other operations.
3. Their main use is in system daemons like NFS servers.
4. The return value is the OLD fsid, success is verified by calling again.

总结:对于 Linux 编程新手,setfsuid 和 setfsgid 是比较特殊的系统调用。它们不是日常编程中会频繁使用的。理解它们有助于理解 Linux 权限模型的细节,特别是文件系统权限检查是如何与进程的其他权限分离的。在编写需要代表不同用户执行文件操作的系统服务(如文件服务器)时,它们会很有用。在常规应用程序开发中,使用标准的 setuid/seteuid 或现代的 capabilities 通常更合适。

sethostname系统调用及示例

我们来深入学习 sethostname 系统调用及示例

我们来深入学习 sethostname 系统调用及示例

1. 函数介绍

在 Linux 系统(以及大多数 Unix-like 系统)中,每台计算机都有一个唯一的标识符,叫做 主机名 (hostname)。这个主机名用于在网络中识别这台机器。例如,当你在命令行输入 hostname 时,它会显示当前机器的主机名。

sethostname 系统调用的作用就是设置这台运行着 Linux 内核的计算机的 主机名。这是一个系统级别的设置,会影响整个机器,而不仅仅是调用它的那个进程。

简单来说,sethostname 就是让你用程序来给你的 Linux 电脑“改名字”。

重要提示:1. 需要权限:修改主机名是一个特权操作,通常只有 root 用户(超级用户)才有权限执行 sethostname。普通用户尝试调用它会失败。2. 影响范围:主机名是系统全局的属性。一旦通过 sethostname 修改,系统中所有查询主机名的地方(如 gethostname, uname 命令)都会返回新的名字。3. 持久性:通过 sethostname 设置的主机名是临时的。它只在当前的系统运行会话(直到关机或重启)中有效。系统重启后,主机会从配置文件(如 /etc/hostname)中读取并恢复原来的主机名。

2. 函数原型

1
2
3
4
5
#include <unistd.h>      // 包含系统调用声明
#include <sys/utsname.h> // 有时也需要,包含主机名长度常量

int sethostname(const char *name, size_t len);

3. 功能

设置内核维护的主机名。这个主机名可以通过 gethostname 系统调用或 uname 系统调用来查询。

4. 参数

name:

  • const char * 类型。

  • 一个指向以 null 结尾的字符串的指针,该字符串包含了新的主机名。

len:

  • size_t 类型。

  • 指定 name 字符串中实际包含的字符数(不包括末尾的 null 终止符 \0)。通常使用 strlen(name) 来获取这个长度。

5. 返回值

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EFAULT: name 指向了调用进程无法访问的内存地址。

  • EINVAL: len 超过了系统允许的最大主机名长度(通常是 MAXHOSTNAMELEN,在 <sys/utsname.h> 或 <limits.h> 中定义,常见值是 64 或 256)。

  • EPERM: 调用进程没有权限(不是 root 用户)来更改主机名。

7. 相似函数或关联函数

  • gethostname: 用于获取当前的主机名。

  • uname: 系统调用,可以获取包括主机名在内的系统信息(系统名、版本、机器类型等)。对应的命令行工具也叫 uname。

  • hostname: 命令行工具,用于显示或设置系统的主机名。它在底层就是调用 sethostname 和 gethostname。

  • /etc/hostname: 在许多 Linux 发行版中,系统启动时会从这个文件读取主机名并使用 sethostname 设置。

8. 示例代码

下面的示例演示了如何使用 sethostname 来修改主机名,以及如何使用 gethostname 来查询它。

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
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h> // 包含 gethostname, uname, MAXHOSTNAMELEN
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define HOST_NAME_BUFFER_SIZE MAXHOSTNAMELEN // 通常定义在 <sys/utsname.h> 或 <limits.h>

void print_current_hostname(const char* context) {
char hostname&#91;HOST_NAME_BUFFER_SIZE];
if (gethostname(hostname, sizeof(hostname)) == 0) {
printf("&#91;%s] Current hostname is: '%s'\n", context, hostname);
} else {
perror("gethostname");
}
}

int main(int argc, char *argv&#91;]) {
char new_hostname&#91;HOST_NAME_BUFFER_SIZE];
struct utsname uname_info; // 用于 uname 系统调用

printf("--- Demonstrating sethostname ---\n");

// 1. 显示当前主机名
print_current_hostname("Initial");

// 2. 使用 uname 系统调用获取更详细的系统信息
if (uname(&uname_info) == 0) {
printf("&#91;uname] System name: %s\n", uname_info.sysname);
printf("&#91;uname] Node name (hostname): %s\n", uname_info.nodename);
printf("&#91;uname] Release: %s\n", uname_info.release);
printf("&#91;uname] Version: %s\n", uname_info.version);
printf("&#91;uname] Machine: %s\n", uname_info.machine);
} else {
perror("uname");
}

// 3. 检查命令行参数
if (argc != 2) {
printf("Usage: %s <new_hostname>\n", argv&#91;0]);
printf("Note: You need to run this program as root to change the hostname.\n");
exit(EXIT_FAILURE);
}

strncpy(new_hostname, argv&#91;1], sizeof(new_hostname) - 1);
new_hostname&#91;sizeof(new_hostname) - 1] = '\0'; // 确保 null 终止

printf("\nAttempting to change hostname to: '%s'\n", new_hostname);

// 4. 调用 sethostname
// 注意:这需要 root 权限
if (sethostname(new_hostname, strlen(new_hostname)) == -1) {
perror("sethostname");
if (errno == EPERM) {
printf("Error: Permission denied. You must run this program as root (e.g., using sudo).\n");
}
exit(EXIT_FAILURE);
}

printf("sethostname('%s') succeeded.\n", new_hostname);

// 5. 再次显示主机名以验证更改
print_current_hostname("After sethostname");

// 6. 再次使用 uname 验证
if (uname(&uname_info) == 0) {
printf("&#91;uname after change] Node name (hostname): %s\n", uname_info.nodename);
}

printf("\n--- Important Notes ---\n");
printf("1. The hostname change is TEMPORARY and only lasts until the system is rebooted.\n");
printf("2. To make the change persistent, you need to update configuration files like /etc/hostname.\n");
printf("3. You need ROOT privileges to call sethostname.\n");

return 0;
}

编译和运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 假设代码保存在 sethostname_example.c 中
gcc -o sethostname_example sethostname_example.c

# 1. 不带参数运行 (会报错)
./sethostname_example

# 2. 带一个参数运行,但没有 root 权限 (会失败)
./sethostname_example MyNewTempHostname

# 3. 带一个参数运行,并使用 sudo 获取 root 权限 (应该成功)
# 注意:请将 MyNewTempHostname 替换为你想要的主机名
sudo ./sethostname_example MyNewTempHostname

# 4. 验证更改
hostname
uname -n

预期输出 (使用 sudo 运行):

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
# 假设原始主机名是 'old-hostname'
$ sudo ./sethostname_example NewTempName
--- Demonstrating sethostname ---
&#91;Initial] Current hostname is: 'old-hostname'
&#91;uname] System name: Linux
&#91;uname] Node name (hostname): old-hostname
&#91;uname] Release: 5.4.0-XX-generic
&#91;uname] Version: #XX-Ubuntu SMP ...
&#91;uname] Machine: x86_64

Attempting to change hostname to: 'NewTempName'
sethostname('NewTempName') succeeded.
&#91;After sethostname] Current hostname is: 'NewTempName'
&#91;uname after change] Node name (hostname): NewTempName

--- Important Notes ---
1. The hostname change is TEMPORARY and only lasts until the system is rebooted.
2. To make the change persistent, you need to update configuration files like /etc/hostname.
3. You need ROOT privileges to call sethostname.

# 验证命令
$ hostname
NewTempName
$ uname -n
NewTempName

重启后:如果你重启系统,主机会名会恢复到 /etc/hostname 文件中配置的名称。

总结:sethostname 是一个用于修改系统全局主机名的系统调用。它需要 root 权限,并且修改是临时的。理解它有助于你编写需要动态管理主机名的系统管理工具。在日常使用中,hostname 命令是更常见的设置主机名的方式。