Unix 域协议
Unix 域协议
unix域协议是在单个主机上执行客户端/服务器通信的一种方法,使用套接字API,可使用IPC的方法一种。
分为两类:字节流套接字(类似TCP)、数据报套接字(类似UDP)
- Unix域套接字比位于同一个主机的TCP套接字快,X Window System 就使用了Unix域协议
- 在不同进程间传递描述符
- 能把客户的凭证(用户ID和组ID)提供给服务器,从而提供额外的安全检查措施
- 使用普通文件系统的路径名作为标识,而非ip和端口。这些特殊文件除非和Unix域套接字关联,否则无法读写
// unix域套接字地址,可使用 bind getsockname 等函数操作
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* Path name. 为空时等价于 INADDR_ANY */
};
// Returns 0 on success, -1 for errors.
int socketpair(
int __domain, // AF_LOCAL
int __type, // SOCK_STREAM or SOCK_DGRAM
int __protocol, // 0
int __fds[2]); // 返回两个新创建的文件描述符,类似 pipe
// 但是此处的两个文件描述符都是全双工的,可以双向通信
- 应当捆绑绝对路径
- connect 指定的绝对路径应绑定某个打开的Unix域套接字,且套接字类型一致
- 调用 connect 连接的权限类似以只读权限 open 路径
- Unix域套接字的connect调用发现监听套接字的队列满后,调用立即返回一个 ECONNREFUSED 错误;TCP此时会忽略新到达的SYN,发起端将重试SYN
- 与TCP UDP会分配临时端口不同,给未绑定的套接字发信息不会自动绑定路径名
流式服务端
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
const char *UNIXSTR_PATH = "/tmp/unix_domain_socket";
int main(int argc, char const *argv[]) {
int server_sockfd, client_sockfd;
socklen_t client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;
// 删除并创建新套接字
if (unlink(UNIXSTR_PATH) < 0) {
perror("unlink");
}
server_sockfd = socket(AF_UNIX, // 等于AF_LOCAL
SOCK_STREAM, 0);
// 命名套接字
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, UNIXSTR_PATH);
// 创建文件权限为 0777 按umask修正后的值
if (bind(server_sockfd, (struct sockaddr *)&server_address,
sizeof(server_address)) < 0) {
perror("bind");
return -1;
}
// 建立连接队列
listen(server_sockfd, 5);
while (1) {
char ch;
printf("server waiting\n");
// 接受一个连接
client_len = sizeof(client_address);
client_sockfd =
accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
// 操作 socket
read(client_sockfd, &ch, 1);
printf("client_sockfd = %d send: %c \n", client_sockfd, ch);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
return 0;
}
流式客户端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
const char* UNIXSTR_PATH = "/tmp/unix_domain_socket";
int main(int argc, char const *argv[]) {
int ret{0};
int client_sockfd;
struct sockaddr_un server_address;
client_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&server_address, sizeof(server_address));
server_address.sun_family = AF_UNIX;
strncpy(server_address.sun_path, UNIXSTR_PATH,
sizeof(server_address.sun_path) - 1);
ret = connect(client_sockfd, (struct sockaddr *)&server_address,
sizeof(server_address));
if (ret < 0) {
perror("connect");
return -1;
}
char ch{'a'};
write(client_sockfd, &ch, 1);
read(client_sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(client_sockfd);
return 0;
}
数据报类型协议与UDP类似,但是必须显示bind一个路径名到套接字
描述符传递
父子进程可通过fork或exec命令传递描述符,而通过Unix域套接字使用 sendmsg
发送特殊消息,可以在任意进程间传递描述符
建立流式域套接字(数据报域套接字可能丢失数据,就像UDP),可以传输任意类型的描述符。发送进程构建一个 msfhdr
结构,其中 msfhdr 的 msg_control 成员用于发送描述符
调用sendmsg发送后,即使在对方接收前关闭了该描述符,此进程仍保持打开状态,发送描述符会使其引用计数加1
接收的描述符可能具有不同的描述符号,这是正常的,传输过程涉及在接收进程中创建一个新的描述符,新旧描述符指向内核中同一个文件表项