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

It's Geek KingYoungy

KEEP CHALLENGE

分类 多进程/多线程并发编程 下的文章

多进程/多线程并发编程

Unix Domain Socket 编程:字节流与数据报套接字详解

2025-03-26 浏览量 123 暂无评论

Unix Domain Socket 编程:字节流与数据报套接字详解


一、字节流套接字(SOCK_STREAM)

1. 核心特性

• 可靠传输:提供类似 TCP 的有序、无丢失数据流,无记录边界。
• 全双工通信:支持双向数据传输,需通过 listen()/accept() 建立连接。
• 适用场景:需高可靠性的连续数据交互(如文件传输、RPC 服务)。

2. 核心系统调用

(1)socket() 创建套接字

int socket(int domain, int type, int protocol);

• 参数:
• domain:必须为 AF_UNIX 或 AF_LOCAL。
• type:设为 SOCK_STREAM。
• protocol:固定为 0。
• 返回值:成功返回文件描述符,失败返回 -1(如 EPROTONOSUPPORT 协议不支持)。

(2)bind() 绑定地址

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

• 地址结构:

struct sockaddr_un {
    sa_family_t sun_family;    // AF_UNIX
    char sun_path[108];        // 文件路径或抽象名称
};

• 路径类型:
• 普通路径:如 /tmp/mysocket.sock,需确保路径可写且无冲突。
• 抽象命名空间(Linux 特有):sun_path[0] = '\0',后续字符为抽象名(无需文件实体)。
• addrlen 计算:offsetof(struct sockaddr_un, sun_path) + strlen(name)(抽象名需 +1)。

(3)listen() 监听队列

int listen(int sockfd, int backlog);

• backlog:最大等待连接数,建议设为 SOMAXCONN。
• 错误处理:若队列满,后续连接请求直接返回 ECONNREFUSED。

(4)accept() 接收连接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

• addr 参数:通常设为 NULL(客户端地址无意义)。
• 返回值:新套接字描述符专用于该连接。

(5)数据传输

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

• flags 标志:
• MSG_OOB:带外数据(需双方支持)。
• MSG_DONTWAIT:非阻塞模式。


二、数据报套接字(SOCK_DGRAM)

1. 核心特性

• 保留消息边界:每次 send() 对应一次 recv(),数据按报文独立接收。
• 无连接特性:无需建立连接,可直接发送数据。
• 可靠性:数据不丢失但无序(与 UDP 不同,Unix 域数据报本身可靠)。
• 适用场景:日志传输、实时状态广播等低延迟场景。

2. 核心系统调用

(1)socket() 创建套接字

int socket(int domain, int type, int protocol);

• type:设为 SOCK_DGRAM。

(2)bind() 绑定地址

• 地址规则:同字节流套接字,但允许同一地址被多次绑定(需设置 SO_REUSEADDR)。

(3)直接数据收发

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

• dest_addr:目标地址(必须已绑定)。
• src_addr:接收时可获取发送方地址。

(4)连接模式(可选)

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

• 作用:绑定默认目标地址,后续可使用 send()/recv()以及write()/read()。


三、Linux 抽象 Socket 名空间(跨进程通用)

1. 实现方式

• 命名规则:sun_path[0] = '\0',后续字符为抽象名(如 \0myapp)。
• 优势:
• 无文件系统依赖,避免路径权限问题。
• 进程退出后自动释放,无需手动清理。

2. 系统查询

ss -x -a | grep '@'        # 查看活跃抽象套接字
cat /proc/net/unix         # 内核级列表(含 inode 和权限)

四、进阶功能对比

特性字节流套接字数据报套接字
连接管理需 listen()/accept()无连接
记录边界无边界(连续流)保留边界
最大消息长度无限制受限于内核缓冲区(通常 130KB)
描述符传递支持 (sendmsg())支持 (sendmsg())
凭证传递支持(SO_PASSCRED)支持(SO_PASSCRED)

五、总结

字节流套接字适用于需要可靠连续数据流的场景(如数据库连接),而数据报套接字更适合短消息、无连接交互。Linux 抽象命名空间通过消除文件依赖提升了安全性和便捷性,尤其在容器化环境中优势显著。开发者应根据业务需求选择通信模式,并注意两者的系统调用差异。

- 阅读全文 -
多进程/多线程并发编程,系统编程基础

文件锁技术详解:flock与fcntl系统调用

2025-03-25 浏览量 129 暂无评论

文件锁技术详解:flock与fcntl系统调用


一、flock系统调用

1.1 功能描述

flock()提供文件级全局锁机制,可对整个文件施加共享锁或互斥锁。该锁为建议性锁(需主动检查),常用于进程间同步文件访问,但不保证强制排他性。该锁由内核维护,存储在内核的打开文件句柄表中。因此只有该文件句柄没有任何进程的文件描述符指向时,才会删除该锁。该锁与其文件句柄共同被删除。

