赞
踩
Unix域套接字(Unix domain socket)是一种在同一台计算机上进程间进行通信的机制,它是在Unix和类Unix操作系统中广泛使用的一种IPC(进程间通信)方式。
与网络套接字(socket)不同,Unix域套接字不依赖于网络协议栈,而是使用文件系统作为通信的基础。它在文件系统中创建一个特殊类型的文件,进程可以通过该文件进行通信。
Unix域套接字提供了一种可靠且高效的进程间通信方式,适用于同一台计算机上的进程之间的通信。它具有以下特点:
Unix域套接字可以用于各种进程间通信的应用场景,比如本地进程之间的通信、服务器与客户端之间的通信等。它在Unix系统上被广泛应用于各种服务和应用程序,如数据库服务器、Web服务器、进程间通信工具等。
Unix域套接字的原理是基于文件系统的通信机制。它利用文件系统中的特殊文件来实现进程间的通信。
下面是一个简单的图解说明Unix域套接字的原理:
+-----------+ +-----------+
| Process 1| | Process 2|
+-----------+ +-----------+
| |
| Unix Domain Socket (File) |
| |
+----------------------------------------------+
| |
| Kernel |
| |
+----------------------------------------------+
在这个过程中,Unix域套接字的通信是通过文件系统来完成的。进程可以使用类似读写文件的方式来读取和写入套接字,而不需要网络协议的参与。
上面只是一个简化的图示,实际的实现细节更加复杂。不同的操作系统和编程语言可能有不同的具体实现方式,但基本原理是相似的。
总之,Unix域套接字利用文件系统的特殊文件作为通信的载体,在内核层实现进程间的通信,提供了高效、可靠且安全的进程间通信机制。
包含头文件:
#include <sys/socket.h> // 套接字相关函数和结构
#include <sys/un.h> // Unix域套接字相关结构
创建套接字:
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
// 处理套接字创建失败的情况
}
绑定套接字到文件路径:
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "/path/to/socket", sizeof(addr.sun_path) - 1);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) == -1) {
// 处理套接字绑定失败的情况
}
监听连接:
if (listen(sockfd, backlog) == -1) {
// 处理监听失败的情况
}
接受连接请求:
int clientfd = accept(sockfd, nullptr, nullptr);
if (clientfd == -1) {
// 处理接受连接失败的情况
}
进行数据读写:
char buffer[1024];
ssize_t bytesRead = recv(clientfd, buffer, sizeof(buffer), 0);
if (bytesRead == -1) {
// 处理接收数据失败的情况
}
ssize_t bytesWritten = send(clientfd, buffer, bytesRead, 0);
if (bytesWritten == -1) {
// 处理发送数据失败的情况
}
关闭套接字:
close(sockfd);
close(clientfd);
上述代码仅为示例,实际使用时需要根据具体情况进行适当的错误处理、数据解析等。此外,还可以使用异常处理、封装等技术来提高代码的可靠性和可维护性。
编写Unix域套接字的C++代码时,还应遵循一般的C++编码规范,如使用合适的命名规范、注释、模块化设计等。
包含头文件:
#include <sys/socket.h> // 套接字相关函数和结构
#include <sys/un.h> // Unix域套接字相关结构
创建套接字:
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
// 处理套接字创建失败的情况
}
连接到服务器:
struct sockaddr_un serverAddr;
memset(&serverAddr, 0, sizeof(struct sockaddr_un));
serverAddr.sun_family = AF_UNIX;
strncpy(serverAddr.sun_path, "/path/to/socket", sizeof(serverAddr.sun_path) - 1);
if (connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr_un)) == -1) {
// 处理连接失败的情况
}
进行数据读写:
char buffer[1024];
ssize_t bytesRead = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytesRead == -1) {
// 处理接收数据失败的情况
}
ssize_t bytesWritten = send(sockfd, buffer, bytesRead, 0);
if (bytesWritten == -1) {
// 处理发送数据失败的情况
}
关闭套接字:
close(sockfd);
与服务器端代码相比,客户端代码的主要区别在于连接到服务器的步骤。客户端需要指定服务器的地址和套接字路径,并通过connect
函数与服务器建立连接。
同样,需要根据实际情况进行适当的错误处理和数据解析。另外,遵循一般的C++编码规范,如命名规范、注释、模块化设计等,也是编写可靠和可维护代码的重要方面。
以下是一个简单的C++示例,演示了使用Unix域套接字的服务端和客户端。该示例中,客户端与服务端建立连接后,客户端连续发送消息给服务端,而服务端能够在客户端断开连接后继续监听。
server.cpp
#include <iostream> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> // #define SOCKET_PATH "/tmp/unix_socket_demo" #define SOCKET_PATH "./unix_socket_demo" int main() { // 创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) { std::cerr << "Failed to create socket" << std::endl; return 1; } // 绑定套接字到文件路径 struct sockaddr_un serverAddr; memset(&serverAddr, 0, sizeof(struct sockaddr_un)); serverAddr.sun_family = AF_UNIX; unlink(SOCKET_PATH); // arnold add 20230608 // 要unlink一下,否则无法重新绑定 strncpy(serverAddr.sun_path, SOCKET_PATH, sizeof(serverAddr.sun_path) - 1); if (bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr_un)) == -1) { std::cerr << "Failed to bind socket" << std::endl; close(sockfd); return 1; } // 监听连接 if (listen(sockfd, 5) == -1) { std::cerr << "Failed to listen" << std::endl; close(sockfd); return 1; } while (true) { std::cout << "Waiting for client connection..." << std::endl; // 接受连接请求 int clientfd = accept(sockfd, nullptr, nullptr); if (clientfd == -1) { std::cerr << "Failed to accept connection" << std::endl; close(sockfd); return 1; } std::cout << "Client connected!" << std::endl; // 接收和发送数据 char buffer[1024]; ssize_t bytesRead; while ((bytesRead = recv(clientfd, buffer, sizeof(buffer), 0)) > 0) { std::cout << "Received message from client: " << buffer << std::endl; // 在此处进行需要的处理 // 发送回复给客户端 ssize_t bytesWritten = send(clientfd, buffer, bytesRead, 0); if (bytesWritten == -1) { std::cerr << "Failed to send response to client" << std::endl; close(clientfd); break; } } if (bytesRead == -1) { std::cerr << "Failed to receive data from client" << std::endl; } std::cout << "Client disconnected" << std::endl; close(clientfd); } close(sockfd); return 0; }
client.cpp
#include <iostream> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> // #define SOCKET_PATH "/tmp/unix_socket_demo" #define SOCKET_PATH "./unix_socket_demo" int main() { // 创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) { std::cerr << "Failed to create socket" << std::endl; return 1; } // 连接到服务器 struct sockaddr_un serverAddr; memset(&serverAddr, 0, sizeof(struct sockaddr_un)); serverAddr.sun_family = AF_UNIX; strncpy(serverAddr.sun_path, SOCKET_PATH, sizeof(serverAddr.sun_path) - 1); while (true) { while (true) { if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr_un)) == -1) { // std::cerr << "Failed to connect to server" << std::endl; std::cerr << "Failed to connect to server, sleep 1s and reconnect..." << std::endl; sleep(1); continue; // close(sockfd); // return 1; } break; } std::cout << "Connected to server!" << std::endl; while (true) { try // 貌似这里try多余了,如果服务端是被强行关闭的,客户端这里是捕获不到的,也会强行结束 { // 输入要发送的消息 // std::cout << "Enter message to send (or 'q' to quit): "; // std::string message; std::string message = "client auto send a message"; // std::getline(std::cin, message); // if (message == "q") // { // break; // } // 发送消息给服务器 ssize_t bytesWritten = send(sockfd, message.c_str(), message.length(), 0); if (bytesWritten == -1) { std::cerr << "Failed to send message to server" << std::endl; break; // close(sockfd); // return 1; } sleep(1); // 接收服务器的回复 char buffer[1024]; ssize_t bytesRead = recv(sockfd, buffer, sizeof(buffer) - 1, 0); if (bytesRead == -1) { std::cerr << "Failed to receive response from server" << std::endl; break; // close(sockfd); // return 1; } buffer[bytesRead] = '\0'; std::cout << "Received response from server: " << buffer << std::endl; } catch(std::exception& e) { std::cout << "catch(std::exception& e): " << e.what() << std::endl; break; } } // std::cout << "Disconnecting from server" << std::endl; // close(sockfd); // return 0; } }
开两个shell窗口,分别执行:
g++ server.cpp -o server && ./server
g++ client.cpp -o client && ./client
服务端
客户端
可以看到,程序在启动后,在当前目录下自动创建了一个套接字文件,删不删也没关系,下回还能继续用:
在使用Unix域套接字时,有一些注意事项需要考虑:
文件路径冲突:Unix域套接字使用文件系统中的特殊文件进行通信,因此需要选择一个合适的文件路径来创建套接字。确保选择的路径不会与现有的文件或目录发生冲突,并且有适当的权限设置。
套接字文件清理:在使用完Unix域套接字后,需要负责清理套接字文件。确保在程序终止或不再需要套接字时删除相关的套接字文件,以避免文件积累和资源浪费。
权限设置:Unix域套接字的文件权限设置很重要。确保只有有权访问该套接字的进程能够读取和写入套接字文件。适当设置文件权限可以提高安全性。
进程间通信协议:使用Unix域套接字进行进程间通信时,需要协调好通信的协议和数据格式。定义清晰的协议和消息格式可以确保通信的正确性和一致性。
错误处理:在使用Unix域套接字时,需要适当处理错误情况。例如,处理连接失败、通信中断或其他错误的情况,并采取适当的措施,如重新连接或报告错误。
平台兼容性:虽然Unix域套接字在不同的Unix和类Unix系统上得到广泛支持,但在使用时仍需注意平台兼容性问题。确保所使用的系统支持Unix域套接字,并了解相关系统调用和函数的差异。
使用Unix域套接字时需要关注文件路径冲突、文件清理、权限设置、通信协议、错误处理和平台兼容性等方面的注意事项,以确保通信的正确性、安全性和可靠性。
解决办法:需要加个unlink
抽象套接字(Abstract Socket)属于Unix域套接字(Unix Domain Socket)的一种特殊类型。Unix域套接字是一种用于本地进程间通信的机制,不依赖于网络协议栈,而是基于文件系统路径来标识和定位套接字。
抽象套接字是Unix域套接字的扩展,引入了一种特殊的命名约定,用于创建不依赖于文件系统路径的套接字。它只存在于内核中,不会在文件系统中创建对应的文件。抽象套接字的名称存储在套接字地址结构的文件路径字段中,但并不对应实际的文件路径。
因此,可以说抽象套接字是Unix域套接字的一种变体或特殊情况,它们共享相同的通信机制和特性,但在套接字的命名和可见性方面有所区别。
创建抽象套接字和普通Unix域套接字的代码有一些区别。以下是它们之间的主要区别:
套接字地址结构类型:
struct sockaddr_un
结构来表示套接字地址。该结构包含一个文件路径字段(sun_path
)和一个地址族字段(sun_family
)。struct sockaddr_un
结构的变体来表示套接字地址。该变体结构中的文件路径字段(sun_path
)不再表示实际的文件路径,而是用于存储套接字名称。绑定套接字地址:
bind()
函数将套接字地址绑定到指定的路径上。sun_path
字段中以特殊格式指定套接字名称,然后使用bind()
函数将套接字地址绑定到特殊格式的路径上。下面是创建抽象套接字和普通Unix域套接字的代码示例:
普通Unix域套接字的代码示例:
#include <sys/socket.h> #include <sys/un.h> int main() { // 创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 绑定套接字到文件路径 struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, "/path/to/socket", sizeof(addr.sun_path) - 1); bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); // 其他操作... return 0; }
抽象套接字的代码示例:
#include <sys/socket.h> #include <sys/un.h> int main() { // 创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 绑定套接字到抽象名称 struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path + 1, "abstract_socket", sizeof(addr.sun_path) - 2); // 在第二个字符位置插入名称 addr.sun_path[0] = '\0'; // 将第一个字符设置为零字节,表示抽象套接字 bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); // 其他操作... return 0; }
请注意,抽象套接字的名称是通过在sun_path
字段的第二个字符位置插入名称来指定的,并在sun_path
的第一个字符位置设置零字节。这是用于表示抽象套接字的特殊命名约定。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。