前面讨论了 UNIX 系统各种 IPC、管道、共享内存、套接字、消息队列,本章将会学习一种高级 IPC,UNIX 域套接字机制。
UNIX 域套接字是一种用于在同一台计算机上的进程之间进行通信的机制。它类似于网络套接字,但不依赖于网络协议栈,而是在操作系统的内核中直接传递数据。
UNIX 域套接字使用文件系统路径作为套接字地址,进程可以通过打开和读写文件来进行通信。这种通信方式比使用网络套接字更高效,因为数据不需要通过网络协议栈进行封装和解封装。
UNIX 域套接字通常用于同一台计算机上的进程间通信,例如在客户端和服务器之间进行本地通信。它提供了一种可靠的、高性能的通信机制,适用于需要高速数据传输和低延迟的应用程序。
socketpair 函数是用于创建一对相互连接的 UNIX 域套接字的函数。它的原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
使用注意事项:
socketpair 为什么会返回两个 socket 文件描述符?
socketpair
函数返回两个套接字文件描述符,一个用于读取数据,一个用于写入数据。这是因为
socketpair 函数创建的是一对相互连接的套接字,可以实现双向通信。
其中, sv[0] 代表读取数据的套接字文件描述符, sv[1]
代表写入数据的套接字文件描述符。通过这两个文件描述符,可以在两个进程之间进行双向通信,实现数据的发送和接收。
需要注意的是,socketpair
函数创建的套接字对是全双工的,即可以同时进行读取和写入操作。因此,通过这两个套接字文件描述符,可以实现双向的数据传输。
从第 1 个 fd 写的数据从第 2 个 fd 读出
从第 2 个 fd 写的数据从第 1 个 fd 读出
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
int main() {
int sv[2];
char buf[256];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
("socketpair");
perrorreturn 1;
}
if (fork() == 0) {
// 子进程
(sv[0]); // 关闭子进程中不需要的套接字端口
close// 子进程向父进程发送消息
char childMsg[] = "Hello from child";
(sv[1], childMsg, strlen(childMsg) + 1);
write// 子进程从父进程接收消息
(sv[1], buf, sizeof(buf));
read("Child received message: %s\n", buf);
printf(sv[1]);
close} else {
// 父进程
(sv[1]); // 关闭父进程中不需要的套接字端口
close// 父进程从子进程接收消息
(sv[0], buf, sizeof(buf));
read("Parent received message: %s\n", buf);
printf// 父进程向子进程发送消息
char parentMsg[] = "Hello from parent";
(sv[0], parentMsg, strlen(parentMsg) + 1);
write(sv[0]);
close}
return 0;
}
但是文件描述符是在进程内有效得啊,不同得进程肯定要命名的,那么就会引出命名 UNIX 域套接字
UNIX 域套接字的地址由 sockaddr_un 结构表示,一般定义在 sys/un.h
服务端样例,如果不进行 unlink 那么下次绑定同样的路径地址,则会出现 Failed to bind add,除非手动将路径的文件删除
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
using namespace std;
int main()
{
// 创建UNIX域套接字
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (-1 == sockfd)
{
<< "Failed to create unix domain socket" << endl;
cerr return EXIT_FAILURE;
}
// 设置地址
;
sockaddr_un domain_addr.sun_family = AF_UNIX;
domain_addr(domain_addr.sun_path, "/tmp/socket", sizeof(domain_addr.sun_path) - 1);
strncpy// 绑定地址
if (bind(sockfd, (struct sockaddr *)&domain_addr, sizeof(domain_addr)) == -1)
{
<< "Failed to bind addr" << endl;
cerr return EXIT_FAILURE;
}
// 监听连接
if (listen(sockfd, 1) == -1)
{
<< "Failed to listen on socket" << endl;
cerr (sockfd);
closereturn EXIT_FAILURE;
}
<< "Waiting to listen on socket" << endl;
cout // 接收连接
;
sockaddr_un client_addrsocklen_t client_len = sizeof(client_addr);
int client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (client_sockfd == -1)
{
<< "Failed to accept client connection" << endl;
cerr (sockfd);
closereturn EXIT_FAILURE;
}
<< "Client connected" << endl;
cout // 处理客户端请求
char buffer[1024];
int len = read(client_sockfd, buffer, sizeof(buffer) - 1);
if (len > 0)
{
[len] = 0;
buffer<< buffer << endl;
cout }
// 关闭套接字
(client_sockfd);
close(sockfd);
close// 删除套接字文件
("/tmp/socket");
unlinkreturn 0;
}
客户端样例
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
// 创建UNIX域套接字
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1)
{
std::cerr << "创建套接字失败" << std::endl;
return 1;
}
// 设置服务器地址
;
sockaddr_un server_addr.sun_family = AF_UNIX;
server_addr(server_addr.sun_path, "/tmp/socket", sizeof(server_addr.sun_path) - 1);
strncpy// 连接服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
std::cerr << "连接服务器失败" << std::endl;
(sockfd);
closereturn 1;
}
std::cout << "已连接到服务器" << std::endl;
// 向服务器发送数据
const char *message = "Hello, server!";
if (write(sockfd, message, strlen(message)) == -1)
{
std::cerr << "发送数据失败" << std::endl;
}
// 关闭套接字
(sockfd);
closereturn 0;
}
如果需要两个进程双方都能收发,则要创建两个 UNIX 域 UDP 套接字,二者分别做 UDP 服务端与客户端
服务端样例
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
int main()
{
// 创建UNIX域套接字
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd == -1)
{
std::cerr << "创建套接字失败" << std::endl;
return 1;
}
// 设置服务器地址
;
sockaddr_un server_addr.sun_family = AF_UNIX;
server_addr(server_addr.sun_path, "/tmp/socket", sizeof(server_addr.sun_path) - 1);
strncpy// 绑定套接字
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
std::cerr << "绑定套接字失败" << std::endl;
(sockfd);
closereturn 1;
}
std::cout << "等待接收数据..." << std::endl;
while (true)
{
char buffer[1024];
;
sockaddr_un client_addrsocklen_t client_len = sizeof(client_addr);
// 接收数据
ssize_t bytesRead = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &client_len);
if (bytesRead == -1)
{
std::cerr << "接收数据失败" << std::endl;
(sockfd);
closereturn 1;
}
std::cout << "接收到的数据:" << buffer << std::endl;
}
// 关闭套接字
(sockfd);
close("/tmp/socket");
unlinkreturn 0;
}
客户端样例
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
int main()
{
// 创建UNIX域套接字
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd == -1)
{
std::cerr << "创建套接字失败" << std::endl;
return 1;
}
// 设置服务器地址
;
sockaddr_un server_addr.sun_family = AF_UNIX;
server_addr(server_addr.sun_path, "/tmp/socket", sizeof(server_addr.sun_path) - 1);
strncpy// 发送数据
const char *message = "Hello, server!";
ssize_t bytesSent = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bytesSent == -1)
{
std::cerr << "发送数据失败" << std::endl;
(sockfd);
closereturn 1;
}
// 关闭套接字
(sockfd);
closereturn 0;
}
每个进程有自己的进程表项,是一个数组,下标为 fd 文件描述符。值为文件指针地址,其指向文件表
//文件表
|--------------|
| 文件状态标志 |
|--------------|
|当前文件偏移量 |
|--------------|
| v节点指针 |
|--------------|
v 节点表中存储 v 节点信息和 v_data,v_data 中能找到 inode 信息
虽然不同进程打开同一个文件时,文件表不同,但是其 v 节点是相同的。有没有方法实现不同进程共享同一个文件表
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <fcntl.h>
int main() {
// 创建UNIX域套接字
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
std::cerr << "创建套接字失败" << std::endl;
return 1;
}
// 设置服务器地址
;
sockaddr_un server_addr.sun_family = AF_UNIX;
server_addr(server_addr.sun_path, "/tmp/socket", sizeof(server_addr.sun_path) - 1);
strncpy// 连接服务器
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
std::cerr << "连接服务器失败" << std::endl;
(sockfd);
closereturn 1;
}
// 打开要传输的文件
int filefd = open("file.txt", O_RDONLY);
if (filefd == -1) {
std::cerr << "打开文件失败" << std::endl;
(sockfd);
closereturn 1;
}
// 构建消息头
char buf[CMSG_SPACE(sizeof(filefd))];
(buf, 0, sizeof(buf));
memsetstruct iovec iov;
.iov_base = (void*)"";
iov.iov_len = 1;
iov// 构建消息
struct msghdr msg;
(&msg, 0, sizeof(msg));
memset.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
msg// 构建文件描述符消息
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
->cmsg_len = CMSG_LEN(sizeof(filefd));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg(CMSG_DATA(cmsg), &filefd, sizeof(filefd));
memcpy// 发送消息
if (sendmsg(sockfd, &msg, 0) == -1) {
std::cerr << "发送消息失败" << std::endl;
}
// 关闭套接字和文件描述符
(filefd);
close(sockfd);
closereturn 0;
}
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <fcntl.h>
int main() {
// 创建UNIX域套接字
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
std::cerr << "创建套接字失败" << std::endl;
return 1;
}
// 设置服务器地址
;
sockaddr_un server_addr.sun_family = AF_UNIX;
server_addr(server_addr.sun_path, "/tmp/socket", sizeof(server_addr.sun_path) - 1);
strncpy// 绑定套接字
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
std::cerr << "绑定套接字失败" << std::endl;
(sockfd);
closereturn 1;
}
// 监听连接
if (listen(sockfd, 1) == -1) {
std::cerr << "监听套接字失败" << std::endl;
(sockfd);
closereturn 1;
}
std::cout << "等待发送进程连接..." << std::endl;
// 接受发送进程连接
;
sockaddr_un client_addrsocklen_t client_len = sizeof(client_addr);
int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
if (client_sockfd == -1) {
std::cerr << "接受连接失败" << std::endl;
(sockfd);
closereturn 1;
}
// 构建消息头
char buf[CMSG_SPACE(sizeof(int))];
(buf, 0, sizeof(buf));
memsetstruct iovec iov;
.iov_base = (void*)"";
iov.iov_len = 1;
iov// 构建消息
struct msghdr msg;
(&msg, 0, sizeof(msg));
memset.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
msg// 接收消息
if (recvmsg(client_sockfd, &msg, 0) == -1) {
std::cerr << "接收消息失败" << std::endl;
(client_sockfd);
close(sockfd);
closereturn 1;
}
// 解析文件描述符
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
int received_fd;
(&received_fd, CMSG_DATA(cmsg), sizeof(int));
memcpy// 使用接收到的文件描述符进行操作
char buffer[1024];
ssize_t bytesRead = read(received_fd, buffer, sizeof(buffer));
if (bytesRead == -1) {
std::cerr << "读取文件失败" << std::endl;
} else {
std::cout << "接收到的文件内容:" << std::endl;
std::cout.write(buffer, bytesRead);
std::cout << std::endl;
}
// 关闭套接字和文件描述符
(received_fd);
close(client_sockfd);
close(sockfd);
closereturn 0;
}
如果你看过了 unix env 部分,收获会非常大,毕竟这些东西都是上层应用的基石,经常回顾学习新知识并复习思考,我想在写代码的路上会越走越远的