1.2 函数声明

#include <sys/file.h>
int flock(int fd, int operation);

1.3 参数详解

参数类型说明
fdint文件描述符,需通过open()或fileno(FILE*)获取
operationint锁操作类型,支持以下宏组合:
- LOCK_SH:共享锁(允许多进程并发读)
- LOCK_EX:互斥锁(仅允许单进程读写)
- LOCK_UN:释放锁
- LOCK_NB:非阻塞模式(与上述宏用 `` 组合,锁冲突时立即返回)

1.4 返回值

• 成功:返回0
• 失败:返回-1,错误码存于errno,常见错误:
• EAGAIN:非阻塞模式下锁冲突
• EBADF:无效文件描述符
• EINTR:被信号中断


二、fcntl系统调用

2.1 功能描述

fcntl()称为POSIX记录锁支持,记录级细粒度锁,可对文件指定区域加锁。提供强制锁与建议锁两种模式(依赖文件系统支持),具备更精细的控制能力。直接由文件系统维护,因此父子进程即使共享文件描述符,确不共享锁。但是任何进程调用close关闭文件描述符,该锁就被删除(内核所为)。

2.2 函数声明

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, struct flock *lock);

2.3 参数详解

参数类型说明
fdint文件描述符
cmdint锁操作命令:
- F_GETLK:检测锁状态(不实际加锁)
- F_SETLK:非阻塞式设置/释放锁
- F_SETLKW:阻塞式设置锁(等待锁释放)
lockstruct flock*描述锁类型与范围的指针,结构体成员如下:
struct flock {                                                      
   short l_type;   // 锁类型:F_RDLCK/F_WRLCK/F_UNLCK              
   short l_whence; // 基准位置:SEEK_SET/SEEK_CUR/SEEK_END         
   off_t l_start;  // 偏移量(相对于l_whence)                     
   off_t l_len;    // 锁定字节数(0表示至文件尾)                  
   pid_t l_pid;    // 持有锁的进程ID(F_GETLK时填充)             
};                                                                  

2.4 返回值

• 成功:根据cmd不同返回不同:
• F_GETLK:非负值(锁信息存于lock结构)
• F_SETLK/F_SETLKW:0
• 失败:返回-1,常见错误:
• EACCES/EAGAIN:锁冲突(非阻塞模式)
• EDEADLK:死锁风险(仅限F_SETLKW)
• EINVAL:无效参数


三、关键特性对比

特性flockfcntl
锁粒度整个文件文件区域(字节级)
锁类型共享/互斥共享/互斥/释放
阻塞控制支持LOCK_NB非阻塞通过F_SETLK/F_SETLKW区分
锁继承子进程不继承锁子进程继承锁
锁检测需显式调用检查内置锁状态查询(F_GETLK)
跨进程兼容性与fcntl锁互斥与flock锁互斥
自动放锁关闭一个文件的所有文件描述符关闭一个文件的任意文件描述符

四、应用场景建议

• 简单同步:优先使用flock,代码简洁且适用于全文件锁定
• 高性能并发:选择fcntl,利用区域锁减少竞争
• 强制锁需求:需结合文件系统配置(如mount -o mand)
• 单实例daemon:在/var/run下创建“进程号.pid”文件,并持有该文件的POSIX写锁。一旦有另一个进程运行同一个程序,发现取得不了锁后就知道已经有一个实例在运行了。一般该文件先用truncate或fturncate截断为长度0,然后写入进程的pid,最后在程序结束的时候删除(remove或unlink)该文件防止占用文件系统资源。

- 阅读全文 -
多进程/多线程并发编程

管道与FIFO的技术解析及系统调用详解

2025-03-24 浏览量 120 暂无评论

管道与FIFO的技术解析及系统调用详解

1. 管道(Pipe)与FIFO概述

管道是Linux进程间通信(IPC)的核心机制之一,分为匿名管道(pipe)和命名管道(FIFO)。两者的本质差异在于:
• 匿名管道:仅适用于有亲缘关系的进程(如父子进程),通过文件描述符传递实现通信。
• 命名管道(FIFO):通过文件系统路径标识,允许任意进程(无需亲缘关系)访问,具备持久化特性。


2. 匿名管道相关系统调用

2.1 pipe() - 创建匿名管道

功能描述

在内核中创建一块缓冲区,返回两个文件描述符,分别对应管道的读端和写端。

函数声明

#include <unistd.h>
int pipe(int pipefd[2]);

参数解析

• pipefd[2]:整型数组,用于存储管道的读端和写端文件描述符:
• pipefd[0]:读端描述符,用于从管道读取数据。
• pipefd[1]:写端描述符,用于向管道写入数据。

