进程间通信

基本概念

进程间通信(IPC,InterProcess Communication)指的是在不同进程之间传播信息的过程,常见的IPC方式有

  • 管道
    • 匿名管道
    • 命名管道
  • 消息队列
  • 共享内存
  • 信号量
  • 信号
  • 文件
  • Socket

传输模式

  • 单工 Simplex
    只允许一方向另一方传输信息,而另一方不能向一方传输
  • 全双工 Full Duplex
    发送数据的同时也能够接受数据,二者可以同步进行。
  • 半双工 Half Duplex
    数据可以在一个信号载体的两个方向上传输,但是不能同时传输

管道

匿名管道 pipe

  • 属于半双工通信,数据只能在一个方向上流动
    • 尽管读端和写端可以同时打开,但每个描述符只能用于读或写中的一种操作,不能同时双向通信
    • 从管道本身的角度看,信息只能从写端进入,从读端离开
  • 只能在父子进程之间通信
  • 是一种特殊的文件,对于它的读写也可以使用普通的readwrite等函数。
  • 但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于进程的内核对象中,无法持久化
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
#include <iostream>
#include <unistd.h>

int main() {
int _pipe[2];
int ret = pipe(_pipe);// 创建一个匿名管道,将读端文件描述符写入_pipe[0],写端文件描述符写入_pipe[1],成功返回0,否则返回-1

pid_t id = fork();

if (id < 0) {
perror("fork\n");
} else if (id == 0) { // 子进程

close(_pipe[1]); // 关闭写端
// char * msg;//❌ msg 是一个未初始化的指针,它根本没有分配内存,也就是说 read 会试图往野指针写数据,导致段错误(segment fault)或者读取失败
char msg[100];
read(_pipe[0], msg, 100);

printf("子进程读取到的管道消息:\n %s\n", msg);

} else { // 父进程
close(_pipe[0]); // 关闭读端
std::string mesg = "父进程写入管道";
write(_pipe[1], mesg.c_str(), mesg.size() + 1);
}


return 0;
}

上述的代码会一直输出

1
2
子进程读取到的管道消息:
父进程写入管道
  • pid_t id = fork();创建子进程时会返回两次
    • 在父进程中,返回子进程的 PID(>0)
    • 在子进程中,返回 0
    • 出错时返回 -1

高级管道 popen

popen的功能是打开一个进程,并创建一个管道与其通信,且这个打开的进程是当前进程的子进程。可以通过它来执行任何程序,包括 shell 命令。

  • 它接受以下两个参数:
    1. 命令字符串: 可以是任何可执行文件的路径或者shell命令
    2. 模式:指定如何与该进程交互,常用的模式有:
      • "r":从进程读取输出(读模式),即进程的标准输出被重定向到管道。
      • "w":向进程写入输入(写模式),即进程的标准输入被重定向到管道。
  • popen创建的管道必须由pclose关闭
    下面的代码就是用于执行shell命令并返回命令执行的输出
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
std::string GetCmdResult(const std::string &cmd, int max_size = 10240) {
char *data = (char *)malloc(max_size);
if (data == NULL) {
return std::string("malloc fail");
}
memset(data, 0, max_size);
const int max_buffer = 256;
char buffer[max_buffer];
// 命令末尾加上 2>&1,将标准错误重定向到标准输出,这样无论是 stdout 还是 stderr 都能被 popen 捕获。
FILE *fdp = popen((cmd + " 2>&1").c_str(), "r");
int data_len = 0;

if (fdp) {
while (!feof(fdp)) {
if (fgets(buffer, max_buffer, fdp)) {
int len = strlen(buffer);
if (data_len + len > max_size) {
cout << "data size larger than " << max_size;
break;
}
memcpy(data + data_len, buffer, len);
data_len += len;
}
}
pclose(fdp);//关闭管道
}
std::string ret(data, data_len);
free(data);
return ret;
}

命名管道 FIFO

与匿名管道的区别在于

  • 有名字
  • 可以双向通信
  • 可以在没有亲缘关系的进程间传输
  • 会在文件系统中创建一个特殊文件,这个特殊的文件会一直以文件的形式存在,而不是仅存在于进程的内核对象中,是持久化的。
    命名管道通过mkfifo创建用于通信的文件
1
int mkfifo(const char* filename, mode_t mode);

并通过读写这个文件的方式进行进程间的通信

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 <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <iostream>

static constexpr char * FIFO_PATH = (char*)"/tmp/my_fifo";

