10.UNIX套接字域
UNIX域套接字是用于在同一台计算机上运行的进程之间进行通信的一种机制。它与传统基于TCP/IP协议栈的套接字不同,UNIX域套接字操作更为高效,因为它避免了网络层的开销,不涉及网络报头、检验和、顺序号等复杂的网络协议处理过程。UNIX域套接字的特点包括:
- 高效的本地通信:由于不需要网络层面的处理,UNIX域套接字在本地进程间通信时比使用TCP套接字更高效。
- 路径名作为地址:UNIX域套接字使用文件系统中的路径名作为地址,创建套接字时会在文件系统相应位置创建一个类型为套接字的文件。
- 两种类型的套接字:UNIX域套接字提供流式(SOCK_STREAM)和数据包式(SOCK_DGRAM)两种类型的套接字,且都是可靠的。
- 权限问题:绑定UNIX域套接字时,调用进程需要有对应目录部分的可写权限,并且默认情况下,创建的文件具有777的权限。
- 抽象路径名:Linux特有的特性,允许将UNIX域套接字绑定到一个名字上,而不会实际在文件系统中创建文件。
- 传递文件描述符:UNIX域套接字可以在同一台主机上的各进程之间传递文件描述符。
- 编程接口:虽然API调用方式与TCP/IP套接字类似,但在UNIX域套接字中,地址是以sockaddr_un结构体来表示的。
流式和数据包式的区别
特性 | 流式 SOCK_STREAM | 数据包式 SOCK_DGRAM |
---|---|---|
连接方式 | 面向连接(TCP协议) | 无连接(UDP协议) |
可靠性 | 高(错误检测和重传机制) | 低(无错误检测和重传) |
数据传输方式 | 字节流(可能分段) | 数据报文(独立单位) |
资源消耗 | 较大(维护连接状态) | 较小(无需维护连接) |
处理速度 | 较慢(保证可靠性) | 较快(不保证可靠性) |
适用场景 | 文件传输、远程登录等 | 音视频传输、广播消息等 |
网络环境适应性 | 适用于稳定网络环境 | 适用于局域网或实时性要求高的环境 |
UNIX流式套接字
示例代码
在Linux终端输入命令:man bind
滑到下面查看示例代码
复制示例代码并稍作修改
服务端
UNIX_sever.c
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>#define MY_SOCK_PATH "/tmp/my_sock_file" // 套接字文件地址
#define LISTEN_BACKLOG 50 // 监听队列长度#define handle_error(msg) \n do { perror(msg); exit(EXIT_FAILURE); } while (0) // 错误处理宏int main(int argc, char *argv[])
{int sfd, cfd; // 服务器套接字描述符和客户端套接字描述符struct sockaddr_un my_addr, peer_addr; // 地址结构体socklen_t peer_addr_size; // 客户端地址结构体大小char buf[BUFSIZ] = {}; // 缓冲区sfd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建套接字if (sfd == -1)handle_error("socket"); // 错误处理memset(&my_addr, 0, sizeof(struct sockaddr_un)); // 清空地址结构体my_addr.sun_family = AF_UNIX; // 设置地址类型为AF_UNIXstrncpy(my_addr.sun_path, MY_SOCK_PATH, sizeof(my_addr.sun_path) - 1); // 设置套接字文件路径if (bind(sfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_un)) == -1) // 绑定套接字handle_error("bind"); // 错误处理if (listen(sfd, LISTEN_BACKLOG) == -1) // 监听套接字handle_error("listen"); // 错误处理peer_addr_size = sizeof(struct sockaddr_un); // 获取客户端地址结构体大小cfd = accept(sfd, (struct sockaddr *) &peer_addr, &peer_addr_size); // 接受客户端连接if (cfd == -1)handle_error("accept"); // 错误处理recv(cfd, buf, BUFSIZ, 0); // 接收客户端发送的数据printf("buf = %s\n", buf); // 打印接收到的数据close(cfd); // 关闭客户端套接字close(sfd); // 关闭服务器套接字return 0;
}
- 编译:
gcc -o UNIX_sever UNIX_sever.c -Wall
- 运行:
./UNIX_sever
- 结束运行:ctrl+c
- 再次运行:
./UNIX_sever
报错,bind: Address already in use
- 删除套接字文件:
rm /tmp/my_sock_file
- 再次运行:
./UNIX_sever
,报错消失 - 原因:ctrl+c 强制结束没有执行
remove(MY_SOCK_PATH);
所以要手动删除
客户端
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>#define MY_SOCK_PATH "/tmp/my_sock_file"//套接字文件地址#define handle_error(msg) \n do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[])
{int fd; // 文件描述符struct sockaddr_un peer_addr; // UNIX域套接字地址结构体char buf[BUFSIZ] = {"Hello World"}; // 发送缓冲区fd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建套接字if (fd == -1)handle_error("socket"); // 错误处理memset(&peer_addr, 0, sizeof(struct sockaddr_un)); // 清空地址结构体peer_addr.sun_family = AF_UNIX; // 设置地址类型为AF_UNIXstrncpy(peer_addr.sun_path, MY_SOCK_PATH, sizeof(peer_addr.sun_path) - 1); // 设置套接字文件路径if (connect(fd, (struct sockaddr *) &peer_addr, sizeof(struct sockaddr_un)) == -1) // 连接服务器handle_error("connect"); // 错误处理send(fd, buf, strlen(buf), 0); // 发送数据close(fd); // 关闭套接字remove(MY_SOCK_PATH); // 删除套接字文件return 0;
}
UNIX数据包式套接字
服务端
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>#define MY_SOCK_PATH "/tmp/my_sock_file"
#define handle_error(msg) \n do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[])
{int fd;struct sockaddr_un my_addr, peer_addr;socklen_t peer_addr_size;char buf[BUFSIZ] = {};// 创建UDP套接字fd = socket(AF_UNIX, SOCK_DGRAM, 0);if (fd == -1)handle_error("socket");// 清空地址结构体并设置类型和路径memset(&my_addr, 0, sizeof(struct sockaddr_un));my_addr.sun_family = AF_UNIX;strncpy(my_addr.sun_path, MY_SOCK_PATH,sizeof(my_addr.sun_path) - 1);// 绑定套接字到指定路径if (bind(fd, (struct sockaddr *) &my_addr,sizeof(struct sockaddr_un)) == -1)handle_error("bind");// 接收数据并打印peer_addr_size = sizeof(struct sockaddr_un);recvfrom(fd, buf, BUFSIZ, 0, (struct sockaddr *) &peer_addr,&peer_addr_size);printf("%s\n",buf);// 关闭套接字并删除文件close(fd);remove(MY_SOCK_PATH);return 0;
}
客户端
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h> #define MY_SOCK_PATH "/tmp/my_sock_file" // 定义套接字文件路径
#define handle_error(msg) \n do { perror(msg); exit(EXIT_FAILURE); } while (0) // 定义错误处理宏int main(int argc, char *argv[])
{int fd; // 文件描述符struct sockaddr_un peer_addr; // UNIX域套接字地址结构体socklen_t peer_addr_size; // UNIX域套接字地址大小char buf[BUFSIZ] = {"Hello World!"}; // 发送缓冲区fd = socket(AF_UNIX, SOCK_DGRAM, 0); // 创建UDP套接字if (fd == -1)handle_error("socket"); memset(&peer_addr, 0, sizeof(struct sockaddr_un)); // 清空地址结构体peer_addr.sun_family = AF_UNIX; // 设置地址类型为AF_UNIXstrncpy(peer_addr.sun_path, MY_SOCK_PATH,sizeof(peer_addr.sun_path) - 1); // 设置套接字文件路径peer_addr_size = sizeof(struct sockaddr_un); // 获取地址大小printf("%s\n", buf); // 打印发送缓冲区内容sendto(fd, buf, strlen(buf), 0, (struct sockaddr *) &peer_addr,peer_addr_size); // 向服务器发送数据close(fd); // 关闭套接字remove(MY_SOCK_PATH); // 删除套接字文件return 0;
}
通信成功