返回值

• 成功:返回0,文件描述符写入pipefd。
• 失败:返回-1,并设置errno(如EMFILE表示进程文件描述符耗尽)。


3. 命名管道(FIFO)相关系统调用

3.1 mkfifo() - 创建命名管道

功能描述

在文件系统中创建一个FIFO特殊文件,作为进程间通信的通道。

函数声明

#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

参数解析

• pathname:FIFO文件的路径名,需保证路径唯一性。
• mode:文件权限模式(八进制掩码),常用宏定义:
• S_IRUSR:用户读权限(0400)
• S_IWUSR:用户写权限(0200)
• S_IRGRP:组读权限(0040)
• 组合示例:0666表示所有用户可读写。

返回值

• 成功:返回0。
• 失败:返回-1,并设置errno(如EEXIST表示文件已存在)。


3.2 open() - 打开FIFO文件

功能描述

打开已存在的FIFO文件,返回文件描述符用于后续读写操作。

函数声明

#include <fcntl.h>
int open(const char *pathname, int flags);

参数解析

• pathname:FIFO文件路径。
• flags:打开模式标志(位掩码),常用选项:
• O_RDONLY:只读模式。
• O_WRONLY:只写模式。
• O_NONBLOCK:非阻塞模式(默认阻塞):

◦ 读端未打开时,写操作返回`ENXIO`错误。
◦ 写端未打开时,读操作立即返回`0`。

返回值

• 成功:返回文件描述符(非负整数)。
• 失败:返回-1,并设置errno(如ENOENT表示路径不存在)。


4. 通用读写操作

4.1 read() - 从管道/FIFO读取数据

函数声明

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数解析

• fd:文件描述符(管道读端或FIFO描述符)。
• buf:用户缓冲区指针,用于存储读取的数据。
• count:请求读取的字节数。

返回值

• 成功:返回实际读取的字节数(可能小于count)。
• 失败:返回-1,并设置errno(如EAGAIN表示非阻塞模式下无数据)。


4.2 write() - 向管道/FIFO写入数据

函数声明

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数解析

• fd:文件描述符(管道写端或FIFO描述符)。
• buf:待写入数据的缓冲区指针。
• count:请求写入的字节数。

返回值

• 成功:返回实际写入的字节数(可能小于count)。
• 失败:返回-1,并设置errno(如EPIPE表示读端已关闭)。


4.3 close() - 关闭文件描述符

函数声明

#include <unistd.h>
int close(int fd);

参数解析

• fd:待关闭的文件描述符。

返回值

• 成功:返回0。
• 失败:返回-1,并设置errno(如EBADF表示无效描述符)。


4.4 unlink() - 删除FIFO文件

函数声明

#include <unistd.h>
int unlink(const char *pathname);

参数解析

• pathname:FIFO文件路径。

返回值

• 成功:返回0。
• 失败:返回-1,并设置errno(如EACCES表示权限不足)。


5. 关键特性对比

特性匿名管道(pipe)命名管道(FIFO)
文件系统可见性不可见(仅内核缓冲区)可见(通过路径访问)
进程关系限制需有亲缘关系无限制
原子性保障单次写入≤PIPE_BUF(4KB)同左
生命周期随进程结束销毁需显式调用unlink()删除

6. 总结

管道与FIFO是Linux IPC的基础组件,理解其系统调用的参数及行为对开发可靠的进程间通信程序至关重要。开发时需特别注意阻塞/非阻塞模式选择、原子写入限制及资源泄漏预防(及时关闭描述符)。

- 阅读全文 -
多进程/多线程并发编程

POSIX进程间通信技术详解

2025-03-23 浏览量 142 暂无评论

POSIX IPC机制详解:消息队列、信号量与共享内存

在操作系统的多进程协作中,POSIX IPC(可移植操作系统接口进程间通信)提供了一套标准化的进程间通信机制,涵盖消息队列、信号量和共享内存三种核心方式。相较于传统的System V IPC,POSIX IPC具有更简洁的API设计和更高的跨平台兼容性。本文将深入解析这三种机制的系统调用及其实现细节。


一、消息队列(Message Queues)

消息队列是一种基于链表的异步通信机制,支持消息的优先级和非阻塞读写。

1. mq_open:创建或打开消息队列

#include <fcntl.h>
#include <mqueue.h>

mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

• 功能:创建或打开一个消息队列。
• 参数:
• name:队列名称,以/开头且不含其他/(如/my_queue)。
• oflag:标志位,常用组合:

◦ `O_RDONLY`:只读模式
◦ `O_WRONLY`:只写模式
◦ `O_RDWR`:读写模式
◦ `O_CREAT`:若不存在则创建
◦ `O_EXCL`:与`O_CREAT`联用,确保创建新队列
◦ `O_NONBLOCK`:非阻塞模式

