第16章 系统函数库

1. 系统函数库概述

1.1 什么是系统函数库

系统函数库是操作系统提供的一组函数集合,用于访问系统功能和资源。这些函数封装了底层的系统调用,为应用程序提供了一个统一的接口,使应用程序能够与操作系统进行交互。

1.2 系统函数库的分类

C语言中的系统函数库主要包括:

  • 标准I/O库:用于文件输入/输出操作
  • 字符串处理库:用于字符串操作
  • 数学库:用于数学计算
  • 时间和日期库:用于时间和日期操作
  • 内存分配库:用于动态内存分配
  • 进程控制库:用于进程管理
  • 网络编程库:用于网络通信
  • 信号处理库:用于信号处理
  • 线程编程库:用于多线程编程
  • 系统调用库:用于直接调用系统服务

1.3 系统函数库的使用

使用系统函数库需要:

  1. 包含相应的头文件:如#include <stdio.h>
  2. 链接相应的库:如使用-lm链接数学库
  3. 遵循函数的调用约定:正确传递参数,处理返回值

2. 标准I/O库

2.1 标准I/O库概述

标准I/O库(<stdio.h>)提供了文件输入/输出操作的函数,是C语言中最常用的库之一。它通过缓冲区机制和文件流抽象,为应用程序提供了高效、统一的I/O操作接口。

2.1.1 标准I/O库的底层实现

标准I/O库的核心是文件流(FILE)和缓冲区管理:

  1. 文件流结构

    • FILE 是一个包含文件描述符、缓冲区状态和定位信息的结构体
    • 每个文件流都有一个关联的文件描述符,用于底层系统调用
    • 流状态包括错误标志、文件结束标志和缓冲区状态
  2. 缓冲区管理

    • 全缓冲:只有当缓冲区满或显式刷新时才执行I/O操作(如普通文件)
    • 行缓冲:当遇到换行符或缓冲区满时执行I/O操作(如标准输入/输出)
    • 无缓冲:直接执行I/O操作,无缓冲区(如标准错误)
  3. 缓冲区大小

    • 默认缓冲区大小通常为8192字节(8KB)
    • 可通过 setvbuf 函数自定义缓冲区大小和类型

2.1.2 标准I/O库的性能考量

标准I/O库的性能优势主要来自于:

  1. 减少系统调用:通过缓冲区减少了系统调用次数,提高了I/O性能
  2. 统一接口:为不同类型的设备提供了统一的操作接口
  3. 错误处理:提供了更详细的错误信息和处理机制
  4. 格式化功能:支持复杂的数据格式化和解析

2.2 常用函数

2.2.1 文件操作函数

函数名功能原型底层实现性能考量
fopen打开文件FILE *fopen(const char *filename, const char *mode);调用 open 系统调用,初始化 FILE 结构体和缓冲区开销较大,应避免频繁打开/关闭文件
fclose关闭文件int fclose(FILE *stream);刷新缓冲区,调用 close 系统调用,释放 FILE 结构体确保所有数据被写入,避免资源泄漏
fread读取文件size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);从缓冲区读取数据,必要时调用 read 系统调用填充缓冲区大块读取更高效,避免频繁调用
fwrite写入文件size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);写入缓冲区,必要时调用 write 系统调用刷新缓冲区大块写入更高效,考虑使用缓冲区优化
fseek设置文件位置int fseek(FILE *stream, long offset, int whence);刷新缓冲区,调用 lseek 系统调用,更新文件位置指针可能触发缓冲区刷新,影响性能
ftell获取文件位置long ftell(FILE *stream);返回当前文件位置指针,无需系统调用开销很小,可频繁使用
rewind重置文件位置void rewind(FILE *stream);等同于 fseek(stream, 0, SEEK_SET)可能触发缓冲区刷新
feof检查文件结束int feof(FILE *stream);检查文件结束标志,无需系统调用开销很小,可频繁使用
ferror检查文件错误int ferror(FILE *stream);检查错误标志,无需系统调用开销很小,可频繁使用
clearerr清除文件错误void clearerr(FILE *stream);清除错误标志和文件结束标志,无需系统调用开销很小,可频繁使用
setvbuf设置缓冲区int setvbuf(FILE *stream, char *buf, int mode, size_t size);设置缓冲区类型和大小,影响I/O性能应在打开文件后立即调用
fflush刷新缓冲区int fflush(FILE *stream);将缓冲区数据写入文件,调用 write 系统调用可能触发系统调用,影响性能

2.2.2 格式化输入/输出函数

