• 首页
  • 搜索
  • 工具
  • 分类
  • 日志
  • 友链
  • 图片

It's Geek KingYoungy

KEEP CHALLENGE

分类 C/C++Unix/Linux应用层程序设计 下的文章

C/C++Unix/Linux应用层程序设计

Local聊天室

2025-03-16 浏览量 424 暂无评论
LOCAL聊天室_client.1.1.drawio.png

本项目基于SYSTEM V消息队列、POSIX线程、ncurses库进行实现,能够创建多个聊天室,用户加入聊天室即可与同一聊天室的其他所有用户下聊天。类似于wx与qq的群聊功能。
使用图:屏幕截图 2025-03-16 181259.png
流程图:LOCAL聊天室_client.1.1.drawio.png LOCAL聊天室_server.1.1.drawio.png
开发日志:
教训(花了我近乎两个小时):
共享库命名一定要规范:.so.x.y.z
不然ld链接器找不到
AI的能力不足以找到错误。

2025 3.15 又一个教训
客户端,服务器头文件不统一。服务器改了头文件的部分内容,客户端没有及时修改
花了我又快两个小时。
解决方法:今后项目根目录新建include文件夹存放头文件

开发的时候,在关键的步骤一定要用printf打印状态/要干什么事/干完了什么事
不可自负,相信能一遍做完大项目
若在开发前期写好输出到日志文件的函数,将会事半功倍,不用在大功告成时注释掉或删除掉密密麻麻的printf了
但这样的坏处就是不能边运行边看。

2025 3.16 最后两个教训
收包输出但是得到乱码不是因为别的,就是发的缓冲区与收的缓冲区大小没设置好,以为发送端会发那么多没想到那个发送大小宏定义竟然是0,最后导致内存访问出界了

本来想搞一个无限循环了的,手贱在一个if语句的最后加了一个break,还在想服务器服务线程怎么莫名其妙不吭声了,操原来是break了。
还在想有什么逻辑错误,没想到是这种小错误。

- 阅读全文 -
C/C++Unix/Linux应用层程序设计

深入理解pthread互斥量与条件变量的使用

2025-03-12 浏览量 459 暂无评论

深入理解pthread互斥量与条件变量的使用

在多线程编程中,线程同步是一个关键问题。为了避免多个线程同时访问共享资源导致的数据竞争和不一致,POSIX线程库(pthread)提供了互斥量(Mutex)和条件变量(Condition Variable)两种重要的同步机制。本文将详细介绍它们的使用方法,并通过代码示例帮助读者更好地理解。

1. 互斥量(Mutex)

互斥量是一种简单的同步机制,用于保护共享资源,确保同一时间只有一个线程可以访问该资源。互斥量的基本操作包括初始化、加锁、解锁和销毁。

1.1 互斥量的初始化

互斥量可以通过静态或动态方式初始化。静态初始化使用宏PTHREAD_MUTEX_INITIALIZER,而动态初始化使用pthread_mutex_init函数。

// 静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 动态初始化
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
1.2 互斥量的加锁与解锁

线程在访问共享资源前需要加锁,访问完成后解锁。如果互斥量已被其他线程锁定,当前线程会被阻塞,直到互斥量被解锁。

pthread_mutex_lock(&mutex);
// 访问共享资源
pthread_mutex_unlock(&mutex);
1.3 互斥量的销毁

当互斥量不再需要时,应调用pthread_mutex_destroy函数销毁它,以释放系统资源。

pthread_mutex_destroy(&mutex);

2. 条件变量(Condition Variable)

条件变量用于生产者-消费者模型中,防止消费者拿到互斥量后的忙等待。它允许线程在某个条件未满足时进入等待状态并释放互斥锁,并在条件满足时被唤醒并请求互斥锁。条件变量通常与互斥量一起使用。

2.1 条件变量的初始化

条件变量也可以通过静态或动态方式初始化。

// 静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 动态初始化
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
2.2 条件变量的等待与通知

线程在等待条件变量时,会释放持有的互斥量,并在条件满足时重新获得互斥量。其他线程可以通过pthread_cond_signal或pthread_cond_broadcast唤醒等待的线程。

// 等待条件变量
pthread_mutex_lock(&mutex);
while (condition == false) { //一定要将循环检查共享资源,防止被唤醒后共享资源被没有被生产者生产
    pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);

// 通知条件变量
pthread_mutex_lock(&mutex);
condition = true;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
2.3 条件变量的销毁

当条件变量不再需要时,应调用pthread_cond_destroy函数销毁它。

pthread_cond_destroy(&cond);

3. 示例代码

以下是一个使用互斥量和条件变量的简单示例,展示了如何实现线程间的同步。

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;

void *thread_function(void *arg) {
    pthread_mutex_lock(&mutex);
    while (ready == 0) {
        pthread_cond_wait(&cond, &mutex);
    }
    printf("Thread condition met, proceeding\n");
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, thread_function, NULL);

    pthread_mutex_lock(&mutex);
    printf("Main thread signaling condition variable\n");
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);

    pthread_join(thread_id, NULL);
    return 0;
}

在这个示例中,主线程通过条件变量唤醒等待的线程,确保线程在条件满足时继续执行。

4. 总结

互斥量和条件变量是多线程编程中常用的同步机制。互斥量用于保护共享资源,而条件变量用于防止线程的忙等待,协调生产者与消费者线程。通过合理使用这两种机制,可以有效地避免数据竞争和死锁问题,提高多线程程序的稳定性和性能。

希望本文能帮助你更好地理解和使用pthread互斥量与条件变量。如果你有任何问题或需要进一步的帮助,请随时留言!

- 阅读全文 -
C/C++Unix/Linux应用层程序设计

System V 消息队列总结

2025-03-05 浏览量 436 暂无评论

System V 消息队列总结:

前言

消息队列是一种面向消息的ipc机制,即内核会保证进程对消息队列IO时数据块(消息)的整体性。且其是双工的,任何进程都可以往里放消息,同时任何进程都可以往里读消息(甚至读到其自己的)。


1. msgget - 创建消息队列/获取已有消息队列的标识符

#include <sys/msg.h>

int msgget(key_t key, int msgflg);

• 参数:
• key:消息队列的唯一键值(通常由 ftok 生成),或使用 IPC_PRIVATE 创建私有队列。
• msgflg:标志位(如 IPC_CREAT 创建队列,IPC_EXCL 配合 IPC_CREAT 确保队列不存在时新建)。
• 返回值:成功返回消息队列标识符(msqid),失败返回 -1。


2. msgsnd - 发送消息

#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

• 参数:
• msqid:消息队列标识符。
• msgp:指向消息结构体的指针(需自定义,见下文)。
• msgsz:消息内容(mtext)的字节数(不包括 mtype 字段)。
• msgflg:标志位(如 IPC_NOWAIT 非阻塞发送,队列满时立即返回错误)。
• 返回值:成功返回 0,失败返回 -1。

注意:就算多进程/线程同时非阻塞地调用该函数,内核依旧会保证消息之间地独立性。因为内核并不是依靠其阻塞来保证独立性的。


3. msgrcv - 接收消息

#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

• 参数:
• msqid:消息队列标识符。
• msgp:接收消息的缓冲区指针(需自定义消息结构体)。
• msgsz:缓冲区中 mtext 部分的最大容量。
• msgtyp:指定接收消息的类型(见下文规则)。
• msgflg:标志位(如 IPC_NOWAIT 非阻塞接收,MSG_NOERROR 允许截断过长消息)。
• 返回值:成功返回实际接收的 mtext 字节数,失败返回 -1。


4. msgctl - 控制消息队列

#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

• 参数:
• msqid:消息队列标识符。
• cmd:控制命令(常用值):

◦ **`IPC_STAT`**:获取队列元数据到 `buf`。
◦ **`IPC_SET`**:通过 `buf` 修改队列元数据(如权限、容量限制)。
◦ **`IPC_RMID`**:立即删除队列(`buf` 可设为 `NULL`)。

