【003_音频开发_基础篇_Linux进程通信(20种你了解几种?)】

003_音频开发_基础篇_Linux进程通信(20种你了解几种?)

文章目录

  • 003_音频开发_基础篇_Linux进程通信(20种你了解几种?)
    • 创作背景
    • `Linux` 进程通信类型
    • `fork()` 函数
      • `fork()` 输出 `2` 次
      • `fork()` 输出 `8` 次
      • `fork()` 返回值
      • `fork()` 创建子进程 方式一
      • `fork()` 创建子进程 方式二
      • `fork()` 创建子进程 方式三
      • `fork()` 创建子进程 方式四
      • `fork()` 复制父进程上下文
    • `Socket` 套接字
    • `Socket` - (`Linux IP` 协议)
      • 总体架构
      • `IP 协议` 协议重要结构体
      • `IP 协议 API函数`
      • `IP 协议族`
      • `IP 协议 socket` 类型
      • `IP 协议` 操作函数
    • `Socket` - `UDS/Unix Domain Socket`
      • `UDS` 简介
      • `UDS Server` 创建流程
      • `UDS Client` 创建流程
      • 创建 `UDS`
      • `UDS` 类型
      • `UDS` 地址格式
        • `UDS` 文件名类型 - 路径名
        • `UDS` 文件名类型 - 抽象类型
        • `UDS` 文件名类型 - 未命名
      • `UDS C# HTTP Server` 示例
      • `UDS C# HTTP Client` 示例1
      • `UDS C# HTTP Client` 示例2
    • `IP 协议` VS. `UDS` (待更新)
    • `UDS` 应用场景 —— `Docker Daemon`
    • `UDS` 应用场景 —— `Azure IoT Edge`
    • Socket Activation
    • 管道 `pipe`
      • 匿名管道 `Anonymous pipes`
      • 命名管道 `Named pipe/FIFOs`
    • 消息队列
    • 共享内存
    • 信号量
    • 文件锁
    • 共享文件
    • RPC (Remote Procedure Call)
    • Protocol Buffer
    • gRPC
    • RabbitQM
    • ZeroMQ
    • RPC over HTTP
    • FlatBuffers

创作背景

学历代表过去、能力代表现在、学习力代表将来。 一个良好的学习方法是通过输出来倒逼自己输入。写博客既是对过去零散知识点的总结和复盘,也是参加了 零声教育 写博客活动。

零声教育体验课:https://xxetb.xetslk.com/s/3fbO81

本文是开发过程中的知识点总结,供大家学习交流使用,如有任何错误或不足之处,请在评论区指出。

Linux 进程通信类型

如下表描述了Linux常用进程间通信方式了,包含了多种技术,例如基于系统调用、套接字、共享内存和消息队列等,用于实现进程之间的数据传输、同步和协作,以满足不同应用场景下的需求。

序号中文名英文名描述
1管道Pipe匿名管道 在父子进程或兄弟进程之间进行通信。
命名管道 独立进程间通信
2消息队列Message Queues进程之间通过消息队列进行通信,可以实现异步通信。
3共享内存Shared Memory进程可以通过映射共享内存区域来实现共享数据,提高通信效率。
4信号Signals进程可以通过信号来通知其他进程发生了某些事件。
5信号量Semaphores用于控制对共享资源的访问,防止多个进程同时访问造成冲突。
6文件锁File Locks进程可以通过文件锁来实现对共享文件的互斥访问。
7共享文件Shared Files进程可以通过读写共享文件来进行通信,但需要注意同步和并发访问的问题。
8Memory Mapped FilesMemory Mapped Files进程可以将文件映射到它们的地址空间中,实现对文件内容的共享访问。
9直接操作内存Direct Memory Access进程可以直接读写其他进程的内存来进行通信,但这通常需要特殊的权限和保护机制。
10Event LoopsEvent Loops进程可以使用事件循环来监听和处理事件,实现进程间的消息传递和通知。
11套接字/UDSUnixUnix域套接字(Unix Domain Socket),用于本地进程间通信。
12DBusdbusD-Bus 是一个进程间通信机制,常用于 Linux 桌面环境中实现进程间通信和协作。
13套接字/IPIPInternet套接字(Internet Domain Socket),用于在网络中进行通信。
14Remote Procedure CallsRPC允许进程调用另一个进程中的函数,实现远程通信和协作。
15RPC over HTTPRPC over HTTP使用HTTP协议作为传输层,通过远程过程调用(RPC)来进行进程间通信。
16ZeroMQZeroMQZeroMQ 是一个开源的消息队列库,提供了丰富的 API 和通信模式,用于实现进程间通信。
17Socket ActivationSocket Activationsystemd中的一种机制,允许在需要时才启动服务进程。通过UNIX域套接字传递连接。
18RabbitMQRabbitMQ基于AMQP协议的开源消息代理,用于实现可靠的异步通信。
19gRPCgRPCgRPC 是一个高性能、开源的远程过程调用(RPC)框架,可以用于实现进程间通信。
20Protocol BuffersProtocol Buffers使用序列化格式将数据结构编码后进行传输,实现进程间通信。
21FlatBuffersFlatBuffers使用序列化格式将数据结构编码后进行传输,实现进程间通信。

fork() 函数

  • fork() 是一个系统调用,用于创建一个与当前进程完全相同的新进程。
  • 原进程称为 父进程,新创建的进程称为 子进程
  • 在父进程中,fork() 返回子进程的进程 ID;在子进程中,fork() 返回 0
  • 在调用 fork() 时,两个内存空间具有相同的内容。
  • 一个进程进行的内存写操作、文件映射和解除映射操作不会影响另一个进程。
  • 注意: fork() 调用会有 2 次返回,正常我们的函数调用只有一次返回。

fork() 输出 2

fork() 函数调用后输出两次:当前进程 +  子进程

int main(void)
{printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());fork();printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());return 0;
}

img_fork_output_twice

fork() 输出 8

int main(void)
{printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());fork(); fork(); fork();printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());return 0;
}

img_fork_output_8_times

fork() 返回值

fork() 函数在父进程中返回子进程的进程 ID,而在子进程中返回 0。调用失败,它将返回一个负值,通常是 -1,表示创建新进程失败。

int main(void)
{printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());pid_t pid = fork();if (0 == pid){printf("child pid=%d, parent pid=%d\n", getpid(), getppid());}else if (0 < pid){printf("pid=%d, parent pid=%d\n", getpid(), getppid());}else{printf("fork() failed.\n");}printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());return 0;
}

img_fork_return_value

fork() 创建子进程 方式一

父进程创建 2 个子进程

img_fork_way1

int main()
{printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());// 1. parentpid_t id = fork();if (id == 0) // 2. new child{fork(); // 3. return twice (one is new child)}// total: 3 thread = 1 current + 2 childprintf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());return 0;
}

img_fork_way1_test_result

fork() 创建子进程 方式二

父进程创建 4 个子进程

img_fork_way2

int main()
{printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());    // 1. parentpid_t id = fork();if (id == 0) // 2. new child{id = fork();if (id == 0) // 3. new child{id = fork();if (id == 0) // 4. new child{fork(); // 5. return twice (one is new child)}}}    // total: 3 thread = 1 parent + 4 child    printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());return 0;
}

img_fork_way2_test_result

fork() 创建子进程 方式三

父进程创建 4 个子进程

img_fork_way3

int main(void) {printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());    pid_t id[4];    // 1. parentfor (int i = 0; i < 4; i++){id[i] = fork();if (id[i] == 0) // 2. new 4 child, by the same parent.{printf("\tI'm child pid=%d, parent pid=%d\n", getpid(), getppid());break;}else if (id[i] > 0){printf("\tI'm parent pid=%d, parent pid=%d\n", getpid(), getppid());}else{printf("fork() failed.\n");}}    // total: 5 thread = 1 parent + 4 child    printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid()); // print 5 timesreturn 0;
}

img_fork_way3_test_result

fork() 创建子进程 方式四

父进程比子进程先退出,子进程会变为“孤儿进程”,被1 号/init --user进程收养,进程编号也会变为 1

img_fork_way4

int main()
{printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());pid_t id[4];// 1. parentfor (int i = 0; i < 4; i++){id[i] = fork();if (id[i] > 0) // 2. new 4 child, child create child.{break;}else if (id[i] == 0) // child exit{}else //failed {}}// total: 5 thread = 1 parent + 4 child    printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());return 0;
}

img_fork_way4_test_result

fork() 复制父进程上下文

char buffer[64] = "hello world!";
int main()
{printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());pid_t pid = fork();if (0 < pid) // parent{printf("\tI'm parent pid=%d, parent pid=%d\n", getpid(), getppid());for (int i = 0; i < 10; i++){sleep(1);printf("\t%d)Parent (%s)\n", i, buffer);}}else if (0 == pid) // child{printf("\tI'm child pid=%d, parent pid=%d\n", getpid(), getppid());for (int i = 0; i < 5; i++){if (1 == i) // child thread modify buffer {strcpy(buffer, "Sub message.");}sleep(1);printf("\t%d)Sub (%s)\n", i, buffer);}}else{printf("fork() failed.\n");}printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());return 0;
}

img_fork_duplicate_parent

Socket 套接字

  • 套接字是一种软件对象,作为端点,建立了服务器端和客户端程序之间的双向网络通信链路。

  • 套接字允许在同一台或不同的机器上两个不同进程之间进行通信。

  • Socket 本意是 插座/插口 的意思,其实也可以这样理解,一台电脑(服务器)有很多插口,另一台电脑(客户端)有很多插头,把客户端电脑的一个插头,插入到服务端电脑的插孔种,这样 2 台电脑就建立了通信桥梁。

  • 下图左边是 服务端 创建socket流程,右边是 客户端 创建流程:
    img_socket_overview

Socket - (Linux IP 协议)

总体架构