函数名功能原型安全考量性能考量
printf格式化输出到标准输出int printf(const char *format, ...);安全,无缓冲区溢出风险频繁调用可能影响性能,考虑使用 putsfputs
scanf格式化输入到标准输入int scanf(const char *format, ...);不安全,可能导致缓冲区溢出输入验证不足,建议使用 fgets + sscanf
fprintf格式化输出到文件int fprintf(FILE *stream, const char *format, ...);安全,无缓冲区溢出风险频繁调用可能影响性能,考虑批量写入
fscanf格式化输入到文件int fscanf(FILE *stream, const char *format, ...);不安全,可能导致缓冲区溢出输入验证不足,建议使用 fgets + sscanf
sprintf格式化输出到字符串int sprintf(char *str, const char *format, ...);不安全,可能导致缓冲区溢出应使用 snprintf 替代
sscanf格式化输入到字符串int sscanf(const char *str, const char *format, ...);相对安全,输入受限于源字符串性能较好,适合解析已知格式的字符串
snprintf安全的格式化输出到字符串int snprintf(char *str, size_t size, const char *format, ...);安全,可防止缓冲区溢出性能略低于 sprintf,但安全性更高
vsnprintf可变参数的安全格式化输出int vsnprintf(char *str, size_t size, const char *format, va_list ap);安全,可防止缓冲区溢出适合实现自定义格式化函数

2.2.3 字符输入/输出函数

函数名功能原型性能考量使用场景
getchar从标准输入读取字符int getchar(void);行缓冲,适合交互式输入逐字符读取标准输入
putchar向标准输出写入字符int putchar(int c);行缓冲,适合交互式输出逐字符写入标准输出
fgetc从文件读取字符int fgetc(FILE *stream);可能触发缓冲区填充,频繁调用影响性能逐字符读取文件
fputc向文件写入字符int fputc(int c, FILE *stream);可能触发缓冲区刷新,频繁调用影响性能逐字符写入文件
ungetc放回字符到输入流int ungetc(int c, FILE *stream);操作流内部缓冲区,开销很小实现词法分析器、解析器等

2.2.4 字符串输入/输出函数

函数名功能原型安全考量性能考量
gets从标准输入读取字符串(不安全)char *gets(char *s);极不安全,已从C11标准中移除应使用 fgets 替代
puts向标准输出写入字符串int puts(const char *s);安全,自动添加换行符性能较好,适合输出字符串
fgets从文件读取字符串char *fgets(char *s, int size, FILE *stream);安全,可防止缓冲区溢出性能较好,适合读取文本行
fputs向文件写入字符串int fputs(const char *s, FILE *stream);安全,不自动添加换行符性能较好,适合写入字符串

2.3 高级文件操作

2.3.1 二进制文件操作

二进制文件操作与文本文件操作的主要区别在于:

  1. 打开模式:使用 "rb""wb""ab" 等模式打开二进制文件
  2. 数据处理:二进制文件直接读写字节,不进行行尾转换
  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
#include <stdio.h>

typedef struct {
int id;
char name[50];
float salary;
} Employee;

int main() {
FILE *fp;
Employee emp1 = {1, "John Doe", 5000.0};
Employee emp2;

// 写入二进制文件
fp = fopen("employees.dat", "wb");
if (fp == NULL) {
perror("fopen");
return 1;
}

if (fwrite(&emp1, sizeof(Employee), 1, fp) != 1) {
perror("fwrite");
fclose(fp);
return 1;
}

fclose(fp);

// 读取二进制文件
fp = fopen("employees.dat", "rb");
if (fp == NULL) {
perror("fopen");
return 1;
}

if (fread(&emp2, sizeof(Employee), 1, fp) != 1) {
perror("fread");
fclose(fp);
return 1;
}

printf("ID: %d\nName: %s\nSalary: %.2f\n", emp2.id, emp2.name, emp2.salary);

fclose(fp);

return 0;
}

2.3.2 文件定位与随机访问

文件定位是实现随机访问文件的基础,通过 fseekftellrewind 函数实现:

  1. 文件位置指示器:每个文件流都有一个文件位置指示器,指向当前读写位置
  2. 定位模式
    • SEEK_SET:从文件开头计算偏移量
    • SEEK_CUR:从当前位置计算偏移量
    • SEEK_END:从文件末尾计算偏移量
  3. 大文件支持:对于超过 2GB 的文件,应使用 fseekoftello 函数,它们使用 off_t 类型

示例:随机访问文件

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
#include <stdio.h>

int main() {
FILE *fp;
int data[] = {1, 2, 3, 4, 5};
int value;
long pos;

// 写入数据
fp = fopen("data.bin", "wb");
if (fp == NULL) {
perror("fopen");
return 1;
}

fwrite(data, sizeof(int), 5, fp);
fclose(fp);

// 随机读取数据
fp = fopen("data.bin", "rb");
if (fp == NULL) {
perror("fopen");
return 1;
}

// 读取第三个元素(索引为2)
pos = 2 * sizeof(int);
if (fseek(fp, pos, SEEK_SET) != 0) {
perror("fseek");
fclose(fp);
return 1;
}

if (fread(&value, sizeof(int), 1, fp) != 1) {
perror("fread");
fclose(fp);
return 1;
}

printf("Third element: %d\n", value);

// 读取最后一个元素
if (fseek(fp, -sizeof(int), SEEK_END) != 0) {
perror("fseek");
fclose(fp);
return 1;
}

if (fread(&value, sizeof(int), 1, fp) != 1) {
perror("fread");
fclose(fp);
return 1;
}

printf("Last element: %d\n", value);

fclose(fp);

return 0;
}