• mode:权限位(如0666),控制队列的读/写权限。
• attr:指向mq_attr结构体的指针,定义队列属性:

struct mq_attr {
    long mq_flags;    // 标志(通常为0)
    long mq_maxmsg;   // 队列最大消息数
    long mq_msgsize;  // 单条消息最大长度
    long mq_curmsgs;  // 当前队列中的消息数(仅读)
};

• 返回值:成功返回消息队列描述符(mqd_t),失败返回-1。


2. mq_send / mq_receive:发送与接收消息

int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
int mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);

• 功能:向队列发送或从队列接收消息。
• 参数:
• mqdes:消息队列描述符。
• msg_ptr:消息缓冲区指针。
• msg_len:消息长度(需≤mq_msgsize)。
• msg_prio:消息优先级(0最低,MQ_PRIO_MAX由系统定义)。
• 返回值:成功返回0(mq_send)或接收的字节数(mq_receive),失败返回-1。


3. mq_close / mq_unlink:关闭与删除队列

int mq_close(mqd_t mqdes);
int mq_unlink(const char *name);

• 功能:关闭队列描述符或删除队列实体。
• 参数:
• mqdes:待关闭的描述符。
• name:待删除的队列名称。
• 返回值:成功返回0,失败返回-1。


二、信号量(Semaphores)

信号量用于进程间的同步控制,支持二进制和计数两种类型。

1. sem_open:创建或打开命名信号量

#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

• 功能:创建或打开一个命名信号量。
• 参数:
• name:信号量名称(格式同消息队列)。
• oflag:标志位(O_CREAT、O_EXCL等)。
• mode:权限位(如0666)。
• value:信号量初始值。
• 返回值:成功返回信号量指针,失败返回SEM_FAILED。


2. sem_init / sem_destroy:初始化与销毁匿名信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);

• 功能:初始化或销毁匿名信号量(仅限线程或进程内使用)。
• 参数:
• sem:信号量指针。
• pshared:共享标志(0表示线程间,非0表示进程间)。
• value:初始值。
• 返回值:成功返回0,失败返回-1。


3. sem_wait / sem_post:P/V操作

int sem_wait(sem_t *sem);     // P操作(阻塞直到信号量>0)
int sem_trywait(sem_t *sem);  // 非阻塞P操作
int sem_post(sem_t *sem);     // V操作(信号量+1)

• 功能:执行信号量的增减操作。
• 参数:sem:信号量指针。
• 返回值:成功返回0,失败返回-1。


三、共享内存(Shared Memory)

共享内存允许进程直接访问同一块物理内存,是IPC中最高效的方式。

1. shm_open:创建或打开共享内存对象

#include <sys/mman.h>
#include <sys/stat.h>

int shm_open(const char *name, int oflag, mode_t mode);

• 功能:创建或打开共享内存对象。
• 参数:
• name:对象名称(格式同消息队列)。
• oflag:标志位(O_CREAT、O_RDWR等)。
• mode:权限位(如0666)。
• 返回值:成功返回文件描述符,失败返回-1。


2. ftruncate:调整共享内存大小

int ftruncate(int fd, off_t length);

• 功能:设置共享内存对象的长度。
• 参数:
• fd:共享内存文件描述符。
• length:目标长度(字节)。
• 返回值:成功返回0,失败返回-1。


3. mmap / munmap:映射与解除映射

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

• 功能:将共享内存映射到进程地址空间或解除映射。
• 参数:
• addr:映射起始地址(通常设为NULL由系统选择)。
• length:映射长度。
• prot:保护模式:

◦ `PROT_READ`:可读
◦ `PROT_WRITE`:可写
◦ `PROT_EXEC`:可执行
◦ `PROT_NONE`:不可访问

• flags:映射标志:

◦ `MAP_SHARED`:多进程共享
◦ `MAP_PRIVATE`:私有映射(写时复制)

• fd:共享内存文件描述符。
• offset:文件偏移量(通常为0)。
• 返回值:成功返回映射地址指针(mmap)或0(munmap),失败返回MAP_FAILED或-1。


4. shm_unlink:删除共享内存对象

int shm_unlink(const char *name);

• 功能:删除共享内存对象。
• 参数:name:对象名称。
• 返回值:成功返回0,失败返回-1。


总结

POSIX IPC通过标准化的API设计,为进程间通信提供了高效、可靠的解决方案。消息队列适用于异步任务分发,信号量用于资源同步,共享内存则适合高频数据交换。开发时应根据场景需求选择合适机制,并注意资源释放与同步问题。

- 阅读全文 -
  1. 1
  2. 2
  3. 3

浏览量 : 5665

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

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

This is just a placeholder img.