img_socket_ip_protocol_overview

  • glibc: glibc 提供对系统调用的封装,可以使用标准 C 函数来进行 系统调用,而无需直接与系统调用接口打交道。
    • glibc(GNU C Library)GNU 项目中的一个重要组件,是 C 语言编程中常用的标准 C 库。
    • 系统调用 是操作系统提供给用户空间程序与内核进行交互的接口。当用户空间程序需要操作硬件设备、访问文件系统、进行网络通信等底层操作时,通常需要通过系统调用来实现。
  • INET: 提供了各种网络协议函数和接口,使用户空间程序能够通过系统调用来进行网络通信。INET 相关的文件通常位于 /proc/net/ 目录下,提供了关于网络状态、连接信息、路由表等的统计数据和配置信息。
  • VFS/virtual File System: 虚拟文件系统。操作系统中的一种抽象概念,用于管理和访问不同类型的文件系统,使用户和应用程序能够以统一的方式访问文件,而无需了解底层文件系统的细节。
  • ARP/Address Resolution Protocol: 地址解析协议。ARP 是用于将 IP 地址映射到物理硬件地址(如 MAC 地址)的协议。当计算机在本地网络中需要发送数据到另一台计算机时,它需要知道目标计算机的 MAC 地址。ARP 协议通过在本地网络上广播 ARP 请求来获取目标 IP 地址的 MAC 地址,然后将这个映射关系保存在 ARP 缓存中,以便后续通信中使用。
  • ICMP/Internet Control Message Protocol: 是因特网控制报文协议, 不需要指定端口。用于在 IP 网络上发送控制消息的协议,用于检测网络连接是否正常、诊断网络问题以及报告错误。还用于 ping 命令,用于测试与另一个主机之间的网络连接是否正常。

IP 协议 协议重要结构体

img_socket_ip_protocol_structure

  • state: 表示套接字当前所处的状态,如连接已建立、连接中等。
  • type: 套接字类型,表示套接字的通信方式,如常用的 流式套接字数据报套接字等。
  • flags: 套接字的标志,用于指示特定的套接字属性或配置。
  • ops: 指向协议特定套接字操作的指针,通常指向一组函数指针,用于执行套接字操作,如发送数据、接收数据等。
  • file: 这是指向套接字相关文件的指针。在 Linux 内核中,套接字也可以通过文件系统进行访问和操作,这个指针可能指向套接字关联的文件结构。
  • sk: 这是一个指向内部网络协议无关套接字表示的指针。它通常指向 struct sock 类型的数据结构,该结构包含了更底层的网络协议相关信息。
  • wq: 等待队列,用于处理各种套接字相关的等待事件,比如等待连接、等待数据等。

IP 协议 API函数

img_socket_ip_protocol_api

  • socket(): 创建新套接字。
  • bind(): 将套接字与地址绑定。
  • listen(): 将套接字设置为监听模式。
  • accept(): 接受连接请求并创建新套接字。
  • connect(): 建立与目标套接字的连接。
  • send(): 发送数据到连接的套接字。
  • recv(): 从套接字接收数据。
  • close(): 关闭套接字。
  • shutdown(): 关闭套接字的读、写或两者。
  • getsockopt(): 获取套接字选项。
  • setsockopt(): 设置套接字选项。

IP 协议族

img_socket_ip_protocl_family

  • 地址族和协议族其实是一样的,值也一样,都是用来识别不同协议的,为什么要搞两套东西呢?
  • 这是因为之前 Unix 有两种风格系统:BSD 系统和 POSIX 系统,对于 BSD 系统,一直用的是 AF,对于 POSIX 系统,一直用的是PFLinux 作为后起之秀,为了兼容,所以两种都支持,这样两种风格的 Unix 下的软件就可以在 Linux 上运行了。
  • POSIX 系统: Bell 实验室的 Ken ThompsonDennis Richie 一起发明了 Unix 系统。
  • BSD 系统: BSD 代表伯克利软件条件,是 20世界 70 年代,加州大学伯克利分校对贝尔实验室 Unix 进行了一系列修改后的版本,它最终发展成了一个完整的操作系统,有着自己的一套标准。

IP 协议 socket 类型

/* Types of sockets.  */
enum __socket_type
{SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams.  */SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams of fixed maximum length.  */SOCK_RAW = 3, /* Raw protocol interface.  */SOCK_RDM = 4, /* Reliably-delivered messages.  */SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based, datagrams of fixed maximum length.  */SOCK_DCCP = 6, /* Datagram Congestion Control Protocol.  */SOCK_PACKET = 10, /* Linux specific way of getting packets at the dev level.  For writing rarp and other similar things on the user level. *//* Flags to be ORed into the type parameter of socket and socketpair and used for the flags parameter of paccept.  */SOCK_CLOEXEC = 02000000, /* Atomically set close-on-exec flag for the new descriptor(s).  */SOCK_NONBLOCK = 00004000 /* Atomically mark descriptor(s) as non-blocking.  */
};
  • SOCK_STREAM: 顺序、可靠、基于连接的字节流,通常用于TCP协议。
  • SOCK_DGRAM: 无连接、不可靠的固定最大长度数据报,通常用于UDP协议。
  • SOCK_RAW: 原始协议接口,允许直接访问网络层以下的协议。
  • SOCK_RDM: 可靠交付的消息,不保证顺序。
  • SOCK_SEQPACKET: 顺序、可靠、基于连接的固定最大长度数据报,每个数据报保持连续性。
  • SOCK_DCCP: 用于 Datagram Congestion Control Protocol(DCCP) 的套接字类型。
  • SOCK_PACKET: Linux 特定的获取数据包的方式,用于在用户级别进行类似 RARP 等操作。
  • SOCK_CLOEXEC: 用于 socketsocketpair 的标志,用于原子性地设置新描述符的 close-on-exec标志。
  • SOCK_NONBLOCK: 用于 socketsocketpair 的标志,用于原子性地将描述符标记为非阻塞。