2.3.3 临时文件操作

临时文件用于存储临时数据,通常在程序结束后自动删除:

  1. 临时文件创建

    • tmpfile():创建一个临时文件,程序结束时自动删除
    • tmpnam():生成一个唯一的临时文件名
    • mkstemp():创建一个唯一的临时文件,更安全
  2. 临时文件安全

    • 避免使用固定的临时文件名,防止文件覆盖攻击
    • 使用 mkstemp()tmpfile() 生成唯一的临时文件名
    • 临时文件应设置适当的权限,防止未授权访问

示例:使用临时文件

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
#include <stdio.h>

int main() {
FILE *fp;
char buffer[100];

// 创建临时文件
fp = tmpfile();
if (fp == NULL) {
perror("tmpfile");
return 1;
}

// 写入数据
fprintf(fp, "Temporary data\n");
fprintf(fp, "More temporary data\n");

// 重置文件位置
rewind(fp);

// 读取数据
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}

// 关闭文件(自动删除)
fclose(fp);

return 0;
}

2.4 性能优化技巧

2.4.1 缓冲区优化

  1. 选择合适的缓冲区大小

    • 过小的缓冲区会增加系统调用次数
    • 过大的缓冲区会浪费内存
    • 建议缓冲区大小为4KB或8KB,与系统页大小匹配
  2. 使用自定义缓冲区

    1
    2
    3
    char buffer[8192];
    FILE *fp = fopen("file.txt", "r");
    setvbuf(fp, buffer, _IOFBF, sizeof(buffer));
  3. 批量读写

    • 避免逐字符或逐行读写大文件
    • 使用 freadfwrite 进行批量读写
    • 合理设置读写块大小,通常为8KB或16KB

2.4.2 错误处理优化

  1. 集中错误处理

    • 定义统一的错误处理函数
    • 避免在每个I/O操作后都检查错误
  2. 使用 perrorstrerror

    • 提供详细的错误信息
    • 有助于快速定位问题
  3. 检查返回值

    • 始终检查I/O函数的返回值
    • 不要依赖文件结束标志作为唯一的错误指示

2.4.3 文件操作优化

  1. 减少文件打开/关闭次数

    • 避免在循环中频繁打开和关闭文件
    • 一次性打开文件,完成所有操作后关闭
  2. 使用二进制模式

    • 对于非文本文件,使用二进制模式读写
    • 避免行尾转换开销
  3. 合理使用文件定位

    • 避免频繁的文件定位操作
    • 尽量顺序访问文件,利用预读机制
  4. 使用内存映射

    • 对于大文件,考虑使用 mmap 进行内存映射
    • 可以显著提高大文件的访问性能

2.5 安全编程实践

2.5.1 防止缓冲区溢出

  1. 使用安全的输入函数

    • 使用 fgets 替代 gets
    • 使用 snprintf 替代 sprintf
    • 使用 strncpy 替代 strcpy
  2. 输入验证

    • 始终验证输入数据的长度和格式
    • 对用户输入进行严格检查
  3. 缓冲区大小计算

    • 使用 sizeof 计算缓冲区大小
    • 避免硬编码缓冲区大小

2.5.2 资源管理

  1. 文件句柄管理

    • 始终关闭打开的文件
    • 使用 fclose 确保所有数据被写入
  2. 异常处理

    • 在函数返回前确保所有资源被释放
    • 考虑使用 goto 语句进行统一的资源清理
  3. 内存管理

    • 避免内存泄漏
    • 及时释放不再使用的内存

2.6 使用示例

2.6.1 高效文件复制

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
#include <stdio.h>
#define BUFFER_SIZE 8192

int main(int argc, char *argv[]) {
FILE *src, *dst;
char buffer[BUFFER_SIZE];
size_t bytes_read;

if (argc != 3) {
fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
return 1;
}

// 打开源文件
src = fopen(argv[1], "rb");
if (src == NULL) {
perror("fopen (source)");
return 1;
}

// 打开目标文件
dst = fopen(argv[2], "wb");
if (dst == NULL) {
perror("fopen (destination)");
fclose(src);
return 1;
}

// 设置缓冲区
setvbuf(src, NULL, _IOFBF, BUFFER_SIZE);
setvbuf(dst, NULL, _IOFBF, BUFFER_SIZE);

// 复制文件
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src)) > 0) {
if (fwrite(buffer, 1, bytes_read, dst) != bytes_read) {
perror("fwrite");
fclose(src);
fclose(dst);
return 1;
}
}

if (ferror(src)) {
perror("fread");
fclose(src);
fclose(dst);
return 1;
}

// 关闭文件
if (fclose(src) != 0) {
perror("fclose (source)");
fclose(dst);
return 1;
}

if (fclose(dst) != 0) {
perror("fclose (destination)");
return 1;
}

printf("File copied successfully\n");
return 0;
}

2.6.2 大文件读取与处理

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
#include <stdio.h>
#define BUFFER_SIZE 16384

int main() {
FILE *fp;
char buffer[BUFFER_SIZE];
size_t bytes_read;
long long total_bytes = 0;
int line_count = 0;

// 打开文件
fp = fopen("large_file.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}

// 设置缓冲区
setvbuf(fp, NULL, _IOFBF, BUFFER_SIZE);

// 读取文件
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {
total_bytes += bytes_read;

// 统计行数(简单实现)
for (size_t i = 0; i < bytes_read; i++) {
if (buffer[i] == '\n') {
line_count++;
}
}
}

if (ferror(fp)) {
perror("fread");
fclose(fp);
return 1;
}

// 关闭文件
fclose(fp);

printf("Total bytes: %lld\n", total_bytes);
printf("Line count: %d\n", line_count);

return 0;
}

2.7 常见问题与解决方案

2.7.1 缓冲区问题

问题:程序输出不显示,或者文件内容未写入

解决方案

  • 对于标准输出,添加换行符或使用 fflush(stdout)
  • 对于文件,使用 fflush(fp) 或确保调用 fclose(fp)

2.7.2 文件权限问题

问题fopen 失败,错误信息为 “Permission denied”

解决方案

  • 检查文件权限
  • 确保文件路径正确
  • 避免在只读文件系统中写入文件

2.7.3 文件锁定问题

问题:多个进程同时访问同一个文件,导致数据损坏

解决方案

  • 使用文件锁(flockfcntl
  • 实现互斥访问机制
  • 避免多个进程同时写入同一个文件

2.7.4 大文件处理问题

问题:无法处理超过2GB的文件

解决方案

  • 使用 fseekoftello 替代 fseekftell
  • 编译时定义 _FILE_OFFSET_BITS=64
  • 考虑使用内存映射(mmap)处理大文件

2.8 总结

标准I/O库是C语言中最常用的库之一,它提供了丰富的文件操作函数和格式化输入/输出函数。通过合理使用标准I/O库,可以:

  1. 提高I/O性能:通过缓冲区机制减少系统调用次数
  2. 简化编程:提供统一的接口处理不同类型的I/O设备
  3. 增强安全性:使用安全的输入/输出函数防止缓冲区溢出
  4. 提高可移植性:标准I/O库在不同平台上的实现基本一致

在实际编程中,应根据具体需求选择合适的I/O函数,并注意性能优化和安全编程实践,以编写高效、可靠的I/O操作代码。

3. 字符串处理库

3.1 字符串处理库概述

字符串处理库(<string.h>)提供了字符串操作的函数,是C语言中最常用的库之一。

3.2 常用函数

3.2.1 字符串操作函数

函数名功能原型
strlen获取字符串长度size_t strlen(const char *s);
strcpy复制字符串(不安全)char *strcpy(char *dest, const char *src);
strncpy安全的复制字符串char *strncpy(char *dest, const char *src, size_t n);
strcat连接字符串(不安全)char *strcat(char *dest, const char *src);
strncat安全的连接字符串char *strncat(char *dest, const char *src, size_t n);
strcmp比较字符串int strcmp(const char *s1, const char *s2);
strncmp比较字符串前n个字符int strncmp(const char *s1, const char *s2, size_t n);
strchr查找字符char *strchr(const char *s, int c);
strrchr反向查找字符char *strrchr(const char *s, int c);
strstr查找子字符串char *strstr(const char *haystack, const char *needle);
strtok分割字符串char *strtok(char *str, const char *delim);
memset填充内存void *memset(void *s, int c, size_t n);
memcpy复制内存void *memcpy(void *dest, const void *src, size_t n);
memmove安全的复制内存void *memmove(void *dest, const void *src, size_t n);
memcmp比较内存int memcmp(const void *s1, const void *s2, size_t n);

3.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
#include <stdio.h>
#include <string.h>

int main() {
char str1[50] = "Hello, ";
char str2[] = "world!";
char str3[50];

// 连接字符串
strcat(str1, str2);
printf("str1: %s\n", str1);

// 复制字符串
strcpy(str3, str1);
printf("str3: %s\n", str3);

// 比较字符串
if (strcmp(str1, str3) == 0) {
printf("str1 and str3 are equal\n");
}

// 获取字符串长度
printf("Length of str1: %zu\n", strlen(str1));

// 查找子字符串
char *ptr = strstr(str1, "world");
if (ptr != NULL) {
printf("Found 'world' at position: %ld\n", ptr - str1);
}

return 0;
}

4. 数学库

4.1 数学库概述

数学库(<math.h>)提供了数学计算的函数,使用时需要链接数学库(-lm)。

4.2 常用函数

4.2.1 基本数学函数

函数名功能原型
sin正弦函数double sin(double x);
cos余弦函数double cos(double x);
tan正切函数double tan(double x);
asin反正弦函数double asin(double x);
acos反余弦函数double acos(double x);
atan反正切函数double atan(double x);
atan2反正切函数(两个参数)double atan2(double y, double x);
sinh双曲正弦函数double sinh(double x);
cosh双曲余弦函数double cosh(double x);
tanh双曲正切函数double tanh(double x);

4.2.2 指数和对数函数

函数名功能原型
exp指数函数double exp(double x);
log自然对数double log(double x);
log10以10为底的对数double log10(double x);
pow幂函数double pow(double x, double y);
sqrt平方根double sqrt(double x);
cbrt立方根double cbrt(double x);

4.2.3 取整和绝对值函数

函数名功能原型
ceil向上取整double ceil(double x);
floor向下取整double floor(double x);
round四舍五入double round(double x);
fabs绝对值double fabs(double x);
abs整数绝对值int abs(int x);
labs长整数绝对值long labs(long x);
llabs长 long 整数绝对值long long llabs(long long x);

4.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
#include <stdio.h>
#include <math.h>

int main() {
double x = 1.0;
double y = 2.0;

// 基本数学函数
printf("sin(1.0) = %f\n", sin(x));
printf("cos(1.0) = %f\n", cos(x));
printf("tan(1.0) = %f\n", tan(x));

// 指数和对数函数
printf("exp(1.0) = %f\n", exp(x));
printf("log(1.0) = %f\n", log(x));
printf("pow(2.0, 3.0) = %f\n", pow(y, 3.0));
printf("sqrt(4.0) = %f\n", sqrt(y * y));

// 取整和绝对值函数
printf("ceil(1.5) = %f\n", ceil(1.5));
printf("floor(1.5) = %f\n", floor(1.5));
printf("round(1.5) = %f\n", round(1.5));
printf("fabs(-1.5) = %f\n", fabs(-1.5));

return 0;
}

5. 时间和日期库

5.1 时间和日期库概述

时间和日期库(<time.h>)提供了时间和日期操作的函数。

5.2 常用函数

5.2.1 时间操作函数

函数名功能原型
time获取当前时间time_t time(time_t *t);
ctime转换时间为字符串char *ctime(const time_t *t);
asctime转换结构体为字符串char *asctime(const struct tm *tm);
localtime转换时间为本地时间struct tm *localtime(const time_t *t);
gmtime转换时间为GMT时间struct tm *gmtime(const time_t *t);
mktime转换结构体为时间time_t mktime(struct tm *tm);
difftime计算时间差double difftime(time_t time1, time_t time0);
strftime格式化时间为字符串size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *tm);

5.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
#include <stdio.h>
#include <time.h>

int main() {
time_t current_time;
struct tm *local_time;
char time_string[50];

// 获取当前时间
current_time = time(NULL);
printf("Current time: %ld\n", current_time);

// 转换为本地时间
local_time = localtime(&current_time);

// 格式化为字符串
strftime(time_string, sizeof(time_string), "%Y-%m-%d %H:%M:%S", local_time);
printf("Formatted time: %s\n", time_string);

// 使用ctime
printf("ctime: %s", ctime(&current_time));

return 0;
}

6. 内存分配库

6.1 内存分配库概述

内存分配库(<stdlib.h>)提供了动态内存分配的函数,是C语言中管理内存的重要工具。

6.2 常用函数

6.2.1 内存分配函数

函数名功能原型
malloc分配内存void *malloc(size_t size);
calloc分配并清零内存void *calloc(size_t nmemb, size_t size);
realloc重新分配内存void *realloc(void *ptr, size_t size);
free释放内存void free(void *ptr);
aligned_alloc分配对齐内存void *aligned_alloc(size_t alignment, size_t size);

6.2.2 其他常用函数

函数名功能原型
exit终止程序void exit(int status);
atoi字符串转整数int atoi(const char *nptr);
atol字符串转长整数long atol(const char *nptr);
atoll字符串转长 long 整数long long atoll(const char *nptr);
strtod字符串转双精度浮点数double strtod(const char *nptr, char **endptr);
strtol字符串转长整数long strtol(const char *nptr, char **endptr, int base);
strtoll字符串转长 long 整数long long strtoll(const char *nptr, char **endptr, int base);
rand生成随机数int rand(void);
srand设置随机数种子void srand(unsigned int seed);
qsort快速排序void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
bsearch二分查找void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

6.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
#include <stdio.h>
#include <stdlib.h>

int compare(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}

int main() {
int *arr;
int n = 5;
int i;

// 分配内存
arr = (int *)malloc(n * sizeof(int));
if (arr == NULL) {
perror("malloc");
return 1;
}

// 初始化数组
srand(time(NULL));
for (i = 0; i < n; i++) {
arr[i] = rand() % 100;
printf("arr[%d] = %d\n", i, arr[i]);
}

// 排序数组
qsort(arr, n, sizeof(int), compare);

// 打印排序后的数组
printf("Sorted array:\n");
for (i = 0; i < n; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}

// 释放内存
free(arr);

return 0;
}

7. 进程控制库

7.1 进程控制库概述

进程控制库(<unistd.h>)提供了进程管理的函数,主要用于Unix-like系统。

7.2 常用函数

函数名功能原型
fork创建子进程pid_t fork(void);
exec 系列执行程序int execl(const char *path, const char *arg, ...);
wait等待子进程结束pid_t wait(int *status);
waitpid等待指定子进程结束pid_t waitpid(pid_t pid, int *status, int options);
getpid获取进程IDpid_t getpid(void);
getppid获取父进程IDpid_t getppid(void);
sleep睡眠指定秒数unsigned int sleep(unsigned int seconds);
usleep睡眠指定微秒数int usleep(useconds_t usec);
nanosleep睡眠指定纳秒数int nanosleep(const struct timespec *req, struct timespec *rem);
getuid获取用户IDuid_t getuid(void);
geteuid获取有效用户IDuid_t geteuid(void);
getgid获取组IDgid_t getgid(void);
getegid获取有效组IDgid_t getegid(void);
chdir改变当前目录int chdir(const char *path);
getcwd获取当前目录char *getcwd(char *buf, size_t size);
unlink删除文件int unlink(const char *pathname);
rename重命名文件int rename(const char *oldpath, const char *newpath);

7.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
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
pid_t pid;

// 创建子进程
pid = fork();

if (pid < 0) {
// 错误
perror("fork");
return 1;
} else if (pid == 0) {
// 子进程
printf("Child process: PID = %d, PPID = %d\n", getpid(), getppid());
sleep(1);
printf("Child process exiting\n");
return 0;
} else {
// 父进程
int status;
printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);
// 等待子进程结束
wait(&status);
printf("Parent process: Child exited with status %d\n", WEXITSTATUS(status));
return 0;
}
}