• buf:指向 struct msqid_ds 的指针(用于读写元数据)。
• 返回值:成功返回 0,失败返回 -1。


关键数据结构

消息结构体(需自定义)

struct msgbuf {
    long mtype;     // 消息类型(必须 > 0)
    char mtext[];  // 消息内容(实际长度由 `msgsz` 指定,这里的字符数组表示就是一块内存,可以是各种数据的组合)
};

队列元数据结构体(struct msqid_ds)

提取自内核维护的msg_ids数据结构的对应entries指针指向的结构。

struct msqid_ds {
    struct ipc_perm msg_perm;  // 权限信息(所有者、读写权限等)
    time_t          msg_stime; // 最后发送时间
    time_t          msg_rtime; // 最后接收时间
    time_t          msg_ctime; // 最后修改时间
    unsigned long   msg_cbytes; // 当前队列中的字节数
    msgqnum_t       msg_qnum;   // 当前队列中的消息数
    msglen_t        msg_qbytes; // 队列最大容量(字节数)
    pid_t           msg_lspid;  // 最后发送消息的进程PID
    pid_t           msg_lrpid;  // 最后接收消息的进程PID
};

核心原理

  1. 消息类型(mtype):
    • 用于分类消息,接收时可指定类型过滤(如 msgtyp > 0 匹配特定类型,msgtyp = 0 接收任意类型,msgtyp < 0 接受mtype小于msgtyp绝对值的mtype最小的消息)。
    • 类型为 long 类型,必须为正整数。
  2. 内核管理:
    • 消息队列以内核对象形式存在,独立于进程生命周期(需显式调用 msgctl(IPC_RMID) 删除)。
    • 消息以链表形式存储,保证先进先出(FIFO),但可通过 msgtyp 实现优先级读取。
  3. 同步与原子性:
    • msgsnd 和 msgrcv 是原子操作:单次调用发送或接收一条完整消息。
    • 若队列满或空,默认阻塞进程(除非指定 IPC_NOWAIT)。

示例代码片段

// 定义消息结构体(实际使用需动态分配 mtext 大小)
struct my_msg {
    long mtype;
    char mtext[100];
};

// 发送消息
struct my_msg msg_send = {1, "Hello"};
msgsnd(msqid, &msg_send, strlen(msg_send.mtext), 0);

// 接收消息
struct my_msg msg_recv;
msgrcv(msqid, &msg_recv, sizeof(msg_recv.mtext), 1, 0);

总结

• 适用场景:需要类型过滤、原子性操作或异步通信的进程间交互(如任务分发、事件通知)。
• 缺点:依赖内核资源,需手动管理队列生命周期;跨平台支持较弱(推荐优先考虑 POSIX 消息队列或套接字)。

- 阅读全文 -
C/C++Unix/Linux应用层程序设计

C语言可变参数与命令行参数解析:stdarg与getopt详解

2025-03-04 浏览量 459 暂无评论

C语言可变参数与命令行参数解析:stdarg与getopt详解


一、可变参数处理:stdarg.h

1.1 核心功能

stdarg.h 头文件提供在函数中处理不定数量参数的能力,常用于实现类似printf()的格式化输出函数。通过宏定义实现,与平台特性强关联。

1.2 核心类型与函数原型

#include <stdarg.h>

// 参数列表指针类型
va_list;

// 初始化va_list指针,last_param是最后一个固定参数名
void va_start(va_list ap, last_param);

// 获取下一个参数,type为参数类型(如int、char*)
// 一定要确定可变参数的个数,不要在最后一个参数被读取了依然调用该函数
type va_arg(va_list ap, type);

// 清理va_list指针
void va_end(va_list ap);

1.3 使用步骤

  1. 函数声明使用省略号(如int func(int n, ...))
  2. 定义va_list变量
  3. va_start初始化指针
  4. va_arg逐个读取参数
  5. va_end释放资源

1.4 示例:简化版printf函数实现