IP 协议 操作函数

应用层相应的函数,通过 glibc 封装的函数,进行系统调用,最后调用这里的函数。

struct proto_ops {int     family;struct module   *owner;int         (*release)      (struct socket *sock);int         (*bind)         (struct socket *sock,   struct sockaddr *myaddr,   int sockaddr_len);int         (*connect)      (struct socket *sock,   struct sockaddr *vaddr,    int sockaddr_len,     int flags);int         (*socketpair)   (struct socket *sock1,  struct socket *sock2);int         (*accept)       (struct socket *sock,   struct socket *newsock,    int flags,            bool kern);int         (*getname)      (struct socket *sock,   struct sockaddr *addr,     int peer);__poll_t    (*poll)         (struct file *file,     struct socket *sock,       struct poll_table_struct *wait);int         (*ioctl)        (struct socket *sock,   unsigned int cmd,          unsigned long arg);
#ifdef CONFIG_COMPATint         (*compat_ioctl) (struct socket *sock,   unsigned int cmd,        unsigned long arg);
#endifint         (*gettstamp)    (struct socket *sock,   void __user *userstamp,  bool timeval,           bool time32);int         (*listen)       (struct socket *sock,   int len);int         (*shutdown)     (struct socket *sock,   int flags);int         (*setsockopt)   (struct socket *sock,   int level, int optname,  char __user *optval,    unsigned int optlen);int         (*getsockopt)   (struct socket *sock,   int level, int optname,  char __user *optval,    int __user *optlen);
#ifdef CONFIG_COMPATint         (*compat_setsockopt)(struct socket *sock,   int level, int optname,    char __user *optval,     unsigned int optlen);int         (*compat_getsockopt)(struct socket *sock,   int level, int optname,    char __user *optval,     int __user *optlen);
#endifint         (*sendmsg)      (struct socket *sock,   struct msghdr *m,        size_t total_len);/* Notes for implementing recvmsg: msg->msg_namelen should get updated by the recvmsg handlers iff msg_name != NULL. * It is by default 0 to prevent returning uninitialized memory to user space.  The recvfrom handlers can assume that msg.msg_name is either          * NULL or has a minimum size of sizeof(struct sockaddr_storage).*/int         (*recvmsg)      (struct socket *sock,   struct msghdr *m,        size_t total_len,                int flags);int         (*mmap)         (struct file *file,     struct socket *sock,     struct vm_area_struct * vma);ssize_t     (*sendpage)     (struct socket *sock,   struct page *page,       int offset, size_t size,         int flags);ssize_t     (*splice_read)  (struct socket *sock,   loff_t *ppos,            struct pipe_inode_info *pipe,    size_t len, unsigned int flags);int         (*set_peek_off) (struct sock *sk,       int val);int         (*peek_len)     (struct socket *sock);/* The following functions are called internally by kernel with sock lock already held. */int         (*read_sock)      (struct sock *sk,     read_descriptor_t *desc, sk_read_actor_t recv_actor);int         (*sendpage_locked)(struct sock *sk,     struct page *page,       int offset,    size_t size,    int flags);int         (*sendmsg_locked) (struct sock *sk,     struct msghdr *msg,      size_t size);int         (*set_rcvlowat)   (struct sock *sk,     int val);
};
  • 如下是简易调用流程图:
    img_socket_ip_protocol_syscall

Socket - UDS/Unix Domain Socket

UDS 简介

  • AF_UNIX(也称为 AF_LOCAL)套接字系列用于在同一台计算机上高效地进行进程间通信。

  • UNIX 域套接字可以是 未命名 的,也可以 绑定到文件系统路径名(标记为套接字类型)。

  • Linux 还支持与文件系统无关的抽象命名空间。

  • 为什么 Unix 需要域套接字?

    • UDS 实现了进程间的高效通信。
    • UDS 支持流式和数据报式两种协议。
  • Unix 套接字是双向的吗?

    • UDS 是双向的,提供了进程之间数据的双向流动,这些进程可能有相同的父进程,也可能没有。
    • 管道提供了类似的功能。但是,它们是单向的,只能在具有相同父进程的进程之间使用。
  • 什么是 UDS 连接?

    • UDS 是用于在同一主机操作系统上执行的进程之间交换数据的数据通信端点。
  • UDS 域套接字路径是什么?

    • UDSUnix 路径命名。例如,套接字可以命名为 /tmp/UnixDeamonSocket.sock
    • UDS 域套接字仅在单个主机上的进程之间通信。
  • Linux 中的 UDS 文件是什么?

    • UDS 可以提供进程交换数据的文件。 用于在同一主机操作系统上运行的进程之间交换数据。
      img_uds_socketfile
  • 什么是 Docker 中的 Unix 套接字?

    • sockDocker 守护程序监听的 UDS。它是 Docker API 的主入口点。
    • 可以是 TCP 套接字,但出于安全原因,Docker 默认使用 UDS
    • 默认情况下,Docker CLI 客户端使用 UDS 来执行 docker 命令。也可以覆盖这些设置。

UDS Server 创建流程

如下是服务端的创建流程,省略了错误处理。

#define TEMP_SOCKET "/tmp/UnixDeamonSocket.sock"
#define MAX_ClIENTS 10
#define MAX_BUFFER_LENGTH 1024
int main(int argc, char *argv[])
{    serverFd = socket(AF_UNIX, SOCK_STREAM, 0);memset(&serverAddress, 0, sizeof(serverAddress));serverAddress.sun_family = AF_UNIX;strncpy(serverAddress.sun_path, TEMP_SOCKET, sizeof(serverAddress.sun_path) - 1);result = bind(serverFd, (const sockaddr *)&serverAddress, sizeof(serverAddress));result = listen(serverFd, MAX_ClIENTS);while (1){struct sockaddr_un clientAddress;socklen_t clientAddressLength = sizeof(clientAddress);int clientFd = accept(serverFd, (struct sockaddr *)&clientAddress, &clientAddressLength);while (1){// receiveresult = recv(clientFd, buffer, MAX_BUFFER_LENGTH, 0);           // sendresult = send(clientFd, buffer, strlen(buffer), 0);}close(clientFd);}
Exit:close(serverFd);unlink(TEMP_SOCKET);return 0;
}

UDS Client 创建流程

#define TEMP_SOCKET "/tmp/UnixDeamonSocket.sock"
#define MAX_BUFFER_LENGTH 1024
int main(int argc, char *argv[])
{struct sockaddr_un serverAddress;fd = socket(AF_UNIX, SOCK_STREAM, 0);serverAddress.sun_family = AF_UNIX;strncpy(serverAddress.sun_path, TEMP_SOCKET, sizeof(serverAddress.sun_path) - 1);result = connect(fd, (struct sockaddr *)&serverAddress, sizeof(serverAddress));while (true){char buffer[MAX_BUFFER_LENGTH];// sendmemset(buffer, 0, sizeof(buffer));signal(SIGPIPE, SIG_IGN);snprintf(buffer, sizeof(buffer), "%04d) I am happy.", ++count);result = send(fd, buffer, strlen(buffer), 0);//receivememset(buffer, 0, sizeof(buffer));result = recv(fd, buffer, sizeof(buffer), 0);printf("RX: %.*s\n\n", result, buffer);sleep(1);}Exit:close(fd);return 0;
}

创建 UDS

#include <sys/socket.h>
#include <sys/un.h>
unix_socket = socket(AF_UNIX, type, 0);
error = socketpair(AF_UNIX, type, 0, int *sv);

socketpair() 创建一对连接的 UDS
第一个参数 AF_UNIX 指定了使用 UDS,
第二个参数 type 指定了套接字的类型,
第三个参数为 0 表示使用默认的协议。
最后一个参数是一个指向长度为2的整型数组的指针 int *sv,用于存放创建的套接字的文件描述符。

UDS 类型

  • SOCK_STREAM:用于流式套接字(序列化、可靠、双向、基于连接的字节流);
  • SOCK_DGRAM:用于数据报式套接字,保留消息边界(在大多数UDS实现中,UDS始终可靠,不会重新排序数据报);
  • SOCK_SEQPACKET(自 Linux 2.6.4 起):用于顺序数据包套接字,它是面向连接的、保留消息边界的,并按发送顺序传递消息。

内核源码,如下所示,只支持SOCK_STREAMSOCK_DGRAMSOCK_SEQPACKET三种:
img_uds_socket_type

UDS 地址格式

地址格式如下:

struct sockaddr_un {__kernel_sa_family_t sun_family; /* AF_UNIX */char sun_path[UNIX_PATH_MAX];   /* pathname */
};
  • sun_family字段始终为AF_UNIX
  • Linux上,sun_path的大小为108字节;

img_uds_address_format

  • 地址类型:
    • 路径名类型: 即以文件路径名的方式作为地址格式。可以使用 bind()UDS 绑定到以空字符结尾的文件系统路径名。当返回路径名套接字的地址时,其长度为 offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1,并且 sun_path 包含以空字符结尾的路径名。
    • 未命名类型: 即没有名字的地址格式。未使用bind()将流套接字绑定到路径名的流套接字没有名称。同样,由socketpair()创建的两个套接字也没有名称。其长度为 sizeof(sa_family_t),sun_path 不应被检查。
    • 抽象类型: sun_path[0]是一个空字节('\0')。在此命名空间中,套接字的地址由sun_path中指定长度的地址结构覆盖的附加字节给出。名称与文件系统路径名无关。
UDS 文件名类型 - 路径名
  • sun_path中的路径名应以空字符结尾。

  • 路径名的长度,包括终止的空字符,不应超过sun_path的大小。

  • 描述封闭的sockaddr_un结构的addrlen参数的值至少应为:

    • offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path) + 1
    • sizeof(struct sockaddr_un)
  • 一些实现的sun_path长度短至92字节。

  • 一些(但不是所有)实现在提供的sun_path中如果不存在空字符终止符,会附加一个空字符终止符。

  • 代码示例:

    #define TEMP_SOCKET "/tmp/UnixDeamonSocket.sock"
    int main(int argc, char *argv[])
    {int result = -1;int serverFd = -1;struct sockaddr_un serverAddress;socklen_t serverAddressLength = 0;unlink(TEMP_SOCKET);serverFd = socket(AF_UNIX, SOCK_STREAM, 0);ASSERT_AND_EXIT(serverFd != -1, "socket() failed.");memset(&serverAddress, 0, sizeof(serverAddress));serverAddress.sun_family = AF_UNIX;strcpy(&serverAddress.sun_path[0], TEMP_SOCKET);serverAddressLength = offsetof(struct sockaddr_un, sun_path) + strlen(&serverAddress.sun_path[0]) + 1;// serverAddressLength = sizeof(serverAddress);printf("actual serverAddressLength: %ld\n", offsetof(struct sockaddr_un, sun_path) + strlen(&serverAddress.sun_path[0]) + 1);printf("serverAddressLength       : %d\n", serverAddressLength);result = bind(serverFd, (const struct sockaddr *)&serverAddress, serverAddressLength);ASSERT_AND_EXIT(result != -1, "bind() failed.");serverAddressLength = sizeof(serverAddress);memset(&serverAddress, 0, sizeof(serverAddress));result = getsockname(serverFd, (struct sockaddr *)&serverAddress, &serverAddressLength);ASSERT_AND_EXIT(result != -1, "getsockname() failed.");printf("getsockname() serverAddressLength: %d, path: %s\n", serverAddressLength, &serverAddress.sun_path[0]);getchar();
    Exit:if (serverFd)close(serverFd);// unlink(TEMP_SOCKET);return 0;
    }
    

    img_uds_pathname

    • 路径名遵守所在目录的权限。
    • 如果进程在创建套接字的目录上没有写入和搜索(执行)权限,则失败。
    • 对于该套接字,需要对其具有写入权限。
    • 套接字文件的所有者和组根据通常的规则进行设置。
    • 套接字文件除了被进程 umask() 关闭的权限之外,其他所有权限都已启用。
UDS 文件名类型 - 抽象类型
  • 服务端
int main(int argc, char *argv[])
{int result = -1;int serverFd = -1;struct sockaddr_un serverAddress;socklen_t serverAddressLength = 0;serverFd = socket(AF_UNIX, SOCK_STREAM, 0);ASSERT_AND_EXIT(serverFd != -1, "socket() failed.");memset(&serverAddress, 0, sizeof(serverAddress));serverAddress.sun_family = AF_UNIX;strcpy(&serverAddress.sun_path[1], TEMP_SOCKET);serverAddressLength = offsetof(struct sockaddr_un, sun_path) + strlen(&serverAddress.sun_path[1]) + 1;// serverAddressLength = sizeof(serverAddress);printf("actual serverAddressLength: %ld\n", offsetof(struct sockaddr_un, sun_path) + strlen(&serverAddress.sun_path[1]) + 1);printf("serverAddressLength       : %d\n", serverAddressLength);result = bind(serverFd, (const struct sockaddr *)&serverAddress, serverAddressLength);ASSERT_AND_EXIT(result != -1, "bind() failed.");serverAddressLength = sizeof(serverAddress);memset(&serverAddress, 0, sizeof(serverAddress));result = getsockname(serverFd, (struct sockaddr *)&serverAddress, &serverAddressLength);ASSERT_AND_EXIT(result != -1, "getsockname() failed.");printf("getsockname() serverAddressLength: %d, path: %s\n", serverAddressLength, &serverAddress.sun_path[1]);getchar();
Exit:if (serverFd)close(serverFd);return 0;
}
  • 客户端
int main(int argc, char *argv[])
{int result = -1;int count = 0;int clientFd = -1;struct sockaddr_un serverAddress;socklen_t serverAddressLength = 0;clientFd = socket(AF_UNIX, SOCK_STREAM, 0);ASSERT_AND_EXIT(clientFd != -1, "socket() failed.");memset(&serverAddress, 0, sizeof(serverAddress));serverAddress.sun_family = AF_UNIX;strcpy(&serverAddress.sun_path[1], TEMP_SOCKET);serverAddressLength = offsetof(struct sockaddr_un, sun_path) + strlen(&serverAddress.sun_path[1]) + 1;printf("actual serverAddressLength: %ld\n", offsetof(struct sockaddr_un, sun_path) + strlen(&serverAddress.sun_path[1]) + 1);printf("serverAddressLength       : %d\n", serverAddressLength);result = connect(clientFd, (struct sockaddr *)&serverAddress, serverAddressLength);ASSERT_AND_EXIT(result != -1, "connect() failed.");}
  • 代码执行结果

img_uds_pathname_abstract_1

  • 套接字权限对抽象套接字无意义。
  • 当所有对套接字的打开引用都关闭时,抽象套接字会自动消失。
  • 套接字的名称包含在 sun_path 的前(addrlen - sizeof(sa_family_t))字节中。
    img_uds_pathname_abstract_2
UDS 文件名类型 - 未命名

img_uds_pathname_unamed

UDS C# HTTP Server 示例

  • 新建 dotnet 工程:
dotnet new webapi -minimal -o SocketDemo
  • 示例代码:
const string UnixSocketPath = "/tmp/demo_sever.sock";
if (File.Exists(UnixSocketPath)) {File.Delete(UnixSocketPath);
}
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{options.ListenUnixSocket(UnixSocketPath);
});
var app = builder.Build();
app.MapGet("/", () => "Hello GET");
app.MapPost("/", () => "hello POST");
app.MapPut("/", () => "Hello PUT");
app.MapDelete("/", () => "Hello DELETE");
app.MapGet("/modules", () =>
{return new List<Dictionary<string, string>>{new Dictionary<string, string>{["name"] = "edgeAgent" },new Dictionary<string, string>{["name"] = "edgeHub" },new Dictionary<string, string>{["name"] = "AppModule" }};
});
app.Run();
  • 代码结果:

img_uds_csharp_example

UDS C# HTTP Client 示例1

  • 示例代码:
using System.Net.Sockets;
const string Hostname = "localhost";
const string UnixSocketPath = "/tmp/demo_sever.sock";
using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
var endpoint = new UnixDomainSocketEndPoint(UnixSocketPath);
socket.Connect(endpoint);
var requestBytes = System.Text.Encoding.UTF8.GetBytes($"GET / HTTP/1.0\r\nHost: {Hostname}\r\nAccept: */*\r\n\r\n");
socket.Send(requestBytes);
byte[] receivedBytes = new byte[1024];
socket.Receive(receivedBytes, 1024, SocketFlags.None);
Console.WriteLine(System.Text.Encoding.UTF8.GetString(receivedBytes));
  • 运行结果:

img_uds_csharp_client_1

UDS C# HTTP Client 示例2

  • 示例代码:
using System.Net.Sockets;
const string BaseAddress = "http://localhost";
const string UnixSocketPath = "/tmp/demo_sever.sock";
var httpHandler  = new SocketsHttpHandler
{ConnectCallback = async (context, token) =>{var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);var endpoint = new UnixDomainSocketEndPoint(UnixSocketPath);await socket.ConnectAsync(endpoint).ConfigureAwait(false);return new NetworkStream(socket, ownsSocket: false);}
};
var client = new HttpClient(httpHandler);
client.BaseAddress = new Uri(BaseAddress);
var response = await client.GetAsync("/").ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Console.WriteLine(content);
  • 运行结果:

img_uds_csharp_client_2

IP 协议 VS. UDS (待更新)

UDS 应用场景 —— Docker Daemon

UDS 应用场景 —— Azure IoT Edge

Socket Activation

管道 pipe

匿名管道 Anonymous pipes

命名管道 Named pipe/FIFOs

消息队列

共享内存

信号量

文件锁

共享文件

RPC (Remote Procedure Call)

Protocol Buffer

gRPC

RabbitQM

ZeroMQ

RPC over HTTP

FlatBuffers

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/316622.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA:maven-->>检查 所有依赖 与 环境 兼容

内容 为了确保你项目中的所有依赖都彼此兼容&#xff0c;并与你的环境相适应&#xff0c;你可以利用 Maven 的依赖管理功能。Maven 有助于解决、升级&#xff0c;并对齐所有库的版本&#xff0c;以避免任何不一致或冲突。以下是检查兼容性的步骤&#xff1a; ### 检查兼容性的…

Pulsar Meetup 深圳 2024 会务介绍

“ Hi&#xff0c;各位热爱 Pulsar 的小伙伴们&#xff0c;Pulsar Meetup 深圳 2024 报名倒计时啦&#xff0c;快来报名。这里汇集了腾讯、华为和谙流科技等大量 Pulsar 大咖&#xff0c;干货多多&#xff0c;礼品多多&#xff0c;不容错过啊。 ” 活动介绍 由 AscentStream 谙…

C语言:插入排序

插入排序 1.解释2.步骤3.举例分析示例结果分析 1.解释 插入排序是一种简单直观的排序算法&#xff0c;它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常采…

[Android]Jetpack Compose加载图标和图片

一、加载本地矢量图标 在 Android 开发中使用本地矢量图标是一种常见的做法&#xff0c;因为矢量图标&#xff08;通常保存为 SVG 或 Android 的 XML vector format&#xff09;具有可缩放性和较小的文件大小。 在 Jetpack Compose 中加载本地矢量图标可以使用内置的支持&…

服务器数据恢复—ESXi无法识别数据存储和VMFS文件系统如何恢复数据?

服务器数据恢复环境&#xff1a; 一台某品牌服务器&#xff0c;通过FreeNAS来做iSCSI&#xff0c;然后使用两台同品牌服务器做ESXi虚拟化系统。 FreeNAS层为UFS2文件系统&#xff0c;使用整个存储建一个稀疏模式的文件&#xff0c;挂载到ESXi虚拟化系统。ESXi虚拟化系统中有3台…

解决VSCode中“#include错误,请更新includePath“问题

目录 1、问题原因 2、解决办法 1、问题原因 在编写C程序时&#xff0c;想引用头文件但是出现如下提示&#xff1a; &#xff08;1&#xff09;首先检查要引用的头文件是否存在&#xff0c;位于哪里。 &#xff08;2&#xff09;如果头文件存在&#xff0c;在编译时提醒VSCo…

【数据结构(邓俊辉)学习笔记】向量03——无序向量

文章目录 0.概述1.元素访问2.置乱器3.判等器与比较器4.无序查找4.1 判等器4.2 顺序查找4.3 实现4.4 复杂度 5. 插入5.1 算法实现5.2 复杂度分析 6. 删除6.1 区间删除6.2 单元删除6.3 复杂度 7. 唯一化7.1 实现7.2 正确性7.3 复杂度 8. 遍历8.1 实现8.2 复杂度 9. 总结 0.概述 …

关闭powertoy自启动

Other methods like task manager, start up program folder, they do not work because you can not even find powertoy at these places

智慧安防视频监控EasyCVR视频汇聚平台无法自动播放视频的原因排查与解决

国标GB28181协议EasyCVR安防视频监控平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;平台支持7*24小时实时高清视频监控&#xff0c;能同时播放多路监控视频流…

全国省级金融发展水平数据集(2000-2022年)

01、数据简介 金融发展水平是一个国家或地区经济实力和国际竞争力的重要体现。它反映了金融体系的成熟程度和发展水平&#xff0c;是衡量一个国家或地区经济发展质量的重要指标。金融发展水平的提高&#xff0c;意味着金融体系能够更好地服务实体经济&#xff0c;推动经济增长…

OFDM802.11a的FPGA实现(五)卷积编码器的FPGA实现与验证(含verilog代码和matlab代码)

目录 1.前言2.卷积编码2.1卷积编码基本概念2.2 802.11a卷积编码器2.3 卷积编码模块设计2.4 Matlab设计与ModelSim仿真验证 1.前言 前面一节完成了扰码器的FPGA设计与Matlab验证&#xff0c;这节继续对卷积编码器进行实现和验证。 2.卷积编码 2.1卷积编码基本概念 卷积码编码器…

STM32学习和实践笔记(22):PWM的介绍以及在STM32中的实现原理

PWM是 Pulse Width Modulation 的缩写&#xff0c;中文意思就是脉冲宽度调制&#xff0c;简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术&#xff0c;其控制简单、灵活和动态响应好等优点而成为电力电子技术最广泛应用的控制方式&#xff…

E-MapReduce极客挑战赛季军方案

前一段时间我参加了E-MapReduce极客挑战赛&#xff0c;很幸运的获得了季军。在这把我的比赛攻略给大家分享一下&#xff0c;希望可以抛砖引玉。 赛题分析与理解 赛题背景&#xff1a; 大数据时代&#xff0c;上云已成为越来越多终端客户大数据方案的落地选择&#xff0c;阿里…

Kimichat使用技巧:方便又实用的kimi+智能体

今天kimi智能助手推出了kimi的功能。简单的说&#xff0c;就是一系列kimi已经写好的提示词&#xff0c;用户可以直接调用、对话。 Kimi分为官方推荐、办公提效、辅助写作、社交娱乐、生活实用这几类。可以从左边侧边栏点击进入。 官方推荐的有&#xff1a; Kimi 001号小客服&…

第十五届蓝桥杯省赛第二场C/C++B组C题【传送阵】题解(AC)

解题思路 由于 a a a 数组是一个 1 1 1 到 n n n 的一个排列&#xff0c;那么形成的一定是如下形式&#xff1a; 一定会构成几个点的循环&#xff0c;或者是几个单独的点。 从任意点开始&#xff0c;如果能进入一个循环&#xff0c;一定可以将整个循环的宝藏都拿走&#x…

【C++】类和对象⑤(static成员 | 友元 | 内部类 | 匿名对象)

&#x1f525;个人主页&#xff1a;Forcible Bug Maker &#x1f525;专栏&#xff1a;C 目录 前言 static静态成员 友元 友元函数 友元类 内部类 匿名对象 结语 前言 本篇主要内容&#xff1a;类和对象的一些知识点补充&#xff0c;包括static静态成员&#xff0c;友…

ITMS-90426: Invalid Swift Support

原文 Please correct the following issues and upload a new binary to App Store Connect. ITMS-90426: Invalid Swift Support - The SwiftSupport folder is missing. Rebuild your app using the current public (GM) version of Xcode and resubmit it. 解决方式 ITMS-…

Linux系统安全及应用(1)

目录 一.账号安全控制 系统账号清理 二.密码安全控制 密码安全控制 三.命令历史限制 命令历史限制 四.限制su切换用户 1&#xff09;将信任的用户加入到wheel组中 2&#xff09;修改su的PAM认证配置文件 ​编辑五.PAM认证的构成 六.使用sudo机制提升权限…

WebSocket的原理、作用、API、常见注解和生命周期的简单介绍,附带SpringBoot示例

文章目录 原理作用客户端 API服务端 API生命周期常见注解SpringBoot示例 WebSocket是一种 通信协议 &#xff0c;它在 客户端和服务器之间建立了一个双向通信的网络连接 。WebSocket是一种基于TCP连接上进行 全双工通信 的 协议 。 WebSocket允许客户端和服务器在 单个TCP连接上…

开放式耳机哪个牌子好?五大爆款机型大盘点

开放式耳机采用挂耳设计&#xff0c;体积小巧&#xff0c;携带方便&#xff0c;并且更加通风透气&#xff0c;避免了耳朵过热和出汗导致的问题&#xff1b;更轻的重量能有效减少长期佩戴对耳朵带来的压力&#xff0c;佩戴时舒适度直接爆表&#xff0c;在跑步、爬山、打球等户外…