003_音频开发_基础篇_Linux进程通信(20种你了解几种?)
文章目录
- 003_音频开发_基础篇_Linux进程通信(20种你了解几种?))
-
- 创作背景
- [`Linux` 进程通信类型](#
Linux
进程通信类型) - [`fork()` 函数](#
fork()
函数) -
- [`fork()` 输出 `2` 次](#
fork()
输出2
次) - [`fork()` 输出 `8` 次](#
fork()
输出8
次) - [`fork()` 返回值](#
fork()
返回值) - [`fork()` 创建子进程 方式一](#
fork()
创建子进程 方式一) - [`fork()` 创建子进程 方式二](#
fork()
创建子进程 方式二) - [`fork()` 创建子进程 方式三](#
fork()
创建子进程 方式三) - [`fork()` 创建子进程 方式四](#
fork()
创建子进程 方式四) - [`fork()` 复制父进程上下文](#
fork()
复制父进程上下文)
- [`fork()` 输出 `2` 次](#
- [`Socket` 套接字](#
Socket
套接字) - [`Socket` - (`Linux IP` 协议)](#
Socket
- (Linux IP
协议)) -
- 总体架构
- [`IP 协议` 协议重要结构体](#
IP 协议
协议重要结构体) - [`IP 协议 API函数`](#
IP 协议 API函数
) - [`IP 协议族`](#
IP 协议族
) - [`IP 协议 socket` 类型](#
IP 协议 socket
类型) - [`IP 协议` 操作函数](#
IP 协议
操作函数)
- [`Socket` - `UDS/Unix Domain Socket`](#
Socket
-UDS/Unix Domain Socket
) -
- [`UDS` 简介](#
UDS
简介) - [`UDS Server` 创建流程](#
UDS Server
创建流程) - [`UDS Client` 创建流程](#
UDS Client
创建流程) - [创建 `UDS`](#创建
UDS
) - [`UDS` 类型](#
UDS
类型) - [`UDS` 地址格式](#
UDS
地址格式) -
- [`UDS` 文件名类型 - 路径名](#
UDS
文件名类型 - 路径名) - [`UDS` 文件名类型 - 抽象类型](#
UDS
文件名类型 - 抽象类型) - [`UDS` 文件名类型 - 未命名](#
UDS
文件名类型 - 未命名)
- [`UDS` 文件名类型 - 路径名](#
- [`UDS C# HTTP Server` 示例](# HTTP Server` 示例)
- [`UDS C# HTTP Client` 示例1](# HTTP Client` 示例1)
- [`UDS C# HTTP Client` 示例2](# HTTP Client` 示例2)
- [`UDS` 简介](#
- [`IP 协议` VS. `UDS` (待更新)](#
IP 协议
VS.UDS
(待更新)) - [`UDS` 应用场景 ------ `Docker Daemon`](#
UDS
应用场景 ——Docker Daemon
) - [`UDS` 应用场景 ------ `Azure IoT Edge`](#
UDS
应用场景 ——Azure IoT Edge
) - [Socket Activation](#Socket Activation)
- [管道 `pipe`](#管道
pipe
) -
- [匿名管道 `Anonymous pipes`](#匿名管道
Anonymous pipes
) - [命名管道 `Named pipe/FIFOs`](#命名管道
Named pipe/FIFOs
)
- [匿名管道 `Anonymous pipes`](#匿名管道
- 消息队列
- 共享内存
- 信号量
- 文件锁
- 共享文件
- RPC (Remote Procedure Call)
- [Protocol Buffer](#Protocol Buffer)
- gRPC
- RabbitQM
- ZeroMQ
- [RPC over HTTP](#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 | 进程可以通过读写共享文件来进行通信,但需要注意同步和并发访问的问题。 |
8 | Memory Mapped Files | Memory Mapped Files | 进程可以将文件映射到它们的地址空间中,实现对文件内容的共享访问。 |
9 | 直接操作内存 | Direct Memory Access | 进程可以直接读写其他进程的内存来进行通信,但这通常需要特殊的权限和保护机制。 |
10 | Event Loops | Event Loops | 进程可以使用事件循环来监听和处理事件,实现进程间的消息传递和通知。 |
11 | 套接字/UDS | Unix | Unix域套接字(Unix Domain Socket),用于本地进程间通信。 |
12 | DBus | dbus | D-Bus 是一个进程间通信机制,常用于 Linux 桌面环境中实现进程间通信和协作。 |
13 | 套接字/IP | IP | Internet套接字(Internet Domain Socket),用于在网络中进行通信。 |
14 | Remote Procedure Calls | RPC | 允许进程调用另一个进程中的函数,实现远程通信和协作。 |
15 | RPC over HTTP | RPC over HTTP | 使用HTTP协议作为传输层,通过远程过程调用(RPC)来进行进程间通信。 |
16 | ZeroMQ | ZeroMQ | ZeroMQ 是一个开源的消息队列库,提供了丰富的 API 和通信模式,用于实现进程间通信。 |
17 | Socket Activation | Socket Activation | systemd中的一种机制,允许在需要时才启动服务进程。通过UNIX域套接字传递连接。 |
18 | RabbitMQ | RabbitMQ | 基于AMQP协议的开源消息代理,用于实现可靠的异步通信。 |
19 | gRPC | gRPC | gRPC 是一个高性能、开源的远程过程调用(RPC)框架,可以用于实现进程间通信。 |
20 | Protocol Buffers | Protocol Buffers | 使用序列化格式将数据结构编码后进行传输,实现进程间通信。 |
21 | FlatBuffers | FlatBuffers | 使用序列化格式将数据结构编码后进行传输,实现进程间通信。 |
fork()
函数
fork()
是一个系统调用,用于创建一个与当前进程完全相同的新进程。- 原进程称为 父进程 ,新创建的进程称为 子进程。
- 在父进程中,
fork()
返回子进程的进程ID
;在子进程中,fork()
返回0
。 - 在调用
fork()
时,两个内存空间具有相同的内容。 - 一个进程进行的内存写操作、文件映射和解除映射操作不会影响另一个进程。
- 注意:
fork()
调用会有2
次返回,正常我们的函数调用只有一次返回。
fork()
输出 2
次
fork()
函数调用后输出两次:当前进程 + 子进程
c
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;
}
fork()
输出 8
次
c
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;
}
fork()
返回值
fork()
函数在父进程中返回子进程的进程 ID
,而在子进程中返回 0
。调用失败,它将返回一个负值,通常是 -1
,表示创建新进程失败。
c
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;
}
fork()
创建子进程 方式一
父进程创建 2
个子进程
c
int main()
{
printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());
// 1. parent
pid_t id = fork();
if (id == 0) // 2. new child
{
fork(); // 3. return twice (one is new child)
}
// total: 3 thread = 1 current + 2 child
printf("#### Exit pid=%d, parent pid=%d\n", getpid(), getppid());
return 0;
}
fork()
创建子进程 方式二
父进程创建 4
个子进程
c
int main()
{
printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid()); // 1. parent
pid_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;
}
fork()
创建子进程 方式三
父进程创建 4
个子进程
c
int main(void) {
printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid()); pid_t id[4]; // 1. parent
for (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 times
return 0;
}
fork()
创建子进程 方式四
父进程比子进程先退出,子进程会变为"孤儿进程 ",被1
号/init --user
进程收养,进程编号也会变为 1
。
c
int main()
{
printf("#### Start pid=%d, parent pid=%d\n\n", getpid(), getppid());
pid_t id[4];
// 1. parent
for (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;
}
fork()
复制父进程上下文
c
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;
}
Socket
套接字
-
套接字是一种软件对象,作为端点,建立了服务器端和客户端程序之间的双向网络通信链路。
-
套接字允许在同一台或不同的机器上两个不同进程之间进行通信。
-
Socket
本意是 插座/插口 的意思,其实也可以这样理解,一台电脑(服务器)有很多插口,另一台电脑(客户端)有很多插头,把客户端电脑的一个插头,插入到服务端电脑的插孔种,这样2
台电脑就建立了通信桥梁。 -
下图左边是 服务端 创建
socket
流程,右边是 客户端 创建流程:
Socket
- (Linux IP
协议)
总体架构
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 协议
协议重要结构体
state:
表示套接字当前所处的状态,如连接已建立、连接中等。type:
套接字类型,表示套接字的通信方式,如常用的 流式套接字 、数据报套接字等。flags:
套接字的标志,用于指示特定的套接字属性或配置。ops:
指向协议特定套接字操作的指针,通常指向一组函数指针,用于执行套接字操作,如发送数据、接收数据等。file:
这是指向套接字相关文件的指针。在Linux
内核中,套接字也可以通过文件系统进行访问和操作,这个指针可能指向套接字关联的文件结构。sk:
这是一个指向内部网络协议无关套接字表示的指针。它通常指向struct sock
类型的数据结构,该结构包含了更底层的网络协议相关信息。wq:
等待队列,用于处理各种套接字相关的等待事件,比如等待连接、等待数据等。
IP 协议 API函数
socket()
: 创建新套接字。bind()
: 将套接字与地址绑定。listen()
: 将套接字设置为监听模式。accept()
: 接受连接请求并创建新套接字。connect()
: 建立与目标套接字的连接。send()
: 发送数据到连接的套接字。recv()
: 从套接字接收数据。close()
: 关闭套接字。shutdown()
: 关闭套接字的读、写或两者。getsockopt()
: 获取套接字选项。setsockopt()
: 设置套接字选项。
IP 协议族
- 地址族和协议族其实是一样的,值也一样,都是用来识别不同协议的,为什么要搞两套东西呢?
- 这是因为之前
Unix
有两种风格系统:BSD
系统和POSIX
系统,对于BSD
系统,一直用的是AF
,对于POSIX
系统,一直用的是PF
。Linux
作为后起之秀,为了兼容,所以两种都支持,这样两种风格的Unix
下的软件就可以在Linux
上运行了。 POSIX
系统:Bell
实验室的Ken Thompson
和Dennis Richie
一起发明了Unix
系统。BSD
系统:BSD
代表伯克利软件条件,是20
世界70
年代,加州大学伯克利分校对贝尔实验室Unix
进行了一系列修改后的版本,它最终发展成了一个完整的操作系统,有着自己的一套标准。
IP 协议 socket
类型
c
/* 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
: 用于socket
和socketpair
的标志,用于原子性地设置新描述符的close-on-exec
标志。SOCK_NONBLOCK
: 用于socket
和socketpair
的标志,用于原子性地将描述符标记为非阻塞。
IP 协议
操作函数
应用层相应的函数,通过 glibc
封装的函数,进行系统调用,最后调用这里的函数。
c
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_COMPAT
int (*compat_ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg);
#endif
int (*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_COMPAT
int (*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);
#endif
int (*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);
};
- 如下是简易调用流程图:
Socket
- UDS/Unix Domain Socket
UDS
简介
-
AF_UNIX
(也称为AF_LOCAL
)套接字系列用于在同一台计算机上高效地进行进程间通信。 -
UNIX
域套接字可以是 未命名 的,也可以 绑定到文件系统路径名(标记为套接字类型)。 -
Linux
还支持与文件系统无关的抽象命名空间。 -
为什么
Unix
需要域套接字?UDS
实现了进程间的高效通信。UDS
支持流式和数据报式两种协议。
-
Unix
套接字是双向的吗?UDS
是双向的,提供了进程之间数据的双向流动,这些进程可能有相同的父进程,也可能没有。- 管道提供了类似的功能。但是,它们是单向的,只能在具有相同父进程的进程之间使用。
-
什么是
UDS
连接?UDS
是用于在同一主机操作系统上执行的进程之间交换数据的数据通信端点。
-
UDS
域套接字路径是什么?UDS
以Unix
路径命名。例如,套接字可以命名为/tmp/UnixDeamonSocket.sock
。UDS
域套接字仅在单个主机上的进程之间通信。
-
Linux
中的UDS
文件是什么?UDS
可以提供进程交换数据的文件。 用于在同一主机操作系统上运行的进程之间交换数据。
-
什么是
Docker
中的Unix
套接字?sock
是Docker
守护程序监听的UDS
。它是Docker API
的主入口点。- 可以是
TCP 套接字
,但出于安全原因,Docker
默认使用UDS
。 - 默认情况下,
Docker CLI
客户端使用UDS
来执行docker
命令。也可以覆盖这些设置。
UDS Server
创建流程
如下是服务端的创建流程,省略了错误处理。
c
#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)
{
// receive
result = recv(clientFd, buffer, MAX_BUFFER_LENGTH, 0); // send
result = send(clientFd, buffer, strlen(buffer), 0);
}
close(clientFd);
}
Exit:
close(serverFd);
unlink(TEMP_SOCKET);
return 0;
}
UDS Client
创建流程
c
#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];
// send
memset(buffer, 0, sizeof(buffer));
signal(SIGPIPE, SIG_IGN);
snprintf(buffer, sizeof(buffer), "%04d) I am happy.", ++count);
result = send(fd, buffer, strlen(buffer), 0);
//receive
memset(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
c
#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_STREAM
、SOCK_DGRAM
和SOCK_SEQPACKET
三种:
UDS
地址格式
地址格式如下:
c
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
字节;
- 地址类型:
- 路径名类型: 即以文件路径名的方式作为地址格式。可以使用
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
中如果不存在空字符终止符,会附加一个空字符终止符。 -
代码示例:
c#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; }
- 路径名遵守所在目录的权限。
- 如果进程在创建套接字的目录上没有写入和搜索(执行)权限,则失败。
- 对于该套接字,需要对其具有写入权限。
- 套接字文件的所有者和组根据通常的规则进行设置。
- 套接字文件除了被进程
umask()
关闭的权限之外,其他所有权限都已启用。
UDS
文件名类型 - 抽象类型
- 服务端
c
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;
}
- 客户端
c
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.");
...
}
- 代码执行结果
- 套接字权限对抽象套接字无意义。
- 当所有对套接字的打开引用都关闭时,抽象套接字会自动消失。
- 套接字的名称包含在
sun_path
的前(addrlen - sizeof(sa_family_t)
)字节中。
UDS
文件名类型 - 未命名
UDS C# HTTP Server
示例
- 新建
dotnet
工程:
shell
dotnet new webapi -minimal -o SocketDemo
- 示例代码:
c#
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();
- 代码结果:
UDS C# HTTP Client
示例1
- 示例代码:
c#
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));
- 运行结果:
UDS C# HTTP Client
示例2
- 示例代码:
c#
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);
- 运行结果: