POSIX 标准详解教程 / 第十五章:最佳实践
第十五章:最佳实践
系统编程规范总结:错误处理、安全编码、性能优化、代码组织、常见陷阱。
15.1 错误处理规范
15.1.1 POSIX 错误处理原则
| 原则 | 说明 |
|---|
| 始终检查返回值 | 所有系统调用和库函数都可能失败 |
| 使用 errno | 失败时 errno 包含具体错误码 |
| 及时处理 | 在调用后立即检查,不要延迟 |
| 清理资源 | 错误路径中释放已分配的资源 |
| 不忽略错误 | 即使"不可能失败"的调用也要检查 |
15.1.2 标准错误处理模式
/*
* error_handling.c - POSIX 错误处理最佳实践
* 编译: gcc -Wall -o error_handling error_handling.c
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
/* ========== 模式 1: 直接退出(适用于致命错误) ========== */
void fatal(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
/* ========== 模式 2: 返回错误码(适用于可恢复错误) ========== */
typedef enum {
ERR_OK = 0,
ERR_NOMEM,
ERR_IO,
ERR_NOT_FOUND,
ERR_PERMISSION,
} error_t;
const char *error_string(error_t err)
{
switch (err) {
case ERR_OK: return "成功";
case ERR_NOMEM: return "内存不足";
case ERR_IO: return "I/O 错误";
case ERR_NOT_FOUND: return "未找到";
case ERR_PERMISSION: return "权限不足";
default: return "未知错误";
}
}
/* ========== 模式 3: goto 清理(推荐的资源管理模式) ========== */
error_t process_file(const char *path, char **result)
{
error_t ret = ERR_OK;
int fd = -1;
char *buf = NULL;
fd = open(path, O_RDONLY);
if (fd == -1) {
switch (errno) {
case ENOENT: return ERR_NOT_FOUND;
case EACCES: return ERR_PERMISSION;
default: return ERR_IO;
}
}
/* 获取文件大小 */
off_t size = lseek(fd, 0, SEEK_END);
if (size == -1) { ret = ERR_IO; goto cleanup; }
lseek(fd, 0, SEEK_SET);
/* 分配缓冲区 */
buf = malloc((size_t)size + 1);
if (!buf) { ret = ERR_NOMEM; goto cleanup; }
/* 读取文件 */
ssize_t n = read(fd, buf, (size_t)size);
if (n != size) { ret = ERR_IO; goto cleanup; }
buf[size] = '\0';
*result = buf;
buf = NULL; /* 防止 cleanup 释放 */
cleanup:
if (buf) free(buf);
if (fd >= 0) close(fd);
return ret;
}
/* ========== 模式 4: 安全的包装函数 ========== */
static void *safe_malloc(size_t size)
{
void *p = malloc(size);
if (!p) {
fprintf(stderr, "malloc(%zu) 失败: %s\n", size, strerror(errno));
exit(EXIT_FAILURE);
}
return p;
}
static char *safe_strdup(const char *s)
{
char *dup = strdup(s);
if (!dup) {
fprintf(stderr, "strdup 失败: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return dup;
}
int main(void)
{
/* 使用 goto 清理模式 */
char *content = NULL;
error_t err = process_file("/etc/hostname", &content);
if (err != ERR_OK) {
fprintf(stderr, "错误: %s\n", error_string(err));
return 1;
}
printf("文件内容: %s", content);
free(content);
/* 使用安全包装函数 */
char *dup = safe_strdup("Hello, POSIX!");
printf("复制: %s\n", dup);
free(dup);
return 0;
}
15.1.3 常见 errno 值及处理
| errno | 宏名 | 常见原因 | 建议处理 |
|---|
| 2 | ENOENT | 文件不存在 | 检查路径或创建文件 |
| 13 | EACCES | 权限不足 | 检查权限或请求提升 |
| 12 | ENOMEM | 内存不足 | 减少使用或优雅退出 |
| 4 | EINTR | 被信号中断 | 重试操作 |
| 11 | EAGAIN | 资源暂时不可用 | 稍后重试 |
| 9 | EBADF | 无效文件描述符 | 检查 fd 有效性 |
| 22 | EINVAL | 无效参数 | 检查输入参数 |
| 32 | EPIPE | 管道破裂 | 关闭连接 |
| 110 | ETIMEDOUT | 操作超时 | 重试或报错 |
15.2 安全编程
15.2.1 缓冲区安全
/*
* secure_coding.c - 安全编码实践
* 编译: gcc -Wall -o secure_coding secure_coding.c
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ========== 规则 1: 使用安全的字符串函数 ========== */
/* ❌ 不安全 */
void unsafe_example(char *input)
{
char buf[64];
/* strcpy 不检查长度,可能导致溢出 */
strcpy(buf, input); /* 危险! */
/* sprintf 不检查长度 */
sprintf(buf, "Hello %s", input); /* 危险! */
/* gets 不检查长度(已从 C11 移除) */
/* gets(buf); /* 绝对禁止! */
}
/* ✅ 安全 */
void safe_example(const char *input)
{
char buf[64];
/* 使用 strncpy 并确保 null 终止 */
strncpy(buf, input, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
/* 使用 snprintf */
snprintf(buf, sizeof(buf), "Hello %s", input);
/* 使用 fgets 替代 gets */
if (fgets(buf, sizeof(buf), stdin)) {
/* 去除换行符 */
buf[strcspn(buf, "\n")] = '\0';
}
}
/* ========== 规则 2: 整数溢出检查 ========== */
int safe_add(size_t a, size_t b, size_t *result)
{
if (a > SIZE_MAX - b) return -1; /* 溢出检查 */
*result = a + b;
return 0;
}
int safe_mul(size_t a, size_t b, size_t *result)
{
if (a != 0 && b > SIZE_MAX / a) return -1; /* 溢出检查 */
*result = a * b;
return 0;
}
/* ========== 规则 3: 格式化字符串安全 ========== */
void safe_format(const char *user_input)
{
/* ❌ 用户输入直接作为格式字符串 */
/* printf(user_input); /* 格式字符串攻击! */
/* ✅ 使用 %s 格式化 */
printf("%s\n", user_input);
}
/* ========== 规则 4: 安全的临时文件 ========== */
#include <unistd.h>
#include <fcntl.h>
int create_safe_tempfile(char *path_buf, size_t buf_size)
{
/* 使用 mkstemp 而非 tmpnam/mktemp */
snprintf(path_buf, buf_size, "/tmp/myapp_XXXXXX");
int fd = mkstemp(path_buf);
if (fd == -1) return -1;
/* 确保文件权限安全(仅所有者可读写) */
fchmod(fd, 0600);
return fd;
}
int main(void)
{
/* 整数溢出检查 */
size_t result;
if (safe_mul(1024, 1024, &result) == 0)
printf("1024 * 1024 = %zu\n", result);
/* 安全临时文件 */
char tmp_path[256];
int fd = create_safe_tempfile(tmp_path, sizeof(tmp_path));
if (fd >= 0) {
printf("安全临时文件: %s\n", tmp_path);
write(fd, "safe data", 9);
close(fd);
unlink(tmp_path);
}
safe_example("world");
safe_format("user input with % special chars");
return 0;
}
15.2.2 输入验证清单
| 检查项 | 说明 |
|---|
| 长度 | 字符串不超过缓冲区大小 |
| 范围 | 数值在有效范围内 |
| 类型 | 数字确实是数字,路径不包含 .. |
| 空字符 | 字符串不含嵌入的 \0 |
| 编码 | 有效的 UTF-8 编码 |
| 权限 | 调用者有权限执行操作 |
| 来源 | 不信任外部输入(文件、网络、环境变量) |
15.3 资源管理
15.3.1 RAII 风格(C 语言实现)
/*
* resource_raii.c - C 语言中的 RAII 风格资源管理
* 编译: gcc -Wall -o resource_raii resource_raii.c
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
/* ========== 自动清理的智能指针 ========== */
/* 清理函数类型 */
typedef void (*cleanup_fn)(void *);
/* 带清理函数的包装结构 */
typedef struct {
void *ptr;
cleanup_fn cleanup;
} auto_ptr_t;
/* __attribute__((cleanup)) 实现自动清理(GCC/Clang) */
#define AUTO_FREE __attribute__((cleanup(auto_free_impl)))
#define AUTO_CLOSE __attribute__((cleanup(auto_close_impl)))
static void auto_free_impl(void *p)
{
void **pp = (void **)p;
if (*pp) {
free(*pp);
*pp = NULL;
}
}
static void auto_close_impl(void *p)
{
int *fd = (int *)p;
if (*fd >= 0) {
close(*fd);
*fd = -1;
}
}
/* 使用示例 */
int process_with_cleanup(const char *path)
{
AUTO_FREE char *buf = NULL;
AUTO_CLOSE int fd = -1;
fd = open(path, O_RDONLY);
if (fd == -1) {
perror("open");
return -1;
}
buf = malloc(4096);
if (!buf) {
perror("malloc");
return -1; /* fd 自动关闭 */
}
ssize_t n = read(fd, buf, 4095);
if (n > 0) {
buf[n] = '\0';
printf("读取: %s", buf);
}
return 0;
/* buf 和 fd 在函数返回时自动清理 */
}
int main(void)
{
process_with_cleanup("/etc/hostname");
return 0;
}
15.3.2 资源泄漏预防
/*
* resource_checklist.c - 资源管理检查清单
* 编译: gcc -Wall -o resource_checklist resource_checklist.c
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
/* ========== 文件描述符管理 ========== */
typedef struct {
int fd;
const char *name;
} managed_fd_t;
static managed_fd_t managed_open(const char *path, int flags, mode_t mode)
{
managed_fd_t mfd = { .fd = -1, .name = path };
mfd.fd = open(path, flags, mode);
if (mfd.fd == -1) {
fprintf(stderr, "打开 %s 失败: ", path);
perror("");
}
return mfd;
}
static void managed_close(managed_fd_t *mfd)
{
if (mfd && mfd->fd >= 0) {
close(mfd->fd);
mfd->fd = -1;
}
}
/* ========== 内存管理 ========== */
typedef struct {
void *addr;
size_t size;
} managed_mmap_t;
static managed_mmap_t managed_mmap(size_t size, int prot, int flags)
{
managed_mmap_t mm = { .addr = MAP_FAILED, .size = size };
mm.addr = mmap(NULL, size, prot, flags, -1, 0);
if (mm.addr == MAP_FAILED)
perror("mmap");
return mm;
}
static void managed_munmap(managed_mmap_t *mm)
{
if (mm && mm->addr != MAP_FAILED) {
munmap(mm->addr, mm->size);
mm->addr = MAP_FAILED;
}
}
int main(void)
{
/* 使用管理的资源 */
managed_fd_t mfd = managed_open("/tmp/resource_test",
O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (mfd.fd >= 0) {
write(mfd.fd, "test", 4);
managed_close(&mfd); /* 显式清理 */
}
managed_mmap_t mm = managed_mmap(4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS);
if (mm.addr != MAP_FAILED) {
memset(mm.addr, 0, 4096);
managed_munmap(&mm); /* 显式清理 */
}
unlink("/tmp/resource_test");
printf("资源管理检查完成\n");
return 0;
}
15.4 性能优化
15.4.1 I/O 优化
/*
* perf_io.c - I/O 性能优化技巧
* 编译: gcc -Wall -O2 -o perf_io perf_io.c
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
/* ========== 技巧 1: 批量读写 ========== */
/* ❌ 低效:逐字节处理 */
void process_byte_by_byte(int fd_in, int fd_out)
{
char c;
while (read(fd_in, &c, 1) > 0)
write(fd_out, &c, 1); /* 每次都是系统调用 */
}
/* ✅ 高效:使用缓冲区 */
void process_buffered(int fd_in, int fd_out, size_t buf_size)
{
char *buf = malloc(buf_size);
if (!buf) return;
ssize_t n;
while ((n = read(fd_in, buf, buf_size)) > 0) {
/* 处理数据 */
ssize_t written = 0;
while (written < n) {
ssize_t w = write(fd_out, buf + written, n - written);
if (w == -1) break;
written += w;
}
}
free(buf);
}
/* ========== 技巧 2: 文件预分配 ========== */
#include <sys/stat.h>
void preallocate_file(int fd, off_t size)
{
/* 使用 ftruncate 预分配(比逐块写入快) */
ftruncate(fd, size);
/* Linux 扩展: fallocate(更好的预分配) */
/* fallocate(fd, 0, 0, size); */
}
/* ========== 技巧 3: 减少 stat 调用 ========== */
/* ❌ 低效 */
int is_regular_file_slow(const char *path)
{
struct stat st;
if (stat(path, &st) == -1) return 0;
return S_ISREG(st.st_mode);
}
/* ✅ 高效:使用 fstat(已有 fd 时) */
int is_regular_file_fast(int fd)
{
struct stat st;
if (fstat(fd, &st) == -1) return 0;
return S_ISREG(st.st_mode);
}
/* ========== 技巧 4: 使用 writev 合并小写入 ========== */
#include <sys/uio.h>
void write_header_body(int fd, const char *header, const char *body)
{
struct iovec iov[2] = {
{ .iov_base = (void *)header, .iov_len = strlen(header) },
{ .iov_base = (void *)body, .iov_len = strlen(body) },
};
/* 一次系统调用写入两个缓冲区 */
writev(fd, iov, 2);
}
int main(void)
{
const char *path = "/tmp/perf_test";
/* 比较写入性能 */
struct timespec start, end;
/* 普通写入 */
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < 100000; i++) {
write(fd, "x", 1);
}
clock_gettime(CLOCK_MONOTONIC, &end);
close(fd);
printf("逐字节写入: %.3f ms\n",
(end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_nsec - start.tv_nsec) / 1e6);
/* 缓冲写入 */
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
char buf[4096];
memset(buf, 'x', sizeof(buf));
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < 100000; i += sizeof(buf)) {
write(fd, buf, sizeof(buf));
}
clock_gettime(CLOCK_MONOTONIC, &end);
close(fd);
printf("缓冲写入: %.3f ms\n",
(end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_nsec - start.tv_nsec) / 1e6);
unlink(path);
return 0;
}
15.4.2 性能优化清单
| 优化项 | 说明 |
|---|
| 减少系统调用 | 缓冲读写,批量操作 |
| 使用 writev/readv | 合并多个缓冲区的 I/O |
| mmap 替代 read | 大文件随机访问用 mmap |
| O_DIRECT | 绕过页缓存(特殊场景) |
| MADV_SEQUENTIAL | 提示顺序访问 |
| 减少内存分配 | 预分配、内存池 |
| 避免 false sharing | 线程数据对齐到缓存行 |
| 使用 poll 替代 select | 不受 FD_SETSIZE 限制 |
15.5 代码组织
15.5.1 头文件规范
/*
* example.h - 规范的头文件示例
*/
#ifndef EXAMPLE_H /* 1. Include guard */
#define EXAMPLE_H
#ifdef __cplusplus /* 2. C++ 兼容 */
extern "C" {
#endif
/* 3. 标准库包含 */
#include <stddef.h>
#include <stdint.h>
/* 4. 公开类型定义 */
typedef struct example_ctx example_ctx_t;
typedef enum {
EXAMPLE_OK = 0,
EXAMPLE_ERROR = -1,
} example_status_t;
/* 5. 公开 API 函数 */
example_status_t example_create(example_ctx_t **ctx);
example_status_t example_process(example_ctx_t *ctx, const void *data, size_t len);
void example_destroy(example_ctx_t *ctx);
const char *example_version(void);
#ifdef __cplusplus
}
#endif
#endif /* EXAMPLE_H */
15.5.2 源文件规范
/*
* example.c - 规范的源文件示例
*/
#define _POSIX_C_SOURCE 200809L /* 1. 特性宏 */
/* 2. 自身头文件 */
#include "example.h"
/* 3. 系统头文件 */
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
/* 4. 私有类型和常量 */
#define MAX_ITEMS 1024
struct example_ctx {
int count;
char **items;
pthread_mutex_t lock;
};
/* 5. 私有函数(static) */
static void cleanup_items(example_ctx_t *ctx)
{
for (int i = 0; i < ctx->count; i++)
free(ctx->items[i]);
free(ctx->items);
ctx->items = NULL;
ctx->count = 0;
}
/* 6. 公开 API 实现 */
example_status_t example_create(example_ctx_t **ctx)
{
if (!ctx) return EXAMPLE_ERROR;
example_ctx_t *c = calloc(1, sizeof(*c));
if (!c) return EXAMPLE_ERROR;
pthread_mutex_init(&c->lock, NULL);
*ctx = c;
return EXAMPLE_OK;
}
example_status_t example_process(example_ctx_t *ctx,
const void *data, size_t len)
{
if (!ctx || !data) return EXAMPLE_ERROR;
pthread_mutex_lock(&ctx->lock);
if (ctx->count >= MAX_ITEMS) {
pthread_mutex_unlock(&ctx->lock);
return EXAMPLE_ERROR;
}
char *item = strndup(data, len);
if (!item) {
pthread_mutex_unlock(&ctx->lock);
return EXAMPLE_ERROR;
}
/* 添加到列表 */
char **new_items = realloc(ctx->items,
sizeof(char *) * (ctx->count + 1));
if (!new_items) {
free(item);
pthread_mutex_unlock(&ctx->lock);
return EXAMPLE_ERROR;
}
ctx->items = new_items;
ctx->items[ctx->count++] = item;
pthread_mutex_unlock(&ctx->lock);
return EXAMPLE_OK;
}
void example_destroy(example_ctx_t *ctx)
{
if (!ctx) return;
pthread_mutex_lock(&ctx->lock);
cleanup_items(ctx);
pthread_mutex_unlock(&ctx->lock);
pthread_mutex_destroy(&ctx->lock);
free(ctx);
}
const char *example_version(void)
{
return "1.0.0";
}
15.6 常见陷阱
15.6.1 陷阱清单
| 陷阱 | 说明 | 正确做法 |
|---|
if (pid = fork()) | 赋值而非比较 | pid = fork(); if (pid < 0) |
char *p = malloc(n) | 未检查 NULL | p = malloc(n); if (!p) ... |
read() 返回值忽略 | 可能部分读取 | 循环读取直到完成 |
close() 返回值忽略 | NFS 等延迟写入可能在 close 时失败 | 检查 close() 返回值 |
signal() 替代 sigaction() | 行为不确定 | 始终使用 sigaction() |
sprintf 替代 snprintf | 缓冲区溢出 | 使用 snprintf 并指定大小 |
strcat 替代 strncat | 无长度检查 | 使用 snprintf 拼接 |
malloc 后不初始化 | 未定义行为 | 使用 calloc 或 memset |
线程中使用 strerror() | 非线程安全 | 使用 strerror_r() |
fork() 后使用 malloc | 多线程中 fork 不安全 | fork 后立即 exec |
15.6.2 段错误调试
# 编译时添加调试信息
gcc -Wall -g -fsanitize=address -o program program.c
# AddressSanitizer (ASan) 检测内存错误
./program # ASan 会报告内存越界、use-after-free 等
# 使用 Valgrind
valgrind --leak-check=full ./program
# 使用 GDB 调试
gcc -Wall -g -o program program.c
gdb ./program
(gdb) run
(gdb) bt # 查看调用栈
15.7 日志与监控
15.7.1 POSIX 日志模块
/*
* posix_logger.h - 生产级日志模块
*/
#ifndef POSIX_LOGGER_H
#define POSIX_LOGGER_H
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR,
LOG_FATAL,
} log_level_t;
static log_level_t g_log_level = LOG_INFO;
static FILE *g_log_file = NULL;
static const char *log_level_str(log_level_t level)
{
switch (level) {
case LOG_DEBUG: return "DEBUG";
case LOG_INFO: return "INFO ";
case LOG_WARN: return "WARN ";
case LOG_ERROR: return "ERROR";
case LOG_FATAL: return "FATAL";
default: return "?????";
}
}
static void log_set_level(log_level_t level) { g_log_level = level; }
static void log_set_file(FILE *fp) { g_log_file = fp; }
static void log_write(log_level_t level, const char *file,
int line, const char *fmt, ...)
{
if (level < g_log_level) return;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm tm;
localtime_r(&ts.tv_sec, &tm);
char timebuf[32];
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm);
/* basename */
const char *basename = strrchr(file, '/');
basename = basename ? basename + 1 : file;
FILE *out = g_log_file ? g_log_file : stderr;
fprintf(out, "%s.%03ld [%s] %s:%d: ",
timebuf, ts.tv_nsec / 1000000,
log_level_str(level), basename, line);
va_list args;
va_start(args, fmt);
vfprintf(out, fmt, args);
va_end(args);
fprintf(out, "\n");
fflush(out);
}
#define LOG_DEBUG(...) log_write(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) log_write(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...) log_write(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) log_write(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_FATAL(...) log_write(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#endif /* POSIX_LOGGER_H */
15.8 代码审查清单
| 类别 | 检查项 |
|---|
| 错误处理 | 所有系统调用返回值是否检查?错误路径是否释放资源? |
| 缓冲区安全 | 是否使用安全函数(snprintf, strncpy)?长度是否正确? |
| 内存管理 | malloc 是否检查 NULL?是否所有路径都释放内存? |
| 文件描述符 | 所有 fd 是否在错误路径中关闭? |
| 信号安全 | 信号处理函数是否只使用异步安全函数? |
| 线程安全 | 共享数据是否有锁保护?是否使用线程安全函数? |
| 整数溢出 | 乘法/加法是否有溢出检查? |
| 格式化字符串 | 用户输入是否作为 %s 参数而非格式字符串? |
| 权限 | 临时文件权限是否为 0600?是否有最小权限? |
| 可移植性 | 是否依赖平台特定扩展?数据类型是否正确? |
15.9 业务场景:生产就绪的 TCP 服务
/*
* production_server.c - 生产就绪的 TCP 服务器框架
* 编译: gcc -Wall -O2 -o production_server production_server.c -lpthread
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <pthread.h>
/* ========== 日志 ========== */
#include "posix_logger.h" /* 使用上面的日志模块 */
/* ========== 优雅关闭 ========== */
static volatile sig_atomic_t g_shutdown = 0;
static void signal_handler(int sig)
{
(void)sig;
g_shutdown = 1;
}
static void setup_signals(void)
{
struct sigaction sa = {
.sa_handler = signal_handler,
.sa_flags = SA_RESTART,
};
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
signal(SIGPIPE, SIG_IGN); /* 忽略 SIGPIPE */
}
/* ========== 套接字工具 ========== */
static int set_nonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
static int create_server_socket(int port)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) { LOG_FATAL("socket: %s", strerror(errno)); return -1; }
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = INADDR_ANY,
};
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
LOG_FATAL("bind: %s", strerror(errno));
close(fd);
return -1;
}
if (listen(fd, 128) == -1) {
LOG_FATAL("listen: %s", strerror(errno));
close(fd);
return -1;
}
LOG_INFO("服务器启动,端口 %d, PID=%d", port, getpid());
return fd;
}
/* ========== 客户端处理 ========== */
static void *handle_client(void *arg)
{
int client_fd = *(int *)arg;
free(arg);
char buf[4096];
ssize_t n;
while (!g_shutdown) {
n = read(client_fd, buf, sizeof(buf));
if (n <= 0) break;
/* 回声 */
ssize_t written = 0;
while (written < n) {
ssize_t w = write(client_fd, buf + written, n - written);
if (w == -1) { if (errno == EINTR) continue; break; }
written += w;
}
}
close(client_fd);
return NULL;
}
/* ========== 主程序 ========== */
int main(int argc, char *argv[])
{
int port = argc > 1 ? atoi(argv[1]) : 8080;
log_set_level(LOG_INFO);
setup_signals();
int server_fd = create_server_socket(port);
if (server_fd == -1) return 1;
while (!g_shutdown) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client_fd = accept(server_fd,
(struct sockaddr *)&client_addr, &len);
if (client_fd == -1) {
if (errno == EINTR) continue;
LOG_ERROR("accept: %s", strerror(errno));
continue;
}
/* TCP_NODELAY 减少延迟 */
int opt = 1;
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
LOG_INFO("新连接 fd=%d", client_fd);
/* 创建线程处理客户端 */
int *fd_arg = malloc(sizeof(int));
if (!fd_arg) { close(client_fd); continue; }
*fd_arg = client_fd;
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&tid, &attr, handle_client, fd_arg) != 0) {
LOG_ERROR("pthread_create: %s", strerror(errno));
free(fd_arg);
close(client_fd);
}
pthread_attr_destroy(&attr);
}
LOG_INFO("服务器正在关闭...");
close(server_fd);
LOG_INFO("服务器已关闭");
return 0;
}
15.10 总结:POSIX 系统编程核心原则
| 原则 | 说明 |
|---|
| 一切皆文件 | 文件、设备、管道、套接字统一使用 fd 操作 |
| 始终检查错误 | 系统调用返回值和 errno |
| 最小权限 | 只请求所需的权限和资源 |
| 资源显式释放 | 文件描述符、内存、锁用后释放 |
| 使用标准接口 | POSIX API > 平台扩展 |
| 安全编码 | 缓冲区检查、整数溢出、格式化字符串 |
| 线程安全 | 使用 _r 函数、适当的同步机制 |
| 信号安全 | 处理函数中只使用异步安全函数 |
| 可移植性 | <stdint.h> 类型、特性宏、条件编译 |
| 测试覆盖 | 边界条件、错误路径、跨平台测试 |
15.11 扩展阅读
- 《The Art of Unix Programming》 — Eric Raymond 著,Unix 哲学
- 《Secure Programming Cookbook》 — Viega & Messier 著,安全编程
- CERT C Coding Standard:https://wiki.sei.cmu.edu/confluence/display/c
- Linux man-pages 项目:https://man7.org/linux/man-pages/
- 《UNIX and Linux System Administration Handbook》 — 系统管理权威
- Google C++ Style Guide(C 部分适用):https://google.github.io/styleguide/cppguide.html
- MISRA C 标准:嵌入式安全编程规范
15.12 全教程总结
恭喜你完成了 POSIX 标准详解教程的全部 15 章学习!回顾一下:
| 部分 | 章节 | 核心收获 |
|---|
| 基础篇 | 1-3 | POSIX 标准体系、文件系统模型、进程生命周期 |
| 核心机制篇 | 4-7 | 线程同步、信号处理、I/O 模型、IPC 机制 |
| 进阶篇 | 8-12 | 网络编程、内存管理、时间函数、环境配置、Shell 脚本 |
| 工程篇 | 13-15 | 可移植性、合规测试、最佳实践 |
POSIX 不仅是一份技术标准,更是 Unix 哲学的体现——简单、可组合、可移植。掌握 POSIX,就是掌握了系统编程的通用语言。