// 错误处理宏
#define CHECK(x,msg) \
if ((x) == -1) { perror(msg); exit(EXIT_FAILURE); }

int main() {
// 1. 创建 FIFO 文件(如果已存在则忽略错误)
if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {
perror("mkfifo");
return EXIT_FAILURE;
}

pid_t pid = fork();
CHECK(pid, "fork");

if (pid == 0) {
// —— 子进程:读者 ——
// 2. 打开 FIFO 读端(阻塞,直到有写端打开)
int fd = open(FIFO_PATH, O_RDONLY);
CHECK(fd, "open for read");

char buf[128];
ssize_t n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
std::cout << "Child read: " << buf << std::endl;
} else {
std::cerr << "Child: read returned " << n << std::endl;
}

close(fd);
// 子进程结束后,可自行删除 FIFO:
unlink(FIFO_PATH);
exit(EXIT_SUCCESS);

} else {
// —— 父进程:写 ——
// 3. 打开 FIFO 写端(阻塞,直到有读端打开)
int fd = open(FIFO_PATH, O_WRONLY);
CHECK(fd, "open for write");

const char * msg = "Hello from parent via FIFO!";
ssize_t n = write(fd, msg, std::strlen(msg));
if (n == -1) {
perror("write");
} else {
std::cout << "Parent wrote " << n << " bytes." << std::endl;
}

close(fd);
wait(nullptr);
}

return 0;
}

管道的优劣

  • 方便,可以传输大量数据
  • 效率相对于共享内存来说较低

消息队列

  • 消息队列的信息传递并不遵循先进先出的规则,而是会对每一条消息都赋予类型,根据消息的类型进行读取。
  • 可以同时存在多个线程对消息队列进行读写

msgget

用来打开一个现存的消息队列(当该消息队列存在时)或者创建一个消息队列。它的原型为:

1
int msgget(key_t key, int msgflg);
  • key
    • 标识一组消息队列的键。可以理解为队列名,不同的进程只要使用相同的 key,就能访问到同一个消息队列。
    • 取值方式
      1. IPC_PRIVATE
        • 特殊常量,数值通常为 0。
        • 每次调用 msgget(IPC_PRIVATE, …) 总是创建一个新队列,且其他进程无法通过该值直接访问
      2. ftok() 生成:
        • 常见做法是先调用 ftok(pathname, proj_id) 生成一个 key_t
        • pathname 通常指向一个已存在的文件,proj_id 是一个小整数。
          • pathname 并不会在运行时被打开或读取,它的作用仅在于提供一组“文件元数据”来帮助生成一个系统范围内唯一(或至少冲突概率很低)的 key_t 值。
        • 这样不同进程只要给出相同的 pathnameproj_id,就能获得相同的 key
      3. 直接指定整数:
        也可以直接写成常量(如 1234),但要保证不会与系统中其它队列冲突。
  • msgflg
    • 用于指定消息队列的权限
    • 常用标志位
      • IPC_CREAT
        如果指定 key 的消息队列不存在,则创建一个新队列。
      • IPC_EXCL
        IPC_CREAT 一起使用时,如果队列已存在,则调用失败并返回 -1,同时 errno = EEXIST
      • 权限掩码(如 0666
        用八进制表示所有者、组、其他用户的读写权限
        共有4位,第1位一般为为0,第二位表示所有者权限,第三位表示同组用户的权限,第四位表示其他用户的权限。具体的权限通过4(读)+2(写)+1(执行)的形式表示。例如0674表示所有者、同组用户的权限分别为可读可写(4+2)、可读可写可执行(4+2+1)和可读(4)
  • 返回一个标识符 msgid
    • 是非零整数
    • key只是用于查找或创建队列,并不是队列的内部编号,而msgid则是内核给队列分配的内部编号
    • 是后序对队列进行操作所用的句柄的操作句柄
    • 失败时返回-1

msgsend

用来把消息添加到消息队列中

1
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
  • msgidmsgget返回的标识符
  • msg_ptr
    • 指向准备发送的消息的指针
    • 对消息的数据结构有要求,必须是以一个long int成员开始的结构体,该成员用于确定消息类型
      1
      2
      3
      4
      5
      struct my_message{
      long int message_type;
      /* The data you wish to transfer*/
        char mtext[512]; /*message data,of length msg_sz*/
      };
  • msg_sz
    • 所要发送的消息的长度
    • 消息的长度不包括类型标识符
  • msgflg
    • 0
      默认行为:如果消息队列已满,调用该函数的进程将阻塞,直到有足够空间或发生错误。
    • IPC_NOWAIT
      不阻塞:如果此时队列已满,调用立即返回 -1,并将 errno 设为 EAGAIN(发送失败)。
  • 函数调用成功时返回0,否则返回-1

msgrcv

用来从一个消息队列获取消息

1
int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
  • msg_ptr 指向用于接收消息的缓存区
  • msgtype 可以用于实现简单的优先级队列
    • msgtype == 0
      接收队列中最先到达的那条消息(FIFO),不做类型匹配
    • msgtype > 0
      接收队列中第一个类型匹配的消息(找到后就返回)
    • msgtype < 0
      接收队列中 msg.message_type ≤ |msgtype| 的消息,且总是返回msg.message_type最小的那条消息,如果有多条msg.message_type最小且相等的消息,则返回先到达的消息。
  • msgflg
    • 0
      默认行为:如果没有匹配的消息,调用进程阻塞直到有消息到达或队列被删除
    • IPC_NOWAIT
      不阻塞:如果没有匹配的消息,立即返回 -1,并将 errno 设为 ENOMSG(接收失败)。
    • MSG_NOERROR
      如果队列头消息的长度大于用户提供的 msg_sz,则截断多余部分并返回,不报错;否则(无此标志且消息过长)返回 -1 并将 errno 设为 E2BIG
    • MSG_EXCEPT
      仅与正数 msgtype 一起使用,匹配 所有 mtype != msgtype 的消息(即取“类型不等于 msgtype”的第一条消息)
    • MSG_COPY
      (Linux 特有,需内核开启 CONFIG_CHECKPOINT_RESTORE)
      不删除队列中的消息,仅将其内容 复制 到用户缓冲区,用于检查点/恢复机制。
  • 调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.

msgctl

用来控制消息队列

1
int msgctl(int msgid, int command, struct msgid_ds *buf);
  • command 要执行的控制命令

    • IPC_STAT
      将内核中该消息队列的状态信息拷贝到用户提供的 buf 中。
    • IPC_SET
      将用户在 buf 中设置的新权限(uid/gidmode)写入内核的消息队列数据结构中(只能修改权限和 ctime)。
    • IPC_RMID
      删除该消息队列;内核会在消息队列被删除后立即释放其所有资源。
    • MSG_STAT
      (Linux 特有),类似于 IPC_STAT,但如果 msgid 已被删除,则返回最新的 msqid 而不是失败。
    • MSG_INFO
      (Linux 特有),不需要 msgid 有效,将系统范围内所有消息队列使用情况写入 buf,并返回系统中消息队列的最大索引。
  • buf
    指向 struct msqid_ds 的指针,用于传递或接收消息队列的元数据信息;其结构体定义通常如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    struct ipc_perm {
    key_t __key; /* 键值 */
    uid_t uid; /* 所有者的用户ID */
    gid_t gid; /* 所有者的组ID */
    uid_t cuid; /* 创建者的用户ID */
    gid_t cgid; /* 创建者的组ID */
    mode_t mode; /* 访问权限 */
    unsigned short __seq; /* 序列号 */
    };

    struct msqid_ds {
    struct ipc_perm msg_perm; /* 操作权限和身份 */
    time_t msg_stime; /* 最后一次发送时间 */
    time_t msg_rtime; /* 最后一次接收时间 */
    time_t msg_ctime; /* 最后一次变更时间 */
    unsigned long __msg_cbytes; /* 当前队列中字节数(Linux 特有)*/
    msgqnum_t msg_qnum; /* 队列中消息数 */
    msglen_t msg_qbytes; /* 队列允许的最大字节数 */
    pid_t msg_lspid; /* 最后发送者的PID */
    pid_t msg_lrpid; /* 最后接收者的PID */
    };
  • 返回值

    • 成功时返回 0(除 MSG_STAT/MSG_INFO 可能返回其他非负值)。

    • 失败时返回 -1,并设置 errno,常见错误原因:

      • EINVALmsgidcommand 无效,或 mode 越界。
      • EACCES:对消息队列的权限不足。
      • EFAULTbuf 指针无效。

消息队列 vs 命名管道

  1. 消息队列可以独立于发送和接收的进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难
    • 例如open(FIFO_PATH, O_RDONLY)打开FIFO读端时,会阻塞直到有写端打开
  2. 过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法
  3. 接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收
  4. 但是消息队列发送的每条消息都有最大长度限制

共享内存

通过多个进程共同使用同一块逻辑内存实现通信,实现时一般把由不同进程之间共享的内存安排为同一段物理内存。

我们执行的每一个程序,它看到的内存其实都是虚拟内存,虚拟内存需要进行页表的映射将进程地址映射到物理内存,那么让两个进程地址通过页表映射到同一片物理地址,就可以进行通信。

具体的实现方式包括

  1. mmap系统调用
  2. Posix共享内存
  3. System V共享内存

共享内存是最快的IPC方式,但是没有同步机制,对开发者的能力要求也很高。

通过Posix实现共享内存

shm_open()

用于在内核中打开或创建一个具名的共享内存对象

1
2
#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);
  • name
    / 开头的共享内存对象名(如 "/my_shm")。
  • oflag
    打开标志,需要#include <fcntl.h>,可按需组合
    • O_CREAT:若不存在则创建
    • O_RDWR:可读写
    • O_RDONLY:只读
    • O_EXCL:配合 O_CREAT 使用,若对象已存在则失败
  • mode
    权限掩码(参照文件权限),如 0666 表示所有用户可读写
  • 打开成功时返回一个非负的文件描述符否则返回 -1 并设置 errno

ftruncate()

设置或调整共享内存对象的大小,确保后续 mmap 能映射到足够的空间。

1
2
#include <unistd.h>
int ftruncate(int fd, off_t length);

  • fd
    文件描述符(这里是共享内存对象的 shm_open 返回值)
  • length
    新的对象大小(字节数),不足则扩展并填零,超出则截断
  • 成功时返回 0,否则返回 -1 并设置 errno

mmap()

将共享内存对象映射到当前进程的虚拟地址空间,读写这个指针就等同于访问共享内存。

1
2
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

  • addr
    建议映射地址,通常传 nullptr 由内核选择

  • length
    映射区域长度(字节数)

  • prot:访问权限

    • PROT_READ:可读
    • PROT_WRITE:可写
  • flags:映射特性

    • MAP_SHARED:写入对其他进程可见
    • MAP_PRIVATE:写入时复制,对外不可见
  • fd
    要映射的文件描述符(共享内存描述符)

  • offset
    从文件的该偏移处开始映射,通常为 0

  • 成功时返回指向映射区首地址的指针否则返回 MAP_FAILED 并设置 errno

munmap()

解除mmap()创建的内存映射,释放资源。

1
2
#include <sys/mman.h>
int munmap(void *addr, size_t length);

  • addr:映射区域起始地址(mmap 返回值)
  • length:映射区域长度
  • 成功时返回 0,否则返回 -1 并设置 errno

从内核中删除命名的共享内存对象,彻底清理。

1
2
#include <sys/mman.h>
int shm_unlink(const char *name);

  • name:要删除的共享内存对象名,与 shm_open 时的相同
  • 成功时返回 0,否则返回 -1 并设置 errno

示例

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
#include <iostream>
#include <unistd.h>
#include <string>
#include <sys/mman.h>
#include <fcntl.h>
#include <cstring>
#include <sys/wait.h>

int main() {
// std::string *share_mem = new std::string; //错误写法,fork() 只是把父进程的地址空间 copy-on-write 地复制给子进程,二者并不是真正共享内存。父进程对 share_mem 的修改不会反映到子进程那一份

int shared_fd = shm_open("/share_mem", O_CREAT | O_RDWR, 0666); //O_CREAT和O_RDWR需要#include <fcntl.h>

if (ftruncate(shared_fd, sizeof(char)*1024) == -1) {
perror("truncate");
return 1;
}

char * share_mem = (char *)mmap(nullptr, sizeof(char)*1024, PROT_READ | PROT_WRITE, MAP_SHARED, shared_fd, 0);

pid_t id = fork();

if (id < 0) {
perror("fork\n");
} else if (id == 0) { // 子进程
sleep(1); //保证在父进程写完后再读取
printf("子进程访问到的共享内存内容:\n %s\n", share_mem);

} else { // 父进程
// share_mem = "父进程写入共享内存\n";//这会把 share_mem 指针的值替换为指向字符串常量 "父进程写入共享内存\n" 的地址,不会真正把内容写入共享内存。
const char *shm_msg = "父进程通过共享内存发送消息\n";
memcpy(share_mem, shm_msg, strlen(shm_msg)+1);
}

munmap(share_mem, sizeof(char)*1024);
shm_unlink("/share_mem");

return 0;
}

会输出

1
2
子进程访问到的共享内存内容:
父进程通过共享内存发送消息

信号量 Semaphore

信号量是一种用于对多个进程访问共享资源进行控制的机制。共享资源通常可以分为两大类:

  • 互斥共享资源,即任一时刻只允许一个进程访问该资源
  • 同步共享资源,即同一时刻允许多个进程访问该资源
    信号量是为了解决互斥共享资源的同步问题引入的机制,其本质是整数计数器,记录可供访问的共享资源的单元个数。

最常见的信号量是二值信号量,只能取0或1。对于二值信号量

  • 当有进程要求使用某一资源时,系统首先要检测该资源的信号量
    • 如果该资源的信号量的值大于 0,则进程可以使用这一资源,同时信号量的值减 1。进程对资源访问结束时,信号量的值加 1。
    • 如果该资源信号量的值等于 0,则进程休眠,直至信号量的值大于 0 时进程被唤醒,访问该资源。

信号 Signal

  • 信号是在软件上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。
  • 它是IPC中的唯一一种异步通信方式。信号可以在任何时候发送给进程。
  • 进程对于信号有三种响应方式
    1. 忽略,即不做任何处理(SIGKILLSIGSTOP这两个信号不能忽略)
    2. 捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;
    3. 执行缺省操作,Linux 对每种信号都规定了默认操作。
  • 信号只能发挥通知的作用,无法传输数据

文件

进程之间可以通过操作同一个文件来进行通信

套接字 socket

套接字是网络编程和IPC中最常用的一种抽象,可以在同一台主机上的不同线程之间通信,也可以通过网络在不同的主机间进行通信。它本身并不和具体的协议绑定,按照协议类型可以分为

  • 流套接字 SOCK_STREAM 基于 TCP 协议,提供面向连接的、可靠的、字节流服务。
  • 数据报套接字 SOCK_DGRAM 基于 UDP 协议,提供无连接的、不可靠的数据报服务。

socket的基本操作

socket本身也是一种特殊的文件,是Unix“一切皆是文件”思想指导下“open—write/read—close”模式的一种实现。

socket()

用于创建一个socket并返回对应的描述符sockfd

1
int socket(int domain, int type, int protocol);
  • domain
    即协议域,又称为协议族(family),决定了socket地址类型,在通信中必须采用对应的地址。常用的协议族有,AF_INETAF_INET6AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。例如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
  • type
    指定socket类型。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等
  • protocol
    指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。当protocol为0时,会根据type自动选择协议

bind()

当我们调用socket()创建一个socket时,返回的socket描述符sockfd存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数。

1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd
  • addr
    一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址对象,其根据协议族的不同有不同的结构
  • addrlen
    地址的长度
  • 如果没有调用bind()sockfd分配地址的话,当调用connect()listen()时系统会自动随机分配一个端口
  • 一般情况下,socket服务器的地址和端口是通过bind()手动绑定的,而客户端则使用在调用listen()时分配的即可

listen()connect()

服务器会在建立socket并绑定地址后调用listen()来监听这个socket,如果客户端调用connect(),服务器就能接收到客户端的连接请求

1
2
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • backlog
    这个socket可以排队的最大连接个数
  • sockfd
    需要注意的是,服务器和客户端各自都是一个单独的socketlisten()connect()中的sockfd参数都是各自sockfd
  • sockaddr
    sockaddr.sin_addr是服务器的地址,sockaddr.sin_port是服务器的端口

accept()

服务器监听到客户端的连接请求后,就会调用accept()函数接受请求,从而建立连接

1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd
    服务器的sockfd
  • addr
    指向struct sockaddr *的指针,用于返回客户端的协议地址
  • 返回值为客户端的sockfd

read()write()

客户端和服务器之间的网络I/O操作可以由下面的几组函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//泛用的文件描述符 I/O 接口,既能操作普通文件,也能操作套接字。
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

//专为套接字设计,只有对 socket 描述符才有意义。
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);

//每次调用可指定或获取对端地址,适用于 UDP
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);

//更通用,支持 scatter/gather I/O(即一次调用操作多个缓冲区),并能携带或接收控制消息(如 ancillary data)。
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

close()

用于在完成通信后关闭相应的socket

1
int close(int fd);

参考


进程间通信
https://guts.homes/2025/06/19/IPC/
作者
guts
发布于
2025年6月19日
许可协议