8. 网络编程库

8.1 网络编程库概述

网络编程库(<sys/socket.h>)提供了网络通信的函数,是C语言中进行网络编程的基础。

8.2 常用函数

8.2.1 套接字函数

函数名功能原型
socket创建套接字int socket(int domain, int type, int protocol);
bind绑定地址int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen监听连接int listen(int sockfd, int backlog);
accept接受连接int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
connect连接服务器int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
send发送数据ssize_t send(int sockfd, const void *buf, size_t len, int flags);
recv接收数据ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sendto发送数据(UDP)ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
recvfrom接收数据(UDP)ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
close关闭套接字int close(int fd);
getaddrinfo获取地址信息int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
freeaddrinfo释放地址信息void freeaddrinfo(struct addrinfo *res);
inet_ntop网络地址转字符串const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
inet_pton字符串转网络地址int inet_pton(int af, const char *src, void *dst);

8.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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from server";

// 创建套接字文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

// 绑定套接字到端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}

// 开始监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}

// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}

// 读取客户端消息
read(new_socket, buffer, BUFFER_SIZE);
printf("Client: %s\n", buffer);

// 发送响应
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");

// 关闭套接字
close(new_socket);
close(server_fd);

return 0;
}

客户端

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from client";

// 创建套接字文件描述符
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("Socket creation error\n");
return -1;
}

serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);

// 转换IPv4地址
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
printf("Invalid address\n");
return -1;
}

// 连接服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("Connection Failed\n");
return -1;
}

// 发送消息
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");

// 读取响应
read(sock, buffer, BUFFER_SIZE);
printf("Server: %s\n", buffer);

// 关闭套接字
close(sock);

return 0;
}

9. 信号处理库

9.1 信号处理库概述

信号处理库(<signal.h>)提供了信号处理的函数,用于处理系统发送的各种信号。

9.2 常用函数

函数名功能原型
signal设置信号处理函数void (*signal(int signum, void (*handler)(int)))(int);
sigaction设置信号处理函数(更灵活)int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
kill发送信号int kill(pid_t pid, int sig);
raise向自身发送信号int raise(int sig);
alarm设置闹钟unsigned int alarm(unsigned int seconds);
pause暂停进程直到信号到达int pause(void);
sigprocmask阻塞/解除阻塞信号int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigemptyset初始化信号集为空int sigemptyset(sigset_t *set);
sigfillset初始化信号集为所有信号int sigfillset(sigset_t *set);
sigaddset向信号集添加信号int sigaddset(sigset_t *set, int signum);
sigdelset从信号集删除信号int sigdelset(sigset_t *set, int signum);
sigismember检查信号是否在信号集中int sigismember(const sigset_t *set, int signum);

9.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
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signal_handler(int signum) {
printf("Received signal %d\n", signum);
if (signum == SIGINT) {
printf("Ctrl+C pressed, exiting...\n");
_exit(0);
}
}

int main() {
// 设置信号处理函数
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);

printf("Waiting for signals... (Press Ctrl+C to exit)\n");

// 无限循环
while (1) {
sleep(1);
printf("Still waiting...\n");
}

return 0;
}

10. 线程编程库

10.1 线程编程库概述

线程编程库(<pthread.h>)提供了多线程编程的函数,使用时需要链接线程库(-lpthread)。

10.2 常用函数

函数名功能原型
pthread_create创建线程int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
pthread_join等待线程结束int pthread_join(pthread_t thread, void **retval);
pthread_detach分离线程int pthread_detach(pthread_t thread);
pthread_exit终止线程void pthread_exit(void *retval);
pthread_self获取线程IDpthread_t pthread_self(void);
pthread_equal比较线程IDint pthread_equal(pthread_t t1, pthread_t t2);
pthread_mutex_init初始化互斥锁int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
pthread_mutex_lock加锁int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_trylock尝试加锁int pthread_mutex_trylock(pthread_mutex_t *mutex);
pthread_mutex_unlock解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_destroy销毁互斥锁int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_cond_init初始化条件变量int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
pthread_cond_wait等待条件变量int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_signal唤醒一个等待的线程int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast唤醒所有等待的线程int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_destroy销毁条件变量int pthread_cond_destroy(pthread_cond_t *cond);

10.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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#define NUM_THREADS 5

pthread_mutex_t mutex;
int counter = 0;

void *thread_function(void *arg) {
int thread_id = *((int *)arg);

// 加锁
pthread_mutex_lock(&mutex);

// 临界区
counter++;
printf("Thread %d: counter = %d\n", thread_id, counter);

// 解锁
pthread_mutex_unlock(&mutex);

pthread_exit(NULL);
}

int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
int i;

// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);

// 创建线程
for (i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
perror("pthread_create");
return 1;
}
}

// 等待所有线程结束
for (i = 0; i < NUM_THREADS; i++) {
if (pthread_join(threads[i], NULL) != 0) {
perror("pthread_join");
return 1;
}
}

// 销毁互斥锁
pthread_mutex_destroy(&mutex);

printf("Final counter value: %d\n", counter);

return 0;
}

11. 系统调用库

11.1 系统调用库概述

系统调用库(<sys/syscall.h>)提供了直接调用系统服务的函数,是操作系统与应用程序之间的接口。

11.2 常用函数

函数名功能原型
syscall直接调用系统调用long syscall(long number, ...);
read读取文件ssize_t read(int fd, void *buf, size_t count);
write写入文件ssize_t write(int fd, const void *buf, size_t count);
open打开文件int open(const char *pathname, int flags, mode_t mode);
close关闭文件int close(int fd);
lseek设置文件位置off_t lseek(int fd, off_t offset, int whence);
mkdir创建目录int mkdir(const char *pathname, mode_t mode);
rmdir删除目录int rmdir(const char *pathname);
chmod修改文件权限int chmod(const char *pathname, mode_t mode);
chown修改文件所有者int chown(const char *pathname, uid_t owner, gid_t group);
link创建硬链接int link(const char *oldpath, const char *newpath);
symlink创建符号链接int symlink(const char *target, const char *linkpath);
readlink读取符号链接ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
unlink删除文件int unlink(const char *pathname);
rename重命名文件int rename(const char *oldpath, const char *newpath);

