概念:
本地套接字是IPC, 即本地进程间通信的一种实现方式。除了本地套接字以外,还有前面学过的其他技术,如管道,共享内存,信号,信号量,信号灯集,消息队列等也是进程间通信的常用方法。
- 创建套接字时使用本地协议PF_UNIX(或PF_LOCAL)。这两种表达方式本质相同,
AF_UNIX
是比较传统的命名方式,AF_LOCAL
是后来为了更通用的命名而引入的。其想法是将这种本地通信(区别于网络通信)的套接字地址族称为 “本地(LOCAL)” 通信方式,而不是和特定的 UNIX 操作系统绑定。 - AF_UNIX(地址族):AF 代表 “Address Family”,即地址族。AF_UNIX 用于指定 UNIX 域套接字的地址格式。它主要关注的是地址的格式和表示方式,用于在同一主机上的进程间通信。例如,当你使用 UNIX 域套接字进行通信时,你需要使用 AF_UNIX 来告诉系统你要使用的地址类型是基于 UNIX 域套接字的地址,这种地址通常和文件系统路径相关。
- PF_UNIX(协议族):PF 代表 “Protocol Family”,即协议族。PF_UNIX 用于指定 UNIX 域套接字所使用的协议族。它更侧重于协议的分类,表明在这个通信过程中所遵循的协议集合是属于 UNIX 域套接字相关的协议。比如,当创建一个套接字时,使用 PF_UNIX 表示这个套接字将遵循 UNIX 域套接字协议来进行通信。
- 分为流式套接字和用户数据报套接字
- 和其他进程间通信方式相比使用方便、效率更高
- 常用于前后台进程通信
利用本地套接字可完成可靠字节流和数据报两种协议
可通过ls -l命令查看Linux系统内的本地套接字状况。
套接字是一种特殊类型的套接字,与TCP/UDP套接字不同。TCP/UDP即使在本地地址通信,也要走系统网络协议栈,而本地套接字,严格意义上来说提供了一种单主机跨进程间调用的手段,减少 协议栈实现的复杂度,效率比TCP/UDP套接字高很多。
本地地址就是本地套接字专属的。
API接口
#include <sys/socket.h>
#include <sys/un.h>
unix_socket = socket(AF_UNIX, type, 0);
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* 本地路径 */
};
struct sockaddr_un myaddr;
bzero(&myaddr, sizeof(myaddr));
myaddr.sun_family = PF_UNIX;
strcpy(myaddr.sun_path, "mysocket"); //可以指定路径
本地字节流套接字
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_socket"
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_un address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
int opt = 1;
int addrlen_size;
// 创建套接字
if ((server_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项,允许重用地址和端口
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
close(server_fd);
exit(EXIT_FAILURE);
}
// 清除地址结构
memset(&address, 0, sizeof(struct sockaddr_un));
// 设置地址和端口
address.sun_family = AF_UNIX;
strncpy(address.sun_path, SOCKET_PATH, sizeof(address.sun_path) - 1);
// 绑定套接字到地址
if (bind(server_fd, (struct sockaddr *)&address, sizeof(struct sockaddr_un)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen");
close(server_fd);
unlink(SOCKET_PATH); // 删除套接字文件
exit(EXIT_FAILURE);
}
// 接受客户端连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen_size)) < 0) {
perror("accept");
close(server_fd);
unlink(SOCKET_PATH); // 删除套接字文件
exit(EXIT_FAILURE);
}
// 读取客户端发送的数据
int valread = read(new_socket, buffer, BUFFER_SIZE);
printf("Received: %s\n", buffer);
// 发送响应给客户端
char *hello = "Hello from server";
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 关闭套接字
close(new_socket);
close(server_fd);
unlink(SOCKET_PATH); // 删除套接字文件
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_socket"
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_un serv_addr;
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from client";
// 创建套接字
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("Socket creation error");
return -1;
}
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, SOCKET_PATH);
// 连接服务端
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_un)) < 0) {
perror("Connection Failed");
return -1;
}
// 发送数据到服务端
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 读取服务端的响应
int valread = read(sock, buffer, BUFFER_SIZE);
printf("Received: %s\n", buffer);
// 关闭套接字
close(sock);
return 0;
}
注意:关于本地文件路径,需要明确一点,它必须是“绝对路径”,这样的话,编写好的程序可以在任何目录里被启动和管理。如果是“相对路径”,为了保持同样的目的,这个程序的启动路径就必须固定,这样一来,对程序的管理反而是一个很大的负担。
另外还要明确一点,这个本地文件,必须是一个“文件”,不能是一个“目录”。如果文件不存在,后面 bind 操作时会自动创建这个文件。
本地数据报套接字
这部分内容省略,详细看:
IPC——数据报套接字通信 - Jessica程序猿 - 博客园