#include <stdarg.h>
#include <stdio.h>

void my_printf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);  // 初始化参数列表

    for (const char *p = fmt; *p != '\0'; p++) {
        if (*p != '%') {
            putchar(*p);  // 普通字符直接输出
            continue;
        }

        // 处理格式符 %
        switch (*++p) {    // 跳过%并读取下一个字符
            case 'd': {    // 处理整数
                int num = va_arg(ap, int);
                printf("%d", num);  // 借用标准库输出
                break;
            }
            case 's': {    // 处理字符串
                char *str = va_arg(ap, char*);
                printf("%s", str);
                break;
            }
            case 'c': {    // 处理字符
                char ch = va_arg(ap, int);  // char提升为int传递
                putchar(ch);
                break;
            }
            case '%': {    // 转义%符号
                putchar('%');
                break;
            }
            default: {     // 未知格式符
                putchar('?');
                break;
            }
        }
    }

    va_end(ap);  // 清理参数列表
}

// 调用示例:
// my_printf("ID:%d, Name:%s, Level:%c%%", 101, "Alice", 'A');
// 输出: ID:101, Name:Alice, Level:A%

1.5 注意事项

• 必须至少包含一个固定参数
• va_arg必须按实际类型调用(类型错误导致未定义行为)
• C99后支持va_copy复制参数列表


二、命令行参数解析:getopt()

2.1 核心功能

unistd.h中的getopt()函数提供命令行选项解析能力,支持带参数选项(如-f filename)、组合短选项(如-abc)等常见格式。

2.2 函数原型与全局变量

#include <unistd.h>

int getopt(int argc, char *const argv[],
           const char *optstring);

// 全局变量:
extern char *optarg;  // 当前选项的参数值
extern int optind;    // 下一个要处理的argv索引,在getopt返回-1时其值为第一个非选项或选项参数的索引
extern int optopt;    // 无效选项字符

选项字符串格式:

• 单个字符:表示无参数选项(如"a"对应-a)
• 字符后接冒号:必须带参数(如"f:"对应-f file)
• 字符后接双冒号:可选参数(非标准扩展,谨慎使用)

2.3 使用流程

int main(int argc, char **argv) {
    int opt;
    while ((opt = getopt(argc, argv, "hf:v")) != -1) {
        switch (opt) {
            case 'h':
                printf("Help info\n");
                break;
            case 'f':
                printf("File: %s\n", optarg); // 获取参数
                break;
            case 'v':
                printf("Version 1.0\n");
                break;
            case '?': // 未知选项
                printf("Unknown option: %c\n", optopt);
                break;
        }
    }
    // 处理剩余参数(非选项参数)
    for (int i = optind; i < argc; i++) {
        printf("Extra argument: %s\n", argv[i]);
    }
    return 0;
}

2.4 运行示例

$ ./demo -f config.txt -v input.txt
File: config.txt
Version 1.0
Extra argument: input.txt

2.5 注意事项

• 选项参数通过optarg获取
• 遇到非选项参数时停止解析(可用--强制结束选项解析)
• 重复调用getopt()继续解析时需重置optind = 1


三、总结对比

特性stdarggetopt
主要用途函数内部处理可变参数解析命令行选项
核心操作va_start/va_arg/va_end宏getopt()函数循环调用
典型场景自定义格式化输出、数学计算开发命令行工具
参数类型安全无(依赖开发者保证)选项字符预先定义
跨平台性标准C库支持POSIX系统支持(Windows需额外实现)

掌握这两个工具,能够显著提升C语言函数设计的灵活性和命令行工具的开发效率。实际使用时需注意参数类型匹配与错误处理,避免因类型错误导致程序崩溃。

- 阅读全文 -
  1. 1
  2. ...
  3. 4
  4. 5
  5. 6
  6. 7

浏览量 : 17462

© 2025 It's Geek KingYoungy. Power By Typecho . Theme by Shiyi

浙ICP备2025160639号  |  浙公网安备33020502001222号

This is just a placeholder img.