11.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
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int main() {
int fd;
char buffer[100];
ssize_t n;

// 打开文件
fd = open("example.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}

// 写入文件
n = write(fd, "Hello, world!\n", 13);
if (n < 0) {
perror("write");
close(fd);
return 1;
}

// 关闭文件
close(fd);

// 重新打开文件读取
fd = open("example.txt", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}

// 读取文件
n = read(fd, buffer, sizeof(buffer));
if (n < 0) {
perror("read");
close(fd);
return 1;
}

// 打印读取的内容
write(STDOUT_FILENO, buffer, n);

// 关闭文件
close(fd);

return 0;
}

12. 系统函数库的最佳实践

12.1 一般原则

  1. 包含正确的头文件:确保包含了所有需要的头文件
  2. 链接必要的库:如数学库(-lm)、线程库(-lpthread)等
  3. 检查函数返回值:始终检查函数的返回值,处理错误情况
  4. 释放资源:确保所有分配的资源都被释放,如文件描述符、内存等
  5. 使用安全的函数:避免使用不安全的函数,如getsstrcpy
  6. 遵循函数的调用约定:正确传递参数,处理返回值
  7. 了解函数的限制:如缓冲区大小、参数范围等
  8. 使用适当的错误处理:如perrorstrerror
  9. 测试边界情况:测试函数在边界情况下的行为
  10. 参考文档:查阅系统函数库的文档,了解函数的详细用法

12.2 性能优化

  1. 减少函数调用:对于频繁调用的函数,考虑内联或使用宏
  2. 缓存计算结果:对于计算密集型函数,缓存结果
  3. 使用适当的数据结构:选择高效的数据结构
  4. 避免不必要的转换:如字符串和数字之间的转换
  5. 使用编译器优化:如-O2-O3等优化选项

12.3 安全性

  1. 检查参数:验证所有函数参数的有效性
  2. 防止缓冲区溢出:使用安全的字符串处理函数
  3. 释放资源:确保所有分配的资源都被释放
  4. 避免使用不安全的函数:如getsstrcpy
  5. 使用地址随机化:编译时启用地址随机化
  6. 限制权限:最小化程序的权限
  7. 加密敏感数据:对敏感数据进行加密
  8. 防止注入攻击:如SQL注入、命令注入等

13. 系统函数库的跨平台兼容性

13.1 跨平台兼容性问题

  • 头文件差异:不同平台的头文件可能不同
  • 函数差异:不同平台的函数可能有不同的实现或行为
  • 类型差异:不同平台的类型大小可能不同
  • 系统调用差异:不同平台的系统调用可能不同
  • 路径分隔符:不同平台的路径分隔符可能不同(/ vs \

13.2 解决方案

  1. 使用条件编译:根据不同平台使用不同的代码
  2. 使用宏定义:定义平台相关的宏
  3. 使用抽象层:为平台相关的功能创建抽象层
  4. 使用跨平台库:如SDL、Boost等
  5. 测试多个平台:确保代码在多个平台上都能正常工作
  6. 参考标准:遵循C语言标准,避免使用非标准功能

13.3 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

#ifdef _WIN32
#include <windows.h>
#define SLEEP(ms) Sleep(ms)
#elif defined(__linux__)
#include <unistd.h>
#define SLEEP(ms) usleep((ms) * 1000)
#elif defined(__APPLE__)
#include <unistd.h>
#define SLEEP(ms) usleep((ms) * 1000)
#else
#error Unsupported platform
#endif

int main() {
printf("Sleeping for 1 second...\n");
SLEEP(1000);
printf("Woke up!\n");
return 0;
}

14. 系统函数库的调试

14.1 调试工具

  • gdb:GNU调试器
  • valgrind:内存分析工具
  • strace:系统调用跟踪工具
  • ltrace:库函数调用跟踪工具
  • gprof:性能分析工具
  • addr2line:地址转换工具

14.2 调试技巧

  1. 使用printf:在关键位置添加打印语句
  2. 使用assert:在关键位置添加断言
  3. 检查返回值:始终检查函数的返回值
  4. 使用调试器:使用gdb等调试器逐步执行代码
  5. 使用内存分析工具:使用valgrind等工具检查内存问题
  6. 使用系统调用跟踪:使用strace等工具跟踪系统调用
  7. 查看核心转储:分析程序崩溃时生成的核心转储文件
  8. 使用日志:使用日志记录程序的执行情况

14.3 常见问题

14.3.1 段错误

原因:访问了无效的内存地址
解决:使用gdb或valgrind检查内存访问

14.3.2 内存泄漏

原因:分配的内存未被释放
解决:使用valgrind检查内存泄漏

14.3.3 文件操作错误

原因:文件不存在、权限不足等
解决:检查文件路径、权限,使用perror查看错误信息

14.3.4 网络连接错误

原因:网络不可达、端口未开放等
解决:检查网络连接、防火墙设置,使用perror查看错误信息

15. 示例代码

15.1 综合示例:文件复制程序

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
#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 4096

int main(int argc, char *argv[]) {
FILE *src, *dest;
char buffer[BUFFER_SIZE];
size_t n;

// 检查参数
if (argc != 3) {
fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
return 1;
}

// 打开源文件
src = fopen(argv[1], "rb");
if (src == NULL) {
perror("fopen source");
return 1;
}

// 打开目标文件
dest = fopen(argv[2], "wb");
if (dest == NULL) {
perror("fopen destination");
fclose(src);
return 1;
}

// 复制文件
while ((n = fread(buffer, 1, BUFFER_SIZE, src)) > 0) {
if (fwrite(buffer, 1, n, dest) != n) {
perror("fwrite");
fclose(src);
fclose(dest);
return 1;
}
}

// 检查读取错误
if (ferror(src)) {
perror("fread");
fclose(src);
fclose(dest);
return 1;
}

// 关闭文件
if (fclose(src) != 0) {
perror("fclose source");
fclose(dest);
return 1;
}

if (fclose(dest) != 0) {
perror("fclose destination");
return 1;
}

printf("File copied successfully\n");

return 0;
}

15.2 综合示例:简单的HTTP服务器

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define BUFFER_SIZE 4096

int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nHello World!";

// 创建套接字文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

// 绑定套接字到端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}

// 开始监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}

printf("Server listening on port %d...\n", PORT);

while (1) {
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}

// 读取请求
read(new_socket, buffer, BUFFER_SIZE);
printf("Request:\n%s\n", buffer);

// 发送响应
send(new_socket, response, strlen(response), 0);
printf("Response sent\n");

// 关闭连接
close(new_socket);
}

return 0;
}